| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| | """This modules sets up and manages the IFC-related properties, types |
| | and attributes of Arch/BIM objects. |
| | """ |
| |
|
| | import json |
| |
|
| | import FreeCAD |
| | import ArchIFCSchema |
| |
|
| | if FreeCAD.GuiUp: |
| | from PySide.QtCore import QT_TRANSLATE_NOOP |
| | else: |
| |
|
| | def QT_TRANSLATE_NOOP(ctx, txt): |
| | return txt |
| |
|
| |
|
| | def uncamel(t): |
| | return "".join(map(lambda x: x if x.islower() else " " + x, t[3:]))[1:] |
| |
|
| |
|
| | IfcTypes = [uncamel(t) for t in ArchIFCSchema.IfcProducts.keys()] |
| |
|
| |
|
| | class IfcRoot: |
| | """This class defines the common methods and properties for managing IFC data. |
| | |
| | IFC, or Industry Foundation Classes are a standardised way to digitally |
| | describe the built environment. The ultimate goal of IFC is to provide |
| | better interoperability between software that deals with the built |
| | environment. You can learn more here: |
| | https://technical.buildingsmart.org/standards/ifc/ |
| | |
| | You can learn more about the technical details of the IFC schema here: |
| | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/ |
| | |
| | This class is further segmented down into IfcProduct and IfcContext. |
| | """ |
| |
|
| | def setProperties(self, obj): |
| | """Give the object properties for storing IFC data. |
| | |
| | Also migrate old versions of IFC properties to the new property names |
| | using the .migrateDeprecatedAttributes() method. |
| | """ |
| |
|
| | if not "IfcData" in obj.PropertiesList: |
| | obj.addProperty( |
| | "App::PropertyMap", |
| | "IfcData", |
| | "IFC", |
| | QT_TRANSLATE_NOOP("App::Property", "IFC data"), |
| | locked=True, |
| | ) |
| |
|
| | if not "IfcType" in obj.PropertiesList: |
| | obj.addProperty( |
| | "App::PropertyEnumeration", |
| | "IfcType", |
| | "IFC", |
| | QT_TRANSLATE_NOOP("App::Property", "The type of this object"), |
| | locked=True, |
| | ) |
| | obj.IfcType = self.getCanonicalisedIfcTypes() |
| |
|
| | if not "IfcProperties" in obj.PropertiesList: |
| | obj.addProperty( |
| | "App::PropertyMap", |
| | "IfcProperties", |
| | "IFC", |
| | QT_TRANSLATE_NOOP("App::Property", "IFC properties of this object"), |
| | locked=True, |
| | ) |
| |
|
| | self.migrateDeprecatedAttributes(obj) |
| |
|
| | def onChanged(self, obj, prop): |
| | """Method called when the object has a property changed. |
| | |
| | If the object's IfcType has changed, change the object's properties |
| | that relate to IFC attributes in order to match the IFC schema |
| | definition of the new IFC type. |
| | |
| | If a property changes that is in the "IFC Attributes" group, also |
| | change the value stored in the IfcData property's JSON. |
| | |
| | Parameters |
| | ---------- |
| | prop: string |
| | The name of the property that has changed. |
| | """ |
| |
|
| | if prop == "IfcType": |
| | self.setupIfcAttributes(obj) |
| | self.setupIfcComplexAttributes(obj) |
| | if prop in obj.PropertiesList: |
| | if obj.getGroupOfProperty(prop) == "IFC Attributes": |
| | self.setObjIfcAttributeValue(obj, prop, obj.getPropertyByName(prop)) |
| |
|
| | def setupIfcAttributes(self, obj): |
| | """Set up the IFC attributes in the object's properties. |
| | |
| | Add the attributes specified in the object's IFC type schema, to the |
| | object's properties. Do not re-add them if they're already present. |
| | Also remove old IFC attribute properties that no longer appear in the |
| | schema for backwards compatibility. |
| | |
| | Do so using the .addIfcAttributes() and |
| | .purgeUnusedIfcAttributesFromPropertiesList() methods. |
| | |
| | Learn more about IFC attributes here: |
| | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/chapter-3.htm#attribute |
| | """ |
| |
|
| | ifcTypeSchema = self.getIfcTypeSchema(obj.IfcType) |
| | if ifcTypeSchema is None: |
| | return |
| | self.purgeUnusedIfcAttributesFromPropertiesList(ifcTypeSchema, obj) |
| | self.addIfcAttributes(ifcTypeSchema, obj) |
| |
|
| | def setupIfcComplexAttributes(self, obj): |
| | """Add the IFC type's complex attributes to the object. |
| | |
| | Get the object's IFC type schema, and add the schema for the type's |
| | complex attributes within the IfcData property. |
| | """ |
| |
|
| | ifcTypeSchema = self.getIfcTypeSchema(obj.IfcType) |
| | if ifcTypeSchema is None: |
| | return |
| | IfcData = obj.IfcData |
| | if "complex_attributes" not in IfcData: |
| | IfcData["complex_attributes"] = "{}" |
| | ifcComplexAttributes = json.loads(IfcData["complex_attributes"]) |
| | for attribute in ifcTypeSchema["complex_attributes"]: |
| | if attribute["name"] not in ifcComplexAttributes: |
| | ifcComplexAttributes[attribute["name"]] = {} |
| | IfcData["complex_attributes"] = json.dumps(ifcComplexAttributes) |
| | obj.IfcData = IfcData |
| |
|
| | def getIfcTypeSchema(self, IfcType): |
| | """Get the schema of the IFC type provided. |
| | |
| | If the IFC type is undefined, return the schema of the |
| | IfcBuildingElementProxy. |
| | |
| | Parameter |
| | --------- |
| | IfcType: str |
| | The IFC type whose schema you want. |
| | |
| | Returns |
| | ------- |
| | dict |
| | Returns the schema of the type as a dict. |
| | None |
| | Returns None if the IFC type does not exist. |
| | """ |
| | name = "Ifc" + IfcType.replace(" ", "") |
| | if IfcType == "Undefined": |
| | name = "IfcBuildingElementProxy" |
| | if name in self.getIfcSchema(): |
| | return self.getIfcSchema()[name] |
| | return None |
| |
|
| | def getIfcSchema(self): |
| | """Get the IFC schema of all types relevant to this class. |
| | |
| | Intended to be overwritten by the classes that inherit this class. |
| | |
| | Returns |
| | ------- |
| | dict |
| | The schema of all the types relevant to this class. |
| | """ |
| |
|
| | return {} |
| |
|
| | def getCanonicalisedIfcTypes(self): |
| | """Get the names of IFC types, converted to the form used in Arch. |
| | |
| | Change the names of all IFC types to a more human readable form which |
| | is used instead throughout Arch instead of the raw type names. The |
| | names have the "Ifc" stripped from the start of their name, and spaces |
| | inserted between the words. |
| | |
| | Returns |
| | ------- |
| | list of str |
| | The list of every IFC type name in their form used in Arch. List |
| | will have names in the same order as they appear in the schema's |
| | JSON, as per the .keys() method of dicts. |
| | |
| | """ |
| | schema = self.getIfcSchema() |
| | return [ |
| | "".join(map(lambda x: x if x.islower() else " " + x, t[3:]))[1:] for t in schema.keys() |
| | ] |
| |
|
| | def getIfcAttributeSchema(self, ifcTypeSchema, name): |
| | """Get the schema of an IFC attribute with the given name. |
| | |
| | Convert the IFC attribute's name from the human readable version Arch |
| | uses, and convert it to the less readable name it has in the IFC |
| | schema. |
| | |
| | Parameters |
| | ---------- |
| | ifcTypeSchema: dict |
| | The schema of the IFC type to access the attribute of. |
| | name: str |
| | The name the attribute has in Arch. |
| | |
| | Returns |
| | ------- |
| | dict |
| | Returns the schema of the attribute. |
| | None |
| | Returns None if the IFC type does not have the attribute requested. |
| | |
| | """ |
| |
|
| | for attribute in ifcTypeSchema["attributes"]: |
| | if attribute["name"].replace(" ", "") == name: |
| | return attribute |
| | return None |
| |
|
| | def addIfcAttributes(self, ifcTypeSchema, obj): |
| | """Add the attributes of the IFC type's schema to the object's properties. |
| | |
| | Add the attributes as properties of the object. Also add the |
| | attribute's schema within the object's IfcData property. Do so using |
| | the .addIfcAttribute() method. |
| | |
| | Also add expressions to copy data from the object's editable |
| | properties. This means the IFC properties will remain accurate with |
| | the actual values of the object. Do not do so for all IFC properties. |
| | Do so using the .addIfcAttributeValueExpressions() method. |
| | |
| | Learn more about expressions here: |
| | https://wiki.freecad.org/Expressions |
| | |
| | Do not add the attribute if the object has a property with the |
| | attribute's name. Also do not add the attribute if its name is |
| | RefLatitude, RefLongitude, or Name. |
| | |
| | Parameters |
| | ---------- |
| | ifcTypeSchema: dict |
| | The schema of the IFC type. |
| | """ |
| |
|
| | for attribute in ifcTypeSchema["attributes"]: |
| | if ( |
| | attribute["name"] in obj.PropertiesList |
| | or attribute["name"] == "RefLatitude" |
| | or attribute["name"] == "RefLongitude" |
| | or attribute["name"] == "Name" |
| | ): |
| | continue |
| | self.addIfcAttribute(obj, attribute) |
| | self.addIfcAttributeValueExpressions(obj, attribute) |
| |
|
| | def addIfcAttribute(self, obj, attribute): |
| | """Add an IFC type's attribute to the object, within its properties. |
| | |
| | Add the attribute's schema to the object's IfcData property, as an |
| | item under its "attributes" array. |
| | |
| | Also add the attribute as a property of the object. |
| | |
| | Parameters |
| | ---------- |
| | attribute: dict |
| | The attribute to add. Should have the structure of an attribute |
| | found within the IFC schemas. |
| | """ |
| | if not hasattr(obj, "IfcData"): |
| | return |
| | 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::" + ArchIFCSchema.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(self, obj, attribute): |
| | """Add expressions for IFC attributes, so they stay accurate with the object. |
| | |
| | Add expressions to the object that copy data from the editable |
| | properties of the object. This ensures that the IFC attributes will |
| | remain accurate with the actual values of the object. |
| | |
| | Currently, add expressions for the following IFC attributes: |
| | |
| | - OverallWidth |
| | - OverallHeight |
| | - ElevationWithFlooring |
| | - Elevation |
| | - NominalDiameter |
| | - BarLength |
| | - RefElevation |
| | - LongName |
| | |
| | Learn more about expressions here: |
| | https://wiki.freecad.org/Expressions |
| | |
| | Parameters |
| | ---------- |
| | attribute: dict |
| | The schema of the attribute to add the expression for. |
| | """ |
| |
|
| | if ( |
| | obj.getGroupOfProperty(attribute["name"]) != "IFC Attributes" |
| | or attribute["name"] not in obj.PropertiesList |
| | ): |
| | return |
| | if attribute["name"] == "OverallWidth": |
| | if "Length" in obj.PropertiesList: |
| | obj.setExpression("OverallWidth", "Length.Value") |
| | elif "Width" in obj.PropertiesList: |
| | obj.setExpression("OverallWidth", "Width.Value") |
| | elif obj.Shape and (obj.Shape.BoundBox.XLength > obj.Shape.BoundBox.YLength): |
| | obj.setExpression("OverallWidth", "Shape.BoundBox.XLength") |
| | elif obj.Shape: |
| | obj.setExpression("OverallWidth", "Shape.BoundBox.YLength") |
| | elif attribute["name"] == "OverallHeight": |
| | if "Height" in obj.PropertiesList: |
| | obj.setExpression("OverallHeight", "Height.Value") |
| | else: |
| | obj.setExpression("OverallHeight", "Shape.BoundBox.ZLength") |
| | elif attribute["name"] == "ElevationWithFlooring" and "Shape" in obj.PropertiesList: |
| | obj.setExpression("ElevationWithFlooring", "Shape.BoundBox.ZMin") |
| | elif attribute["name"] == "Elevation" and "Placement" in obj.PropertiesList: |
| | obj.setExpression("Elevation", "Placement.Base.z") |
| | elif attribute["name"] == "NominalDiameter" and "Diameter" in obj.PropertiesList: |
| | obj.setExpression("NominalDiameter", "Diameter.Value") |
| | elif attribute["name"] == "BarLength" and "Length" in obj.PropertiesList: |
| | obj.setExpression("BarLength", "Length.Value") |
| | elif attribute["name"] == "RefElevation" and "Elevation" in obj.PropertiesList: |
| | obj.setExpression("RefElevation", "Elevation.Value") |
| | elif attribute["name"] == "LongName": |
| | obj.LongName = obj.Label |
| |
|
| | def setObjIfcAttributeValue(self, obj, attributeName, value): |
| | """Change the value of an IFC attribute within the IfcData property's json. |
| | |
| | Parameters |
| | ---------- |
| | attributeName: str |
| | The name of the attribute to change. |
| | value: |
| | The new value to set. |
| | """ |
| | IfcData = obj.IfcData |
| | if "attributes" not in IfcData: |
| | IfcData["attributes"] = "{}" |
| | IfcAttributes = json.loads(IfcData["attributes"]) |
| | if isinstance(value, FreeCAD.Units.Quantity): |
| | value = float(value) |
| | if not attributeName in IfcAttributes: |
| | IfcAttributes[attributeName] = {} |
| | IfcAttributes[attributeName]["value"] = value |
| | IfcData["attributes"] = json.dumps(IfcAttributes) |
| | obj.IfcData = IfcData |
| |
|
| | def setObjIfcComplexAttributeValue(self, obj, attributeName, value): |
| | """Changes the value of the complex attribute in the IfcData property JSON. |
| | |
| | Parameters |
| | ---------- |
| | attributeName: str |
| | The name of the attribute to change. |
| | value: |
| | The new value to set. |
| | """ |
| |
|
| | IfcData = obj.IfcData |
| | IfcAttributes = json.loads(IfcData["complex_attributes"]) |
| | IfcAttributes[attributeName] = value |
| | IfcData["complex_attributes"] = json.dumps(IfcAttributes) |
| | obj.IfcData = IfcData |
| |
|
| | def getObjIfcComplexAttribute(self, obj, attributeName): |
| | """Get the value of the complex attribute, as stored in the IfcData JSON. |
| | |
| | Parameters |
| | ---------- |
| | attributeName: str |
| | The name of the complex attribute to access. |
| | |
| | Returns |
| | ------- |
| | The value of the complex attribute. |
| | """ |
| |
|
| | return json.loads(obj.IfcData["complex_attributes"])[attributeName] |
| |
|
| | def purgeUnusedIfcAttributesFromPropertiesList(self, ifcTypeSchema, obj): |
| | """Remove properties representing IFC attributes if they no longer appear. |
| | |
| | Remove the property representing an IFC attribute, if it does not |
| | appear in the schema of the IFC type provided. Also, remove the |
| | property if its attribute is an enum type, presumably for backwards |
| | compatibility. |
| | |
| | Learn more about IFC enums here: |
| | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/chapter-3.htm#enumeration |
| | """ |
| |
|
| | for property in obj.PropertiesList: |
| | if obj.getGroupOfProperty(property) != "IFC Attributes": |
| | continue |
| | ifcAttribute = self.getIfcAttributeSchema(ifcTypeSchema, property) |
| | if ifcAttribute is None or ifcAttribute["is_enum"] is True: |
| | obj.removeProperty(property) |
| |
|
| | def migrateDeprecatedAttributes(self, obj): |
| | """Update the object to use the newer property names for IFC related properties.""" |
| |
|
| | if "Role" in obj.PropertiesList: |
| | r = obj.Role |
| | obj.removeProperty("Role") |
| | if r in IfcTypes: |
| | obj.IfcType = r |
| | FreeCAD.Console.PrintMessage( |
| | "Upgrading " + obj.Label + " Role property to IfcType\n" |
| | ) |
| |
|
| | if "IfcRole" in obj.PropertiesList: |
| | r = obj.IfcRole |
| | obj.removeProperty("IfcRole") |
| | if r in IfcTypes: |
| | obj.IfcType = r |
| | FreeCAD.Console.PrintMessage( |
| | "Upgrading " + obj.Label + " IfcRole property to IfcType\n" |
| | ) |
| |
|
| | if "IfcAttributes" in obj.PropertiesList: |
| | obj.IfcData = obj.IfcAttributes |
| | obj.removeProperty("IfcAttributes") |
| |
|
| |
|
| | class IfcProduct(IfcRoot): |
| | """This class is subclassed by classes that have a specific location in space. |
| | |
| | The obvious example are actual structures, such as the _Wall class, but it |
| | also includes the _Floor class, which is just a grouping of all the |
| | structures that make up one floor of a building. |
| | |
| | You can learn more about how products fit into the IFC schema here: |
| | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifckernel/lexical/ifcproduct.htm |
| | """ |
| |
|
| | def getIfcSchema(self): |
| | """Get the IFC schema of all IFC types that inherit from IfcProducts. |
| | |
| | Returns |
| | ------- |
| | dict |
| | The schema of all the types relevant to this class. |
| | """ |
| | return ArchIFCSchema.IfcProducts |
| |
|
| |
|
| | class IfcContext(IfcRoot): |
| | """This class is subclassed by classes that define a particular context. |
| | |
| | Currently, only the _Project inherits this class. |
| | |
| | You can learn more about how contexts fit into the IFC schema here: |
| | https://standards.buildingsmart.org/IFC/RELEASE/IFC4/FINAL/HTML/schema/ifckernel/lexical/ifccontext.htm |
| | """ |
| |
|
| | def getIfcSchema(self): |
| | """Get the IFC schema of all IFC types that inherit from IfcContexts. |
| | |
| | Returns |
| | ------- |
| | dict |
| | The schema of all the types relevant to this class. |
| | """ |
| | return ArchIFCSchema.IfcContexts |
| |
|