Work in progress example to import multiple representation items as an object-based breakdown

This commit is contained in:
Dion Moult
2024-09-11 18:03:46 +10:00
parent f8f3519a68
commit 5fe85e71bb
6 changed files with 253 additions and 1 deletions
@@ -36,6 +36,7 @@ classes = (
operator.EnableEditingRepresentationItems, operator.EnableEditingRepresentationItems,
operator.FlipObject, operator.FlipObject,
operator.GetRepresentationIfcParameters, operator.GetRepresentationIfcParameters,
operator.ImportRepresentationItems,
operator.OverrideDelete, operator.OverrideDelete,
operator.OverrideDuplicateMove, operator.OverrideDuplicateMove,
operator.OverrideDuplicateMoveLinked, operator.OverrideDuplicateMoveLinked,
@@ -60,6 +61,7 @@ classes = (
operator.UpdateParametricRepresentation, operator.UpdateParametricRepresentation,
operator.UpdateRepresentation, operator.UpdateRepresentation,
prop.RepresentationItem, prop.RepresentationItem,
prop.RepresentationItemObject,
prop.ShapeAspect, prop.ShapeAspect,
prop.BIMObjectGeometryProperties, prop.BIMObjectGeometryProperties,
prop.BIMGeometryProperties, prop.BIMGeometryProperties,
@@ -0,0 +1,138 @@
# Bonsai - OpenBIM Blender Add-on
# Copyright (C) 2024 Dion Moult <dion@thinkmoult.com>
#
# This file is part of Bonsai.
#
# Bonsai 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.
#
# Bonsai 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 Bonsai. If not, see <http://www.gnu.org/licenses/>.
import blf
import gpu
import json
import bmesh
import bonsai.tool as tool
from bpy.types import SpaceView3D
from mathutils import Vector
from gpu_extras.batch import batch_for_shader
from bpy_extras.view3d_utils import location_3d_to_region_2d
class ItemDecorator:
is_installed = False
handlers = []
@classmethod
def install(cls, context):
if cls.is_installed:
cls.uninstall()
handler = cls()
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw_text, (context,), "WINDOW", "POST_PIXEL"))
cls.handlers.append(SpaceView3D.draw_handler_add(handler.draw, (context,), "WINDOW", "POST_VIEW"))
cls.is_installed = True
@classmethod
def uninstall(cls):
for handler in cls.handlers:
try:
SpaceView3D.draw_handler_remove(handler, "WINDOW")
except ValueError:
pass
cls.is_installed = False
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_text(self, context):
self.addon_prefs = tool.Blender.get_addon_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
font_id = 0
blf.size(font_id, 12)
blf.enable(font_id, blf.SHADOW)
color = selected_elements_color
blf.color(font_id, *color)
for item in context.scene.BIMGeometryProperties.item_objs:
if (obj := item.obj) and obj.hide_get() == False:
if obj.select_get():
centroid = obj.matrix_world @ Vector(obj.bound_box[0]).lerp(Vector(obj.bound_box[6]), 0.5)
tag = obj.name.split("/")[1]
coords_2d = location_3d_to_region_2d(context.region, context.region_data, centroid)
if coords_2d:
w, h = blf.dimensions(font_id, tag)
coords_2d -= Vector((w * 0.5, h * 0.5))
blf.position(font_id, coords_2d[0], coords_2d[1], 0)
blf.draw(font_id, tag)
def draw(self, context):
def transparent_color(color, alpha=0.3):
color = [i for i in color]
color[3] = alpha
return color
self.addon_prefs = tool.Blender.get_addon_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")
self.line_shader = gpu.shader.from_builtin("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("UNIFORM_COLOR")
selected_verts = []
selected_edges = []
selected_tris = []
unselected_verts = []
unselected_edges = []
unselected_tris = []
for item in context.scene.BIMGeometryProperties.item_objs:
if (obj := item.obj) and obj.hide_get() == False:
if obj.select_get():
if context.mode != "OBJECT":
continue
edges = selected_edges
verts = selected_verts
offset = len(selected_verts)
selected_verts.extend([tuple(obj.matrix_world @ v.co) for v in obj.data.vertices])
selected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
else:
offset = len(unselected_verts)
unselected_verts.extend([tuple(obj.matrix_world @ v.co) for v in obj.data.vertices])
unselected_tris.extend([tuple([i + offset for i in t.vertices]) for t in obj.data.loop_triangles])
edges = unselected_edges
verts = unselected_verts
i = len(verts)
edges.extend([[ei + i for ei in e] for e in json.loads(item.edges)])
matrix_world = obj.matrix_world
verts.extend([matrix_world @ Vector(v) for v in json.loads(item.verts)])
if unselected_verts:
self.draw_batch("LINES", unselected_verts, transparent_color(unselected_elements_color), unselected_edges)
self.draw_batch("TRIS", unselected_verts, transparent_color(special_elements_color), unselected_tris)
if selected_verts:
self.draw_batch("LINES", selected_verts, selected_elements_color, selected_edges)
self.draw_batch("TRIS", selected_verts, transparent_color(selected_elements_color), selected_tris)
@@ -2214,3 +2214,96 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, tool.Ifc.Opera
if not styled_item.Styles: if not styled_item.Styles:
ifc_file.remove(styled_item) ifc_file.remove(styled_item)
class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.xxx_import_representation_items"
bl_label = "Import Representation Items"
bl_options = {"REGISTER", "UNDO"}
def _execute(self, context):
obj = context.active_object
tool.Geometry.apply_item_ids_as_vertex_groups(obj)
tool.Geometry.dissolve_triangulated_edges(obj)
bm_dict = self.separate_faces_by_vertex_group(obj)
# bm_dict = self.separate_faces_by_id(obj)
props = context.scene.BIMGeometryProperties
props.item_objs.clear()
for item_id, bm in bm_dict.items():
item_mesh = bpy.data.meshes.new(f"mesh_id_{item_id}")
bm.to_mesh(item_mesh)
bm.free()
item = tool.Ifc.get().by_id(item_id)
item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh)
item_obj.matrix_world = obj.matrix_world
item_obj.show_in_front = True
bpy.context.collection.objects.link(item_obj)
new = props.item_objs.add()
new.obj = item_obj
verts = [list(co) for co in item_obj.bound_box]
edges = [(0, 3), (3, 7), (7, 4), (4, 0), (0, 1), (3, 2), (7, 6), (4, 5), (1, 2), (2, 6), (6, 5), (5, 1)]
new.verts = json.dumps(verts)
new.edges = json.dumps(edges)
def separate_faces_by_id(self, obj):
mesh = obj.data
bm = bmesh.new()
bm.from_mesh(mesh)
results = {}
face_ids = mesh["ios_item_ids"]
unique_ids = set(face_ids)
for item_id in unique_ids:
item_bm = bmesh.new()
for face, face_id in zip(bm.faces, face_ids):
if face_id == item_id:
# Copy the face and its vertices into the new BMesh
new_face_verts = [item_bm.verts.new(v.co) for v in face.verts]
item_bm.faces.new(new_face_verts)
item_bm.verts.ensure_lookup_table()
item_bm.faces.ensure_lookup_table()
results[item_id] = item_bm
return results
def separate_faces_by_vertex_group(self, obj):
mesh = obj.data
bm = bmesh.new()
bm.from_mesh(mesh)
# Ensure the bmesh has up-to-date vertex weights (vertex groups)
bm.verts.layers.deform.verify()
results = {}
for vgroup in obj.vertex_groups:
group_bm = bmesh.new()
deform_layer = bm.verts.layers.deform.active
# Iterate over the faces and check if all vertices of the face belong to the vertex group
for face in bm.faces:
face_in_group = True
for vert in face.verts:
# Get the vertex groups the vertex belongs to
deform = vert[deform_layer]
# If this vertex does not belong to the current vertex group, mark the face as outside the group
if vgroup.index not in deform:
face_in_group = False
break
# If all vertices of the face belong to the current vertex group, add the face to the new BMesh
if face_in_group:
new_face_verts = [group_bm.verts.new(v.co) for v in face.verts]
group_bm.faces.new(new_face_verts)
# Ensure the mesh is valid and doesn't have duplicate elements
group_bm.verts.ensure_lookup_table()
group_bm.faces.ensure_lookup_table()
results[int(vgroup.name.split("_")[3])] = group_bm
return results
+12 -1
View File
@@ -18,7 +18,7 @@
import bpy import bpy
import bonsai.tool as tool import bonsai.tool as tool
from bonsai.bim.prop import StrProperty, Attribute from bonsai.bim.prop import StrProperty, Attribute, ObjProperty
from bonsai.bim.module.geometry.data import RepresentationsData, ViewportData from bonsai.bim.module.geometry.data import RepresentationsData, ViewportData
from bpy.types import PropertyGroup from bpy.types import PropertyGroup
from bpy.props import ( from bpy.props import (
@@ -100,6 +100,16 @@ class RepresentationItem(PropertyGroup):
tags: StringProperty(name="Tags") tags: StringProperty(name="Tags")
class RepresentationItemObject(PropertyGroup):
name: StringProperty(name="Name")
ifc_definition_id: IntProperty(name="IFC Definition ID")
obj: PointerProperty(type=bpy.types.Object)
verts: StringProperty(name="Verts")
edges: StringProperty(name="Edges")
special_verts: StringProperty(name="Special Verts")
special_edges: StringProperty(name="Special Edges")
class ShapeAspect(PropertyGroup): class ShapeAspect(PropertyGroup):
name: StringProperty( name: StringProperty(
name="Name", name="Name",
@@ -137,6 +147,7 @@ class BIMGeometryProperties(PropertyGroup):
should_force_triangulation: BoolProperty(name="Force Triangulation", default=False) should_force_triangulation: BoolProperty(name="Force Triangulation", default=False)
is_changing_mode: BoolProperty(name="Is Changing Mode", default=False) is_changing_mode: BoolProperty(name="Is Changing Mode", default=False)
mode: EnumProperty(items=get_mode, name="IFC Interaction Mode", update=update_mode) mode: EnumProperty(items=get_mode, name="IFC Interaction Mode", update=update_mode)
item_objs: CollectionProperty(name="Item Objects", type=RepresentationItemObject)
def is_object_valid_for_representation_copy(self, obj: bpy.types.Object) -> bool: def is_object_valid_for_representation_copy(self, obj: bpy.types.Object) -> bool:
return bool(obj != bpy.context.active_object and obj.data) return bool(obj != bpy.context.active_object and obj.data)
@@ -820,6 +820,7 @@ class LoadProjectElements(bpy.types.Operator):
tool.Project.set_default_context() tool.Project.set_default_context()
tool.Project.set_default_modeling_dimensions() tool.Project.set_default_modeling_dimensions()
tool.Root.reload_grid_decorator() tool.Root.reload_grid_decorator()
tool.Root.reload_item_decorator()
return {"FINISHED"} return {"FINISHED"}
def get_decomposition_elements(self): def get_decomposition_elements(self):
+7
View File
@@ -28,6 +28,7 @@ import bonsai.core.geometry
import bonsai.tool as tool import bonsai.tool as tool
from typing import Union, Optional, Any from typing import Union, Optional, Any
from bonsai.bim.module.spatial.decorator import GridDecorator from bonsai.bim.module.spatial.decorator import GridDecorator
from bonsai.bim.module.geometry.decorator import ItemDecorator
class Root(bonsai.core.tool.Root): class Root(bonsai.core.tool.Root):
@@ -214,6 +215,12 @@ class Root(bonsai.core.tool.Root):
new.obj = obj new.obj = obj
GridDecorator.install(bpy.context) GridDecorator.install(bpy.context)
@classmethod
def reload_item_decorator(cls) -> None:
item_objs = bpy.context.scene.BIMGeometryProperties.item_objs
item_objs.clear()
ItemDecorator.install(bpy.context)
@classmethod @classmethod
def link_object_data(cls, source_obj: bpy.types.Object, destination_obj: bpy.types.Object) -> None: def link_object_data(cls, source_obj: bpy.types.Object, destination_obj: bpy.types.Object) -> None:
destination_obj.data = source_obj.data destination_obj.data = source_obj.data