BIM: Quantities support for nativeIFC objects (#18689)

* BIM: Quantities support for nativeIFC objects

* BIM: Added nativeIFC support for schedules
This commit is contained in:
Yorik van Havre
2025-01-06 17:55:50 +01:00
committed by GitHub
parent fccb75e364
commit f7a39fc313
19 changed files with 1407 additions and 80221 deletions

View File

@@ -187,6 +187,8 @@ def get_object_type(ifcentity, objecttype=None):
objecttype = "text"
elif ifcentity.is_a("IfcGridAxis"):
objecttype = "axis"
elif ifcentity.is_a("IfcControl"):
objecttype = "schedule"
return objecttype

View File

@@ -84,6 +84,8 @@ class ifc_object:
obj.ViewObject.signalChangeIcon()
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Geometry":
self.edit_geometry(obj, prop)
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) == "Quantities":
self.edit_quantity(obj, prop)
elif hasattr(obj, prop) and obj.getGroupOfProperty(prop) not in NON_PSETS:
# Treat all property groups outside the default ones as Psets
# print("DEBUG: editinog pset prop",prop)
@@ -353,6 +355,11 @@ class ifc_object:
# Not doing anything right now because an unset Type property could screw the ifc file
pass
def edit_quantity(self, obj, prop):
"""Edits the given quantity"""
pass # TODO implement
def get_section_data(self, obj):
"""Returns two things: a list of objects and a cut plane"""

View File

@@ -24,7 +24,6 @@
import os
import FreeCAD
params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/NativeIFC")
@@ -197,8 +196,10 @@ class ifc_observer:
return
del self.docname
del self.objname
if obj.isDerivedFrom("Part::Feature") or "IfcType" in obj.PropertiesList:
FreeCAD.Console.PrintLog("Converting" + obj.Label + "to IFC\n")
if obj.isDerivedFrom("Part::Feature") \
or "IfcType" in obj.PropertiesList \
or "CreateSpreadsheet" in obj.PropertiesList:
FreeCAD.Console.PrintLog("Converting " + obj.Label + " to IFC\n")
from nativeifc import ifc_geometry # lazy loading
from nativeifc import ifc_tools # lazy loading

View File

@@ -105,34 +105,35 @@ def show_psets(obj):
ttip = (
ptype + ":" + oname
) # setting IfcType:PropName as a tooltip to desambiguate
while pname in obj.PropertiesList:
#while pname in obj.PropertiesList:
# print("DEBUG: property", pname, "(", value, ") already exists in", obj.Label)
pname += "_"
# pname += "_"
ftype = None
if ptype in [
"IfcPositiveLengthMeasure",
"IfcLengthMeasure",
"IfcNonNegativeLengthMeasure",
]:
obj.addProperty("App::PropertyDistance", pname, gname, ttip)
ftype = "App::PropertyDistance"
elif ptype in ["IfcVolumeMeasure"]:
obj.addProperty("App::PropertyVolume", pname, gname, ttip)
ftype = "App::PropertyVolume"
elif ptype in ["IfcPositivePlaneAngleMeasure", "IfcPlaneAngleMeasure"]:
obj.addProperty("App::PropertyAngle", pname, gname, ttip)
ftype = "App::PropertyAngle"
value = float(value)
while value > 360:
value = value - 360
elif ptype in ["IfcMassMeasure"]:
obj.addProperty("App::PropertyMass", pname, gname, ttip)
ftype = "App::PropertyMass"
elif ptype in ["IfcAreaMeasure"]:
obj.addProperty("App::PropertyArea", pname, gname, ttip)
ftype = "App::PropertyArea"
elif ptype in ["IfcCountMeasure", "IfcInteger"]:
obj.addProperty("App::PropertyInteger", pname, gname, ttip)
ftype = "App::PropertyInteger"
value = int(value.strip("."))
elif ptype in ["IfcReal"]:
obj.addProperty("App::PropertyFloat", pname, gname, ttip)
ftype = "App::PropertyFloat"
value = float(value)
elif ptype in ["IfcBoolean", "IfcLogical"]:
obj.addProperty("App::PropertyBool", pname, gname, ttip)
ftype = "App::PropertyBool"
if value in [".T."]:
value = True
else:
@@ -144,14 +145,30 @@ def show_psets(obj):
"IfcDuration",
"IfcTimeStamp",
]:
obj.addProperty("App::PropertyTime", pname, gname, ttip)
ftype = "App::PropertyTime"
elif isinstance(value, str) and "::" in value:
# FreeCAD-specific: split strings by :: delimiter
ftype = "App::PropertyStringList"
value = value.split("::")
else:
obj.addProperty("App::PropertyString", pname, gname, ttip)
ftype = "App::PropertyString"
# print("DEBUG: setting",pname, ptype, value)
setattr(obj, pname, value)
if ftype:
if pname in obj.PropertiesList \
and obj.getGroupOfProperty(pname) == gname:
if obj.getTypeOfProperty(pname) == ftype:
pass
if ftype == "App::PropertyString" \
and obj.getTypeOfProperty(pname) == "App::PropertyStringList":
value = [value]
else:
print(pname, gname, obj.PropertiesList)
obj.addProperty(ftype, pname, gname, ttip)
if pname in obj.PropertiesList:
setattr(obj, pname, value)
def edit_pset(obj, prop, value=None, force=False):
def edit_pset(obj, prop, value=None, force=False, ifcfile=None, element=None):
"""Edits the corresponding property. If force is True,
the property is created even if it has no value"""
@@ -159,8 +176,14 @@ def edit_pset(obj, prop, value=None, force=False):
ptype = obj.getDocumentationOfProperty(prop)
if value is None:
value = getattr(obj, prop)
ifcfile = ifc_tools.get_ifcfile(obj)
element = ifc_tools.get_ifc_element(obj)
if not ifcfile:
ifcfile = ifc_tools.get_ifcfile(obj)
if not ifcfile:
return
if not element:
element = ifc_tools.get_ifc_element(obj)
if not element:
return
pset_exist = get_psets(element)
target_prop = None
value_exist = None
@@ -242,7 +265,7 @@ def edit_pset(obj, prop, value=None, force=False):
"IFC: property changed for "
+ obj.Label
+ " ("
+ str(obj.StepId)
+ str(element.id())
+ "): "
+ str(target_prop)
+ ": "
@@ -356,3 +379,7 @@ def remove_property(obj, prop):
# delete the pset too
FreeCAD.Console.PrintMessage(translate("BIM","Removing property set")+": "+psetname)
ifc_tools.api_run("pset.remove_pset", ifcfile, product=element, pset=pset)
# Quantity types
# https://ifc43-docs.standards.buildingsmart.org/IFC/RELEASE/IFC4x3/HTML/ifcsharedbldgelements/content.html#6.1.5-Quantity-Sets

