Basic support for deleting items, and editing meshlike items

This commit is contained in:
Dion Moult
2024-09-18 21:35:18 +10:00
parent ed530f737c
commit a1d99b544a
8 changed files with 218 additions and 142 deletions
+1 -40
View File
@@ -1370,46 +1370,7 @@ class IfcImporter:
verts = geometry.verts verts = geometry.verts
mesh["has_cartesian_point_offset"] = False mesh["has_cartesian_point_offset"] = False
if geometry.faces: return tool.Loader.convert_geometry_to_mesh(geometry, mesh, verts=verts)
num_vertices = len(verts) // 3
total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
loop_total = [3] * num_loops
num_vertex_indices = len(geometry.faces)
# See bug 3546
# ios_edges holds true edges that aren't triangulated.
#
# we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist()))
mesh["ios_item_ids"] = ifcopenshell.util.shape.get_representation_item_ids(geometry).tolist()
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
rep_str: str = geometry.id
if "openings" not in rep_str:
rep_id = rep_str.split("-", 1)[0]
rep = self.file.by_id(int(rep_id))
tool.Loader.load_indexed_colour_map(rep, mesh)
else:
e = geometry.edges
v = verts
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, [])
mesh["ios_materials"] = [m.instance_id() for m in geometry.materials]
mesh["ios_material_ids"] = geometry.material_ids
return mesh
except: except:
self.ifc_import_settings.logger.error("Could not create mesh for %s", element) self.ifc_import_settings.logger.error("Could not create mesh for %s", element)
import traceback import traceback
@@ -47,18 +47,18 @@ class ViewportData:
@classmethod @classmethod
def mode(cls): def mode(cls):
obj = bpy.context.active_object obj = bpy.context.active_object
obj_mode = [("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0)] modes = [
mesh_modes = [
("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0), ("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0),
("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1), ("ITEM", "IFC Item Mode", "", "MESH_DATA", 1),
] ]
if ( if (
not obj not obj
or not tool.Blender.is_editable(obj) or not tool.Blender.is_editable(obj)
or ((element := tool.Ifc.get_entity(obj)) and tool.Geometry.is_locked(element)) or ((element := tool.Ifc.get_entity(obj)) and tool.Geometry.is_locked(element))
): ):
return obj_mode return modes
return mesh_modes modes.append(("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 2))
return modes
class RepresentationsData: class RepresentationsData:
@@ -18,7 +18,6 @@
import blf import blf
import gpu import gpu
import json
import bmesh import bmesh
import bonsai.tool as tool import bonsai.tool as tool
from bpy.types import SpaceView3D from bpy.types import SpaceView3D
@@ -125,9 +124,25 @@ class ItemDecorator:
edges = unselected_edges edges = unselected_edges
verts = unselected_verts verts = unselected_verts
i = len(verts) i = len(verts)
edges.extend([[ei + i for ei in e] for e in json.loads(item.edges)])
matrix_world = obj.matrix_world matrix_world = obj.matrix_world
verts.extend([matrix_world @ Vector(v) for v in json.loads(item.verts)]) bbox_verts = [matrix_world @ Vector(co) for co in obj.bound_box]
bbox_edges = [
(0 + i, 3 + i),
(3 + i, 7 + i),
(7 + i, 4 + i),
(4 + i, 0 + i),
(0 + i, 1 + i),
(3 + i, 2 + i),
(7 + i, 6 + i),
(4 + i, 5 + i),
(1 + i, 2 + i),
(2 + i, 6 + i),
(6 + i, 5 + i),
(5 + i, 1 + i),
]
edges.extend(bbox_edges)
verts.extend(bbox_verts)
if unselected_verts: if unselected_verts:
self.draw_batch("LINES", unselected_verts, transparent_color(unselected_elements_color), unselected_edges) self.draw_batch("LINES", unselected_verts, transparent_color(unselected_elements_color), unselected_edges)
@@ -16,12 +16,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with Bonsai. If not, see <http://www.gnu.org/licenses/>. # along with Bonsai. If not, see <http://www.gnu.org/licenses/>.
import re
import bpy import bpy
import bmesh import bmesh
import logging import logging
import numpy as np import numpy as np
import json
import re
import ifcopenshell import ifcopenshell
import ifcopenshell.util.unit import ifcopenshell.util.unit
import ifcopenshell.util.element import ifcopenshell.util.element
@@ -637,6 +636,8 @@ class OverrideDelete(bpy.types.Operator):
self.report({"INFO"}, "Elements that are part of an array cannot be deleted.") self.report({"INFO"}, "Elements that are part of an array cannot be deleted.")
return {"FINISHED"} return {"FINISHED"}
tool.Geometry.delete_ifc_object(obj) tool.Geometry.delete_ifc_object(obj)
elif tool.Geometry.is_representation_item(obj):
tool.Geometry.delete_ifc_item(obj)
else: else:
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
@@ -1591,6 +1592,12 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
return bpy.ops.bim.enable_editing_boundary_geometry() return bpy.ops.bim.enable_editing_boundary_geometry()
for obj in selected_objs: for obj in selected_objs:
if obj == context.scene.BIMGeometryProperties.representation_obj:
self.report({"ERROR"}, f"Element '{obj.name}' is in item mode and cannot be edited directly.")
obj.select_set(False)
continue
if tool.Geometry.is_representation_item(obj):
return self.enable_editing_representation_item(context, obj)
if not tool.Blender.is_editable(obj): if not tool.Blender.is_editable(obj):
self.report({"INFO"}, f"Object cannot be edited: {obj.name}") self.report({"INFO"}, f"Object cannot be edited: {obj.name}")
obj.select_set(False) obj.select_set(False)
@@ -1694,6 +1701,21 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
context.view_layer.objects.active = active_obj context.view_layer.objects.active = active_obj
return {"FINISHED"} return {"FINISHED"}
def enable_editing_representation_item(self, context, obj):
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if tool.Geometry.is_meshlike_item(item):
tool.Blender.select_and_activate_single_object(context, obj)
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
return {"CANCELLED"}
context.scene.BIMGeometryProperties.is_changing_mode = True
if context.scene.BIMGeometryProperties.mode != "EDIT":
context.scene.BIMGeometryProperties.mode = "EDIT"
context.scene.BIMGeometryProperties.is_changing_mode = False
return {"FINISHED"}
else:
self.report({"INFO"}, f"Editing {item.is_a()} geometry is not supported")
def invoke(self, context, event): def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True) return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
@@ -1716,28 +1738,49 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
if self.should_save: if self.should_save:
bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="") bpy.ops.bim.update_representation(obj=obj.name, ifc_representation_class="")
if getattr(tool.Ifc.get_entity(obj), "HasOpenings", False): if getattr(tool.Ifc.get_entity(obj), "HasOpenings", False):
self.reload_representation(obj) tool.Geometry.reload_representation(obj)
else: else:
self.reload_representation(obj) tool.Geometry.reload_representation(obj)
tool.Ifc.finish_edit(obj) tool.Ifc.finish_edit(obj)
for obj in self.unchanged_objs_with_openings: for obj in self.unchanged_objs_with_openings:
self.reload_representation(obj) tool.Geometry.reload_representation(obj)
tool.Ifc.finish_edit(obj) tool.Ifc.finish_edit(obj)
return {"FINISHED"} return {"FINISHED"}
def reload_representation(self, obj): def edit_representation_item(self, obj):
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
core.switch_representation( if tool.Geometry.is_meshlike_item(item):
tool.Ifc, if tool.Geometry.has_geometric_data(obj) and obj.data.polygons:
tool.Geometry, if obj.data.BIMMeshProperties.mesh_checksum == tool.Geometry.get_mesh_checksum(obj.data):
obj=obj, return {"FINISHED"}
representation=representation, builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
should_reload=True, unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
is_global=True, verts = [v.co / unit_scale for v in obj.data.vertices]
should_sync_changes_first=False, faces = [p.vertices[:] for p in obj.data.polygons]
apply_openings=True, if item.is_a("IfcAdvancedBrep"):
) new_item = builder.faceted_brep(verts, faces)
else:
new_item = builder.mesh(verts, faces)
for inverse in tool.Ifc.get().get_inverse(item):
ifcopenshell.util.element.replace_attribute(inverse, item, new_item)
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
return {"FINISHED"}
else:
self.reload_item(obj)
return {"FINISHED"}
return {"FINISHED"}
def reload_item(self, obj):
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
tool.Loader.settings.context_settings = tool.Loader.create_settings()
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
geometry = tool.Loader.create_generic_shape(item)
obj.data.clear_geometry()
tool.Loader.convert_geometry_to_mesh(geometry, obj.data)
def invoke(self, context, event): def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True) return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
@@ -1750,6 +1793,10 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
bpy.ops.object.mode_set(mode="EDIT", toggle=True) bpy.ops.object.mode_set(mode="EDIT", toggle=True)
context.scene.BIMGeometryProperties.is_changing_mode = True context.scene.BIMGeometryProperties.is_changing_mode = True
if context.scene.BIMGeometryProperties.representation_obj:
if context.scene.BIMGeometryProperties.mode != "ITEM":
context.scene.BIMGeometryProperties.mode = "ITEM"
else:
if context.scene.BIMGeometryProperties.mode != "OBJECT": if context.scene.BIMGeometryProperties.mode != "OBJECT":
context.scene.BIMGeometryProperties.mode = "OBJECT" context.scene.BIMGeometryProperties.mode = "OBJECT"
context.scene.BIMGeometryProperties.is_changing_mode = False context.scene.BIMGeometryProperties.is_changing_mode = False
@@ -1761,6 +1808,8 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
element = tool.Ifc.get_entity(context.active_object) element = tool.Ifc.get_entity(context.active_object)
if element and element.is_a("IfcRelSpaceBoundary"): if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.edit_boundary_geometry() return bpy.ops.bim.edit_boundary_geometry()
elif tool.Geometry.is_representation_item(context.active_object):
return self.edit_representation_item(context.active_object)
objs = context.selected_objects or [context.active_object] objs = context.selected_objects or [context.active_object]
@@ -1909,7 +1958,6 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.remove_representation_item" bl_idname = "bim.remove_representation_item"
bl_label = "Remove Representation Item" bl_label = "Remove Representation Item"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
representation_item_id: bpy.props.IntProperty() representation_item_id: bpy.props.IntProperty()
@classmethod @classmethod
@@ -1923,7 +1971,6 @@ class RemoveRepresentationItem(bpy.types.Operator, tool.Ifc.Operator):
def _execute(self, context): def _execute(self, context):
obj = context.active_object obj = context.active_object
props = obj.BIMGeometryProperties
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
representation_item = ifc_file.by_id(self.representation_item_id) representation_item = ifc_file.by_id(self.representation_item_id)
@@ -2217,7 +2264,7 @@ class RemoveRepresentationItemFromShapeAspect(bpy.types.Operator, tool.Ifc.Opera
class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator): class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.xxx_import_representation_items" bl_idname = "bim.import_representation_items"
bl_label = "Import Representation Items" bl_label = "Import Representation Items"
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
@@ -2225,85 +2272,38 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object obj = context.active_object
tool.Geometry.apply_item_ids_as_vertex_groups(obj) tool.Geometry.apply_item_ids_as_vertex_groups(obj)
tool.Geometry.dissolve_triangulated_edges(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 = context.scene.BIMGeometryProperties
props.item_objs.clear() props.representation_obj = obj
tool.Geometry.lock_object(obj)
for item_id, bm in bm_dict.items(): context.scene.BIMGeometryProperties.is_changing_mode = True
item_mesh = bpy.data.meshes.new(f"mesh_id_{item_id}") if context.scene.BIMGeometryProperties.mode != "ITEM":
bm.to_mesh(item_mesh) context.scene.BIMGeometryProperties.mode = "ITEM"
bm.free() context.scene.BIMGeometryProperties.is_changing_mode = False
item_ids = set()
for vgroup in obj.vertex_groups:
if vgroup.name.startswith("ios_item_id_"):
item_ids.add(int(vgroup.name.split("_")[-1]))
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
tool.Loader.settings.context_settings = tool.Loader.create_settings()
tool.Loader.settings.gross_context_settings = tool.Loader.create_settings(is_gross=True)
for item_id in item_ids:
item = tool.Ifc.get().by_id(item_id) item = tool.Ifc.get().by_id(item_id)
item_mesh = bpy.data.meshes.new(f"Item/{item.is_a()}/{item_id}")
item_mesh.BIMMeshProperties.ifc_definition_id = item_id
geometry = tool.Loader.create_generic_shape(item)
tool.Loader.convert_geometry_to_mesh(geometry, item_mesh)
item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh) item_obj = bpy.data.objects.new(f"Item/{item.is_a()}/{item_id}", item_mesh)
item_obj.matrix_world = obj.matrix_world item_obj.matrix_world = obj.matrix_world
item_obj.show_in_front = True item_obj.show_in_front = True
bpy.context.collection.objects.link(item_obj) bpy.context.collection.objects.link(item_obj)
if tool.Geometry.is_meshlike_item(item):
tool.Geometry.lock_object(item_obj)
new = props.item_objs.add() new = props.item_objs.add()
new.obj = item_obj 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
@@ -43,11 +43,23 @@ def update_mode(self, context):
if self.is_changing_mode: if self.is_changing_mode:
return return
if self.mode == "OBJECT": if self.mode == "OBJECT":
if self.representation_obj:
tool.Geometry.unlock_object(self.representation_obj)
self.representation_obj = None
bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT") bpy.ops.bim.override_mode_set_object("INVOKE_DEFAULT")
elif self.mode == "ITEM":
bpy.ops.bim.import_representation_items()
elif self.mode == "EDIT": elif self.mode == "EDIT":
bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT") bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT")
def update_representation_obj(self, context):
for item_obj in self.item_objs:
if item_obj.obj:
bpy.data.objects.remove(item_obj.obj)
self.item_objs.clear()
def get_mode(self, context): def get_mode(self, context):
if not ViewportData.is_loaded: if not ViewportData.is_loaded:
ViewportData.load() ViewportData.load()
@@ -147,6 +159,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)
representation_obj: PointerProperty(name="Representation Object", type=bpy.types.Object, update=update_representation_obj)
item_objs: CollectionProperty(name="Item Objects", type=RepresentationItemObject) 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:
+5 -1
View File
@@ -456,7 +456,11 @@ class BIM_PT_tabs(Panel):
op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files" op.uri = "https://docs.bonsaibim.org/guides/troubleshooting.html#saving-and-loading-blend-files"
row.operator("bim.close_blend_warning", text="", icon="CANCEL") row.operator("bim.close_blend_warning", text="", icon="CANCEL")
if not context.mode.startswith(context.scene.BIMGeometryProperties.mode): if context.mode == "OBJECT" and context.scene.BIMGeometryProperties.mode in ("OBJECT", "ITEM"):
pass
elif context.mode.startswith("EDIT") and context.scene.BIMGeometryProperties.mode == "EDIT":
pass
else:
box = self.layout.box() box = self.layout.box()
box.alert = True box.alert = True
row = box.row(align=True) row = box.row(align=True)
+38
View File
@@ -22,6 +22,7 @@ import struct
import hashlib import hashlib
import logging import logging
import numpy as np import numpy as np
import multiprocessing
import ifcopenshell import ifcopenshell
import ifcopenshell.api import ifcopenshell.api
import ifcopenshell.api.grid import ifcopenshell.api.grid
@@ -114,6 +115,20 @@ class Geometry(bonsai.core.tool.Geometry):
obj.lock_location = (False, False, False) obj.lock_location = (False, False, False)
obj.lock_rotation = (False, False, False) obj.lock_rotation = (False, False, False)
@classmethod
def delete_ifc_item(cls, obj: bpy.types.Object) -> None:
props = bpy.context.scene.BIMGeometryProperties
if len(props.item_objs) == 1:
return
for i, item_obj in enumerate(props.item_objs):
if item_obj.obj == obj:
props.item_objs.remove(i)
break
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
cls.remove_representation_item(item)
cls.reload_representation(props.representation_obj)
bpy.data.objects.remove(obj)
@classmethod @classmethod
def delete_ifc_object(cls, obj: bpy.types.Object) -> None: def delete_ifc_object(cls, obj: bpy.types.Object) -> None:
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
@@ -184,6 +199,8 @@ class Geometry(bonsai.core.tool.Geometry):
bonsai.core.spatial.import_spatial_decomposition(tool.Spatial) bonsai.core.spatial.import_spatial_decomposition(tool.Spatial)
try: try:
obj.name obj.name
if bpy.context.scene.BIMGeometryProperties.representation_obj == obj:
bpy.context.scene.BIMGeometryProperties.representation_obj = None
bpy.data.objects.remove(obj) bpy.data.objects.remove(obj)
except: except:
pass pass
@@ -401,6 +418,11 @@ class Geometry(bonsai.core.tool.Geometry):
if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id: if obj.data and hasattr(obj.data, "BIMMeshProperties") and obj.data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) return tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
@classmethod
def get_data_representation(cls, data: bpy.types.Mesh) -> ifcopenshell.entity_instance | None:
if hasattr(data, "BIMMeshProperties") and data.BIMMeshProperties.ifc_definition_id:
return tool.Ifc.get().by_id(data.BIMMeshProperties.ifc_definition_id)
@classmethod @classmethod
def get_active_representation_context(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance: def get_active_representation_context(cls, obj: bpy.types.Object) -> ifcopenshell.entity_instance:
active_representation = tool.Geometry.get_active_representation(obj) active_representation = tool.Geometry.get_active_representation(obj)
@@ -806,6 +828,10 @@ class Geometry(bonsai.core.tool.Geometry):
return True return True
return False return False
@classmethod
def is_meshlike_item(cls, item: ifcopenshell.entity_instance) -> bool:
return item.is_a("IfcTessellatedItem") or item.is_a("IfcManifoldSolidBrep")
@classmethod @classmethod
def is_profile_based(cls, data: bpy.types.Mesh) -> bool: def is_profile_based(cls, data: bpy.types.Mesh) -> bool:
return data.BIMMeshProperties.subshape_type == "PROFILE" return data.BIMMeshProperties.subshape_type == "PROFILE"
@@ -816,6 +842,14 @@ class Geometry(bonsai.core.tool.Geometry):
"SweptSolid", "SweptSolid",
) )
@classmethod
def is_representation_item(cls, obj: bpy.types.Object) -> bool:
return (
obj.data
and (ifc_id := obj.data.BIMMeshProperties.ifc_definition_id)
and tool.Ifc.get().by_id(ifc_id).is_a("IfcRepresentationItem")
)
@classmethod @classmethod
def is_text_literal(cls, representation: ifcopenshell.entity_instance) -> bool: def is_text_literal(cls, representation: ifcopenshell.entity_instance) -> bool:
items = ifcopenshell.util.representation.resolve_items(representation) items = ifcopenshell.util.representation.resolve_items(representation)
@@ -1365,3 +1399,7 @@ class Geometry(bonsai.core.tool.Geometry):
use_immediate_repr = apply_openings and bool(getattr(element, "HasOpenings", None)) use_immediate_repr = apply_openings and bool(getattr(element, "HasOpenings", None))
use_immediate_repr = use_immediate_repr or cls.has_material_style_override(element) use_immediate_repr = use_immediate_repr or cls.has_material_style_override(element)
return use_immediate_repr return use_immediate_repr
@classmethod
def get_elements_by_representation(cls, representation):
return ifcopenshell.util.element.get_elements_by_representation(tool.Ifc.get(), representation)
+45
View File
@@ -879,3 +879,48 @@ class Loader(bonsai.core.tool.Loader):
float(props.blender_x_axis_ordinate), float(props.blender_x_axis_ordinate),
) )
return Matrix(matrix.tolist()) return Matrix(matrix.tolist())
@classmethod
def convert_geometry_to_mesh(cls, geometry, mesh: bpy.types.Mesh, verts=None) -> bpy.types.Mesh:
if verts is None:
verts = geometry.verts
if geometry.faces:
num_vertices = len(verts) // 3
total_faces = len(geometry.faces)
loop_start = range(0, total_faces, 3)
num_loops = total_faces // 3
loop_total = [3] * num_loops
num_vertex_indices = len(geometry.faces)
# See bug 3546
# ios_edges holds true edges that aren't triangulated.
#
# we do `.tolist()` because Blender can't assign `np.int32` to it's custom attributes
mesh["ios_edges"] = list(set(tuple(e) for e in ifcopenshell.util.shape.get_edges(geometry).tolist()))
mesh["ios_item_ids"] = ifcopenshell.util.shape.get_representation_item_ids(geometry).tolist()
mesh.vertices.add(num_vertices)
mesh.vertices.foreach_set("co", verts)
mesh.loops.add(num_vertex_indices)
mesh.loops.foreach_set("vertex_index", geometry.faces)
mesh.polygons.add(num_loops)
mesh.polygons.foreach_set("loop_start", loop_start)
mesh.polygons.foreach_set("loop_total", loop_total)
mesh.polygons.foreach_set("use_smooth", [0] * total_faces)
mesh.update()
# TODO: geometry id is not always an int.
rep_id = geometry.id
if rep_id.isdigit():
if (rep := tool.Ifc.get().by_id(int(rep_id))) and rep.is_a("IfcShapeRepresentation"):
tool.Loader.load_indexed_colour_map(rep, mesh)
else:
e = geometry.edges
v = verts
vertices = [[v[i], v[i + 1], v[i + 2]] for i in range(0, len(v), 3)]
edges = [[e[i], e[i + 1]] for i in range(0, len(e), 2)]
mesh.from_pydata(vertices, edges, [])
mesh["ios_materials"] = [m.instance_id() for m in geometry.materials]
mesh["ios_material_ids"] = geometry.material_ids
return mesh