See #1676. You can now toggle boundaries on or off in the BIM tool via Alt-B, with a special boundary decorator.

This commit is contained in:
Dion Moult
2023-05-01 21:19:18 +10:00
parent 63b201fd95
commit 644c2652ea
7 changed files with 204 additions and 11 deletions
@@ -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
@@ -0,0 +1,112 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>
#
# 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 <http://www.gnu.org/licenses/>.
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)
@@ -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"}
@@ -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)
@@ -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
@@ -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"}
@@ -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