mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-08-10 09:48:32 +00:00
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:
@@ -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"
|
||||
|
||||
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user