mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-18 14:31:39 +00:00
Work in progress example to import multiple representation items as an object-based breakdown
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user