diff --git a/freecad/bem/entities.py b/freecad/bem/entities.py index cd94bfe..c389872 100644 --- a/freecad/bem/entities.py +++ b/freecad/bem/entities.py @@ -1,521 +1,520 @@ # coding: utf8 """This module contains FreeCAD wrapper class for IfcEntities © All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, Laboratory CNPA, 2019-2020 See the LICENSE.TXT file for more details. Author : Cyril Waechter """ import typing from typing import Iterable import ifcopenshell import FreeCAD import Part from freecad.bem.bem_logging import logger from freecad.bem import utils if typing.TYPE_CHECKING: # https://www.python.org/dev/peps/pep-0484/#id36 from freecad.bem.ifc_importer import IfcImporter from freecad.bem.typing import ( # pylint: disable=import-error, no-name-in-module RootFeature, BEMBoundaryFeature, RelSpaceBoundaryFeature, ContainerFeature, SpaceFeature, ProjectFeature, ElementFeature, ) def get_color(ifc_boundary): """Return a color depending on IfcClass given""" product_colors = { "IfcWall": (0.7, 0.3, 0.0), "IfcWindow": (0.0, 0.7, 1.0), "IfcSlab": (0.7, 0.7, 0.5), "IfcRoof": (0.0, 0.3, 0.0), "IfcDoor": (1.0, 1.0, 1.0), } if ifc_boundary.PhysicalOrVirtualBoundary == "VIRTUAL": return (1.0, 0.0, 1.0) ifc_product = ifc_boundary.RelatedBuildingElement if not ifc_product: return (1.0, 0.0, 0.0) for product, color in product_colors.items(): # Not only test if IFC class is in dictionnary but it is a subclass if ifc_product.is_a(product): return color print(f"No color found for {ifc_product.is_a()}") return (0.0, 0.0, 0.0) def get_related_element(ifc_entity, doc=FreeCAD.ActiveDocument) -> Part.Feature: if not ifc_entity.RelatedBuildingElement: return guid = ifc_entity.RelatedBuildingElement.GlobalId for element in doc.Objects: try: if element.GlobalId == guid: return element except AttributeError: continue class Root: """Wrapping various IFC entity : https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcroot.htm """ def __init__(self, obj: "RootFeature") -> None: self.Type = self.__class__.__name__ # pylint: disable=invalid-name obj.Proxy = self # pylint: disable=invalid-name obj.addExtension("App::GroupExtensionPython", self) @classmethod def _init_properties(cls, obj: "RootFeature") -> None: ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyString", "IfcType", "IFC") obj.addProperty("App::PropertyInteger", "Id", ifc_attributes) obj.addProperty("App::PropertyString", "GlobalId", ifc_attributes) obj.addProperty("App::PropertyString", "IfcName", ifc_attributes) obj.addProperty("App::PropertyString", "Description", ifc_attributes) @classmethod def create(cls) -> "RootFeature": """Stantard FreeCAD FeaturePython Object creation method""" obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", cls.__name__) cls(obj) cls._init_properties(obj) if FreeCAD.GuiUp: obj.ViewObject.Proxy = ViewProviderRoot(obj.ViewObject) return obj @classmethod def create_from_ifc(cls, ifc_entity, ifc_importer: "IfcImporter") -> "RootFeature": """As cls.create but providing an ifc source""" obj = cls.create() obj.Proxy.ifc_importer = ifc_importer cls.read_from_ifc(obj, ifc_entity) cls.set_label(obj) return obj @classmethod def read_from_ifc(cls, obj: "RootFeature", ifc_entity) -> None: obj.Id = ifc_entity.id() obj.GlobalId = ifc_entity.GlobalId obj.IfcType = ifc_entity.is_a() obj.IfcName = ifc_entity.Name or "" obj.Description = ifc_entity.Description or "" @staticmethod def set_label(obj: "RootFeature") -> None: """Allow specific method for specific elements""" obj.Label = f"{obj.Id}_{obj.IfcName or obj.IfcType}" @staticmethod def read_pset_from_ifc( obj: "RootFeature", ifc_entity, properties: Iterable[str] ) -> None: psets = ifcopenshell.util.element.get_psets(ifc_entity) for pset in psets.values(): for prop_name, prop in pset.items(): if prop_name in properties: setattr(obj, prop_name, getattr(prop, "wrappedValue", prop)) class ViewProviderRoot: def __init__(self, vobj): vobj.Proxy = self vobj.addExtension("Gui::ViewProviderGroupExtensionPython", self) class RelSpaceBoundary(Root): """Wrapping IFC entity : https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcrelspaceboundary2ndlevel.htm""" def __init__(self, obj: "RelSpaceBoundaryFeature") -> None: super().__init__(obj) obj.Proxy = self @classmethod def _init_properties(cls, obj: "RelSpaceBoundaryFeature") -> None: super()._init_properties(obj) bem_category = "BEM" ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyLinkHidden", "RelatingSpace", ifc_attributes) obj.addProperty("App::PropertyLink", "RelatedBuildingElement", ifc_attributes) obj.addProperty( "App::PropertyEnumeration", "PhysicalOrVirtualBoundary", ifc_attributes ).PhysicalOrVirtualBoundary = ["PHYSICAL", "VIRTUAL", "NOTDEFINED"] obj.addProperty( "App::PropertyEnumeration", "InternalOrExternalBoundary", ifc_attributes ).InternalOrExternalBoundary = [ "INTERNAL", "EXTERNAL", "EXTERNAL_EARTH", "EXTERNAL_WATER", "EXTERNAL_FIRE", "NOTDEFINED", ] obj.addProperty( "App::PropertyLinkHidden", "CorrespondingBoundary", ifc_attributes ) obj.addProperty("App::PropertyLinkHidden", "ParentBoundary", ifc_attributes) obj.addProperty("App::PropertyLinkList", "InnerBoundaries", ifc_attributes) obj.addProperty("App::PropertyVector", "Normal", bem_category) obj.addProperty("App::PropertyVector", "TranslationToSpace", bem_category) obj.addProperty("App::PropertyLength", "UndergroundDepth", bem_category) obj.addProperty("App::PropertyIntegerList", "ClosestBoundaries", bem_category) obj.addProperty("App::PropertyIntegerList", "ClosestEdges", bem_category) obj.addProperty("App::PropertyIntegerList", "ClosestDistance", bem_category) obj.addProperty("App::PropertyBool", "IsHosted", bem_category) obj.addProperty( "App::PropertyInteger", "InternalToExternal", bem_category, """Define if material layers direction. 1 mean from inside of the space toward outside of the space. -1 is the opposite. 0 is undefined (bool not used as 3 state bool do no exist in FreeCAD yet).""", ) obj.addProperty("App::PropertyArea", "Area", bem_category) obj.addProperty("App::PropertyArea", "AreaWithHosted", bem_category) obj.addProperty("App::PropertyLink", "SIA_Interior", bem_category) obj.addProperty("App::PropertyLink", "SIA_Exterior", bem_category) obj.addProperty( "App::PropertyEnumeration", "LesoType", bem_category ).LesoType = [ "Ceiling", "Wall", "Flooring", "Window", "Door", "Opening", "Unknown", ] @classmethod def read_from_ifc(cls, obj: "RelSpaceBoundaryFeature", ifc_entity) -> None: super().read_from_ifc(obj, ifc_entity) ifc_importer = obj.Proxy.ifc_importer element = get_related_element(ifc_entity, ifc_importer.doc) if element: obj.RelatedBuildingElement = element utils.append(element, "ProvidesBoundaries", obj) obj.InternalOrExternalBoundary = ifc_entity.InternalOrExternalBoundary obj.PhysicalOrVirtualBoundary = ifc_entity.PhysicalOrVirtualBoundary try: obj.Shape = ifc_importer.create_fc_shape(ifc_entity) except utils.ShapeCreationError: ifc_importer.doc.removeObject(obj.Name) raise utils.ShapeCreationError obj.Area = obj.AreaWithHosted = obj.Shape.Area if obj.Area < utils.TOLERANCE: ifc_importer.doc.removeObject(obj.Name) raise utils.IsTooSmall try: obj.IsHosted = bool(ifc_entity.RelatedBuildingElement.FillsVoids) except AttributeError: obj.IsHosted = False obj.LesoType = "Unknown" obj.CorrespondingBoundary = getattr(ifc_entity, "CorrespondingBoundary", None) if FreeCAD.GuiUp: obj.ViewObject.Proxy = 0 obj.ViewObject.ShapeColor = get_color(ifc_entity) def onChanged( self, obj: "RelSpaceBoundaryFeature", prop ): # pylint: disable=invalid-name if prop == "InnerBoundaries": self.recompute_area_with_hosted(obj) @classmethod def recompute_areas(cls, obj: "RelSpaceBoundaryFeature") -> None: obj.Area = obj.Shape.Faces[0].Area cls.recompute_area_with_hosted(obj) @staticmethod def recompute_area_with_hosted(obj: "RelSpaceBoundaryFeature") -> None: """Recompute area including inner boundaries""" area = obj.Area for boundary in obj.InnerBoundaries: area = area + boundary.Area obj.AreaWithHosted = area @classmethod def set_label(cls, obj: "RelSpaceBoundaryFeature") -> None: try: obj.Label = f"{obj.Id}_{obj.RelatedBuildingElement.IfcName}" except AttributeError: obj.Label = f"{obj.Id} VIRTUAL" if obj.PhysicalOrVirtualBoundary != "VIRTUAL": logger.warning( f"{obj.Id} is not VIRTUAL and has no RelatedBuildingElement" ) @staticmethod def get_wires(obj): return utils.get_wires(obj) class Element(Root): """Wrapping various IFC entity : https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/schema/ifcproductextension/lexical/ifcelement.htm """ def __init__(self, obj: "ElementFeature") -> None: super().__init__(obj) self.Type = "IfcElement" obj.Proxy = self @classmethod def create_from_ifc( cls, ifc_entity, ifc_importer: "IfcImporter" ) -> "ElementFeature": """Stantard FreeCAD FeaturePython Object creation method""" obj = super().create_from_ifc(ifc_entity, ifc_importer) ifc_importer.create_element_type( obj, ifcopenshell.util.element.get_type(ifc_entity) ) ifc_importer.material_creator.create(obj, ifc_entity) obj.Thickness = ifc_importer.guess_thickness(obj, ifc_entity) - obj.Placement = ifc_importer.get_global_placement(ifc_entity.ObjectPlacement) if FreeCAD.GuiUp: obj.ViewObject.Proxy = 0 return obj @classmethod def _init_properties(cls, obj: "ElementFeature") -> None: super()._init_properties(obj) ifc_attributes = "IFC Attributes" bem_category = "BEM" obj.addProperty("App::PropertyLink", "Material", ifc_attributes) obj.addProperty("App::PropertyLinkList", "FillsVoids", ifc_attributes) obj.addProperty("App::PropertyLinkList", "HasOpenings", ifc_attributes) obj.addProperty( "App::PropertyLinkListHidden", "ProvidesBoundaries", ifc_attributes ) obj.addProperty("App::PropertyLinkHidden", "IsTypedBy", ifc_attributes) obj.addProperty("App::PropertyFloat", "ThermalTransmittance", ifc_attributes) obj.addProperty("App::PropertyLinkList", "HostedElements", bem_category) obj.addProperty("App::PropertyLinkHidden", "HostElement", bem_category) obj.addProperty("App::PropertyLength", "Thickness", bem_category) @classmethod def read_from_ifc(cls, obj, ifc_entity): super().read_from_ifc(obj, ifc_entity) obj.Label = f"{obj.Id}_{obj.IfcType}" super().read_pset_from_ifc( obj, ifc_entity, [ "ThermalTransmittance", ], ) class ElementType(Root): """Wrapping various IFC entity : https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/schema/ifcproductextension/lexical/ifcelement.htm """ def __init__(self, obj: "ElementFeature") -> None: super().__init__(obj) self.Type = "IfcElementType" obj.Proxy = self @classmethod def create_from_ifc( cls, ifc_entity, ifc_importer: "IfcImporter" ) -> "ElementFeature": """Stantard FreeCAD FeaturePython Object creation method""" obj = super().create_from_ifc(ifc_entity, ifc_importer) ifc_importer.material_creator.create(obj, ifc_entity) obj.Thickness = ifc_importer.guess_thickness(obj, ifc_entity) if FreeCAD.GuiUp: obj.ViewObject.Proxy = 0 return obj @classmethod def _init_properties(cls, obj: "ElementFeature") -> None: super()._init_properties(obj) ifc_attributes = "IFC Attributes" bem_category = "BEM" obj.addProperty("App::PropertyLink", "Material", ifc_attributes) obj.addProperty("App::PropertyFloat", "ThermalTransmittance", ifc_attributes) obj.addProperty("App::PropertyLinkList", "ApplicableOccurrence", ifc_attributes) obj.addProperty("App::PropertyLength", "Thickness", bem_category) @classmethod def read_from_ifc(cls, obj, ifc_entity): super().read_from_ifc(obj, ifc_entity) obj.Label = f"{obj.Id}_{obj.IfcType}" super().read_pset_from_ifc( obj, ifc_entity, [ "ThermalTransmittance", ], ) class BEMBoundary: def __init__( self, obj: "BEMBoundaryFeature", boundary: "RelSpaceBoundaryFeature" ) -> None: self.Type = "BEMBoundary" # pylint: disable=invalid-name obj.Proxy = self category_name = "BEM" obj.addProperty("App::PropertyLinkHidden", "SourceBoundary", category_name) obj.SourceBoundary = boundary obj.addProperty("App::PropertyArea", "Area", category_name) obj.addProperty("App::PropertyArea", "AreaWithHosted", category_name) obj.Shape = boundary.Shape.copy() self.set_label(obj, boundary) obj.Area = boundary.Area obj.AreaWithHosted = boundary.AreaWithHosted @staticmethod def create(boundary: "RelSpaceBoundaryFeature", geo_type) -> "BEMBoundaryFeature": """Stantard FreeCAD FeaturePython Object creation method""" obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "BEMBoundary") BEMBoundary(obj, boundary) setattr(boundary, geo_type, obj) if FreeCAD.GuiUp: # ViewProviderRelSpaceBoundary(obj.ViewObject) obj.ViewObject.Proxy = 0 obj.ViewObject.ShapeColor = boundary.ViewObject.ShapeColor return obj @staticmethod def set_label(obj: "RelSpaceBoundaryFeature", source_boundary) -> None: obj.Label = source_boundary.Label @staticmethod def get_wires(obj): return utils.get_wires(obj) class Container(Root): """Representation of an IfcProject: https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcproject.htm""" def __init__(self, obj): super().__init__(obj) obj.Proxy = self @classmethod def create_from_ifc(cls, ifc_entity, ifc_importer: "IfcImporter"): obj = super().create_from_ifc(ifc_entity, ifc_importer) cls.set_label(obj) if FreeCAD.GuiUp: obj.ViewObject.DisplayMode = "Wireframe" return obj class Project(Root): """Representation of an IfcProject: https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcproject.htm""" @classmethod def create(cls) -> "ProjectFeature": obj = super().create() if FreeCAD.GuiUp: obj.ViewObject.DisplayMode = "Wireframe" return obj @classmethod def _init_properties(cls, obj: "ProjectFeature") -> None: super()._init_properties(obj) ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyString", "LongName", ifc_attributes) obj.addProperty("App::PropertyVector", "TrueNorth", ifc_attributes) obj.addProperty("App::PropertyVector", "WorldCoordinateSystem", ifc_attributes) owning_application = "OwningApplication" obj.addProperty( "App::PropertyString", "ApplicationIdentifier", owning_application ) obj.addProperty("App::PropertyString", "ApplicationVersion", owning_application) obj.addProperty( "App::PropertyString", "ApplicationFullName", owning_application ) @classmethod def read_from_ifc(cls, obj: "ProjectFeature", ifc_entity) -> None: super().read_from_ifc(obj, ifc_entity) obj.LongName = ifc_entity.LongName or "" true_north = ifc_entity.RepresentationContexts[0].TrueNorth obj.TrueNorth = ( FreeCAD.Vector(*true_north.DirectionRatios) if true_north else FreeCAD.Vector(0, 1) ) obj.WorldCoordinateSystem = FreeCAD.Vector( ifc_entity.RepresentationContexts[ 0 ].WorldCoordinateSystem.Location.Coordinates ) owning_application = ifc_entity.OwnerHistory.OwningApplication obj.ApplicationIdentifier = owning_application.ApplicationIdentifier obj.ApplicationVersion = owning_application.Version obj.ApplicationFullName = owning_application.ApplicationFullName @classmethod def set_label(cls, obj): obj.Label = f"{obj.IfcName}_{obj.LongName}" class Space(Root): """Representation of an IfcProject: https://standards.buildingsmart.org/IFC/RELEASE/IFC4_1/FINAL/HTML/link/ifcproject.htm""" @classmethod def create(cls) -> "SpaceFeature": obj = super().create() if FreeCAD.GuiUp: obj.ViewObject.ShapeColor = (0.33, 1.0, 1.0) obj.ViewObject.Transparency = 90 return obj @classmethod def _init_properties(cls, obj: "SpaceFeature") -> None: super()._init_properties(obj) ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyString", "LongName", ifc_attributes) category_name = "Boundaries" obj.addProperty("App::PropertyLink", "Boundaries", category_name) obj.addProperty("App::PropertyLink", "SecondLevel", category_name) obj.addProperty("App::PropertyLink", "SIA", category_name) obj.addProperty("App::PropertyLink", "SIA_Interiors", category_name) obj.addProperty("App::PropertyLink", "SIA_Exteriors", category_name) bem_category = "BEM" obj.addProperty("App::PropertyArea", "Area", bem_category) obj.addProperty("App::PropertyArea", "AreaAE", bem_category) @classmethod def read_from_ifc(cls, obj: "SpaceFeature", ifc_entity) -> None: super().read_from_ifc(obj, ifc_entity) ifc_importer = obj.Proxy.ifc_importer obj.Shape = ifc_importer.space_shape_by_brep(ifc_entity) obj.LongName = ifc_entity.LongName or "" space_full_name = f"{ifc_entity.Name} {ifc_entity.LongName}" obj.Label = space_full_name obj.Description = ifc_entity.Description or "" @classmethod def set_label(cls, obj: "SpaceFeature") -> None: obj.Label = f"{obj.IfcName}_{obj.LongName}" diff --git a/freecad/bem/materials.py b/freecad/bem/materials.py index 00ece72..8ed2cab 100644 --- a/freecad/bem/materials.py +++ b/freecad/bem/materials.py @@ -1,421 +1,427 @@ # coding: utf8 """This module reads IfcRelSpaceBoundary from an IFC file and display them in FreeCAD © All rights reserved. ECOLE POLYTECHNIQUE FEDERALE DE LAUSANNE, Switzerland, Laboratory CNPA, 2019-2020 See the LICENSE.TXT file for more details. Author : Cyril Waechter """ import typing import ifcopenshell import FreeCAD from freecad.bem import utils if typing.TYPE_CHECKING: from freecad.bem.typing import ( # pylint: disable=no-name-in-module, import-error MaterialFeature, LayerSetFeature, ConstituentSetFeature, ) class MaterialCreator: def __init__(self, ifc_importer=None): self.obj = None self.materials = {} self.material_layer_sets = {} self.material_constituent_sets = {} self.material_profile_sets = {} self.ifc_scale = 1 self.fc_scale = 1 if ifc_importer: self.ifc_scale = ifc_importer.ifc_scale self.fc_scale = ifc_importer.fc_scale self.ifc_importer = ifc_importer def create(self, obj, ifc_entity): self.obj = obj self.parse_material(ifc_entity) def parse_material(self, ifc_entity): self.parse_associations(ifc_entity) if self.obj.Material: return entity_type = ifcopenshell.util.element.get_type(ifc_entity) if entity_type: self.parse_associations(entity_type) if self.obj.Material: return # When an element is composed of multiple elements if ifc_entity.IsDecomposedBy: # TODO: See in a real case how to handle every part and not only the first ifc_entity = ifc_entity.IsDecomposedBy[0].RelatedObjects[0] self.parse_associations(ifc_entity) + # Set placement except for object type + if hasattr(ifc_entity, "ObjectPlacement"): + self.obj.Placement = self.ifc_importer.get_global_placement( + ifc_entity.ObjectPlacement + ) + def parse_associations(self, ifc_entity): for association in ifc_entity.HasAssociations: if association.is_a("IfcRelAssociatesMaterial"): material_select = association.RelatingMaterial if self.is_material_definition(material_select): self.assign_material(material_select) elif material_select.is_a("IfcMaterialLayerSetUsage"): self.create_layer_set_usage(material_select) elif material_select.is_a("IfcMaterialList"): self.create_constituent_set_from_material_list( material_select, ifc_entity ) else: raise NotImplementedError( f"{material_select.is_a()} not handled yet" ) @staticmethod def is_material_definition(material_select): """Material Definition is new to IFC4. So doing material_select.is_a("MaterialDefinition") in IFC2x3 would return False event if it is in IFC4 schema.""" valid_class = ( "IfcMaterial", "IfcMaterialLayer", "IfcMaterialLayerSet", "IfcMaterialProfile", "IfcMaterialProfileSet", "IfcMaterialConstituent", "IfcMaterialConstituentSet", ) return material_select.is_a() in valid_class def create_layer_set_usage(self, usage): self.assign_material(usage.ForLayerSet) self.obj.Material.LayerSetDirection = usage.LayerSetDirection self.obj.Material.DirectionSense = usage.DirectionSense def assign_material(self, material_select): if material_select.is_a("IfcMaterial"): self.obj.Material = self.create_single(material_select) elif material_select.is_a("IfcMaterialLayerSet"): self.obj.Material = self.create_layer_set(material_select) utils.append(self.obj.Material, "AssociatedTo", self.obj) elif material_select.is_a("IfcMaterialConstituentSet"): self.obj.Material = self.create_constituent_set(material_select) elif material_select.is_a("IfcMaterialProfileSet"): self.obj.Material = self.create_profile_set(material_select) else: raise NotImplementedError(f"{material_select.is_a()} not handled yet") def create_single(self, material): if material.Name not in self.materials: return self.create_new_single(material) return self.materials[material.Name] def create_new_single(self, material): fc_material = Material.create(material) self.materials[material.Name] = fc_material return fc_material def create_layer_set(self, layer_set): if layer_set.LayerSetName not in self.material_layer_sets: fc_layer_set = LayerSet.create(layer_set) layers = [] layers_thickness = [] for layer in layer_set.MaterialLayers: layers.append(self.create_single(layer.Material)) layers_thickness.append(layer.LayerThickness * self.ifc_scale) fc_layer_set.MaterialLayers = layers fc_layer_set.Thicknesses = layers_thickness if not fc_layer_set.TotalThickness: fc_layer_set.TotalThickness = sum(layers_thickness) * self.fc_scale self.material_layer_sets[fc_layer_set.IfcName] = fc_layer_set return fc_layer_set return self.material_layer_sets[layer_set.LayerSetName] def create_constituent_set(self, constituent_set): if constituent_set.Name not in self.material_constituent_sets: fc_constituent_set = ConstituentSet.create(constituent_set) constituents = [] constituents_fraction = [] constituents_categories = [] for constituent in constituent_set.MaterialConstituents: constituents.append(self.create_single(constituent.Material)) constituents_fraction.append(constituent.Fraction or 0) constituents_categories.append(constituent.Category or "") fc_constituent_set.MaterialConstituents = constituents fc_constituent_set.Fractions = constituents_fraction fc_constituent_set.Categories = constituents_categories self.material_constituent_sets[ fc_constituent_set.IfcName ] = fc_constituent_set return fc_constituent_set return self.material_constituent_sets[constituent_set.Name] def create_constituent_set_from_material_list(self, material_list, ifc_element): constituent_set = ConstituentSet.create() constituent_set.IfcType = material_list.is_a() constituent_set.IfcName = self.get_type_name(ifc_element) constituent_set.Id = material_list.id() constituent_set.Label = f"{constituent_set.Id}_{constituent_set.IfcName}" materials = material_list.Materials constituent_set.Fractions = [1 / len(materials)] * len(materials) constituent_set.Categories = ["MaterialList"] * len(materials) material_constituents = list() for material in materials: material_constituents.append(self.create_single(material)) constituent_set.MaterialConstituents = material_constituents self.obj.Material = constituent_set def create_profile_set(self, profile_set): if profile_set.Name not in self.material_profile_sets: fc_profile_set = ProfileSet.create(profile_set) profiles = [] profiles_categories = [] for profile in profile_set.MaterialProfiles: profiles.append(self.create_single(profile.Material)) profiles_categories.append(profile.Category or "") fc_profile_set.MaterialProfiles = profiles fc_profile_set.Categories = profiles_categories self.material_profile_sets[fc_profile_set.IfcName] = fc_profile_set return fc_profile_set return self.material_profile_sets[profile_set.Name] def get_type_name(self, ifc_element): if ifc_element.is_a("IfcTypeObject"): return ifc_element.Name if ifc_element.ObjectType: return ifc_element.ObjectType for definition in ifc_element.IsDefinedBy: if definition.is_a("IfcRelDefinesByType"): return definition.RelatingType.Name class ConstituentSet: attributes = () psets_dict = {} parts_name = "Constituents" part_name = "Constituent" part_props = ("MaterialConstituents", "Fractions", "Categories") part_attribs = ("Id", "Fraction", "Category") def __init__(self, obj, ifc_entity): self.ifc_entity = ifc_entity self._init_properties(obj, ifc_entity) self.Type = "MaterialConstituentSet" # pylint: disable=invalid-name obj.Proxy = self @classmethod def create(cls, ifc_entity=None) -> "ConstituentSetFeature": """Stantard FreeCAD FeaturePython Object creation method ifc_entity : Optionnally provide a base entity. """ obj = FreeCAD.ActiveDocument.addObject( "Part::FeaturePython", "MaterialConstituentSet" ) ConstituentSet(obj, ifc_entity) return obj def _init_properties(self, obj: "ConstituentSetFeature", ifc_entity) -> None: obj.addProperty("App::PropertyString", "IfcType", "IFC") obj.addProperty("App::PropertyFloatList", "Fractions", "BEM") obj.addProperty("App::PropertyStringList", "Categories", "BEM") ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyInteger", "Id", ifc_attributes) obj.addProperty("App::PropertyString", "IfcName", ifc_attributes) obj.addProperty("App::PropertyString", "Description", ifc_attributes) obj.addProperty("App::PropertyLinkList", "MaterialConstituents", ifc_attributes) if not ifc_entity: return obj.Id = ifc_entity.id() obj.IfcName = ifc_entity.Name or "" obj.Description = ifc_entity.Description or "" obj.Label = f"{obj.Id}_{obj.IfcName}" obj.IfcType = ifc_entity.is_a() class LayerSet: attributes = ("TotalThickness",) psets_dict = {} parts_name = "Layers" part_name = "Layer" part_props = ("MaterialLayers", "Thicknesses") part_attribs = ("Id", "Thickness") def __init__(self, obj: "LayerSetFeature", ifc_entity) -> None: self.ifc_entity = ifc_entity self._init_properties(obj, ifc_entity) self.materials = {} self.Type = "MaterialLayerSet" # pylint: disable=invalid-name obj.Proxy = self @classmethod def create(cls, ifc_entity=None) -> "ConstituentSetFeature": """Stantard FreeCAD FeaturePython Object creation method ifc_entity : Optionnally provide a base entity. """ obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Material") LayerSet(obj, ifc_entity) return obj def _init_properties(self, obj: "LayerSetFeature", ifc_entity) -> None: obj.addProperty("App::PropertyString", "IfcType", "IFC") obj.addProperty("App::PropertyFloatList", "Thicknesses", "BEM") ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyInteger", "Id", ifc_attributes) obj.addProperty("App::PropertyLinkList", "MaterialLayers", ifc_attributes) obj.addProperty("App::PropertyString", "IfcName", ifc_attributes) obj.addProperty("App::PropertyString", "Description", ifc_attributes) obj.addProperty("App::PropertyLength", "TotalThickness", ifc_attributes) obj.addProperty( "App::PropertyEnumeration", "LayerSetDirection", ifc_attributes ).LayerSetDirection = ["AXIS1", "AXIS2", "AXIS3"] obj.addProperty( "App::PropertyEnumeration", "DirectionSense", ifc_attributes ).DirectionSense = ["POSITIVE", "NEGATIVE"] obj.addProperty("App::PropertyLinkListHidden", "AssociatedTo", ifc_attributes) if not ifc_entity: return obj.Id = ifc_entity.id() obj.IfcName = ifc_entity.LayerSetName or "" # Description is new to IFC4 so IFC2x3 raise attribute error try: obj.Description = ifc_entity.Description or "" except AttributeError: pass obj.Label = f"{obj.Id}_{obj.IfcName}" obj.IfcType = ifc_entity.is_a() if ifc_entity.is_a("IfcWall"): obj.LayerSetDirection = "AXIS2" elif ifc_entity.is_a("IfcSlab") or ifc_entity.is_a("IfcRoof"): obj.LayerSetDirection = "AXIS3" obj.DirectionSense = "POSITIVE" class Material: attributes = ("Category",) psets_dict = { "Pset_MaterialCommon": ("MassDensity", "Porosity"), "Pset_MaterialOptical": ( "VisibleTransmittance", "SolarTransmittance", "ThermalIrTransmittance", "ThermalIrEmissivityBack", "ThermalIrEmissivityFront", ), "Pset_MaterialThermal": ( "SpecificHeatCapacity", "ThermalConductivity", ), } parts_name = "" part_name = "" part_props = () part_attribs = () def __init__(self, obj, ifc_entity=None): self.ifc_entity = ifc_entity self._init_properties(obj) self.Type = "Material" # pylint: disable=invalid-name obj.Proxy = self @classmethod def create(cls, ifc_entity=None) -> "MaterialFeature": """Stantard FreeCAD FeaturePython Object creation method ifc_entity : Optionnally provide a base entity. """ obj = FreeCAD.ActiveDocument.addObject("Part::FeaturePython", "Material") cls(obj, ifc_entity) return obj def _init_properties(self, obj) -> None: obj.addProperty("App::PropertyString", "IfcType", "IFC") ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyInteger", "Id", ifc_attributes) obj.addProperty("App::PropertyString", "IfcName", ifc_attributes) obj.addProperty("App::PropertyString", "Description", ifc_attributes) obj.addProperty("App::PropertyString", "Category", ifc_attributes) pset_name = "Pset_MaterialCommon" obj.addProperty("App::PropertyFloat", "MassDensity", pset_name) obj.addProperty("App::PropertyFloat", "Porosity", pset_name) pset_name = "Pset_MaterialOptical" obj.addProperty("App::PropertyFloat", "VisibleTransmittance", pset_name) obj.addProperty("App::PropertyFloat", "SolarTransmittance", pset_name) obj.addProperty("App::PropertyFloat", "ThermalIrTransmittance", pset_name) obj.addProperty("App::PropertyFloat", "ThermalIrEmissivityBack", pset_name) obj.addProperty("App::PropertyFloat", "ThermalIrEmissivityFront", pset_name) pset_name = "Pset_MaterialThermal" obj.addProperty("App::PropertyFloat", "SpecificHeatCapacity", pset_name) obj.addProperty("App::PropertyFloat", "ThermalConductivity", pset_name) ifc_entity = self.ifc_entity if not ifc_entity: return obj.Id = ifc_entity.id() obj.IfcName = ifc_entity.Name obj.Label = f"{obj.Id}_{obj.IfcName}" # Description is new to IFC4 so IFC2x3 raise attribute error obj.Description = getattr(ifc_entity, "Description", "") or "" obj.IfcType = ifc_entity.is_a() for pset in getattr(ifc_entity, "HasProperties", ()): if pset.Name in self.psets_dict.keys(): for prop in pset.Properties: if prop.Name in self.psets_dict[pset.Name]: setattr(obj, prop.Name, prop.NominalValue.wrappedValue) class ProfileSet: attributes = () psets_dict = {} parts_name = "Profiles" part_name = "Profile" part_props = ("MaterialProfiles",) part_attribs = ( "Id", "Category", ) def __init__(self, obj, ifc_entity): self.ifc_entity = ifc_entity self._init_properties(obj, ifc_entity) self.Type = "ProfileSet" # pylint: disable=invalid-name obj.Proxy = self @classmethod def create(cls, ifc_entity=None) -> "ProfileSetFeature": """Stantard FreeCAD FeaturePython Object creation method ifc_entity : Optionnally provide a base entity. """ obj = FreeCAD.ActiveDocument.addObject( "Part::FeaturePython", "MaterialProfileSet" ) ProfileSet(obj, ifc_entity) return obj def _init_properties(self, obj: "ProfileSetFeature", ifc_entity) -> None: obj.addProperty("App::PropertyString", "IfcType", "IFC") obj.addProperty("App::PropertyStringList", "Categories", "BEM") ifc_attributes = "IFC Attributes" obj.addProperty("App::PropertyInteger", "Id", ifc_attributes) obj.addProperty("App::PropertyString", "IfcName", ifc_attributes) obj.addProperty("App::PropertyString", "Description", ifc_attributes) obj.addProperty("App::PropertyLinkList", "MaterialProfiles", ifc_attributes) if not ifc_entity: return obj.Id = ifc_entity.id() obj.IfcName = ifc_entity.Name or "" obj.Description = ifc_entity.Description or "" obj.Label = f"{obj.Id}_{obj.IfcName}" obj.IfcType = ifc_entity.is_a() def get_type(ifc_entity): if ifc_entity.ObjectType: return ifc_entity.ObjectType for definition in ifc_entity.IsDefinedBy: if definition.is_a("IfcRelDefineByType"): return definition.RelatingType.Name