diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 170cbf2a56b..640feb95a8c 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -39,14 +39,14 @@ class MaterialCreator: def __init__(self, ifc_import_settings, ifc_importer): self.mesh = None self.materials = {} + self.styles = {} self.parsed_meshes = set() self.ifc_import_settings = ifc_import_settings self.ifc_importer = ifc_importer def create(self, element, obj, mesh): - self.obj = obj self.mesh = mesh - self.parse_material(element) + self.obj = obj if (hasattr(element, "Representation") and not element.Representation) or ( hasattr(element, "RepresentationMaps") and not element.RepresentationMaps ): @@ -55,7 +55,7 @@ def create(self, element, obj, mesh): return self.parsed_meshes.add(self.mesh.name) if self.parse_representations(element): - self.assign_material_slots_to_faces(obj) + self.assign_material_slots_to_faces() def parse_representations(self, element): has_parsed = False @@ -80,51 +80,23 @@ def parse_representation(self, representation): def parse_representation_item(self, item): if not item.StyledByItem: return - - item_id = self.mesh.BIMMeshProperties.ifc_item_ids.add() - item_id.name = str(item.id()) - - styled_item = item.StyledByItem[0] # Cardinality is S[0:1] - style_name = self.get_surface_style_name(styled_item) - - if not style_name: + style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")] + if not style_ids: return - - if self.mesh.materials.get(style_name): - item_id.slot_index = self.mesh.materials.find(style_name) - return True - - style = bpy.data.materials.get(style_name) - if not style: - style = bpy.data.materials.new(style_name) - self.parse_styled_item(styled_item, style) - - self.assign_style_to_mesh(style) - item_id.slot_index = len(self.mesh.materials) - 1 + for style_id in style_ids: + material = self.styles[style_id] + if self.mesh.materials.find(material.name) == -1: + self.mesh.materials.append(material) return True - def assign_material_slots_to_faces(self, obj): + def assign_material_slots_to_faces(self): if "ios_materials" not in self.mesh or not self.mesh["ios_materials"]: return - if len(obj.material_slots) == 1: + if len(self.obj.material_slots) == 1: return material_to_slot = {} for i, material in enumerate(self.mesh["ios_materials"]): - if material == "NULLMAT": - continue - elif "surface-style-" in material: - material = material.split("-")[2] - if len(bytes(material, "utf-8")) > 63: # Blender material names are up to 63 UTF-8 bytes - material = bytes(material, "utf-8")[0:63].decode("utf-8") - - slot_index = obj.material_slots.find(material) - if slot_index == -1: - # If we can't find the material, it is possible that the - # material name is duplicated, and so a '.001' is added. - # The maximum characters for the material name is 59 in this - # scenario. - material = bytes(material, "utf-8")[0:59].decode("utf-8") - slot_index = [self.canonicalise_material_name(s.name) for s in obj.material_slots].index(material) + slot_index = self.obj.material_slots.find(self.styles[material].name) material_to_slot[i] = slot_index if len(self.mesh.polygons) == len(self.mesh["ios_material_ids"]): @@ -133,130 +105,6 @@ def assign_material_slots_to_faces(self, obj): ] self.mesh.polygons.foreach_set("material_index", material_index) - def canonicalise_material_name(self, name): - return re.sub(r"\.[0-9]{3}$", "", name) - - def parse_material(self, element): - for association in element.HasAssociations: - if association.is_a("IfcRelAssociatesMaterial"): - material_select = association.RelatingMaterial - if material_select.is_a("IfcMaterialDefinition"): - self.create_definition(material_select) - elif material_select.is_a("IfcMaterialUsageDefinition"): - self.create_usage_definition(material_select) - elif material_select.is_a("IfcMaterialList"): - # Note that lists are deprecated - self.create_material_list(material_select) - # To support IFC2X3 equivalent of IfcMaterialDefinition - elif material_select.is_a("IfcMaterial") or material_select.is_a("IfcMaterialLayerSet"): - self.create_definition(material_select) - # To support IFC2X3 equivalent of IfcMaterialUsageDefinition - elif material_select.is_a("IfcMaterialLayerSetUsage"): - self.create_usage_definition(material_select) - # IFC2X3 supports assigning a material layer directly. This is silly. - elif material_select.is_a("IfcMaterialLayer"): - pass - - def create_layer_set_usage(self, usage): - # TODO import rest of the layer set usage data - self.create_definition(usage.ForLayerSet) - - def create_definition(self, material): - if material.is_a("IfcMaterial"): - self.create_single(material) - elif material.is_a("IfcMaterialConstituentSet"): - self.create_constituent_set(material) - elif material.is_a("IfcMaterialLayerSet"): - self.create_layer_set(material) - elif material.is_a("IfcMaterialProfileSet"): - self.create_profile_set(material) - - def create_usage_definition(self, material): - if material.is_a("IfcMaterialLayerSetUsage"): - self.create_layer_set_usage(material) - elif material.is_a("IfcMaterialProfileSetUsage"): - pass # TODO - - def create_single(self, material): - if material.Name not in self.materials: - self.create_new_single(material) - - def create_layer_set(self, layer_set): - for layer in layer_set.MaterialLayers: - if layer.Material: - if layer.Material.Name not in self.materials: - self.create_new_single(layer.Material) - - def create_constituent_set(self, constituent_set): - for constituent in constituent_set.MaterialConstituents: - if constituent.Material.Name not in self.materials: - self.create_new_single(constituent.Material) - - def create_profile_set(self, profile_set): - for profile in profile_set.MaterialProfiles: - if profile.Material.Name not in self.materials: - self.create_new_single(profile.Material) - - def create_material_list(self, material_list): - for material in material_list.Materials: - if material.Name not in self.materials: - self.create_new_single(material) - - def create_new_single(self, material): - self.materials[material.Name] = obj = bpy.data.materials.new(material.Name) - obj.BIMObjectProperties.ifc_definition_id = int(material.id()) - if not material.HasRepresentation or not material.HasRepresentation[0].Representations: - return - for representation in material.HasRepresentation[0].Representations: - if not representation.Items: - continue - for item in representation.Items: - if not item.is_a("IfcStyledItem"): - continue - self.parse_styled_item(item, obj) - - def get_surface_style_name(self, styled_item): - if styled_item.Name: - return styled_item.Name - styles = self.get_styled_item_styles(styled_item) - for style in styles: - if not style.is_a("IfcSurfaceStyle"): - continue - if style.Name: - return style.Name - return str(style.id()) - return None # We only support surface styles right now - - def parse_styled_item(self, styled_item, material): - styles = self.get_styled_item_styles(styled_item) - for style in styles: - if not style.is_a("IfcSurfaceStyle"): - continue - material.BIMMaterialProperties.ifc_style_id = int(style.id()) - for surface_style in style.Styles: - if surface_style.is_a("IfcSurfaceStyleShading"): - alpha = 1.0 - # Transparency was added in IFC4 - if hasattr(surface_style, "Transparency") and surface_style.Transparency: - alpha = 1 - surface_style.Transparency - material.diffuse_color = ( - surface_style.SurfaceColour.Red, - surface_style.SurfaceColour.Green, - surface_style.SurfaceColour.Blue, - alpha, - ) - - # IfcPresentationStyleAssignment is deprecated as of IFC4 - # However it is still widely used thanks to Revit :( - def get_styled_item_styles(self, styled_item): - styles = [] - for style in styled_item.Styles: - if style.is_a("IfcPresentationStyleAssignment"): - styles.extend(self.get_styled_item_styles(style)) - else: - styles.append(style) - return styles - def resolve_mapped_representation_items(self, representation): items = [] for item in representation.Items: @@ -266,11 +114,6 @@ def resolve_mapped_representation_items(self, representation): items.append(item) return items - def assign_style_to_mesh(self, material): - if not self.mesh: - return - self.mesh.materials.append(material) - class IfcImporter: def __init__(self, ifc_import_settings): @@ -336,6 +179,10 @@ def execute(self): self.profile_code("Create aggregate tree") self.create_openings_collection() self.profile_code("Create opening collection") + self.create_materials() + self.profile_code("Create materials") + self.create_styles() + self.profile_code("Create styles") self.process_element_filter() self.profile_code("Process element filter") self.parse_native_elements() @@ -718,7 +565,9 @@ def create_products(self): def create_empty_products(self): for element in self.file.by_type("IfcProduct"): - if element.GlobalId in self.added_data: + if element.id() in self.added_data: + continue + if element.is_a("IfcPort"): continue if not element.Representation: self.create_product(element) @@ -837,8 +686,8 @@ def create_product(self, element, shape=None, mesh=None): def get_representation_item_material_name(self, item): if not item.StyledByItem: return - styled_item = item.StyledByItem[0] - return self.material_creator.get_surface_style_name(styled_item) + style_ids = [e.id() for e in self.ifc_importer.file.traverse(item.StyledByItem[0]) if e.is_a("IfcSurfaceStyle")] + return style_ids[0] if style_ids else None def create_native_faceted_brep(self, element, mesh_name): # TODO: georeferencing? @@ -859,6 +708,7 @@ def create_native_faceted_brep(self, element, mesh_name): for representation in self.native_data[element.GlobalId]["representations"]: for item in representation["raw"].Items: + # TODO: if I reimplement native faceted breps, recheck this material implementation materials.append(self.get_representation_item_material_name(item) or "NULLMAT") mesh = item.get_info_2(recursive=True) # See bug #841 total_item_polygons = 0 @@ -1145,6 +995,47 @@ def create_openings_collection(self): self.opening_collection = bpy.data.collections.new("IfcOpeningElements") self.project["blender"].children.link(self.opening_collection) + def create_materials(self): + for material in self.file.by_type("IfcMaterial"): + blender_material = bpy.data.materials.new(material.Name) + blender_material.BIMObjectProperties.ifc_definition_id = material.id() + self.material_creator.materials[material.id()] = blender_material + blender_material.use_fake_user = True + + def create_styles(self): + parsed_styles = set() + + for material_definition_representation in self.file.by_type("IfcMaterialDefinitionRepresentation"): + material = material_definition_representation.RepresentedMaterial + for representation in material_definition_representation.Representations: + for style in [e for e in self.file.traverse(representation) if e.is_a("IfcSurfaceStyle")]: + blender_material = self.material_creator.materials[material.id()] + self.create_style(style, blender_material) + parsed_styles.add(style.id()) + + for style in self.file.by_type("IfcSurfaceStyle"): + if style.id() in parsed_styles: + continue + name = style.Name or str(style.id()) + blender_material = bpy.data.materials.new(name) + self.create_style(style, blender_material) + + def create_style(self, style, blender_material): + blender_material.BIMMaterialProperties.ifc_style_id = style.id() + self.material_creator.styles[style.id()] = blender_material + for surface_style in style.Styles: + if surface_style.is_a("IfcSurfaceStyleShading"): + alpha = 1.0 + # Transparency was added in IFC4 + if hasattr(surface_style, "Transparency") and surface_style.Transparency: + alpha = 1 - surface_style.Transparency + blender_material.diffuse_color = ( + surface_style.SurfaceColour.Red, + surface_style.SurfaceColour.Green, + surface_style.SurfaceColour.Blue, + alpha, + ) + def get_name(self, element): return "{}/{}".format(element.is_a(), element.Name) @@ -1161,8 +1052,8 @@ def purge_diff(self): bpy.ops.object.delete({"selected_objects": objects_to_purge}) def place_objects_in_spatial_tree(self): - for global_id, obj in self.added_data.items(): - self.place_object_in_spatial_tree(self.file.by_guid(global_id), obj) + for ifc_definition_id, obj in self.added_data.items(): + self.place_object_in_spatial_tree(self.file.by_id(ifc_definition_id), obj) def place_object_in_spatial_tree(self, element, obj): if element.is_a("IfcProject"): @@ -1349,9 +1240,7 @@ def create_camera(self, element, shape): if "TargetView" in pset: camera.BIMCameraProperties.target_view = pset["TargetView"] if "Scale" in pset: - valid_scales = [ - i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1] - ] + valid_scales = [i[0] for i in getDiagramScales(None, None) if pset["Scale"] == i[0].split("|")[-1]] if valid_scales: camera.BIMCameraProperties.diagram_scale = valid_scales[0] else: @@ -1414,14 +1303,7 @@ def create_mesh(self, element, shape): edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)] mesh.from_pydata(vertices, edges, []) - ios_materials = [] - for mat in geometry.materials: - # See bug #866 - if mat.original_name(): - ios_materials.append(mat.original_name()) - else: - ios_materials.append(mat.name) - mesh["ios_materials"] = ios_materials + mesh["ios_materials"] = [int(m.name.split("-")[2]) for m in geometry.materials] mesh["ios_material_ids"] = geometry.material_ids return mesh except: @@ -1473,7 +1355,7 @@ def set_default_context(self): break def link_element(self, element, obj): - self.added_data[element.GlobalId] = obj + self.added_data[element.id()] = obj IfcStore.link_element(element, obj)