From 5127df41646b2f0830b83bffae38814f4e592f26 Mon Sep 17 00:00:00 2001 From: andreiveselov <35749135+andreiveselov@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:18:36 -0400 Subject: [PATCH 1/2] Added new "--no_normals" option to not include normals in 3D tiles --- py3dtilers/Common/README.md | 18 ++++++++++++++++++ py3dtilers/Common/tiler.py | 8 +++++++- py3dtilers/Common/tileset_creation.py | 14 +++++++------- tests/test_geojsonTiler.py | 2 +- tests/test_ifcTiler.py | 2 +- tests/test_objTiler.py | 13 ++++++++++++- tests/test_tiler.py | 2 +- 7 files changed, 47 insertions(+), 12 deletions(-) diff --git a/py3dtilers/Common/README.md b/py3dtilers/Common/README.md index 4e3b0847..2bf14ba3 100644 --- a/py3dtilers/Common/README.md +++ b/py3dtilers/Common/README.md @@ -178,6 +178,24 @@ Project the features on another CRS. The `crs_in` flag allows to specify the inp --crs_in EPSG:3946 --crs_out EPSG:4171 ``` +### No Normals + +| Tiler | | +| ------------ | ------------------ | +| CityTiler | :heavy_check_mark: | +| ObjTiler | :heavy_check_mark: | +| GeojsonTiler | :heavy_check_mark: | +| IfcTiler | :heavy_check_mark: | +| TilesetTiler | :heavy_check_mark: | + +Optionally disable creation of normals in 3D tiles: + +```bash + --no_normals +``` + +This could be very useful for 3D tiles created out Photogrammetry OBJ meshes. If normals are not present, Cesium wil display tiles using flat lighning. + ### With texture | Tiler | | diff --git a/py3dtilers/Common/tiler.py b/py3dtilers/Common/tiler.py index 23b81906..ebd005b8 100644 --- a/py3dtilers/Common/tiler.py +++ b/py3dtilers/Common/tiler.py @@ -67,6 +67,11 @@ def __init__(self): dest='with_texture', action='store_true', help='Adds texture to 3DTiles when defined') + + self.parser.add_argument('--no_normals', + dest='no_normals', + action='store_true', + help='If specified, no normals will be written to glTf, useful for Photogrammetry meshes') self.parser.add_argument('--quality', nargs='?', @@ -244,11 +249,12 @@ def create_tileset_from_groups(self, groups: Groups, extension_name=None): """ create_loa = self.args.loa is not None geometric_errors = self.args.geometric_error if hasattr(self.args, 'geometric_error') else [None, None, None] + with_normals = False if self.args.no_normals else True tree = LodTree(groups, self.args.lod1, create_loa, self.args.with_texture, geometric_errors, self.args.texture_lods) self.create_output_directory() - return FromGeometryTreeToTileset.convert_to_tileset(tree, self.args, extension_name, self.get_output_dir()) + return FromGeometryTreeToTileset.convert_to_tileset(tree, self.args, extension_name, self.get_output_dir(), with_normals=with_normals) def create_output_directory(self): """ diff --git a/py3dtilers/Common/tileset_creation.py b/py3dtilers/Common/tileset_creation.py index e5632964..6081fcc4 100644 --- a/py3dtilers/Common/tileset_creation.py +++ b/py3dtilers/Common/tileset_creation.py @@ -19,7 +19,7 @@ class FromGeometryTreeToTileset(): nb_nodes = 0 @staticmethod - def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, extension_name=None, output_dir=None): + def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, extension_name=None, output_dir=None, with_normals=True): """ Recursively creates a tileset from the nodes of a GeometryTree :param geometry_tree: an instance of GeometryTree to transform into 3DTiles. @@ -39,7 +39,7 @@ def convert_to_tileset(geometry_tree: 'GeometryTree', user_arguments=None, exten root_node = geometry_tree.root_nodes[0] root_node.set_node_features_geometry(user_arguments) offset = FromGeometryTreeToTileset.__transform_node(root_node, user_arguments, tree_centroid, obj_writer=obj_writer) - tileset.add_tile(FromGeometryTreeToTileset.__create_tile(root_node, offset, extension_name, output_dir)) + tileset.add_tile(FromGeometryTreeToTileset.__create_tile(root_node, offset, extension_name, output_dir, with_normals)) geometry_tree.root_nodes.remove(root_node) if user_arguments.obj is not None: @@ -84,7 +84,7 @@ def __transform_node(node: 'GeometryNode', user_args, tree_centroid=np.array([0, return distance if user_args.offset[0] == 'centroid' else offset @staticmethod - def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, output_dir=None): + def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, output_dir=None, with_normals=True): """ Create a tile from a node. Recursively create tiles from the children of the node. :param node: the GeometryNode. @@ -98,7 +98,7 @@ def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, o tile = Tile() tile.set_geometric_error(node.geometric_error) - content_b3dm = FromGeometryTreeToTileset.__create_tile_content(feature_list, extension_name, node.has_texture(), node.downsample_factor) + content_b3dm = FromGeometryTreeToTileset.__create_tile_content(feature_list, extension_name, node.has_texture(), node.downsample_factor, with_normals) tile.set_content(content_b3dm) tile.set_content_uri('tiles/' + f'{FromGeometryTreeToTileset.tile_index}.b3dm') tile.write_content(output_dir) @@ -125,12 +125,12 @@ def __create_tile(node: 'GeometryNode', transform_offset, extension_name=None, o FromGeometryTreeToTileset.tile_index += 1 for child_node in node.child_nodes: - tile.add_child(FromGeometryTreeToTileset.__create_tile(child_node, [0., 0., 0.], extension_name, output_dir)) + tile.add_child(FromGeometryTreeToTileset.__create_tile(child_node, [0., 0., 0.], extension_name, output_dir, with_normals)) return tile @staticmethod - def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with_texture=False, downsample_factor=1): + def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with_texture=False, downsample_factor=1, with_normals=True): """ :param pre_tile: an array containing features of a single tile @@ -174,7 +174,7 @@ def __create_tile_content(feature_list: 'FeatureList', extension_name=None, with 0, 1, 0, 0, 0, 0, 0, 1]) - gltf = GlTF.from_binary_arrays(arrays, transform, materials=materials) + gltf = GlTF.from_binary_arrays(arrays, transform, materials=materials, withNormals=with_normals) # Create a batch table and add the ID of each feature to it ids = [feature.get_id() for feature in feature_list] diff --git a/tests/test_geojsonTiler.py b/tests/test_geojsonTiler.py index e6fca795..f2b55805 100644 --- a/tests/test_geojsonTiler.py +++ b/tests/test_geojsonTiler.py @@ -9,7 +9,7 @@ def get_default_namespace(): return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946', crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1, output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, - texture_lods=0, keep_ids=[], exclude_ids=[]) + texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False) class Test_Tile(unittest.TestCase): diff --git a/tests/test_ifcTiler.py b/tests/test_ifcTiler.py index a720eb0d..d204a2eb 100644 --- a/tests/test_ifcTiler.py +++ b/tests/test_ifcTiler.py @@ -8,7 +8,7 @@ def get_default_namespace(): return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946', crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, grouped_by='IfcTypeObject', scale=1, - output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, texture_lods=0, keep_ids=[], exclude_ids=[]) + output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False) class Test_Tile(unittest.TestCase): diff --git a/tests/test_objTiler.py b/tests/test_objTiler.py index bf68b3b2..7bcc01d5 100644 --- a/tests/test_objTiler.py +++ b/tests/test_objTiler.py @@ -9,7 +9,7 @@ def get_default_namespace(): return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946', crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1, output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, - texture_lods=0, keep_ids=[], exclude_ids=[]) + texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False) class Test_Tile(unittest.TestCase): @@ -24,6 +24,17 @@ def test_basic_case(self): if tileset is not None: tileset.write_as_json(Path(obj_tiler.args.output_dir)) + def test_basic_case_no_normals(self): + obj_tiler = ObjTiler() + obj_tiler.files = [Path('tests/obj_tiler_data/Cube/cube_1.obj'), Path('tests/obj_tiler_data/Cube/cube_2.obj')] + obj_tiler.args = get_default_namespace() + obj_tiler.args.output_dir = Path("tests/obj_tiler_data/generated_tilesets/basic_case_no_normals") + obj_tiler.args.no_normals = True + + tileset = obj_tiler.from_obj_directory() + if tileset is not None: + tileset.write_as_json(Path(obj_tiler.args.output_dir)) + def test_texture(self): obj_tiler = ObjTiler() obj_tiler.files = [Path('tests/obj_tiler_data/TexturedCube/cube.obj')] diff --git a/tests/test_tiler.py b/tests/test_tiler.py index 02d18236..d4073a07 100644 --- a/tests/test_tiler.py +++ b/tests/test_tiler.py @@ -12,7 +12,7 @@ def get_default_namespace(): return Namespace(obj=None, loa=None, lod1=False, crs_in='EPSG:3946', crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1, output_dir=None, geometric_error=[None, None, None], kd_tree_max=None, - texture_lods=0, keep_ids=[], exclude_ids=[]) + texture_lods=0, keep_ids=[], exclude_ids=[], no_normals=False) triangles = [[np.array([1843366, 5174473, 200]), From 7930694e439a5cb1284c37d98c8b36d6467b416e Mon Sep 17 00:00:00 2001 From: andreiveselov <35749135+andreiveselov@users.noreply.github.com> Date: Thu, 7 Sep 2023 15:42:58 -0400 Subject: [PATCH 2/2] Updated tests --- tests/test_cityTemporalTiler.py | 2 +- tests/test_cityTiler.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_cityTemporalTiler.py b/tests/test_cityTemporalTiler.py index 8aef178f..c4204ac9 100644 --- a/tests/test_cityTemporalTiler.py +++ b/tests/test_cityTemporalTiler.py @@ -22,7 +22,7 @@ def get_default_namespace(): crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1, output_dir=None, geometric_error=[None, None, None], split_surfaces=False, add_color=False, kd_tree_max=None, texture_lods=0, - keep_ids=[], exclude_ids=[]) + keep_ids=[], exclude_ids=[], no_normals=False) class Test_Tile(unittest.TestCase): diff --git a/tests/test_cityTiler.py b/tests/test_cityTiler.py index 75f00993..3699f5b7 100644 --- a/tests/test_cityTiler.py +++ b/tests/test_cityTiler.py @@ -18,7 +18,7 @@ def get_default_namespace(): crs_out='EPSG:3946', offset=[0, 0, 0], with_texture=False, scale=1, output_dir=None, geometric_error=[None, None, None], split_surfaces=False, add_color=False, kd_tree_max=None, ids=[], texture_lods=0, - keep_ids=[], exclude_ids=[]) + keep_ids=[], exclude_ids=[], no_normals=False) class Test_Tile(unittest.TestCase):