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:
cls.uninstall()
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
cls.installed = SpaceView3D.draw_handler_add(handler, (context,), "WINDOW", "POST_PIXEL")
@@ -1944,6 +1944,7 @@ class DecorationsHandler:
self.decorators[object_type] = self.decorators["FALL"]
def get_objects_and_decorators(self, collection):
# TODO: do it in data instead of the handler for performance?
results = []
for obj in collection.all_objects:
@@ -17,7 +17,7 @@
# along with BlenderBIM Add-on. If not, see <http://www.gnu.org/licenses/>.
import bpy
from . import ui, prop, operator
from . import ui, prop, operator, decorator
classes = (
operator.AddPort,
@@ -51,7 +51,9 @@ classes = (
def register():
bpy.types.Scene.BIMSystemProperties = bpy.props.PointerProperty(type=prop.BIMSystemProperties)
bpy.app.handlers.load_post.append(decorator.toggle_decorations_on_load)
def unregister():
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
ObjectSystemData.is_loaded = False
PortData.is_loaded = False
SystemDecorationData.is_loaded = False
class SystemData:
@@ -140,3 +141,19 @@ class PortData:
data.append((port, port_obj, connected_element))
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
from blenderbim.bim.module.system.data import SystemData
import blenderbim.bim.module.system.decorator as decorator
from blenderbim.bim.prop import StrProperty, Attribute
from bpy.types import PropertyGroup
from bpy.props import (
@@ -44,6 +45,14 @@ class System(PropertyGroup):
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):
system_attributes: CollectionProperty(name="System Attributes", type=Attribute)
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_id: IntProperty(name="Active System Id")
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:
ObjectSystemData.load()
self.props = context.scene.BIMSystemProperties
row = self.layout.row(align=True)
row.prop(self.props, "should_draw_decorations")
if self.props.is_editing:
row = self.layout.row()
row.alignment = "RIGHT"
+77
View File
@@ -182,3 +182,80 @@ class System(blenderbim.core.tool.System):
@classmethod
def set_active_system(cls, system):
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")