View File

@@ -46,6 +46,7 @@ from nativeifc import ifc_import
from nativeifc import ifc_layers
from nativeifc import ifc_status
from nativeifc import ifc_export
from nativeifc import ifc_psets
from draftviewproviders import view_layer
from PySide import QtCore
@@ -443,7 +444,7 @@ def get_ifcfile(obj):
if getattr(project, "Proxy", None):
if hasattr(project.Proxy, "ifcfile"):
return project.Proxy.ifcfile
if project.IfcFilePath:
if getattr(project, "IfcFilePath", None):
ifcfile = ifcopenshell.open(project.IfcFilePath)
if hasattr(project, "Proxy"):
if project.Proxy is None:
@@ -453,7 +454,7 @@ def get_ifcfile(obj):
project.Proxy.ifcfile = ifcfile
return ifcfile
else:
FreeCAD.Console.PrintError("Error: No IFC file attached to this project")
FreeCAD.Console.PrintError("Error: No IFC file attached to this project: "+project.Label)
return None
@@ -508,11 +509,14 @@ def add_object(document, otype=None, oname="IfcObject"):
'dimension',
'sectionplane',
'axis',
'schedule'
or anything else for a standard IFC object"""
if not document:
return None
if otype == "sectionplane":
if otype == "schedule":
obj = Arch.makeSchedule()
elif otype == "sectionplane":
obj = Arch.makeSectionPlane()
obj.Proxy = ifc_objects.ifc_object(otype)
elif otype == "axis":
@@ -746,6 +750,8 @@ def add_properties(
obj.addProperty("App::PropertyStringList", "Text", "Base")
obj.Text = [text.Literal]
obj.Placement = ifc_export.get_placement(ifcentity.ObjectPlacement, ifcfile)
elif ifcentity.is_a("IfcControl"):
ifc_psets.show_psets(obj)
# link Label2 and Description
if "Description" in obj.PropertiesList and hasattr(obj, "setExpression"):
@@ -1150,6 +1156,7 @@ def aggregate(obj, parent, mode=None):
if not ifcfile:
return
product = None
new = False
stepid = getattr(obj, "StepId", None)
if stepid:
# obj might be dragging at this point and has no project anymore
@@ -1163,7 +1170,6 @@ def aggregate(obj, parent, mode=None):
# this object already has an associated IFC product
print("DEBUG:", obj.Label, "is already part of the IFC document")
newobj = obj
new = False
else:
ifcclass = None
if mode == "opening":
@@ -1173,12 +1179,16 @@ def aggregate(obj, parent, mode=None):
product = ifc_export.create_annotation(obj, ifcfile)
if Draft.get_type(obj) in ["DraftText","Text"]:
objecttype = "text"
elif "CreateSpreadsheet" in obj.PropertiesList:
obj.Proxy.create_ifc(obj, ifcfile)
newobj = obj
else:
product = ifc_export.create_product(obj, parent, ifcfile, ifcclass)
if product:
shapemode = getattr(parent, "ShapeMode", DEFAULT_SHAPEMODE)
newobj = create_object(product, obj.Document, ifcfile, shapemode, objecttype)
new = True
create_relationship(obj, newobj, parent, product, ifcfile, mode)
create_relationship(obj, newobj, parent, product, ifcfile, mode)
base = getattr(obj, "Base", None)
if base:
# make sure the base is used only by this object before deleting
@@ -1524,6 +1534,12 @@ def get_orphan_elements(ifcfile):
products = [
p for p in products if not hasattr(p, "VoidsElements") or not p.VoidsElements
]
# add control elements
proj = ifcfile.by_type("IfcProject")[0]
for rel in proj.Declares:
for ctrl in getattr(rel,"RelatedDefinitions", []):
if ctrl.is_a("IfcControl"):
products.append(ctrl)
groups = []
for o in products:
for rel in getattr(o, "HasAssignments", []):