From 16582cf57f83bcaacec596dd61c3eb14216c5a55 Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Mon, 10 Oct 2022 23:13:37 +1100 Subject: [PATCH] Experimental new thumbnail with smarter layer / profile thumbnails and no timers. --- src/blenderbim/blenderbim/bim/import_ifc.py | 6 +- .../blenderbim/bim/module/model/data.py | 15 ++- .../blenderbim/bim/module/model/product.py | 116 ++++++++++++++++++ .../blenderbim/bim/module/model/prop.py | 9 +- .../blenderbim/bim/module/model/workspace.py | 23 ++-- 5 files changed, 151 insertions(+), 18 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/import_ifc.py b/src/blenderbim/blenderbim/bim/import_ifc.py index 3236395fa2..daf9465454 100644 --- a/src/blenderbim/blenderbim/bim/import_ifc.py +++ b/src/blenderbim/blenderbim/bim/import_ifc.py @@ -328,7 +328,11 @@ class IfcImporter: if self.ifc_import_settings.has_filter or offset or offset_limit < len(self.elements): self.element_types = set([ifcopenshell.util.element.get_type(e) for e in self.elements]) else: - self.element_types = set(self.file.by_type("IfcElementType")) + self.element_types = set( + self.file.by_type("IfcElementType") + + self.file.by_type("IfcDoorStyle") + + self.file.by_type("IfcWindowStyle") + ) if self.ifc_import_settings.has_filter and self.ifc_import_settings.should_filter_spatial_elements: self.spatial_elements = self.get_spatial_elements_filtered_by_elements(self.elements) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 2eef391382..b7ee343f83 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -29,21 +29,32 @@ def refresh(): class AuthoringData: data = {} + type_thumbnails = {} is_loaded = False @classmethod def load(cls): cls.is_loaded = True - if not hasattr(cls, "data"): - cls.data = {} cls.props = bpy.context.scene.BIMModelProperties cls.load_ifc_classes() cls.load_relating_types() cls.load_relating_types_browser() + cls.data["type_thumbnail"] = cls.type_thumbnail() cls.data["is_voidable_element"] = cls.is_voidable_element() cls.data["has_visible_openings"] = cls.has_visible_openings() cls.data["active_class"] = cls.active_class() + @classmethod + def type_thumbnail(cls): + props = bpy.context.scene.BIMModelProperties + if not props.relating_type_id: + return 0 + element = tool.Ifc.get().by_id(int(props.relating_type_id)) + obj = tool.Ifc.get_object(element) + if not obj: + return 0 + return cls.type_thumbnails.get(element.id(), None) or 0 + @classmethod def load_ifc_classes(cls): cls.data["ifc_classes"] = cls.ifc_classes() diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index 887eb8d43c..eabbbbbad8 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -339,6 +339,122 @@ class DynamicallyVoidProduct(bpy.types.Operator): return {"FINISHED"} +class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.load_type_thumbnails" + bl_label = "Load Type Thumbnails" + bl_options = {"REGISTER", "UNDO"} + ifc_class: bpy.props.StringProperty() + + def _execute(self, context): + from PIL import Image, ImageDraw + + processing = set() + # Only process at most one class at a time. + # Large projects have hundreds of types which can lead to unnecessary lag. + queue = tool.Ifc.get().by_type(self.ifc_class) + + 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 + settings = ifcopenshell.geom.settings() + settings.set(settings.INCLUDE_CURVES, True) + shape = ifcopenshell.geom.create_shape(settings, profile) + verts = shape.verts + edges = shape.edges + grouped_verts = [[verts[i], verts[i + 1]] for i in range(0, len(verts), 3)] + grouped_edges = [[edges[i], edges[i + 1]] for i in range(0, len(edges), 2)] + + max_x = max([v[0] for v in grouped_verts]) + min_x = min([v[0] for v in grouped_verts]) + max_y = max([v[1] for v in grouped_verts]) + min_y = min([v[1] for v in grouped_verts]) + + dim_x = max_x - min_x + dim_y = max_y - min_y + max_dim = max([dim_x, dim_y]) + scale = 100 / max_dim + + for vert in grouped_verts: + vert[0] = round(scale * (vert[0] - min_x)) + ((size / 2) - scale * (dim_x / 2)) + vert[1] = round(scale * (vert[1] - min_y)) + ((size / 2) - scale * (dim_y / 2)) + + for e in grouped_edges: + draw.line((tuple(grouped_verts[e[0]]), tuple(grouped_verts[e[1]])), fill="white", width=2) + 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.05: + width = 10 + elif si_total_thickness < 0.1: + width = 20 + elif si_total_thickness < 0.2: + width = 30 + elif si_total_thickness < 0.3: + width = 40 + else: + width = 50 + + height = 100 + + if element.is_a("IfcSlabType"): + 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=2) + current_thickness = 0 + 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) + else: + # For things like parametric duct segments + 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) + return {"FINISHED"} + + 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/prop.py b/src/blenderbim/blenderbim/bim/module/model/prop.py index e038ab234a..9fd1963c41 100644 --- a/src/blenderbim/blenderbim/bim/module/model/prop.py +++ b/src/blenderbim/blenderbim/bim/module/model/prop.py @@ -72,10 +72,8 @@ def update_icon_id(self, context, browser=False): def update_ifc_class(self, context): - AuthoringData.load_ifc_classes() - AuthoringData.load_relating_types() - if not self.updating: - update_icon_id(self, context) + AuthoringData.data["relating_types_ids"] = AuthoringData.relating_types() + AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail() def update_ifc_class_browser(self, context): @@ -93,8 +91,7 @@ def update_ifc_class_browser(self, context): def update_relating_type(self, context): AuthoringData.load_relating_types() - if not self.updating: - update_icon_id(self, context) + AuthoringData.data["type_thumbnail"] = AuthoringData.type_thumbnail() def update_relating_type_browser(self, context): diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index a1b0436851..6c20a3a320 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -74,10 +74,10 @@ class BimTool(WorkSpaceTool): AuthoringData.data["relating_types_ids"] if "relating_types_ids" in AuthoringData.data else False ) - if ifc_classes and relating_types_ids and not props.icon_id and not is_tool_header: - # hack Dion won't like to show a preview also on the first time the sidebar is shown - bpy.app.timers.register(lambda: prop.update_ifc_class(props, "lost_context")) - bpy.app.timers.register(lambda: prop.update_relating_type(props, "lost_context")) + #if ifc_classes and relating_types_ids and not props.icon_id and not is_tool_header: + # # hack Dion won't like to show a preview also on the first time the sidebar is shown + # bpy.app.timers.register(lambda: prop.update_ifc_class(props, "lost_context")) + # bpy.app.timers.register(lambda: prop.update_relating_type(props, "lost_context")) ifc_class = props.ifc_class relating_type_id = props.relating_type_id @@ -96,8 +96,8 @@ class BimTool(WorkSpaceTool): else: row.label(text="No Construction Type", icon="FILE_3D") if ifc_classes: - row = layout.row() - row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="") + #row = layout.row() + #row.operator("bim.display_constr_types", icon="TRIA_DOWN", text="") row = layout.row(align=True) row.label(text="", icon="EVENT_SHIFT") @@ -107,7 +107,7 @@ class BimTool(WorkSpaceTool): op.ifc_class = ifc_class if relating_type_id.isnumeric(): op.relating_type_id = int(relating_type_id) - else: + elif is_sidebar: row = layout.row(align=True) if ifc_classes: row.label(text="", icon="FILE_VOLUME") @@ -120,9 +120,14 @@ class BimTool(WorkSpaceTool): prop_with_search(row, props, "relating_type_id", text="") else: row.label(text="No Construction Type", icon="FILE_3D") - if is_sidebar and ifc_classes and relating_types_ids: + + if AuthoringData.data["type_thumbnail"]: box = layout.box() - box.template_icon(icon_value=props.icon_id, scale=8) + box.template_icon(icon_value=AuthoringData.data["type_thumbnail"], scale=5) + else: + box = layout.box() + op = box.operator("bim.load_type_thumbnails", text="Load Thumbnails", icon="FILE_REFRESH") + op.ifc_class = props.ifc_class if ifc_classes: row = layout.row(align=True)