You can now add new mesh representation items

This commit is contained in:
Dion Moult
2024-09-21 16:34:41 +10:00
parent 2a8d43c42c
commit ce010f1e56
6 changed files with 210 additions and 99 deletions
@@ -20,6 +20,7 @@ import bpy
from . import ui, prop, operator from . import ui, prop, operator
classes = ( classes = (
operator.AddMeshlikeItem,
operator.AddRepresentation, operator.AddRepresentation,
operator.CopyRepresentation, operator.CopyRepresentation,
operator.DisableEditingRepresentationItemShapeAspect, operator.DisableEditingRepresentationItemShapeAspect,
+147 -85
View File
@@ -1742,6 +1742,93 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
bl_options = {"REGISTER", "UNDO"} bl_options = {"REGISTER", "UNDO"}
should_save: bpy.props.BoolProperty(name="Should Save", default=True) should_save: bpy.props.BoolProperty(name="Should Save", default=True)
def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
def _invoke(self, context, event):
if not context.active_object:
return {"FINISHED"}
self.is_valid = True
if context.mode != "OBJECT":
bpy.ops.object.mode_set(mode="OBJECT")
if not tool.Ifc.get():
return {"FINISHED"}
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":
context.scene.BIMGeometryProperties.mode = "OBJECT"
context.scene.BIMGeometryProperties.is_changing_mode = False
if context.active_object:
element = tool.Ifc.get_entity(context.active_object)
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.edit_boundary_geometry()
elif tool.Geometry.is_representation_item(context.active_object):
self.edit_representation_item(context.active_object)
return {"FINISHED"}
objs = context.selected_objects or [context.active_object]
print("objs are", objs)
self.edited_objs = []
self.unchanged_objs_with_openings = []
for obj in objs:
if not obj:
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if tool.Profile.is_editing_profile():
profile_id = context.scene.BIMProfileProperties.active_profile_id
if profile_id:
profile = tool.Ifc.get().by_id(profile_id)
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
bpy.ops.bim.edit_arbitrary_profile()
elif tool.Blender.Modifier.is_railing(element):
bpy.ops.bim.finish_editing_railing_path()
elif tool.Blender.Modifier.is_roof(element):
bpy.ops.bim.finish_editing_roof_path()
elif tool.Model.get_usage_type(element) == "PROFILE":
bpy.ops.bim.edit_extrusion_axis()
# if in the process of editing arbitrary profile
elif context.scene.BIMProfileProperties.active_arbitrary_profile_id:
bpy.ops.bim.edit_arbitrary_profile()
else:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
elif obj.data.BIMMeshProperties.ifc_definition_id:
if not tool.Geometry.has_geometric_data(obj):
self.is_valid = False
self.should_save = False
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if tool.Geometry.is_meshlike(
representation
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj)
else:
tool.Ifc.finish_edit(obj)
elif element.is_a("IfcGridAxis"):
if not tool.Geometry.has_geometric_data(obj):
self.is_valid = False
self.should_save = False
if obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
else:
tool.Ifc.finish_edit(obj)
return self.execute(context)
def _execute(self, context): def _execute(self, context):
if not context.active_object: if not context.active_object:
return {"FINISHED"} return {"FINISHED"}
@@ -1866,91 +1953,6 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
context.scene.BIMGeometryProperties.mode = "EDIT" context.scene.BIMGeometryProperties.mode = "EDIT"
context.scene.BIMGeometryProperties.is_changing_mode = False context.scene.BIMGeometryProperties.is_changing_mode = False
def invoke(self, context, event):
return IfcStore.execute_ifc_operator(self, context, is_invoke=True)
def _invoke(self, context, event):
if not context.active_object:
return {"FINISHED"}
self.is_valid = True
bpy.ops.object.mode_set(mode="EDIT", toggle=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":
context.scene.BIMGeometryProperties.mode = "OBJECT"
context.scene.BIMGeometryProperties.is_changing_mode = False
if not tool.Ifc.get():
return {"FINISHED"}
if context.active_object:
element = tool.Ifc.get_entity(context.active_object)
if element and element.is_a("IfcRelSpaceBoundary"):
return bpy.ops.bim.edit_boundary_geometry()
elif tool.Geometry.is_representation_item(context.active_object):
self.edit_representation_item(context.active_object)
return {"FINISHED"}
objs = context.selected_objects or [context.active_object]
self.edited_objs = []
self.unchanged_objs_with_openings = []
for obj in objs:
if not obj:
continue
element = tool.Ifc.get_entity(obj)
if not element:
continue
if tool.Profile.is_editing_profile():
profile_id = context.scene.BIMProfileProperties.active_profile_id
if profile_id:
profile = tool.Ifc.get().by_id(profile_id)
if tool.Ifc.get_object(profile): # We are editing an arbitrary profile
bpy.ops.bim.edit_arbitrary_profile()
elif tool.Blender.Modifier.is_railing(element):
bpy.ops.bim.finish_editing_railing_path()
elif tool.Blender.Modifier.is_roof(element):
bpy.ops.bim.finish_editing_roof_path()
elif tool.Model.get_usage_type(element) == "PROFILE":
bpy.ops.bim.edit_extrusion_axis()
# if in the process of editing arbitrary profile
elif context.scene.BIMProfileProperties.active_arbitrary_profile_id:
bpy.ops.bim.edit_arbitrary_profile()
else:
bpy.ops.bim.edit_extrusion_profile()
return self.execute(context)
elif obj.data.BIMMeshProperties.ifc_definition_id:
if not tool.Geometry.has_geometric_data(obj):
self.is_valid = False
self.should_save = False
representation = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
if tool.Geometry.is_meshlike(
representation
) and obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
elif getattr(element, "HasOpenings", None):
self.unchanged_objs_with_openings.append(obj)
else:
tool.Ifc.finish_edit(obj)
elif element.is_a("IfcGridAxis"):
if not tool.Geometry.has_geometric_data(obj):
self.is_valid = False
self.should_save = False
if obj.data.BIMMeshProperties.mesh_checksum != tool.Geometry.get_mesh_checksum(obj.data):
self.edited_objs.append(obj)
else:
tool.Ifc.finish_edit(obj)
return self.execute(context)
class FlipObject(bpy.types.Operator): class FlipObject(bpy.types.Operator):
bl_idname = "bim.flip_object" bl_idname = "bim.flip_object"
@@ -2409,3 +2411,63 @@ class UpdateItemAttributes(bpy.types.Operator, tool.Ifc.Operator):
item.Depth = attribute.float_value item.Depth = attribute.float_value
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj) tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
tool.Geometry.import_item(obj) tool.Geometry.import_item(obj)
class AddMeshlikeItem(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.add_meshlike_item"
bl_label = "Add Meshlike Item"
bl_options = {"REGISTER", "UNDO"}
shape: bpy.props.StringProperty(name="Shape")
def _execute(self, context):
props = context.scene.BIMGeometryProperties
mesh = bpy.data.meshes.new("Tmp")
obj = bpy.data.objects.new("Tmp", mesh)
scene = bpy.context.scene
scene.collection.objects.link(obj)
bpy.context.view_layer.objects.active = obj
obj.select_set(True)
bm = bmesh.new()
matrix = props.representation_obj.matrix_world.copy()
matrix.translation = context.scene.cursor.location
matrix = props.representation_obj.matrix_world.inverted() @ matrix
if self.shape == "CUBE":
bmesh.ops.create_cube(bm, matrix=matrix, size=0.5)
elif self.shape == "PLANE":
bmesh.ops.create_grid(bm, matrix=matrix, size=0.5)
elif self.shape == "CIRCLE":
bmesh.ops.create_circle(bm, matrix=matrix, radius=0.25, segments=16, cap_ends=True)
elif self.shape == "UVSPHERE":
bmesh.ops.create_uvsphere(bm, matrix=matrix, radius=0.25, u_segments=16, v_segments=16)
elif self.shape == "ICOSPHERE":
bmesh.ops.create_icosphere(bm, matrix=matrix, radius=0.25, subdivisions=2)
elif self.shape == "CYLINDER":
# Cone is legitimate.
bmesh.ops.create_cone(bm, matrix=matrix, radius1=0.25, radius2=0.25, depth=0.5, segments=16, cap_ends=True)
bm.to_mesh(mesh)
bm.free()
mesh.update()
obj.matrix_world = props.representation_obj.matrix_world
new = props.item_objs.add()
new.obj = obj
tool.Geometry.lock_object(obj)
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
verts = [v.co / unit_scale for v in obj.data.vertices]
faces = [p.vertices[:] for p in obj.data.polygons]
representation = tool.Geometry.get_active_representation(props.representation_obj)
representation = ifcopenshell.util.representation.resolve_representation(representation)
if representation.RepresentationType in ("Brep", "AdvancedBrep"):
item = builder.faceted_brep(verts, faces)
elif representation.RepresentationType in ("Tessellation"):
item = builder.mesh(verts, faces)
representation.Items = list(representation.Items) + [item]
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
obj.name = obj.data.name = f"Item/{item.is_a()}/{item.id()}"
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
@@ -64,6 +64,7 @@ classes = (
product.LoadTypeThumbnails, product.LoadTypeThumbnails,
product.MirrorElements, product.MirrorElements,
workspace.Hotkey, workspace.Hotkey,
workspace.BIM_MT_add_meshlike_item,
wall.AlignWall, wall.AlignWall,
wall.ChangeExtrusionDepth, wall.ChangeExtrusionDepth,
wall.ChangeExtrusionXAngle, wall.ChangeExtrusionXAngle,
@@ -30,6 +30,7 @@ from natsort import natsorted
def refresh(): def refresh():
AuthoringData.is_loaded = False AuthoringData.is_loaded = False
ItemData.is_loaded = False
ArrayData.is_loaded = False ArrayData.is_loaded = False
StairData.is_loaded = False StairData.is_loaded = False
SverchokData.is_loaded = False SverchokData.is_loaded = False
@@ -620,3 +621,27 @@ class RoofData:
general_params[prop_readable_name] = prop_value general_params[prop_readable_name] = prop_value
return general_params return general_params
class ItemData:
data = {}
is_loaded = False
@classmethod
def load(cls):
cls.is_loaded = True
cls.data = {}
cls.data["representation_identifier"] = cls.representation_identifier()
cls.data["representation_type"] = cls.representation_type()
@classmethod
def representation_identifier(cls):
props = bpy.context.scene.BIMGeometryProperties
rep = tool.Geometry.get_active_representation(props.representation_obj)
return rep.RepresentationIdentifier
@classmethod
def representation_type(cls):
props = bpy.context.scene.BIMGeometryProperties
rep = tool.Geometry.get_active_representation(props.representation_obj)
return rep.RepresentationType
@@ -24,8 +24,8 @@ import bonsai.tool as tool
import bonsai.core.model as core import bonsai.core.model as core
from bonsai.bim.module.model.wall import DumbWallJoiner from bonsai.bim.module.model.wall import DumbWallJoiner
from bonsai.bim.helper import prop_with_search from bonsai.bim.helper import prop_with_search
from bpy.types import WorkSpaceTool from bpy.types import WorkSpaceTool, Menu
from bonsai.bim.module.model.data import AuthoringData from bonsai.bim.module.model.data import AuthoringData, ItemData
from bonsai.bim.module.system.data import PortData from bonsai.bim.module.system.data import PortData
from bonsai.bim.module.model.prop import get_ifc_class from bonsai.bim.module.model.prop import get_ifc_class
@@ -262,9 +262,17 @@ def format_ifc_camel_case(string):
class EditItemUI: class EditItemUI:
@classmethod @classmethod
def draw(cls, context, layout): def draw(cls, context, layout):
if not ItemData.is_loaded:
ItemData.load()
cls.layout = layout cls.layout = layout
row = cls.layout.row() row = cls.layout.row()
row.label(text="Item Mode", icon="MESH_DATA") row.label(text="Item Mode", icon="MESH_DATA")
row = cls.layout.row()
row.label(text="Context: " + ItemData.data["representation_identifier"], icon="SCENE_DATA")
row = cls.layout.row()
row.label(text="Type: " + ItemData.data["representation_type"], icon="OUTLINER_OB_MESH")
cls.layout.menu("BIM_MT_add_meshlike_item", icon="ADD")
if not (obj := context.active_object) or not tool.Geometry.is_representation_item(obj): if not (obj := context.active_object) or not tool.Geometry.is_representation_item(obj):
return return
for item_attribute in obj.data.BIMMeshProperties.item_attributes: for item_attribute in obj.data.BIMMeshProperties.item_attributes:
@@ -275,6 +283,19 @@ class EditItemUI:
row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="") row.operator("bim.update_item_attributes", icon="FILE_REFRESH", text="")
class BIM_MT_add_meshlike_item(Menu):
bl_idname = "BIM_MT_add_meshlike_item"
bl_label = "Add Item"
def draw(self, context):
self.layout.operator("bim.add_meshlike_item", icon="MESH_PLANE", text="Mesh Plane").shape = "PLANE"
self.layout.operator("bim.add_meshlike_item", icon="MESH_CUBE", text="Mesh Cube").shape = "CUBE"
self.layout.operator("bim.add_meshlike_item", icon="MESH_CIRCLE", text="Mesh Circle").shape = "CIRCLE"
self.layout.operator("bim.add_meshlike_item", icon="MESH_UVSPHERE", text="Mesh UV Sphere").shape = "UVSPHERE"
self.layout.operator("bim.add_meshlike_item", icon="MESH_ICOSPHERE", text="Mesh Icosphere").shape = "ICOSPHERE"
self.layout.operator("bim.add_meshlike_item", icon="MESH_CYLINDER", text="Mesh Cylinder").shape = "CYLINDER"
class CreateObjectUI: class CreateObjectUI:
@classmethod @classmethod
def draw(cls, context, layout, ifc_element_type=None): def draw(cls, context, layout, ifc_element_type=None):
+13 -12
View File
@@ -1422,21 +1422,22 @@ class Geometry(bonsai.core.tool.Geometry):
has_changed = False has_changed = False
for item_obj in props.item_objs: for item_obj in props.item_objs:
obj = item_obj.obj obj = item_obj.obj
if not tool.Ifc.is_moved(obj):
continue
has_changed = True
item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id) item = tool.Ifc.get().by_id(obj.data.BIMMeshProperties.ifc_definition_id)
old_position = item.Position if item.is_a("IfcSweptAreaSolid"):
if np.allclose(np.array(obj.matrix_world), np.array(rep_obj.matrix_world), atol=1e-4): if not tool.Ifc.is_moved(obj):
continue
has_changed = True
old_position = item.Position
if np.allclose(np.array(obj.matrix_world), np.array(rep_obj.matrix_world), atol=1e-4):
if old_position:
item.Position = None
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position)
continue
rel_matrix = rep_matrix_i @ obj.matrix_world
rel_matrix.translation /= unit_scale
item.Position = builder.create_axis2_placement_3d_from_matrix(np.array(rel_matrix))
if old_position: if old_position:
item.Position = None
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position) ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position)
continue
rel_matrix = rep_matrix_i @ obj.matrix_world
rel_matrix.translation /= unit_scale
item.Position = builder.create_axis2_placement_3d_from_matrix(np.array(rel_matrix))
if old_position:
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position)
if has_changed: if has_changed:
cls.reload_representation(rep_obj) cls.reload_representation(rep_obj)