From f01900bdb1cf20d87cabee5b990cb1fd8973e294 Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 31 Jan 2023 13:42:35 +0500 Subject: [PATCH] Profile manager thumnails (#2568) --- src/blenderbim/blenderbim/bim/helper.py | 29 ++++++++++++++++ .../blenderbim/bim/module/model/product.py | 25 ++------------ .../blenderbim/bim/module/profile/__init__.py | 4 ++- .../blenderbim/bim/module/profile/data.py | 1 + .../blenderbim/bim/module/profile/operator.py | 3 ++ .../blenderbim/bim/module/profile/prop.py | 34 +++++++++++++++++++ .../blenderbim/bim/module/profile/ui.py | 19 +++++++++-- 7 files changed, 89 insertions(+), 26 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/helper.py b/src/blenderbim/blenderbim/bim/helper.py index cf1018c3d7..38af2fc914 100644 --- a/src/blenderbim/blenderbim/bim/helper.py +++ b/src/blenderbim/blenderbim/bim/helper.py @@ -267,6 +267,35 @@ def convert_property_group_from_si(property_group, skip_props=()): setattr(property_group, prop_name, prop_value) +def draw_image_for_ifc_profile(draw, profile, size): + """generates image based on `profile` using `PIL.ImageDraw`""" + 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) + + class IfcHeaderExtractor: def __init__(self, filepath: str): self.filepath = filepath diff --git a/src/blenderbim/blenderbim/bim/module/model/product.py b/src/blenderbim/blenderbim/bim/module/model/product.py index cd4c662948..397563b416 100644 --- a/src/blenderbim/blenderbim/bim/module/model/product.py +++ b/src/blenderbim/blenderbim/bim/module/model/product.py @@ -32,6 +32,7 @@ from . import wall, slab, profile, mep from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.model.data import AuthoringData from blenderbim.bim.module.model.prop import store_cursor_position +from blenderbim.bim.helper import draw_image_for_ifc_profile from ifcopenshell.api.pset.data import Data as PsetData from mathutils import Vector, Matrix from bpy_extras.object_utils import AddObjectHelper @@ -370,30 +371,8 @@ class LoadTypeThumbnails(bpy.types.Operator, tool.Ifc.Operator): 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)] + draw_image_for_ifc_profile(draw, profile, size) - 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) diff --git a/src/blenderbim/blenderbim/bim/module/profile/__init__.py b/src/blenderbim/blenderbim/bim/module/profile/__init__.py index 101e9a3cde..9c697aaf60 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/profile/__init__.py @@ -17,7 +17,7 @@ # along with BlenderBIM Add-on. If not, see . import bpy -from . import ui, prop, operator +from . import ui, prop, operator, data classes = ( operator.AddProfileDef, @@ -43,3 +43,5 @@ def register(): def unregister(): del bpy.types.Scene.BIMProfileProperties + if data.ProfileData.preview_collection: + bpy.utils.previews.remove(data.ProfileData.preview_collection) diff --git a/src/blenderbim/blenderbim/bim/module/profile/data.py b/src/blenderbim/blenderbim/bim/module/profile/data.py index 23c03c969a..83f614319f 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/data.py +++ b/src/blenderbim/blenderbim/bim/module/profile/data.py @@ -27,6 +27,7 @@ def refresh(): class ProfileData: data = {} + preview_collection = bpy.utils.previews.new() is_loaded = False @classmethod diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index 6972837cc8..f925609b60 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -22,6 +22,7 @@ import blenderbim.bim.helper import blenderbim.tool as tool import blenderbim.bim.module.model.profile as model_profile from blenderbim.bim.module.model.slab import DecorationsHandler +from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile class LoadProfiles(bpy.types.Operator): @@ -103,6 +104,7 @@ class EditProfile(bpy.types.Operator, tool.Ifc.Operator): ifcopenshell.api.run("profile.edit_profile", tool.Ifc.get(), profile=profile, attributes=attributes) model_profile.DumbProfileRegenerator().regenerate_from_profile_def(profile) bpy.ops.bim.load_profiles() + generate_thumbnail_for_active_profile() class AddProfileDef(bpy.types.Operator, tool.Ifc.Operator): @@ -169,6 +171,7 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): profile = tool.Model.export_profile(obj) if not profile: + def msg(self, context): self.layout.label(text="INVALID PROFILE: " + indices[1]) diff --git a/src/blenderbim/blenderbim/bim/module/profile/prop.py b/src/blenderbim/blenderbim/bim/module/profile/prop.py index 3b7b0a91ef..8c833c95ee 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/prop.py +++ b/src/blenderbim/blenderbim/bim/module/profile/prop.py @@ -20,9 +20,11 @@ import bpy import ifcopenshell import ifcopenshell.util.schema import ifcopenshell.util.attribute +import blenderbim.tool as tool from blenderbim.bim.ifc import IfcStore from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.module.profile.data import ProfileData +from blenderbim.bim.helper import draw_image_for_ifc_profile from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -55,3 +57,35 @@ class BIMProfileProperties(PropertyGroup): active_profile_id: IntProperty(name="Active Profile Id") profile_attributes: CollectionProperty(name="Profile Attributes", type=Attribute) profile_classes: EnumProperty(items=get_profile_classes, name="Profile Classes") + + +def generate_thumbnail_for_active_profile(): + from PIL import Image, ImageDraw + + if bpy.app.background: + return + + props = bpy.context.scene.BIMProfileProperties + ifc_file = tool.Ifc.get() + preview_collection = ProfileData.preview_collection + + if not props.profiles: + bpy.ops.bim.load_profiles() + + profile_id = props.profiles[props.active_profile_index].ifc_definition_id + profile = ifc_file.by_id(profile_id) + + # generate image + size = 128 + img = Image.new("RGBA", (size, size)) + draw = ImageDraw.Draw(img) + draw_image_for_ifc_profile(draw, profile, size) + pixels = [item for sublist in img.getdata() for item in sublist] + + # save generated image to preview collection + profile_id_str = str(profile_id) + if profile_id_str in preview_collection: + del preview_collection[profile_id_str] + preview_image = preview_collection.new(profile_id_str) + preview_image.image_size = size, size + preview_image.image_pixels_float = pixels diff --git a/src/blenderbim/blenderbim/bim/module/profile/ui.py b/src/blenderbim/blenderbim/bim/module/profile/ui.py index 1226be9f87..4228c9f225 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/ui.py +++ b/src/blenderbim/blenderbim/bim/module/profile/ui.py @@ -16,12 +16,15 @@ # You should have received a copy of the GNU General Public License # along with BlenderBIM Add-on. If not, see . +from bpy.types import Panel, UIList +import bpy + import blenderbim.bim.helper import blenderbim.tool as tool -from bpy.types import Panel, UIList from blenderbim.bim.module.profile.data import ProfileData +from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile - +# Profile Manager Class class BIM_PT_profiles(Panel): bl_label = "IFC Profiles" bl_idname = "BIM_PT_profiles" @@ -40,6 +43,18 @@ class BIM_PT_profiles(Panel): ProfileData.load() self.props = context.scene.BIMProfileProperties + if self.props.is_editing: + preview_collection = ProfileData.preview_collection + box = self.layout.box() + profile_id = self.props.profiles[self.props.active_profile_index].ifc_definition_id + preview_image = preview_collection.get(str(profile_id), None) + + if preview_image: + box.template_icon(icon_value=preview_image.icon_id, scale=5) + else: + # TODO: test if called multiple times before one is loaded + generate_thumbnail_for_active_profile() + row = self.layout.row(align=True) row.label(text=f"{ProfileData.data['total_profiles']} Named Profiles Found", icon="SNAP_GRID") if self.props.is_editing: