From 0c7a0a6bda709a8d2932ae7941bf5336dfcd2f7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bruno=20Perdig=C3=A3o?= Date: Fri, 22 Nov 2024 13:18:01 -0300 Subject: [PATCH] Add aggregate decorator. --- .../bonsai/bim/module/aggregate/decorator.py | 166 ++++++++++++++++++ .../bonsai/bim/module/aggregate/prop.py | 14 +- src/bonsai/bonsai/bim/module/aggregate/ui.py | 3 + 3 files changed, 182 insertions(+), 1 deletion(-) create mode 100644 src/bonsai/bonsai/bim/module/aggregate/decorator.py diff --git a/src/bonsai/bonsai/bim/module/aggregate/decorator.py b/src/bonsai/bonsai/bim/module/aggregate/decorator.py new file mode 100644 index 0000000000..820204b45b --- /dev/null +++ b/src/bonsai/bonsai/bim/module/aggregate/decorator.py @@ -0,0 +1,166 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2024 Bruno Perdigão +# +# This file is part of Bonsai. +# +# Bonsai is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# Bonsai is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Bonsai. If not, see . + +import bpy +import gpu +import ifcopenshell +import bonsai.tool as tool +from bpy.types import SpaceView3D +from gpu_extras.batch import batch_for_shader +from mathutils import Vector +from bonsai.bim.module.geometry.decorator import ItemDecorator + + +class AggregateDecorator: + is_installed = False + handlers = [] + + @classmethod + def install(cls, context): + if cls.is_installed: + cls.uninstall() + handler = cls() + cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_aggregate, (context,), "WINDOW", "POST_VIEW")) + cls.handlers.append(SpaceView3D.draw_handler_add(handler.lock_objects, (context,), "WINDOW", "POST_VIEW")) + cls.is_installed = True + + @classmethod + def uninstall(cls): + for handler in cls.handlers: + try: + SpaceView3D.draw_handler_remove(handler, "WINDOW") + except ValueError: + pass + cls.is_installed = False + + def transparent_color(self, color, alpha=0.1): + color = [i for i in color] + color[3] = alpha + return color + + def dotted_line_shader(self): + vert_out = gpu.types.GPUStageInterfaceInfo("my_interface") + vert_out.smooth('FLOAT', "v_ArcLength") + + shader_info = gpu.types.GPUShaderCreateInfo() + shader_info.push_constant('MAT4', "u_ViewProjectionMatrix") + shader_info.push_constant('FLOAT', "u_Scale") + shader_info.vertex_in(0, 'VEC3', "position") + shader_info.vertex_in(1, 'FLOAT', "arcLength") + shader_info.vertex_out(vert_out) + shader_info.fragment_out(0, 'VEC4', "FragColor") + shader_info.push_constant('VEC4', "color") + + shader_info.vertex_source( + "void main()" + "{" + " v_ArcLength = arcLength;" + " gl_Position = u_ViewProjectionMatrix * vec4(position, 1.0f);" + "}" + ) + + shader_info.fragment_source( + "void main()" + "{" + " if (step(sin(v_ArcLength * u_Scale), 0.4) == 1) discard;" + " FragColor = color;" + "}" + ) + + shader = gpu.shader.create_from_info(shader_info) + del vert_out + del shader_info + return shader + + + def draw_custom_batch(self, coords, color): + shader = self.dotted_line_shader() + + arc_lengths = [0] + for a, b in zip(coords[:-1], coords[1:]): + arc_lengths.append(arc_lengths[-1] + (a - b).length) + + batch = batch_for_shader( + shader, 'LINE_STRIP', + {"position": coords, "arcLength": arc_lengths}, + ) + + matrix = bpy.context.region_data.perspective_matrix + shader.uniform_float("color", color) + shader.uniform_float("u_ViewProjectionMatrix", matrix) + shader.uniform_float("u_Scale", 25) + batch.draw(shader) + + + def draw_batch(self, shader_type, content_pos, color, indices=None): + shader = self.line_shader if shader_type == "LINES" else self.shader + batch = batch_for_shader(shader, shader_type, {"pos": content_pos}, indices=indices) + shader.uniform_float("color", color) + batch.draw(shader) + + def draw_aggregate(self, context): + self.addon_prefs = tool.Blender.get_addon_preferences() + decorator_color_special = self.addon_prefs.decorator_color_special + decorator_color_selected = self.addon_prefs.decorator_color_selected + decorator_color_error = self.addon_prefs.decorator_color_error + decorator_color_unselected = self.addon_prefs.decorator_color_unselected + decorator_color_background = self.addon_prefs.decorator_color_background + theme = context.preferences.themes.items()[0][1] + selected_object_color = (*theme.view_3d.object_active, 1) + + self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR") + self.line_shader.bind() + # POLYLINE_UNIFORM_COLOR specific uniforms + self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height)) + aggregates = [] + if not (selected_objects := context.selected_objects): + return + for obj in selected_objects: + element = tool.Ifc.get_entity(obj) + if not element or not element.is_a("IfcElement"): + return + + parts = ifcopenshell.util.element.get_parts(element) + if parts: + aggregates.append(obj) + + aggregate = ifcopenshell.util.element.get_aggregate(element) + if aggregate: + aggregates.append(tool.Ifc.get_object(aggregate)) + + aggregates = set(aggregates) + for aggregate in aggregates: + self.line_shader.uniform_float("lineWidth", 2.0) + color = decorator_color_unselected + if aggregate in selected_objects: + color = selected_object_color + size = aggregate.empty_display_size + location = aggregate.location + line_x = (location - Vector((size, 0.0, 0.0)), location + Vector((size, 0.0, 0.0))) + self.draw_batch("LINES", line_x, color, [(0, 1)]) + line_y = (location - Vector((0.0, size, 0.0)), location + Vector((0.0, size, 0.0))) + self.draw_batch("LINES", line_y, color, [(0, 1)]) + line_z = (location - Vector((0.0, 0.0, size)), location + Vector((0.0, 0.0, size))) + self.draw_batch("LINES", line_z, color, [(0, 1)]) + if context.scene.BIMAggregateProperties.in_edit_mode: + return + parts = ifcopenshell.util.element.get_parts(tool.Ifc.get_entity(aggregate)) + for part in parts: + part_obj = tool.Ifc.get_object(part) + line = (part_obj.matrix_world.translation, location) + self.draw_custom_batch(line, decorator_color_unselected) diff --git a/src/bonsai/bonsai/bim/module/aggregate/prop.py b/src/bonsai/bonsai/bim/module/aggregate/prop.py index b5cf9c5f98..f5aae5e193 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/prop.py +++ b/src/bonsai/bonsai/bim/module/aggregate/prop.py @@ -32,6 +32,7 @@ from bpy.props import ( FloatVectorProperty, CollectionProperty, ) +from bonsai.bim.module.aggregate.decorator import AggregateDecorator def can_aggregate(relating_obj: bpy.types.Object, related_obj: bpy.types.Object) -> bool: @@ -73,6 +74,13 @@ def poll_related_object(self: "BIMObjectAggregateProperties", related_obj: bpy.t return True +def update_aggregate_decorator(self, context): + if self.aggregate_decorator: + AggregateDecorator.install(bpy.context) + else: + AggregateDecorator.uninstall() + + class BIMObjectAggregateProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") relating_object: PointerProperty(name="Relating Whole", type=bpy.types.Object, poll=poll_relating_object) @@ -89,4 +97,8 @@ class NotEditingObjects(bpy.types.PropertyGroup): class BIMAggregateProperties(PropertyGroup): in_edit_mode: BoolProperty(name="In Edit Mode") not_editing_objects: CollectionProperty(type=NotEditingObjects) - + aggregate_decorator: BoolProperty( + name="Display Aggregate", + default=False, + update=update_aggregate_decorator, + ) diff --git a/src/bonsai/bonsai/bim/module/aggregate/ui.py b/src/bonsai/bonsai/bim/module/aggregate/ui.py index f30211bce2..7b9503f530 100644 --- a/src/bonsai/bonsai/bim/module/aggregate/ui.py +++ b/src/bonsai/bonsai/bim/module/aggregate/ui.py @@ -47,6 +47,9 @@ class BIM_PT_aggregate(Panel): def draw(self, context): layout = self.layout + row = layout.row() + row.label(text="Aggregate Decorator") + row.prop(context.scene.BIMAggregateProperties, "aggregate_decorator", icon="HIDE_OFF", text="") if not AggregateData.is_loaded: AggregateData.load()