import functools import itertools import multiprocessing import operator import os from typing import get_args import pytest import ifcopenshell import ifcopenshell.api.context import ifcopenshell.api.owner.settings import ifcopenshell.api.project import ifcopenshell.api.root import ifcopenshell.api.unit import ifcopenshell.geom import ifcopenshell.ifcopenshell_wrapper as W import ifcopenshell.util.shape import test.bootstrap from ifcopenshell.util.shape_builder import ShapeBuilder fn = os.path.join(os.path.dirname(__file__), "fixtures/ColumnPSetsOfSets.ifc") class TestGeomSettings: def test_settings(self): settings = ifcopenshell.geom.settings() assert set(get_args(ifcopenshell.geom.SETTING)) == set( settings.setting_names() ), "Also need to update IfcPython.i, if new settings were added/removed." assert "use-python-opencascade" in settings.setting_names() assert settings.get(settings.USE_PYTHON_OPENCASCADE) is False assert settings.get("use-python-opencascade") is False assert "USE_PYTHON_OPENCASCADE = False" in repr(settings) # Testing both new and old ways of setting geometry settings. if ifcopenshell.geom.has_occ: settings.set("use-python-opencascade", True) settings.set(settings.USE_PYTHON_OPENCASCADE, True) assert settings.get(settings.USE_PYTHON_OPENCASCADE) is True assert "USE_PYTHON_OPENCASCADE = True" in repr(settings) else: with pytest.raises(AttributeError): settings.set("use-python-opencascade", True) with pytest.raises(AttributeError): settings.set(settings.USE_PYTHON_OPENCASCADE, True) assert "USE_PYTHON_OPENCASCADE = False" in repr(settings) def test_serializer_settings(self): settings = ifcopenshell.geom.serializer_settings() assert set(get_args(ifcopenshell.geom.SERIALIZER_SETTING)) == set( settings.setting_names() ), "Also need to update IfcPython.i, if new settings were added/removed." # Only for settings. assert "use-python-opencascade" not in settings.setting_names() with pytest.raises(AttributeError): settings.get(settings.USE_PYTHON_OPENCASCADE) with pytest.raises(RuntimeError): settings.get("use-python-opencascade") with pytest.raises(RuntimeError): settings.set("use-python-opencascade", True) assert "USE_PYTHON_OPENCASCADE" not in repr(settings) class TestTriangulationAttributes(test.bootstrap.IFC4): def test_faces_representation_item_ids(self): ifc_file = ifcopenshell.file() ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") builder = ShapeBuilder(ifc_file) extrusion = builder.extrude(builder.rectangle(), magnitude=1.0) representation = builder.get_representation(context, extrusion) settings = ifcopenshell.geom.settings() shape = ifcopenshell.geom.create_shape(settings, representation) faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) faces = ifcopenshell.util.shape.get_faces(shape) assert set(faces_item_ids) == {extrusion.id()} assert len(faces) == 12 # Cube has 12 tris. assert len(faces_item_ids) == len(faces) edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) edges = ifcopenshell.util.shape.get_edges(shape) assert set(edges_item_ids) == {extrusion.id()} assert len(edges) == 12 # Cube has 12 edges. assert len(edges_item_ids) == len(edges) def test_curve_representation_item_ids(self): ifc_file = ifcopenshell.file() ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") builder = ShapeBuilder(ifc_file) curve = builder.rectangle() representation = builder.get_representation(context, curve) settings = ifcopenshell.geom.settings() settings.set("dimensionality", W.CURVES_SURFACES_AND_SOLIDS) shape = ifcopenshell.geom.create_shape(settings, representation) faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) assert len(faces_item_ids) == 0 edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) edges = ifcopenshell.util.shape.get_edges(shape) assert set(edges_item_ids) == {curve.id()} assert len(edges) == 4 assert len(edges_item_ids) == len(edges) def test_mixed_representation_item_ids(self): ifc_file = ifcopenshell.file() ifcopenshell.api.root.create_entity(ifc_file, ifc_class="IfcProject", name="Test") context = ifcopenshell.api.context.add_context(ifc_file, context_type="Model") builder = ShapeBuilder(ifc_file) curve = builder.rectangle() fill = ifc_file.create_entity("IfcAnnotationFillArea", builder.rectangle()) representation = builder.get_representation(context, (curve, fill)) settings = ifcopenshell.geom.settings() settings.set("dimensionality", W.CURVES_SURFACES_AND_SOLIDS) shape = ifcopenshell.geom.create_shape(settings, representation) faces_item_ids = ifcopenshell.util.shape.get_faces_representation_item_ids(shape) faces = ifcopenshell.util.shape.get_faces(shape) assert len(faces) == 2 # Fill area will produce a triangulated face. assert set(faces_item_ids) == {fill.id()} assert len(faces_item_ids) == len(faces) edges_item_ids = ifcopenshell.util.shape.get_edges_representation_item_ids(shape) edges = ifcopenshell.util.shape.get_edges(shape) assert set(edges_item_ids) == {fill.id(), curve.id()} assert len(edges) == 8 # 4 edges rectangle curve + 4 edges fill area assert len(edges_item_ids) == len(edges) class TestAssignObject: def test_no_welding_on_distinct_items(self): self.file = ifcopenshell.api.project.create_file() ifcopenshell.api.owner.settings.get_user = lambda ifc: (ifc.by_type("IfcPersonAndOrganization") or [None])[0] ifcopenshell.api.owner.settings.get_application = lambda ifc: (ifc.by_type("IfcApplication") or [None])[0] ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcProject", name="Test") unit = ifcopenshell.api.unit.add_si_unit(self.file, unit_type="LENGTHUNIT") ifcopenshell.api.unit.assign_unit(self.file, units=[unit]) context = ifcopenshell.api.context.add_context(self.file, context_type="Model") element = ifcopenshell.api.root.create_entity(self.file, ifc_class="IfcWall") def create_extrusion(x, y): points = ( (x + 0.0, y + 0.0), (x + 0.0, y + 1.0), (x + 1.0, y + 1.0), (x + 1.0, y + 0.0), (x + 0.0, y + 0.0), ) curve = self.file.createIfcPolyline([self.file.createIfcCartesianPoint(p) for p in points]) extrusion_direction = self.file.createIfcDirection((0.0, 0.0, 1.0)) return self.file.createIfcExtrudedAreaSolid( self.file.createIfcArbitraryClosedProfileDef("AREA", None, curve), self.file.createIfcAxis2Placement3D( self.file.createIfcCartesianPoint((0.0, 0.0, 0.0)), ), extrusion_direction, 1.0, ) extrusions = [create_extrusion(x, 0.0) for x in [0.0, 1.0]] element.Representation = self.file.createIfcProductDefinitionShape( Representations=[ self.file.createIfcShapeRepresentation( context, context.ContextIdentifier, "SweptSolid", extrusions, ) ] ) obj = ifcopenshell.geom.create_shape(ifcopenshell.geom.settings(WELD_VERTICES=True), element) # item_ids is a per-triangle array, so we have 12 triangles per cube # even though not documented, the order in representation items should match assert obj.geometry.item_ids == (extrusions[0].id(),) * 12 + (extrusions[1].id(),) * 12 # group the vertices vs = [obj.geometry.verts[i : i + 3] for i in range(0, len(obj.geometry.verts), 3)] # welding should not happen between distinct items so the total number of verts should be 2 times 8 assert len(vs) == 16 # even though there are only 12 unique vertices as the cubes are touching assert len(set(vs)) == 12 def test_iterator(): # just test some permutations of invocation settings = ifcopenshell.geom.settings() file_or_filename = [fn, ifcopenshell.open(fn)] with_or_without_threads = [[], [multiprocessing.cpu_count()]] includes = [ {}, {"include": ["IfcColumn"]}, {"include": [file_or_filename[1].by_type("IfcColumn")[0]]}, ] for args in itertools.product(file_or_filename, with_or_without_threads, includes): kwargs = functools.reduce(operator.or_, (a for a in args if isinstance(a, dict))) pargs = [] for a in (_ for _ in args if not isinstance(_, dict)): if isinstance(a, list): pargs.extend(a) else: pargs.append(a) iterator = ifcopenshell.geom.iterator(settings, *pargs, **kwargs) assert iterator.initialize() def test_logging(): assert ifcopenshell.logger logger = ifcopenshell.logger() logger.OutputFormat(logger.FMT_INMEMORY) settings = ifcopenshell.geom.settings() f = ifcopenshell.open(fn) col = f.by_type("IfcColumn")[0] _ = ifcopenshell.geom.create_shape(settings, col, logger=logger) num_log_items = len(list(logger)) col.Representation.Representations[0].Items[0].MappingSource.MappedRepresentation.Items[0].Depth *= -1.0 with pytest.raises(RuntimeError): _ = ifcopenshell.geom.create_shape(settings, col, logger=logger) new_items = list(logger)[num_log_items:] assert ("GEO089", "Non-positive extrusion height encountered for:") in [ (msg.code, msg.message) for msg in new_items ] if __name__ == "__main__": import pytest pytest.main(["-vvsx", __file__])