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
This commit is contained in:
Dion Moult
2019-01-27 23:21:04 +11:00
parent 3448bb477c
commit c85514b2cf
5 changed files with 184 additions and 152 deletions

View File

@@ -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"]:

View File

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

115
src/Mod/Arch/ArchIFC.py Normal file
View File

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

View File

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

View File

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