From c85514b2cf73ad0004301a1d6ffc444fa36e657a Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sun, 27 Jan 2019 23:21:04 +1100 Subject: [PATCH] Refactor all IFC functions into ArchIFC and preload attributes with expressions * The import code will manually set 2x3 attributes * The import code will rely on "smart" attributes for ifc 4 * ArchSite and ArchBuildingPart now support IFC * IfcSite lat/lon is left alone until I better understand how coords work --- src/Mod/Arch/ArchBuildingPart.py | 5 +- src/Mod/Arch/ArchComponent.py | 85 +-------------------- src/Mod/Arch/ArchIFC.py | 115 ++++++++++++++++++++++++++++ src/Mod/Arch/ArchSite.py | 5 +- src/Mod/Arch/importIFC.py | 126 +++++++++++++++---------------- 5 files changed, 184 insertions(+), 152 deletions(-) create mode 100644 src/Mod/Arch/ArchIFC.py diff --git a/src/Mod/Arch/ArchBuildingPart.py b/src/Mod/Arch/ArchBuildingPart.py index 9d942f56f7..9c86c826aa 100644 --- a/src/Mod/Arch/ArchBuildingPart.py +++ b/src/Mod/Arch/ArchBuildingPart.py @@ -22,7 +22,7 @@ #* * #*************************************************************************** -import FreeCAD,Draft,ArchCommands,DraftVecUtils,sys +import FreeCAD,Draft,ArchCommands,DraftVecUtils,sys,ArchIFC if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -320,6 +320,7 @@ class BuildingPart: self.setProperties(obj) def setProperties(self,obj): + ArchIFC.setProperties(obj) pl = obj.PropertiesList if not "Height" in pl: @@ -362,7 +363,7 @@ class BuildingPart: self.oldPlacement = FreeCAD.Placement(obj.Placement) def onChanged(self,obj,prop): - + ArchIFC.onChanged(obj, prop) if prop == "Height": for child in obj.Group: if Draft.getType(child) in ["Wall","Structure"]: diff --git a/src/Mod/Arch/ArchComponent.py b/src/Mod/Arch/ArchComponent.py index e9b13505f9..0fc0ff8cce 100644 --- a/src/Mod/Arch/ArchComponent.py +++ b/src/Mod/Arch/ArchComponent.py @@ -25,8 +25,9 @@ __title__="FreeCAD Arch Component" __author__ = "Yorik van Havre" __url__ = "http://www.freecadweb.org" -import FreeCAD,Draft,ArchCommands,math,sys,json,os,ifcopenshell +import FreeCAD,Draft,ArchCommands,math,sys,json,os,ifcopenshell,ArchIFC from FreeCAD import Vector +from ArchIFC import ifcProducts, ifcTypes if FreeCAD.GuiUp: import FreeCADGui from PySide import QtGui,QtCore @@ -47,28 +48,9 @@ else: # This module provides the base Arch component class, that # is shared by all of the Arch BIM objects -with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets", - "ifc_products_" + ifcopenshell.schema_identifier + ".json")) as f: - ifcProducts = json.load(f) - -with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets", - "ifc_types_" + ifcopenshell.schema_identifier + ".json")) as f: - ifcTypes = json.load(f) - # Possible roles for FreeCAD BIM objects IfcRoles = ['Undefined']+[''.join(map(lambda x: x if x.islower() else " "+x, t[3:]))[1:] for t in ifcProducts.keys()] -def getIfcProduct(IfcRole): - name = "Ifc" + IfcRole.replace(" ", "") - if name in ifcProducts: - return ifcProducts[name] - -def getIfcProductAttribute(ifcProduct, name): - for attribute in ifcProduct["attributes"]: - if attribute["name"].replace(' ', '') == name: - return attribute - return None - def convertOldComponents(objs=[]): """converts Arch Objects with a Role property to the new IfcRole. @@ -186,59 +168,8 @@ class Component: Component.setProperties(self,obj) self.Type = "Component" - def setIfcAttributes(self, obj): - ifcProduct = getIfcProduct(obj.IfcRole) - if ifcProduct is None: - return - self.purgeUnusedIfcAttributesFromPropertiesList(ifcProduct, obj) - self.addIfcProductAttributesToObj(ifcProduct, obj) - - def addIfcProductAttributesToObj(self, ifcProduct, obj): - for attribute in ifcProduct["attributes"]: - if attribute["name"] in obj.PropertiesList: - continue - self.addIfcProductAttributeToObj(attribute, obj) - - def addIfcProductAttributeToObj(self, attribute, obj): - IfcData = obj.IfcData - if "attributes" not in IfcData: - IfcData["attributes"] = "{}" - IfcAttributes = json.loads(IfcData["attributes"]) - IfcAttributes[attribute["name"]] = attribute - IfcData["attributes"] = json.dumps(IfcAttributes) - obj.IfcData = IfcData - if attribute["is_enum"]: - obj.addProperty("App::PropertyEnumeration", attribute["name"], "IFC Attributes", QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented")) - setattr(obj, attribute["name"], attribute["enum_values"]) - else: - propertyType = "App::" + ifcTypes[attribute["type"]]["property"] - obj.addProperty(propertyType, attribute["name"], "IFC Attributes", QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented")) - - def setObjIfcAttributeValue(self, obj, attributeName, value): - IfcData = obj.IfcData - IfcAttributes = json.loads(IfcData["attributes"]) - if isinstance(value, FreeCAD.Units.Quantity): - value = float(value) - IfcAttributes[attributeName]["value"] = value - IfcData["attributes"] = json.dumps(IfcAttributes) - obj.IfcData = IfcData - - def purgeUnusedIfcAttributesFromPropertiesList(self, ifcProduct, obj): - for property in obj.PropertiesList: - if obj.getGroupOfProperty(property) != "IFC Attributes": - continue - ifcProductAttribute = getIfcProductAttribute(ifcProduct, property) - if ifcProductAttribute is None or ifcProductAttribute["is_enum"] is True: - obj.removeProperty(property) - - - def migrateDeprecatedAttributes(self, obj): - # FreeCAD <= 0.17 stored IFC data in IfcAttributes - if hasattr(obj, "IfcAttributes"): - obj.IfcData = obj.IfcAttributes - obj.removeProperty("IfcAttributes") - def setProperties(self,obj): + ArchIFC.setProperties(obj) pl = obj.PropertiesList if not "Base" in pl: obj.addProperty("App::PropertyLink","Base","Component",QT_TRANSLATE_NOOP("App::Property","The base object this component is built upon")) @@ -254,8 +185,6 @@ class Component: obj.addProperty("App::PropertyString","Tag","Component",QT_TRANSLATE_NOOP("App::Property","An optional tag for this component")) if not "StandardCode" in pl: obj.addProperty("App::PropertyString","StandardCode","Component",QT_TRANSLATE_NOOP("App::Property","An optional standard (OmniClass, etc...) code for this component")) - if not "IfcData" in pl: - obj.addProperty("App::PropertyMap","IfcData","Component",QT_TRANSLATE_NOOP("App::Property","IFC data")) if not "Material" in pl: obj.addProperty("App::PropertyLink","Material","Component",QT_TRANSLATE_NOOP("App::Property","A material for this object")) if "BaseMaterial" in pl: @@ -298,7 +227,6 @@ class Component: self.Subvolume = None #self.MoveWithHost = False self.Type = "Component" - self.migrateDeprecatedAttributes(obj) def onDocumentRestored(self,obj): @@ -331,12 +259,7 @@ class Component: self.oldPlacement = FreeCAD.Placement(obj.Placement) def onChanged(self,obj,prop): - - if prop == "IfcRole": - self.setIfcAttributes(obj) - - if obj.getGroupOfProperty(prop) == "IFC Attributes": - self.setObjIfcAttributeValue(obj, prop, obj.getPropertyByName(prop)) + ArchIFC.onChanged(obj, prop) if prop == "Placement": if hasattr(self,"oldPlacement"): diff --git a/src/Mod/Arch/ArchIFC.py b/src/Mod/Arch/ArchIFC.py new file mode 100644 index 0000000000..0fba391f02 --- /dev/null +++ b/src/Mod/Arch/ArchIFC.py @@ -0,0 +1,115 @@ +import FreeCAD, os, ifcopenshell, json +if FreeCAD.GuiUp: + from PySide.QtCore import QT_TRANSLATE_NOOP + +with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets", + "ifc_products_" + ifcopenshell.schema_identifier + ".json")) as f: + ifcProducts = json.load(f) + +with open(os.path.join(FreeCAD.getResourceDir(), "Mod", "Arch", "Presets", + "ifc_types_" + ifcopenshell.schema_identifier + ".json")) as f: + ifcTypes = json.load(f) + +def setProperties(obj): + if not "IfcData" in obj.PropertiesList: + obj.addProperty("App::PropertyMap","IfcData","Component",QT_TRANSLATE_NOOP("App::Property","IFC data")) + migrateDeprecatedAttributes(obj) + +def onChanged(obj, prop): + if prop == "IfcRole": + setupIfcAttributes(obj) + if obj.getGroupOfProperty(prop) == "IFC Attributes": + setObjIfcAttributeValue(obj, prop, obj.getPropertyByName(prop)) + +def getIfcProduct(IfcRole): + name = "Ifc" + IfcRole.replace(" ", "") + if name in ifcProducts: + return ifcProducts[name] + +def getIfcProductAttribute(ifcProduct, name): + for attribute in ifcProduct["attributes"]: + if attribute["name"].replace(' ', '') == name: + return attribute + return None + +def setupIfcAttributes(obj): + ifcProduct = getIfcProduct(obj.IfcRole) + if ifcProduct is None: + return + purgeUnusedIfcAttributesFromPropertiesList(ifcProduct, obj) + addIfcProductAttributesToObj(ifcProduct, obj) + +def addIfcProductAttributesToObj(ifcProduct, obj): + for attribute in ifcProduct["attributes"]: + if attribute["name"] in obj.PropertiesList \ + or attribute["name"] == "RefLatitude" \ + or attribute["name"] == "RefLongitude": + continue + addIfcProductAttribute(obj, attribute) + addIfcAttributeValueExpressions(obj, attribute) + +def addIfcProductAttribute(obj, attribute): + IfcData = obj.IfcData + if "attributes" not in IfcData: + IfcData["attributes"] = "{}" + IfcAttributes = json.loads(IfcData["attributes"]) + IfcAttributes[attribute["name"]] = attribute + IfcData["attributes"] = json.dumps(IfcAttributes) + obj.IfcData = IfcData + if attribute["is_enum"]: + obj.addProperty("App::PropertyEnumeration", attribute["name"], "IFC Attributes", QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented")) + setattr(obj, attribute["name"], attribute["enum_values"]) + else: + propertyType = "App::" + ifcTypes[attribute["type"]]["property"] + obj.addProperty(propertyType, attribute["name"], "IFC Attributes", QT_TRANSLATE_NOOP("App::Property", "Description of IFC attributes are not yet implemented")) + +def addIfcAttributeValueExpressions(obj, attribute): + if obj.getGroupOfProperty(attribute["name"]) != "Ifc Attributes": + return + if attribute["name"] == "OverallWidth": + if hasattr(obj, "Length"): + obj.setExpression("OverallWidth", "Length.Value") + elif hasattr(obj, "Width"): + obj.setExpression("OverallWidth", "Width.Value") + elif obj.Shape.BoundBox.XLength > obj.Shape.BoundBox.YLength: + obj.setExpression("OverallWidth", "Shape.BoundBox.XLength") + else: + obj.setExpression("OverallWidth", "Shape.BoundBox.YLength") + elif attribute["name"] == "OverallHeight": + if hasattr(obj, "Height"): + obj.setExpression("OverallHeight", "Height.Value") + else: + obj.setExpression("OverallHeight", "Shape.BoundBox.ZLength") + elif attribute["name"] == "ElevationWithFlooring": + obj.setExpression("ElevationWithFlooring", "Shape.BoundBox.ZMin") + elif attribute["name"] == "Elevation": + obj.setExpression("Elevation", "Placement.Base.z") + elif attribute["name"] == "NominalDiameter": + obj.setExpression("NominalDiameter", "Diameter.Value") + elif attribute["name"] == "BarLength": + obj.setExpression("BarLength", "Length.Value") + elif attribute["name"] == "RefElevation": + obj.setExpression("RefElevation", "Elevation.Value") + +def setObjIfcAttributeValue(obj, attributeName, value): + IfcData = obj.IfcData + IfcAttributes = json.loads(IfcData["attributes"]) + if isinstance(value, FreeCAD.Units.Quantity): + value = float(value) + IfcAttributes[attributeName]["value"] = value + IfcData["attributes"] = json.dumps(IfcAttributes) + obj.IfcData = IfcData + +def purgeUnusedIfcAttributesFromPropertiesList(ifcProduct, obj): + for property in obj.PropertiesList: + if obj.getGroupOfProperty(property) != "IFC Attributes": + continue + ifcProductAttribute = getIfcProductAttribute(ifcProduct, property) + if ifcProductAttribute is None or ifcProductAttribute["is_enum"] is True: + obj.removeProperty(property) + +def migrateDeprecatedAttributes(obj): + # FreeCAD <= 0.17 stored IFC data in IfcAttributes + if hasattr(obj, "IfcAttributes"): + obj.IfcData = obj.IfcAttributes + obj.removeProperty("IfcAttributes") diff --git a/src/Mod/Arch/ArchSite.py b/src/Mod/Arch/ArchSite.py index e86da64af4..f1148c758f 100644 --- a/src/Mod/Arch/ArchSite.py +++ b/src/Mod/Arch/ArchSite.py @@ -23,7 +23,7 @@ #* * #*************************************************************************** -import FreeCAD,Draft,ArchCommands,ArchFloor,math,re,datetime +import FreeCAD,Draft,ArchCommands,ArchFloor,math,re,datetime,ArchIFC if FreeCAD.GuiUp: import FreeCADGui from PySide import QtCore, QtGui @@ -324,7 +324,7 @@ class _Site(ArchFloor._Floor): obj.IfcRole = "Site" def setProperties(self,obj): - + ArchIFC.setProperties(obj) pl = obj.PropertiesList if not "Terrain" in pl: obj.addProperty("App::PropertyLink","Terrain","Site",QT_TRANSLATE_NOOP("App::Property","The base terrain of this site")) @@ -424,6 +424,7 @@ class _Site(ArchFloor._Floor): self.computeAreas(obj) def onChanged(self,obj,prop): + ArchIFC.onChanged(obj, prop) ArchFloor._Floor.onChanged(self,obj,prop) if prop == "Terrain": diff --git a/src/Mod/Arch/importIFC.py b/src/Mod/Arch/importIFC.py index e1ff55046b..57efb4ad93 100644 --- a/src/Mod/Arch/importIFC.py +++ b/src/Mod/Arch/importIFC.py @@ -29,7 +29,7 @@ __url__ = "http://www.freecadweb.org" import os,time,tempfile,uuid,FreeCAD,Part,Draft,Arch,math,DraftVecUtils,sys from DraftGeomUtils import vec -from ArchComponent import ifcProducts +from ArchIFC import ifcProducts ## @package importIFC # \ingroup ARCH @@ -1648,26 +1648,11 @@ def export(exportList,filename): d["IfcUID"] = uid obj.IfcData = d - # setting the IFC type + name conversions + ifctype = getIfcRoleFromObj(obj) - if hasattr(obj,"IfcRole"): - ifctype = obj.IfcRole.replace(" ","") - elif hasattr(obj,"Role"): - ifctype = obj.Role.replace(" ","") - else: - ifctype = Draft.getType(obj) - if ifctype in translationtable.keys(): - ifctype = translationtable[ifctype] - ifctype = "Ifc" + ifctype - if ifctype == "IfcVisGroup": - ifctype = "IfcGroup" if ifctype == "IfcGroup": groups[obj.Name] = [o.Name for o in obj.Group] continue - if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcRole") and (obj.IfcRole == "Undefined"): - ifctype = "IfcBuildingStorey" # export BuildingParts as Storeys if their type wasn't explicitly set - if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcRole") and (obj.IfcRole == "Building"): - ifctype = "IfcBuilding" # export grids @@ -1749,61 +1734,15 @@ def export(exportList,filename): kwargs = {"GlobalId": uid, "OwnerHistory": history, "Name": name, "Description": description, "ObjectPlacement": placement, "Representation": representation} - if ifctype in ["IfcSlab","IfcFooting","IfcRoof"]: - kwargs.update({"PredefinedType": "NOTDEFINED"}) - elif ifctype in ["IfcWindow","IfcDoor"]: - if hasattr(obj,"Width") and hasattr(obj,"Height"): - kwargs.update({"OverallHeight": obj.Width.Value/1000.0, - "OverallWidth": obj.Height.Value/1000.0}) - else: - if obj.Shape.BoundBox.XLength > obj.Shape.BoundBox.YLength: - l = obj.Shape.BoundBox.XLength - else: - l = obj.Shape.BoundBox.YLength - kwargs.update({"OverallHeight": l/1000.0, - "OverallWidth": obj.Shape.BoundBox.ZLength/1000.0}) - elif ifctype == "IfcSpace": - internal = "NOTDEFINED" - if hasattr(obj,"Internal"): - if obj.Internal: - internal = "INTERNAL" - else: - internal = "EXTERNAL" - if schema == "IFC2X3": - kwargs.update({"CompositionType": "ELEMENT", - "InteriorOrExteriorSpace": internal, - "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0}) - else: - kwargs.update({"CompositionType": "ELEMENT", - "PredefinedType": internal, - "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0}) - elif ifctype == "IfcBuildingElementProxy": - if ifcopenshell.schema_identifier == "IFC4": - kwargs.update({"PredefinedType": "ELEMENT"}) - else: - kwargs.update({"CompositionType": "ELEMENT"}) - elif ifctype == "IfcSite": + if ifctype == "IfcSite": kwargs.update({"RefLatitude":dd2dms(obj.Latitude), "RefLongitude":dd2dms(obj.Longitude), "RefElevation":obj.Elevation.Value/1000.0, "SiteAddress":buildAddress(obj,ifcfile), "CompositionType": "ELEMENT"}) - elif ifctype == "IfcBuilding": - kwargs.update({"CompositionType": "ELEMENT"}) - elif ifctype == "IfcBuildingStorey": - kwargs.update({"CompositionType": "ELEMENT", - "Elevation": obj.Placement.Base.z/1000.0}) - elif ifctype == "IfcReinforcingBar": - kwargs.update({"NominalDiameter": obj.Diameter.Value, - "BarLength": obj.Length.Value}) - # TODO: Reconcile the attributes above which can be cleverly derived - # from FreeCAD native data, with the explicit IFC attribute values below - for property in obj.PropertiesList: - if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property): - value = obj.getPropertyByName(property) - if isinstance(value, FreeCAD.Units.Quantity): - value = float(value) - kwargs.update({ property: value }) + if ifcopenshell.schema_identifier == "IFC2X3": + kwargs = exportIFC2X3Attributes(obj, kwargs) + kwargs = exportIfcAttributes(obj, kwargs) # creating the product @@ -2397,6 +2336,59 @@ def export(exportList,filename): print("Compression ratio:",int((float(ifcbin.spared)/(s+ifcbin.spared))*100),"%") del ifcbin +def getIfcRoleFromObj(obj): + if (Draft.getType(obj) == "BuildingPart") and hasattr(obj,"IfcRole") and (obj.IfcRole == "Undefined"): + ifctype = "IfcBuildingStorey" # export BuildingParts as Storeys if their type wasn't explicitly set + elif hasattr(obj,"IfcRole"): + ifctype = obj.IfcRole.replace(" ","") + elif hasattr(obj,"Role"): + ifctype = obj.Role.replace(" ","") + else: + ifctype = Draft.getType(obj) + + if ifctype in translationtable.keys(): + ifctype = translationtable[ifctype] + if ifctype == "VisGroup": + ifctype = "Group" + + return "Ifc" + ifctype + +def exportIFC2X3Attributes(obj, kwargs): + role = getIfcRoleFromObj(obj) + if role in ["IfcSlab", "IfcFooting", "IfcRoof"]: + kwargs.update({"PredefinedType": "NOTDEFINED"}) + elif role == "IfcBuilding": + kwargs.update({"CompositionType": "ELEMENT"}) + elif role == "IfcBuildingStorey": + kwargs.update({"CompositionType": "ELEMENT"}) + elif role == "IfcBuildingElementProxy": + kwargs.update({"CompositionType": "ELEMENT"}) + elif role == "IfcSpace": + internal = "NOTDEFINED" + if hasattr(obj,"Internal"): + if obj.Internal: + internal = "INTERNAL" + else: + internal = "EXTERNAL" + kwargs.update({"CompositionType": "ELEMENT", + "InteriorOrExteriorSpace": internal, + "ElevationWithFlooring": obj.Shape.BoundBox.ZMin/1000.0}) + elif role == "IfcReinforcingBar": + kwargs.update({"NominalDiameter": obj.Diameter.Value, + "BarLength": obj.Length.Value}) + elif role == "IfcBuildingStorey": + kwargs.update({"Elevation": obj.Placement.Base.z/1000.0}) + return kwargs + + +def exportIfcAttributes(obj, kwargs): + for property in obj.PropertiesList: + if obj.getGroupOfProperty(property) == "IFC Attributes" and obj.getPropertyByName(property): + value = obj.getPropertyByName(property) + if isinstance(value, FreeCAD.Units.Quantity): + value = float(value) + kwargs.update({ property: value }) + return kwargs def buildAddress(obj,ifcfile):