diff --git a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py index ea79a4aaa7..b203854de3 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/__init__.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/__init__.py @@ -27,6 +27,7 @@ classes = ( operator.EditBoundaryGeometry, operator.EnableEditingBoundary, operator.EnableEditingBoundaryGeometry, + operator.HideBoundaries, operator.LoadBoundary, operator.LoadProjectSpaceBoundaries, operator.LoadSpaceBoundaries, @@ -34,17 +35,21 @@ classes = ( operator.SelectRelatedElementBoundaries, operator.SelectRelatedElementTypeBoundaries, operator.SelectSpaceBoundaries, + operator.ShowBoundaries, operator.UpdateBoundaryGeometry, ui.BIM_PT_Boundary, ui.BIM_PT_SceneBoundaries, ui.BIM_PT_SpaceBoundaries, prop.BIMBoundaryProperties, + prop.BIMObjectBoundaryProperties, ) def register(): - bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties) + bpy.types.Scene.BIMBoundaryProperties = bpy.props.PointerProperty(type=prop.BIMBoundaryProperties) + bpy.types.Object.bim_boundary_properties = bpy.props.PointerProperty(type=prop.BIMObjectBoundaryProperties) def unregister(): + del bpy.types.Scene.BIMBoundaryProperties del bpy.types.Object.bim_boundary_properties diff --git a/src/blenderbim/blenderbim/bim/module/boundary/decorator.py b/src/blenderbim/blenderbim/bim/module/boundary/decorator.py new file mode 100644 index 0000000000..9012bda523 --- /dev/null +++ b/src/blenderbim/blenderbim/bim/module/boundary/decorator.py @@ -0,0 +1,112 @@ +# BlenderBIM Add-on - OpenBIM Blender Add-on +# Copyright (C) 2023 Dion Moult +# +# This file is part of BlenderBIM Add-on. +# +# BlenderBIM Add-on 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. +# +# BlenderBIM Add-on 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 BlenderBIM Add-on. If not, see . + +import gpu +import bmesh +import blenderbim.tool as tool +from bpy.types import SpaceView3D +from mathutils import Vector +from gpu_extras.batch import batch_for_shader + + +class BoundaryDecorator: + installed = None + + @classmethod + def install(cls, context): + if cls.installed: + cls.uninstall() + handler = cls() + cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_VIEW") + + @classmethod + def uninstall(cls): + try: + SpaceView3D.draw_handler_remove(cls.installed, "WINDOW") + except ValueError: + pass + cls.installed = None + + 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 __call__(self, context): + self.addon_prefs = context.preferences.addons["blenderbim"].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 + + def transparent_color(color, alpha=0.1): + color = [i for i in color] + color[3] = alpha + return color + + gpu.state.point_size_set(6) + gpu.state.blend_set("ALPHA") + + self.line_shader = gpu.shader.from_builtin("3D_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("3D_UNIFORM_COLOR") + + selected_vertices = [] + selected_edges = [] + selected_tris = [] + unselected_vertices = [] + unselected_edges = [] + unselected_tris = [] + + for boundary in context.scene.BIMBoundaryProperties.boundaries: + obj = boundary.obj + if not obj: + continue + + if obj.mode == "EDIT": + continue # A profile decorator or something else is used here. + else: + bm = bmesh.new() + bm.from_mesh(obj.data) + obj.data.calc_loop_triangles() + + if obj.select_get(): + offset = len(selected_vertices) + selected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts]) + selected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges]) + selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles]) + else: + offset = len(unselected_vertices) + unselected_vertices.extend([tuple(obj.matrix_world @ v.co) for v in bm.verts]) + unselected_edges.extend([tuple([v.index + offset for v in e.verts]) for e in bm.edges]) + unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles]) + + if obj.mode != "EDIT": + bm.free() + + if unselected_edges: + self.draw_batch("LINES", unselected_vertices, special_elements_color, unselected_edges) + self.draw_batch("TRIS", unselected_vertices, transparent_color(special_elements_color), unselected_tris) + if selected_edges: + self.draw_batch("LINES", selected_vertices, selected_elements_color, selected_edges) + self.draw_batch("TRIS", selected_vertices, transparent_color(selected_elements_color), selected_tris) diff --git a/src/blenderbim/blenderbim/bim/module/boundary/operator.py b/src/blenderbim/blenderbim/bim/module/boundary/operator.py index 4bdf90097f..072dfc79ab 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/operator.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/operator.py @@ -27,6 +27,7 @@ import blenderbim.tool as tool import blenderbim.bim.import_ifc as import_ifc from blenderbim.bim.ifc import IfcStore from blenderbim.bim.module.model.decorator import ProfileDecorator +from blenderbim.bim.module.boundary.decorator import BoundaryDecorator def get_boundaries_collection(blender_space): @@ -468,3 +469,55 @@ class DisableEditingBoundaryGeometry(bpy.types.Operator, tool.Ifc.Operator): def _execute(self, context): return disable_editing_boundary_geometry(context) + + +class ShowBoundaries(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.show_boundaries" + bl_label = "Show Boundaries" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + props = bpy.context.scene.BIMBoundaryProperties + loader = Loader() + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element or not getattr(element, "BoundedBy", None): + continue + if tool.Ifc.is_moved(obj): + blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) + element = tool.Ifc.get_entity(context.active_object) + for rel in element.BoundedBy or []: + boundary_obj = loader.load_boundary(rel, context.active_object) + new = props.boundaries.add() + new.obj = boundary_obj + BoundaryDecorator.install(bpy.context) + return {"FINISHED"} + + +class HideBoundaries(bpy.types.Operator, tool.Ifc.Operator): + bl_idname = "bim.hide_boundaries" + bl_label = "Hide Boundaries" + bl_options = {"REGISTER", "UNDO"} + + def _execute(self, context): + to_delete = set() + spaces = set() + for obj in context.selected_objects: + element = tool.Ifc.get_entity(obj) + if not element: + continue + if element.is_a("IfcSpace"): + spaces.add(element) + elif element.is_a("IfcRelSpaceBoundary"): + spaces.add(element.RelatingSpace) + + for element in spaces: + for boundary in element.BoundedBy or []: + boundary_obj = tool.Ifc.get_object(boundary) + if boundary_obj: + to_delete.add(boundary_obj) + for boundary_obj in to_delete: + tool.Ifc.unlink(obj=boundary_obj) + bpy.data.objects.remove(boundary_obj) + context.scene.BIMBoundaryProperties.boundaries.clear() + return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/boundary/prop.py b/src/blenderbim/blenderbim/bim/module/boundary/prop.py index e42d414e33..11ffa39706 100644 --- a/src/blenderbim/blenderbim/bim/module/boundary/prop.py +++ b/src/blenderbim/blenderbim/bim/module/boundary/prop.py @@ -18,6 +18,7 @@ import bpy from bpy.types import PropertyGroup +from blenderbim.bim.prop import ObjProperty from bpy.props import ( PointerProperty, StringProperty, @@ -52,9 +53,13 @@ def element_filter(self, object): return False -class BIMBoundaryProperties(PropertyGroup): +class BIMObjectBoundaryProperties(PropertyGroup): is_editing: BoolProperty(name="Is Editing") relating_space: PointerProperty(name="RelatingSpace", type=bpy.types.Object, poll=space_filter) related_building_element: PointerProperty(name="RelatedBuildingElement", type=bpy.types.Object, poll=element_filter) parent_boundary: PointerProperty(name="ParentBoundary", type=bpy.types.Object, poll=boundary_filter) corresponding_boundary: PointerProperty(name="CorrespondingBoundary", type=bpy.types.Object, poll=boundary_filter) + + +class BIMBoundaryProperties(PropertyGroup): + boundaries: bpy.props.CollectionProperty(type=ObjProperty) diff --git a/src/blenderbim/blenderbim/bim/module/model/data.py b/src/blenderbim/blenderbim/bim/module/model/data.py index 8e90bfa59e..d9ba12ae5e 100644 --- a/src/blenderbim/blenderbim/bim/module/model/data.py +++ b/src/blenderbim/blenderbim/bim/module/model/data.py @@ -59,6 +59,7 @@ class AuthoringData: 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["has_visible_boundaries"] = cls.has_visible_boundaries() cls.data["active_class"] = cls.active_class() cls.data["active_material_usage"] = cls.active_material_usage() cls.data["active_representation_type"] = cls.active_representation_type() @@ -161,6 +162,17 @@ class AuthoringData: return True return False + @classmethod + def has_visible_boundaries(cls): + element = tool.Ifc.get_entity(bpy.context.active_object) + if element: + if element.is_a("IfcRelSpaceBoundary"): + return True + for boundary in getattr(element, "BoundedBy", []): + if tool.Ifc.get_object(boundary): + return True + return False + @classmethod def active_class(cls): element = tool.Ifc.get_entity(bpy.context.active_object) @@ -177,7 +189,7 @@ class AuthoringData: def active_representation_type(cls): if bpy.context.active_object: representation = tool.Geometry.get_active_representation(bpy.context.active_object) - if representation: + if representation and representation.is_a("IfcShapeRepresentation"): return representation.RepresentationType @classmethod diff --git a/src/blenderbim/blenderbim/bim/module/model/opening.py b/src/blenderbim/blenderbim/bim/module/model/opening.py index b26621c62f..6378aaa297 100644 --- a/src/blenderbim/blenderbim/bim/module/model/opening.py +++ b/src/blenderbim/blenderbim/bim/module/model/opening.py @@ -657,7 +657,7 @@ class ShowOpenings(Operator, tool.Ifc.Operator): props = bpy.context.scene.BIMModelProperties for obj in context.selected_objects: element = tool.Ifc.get_entity(obj) - if not element: + if not element or not getattr(element, "HasOpenings", None): continue if tool.Ifc.is_moved(obj): blenderbim.core.geometry.edit_object_placement(tool.Ifc, tool.Geometry, tool.Surveyor, obj=obj) @@ -682,7 +682,6 @@ class HideOpenings(Operator, tool.Ifc.Operator): bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - props = bpy.context.scene.BIMModelProperties to_delete = set() for obj in context.selected_objects: element = tool.Ifc.get_entity(obj) @@ -694,7 +693,7 @@ class HideOpenings(Operator, tool.Ifc.Operator): if opening_obj: to_delete.add(opening_obj) for opening_obj in to_delete: - tool.Ifc.unlink(element=opening, obj=opening_obj) + tool.Ifc.unlink(obj=opening_obj) bpy.data.objects.remove(opening_obj) tool.Model.clear_scene_openings() return {"FINISHED"} diff --git a/src/blenderbim/blenderbim/bim/module/model/workspace.py b/src/blenderbim/blenderbim/bim/module/model/workspace.py index db29afe44a..53b233bd3c 100644 --- a/src/blenderbim/blenderbim/bim/module/model/workspace.py +++ b/src/blenderbim/blenderbim/bim/module/model/workspace.py @@ -57,6 +57,7 @@ class BimTool(WorkSpaceTool): ("bim.hotkey", {"type": "V", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_V")]}), ("bim.hotkey", {"type": "X", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_X")]}), ("bim.hotkey", {"type": "Y", "value": "PRESS", "shift": True}, {"properties": [("hotkey", "S_Y")]}), + ("bim.hotkey", {"type": "B", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_B")]}), ("bim.hotkey", {"type": "D", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_D")]}), ("bim.hotkey", {"type": "E", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_E")]}), ("bim.hotkey", {"type": "O", "value": "PRESS", "alt": True}, {"properties": [("hotkey", "A_O")]}), @@ -274,11 +275,9 @@ class BimToolUI: add_layout_hotkey_operator(cls.layout, "Mirror", "S_M", bpy.ops.bim.mirror_elements.__doc__) cls.layout.row(align=True).label(text="Mode") - add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Show / edit openings") - row = cls.layout.row(align=True) - row.label(text="", icon="EVENT_ALT") - row.label(text="", icon="EVENT_D") - row.operator("bim.hotkey", text="Decomposition").hotkey = "A_D" + add_layout_hotkey_operator(cls.layout, "Void", "A_O", "Toggle openings") + add_layout_hotkey_operator(cls.layout, "Decomposition", "A_D", "Select decomposition") + add_layout_hotkey_operator(cls.layout, "Boundaries", "A_B", "Toggle boundaries") @classmethod def draw_header_interface(cls): @@ -581,6 +580,14 @@ class Hotkey(bpy.types.Operator, tool.Ifc.Operator): self.props.y = self.y self.props.z = self.z + def hotkey_A_B(self): + if not bpy.context.selected_objects: + return + if AuthoringData.data["has_visible_boundaries"]: + bpy.ops.bim.hide_boundaries() + else: + bpy.ops.bim.show_boundaries() + def hotkey_A_D(self): if not bpy.context.selected_objects: return