diff --git a/src/bonsai/bonsai/bim/module/model/grid.py b/src/bonsai/bonsai/bim/module/model/grid.py index 8f7123c8fd..2d4cdfa501 100644 --- a/src/bonsai/bonsai/bim/module/model/grid.py +++ b/src/bonsai/bonsai/bim/module/model/grid.py @@ -19,6 +19,7 @@ import bpy import ifcopenshell.api import bonsai.tool as tool +import bonsai.core.root from bpy.types import Operator from bpy.props import FloatProperty, IntProperty from mathutils import Vector @@ -28,7 +29,9 @@ def add_object(self, context): obj = bpy.data.objects.new("Grid", None) obj.name = "Grid" - bpy.ops.bim.assign_class(obj=obj.name, ifc_class="IfcGrid") + bonsai.core.root.assign_class( + tool.Ifc, tool.Collector, tool.Root, obj=obj, ifc_class="IfcGrid", should_add_representation=False + ) grid = tool.Ifc.get_entity(obj) for i in range(0, self.total_u): @@ -69,6 +72,8 @@ def add_object(self, context): ifcopenshell.api.run("grid.create_axis_curve", tool.Ifc.get(), axis_curve=obj, grid_axis=result) tool.Collector.assign(obj) + tool.Root.reload_grid_decorator() + class BIM_OT_add_object(Operator, tool.Ifc.Operator): bl_idname = "mesh.add_grid" diff --git a/src/bonsai/bonsai/bim/module/project/operator.py b/src/bonsai/bonsai/bim/module/project/operator.py index c9dca610ff..1d76170979 100644 --- a/src/bonsai/bonsai/bim/module/project/operator.py +++ b/src/bonsai/bonsai/bim/module/project/operator.py @@ -827,6 +827,7 @@ class LoadProjectElements(bpy.types.Operator): tool.Project.load_default_thumbnails() tool.Project.set_default_context() tool.Project.set_default_modeling_dimensions() + tool.Root.reload_grid_decorator() return {"FINISHED"} def get_decomposition_elements(self): diff --git a/src/bonsai/bonsai/bim/module/spatial/__init__.py b/src/bonsai/bonsai/bim/module/spatial/__init__.py index 2061055974..4a30a1f2ab 100644 --- a/src/bonsai/bonsai/bim/module/spatial/__init__.py +++ b/src/bonsai/bonsai/bim/module/spatial/__init__.py @@ -44,6 +44,7 @@ classes = ( prop.BIMObjectSpatialProperties, prop.BIMContainer, prop.BIMSpatialDecompositionProperties, + prop.BIMGridProperties, ui.BIM_PT_spatial, ui.BIM_UL_containers_manager, ui.BIM_UL_elements, @@ -60,6 +61,7 @@ def register(): bpy.types.Scene.BIMSpatialDecompositionProperties = bpy.props.PointerProperty( type=prop.BIMSpatialDecompositionProperties ) + bpy.types.Scene.BIMGridProperties = bpy.props.PointerProperty(type=prop.BIMGridProperties) def unregister(): @@ -67,3 +69,4 @@ def unregister(): bpy.utils.unregister_tool(workspace.SpatialTool) del bpy.types.Object.BIMObjectSpatialProperties del bpy.types.Scene.BIMSpatialDecompositionProperties + del bpy.types.Scene.BIMGridProperties diff --git a/src/bonsai/bonsai/bim/module/spatial/decorator.py b/src/bonsai/bonsai/bim/module/spatial/decorator.py new file mode 100644 index 0000000000..73c13cacc7 --- /dev/null +++ b/src/bonsai/bonsai/bim/module/spatial/decorator.py @@ -0,0 +1,135 @@ +# Bonsai - OpenBIM Blender Add-on +# Copyright (C) 2024 Dion Moult +# +# 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 blf +import gpu +import bmesh +import bonsai.tool as tool +from bpy.types import SpaceView3D +from mathutils import Vector +from gpu_extras.batch import batch_for_shader +from bpy_extras.view3d_utils import location_3d_to_region_2d + + +class GridDecorator: + 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_text, (context,), "WINDOW", "POST_PIXEL")) + cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw, (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 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_text(self, context): + self.addon_prefs = tool.Blender.get_addon_preferences() + selected_elements_color = self.addon_prefs.decorator_color_selected + unselected_elements_color = self.addon_prefs.decorator_color_unselected + special_elements_color = self.addon_prefs.decorator_color_special + + font_id = 0 + blf.size(font_id, 12) + blf.enable(font_id, blf.SHADOW) + + for axis in context.scene.BIMGridProperties.grid_axes: + if not (obj := axis.obj): + continue + tag = obj.name.split("/")[-1] + matrix_world = obj.matrix_world + v1 = matrix_world @ obj.data.vertices[0].co + v2 = matrix_world @ obj.data.vertices[1].co + + color = selected_elements_color if obj.select_get() else unselected_elements_color + blf.color(font_id, *color) + + coords_2d = location_3d_to_region_2d(context.region, context.region_data, v1) + if coords_2d: + w, h = blf.dimensions(font_id, tag) + coords_2d -= Vector((w * 0.5, h * 0.5)) + blf.position(font_id, coords_2d[0], coords_2d[1], 0) + blf.draw(font_id, tag) + + coords_2d = location_3d_to_region_2d(context.region, context.region_data, v2) + if coords_2d: + w, h = blf.dimensions(font_id, tag) + coords_2d -= Vector((w * 0.5, h * 0.5)) + blf.position(font_id, coords_2d[0], coords_2d[1], 0) + blf.draw(font_id, tag) + + + def draw(self, context): + self.addon_prefs = tool.Blender.get_addon_preferences() + selected_elements_color = self.addon_prefs.decorator_color_selected + unselected_elements_color = self.addon_prefs.decorator_color_unselected + special_elements_color = self.addon_prefs.decorator_color_special + + gpu.state.point_size_set(6) + gpu.state.blend_set("ALPHA") + + self.line_shader = gpu.shader.from_builtin("POLYLINE_UNIFORM_COLOR") + self.line_shader.bind() # required to be able to change uniforms of the shader + # POLYLINE_UNIFORM_COLOR specific uniforms + self.line_shader.uniform_float("viewportSize", (context.region.width, context.region.height)) + self.line_shader.uniform_float("lineWidth", 2.0) + + # general shader + self.shader = gpu.shader.from_builtin("UNIFORM_COLOR") + + selected_verts = [] + selected_edges = [] + selected_tags = [] + unselected_verts = [] + unselected_edges = [] + for axis in context.scene.BIMGridProperties.grid_axes: + if obj := axis.obj: + if obj.select_get(): + edges = selected_edges + verts = selected_verts + else: + edges = unselected_edges + verts = unselected_verts + i = len(verts) + edges.append([i, i + 1]) + matrix_world = obj.matrix_world + v1 = matrix_world @ obj.data.vertices[0].co + v2 = matrix_world @ obj.data.vertices[1].co + verts.extend([v1, v2]) + + if selected_verts: + self.draw_batch("LINES", selected_verts, selected_elements_color, selected_edges) + + if unselected_verts: + self.draw_batch("LINES", unselected_verts, unselected_elements_color, unselected_edges) diff --git a/src/bonsai/bonsai/bim/module/spatial/prop.py b/src/bonsai/bonsai/bim/module/spatial/prop.py index 2e4a7d1881..9674db84bb 100644 --- a/src/bonsai/bonsai/bim/module/spatial/prop.py +++ b/src/bonsai/bonsai/bim/module/spatial/prop.py @@ -17,7 +17,7 @@ # along with Bonsai. If not, see . import bpy -from bonsai.bim.prop import StrProperty, Attribute +from bonsai.bim.prop import ObjProperty from bonsai.bim.module.spatial.data import SpatialDecompositionData from bpy.types import PropertyGroup from bpy.props import ( @@ -166,3 +166,7 @@ class BIMSpatialDecompositionProperties(PropertyGroup): def active_element(self): if self.elements and self.active_element_index < len(self.elements): return self.elements[self.active_element_index] + + +class BIMGridProperties(PropertyGroup): + grid_axes: CollectionProperty(name="Grid Axes", type=ObjProperty) diff --git a/src/bonsai/bonsai/core/project.py b/src/bonsai/bonsai/core/project.py index cf09008ef2..9b45f38507 100644 --- a/src/bonsai/bonsai/core/project.py +++ b/src/bonsai/bonsai/core/project.py @@ -107,4 +107,5 @@ def create_project( project.load_default_thumbnails() project.set_default_context() project.set_default_modeling_dimensions() + project.run_root_reload_grid_decorator() georeference.set_model_origin() diff --git a/src/bonsai/bonsai/tool/project.py b/src/bonsai/bonsai/tool/project.py index 5f0491a4fb..936184181d 100644 --- a/src/bonsai/bonsai/tool/project.py +++ b/src/bonsai/bonsai/tool/project.py @@ -209,3 +209,7 @@ class Project(bonsai.core.tool.Project): @classmethod def get_appendable_asset_types(cls) -> tuple[str, ...]: return tuple(e for e in APPENDABLE_ASSET_TYPES if e != "IfcProduct") + + @classmethod + def run_root_reload_grid_decorator(cls) -> None: + tool.Root.reload_grid_decorator() diff --git a/src/bonsai/bonsai/tool/root.py b/src/bonsai/bonsai/tool/root.py index a94759a375..13a47b386c 100644 --- a/src/bonsai/bonsai/tool/root.py +++ b/src/bonsai/bonsai/tool/root.py @@ -27,6 +27,7 @@ import bonsai.core.aggregate import bonsai.core.geometry import bonsai.tool as tool from typing import Union, Optional, Any +from bonsai.bim.module.spatial.decorator import GridDecorator class Root(bonsai.core.tool.Root): @@ -199,6 +200,20 @@ class Root(bonsai.core.tool.Root): return element.is_a("IfcSpatialStructureElement") return element.is_a("IfcSpatialElement") + @classmethod + def is_grid_axis(cls, element: ifcopenshell.entity_instance) -> bool: + return element.is_a("IfcGridAxis") + + @classmethod + def reload_grid_decorator(cls) -> None: + axes = bpy.context.scene.BIMGridProperties.grid_axes + axes.clear() + for axis in tool.Ifc.get().by_type("IfcGridAxis"): + if obj := tool.Ifc.get_object(axis): + new = axes.add() + new.obj = obj + GridDecorator.install(bpy.context) + @classmethod def link_object_data(cls, source_obj: bpy.types.Object, destination_obj: bpy.types.Object) -> None: destination_obj.data = source_obj.data