mep system decorators - basic implementation

All it does currently it draws all MEP elements connections in viewport. It also highlights with green color all elements connected to the active object

Demo - https://imgur.com/a/haAQeWt
This commit is contained in:
Andrej730
2023-09-20 15:19:19 +05:00
parent bc5d6ee113
commit dee4cf591e
7 changed files with 260 additions and 2 deletions
@@ -1926,7 +1926,7 @@ class DecorationsHandler:
if cls.installed: if cls.installed:
cls.uninstall() cls.uninstall()
handler = cls() handler = cls()
# NOTE: we USE POST_PIXEL here so that we can draw use both 3D_POLYLINE_UNIFORM_COLOR # NOTE: we USE POST_PIXEL here so that we can use both 3D_POLYLINE_UNIFORM_COLOR
# and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE # and drawing text in the same handler. BUT this means that we supply coordinates in WINSPACE
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL") cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@@ -1944,6 +1944,7 @@ class DecorationsHandler:
self.decorators[object_type] = self.decorators["FALL"] self.decorators[object_type] = self.decorators["FALL"]
def get_objects_and_decorators(self, collection): def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
results = [] results = []
for obj in collection.all_objects: for obj in collection.all_objects:
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>. # along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy import bpy
from . import ui, prop, operator from . import ui, prop, operator, decorator
classes = ( classes = (
operator.AddPort, operator.AddPort,
@@ -51,7 +51,9 @@ classes = (
def register(): def register():
bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties) bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
def unregister(): def unregister():
del bpy.types.Scene.BIMSystemProperties del bpy.types.Scene.BIMSystemProperties
bpy.app.handlers.load_post.remove(decorator.toggle_decorations_on_load)
@@ -27,6 +27,7 @@ def refresh():
SystemData.is_loaded = False SystemData.is_loaded = False
ObjectSystemData.is_loaded = False ObjectSystemData.is_loaded = False
PortData.is_loaded = False PortData.is_loaded = False
SystemDecorationData.is_loaded = False
class SystemData: class SystemData:
@@ -140,3 +141,19 @@ class PortData:
data.append((port, port_obj, connected_element)) data.append((port, port_obj, connected_element))
return data return data
class SystemDecorationData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.data = {
"decoration_data": cls.decoration_data(),
}
cls.is_loaded = True
@classmethod
def decoration_data(cls):
return tool.System.get_decoration_data()
@@ -0,0 +1,145 @@
# BlenderBIM Add-on - OpenBIM Blender Add-on
# Copyright (C) 2023 Dion Moult <dion@thinkmoult.com>, @Andrej730
#
# 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 bpy
import gpu
import bmesh
import blenderbim.tool as tool
from math import sin, cos, radians
from bpy.types import SpaceView3D
from mathutils import Vector, Matrix
from gpu_extras.batch import batch_for_shader
import ifcopenshell
from blenderbim.bim.module.system.data import SystemDecorationData
from bpy.app.handlers import persistent
ERROR_ELEMENTS_COLOR = (1, 0.2, 0.322, 1) # RED
UNSPECIAL_ELEMENT_COLOR = (0.2, 0.2, 0.2, 1) # GREY
def transparent_color(color, alpha=0.1):
color = [i for i in color]
color[3] = alpha
return color
@persistent
def toggle_decorations_on_load(*args):
if bpy.context.scene.BIMSystemProperties.should_draw_decorations:
SystemDecorator.install(bpy.context)
else:
SystemDecorator.uninstall()
class SystemDecorator:
installed = None
@classmethod
def install(cls, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
"""Note that operators that change mesh in `exit_edit_mode_callback` can freeze blender.
The workaround is to move their code to function and use it for callback.
Example: https://devtalk.blender.org/t/calling-operator-that-saves-bmesh-freezes-blender-forever/28595"""
if cls.installed:
cls.uninstall()
handler = cls()
cls.installed = SpaceView3D.draw_handler_add(
handler, (context, get_custom_bmesh, draw_faces, exit_edit_mode_callback), "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 draw_faces(self, bm, vertices_coords):
"""mutates original bm (triangulates it)
so the triangulation edges will be shown too
"""
traingulated_bm = bm
bmesh.ops.triangulate(traingulated_bm, faces=traingulated_bm.faces)
face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces]
faces_color = transparent_color(self.addon_prefs.decorator_color_special)
self.draw_batch("TRIS", vertices_coords, faces_color, face_indices)
def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None):
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
gpu.state.point_size_set(6)
gpu.state.blend_set("ALPHA")
### Actually drawing
all_vertices = []
error_vertices = []
selected_vertices = []
unselected_vertices = []
# special = associated with arcs/circles
special_vertices = []
special_vertex_indices = {}
selected_edges = []
unselected_edges = []
arc_edges = []
roof_angle_edges = []
preview_edges = []
if not SystemDecorationData.is_loaded:
SystemDecorationData.load()
decoration_data = SystemDecorationData.data["decoration_data"]
all_vertices = decoration_data["all_vertices"]
preview_edges = decoration_data["preview_edges"]
special_vertices = decoration_data["special_vertices"]
selected_edges = decoration_data["selected_edges"]
selected_vertices = decoration_data["selected_vertices"]
### Actually drawing
# 3D_POLYLINE_UNIFORM_COLOR is good for smoothed lines since `bgl.enable(GL_LINE_SMOOTH)` is deprecated
self.line_shader = gpu.shader.from_builtin("3D_POLYLINE_UNIFORM_COLOR")
self.line_shader.bind()
# 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")
self.shader.bind()
self.draw_batch("LINES", all_vertices, transparent_color(unselected_elements_color), unselected_edges)
self.draw_batch("LINES", all_vertices, selected_elements_color, selected_edges)
self.draw_batch("LINES", all_vertices, UNSPECIAL_ELEMENT_COLOR, arc_edges)
self.draw_batch("LINES", all_vertices, special_elements_color, preview_edges)
self.draw_batch("LINES", all_vertices, special_elements_color, roof_angle_edges)
self.draw_batch("POINTS", unselected_vertices, transparent_color(unselected_elements_color, 0.5))
self.draw_batch("POINTS", error_vertices, ERROR_ELEMENTS_COLOR)
self.draw_batch("POINTS", special_vertices, special_elements_color)
self.draw_batch("POINTS", selected_vertices, selected_elements_color)
@@ -18,6 +18,7 @@
import bpy import bpy
from blenderbim.bim.module.system.data import SystemData from blenderbim.bim.module.system.data import SystemData
import blenderbim.bim.module.system.decorator as decorator
from blenderbim.bim.prop import StrProperty, Attribute from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -44,6 +45,14 @@ class System(PropertyGroup):
ifc_definition_id: IntProperty(name="IFC Definition ID") ifc_definition_id: IntProperty(name="IFC Definition ID")
def toggle_decorations(self, context):
toggle = self.should_draw_decorations
if toggle:
decorator.SystemDecorator.install(context)
else:
decorator.SystemDecorator.uninstall()
class BIMSystemProperties(PropertyGroup): class BIMSystemProperties(PropertyGroup):
system_attributes: CollectionProperty(name="System Attributes", type=Attribute) system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
is_editing: BoolProperty(name="Is Editing", default=False) is_editing: BoolProperty(name="Is Editing", default=False)
@@ -52,3 +61,6 @@ class BIMSystemProperties(PropertyGroup):
active_system_index: IntProperty(name="Active System Index") active_system_index: IntProperty(name="Active System Index")
active_system_id: IntProperty(name="Active System Id") active_system_id: IntProperty(name="Active System Id")
system_class: EnumProperty(items=get_system_class, name="Class") system_class: EnumProperty(items=get_system_class, name="Class")
should_draw_decorations: BoolProperty(
name="Should Draw Decorations", description="Toggle system decorations", update=toggle_decorations
)
@@ -101,6 +101,10 @@ class BIM_PT_object_systems(Panel):
if not ObjectSystemData.is_loaded: if not ObjectSystemData.is_loaded:
ObjectSystemData.load() ObjectSystemData.load()
self.props = context.scene.BIMSystemProperties self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.prop(self.props, "should_draw_decorations")
if self.props.is_editing: if self.props.is_editing:
row = self.layout.row() row = self.layout.row()
row.alignment = "RIGHT" row.alignment = "RIGHT"
+77
View File
@@ -182,3 +182,80 @@ class System(blenderbim.core.tool.System):
@classmethod @classmethod
def set_active_system(cls, system): def set_active_system(cls, system):
bpy.context.scene.BIMSystemProperties.active_system_id = system.id() bpy.context.scene.BIMSystemProperties.active_system_id = system.id()
@classmethod
def get_decoration_data(cls):
all_vertices = []
preview_edges = []
special_vertices = []
selected_edges = []
selected_vertices = []
si_conversion = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
start_vert_i = 0
if bpy.context.active_object and (active_element := tool.Ifc.get_entity(bpy.context.active_object)):
selected_elements = cls.get_connected_elements(active_element)
else:
selected_elements = set()
# TODO: get only objects visible in viewport
objects = set(bpy.data.objects) - set(bpy.data.collections["Types"].objects)
for obj in objects:
start_vert_i = len(all_vertices)
if obj.hide_get():
continue
if not isinstance(obj.data, bpy.types.Mesh):
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if not cls.is_mep_element(element):
continue
ports = tool.System.get_ports(element)
for port in ports:
position = tool.Model.get_element_matrix(port).translation * si_conversion
all_vertices.append(position)
verts = range(start_vert_i, start_vert_i + len(ports))
edges = [(i, i + 1) for i in range(start_vert_i, start_vert_i + len(ports) - 1)]
if element in selected_elements:
selected_vertices.extend(verts)
selected_edges.extend(edges)
else:
special_vertices.extend(verts)
preview_edges.extend(edges)
decoration_data = {
"all_vertices": all_vertices,
"preview_edges": preview_edges,
"special_vertices": [all_vertices[i] for i in special_vertices],
"selected_edges": selected_edges,
"selected_vertices": [all_vertices[i] for i in selected_vertices],
}
return decoration_data
@classmethod
def get_connected_elements(cls, element, elements=None):
if elements is None:
elements = set((element,))
connected_elements = ifcopenshell.util.system.get_connected_from(element)
connected_elements += ifcopenshell.util.system.get_connected_to(element)
for element in connected_elements:
if element in elements:
continue
elements.add(element)
cls.get_connected_elements(element, elements)
return elements
@classmethod
def is_mep_element(cls, element):
return element.is_a("IfcFlowSegment") or element.is_a("IfcFlowFitting")