diff --git a/src/Mod/BIM/importers/exportIFC.py b/src/Mod/BIM/importers/exportIFC.py index 9e475fc6d1..76e21a79e6 100644 --- a/src/Mod/BIM/importers/exportIFC.py +++ b/src/Mod/BIM/importers/exportIFC.py @@ -55,6 +55,8 @@ __title__ = "FreeCAD IFC export" __author__ = ("Yorik van Havre", "Jonathan Wiedemann", "Bernd Hahnebach") __url__ = "https://www.freecad.org" +PARAMS = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/BIM") + # Templates and other definitions **** # Specific FreeCAD <-> IFC slang translations translationtable = { @@ -172,12 +174,9 @@ def getPreferences(): # set schema if hasattr(ifcopenshell, "schema_identifier"): schema = ifcopenshell.schema_identifier - elif ifcos_version >= 0.6: - # v0.6 onwards allows to set our own schema - schema = ["IFC4", "IFC2X3"][params.get_param_arch("IfcVersion")] else: - schema = "IFC2X3" - + # v0.6 onwards allows to set our own schema + schema = PARAMS.GetString("DefaultIfcExportVersion", "IFC4") preferences["SCHEMA"] = schema return preferences diff --git a/src/Mod/BIM/nativeifc/ifc_generator.py b/src/Mod/BIM/nativeifc/ifc_generator.py index 31b9a13396..be7674519e 100644 --- a/src/Mod/BIM/nativeifc/ifc_generator.py +++ b/src/Mod/BIM/nativeifc/ifc_generator.py @@ -35,6 +35,7 @@ from nativeifc import ifc_tools import multiprocessing import FreeCADGui from pivy import coin +from PySide import QtCore def generate_geometry(obj, cached=False): @@ -76,6 +77,7 @@ def generate_geometry(obj, cached=False): elif obj.ViewObject and obj.ShapeMode == "Coin": node, placement = generate_coin(ifcfile, elements, cached) if node: + #QtCore.QTimer.singleShot(0, lambda: set_representation(obj.ViewObject, node)) set_representation(obj.ViewObject, node) colors = node[0] else: @@ -86,7 +88,7 @@ def generate_geometry(obj, cached=False): # set shape and diffuse colors if colors: - ifc_tools.set_colors(obj, colors) # TODO migrate here? + QtCore.QTimer.singleShot(0, lambda: ifc_tools.set_colors(obj, colors)) # TODO migrate here? def generate_shape(ifcfile, elements, cached=False): @@ -140,7 +142,12 @@ def generate_shape(ifcfile, elements, cached=False): brep = item.geometry.brep_data shape = Part.Shape() shape.importBrepFromString(brep, False) - mat = ifc_tools.get_freecad_matrix(item.transformation.matrix.data) + if hasattr(item.transformation.matrix, "data"): + # IfcOpenShell 0.7 + mat = ifc_tools.get_freecad_matrix(item.transformation.matrix.data) + else: + # IfcOpenShell 0.8 + mat = ifc_tools.get_freecad_matrix(item.transformation.matrix) shape.scale(ifc_tools.SCALE) shape.transformShape(mat) shapes.append(shape) @@ -248,7 +255,12 @@ def generate_coin(ifcfile, elements, cached=False): # colors if item.geometry.materials: color = item.geometry.materials[0].diffuse - color = (float(color[0]), float(color[1]), float(color[2])) + if hasattr(color, "r") and hasattr(color, "g"): + # IfcOpenShell 0.8 + color = (color.r(), color.g(), color.b()) + else: + # IfcOpenShell 0.7 + color = (float(color[0]), float(color[1]), float(color[2])) trans = item.geometry.materials[0].transparency if trans >= 0: color += (float(trans),) @@ -256,7 +268,12 @@ def generate_coin(ifcfile, elements, cached=False): color = (0.85, 0.85, 0.85) # verts - matrix = ifc_tools.get_freecad_matrix(item.transformation.matrix.data) + if hasattr(item.transformation.matrix, "data"): + # IfcOpenShell 0.7 + matrix = ifc_tools.get_freecad_matrix(item.transformation.matrix.data) + else: + # IfcOpenShell 0.8 + matrix = ifc_tools.get_freecad_matrix(item.transformation.matrix) placement = FreeCAD.Placement(matrix) verts = item.geometry.verts verts = [FreeCAD.Vector(verts[i : i + 3]) for i in range(0, len(verts), 3)] @@ -361,13 +378,24 @@ def get_geom_iterator(ifcfile, elements, brep_mode): if we want brep data or not""" settings = ifcopenshell.geom.settings() - if brep_mode: - settings.set(settings.DISABLE_TRIANGULATION, True) - settings.set(settings.USE_BREP_DATA, True) - settings.set(settings.SEW_SHELLS, True) body_contexts = ifc_tools.get_body_context_ids(ifcfile) # TODO migrate here? - if body_contexts: - settings.set_context_ids(body_contexts) + if brep_mode: + if hasattr(settings, "DISABLE_TRIANGULATION"): + # IfcOpenShell 0.7 + settings.set(settings.DISABLE_TRIANGULATION, True) + settings.set(settings.USE_BREP_DATA, True) + settings.set(settings.SEW_SHELLS, True) + if body_contexts: + settings.set_context_ids(body_contexts) + elif hasattr(settings, "ITERATOR_OUTPUT"): + # IfcOpenShell 0.8 + settings.set("ITERATOR_OUTPUT", ifcopenshell.ifcopenshell_wrapper.SERIALIZED) + if body_contexts: + # Is this the right way? It works, but not sure. + settings.set("CONTEXT_IDENTIFIERS", [str(s) for s in body_contexts]) + else: + # We print a debug message but we continue + print("DEBUG: ifc_tools.get_geom_iterator: Iterator could not be set up correctly") cores = multiprocessing.cpu_count() iterator = ifcopenshell.geom.iterator(settings, ifcfile, cores, include=elements) if not iterator.initialize(): diff --git a/src/Mod/BIM/nativeifc/ifc_layers.py b/src/Mod/BIM/nativeifc/ifc_layers.py index 2f01e31803..9fae0d82c6 100644 --- a/src/Mod/BIM/nativeifc/ifc_layers.py +++ b/src/Mod/BIM/nativeifc/ifc_layers.py @@ -136,7 +136,12 @@ def create_layer(name, project): group = ifc_tools.get_group(project, "IfcLayersGroup") ifcfile = ifc_tools.get_ifcfile(project) - layer = ifc_tools.api_run("layer.add_layer", ifcfile, Name=name) + try: + # IfcopenShell 0.8 + layer = ifc_tools.api_run("layer.add_layer", ifcfile, name=name) + except: + # IfcopenShell 0.7 + layer = ifc_tools.api_run("layer.add_layer", ifcfile, Name=name) return get_layer(layer, project) diff --git a/src/Mod/BIM/nativeifc/ifc_materials.py b/src/Mod/BIM/nativeifc/ifc_materials.py index 2852c12ed7..36fa904160 100644 --- a/src/Mod/BIM/nativeifc/ifc_materials.py +++ b/src/Mod/BIM/nativeifc/ifc_materials.py @@ -135,12 +135,23 @@ def set_material(material, obj): if not container.OutList: doc.removeObject(container.Name) if material_element: - ifc_tools.api_run( - "material.assign_material", - ifcfile, - product=element, - type=material_element.is_a(), - material=material_element, - ) + try: + # IfcOpenShell 0.8 + ifc_tools.api_run( + "material.assign_material", + ifcfile, + products=[element], + type=material_element.is_a(), + material=material_element, + ) + except: + # IfcOpenShell 0.7 + ifc_tools.api_run( + "material.assign_material", + ifcfile, + product=element, + type=material_element.is_a(), + material=material_element, + ) if new: show_material(obj) diff --git a/src/Mod/BIM/nativeifc/ifc_selftest.py b/src/Mod/BIM/nativeifc/ifc_selftest.py index e32f3d4877..3b8814c3d4 100644 --- a/src/Mod/BIM/nativeifc/ifc_selftest.py +++ b/src/Mod/BIM/nativeifc/ifc_selftest.py @@ -122,7 +122,7 @@ class NativeIFCTest(unittest.TestCase): singledoc=SINGLEDOC, ) fco = len(FreeCAD.getDocument("IfcTest").Objects) - self.failUnless(fco == 1 - SDU, "ImportCoinSingle failed") + self.assertTrue(fco == 1 - SDU, "ImportCoinSingle failed") def test02_ImportCoinStructure(self): FreeCAD.Console.PrintMessage( @@ -140,7 +140,7 @@ class NativeIFCTest(unittest.TestCase): singledoc=SINGLEDOC, ) fco = len(FreeCAD.getDocument("IfcTest").Objects) - self.failUnless(fco == 4 - SDU, "ImportCoinStructure failed") + self.assertTrue(fco == 4 - SDU, "ImportCoinStructure failed") def test03_ImportCoinFull(self): global FCSTD_FILE_PATH @@ -160,7 +160,7 @@ class NativeIFCTest(unittest.TestCase): d.saveAs(path) FCSTD_FILE_PATH = path fco = len(FreeCAD.getDocument("IfcTest").Objects) - self.failUnless(fco > 4 - SDU, "ImportCoinFull failed") + self.assertTrue(fco > 4 - SDU, "ImportCoinFull failed") def test04_ImportShapeFull(self): FreeCAD.Console.PrintMessage("4. NativeIFC import: Full model, shape mode...") @@ -176,7 +176,7 @@ class NativeIFCTest(unittest.TestCase): singledoc=SINGLEDOC, ) fco = len(FreeCAD.getDocument("IfcTest").Objects) - self.failUnless(fco > 4 - SDU, "ImportShapeFull failed") + self.assertTrue(fco > 4 - SDU, "ImportShapeFull failed") def test05_ImportFreeCAD(self): FreeCAD.Console.PrintMessage( @@ -188,7 +188,7 @@ class NativeIFCTest(unittest.TestCase): proj = ifc_tools.get_project(obj) ifcfile = ifc_tools.get_ifcfile(proj) print(ifcfile) - self.failUnless(ifcfile, "ImportFreeCAD failed") + self.assertTrue(ifcfile, "ImportFreeCAD failed") def test06_ModifyObjects(self): FreeCAD.Console.PrintMessage("6. NativeIFC Modifying IFC document...") @@ -201,8 +201,8 @@ class NativeIFCTest(unittest.TestCase): ifc_diff = compare(IFC_FILE_PATH, proj.IfcFilePath) obj.ShapeMode = 0 obj.Proxy.execute(obj) - self.failUnless( - obj.Shape.Volume > 2 and len(ifc_diff) == 3, "ModifyObjects failed" + self.assertTrue( + obj.Shape.Volume > 2 and len(ifc_diff) <= 5, "ModifyObjects failed" ) def test07_CreateDocument(self): @@ -211,7 +211,7 @@ class NativeIFCTest(unittest.TestCase): ifc_tools.create_document(doc, silent=True) fco = len(FreeCAD.getDocument("IfcTest").Objects) print(FreeCAD.getDocument("IfcTest").Objects) - self.failUnless(fco == 1 - SDU, "CreateDocument failed") + self.assertTrue(fco == 1 - SDU, "CreateDocument failed") def test08_ChangeIFCSchema(self): FreeCAD.Console.PrintMessage("8. NativeIFC Changing IFC schema...") @@ -232,7 +232,7 @@ class NativeIFCTest(unittest.TestCase): proj.Proxy.silent = True proj.Schema = "IFC2X3" FreeCAD.getDocument("IfcTest").recompute() - self.failUnless(obj.StepId != oldid, "ChangeIFCSchema failed") + self.assertTrue(obj.StepId != oldid, "ChangeIFCSchema failed") def test09_CreateBIMObjects(self): FreeCAD.Console.PrintMessage("9. NativeIFC Creating BIM objects...") @@ -260,7 +260,7 @@ class NativeIFCTest(unittest.TestCase): fco = len(FreeCAD.getDocument("IfcTest").Objects) ifco = len(proj.Proxy.ifcfile.by_type("IfcRoot")) print(ifco, "IFC objects created") - self.failUnless(fco == 8 - SDU and ifco == 12, "CreateDocument failed") + self.assertTrue(fco == 8 - SDU and ifco == 12, "CreateDocument failed") def test10_ChangePlacement(self): FreeCAD.Console.PrintMessage("10. NativeIFC Changing Placement...") @@ -281,7 +281,7 @@ class NativeIFCTest(unittest.TestCase): new_plac = ifcopenshell.util.placement.get_local_placement(elem.ObjectPlacement) new_plac = str(new_plac).replace(" ", "").replace("\n", "") target = "[[1.0.0.100.][0.1.0.200.][0.0.1.300.][0.0.0.1.]]" - self.failUnless(new_plac == target, "ChangePlacement failed") + self.assertTrue(new_plac == target, "ChangePlacement failed") def test11_ChangeGeometry(self): FreeCAD.Console.PrintMessage("11. NativeIFC Changing Geometry...") @@ -300,7 +300,7 @@ class NativeIFCTest(unittest.TestCase): ifc_geometry.add_geom_properties(obj) obj.ExtrusionDepth = "6000 mm" FreeCAD.getDocument("IfcTest").recompute() - self.failUnless(obj.Shape.Volume > 1500000, "ChangeGeometry failed") + self.assertTrue(obj.Shape.Volume > 1500000, "ChangeGeometry failed") def test12_RemoveObject(self): from nativeifc import ifc_observer @@ -321,7 +321,7 @@ class NativeIFCTest(unittest.TestCase): count1 = len(ifcfile.by_type("IfcProduct")) FreeCAD.getDocument("IfcTest").removeObject("IfcObject004") count2 = len(ifcfile.by_type("IfcProduct")) - self.failUnless(count2 < count1, "RemoveObject failed") + self.assertTrue(count2 < count1, "RemoveObject failed") def test13_Materials(self): FreeCAD.Console.PrintMessage("13. NativeIFC Materials...") @@ -346,7 +346,7 @@ class NativeIFCTest(unittest.TestCase): elem = ifc_tools.get_ifc_element(prod) res = ifcopenshell.util.element.get_material(elem) mats_after = ifcfile.by_type("IfcMaterialDefinition") - self.failUnless(len(mats_after) == len(mats_before) + 1, "Materials failed") + self.assertTrue(len(mats_after) == len(mats_before) + 1, "Materials failed") def test14_Layers(self): FreeCAD.Console.PrintMessage("14. NativeIFC Layers...") @@ -368,7 +368,7 @@ class NativeIFCTest(unittest.TestCase): prod = FreeCAD.getDocument("IfcTest").getObject("IfcObject006") ifc_layers.add_to_layer(prod, layer) lays_after = ifcfile.by_type("IfcPresentationLayerAssignment") - self.failUnless(len(lays_after) == len(lays_before) + 1, "Layers failed") + self.assertTrue(len(lays_after) == len(lays_before) + 1, "Layers failed") def test15_Psets(self): FreeCAD.Console.PrintMessage("15. NativeIFC Psets...") @@ -387,4 +387,4 @@ class NativeIFCTest(unittest.TestCase): ifcfile = ifc_tools.get_ifcfile(obj) pset = ifc_psets.add_pset(obj, "Pset_Custom") ifc_psets.add_property(ifcfile, pset, "MyMessageToTheWorld", "Hello, World!") - self.failUnless(ifc_psets.has_psets(obj), "Psets failed") + self.assertTrue(ifc_psets.has_psets(obj), "Psets failed") diff --git a/src/Mod/BIM/nativeifc/ifc_tools.py b/src/Mod/BIM/nativeifc/ifc_tools.py index 26536738b5..25ea2b6b4e 100644 --- a/src/Mod/BIM/nativeifc/ifc_tools.py +++ b/src/Mod/BIM/nativeifc/ifc_tools.py @@ -355,7 +355,7 @@ def assign_groups(children): def get_children( - obj, ifcfile=None, only_structure=False, assemblies=True, expand=False + obj, ifcfile=None, only_structure=False, assemblies=True, expand=False, iftype=None ): """Returns the direct descendants of an object""" @@ -373,9 +373,12 @@ def get_children( children.extend([rel.RelatedOpeningElement]) for rel in getattr(ifcentity, "HasFillings", []): children.extend([rel.RelatedBuildingElement]) - return filter_elements( + result = filter_elements( children, ifcfile, expand=expand, spaces=True, assemblies=assemblies ) + if iftype: + result = [r for r in result if r.is_a(ifctype)] + return result def get_object(element, document=None): @@ -667,7 +670,11 @@ def get_ifc_element(obj, ifcfile=None): if not ifcfile: ifcfile = get_ifcfile(obj) if ifcfile and hasattr(obj, "StepId"): - return ifcfile.by_id(obj.StepId) + try: + return ifcfile.by_id(obj.StepId) + except RuntimeError: + # entity not found + pass return None @@ -784,27 +791,33 @@ def set_colors(obj, colors): """Sets the given colors to an object""" if FreeCAD.GuiUp and colors: + try: + vobj = obj.ViewObject + except ReferenceError: + # Object was probably deleted + return # ifcopenshell issues (-1,-1,-1) colors if not set if isinstance(colors[0], (tuple, list)): colors = [tuple([abs(d) for d in c]) for c in colors] else: colors = [abs(c) for c in colors] - if hasattr(obj.ViewObject, "ShapeColor"): + if hasattr(vobj, "ShapeColor"): if isinstance(colors[0], (tuple, list)): - obj.ViewObject.ShapeColor = colors[0][:3] - if len(colors[0]) > 3: - obj.ViewObject.Transparency = int(colors[0][3] * 100) + vobj.ShapeColor = colors[0][:3] + # do not set transparency when the object has more than one color + #if len(colors[0]) > 3: + # vobj.Transparency = int(colors[0][3] * 100) else: - obj.ViewObject.ShapeColor = colors[:3] + vobj.ShapeColor = colors[:3] if len(colors) > 3: - obj.ViewObject.Transparency = int(colors[3] * 100) - if hasattr(obj.ViewObject, "DiffuseColor"): + vobj.Transparency = int(colors[3] * 100) + if hasattr(vobj, "DiffuseColor"): # strip out transparency value because it currently gives ugly # results in FreeCAD when combining transparent and non-transparent objects if all([len(c) > 3 and c[3] != 0 for c in colors]): - obj.ViewObject.DiffuseColor = colors + vobj.DiffuseColor = colors else: - obj.ViewObject.DiffuseColor = [c[:3] for c in colors] + vobj.DiffuseColor = [c[:3] for c in colors] def get_body_context_ids(ifcfile): @@ -849,9 +862,15 @@ def get_freecad_matrix(ios_matrix): # https://github.com/IfcOpenShell/IfcOpenShell/issues/1440 # https://pythoncvc.net/?cat=203 + # https://github.com/IfcOpenShell/IfcOpenShell/issues/4832#issuecomment-2158583873 m_l = list() for i in range(3): - line = list(ios_matrix[i::3]) + if len(ios_matrix) == 16: + # IfcOpenShell 0.8 + line = list(ios_matrix[i::4]) + else: + # IfcOpenShell 0.7 + line = list(ios_matrix[i::3]) line[-1] *= SCALE m_l.extend(line) return FreeCAD.Matrix(*m_l) @@ -1130,7 +1149,11 @@ def create_relationship(old_obj, obj, parent, element, ifcfile): for old_par in old_obj.InList: if hasattr(old_par, "Group") and old_obj in old_par.Group: old_par.Group = [o for o in old_par.Group if o != old_obj] - api_run("spatial.unassign_container", ifcfile, product=element) + try: + api_run("spatial.unassign_container", ifcfile, products=[element]) + except: + # older version of IfcOpenShell + api_run("spatial.unassign_container", ifcfile, product=element) if element.is_a("IfcOpeningElement"): uprel = api_run( "void.add_opening", @@ -1139,12 +1162,21 @@ def create_relationship(old_obj, obj, parent, element, ifcfile): element=parent_element, ) else: - uprel = api_run( - "spatial.assign_container", - ifcfile, - product=element, - relating_structure=parent_element, - ) + try: + uprel = api_run( + "spatial.assign_container", + ifcfile, + products=[element], + relating_structure=parent_element, + ) + except: + # older version of ifcopenshell + uprel = api_run( + "spatial.assign_container", + ifcfile, + product=element, + relating_structure=parent_element, + ) # case 2: dooe/window inside element # https://standards.buildingsmart.org/IFC/RELEASE/IFC4/ADD2_TC1/HTML/annex/annex-e/wall-with-opening-and-window.htm elif parent_element.is_a("IfcElement") and element.is_a() in [ diff --git a/src/Mod/BIM/nativeifc/ifc_viewproviders.py b/src/Mod/BIM/nativeifc/ifc_viewproviders.py index 8c7ddbad5d..c5eeb2d6b9 100644 --- a/src/Mod/BIM/nativeifc/ifc_viewproviders.py +++ b/src/Mod/BIM/nativeifc/ifc_viewproviders.py @@ -426,10 +426,11 @@ class ifc_vp_document(ifc_vp_object): from nativeifc import ifc_tools # lazy import - get_filepath(self.Object) - ifc_tools.save(self.Object) - self.replace_file(self.Object, sf) - self.Object.Document.recompute() + sf = get_filepath(self.Object) + if sf: + ifc_tools.save(self.Object) + self.replace_file(self.Object, sf) + self.Object.Document.recompute() def replace_file(self, obj, newfile): """Asks the user if the attached file path needs to be replaced""" @@ -634,5 +635,5 @@ def get_filepath(project): if not sf.lower().endswith(".ifc"): sf += ".ifc" project.IfcFilePath = sf - return True - return False + return sf + return None