mirror of
https://github.com/IfcOpenShell/IfcOpenShell.git
synced 2026-09-21 23:11:00 +00:00
Basic support for editing swept profiles and positions
This commit is contained in:
@@ -88,6 +88,7 @@ def name_callback(obj, data):
|
|||||||
def active_object_callback():
|
def active_object_callback():
|
||||||
refresh_ui_data()
|
refresh_ui_data()
|
||||||
update_bim_tool_props()
|
update_bim_tool_props()
|
||||||
|
tool.Geometry.sync_item_positions()
|
||||||
|
|
||||||
|
|
||||||
def update_bim_tool_props():
|
def update_bim_tool_props():
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ from time import time
|
|||||||
from bonsai.bim.ifc import IfcStore
|
from bonsai.bim.ifc import IfcStore
|
||||||
from ifcopenshell.util.shape_builder import ShapeBuilder
|
from ifcopenshell.util.shape_builder import ShapeBuilder
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from bonsai.bim.module.model.decorator import ProfileDecorator
|
||||||
|
|
||||||
|
|
||||||
class EditObjectPlacement(bpy.types.Operator, tool.Ifc.Operator):
|
class EditObjectPlacement(bpy.types.Operator, tool.Ifc.Operator):
|
||||||
@@ -1706,16 +1707,26 @@ class OverrideModeSetEdit(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if tool.Geometry.is_meshlike_item(item):
|
if tool.Geometry.is_meshlike_item(item):
|
||||||
tool.Blender.select_and_activate_single_object(context, obj)
|
tool.Blender.select_and_activate_single_object(context, obj)
|
||||||
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
obj.data.BIMMeshProperties.mesh_checksum = tool.Geometry.get_mesh_checksum(obj.data)
|
||||||
if tool.Blender.toggle_edit_mode(context) == {"CANCELLED"}:
|
self.enable_edit_mode(context)
|
||||||
return {"CANCELLED"}
|
elif item.is_a("IfcSweptAreaSolid"):
|
||||||
context.scene.BIMGeometryProperties.is_changing_mode = True
|
tool.Geometry.sync_item_positions()
|
||||||
if context.scene.BIMGeometryProperties.mode != "EDIT":
|
tool.Model.import_profile(item.SweptArea, obj=obj)
|
||||||
context.scene.BIMGeometryProperties.mode = "EDIT"
|
obj.data.BIMMeshProperties.ifc_definition_id = item.id()
|
||||||
context.scene.BIMGeometryProperties.is_changing_mode = False
|
self.enable_edit_mode(context)
|
||||||
return {"FINISHED"}
|
ProfileDecorator.install(context)
|
||||||
|
if not bpy.app.background:
|
||||||
|
tool.Blender.set_viewport_tool("bim.cad_tool")
|
||||||
else:
|
else:
|
||||||
self.report({"INFO"}, f"Editing {item.is_a()} geometry is not supported")
|
self.report({"INFO"}, f"Editing {item.is_a()} geometry is not supported")
|
||||||
|
|
||||||
|
def enable_edit_mode(self, context):
|
||||||
|
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
|
||||||
|
|
||||||
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)
|
||||||
|
|
||||||
@@ -1753,7 +1764,7 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
if tool.Geometry.is_meshlike_item(item):
|
if tool.Geometry.is_meshlike_item(item):
|
||||||
if tool.Geometry.has_geometric_data(obj) and obj.data.polygons:
|
if tool.Geometry.has_geometric_data(obj) and obj.data.polygons:
|
||||||
if obj.data.BIMMeshProperties.mesh_checksum == tool.Geometry.get_mesh_checksum(obj.data):
|
if obj.data.BIMMeshProperties.mesh_checksum == tool.Geometry.get_mesh_checksum(obj.data):
|
||||||
return {"FINISHED"}
|
return
|
||||||
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(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]
|
verts = [v.co / unit_scale for v in obj.data.vertices]
|
||||||
@@ -1767,11 +1778,93 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
|
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), item)
|
||||||
obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
|
obj.data.BIMMeshProperties.ifc_definition_id = new_item.id()
|
||||||
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
|
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_obj)
|
||||||
return {"FINISHED"}
|
return
|
||||||
else:
|
else:
|
||||||
self.reload_item(obj)
|
self.reload_item(obj)
|
||||||
return {"FINISHED"}
|
return
|
||||||
return {"FINISHED"}
|
elif item.is_a("IfcSweptAreaSolid"):
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
ProfileDecorator.uninstall()
|
||||||
|
|
||||||
|
profile = tool.Model.export_profile(obj)
|
||||||
|
print("got profile", profile)
|
||||||
|
|
||||||
|
if not profile:
|
||||||
|
|
||||||
|
def msg(self, context):
|
||||||
|
self.layout.label(text="INVALID PROFILE")
|
||||||
|
|
||||||
|
bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
|
||||||
|
ProfileDecorator.install(bpy.context)
|
||||||
|
self.enable_edit_mode(bpy.context)
|
||||||
|
return
|
||||||
|
|
||||||
|
old_profile = item.SweptArea
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(old_profile):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
|
||||||
|
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_profile)
|
||||||
|
|
||||||
|
tool.Geometry.reload_representation(bpy.context.scene.BIMGeometryProperties.representation_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)
|
||||||
|
geometry = tool.Loader.create_generic_shape(item)
|
||||||
|
obj.data.clear_geometry()
|
||||||
|
tool.Loader.convert_geometry_to_mesh(geometry, obj.data)
|
||||||
|
if item.Position:
|
||||||
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(item.Position).tolist())
|
||||||
|
position.translation *= unit_scale
|
||||||
|
position_i = position.inverted()
|
||||||
|
for vert in obj.data.vertices:
|
||||||
|
vert.co = position_i @ vert.co
|
||||||
|
|
||||||
|
element = tool.Ifc.get_entity(bpy.context.scene.BIMGeometryProperties.representation_obj)
|
||||||
|
# Only certain classes should have a footprint
|
||||||
|
if element.is_a() in ("IfcSlab", "IfcRamp"):
|
||||||
|
footprint_context = ifcopenshell.util.representation.get_context(
|
||||||
|
tool.Ifc.get(), "Plan", "FootPrint", "SKETCH_VIEW"
|
||||||
|
)
|
||||||
|
if footprint_context:
|
||||||
|
if profile.is_a("IfcCompositeProfileDef"):
|
||||||
|
profiles = profile.Profiles
|
||||||
|
else:
|
||||||
|
profiles = [profile]
|
||||||
|
curves = []
|
||||||
|
for profile in profiles:
|
||||||
|
curves.append(profile.OuterCurve)
|
||||||
|
if profile.is_a("IfcArbitraryProfileDefWithVoids"):
|
||||||
|
curves.extend(profile.InnerCurves)
|
||||||
|
new_footprint = ifcopenshell.api.run(
|
||||||
|
"geometry.add_footprint_representation",
|
||||||
|
tool.Ifc.get(),
|
||||||
|
context=footprint_context,
|
||||||
|
curves=curves,
|
||||||
|
)
|
||||||
|
old_footprint = ifcopenshell.util.representation.get_representation(
|
||||||
|
element, "Plan", "FootPrint", "SKETCH_VIEW"
|
||||||
|
)
|
||||||
|
if old_footprint:
|
||||||
|
for inverse in tool.Ifc.get().get_inverse(old_footprint):
|
||||||
|
ifcopenshell.util.element.replace_attribute(inverse, old_footprint, new_footprint)
|
||||||
|
bonsai.core.geometry.remove_representation(
|
||||||
|
tool.Ifc, tool.Geometry, obj=obj, representation=old_footprint
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
ifcopenshell.api.run(
|
||||||
|
"geometry.assign_representation",
|
||||||
|
tool.Ifc.get(),
|
||||||
|
product=element,
|
||||||
|
representation=new_footprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
def enable_edit_mode(self, context):
|
||||||
|
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
|
||||||
|
|
||||||
def reload_item(self, obj):
|
def reload_item(self, obj):
|
||||||
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
|
tool.Loader.settings.contexts = ifcopenshell.util.representation.get_prioritised_contexts(tool.Ifc.get())
|
||||||
@@ -1809,7 +1902,8 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
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):
|
elif tool.Geometry.is_representation_item(context.active_object):
|
||||||
return self.edit_representation_item(context.active_object)
|
self.edit_representation_item(context.active_object)
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
objs = context.selected_objects or [context.active_object]
|
objs = context.selected_objects or [context.active_object]
|
||||||
|
|
||||||
@@ -2307,3 +2401,14 @@ class ImportRepresentationItems(bpy.types.Operator, tool.Ifc.Operator):
|
|||||||
|
|
||||||
new = props.item_objs.add()
|
new = props.item_objs.add()
|
||||||
new.obj = item_obj
|
new.obj = item_obj
|
||||||
|
|
||||||
|
if item.is_a("IfcSweptAreaSolid"):
|
||||||
|
tool.Geometry.record_object_position(item_obj)
|
||||||
|
if item.Position:
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
position = Matrix(ifcopenshell.util.placement.get_axis2placement(item.Position).tolist())
|
||||||
|
position.translation *= unit_scale
|
||||||
|
position_i = position.inverted()
|
||||||
|
item_obj.matrix_world = item_obj.matrix_world @ position
|
||||||
|
for vert in item_obj.data.vertices:
|
||||||
|
vert.co = position_i @ vert.co
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ def update_mode(self, context):
|
|||||||
if self.mode == "OBJECT":
|
if self.mode == "OBJECT":
|
||||||
if self.representation_obj:
|
if self.representation_obj:
|
||||||
tool.Geometry.unlock_object(self.representation_obj)
|
tool.Geometry.unlock_object(self.representation_obj)
|
||||||
|
tool.Geometry.sync_item_positions()
|
||||||
self.representation_obj = None
|
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":
|
elif self.mode == "ITEM":
|
||||||
|
|||||||
@@ -1408,3 +1408,33 @@ class Geometry(bonsai.core.tool.Geometry):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def get_elements_by_representation(cls, representation):
|
def get_elements_by_representation(cls, representation):
|
||||||
return ifcopenshell.util.element.get_elements_by_representation(tool.Ifc.get(), representation)
|
return ifcopenshell.util.element.get_elements_by_representation(tool.Ifc.get(), representation)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def sync_item_positions(cls):
|
||||||
|
props = bpy.context.scene.BIMGeometryProperties
|
||||||
|
if not props.representation_obj:
|
||||||
|
return
|
||||||
|
unit_scale = ifcopenshell.util.unit.calculate_unit_scale(tool.Ifc.get())
|
||||||
|
rep_obj = props.representation_obj
|
||||||
|
rep_matrix_i = rep_obj.matrix_world.inverted()
|
||||||
|
builder = ifcopenshell.util.shape_builder.ShapeBuilder(tool.Ifc.get())
|
||||||
|
has_changed = False
|
||||||
|
for item_obj in props.item_objs:
|
||||||
|
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)
|
||||||
|
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:
|
||||||
|
ifcopenshell.util.element.remove_deep2(tool.Ifc.get(), old_position)
|
||||||
|
if has_changed:
|
||||||
|
cls.reload_representation(rep_obj)
|
||||||
|
|||||||
@@ -72,7 +72,9 @@ class Ifc(bonsai.core.tool.Ifc):
|
|||||||
@classmethod
|
@classmethod
|
||||||
def is_moved(cls, obj: bpy.types.Object) -> bool:
|
def is_moved(cls, obj: bpy.types.Object) -> bool:
|
||||||
element = cls.get_entity(obj)
|
element = cls.get_entity(obj)
|
||||||
if not element or element.is_a("IfcTypeProduct") or element.is_a("IfcProject"):
|
if not element and not tool.Geometry.is_representation_item(obj):
|
||||||
|
return False
|
||||||
|
if element and (element.is_a("IfcTypeProduct") or element.is_a("IfcProject")):
|
||||||
return False
|
return False
|
||||||
if not obj.BIMObjectProperties.location_checksum:
|
if not obj.BIMObjectProperties.location_checksum:
|
||||||
return True # Let's be conservative
|
return True # Let's be conservative
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ class Model(bonsai.core.tool.Model):
|
|||||||
|
|
||||||
helper = Helper(tool.Ifc.get())
|
helper = Helper(tool.Ifc.get())
|
||||||
result = helper.auto_detect_profiles(obj, obj.data, position)
|
result = helper.auto_detect_profiles(obj, obj.data, position)
|
||||||
if result["profile_def"]:
|
if isinstance(result, dict) and result["profile_def"]:
|
||||||
return tool.Ifc.get().add(result["profile_def"])
|
return tool.Ifc.get().add(result["profile_def"])
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
|
|||||||
Reference in New Issue
Block a user