BIM: Support for IfcOpenShell 0.8

This commit is contained in:
Yorik van Havre
2024-09-13 10:57:05 +02:00
committed by Chris Hennes
parent 16129930ab
commit 78a95759eb
7 changed files with 141 additions and 65 deletions

View File

@@ -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

View File

@@ -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():

View File

@@ -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)

View File

@@ -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)

View File

@@ -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")

View File

@@ -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 [

View File

@@ -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