From bc6189f44858bf09e84bec42ecbef9f832a8a818 Mon Sep 17 00:00:00 2001 From: gumyr Date: Thu, 10 Aug 2023 14:24:05 -0400 Subject: [PATCH] Adding unittests for Mesher class --- src/build123d/mesher.py | 53 +++++++------ tests/test_mesher.py | 168 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 199 insertions(+), 22 deletions(-) create mode 100644 tests/test_mesher.py diff --git a/src/build123d/mesher.py b/src/build123d/mesher.py index 6a85323..9442d7b 100644 --- a/src/build123d/mesher.py +++ b/src/build123d/mesher.py @@ -217,38 +217,42 @@ class Mesher: must_preserve=False, ) - def get_meta_data(self) -> list[str]: + def get_meta_data(self) -> list[dict]: """Retrieve all of the metadata""" meta_data_group = self.model.GetMetaDataGroup() meta_data_contents = [] for i in range(meta_data_group.GetMetaDataCount()): meta_data = meta_data_group.GetMetaData(i) - meta_data_contents.append(f"Name Space: {meta_data.GetNameSpace()}") - meta_data_contents.append(f"Name: {meta_data.GetName()}") - meta_data_contents.append(f"Type: {meta_data.GetType()}") - meta_data_contents.append(f"Value: {meta_data.GetValue()}") + meta_data_dict = {} + meta_data_dict["name_space"] = meta_data.GetNameSpace() + meta_data_dict["name"] = meta_data.GetName() + meta_data_dict["type"] = meta_data.GetType() + meta_data_dict["value"] = meta_data.GetValue() + meta_data_contents.append(meta_data_dict) return meta_data_contents - def get_meta_data_by_key(self, name_space: str, name: str) -> list[str]: + def get_meta_data_by_key(self, name_space: str, name: str) -> dict: """Retrive the metadata value and type for the provided name space and name""" meta_data_group = self.model.GetMetaDataGroup() - meta_data_contents = [] + meta_data_contents = {} meta_data = meta_data_group.GetMetaDataByKey(name_space, name) - meta_data_contents.append(f"Type: {meta_data.GetType()}") - meta_data_contents.append(f"Value: {meta_data.GetValue()}") + meta_data_contents["type"] = meta_data.GetType() + meta_data_contents["value"] = meta_data.GetValue() return meta_data_contents - def get_mesh_properties(self) -> list[str]: + def get_mesh_properties(self) -> list[dict]: """Retrieve the properties from all the meshes""" properties = [] for mesh in self.meshes: - properties += f"Name: {mesh.GetName()}" - properties += f"Part Number: {mesh.GetPartNumber()}" + property_dict = {} + property_dict["name"] = mesh.GetName() + property_dict["part_number"] = mesh.GetPartNumber() type_3mf = Lib3MF.ObjectType(mesh.GetType()) - properties += f"Type: {Mesher._map_3mf_to_b3d_mesh_type[type_3mf].name}" - uuid_valid, uuid_value = mesh.GetUUID() - if uuid_valid: - properties += f"UUID: {uuid_value}" + property_dict["type"] = Mesher._map_3mf_to_b3d_mesh_type[type_3mf].name + _uuid_valid, uuid_value = mesh.GetUUID() + property_dict["uuid"] = uuid_value # are bad values possible? + properties.append(property_dict) + return properties @staticmethod def _is_facet_forward( @@ -424,7 +428,10 @@ class Mesher: ocp_mesh_vertices, triangles, b3d_shape.center() ) - # Add the meta data + # Build the mesh + mesh_3mf.SetGeometry(vertices_3mf, triangles_3mf) + + # Add the mesh properties mesh_3mf.SetType(Mesher._map_b3d_mesh_type_3mf[mesh_type]) if b3d_shape.label: mesh_3mf.SetName(b3d_shape.label) @@ -435,9 +442,6 @@ class Mesher: # mesh_3mf.SetAttachmentAsThumbnail # mesh_3mf.SetPackagePart - # Create the mesh - mesh_3mf.SetGeometry(vertices_3mf, triangles_3mf) - # Add color self._add_color(b3d_shape, mesh_3mf) @@ -514,6 +518,7 @@ class Mesher: for mesh in self.meshes: shape = self._get_shape(mesh) shape.label = mesh.GetName() + # Extract color color_indices = [] for triangle_property in mesh.GetAllTriangleProperties(): color_indices.extend( @@ -523,9 +528,13 @@ class Mesher: ] ) unique_color_indices = list(set(color_indices)) + try: + color_group = self.model.GetColorGroupByID(unique_color_indices[0][0]) + except: + shapes.append(shape) # There are no colors + continue if len(unique_color_indices) > 1: - warnings.warn("Warning multiple colors found on mesh - one used") - color_group = self.model.GetColorGroupByID(unique_color_indices[0][0]) + warnings.warn("Warning multiple colors found on mesh - only one used") color_3mf = color_group.GetColor(unique_color_indices[0][1]) color = (color_3mf.Red, color_3mf.Green, color_3mf.Blue, color_3mf.Alpha) color = (c / 255.0 for c in color) diff --git a/tests/test_mesher.py b/tests/test_mesher.py new file mode 100644 index 0000000..47e3c41 --- /dev/null +++ b/tests/test_mesher.py @@ -0,0 +1,168 @@ +import os, unittest, uuid +from build123d.build_enums import MeshType, Unit +from build123d.topology import Compound, Solid +from build123d.geometry import Color, Vector, VectorLike, Location +from build123d.mesher import Mesher + + +class DirectApiTestCase(unittest.TestCase): + def assertTupleAlmostEquals( + self, + first: tuple[float, ...], + second: tuple[float, ...], + places: int, + msg: str = None, + ): + """Check Tuples""" + self.assertEqual(len(second), len(first)) + for i, j in zip(second, first): + self.assertAlmostEqual(i, j, places, msg=msg) + + def assertVectorAlmostEquals( + self, first: Vector, second: VectorLike, places: int, msg: str = None + ): + second_vector = Vector(second) + self.assertAlmostEqual(first.X, second_vector.X, places, msg=msg) + self.assertAlmostEqual(first.Y, second_vector.Y, places, msg=msg) + self.assertAlmostEqual(first.Z, second_vector.Z, places, msg=msg) + + +class TestProperties(unittest.TestCase): + def test_version(self): + exporter = Mesher() + self.assertEqual(exporter.library_version, "2.2.0") + + def test_units(self): + for unit in Unit: + exporter = Mesher(unit=unit) + exporter.add_shape(Solid.make_box(1, 1, 1)) + exporter.write("test.3mf") + importer = Mesher() + _shape = importer.read("test.3mf") + self.assertEqual(unit, importer.model_unit) + + def test_vertex_and_triangle_counts(self): + exporter = Mesher() + exporter.add_shape(Solid.make_box(1, 1, 1)) + self.assertEqual(exporter.vertex_counts[0], 8) + self.assertEqual(exporter.triangle_counts[0], 12) + + def test_mesh_counts(self): + exporter = Mesher() + exporter.add_shape(Solid.make_box(1, 1, 1)) + exporter.add_shape(Solid.make_cone(1, 0, 2)) + self.assertEqual(exporter.mesh_count, 2) + + +class TestMetaData(unittest.TestCase): + def test_add_meta_data(self): + exporter = Mesher() + exporter.add_shape(Solid.make_box(1, 1, 1)) + exporter.add_meta_data("test_space", "test0", "some data", "str", True) + exporter.add_meta_data("test_space", "test1", "more data", "str", True) + exporter.write("test.3mf") + importer = Mesher() + _shape = importer.read("test.3mf") + imported_meta_data: list[dict] = importer.get_meta_data() + self.assertEqual(imported_meta_data[0]["name_space"], "test_space") + self.assertEqual(imported_meta_data[0]["name"], "test0") + self.assertEqual(imported_meta_data[0]["value"], "some data") + self.assertEqual(imported_meta_data[0]["type"], "str") + self.assertEqual(imported_meta_data[1]["name_space"], "test_space") + self.assertEqual(imported_meta_data[1]["name"], "test1") + self.assertEqual(imported_meta_data[1]["value"], "more data") + self.assertEqual(imported_meta_data[1]["type"], "str") + + def test_add_code(self): + exporter = Mesher() + exporter.add_shape(Solid.make_box(1, 1, 1)) + exporter.add_code_to_metadata() + exporter.write("test.3mf") + importer = Mesher() + _shape = importer.read("test.3mf") + source_code = importer.get_meta_data_by_key("build123d", "test_mesher.py") + self.assertEqual(len(source_code), 2) + self.assertEqual(source_code["type"], "python") + self.assertGreater(len(source_code["value"]), 10) + + +class TestMeshProperties(unittest.TestCase): + def test_properties(self): + # Note: MeshType.OTHER can't be used with a Solid shape + for mesh_type in [MeshType.MODEL, MeshType.SUPPORT, MeshType.SOLIDSUPPORT]: + with self.subTest("MeshTYpe", mesh_type=mesh_type): + exporter = Mesher() + if mesh_type != MeshType.SUPPORT: + test_uuid = uuid.uuid1() + else: + test_uuid = None + name = "test" + mesh_type.name + shape = Solid.make_box(1, 1, 1) + shape.label = name + exporter.add_shape( + shape, + mesh_type=mesh_type, + part_number=str(mesh_type.value), + uuid_value=test_uuid, + ) + exporter.write("test.3mf") + importer = Mesher() + shape = importer.read("test.3mf") + self.assertEqual(shape[0].label, name) + self.assertEqual(importer.mesh_count, 1) + properties = importer.get_mesh_properties() + self.assertEqual(properties[0]["name"], name) + self.assertEqual(properties[0]["part_number"], str(mesh_type.value)) + self.assertEqual(properties[0]["type"], mesh_type.name) + if mesh_type != MeshType.SUPPORT: + self.assertEqual(properties[0]["uuid"], str(test_uuid)) + + +class TestAddShape(DirectApiTestCase): + def test_add_shape(self): + exporter = Mesher() + blue_shape = Solid.make_box(1, 1, 1) + blue_shape.color = Color("blue") + blue_shape.label = "blue" + red_shape = Solid.make_cone(1, 0, 2).locate(Location((0, -1, 0))) + red_shape.color = Color("red") + red_shape.label = "red" + exporter.add_shape([blue_shape, red_shape]) + exporter.write("test.3mf") + importer = Mesher() + box, cone = importer.read("test.3mf") + self.assertVectorAlmostEquals(box.bounding_box().size, (1, 1, 1), 2) + self.assertVectorAlmostEquals(box.bounding_box().size, (1, 1, 1), 2) + self.assertEqual(len(box.clean().faces()), 6) + self.assertEqual(box.label, "blue") + self.assertEqual(cone.label, "red") + self.assertTupleAlmostEquals(box.color.to_tuple(), (0, 0, 1, 1), 5) + self.assertTupleAlmostEquals(cone.color.to_tuple(), (1, 0, 0, 1), 5) + + def test_add_compound(self): + exporter = Mesher() + box = Solid.make_box(1, 1, 1) + cone = Solid.make_cone(1, 0, 2).locate(Location((0, -1, 0))) + shape_assembly = Compound.make_compound([box, cone]) + exporter.add_shape(shape_assembly) + exporter.write("test.3mf") + importer = Mesher() + shapes = importer.read("test.3mf") + self.assertEqual(importer.mesh_count, 2) + + +class TestErrorChecking(unittest.TestCase): + def test_read_invalid_file(self): + with self.assertRaises(ValueError): + importer = Mesher() + importer.read("unknown_file.type") + + def test_write_invalid_file(self): + exporter = Mesher() + exporter.add_shape(Solid.make_box(1, 1, 1)) + with self.assertRaises(ValueError): + exporter.write("unknown_file.type") + + +if __name__ == "__main__": + unittest.main()