From ccf09b962a2789cc573b498bc3cd9c1368d208f4 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Mon, 3 Jul 2023 14:31:54 +0500 Subject: [PATCH] Update type manager thumbnails on editing profiles Example - https://imgur.com/a/IcV8IIR --- .../blenderbim/bim/module/model/product.py | 102 +----------------- .../blenderbim/bim/module/model/profile.py | 26 ++++- src/blenderbim/blenderbim/tool/loader.py | 1 - src/blenderbim/blenderbim/tool/model.py | 99 +++++++++++++++++ 4 files changed, 125 insertions(+), 103 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index f938ea1e49..f85037b07b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -292,8 +292,6 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): offset: bpy.props.IntProperty() def _execute(self, context): - from PIL import Image, ImageDraw - if bpy.app.background: return @@ -310,106 +308,11 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): offset = 0 queue = queue[offset : offset + 9] - unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) - while queue: # if bpy.app.is_job_running("RENDER_PREVIEW") does not seem to reflect asset preview generation element = queue.pop() - obj = tool.Ifc.get_object(element) - - if not obj: - continue # Nothing to process - elif AuthoringData.type_thumbnails.get(element.id(), None): - continue # Already processed - elif obj.preview and obj.preview.icon_id: - AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id - continue - - if obj.data: - obj.asset_generate_preview() - while not obj.preview: - pass - else: - size = 128 - img = Image.new("RGBA", (size, size)) - draw = ImageDraw.Draw(img) - - material = ifcopenshell.util.element.get_material(element) - if material and material.is_a("IfcMaterialProfileSet"): - profile = material.MaterialProfiles[0].Profile - tool.Profile.draw_image_for_ifc_profile(draw, profile, size) - - elif material and material.is_a("IfcMaterialLayerSet"): - thicknesses = [l.LayerThickness for l in material.MaterialLayers] - total_thickness = sum(thicknesses) - si_total_thickness = total_thickness * unit_scale - if si_total_thickness <= 0.051: - width = 10 - elif si_total_thickness <= 0.11: - width = 20 - elif si_total_thickness <= 0.21: - width = 30 - elif si_total_thickness <= 0.31: - width = 40 - else: - width = 50 - - height = 100 - - is_horizontal = False - if element.is_a("IfcSlabType"): - is_horizontal = True - - parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") - if parametric: - layer_set_direction = parametric.get("LayerSetDirection", None) - if layer_set_direction == "AXIS2": - is_horizontal = False - elif layer_set_direction == "AXIS3": - is_horizontal = True - - if is_horizontal: - width, height = height, width - - x_offset = (size / 2) - (width / 2) - y_offset = (size / 2) - (height / 2) - draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) - current_thickness = 0 - del thicknesses[-1] - for thickness in thicknesses: - current_thickness += thickness - if element.is_a("IfcSlabType"): - y = (current_thickness / total_thickness) * height - line = [x_offset, y_offset + y, x_offset + width, y_offset + y] - else: - x = (current_thickness / total_thickness) * width - line = [x_offset + x, y_offset, x_offset + x, y_offset + height] - draw.line(line, fill="white", width=2) - elif False: - # TODO: things like parametric duct segments - pass - elif not element.RepresentationMaps: - # Empties are represented by a generic thumbnail - width = height = 100 - x_offset = (size / 2) - (width / 2) - y_offset = (size / 2) - (height / 2) - draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2) - draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2) - draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) - else: - draw.line([0, 0, size, size], fill="red", width=2) - draw.line([0, size, size, 0], fill="red", width=2) - - pixels = [item for sublist in img.getdata() for item in sublist] - - obj.asset_generate_preview() - while not obj.preview: - pass - - obj.preview.image_size = size, size - obj.preview.image_pixels_float = pixels - - queue.append(element) + if tool.Model.update_thumbnail_for_element(element): + queue.append(element) return {"FINISHED"} @@ -472,7 +375,6 @@ class MirrorElements(bpy.types.Operator, tool.Ifc.Operator): obj.matrix_world = newmat - def generate_box(usecase_path, ifc_file, settings): box_context = ifcopenshell.util.representation.get_context(ifc_file, "Model", "Box", "MODEL_VIEW") if not box_context: diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 113b192da4..6e4b4ec16c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -145,12 +145,21 @@ class DumbProfileRegenerator: objs = [] if not profile: return + + element_types = set() for element in self.get_elements_using_profile(profile): obj = tool.Ifc.get_object(element) if obj: objs.append(obj) + if element.is_a("IfcElementType"): + element_types.add(element) + DumbProfileRecalculator().recalculate(objs) + # update related thumbnails + for element in self.get_element_types_using_profile(profile): + tool.Model.update_thumbnail_for_element(element, refresh=True) + def regenerate_from_profile(self, usecase_path, ifc_file, settings): self.file = ifc_file objs = [] @@ -165,9 +174,10 @@ class DumbProfileRegenerator: def get_elements_using_profile(self, profile): results = [] - for profile_set in [ + profile_sets = [ mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") - ]: + ] + for profile_set in profile_sets: for inverse in self.file.get_inverse(profile_set): if not inverse.is_a("IfcMaterialProfileSetUsage"): continue @@ -181,6 +191,18 @@ class DumbProfileRegenerator: results.extend(rel.RelatedObjects) return results + def get_element_types_using_profile(self, profile): + results = [] + profile_sets = [ + mp.ToMaterialProfileSet[0] for mp in self.file.get_inverse(profile) if mp.is_a("IfcMaterialProfile") + ] + for profile_set in profile_sets: + for inverse in self.file.get_inverse(profile_set): + if not inverse.is_a("IfcRelAssociatesMaterial"): + continue + results.extend(inverse.RelatedObjects) + return results + def regenerate_from_type(self, usecase_path, ifc_file, settings): obj = tool.Ifc.get_object(settings["related_object"]) if not obj or not obj.data or not obj.data.BIMMeshProperties.ifc_definition_id: diff --git a/src/blenderbim/blenderbim/tool/loader.py b/src/blenderbim/blenderbim/tool/loader.py index e695ebafbb..1bb487728d 100644 --- a/src/blenderbim/blenderbim/tool/loader.py +++ b/src/blenderbim/blenderbim/tool/loader.py @@ -202,7 +202,6 @@ class Loader(blenderbim.core.tool.Loader): ifc_path = Path(tool.Ifc.get_path()) image_url = ifc_path.parent / image_url - # import pdb; pdb.set_trace() if not image_url.exists(): print(f"WARNING. Couldn't find texture by path {image_url}, it will be skipped.") continue diff --git a/src/blenderbim/blenderbim/tool/model.py b/src/blenderbim/blenderbim/tool/model.py index c9d8249a4d..c958e47fbe 100644 --- a/src/blenderbim/blenderbim/tool/model.py +++ b/src/blenderbim/blenderbim/tool/model.py @@ -29,6 +29,7 @@ from mathutils import Matrix, Vector from blenderbim.bim import import_ifc from blenderbim.bim.module.geometry.helper import Helper import collections +from blenderbim.bim.module.model.data import AuthoringData class Model(blenderbim.core.tool.Model): @@ -630,3 +631,101 @@ class Model(blenderbim.core.tool.Model): is_global=True, should_sync_changes_first=False, ) + + @classmethod + def update_thumbnail_for_element(cls, element, refresh=False): + if bpy.app.background: + return + + from PIL import Image, ImageDraw + + obj = tool.Ifc.get_object(element) + if not obj: + return # Nothing to process + + if not refresh and element.id() in AuthoringData.type_thumbnails: + return # Already processed + + obj.asset_generate_preview() + while not obj.preview: + pass + + # if object has .data we can use default blender .asset_generate_preview() + if not obj.data: + unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get()) + size = 128 + img = Image.new("RGBA", (size, size)) + draw = ImageDraw.Draw(img) + + material = ifcopenshell.util.element.get_material(element) + if material and material.is_a("IfcMaterialProfileSet"): + profile = material.MaterialProfiles[0].Profile + tool.Profile.draw_image_for_ifc_profile(draw, profile, size) + + elif material and material.is_a("IfcMaterialLayerSet"): + thicknesses = [l.LayerThickness for l in material.MaterialLayers] + total_thickness = sum(thicknesses) + si_total_thickness = total_thickness * unit_scale + if si_total_thickness <= 0.051: + width = 10 + elif si_total_thickness <= 0.11: + width = 20 + elif si_total_thickness <= 0.21: + width = 30 + elif si_total_thickness <= 0.31: + width = 40 + else: + width = 50 + + height = 100 + + is_horizontal = False + if element.is_a("IfcSlabType"): + is_horizontal = True + + parametric = ifcopenshell.util.element.get_psets(element).get("EPset_Parametric") + if parametric: + layer_set_direction = parametric.get("LayerSetDirection", None) + if layer_set_direction == "AXIS2": + is_horizontal = False + elif layer_set_direction == "AXIS3": + is_horizontal = True + + if is_horizontal: + width, height = height, width + + x_offset = (size / 2) - (width / 2) + y_offset = (size / 2) - (height / 2) + draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) + current_thickness = 0 + del thicknesses[-1] + for thickness in thicknesses: + current_thickness += thickness + if element.is_a("IfcSlabType"): + y = (current_thickness / total_thickness) * height + line = [x_offset, y_offset + y, x_offset + width, y_offset + y] + else: + x = (current_thickness / total_thickness) * width + line = [x_offset + x, y_offset, x_offset + x, y_offset + height] + draw.line(line, fill="white", width=2) + elif False: + # TODO: things like parametric duct segments + pass + elif not element.RepresentationMaps: + # Empties are represented by a generic thumbnail + width = height = 100 + x_offset = (size / 2) - (width / 2) + y_offset = (size / 2) - (height / 2) + draw.line([x_offset, y_offset, width + x_offset, height + y_offset], fill="white", width=2) + draw.line([x_offset, y_offset + height, width + x_offset, y_offset], fill="white", width=2) + draw.rectangle([x_offset, y_offset, width + x_offset, height + y_offset], outline="white", width=5) + else: + draw.line([0, 0, size, size], fill="red", width=2) + draw.line([0, size, size, 0], fill="red", width=2) + + pixels = [item for sublist in img.getdata() for item in sublist] + + obj.preview.image_size = size, size + obj.preview.image_pixels_float = pixels + + AuthoringData.type_thumbnails[element.id()] = obj.preview.icon_id