This commit is contained in:
Andrej730
2025-11-17 11:08:47 +05:00
parent a038fa62ba
commit 4a5f1b01a1
3 changed files with 53 additions and 48 deletions
@@ -175,10 +175,10 @@ class BaseDecorator:
# https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view # https://blender.stackexchange.com/questions/16493/is-there-a-way-to-fit-the-viewport-to-the-current-field-of-view
camera = bpy.context.scene.camera camera = bpy.context.scene.camera
render = bpy.context.scene.render render = bpy.context.scene.render
# Always use the camera's ortho_scale as the width in model space # Always use the camera's ortho_scale as the width in model space
camera_width_model = camera.data.ortho_scale camera_width_model = camera.data.ortho_scale
scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"]) scale = tool.Drawing.get_scale_ratio(tool.Drawing.get_diagram_scale(camera)["Scale"])
camera_width_mm = scale * camera_width_model camera_width_mm = scale * camera_width_model
return camera_width_mm return camera_width_mm
@@ -2544,7 +2544,6 @@ class OverrideModeSetObject(bpy.types.Operator, tool.Ifc.Operator):
props.is_changing_mode = False props.is_changing_mode = False
class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator): class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
bl_idname = "bim.direct_profile_edit" bl_idname = "bim.direct_profile_edit"
bl_label = "IFC Direct Profile Edit" bl_label = "IFC Direct Profile Edit"
@@ -2556,22 +2555,22 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
# Only allow in OBJECT or EDIT modes # Only allow in OBJECT or EDIT modes
if context.mode not in ("OBJECT", "EDIT_MESH", "EDIT_CURVE"): if context.mode not in ("OBJECT", "EDIT_MESH", "EDIT_CURVE"):
return False return False
# Must have an active object # Must have an active object
if not context.active_object: if not context.active_object:
return False return False
# In edit mode, we're good to go (we'll exit) # In edit mode, we're good to go (we'll exit)
if context.mode in ("EDIT_MESH", "EDIT_CURVE"): if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
return True return True
# In object mode, check if it's an editable object type # In object mode, check if it's an editable object type
obj = context.active_object obj = context.active_object
# Only mesh and curve objects are editable # Only mesh and curve objects are editable
if obj.type not in ("MESH", "CURVE"): if obj.type not in ("MESH", "CURVE"):
return False return False
return True return True
def _execute(self, context): def _execute(self, context):
@@ -2580,15 +2579,18 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
active_obj = context.active_object active_obj = context.active_object
if active_obj and context.mode == "OBJECT": if active_obj and context.mode == "OBJECT":
if len(context.selected_objects) > 1: if len(context.selected_objects) > 1:
self.report({"WARNING"}, "Currently Direct Profile Edit only works on one object at a time. Operating on active object only.") self.report(
bpy.ops.object.select_all(action='DESELECT') {"WARNING"},
"Currently Direct Profile Edit only works on one object at a time. Operating on active object only.",
)
bpy.ops.object.select_all(action="DESELECT")
active_obj.select_set(True) active_obj.select_set(True)
context.view_layer.objects.active = active_obj context.view_layer.objects.active = active_obj
# If we're already in edit mode, exit to object mode and SAVE changes # If we're already in edit mode, exit to object mode and SAVE changes
if context.mode in ("EDIT_MESH", "EDIT_CURVE"): if context.mode in ("EDIT_MESH", "EDIT_CURVE"):
return self.handle_exit_edit_mode(context) return self.handle_exit_edit_mode(context)
# We're in object mode, try to enter profile/axis edit # We're in object mode, try to enter profile/axis edit
return self.handle_enter_edit_mode(context) return self.handle_enter_edit_mode(context)
@@ -2597,16 +2599,16 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object obj = context.active_object
if not obj: if not obj:
return {"CANCELLED"} return {"CANCELLED"}
# Check if we're editing a representation item # Check if we're editing a representation item
if tool.Geometry.is_representation_item(obj): if tool.Geometry.is_representation_item(obj):
return self.exit_item_edit_mode(context, obj) return self.exit_item_edit_mode(context, obj)
# Check if we're editing an element-level profile or axis # Check if we're editing an element-level profile or axis
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if element and tool.Geometry.has_mesh_properties(obj.data): if element and tool.Geometry.has_mesh_properties(obj.data):
return self.exit_element_edit_mode(context, obj, element) return self.exit_element_edit_mode(context, obj, element)
# For other edit modes, use standard operator # For other edit modes, use standard operator
return bpy.ops.bim.override_mode_set_object() return bpy.ops.bim.override_mode_set_object()
@@ -2616,36 +2618,39 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
item = tool.Geometry.get_active_representation(obj) item = tool.Geometry.get_active_representation(obj)
if not item or not item.is_a("IfcSweptAreaSolid"): if not item or not item.is_a("IfcSweptAreaSolid"):
return bpy.ops.bim.override_mode_set_object() return bpy.ops.bim.override_mode_set_object()
# Use the standard save workflow for items # Use the standard save workflow for items
from bonsai.bim.module.model.decorator import ProfileDecorator from bonsai.bim.module.model.decorator import ProfileDecorator
ProfileDecorator.uninstall() ProfileDecorator.uninstall()
tool.Blender.toggle_edit_mode(context) tool.Blender.toggle_edit_mode(context)
profile = tool.Model.export_profile(obj) profile = tool.Model.export_profile(obj)
if not profile: if not profile:
def msg(self, context): def msg(self, context):
self.layout.label(text="INVALID PROFILE") self.layout.label(text="INVALID PROFILE")
context.window_manager.popup_menu(msg, title="Error", icon="ERROR") context.window_manager.popup_menu(msg, title="Error", icon="ERROR")
tool.Blender.toggle_edit_mode(context) tool.Blender.toggle_edit_mode(context)
ProfileDecorator.install(context) ProfileDecorator.install(context)
return {"CANCELLED"} return {"CANCELLED"}
old_profile = item.SweptArea old_profile = item.SweptArea
profile.ProfileName = old_profile.ProfileName profile.ProfileName = old_profile.ProfileName
ifc_file = tool.Ifc.get() ifc_file = tool.Ifc.get()
for inverse in ifc_file.get_inverse(old_profile): for inverse in ifc_file.get_inverse(old_profile):
ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile) ifcopenshell.util.element.replace_attribute(inverse, old_profile, profile)
tool.Profile.replace_profile_in_profiles_ui(old_profile.id(), profile.id()) tool.Profile.replace_profile_in_profiles_ui(old_profile.id(), profile.id())
ifcopenshell.util.element.remove_deep2(ifc_file, old_profile) ifcopenshell.util.element.remove_deep2(ifc_file, old_profile)
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
if props.representation_obj: if props.representation_obj:
tool.Geometry.reload_representation(props.representation_obj) tool.Geometry.reload_representation(props.representation_obj)
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
return {"FINISHED"} return {"FINISHED"}
except Exception as e: except Exception as e:
self.report({"ERROR"}, f"Failed to save item changes: {str(e)}") self.report({"ERROR"}, f"Failed to save item changes: {str(e)}")
@@ -2655,11 +2660,11 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
"""Exit from element-level profile or axis editing.""" """Exit from element-level profile or axis editing."""
try: try:
mesh_props = tool.Geometry.get_mesh_props(obj.data) mesh_props = tool.Geometry.get_mesh_props(obj.data)
# Check if we're editing axis # Check if we're editing axis
if mesh_props.subshape_type == "AXIS": if mesh_props.subshape_type == "AXIS":
return bpy.ops.bim.edit_extrusion_axis() return bpy.ops.bim.edit_extrusion_axis()
# Otherwise, we're editing a profile # Otherwise, we're editing a profile
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
if body: if body:
@@ -2670,7 +2675,7 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
except Exception as e: except Exception as e:
self.report({"ERROR"}, f"Failed to save profile/axis changes: {str(e)}") self.report({"ERROR"}, f"Failed to save profile/axis changes: {str(e)}")
return {"CANCELLED"} return {"CANCELLED"}
# Fallback to standard operator # Fallback to standard operator
return bpy.ops.bim.override_mode_set_object() return bpy.ops.bim.override_mode_set_object()
@@ -2679,37 +2684,37 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
obj = context.active_object obj = context.active_object
if not obj: if not obj:
return {"CANCELLED"} return {"CANCELLED"}
# Check if object has mesh or curve data # Check if object has mesh or curve data
if not hasattr(obj, 'data') or not obj.data: if not hasattr(obj, "data") or not obj.data:
self.report({"INFO"}, "Object has no editable data") self.report({"INFO"}, "Object has no editable data")
return {"CANCELLED"} return {"CANCELLED"}
element = tool.Ifc.get_entity(obj) element = tool.Ifc.get_entity(obj)
if not element: if not element:
self.report({"INFO"}, "Active object is not an IFC element") self.report({"INFO"}, "Active object is not an IFC element")
return {"CANCELLED"} return {"CANCELLED"}
# Check if this is a LAYER2 element (wall, railing, etc.) # Check if this is a LAYER2 element (wall, railing, etc.)
try: try:
material_usage = tool.Model.get_usage_type(element) material_usage = tool.Model.get_usage_type(element)
except: except:
material_usage = None material_usage = None
if material_usage == "LAYER2": if material_usage == "LAYER2":
self.report({"ERROR"}, "LAYER2 elements (walls, railings, etc.) cannot use profile editing.") self.report({"ERROR"}, "LAYER2 elements (walls, railings, etc.) cannot use profile editing.")
return {"CANCELLED"} return {"CANCELLED"}
# Try to get representation # Try to get representation
try: try:
representation = tool.Geometry.get_active_representation(obj) representation = tool.Geometry.get_active_representation(obj)
except: except:
representation = None representation = None
if not representation: if not representation:
self.report({"INFO"}, "Object has no active representation") self.report({"INFO"}, "Object has no active representation")
return {"CANCELLED"} return {"CANCELLED"}
# Check for PROFILE usage (beams, columns, members) - should edit axis # Check for PROFILE usage (beams, columns, members) - should edit axis
if material_usage == "PROFILE": if material_usage == "PROFILE":
try: try:
@@ -2717,7 +2722,7 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
except Exception as e: except Exception as e:
self.report({"ERROR"}, f"Failed to enable axis editing: {str(e)}") self.report({"ERROR"}, f"Failed to enable axis editing: {str(e)}")
return {"CANCELLED"} return {"CANCELLED"}
# Check if this is an element with an extrusion profile (LAYER3, etc) # Check if this is an element with an extrusion profile (LAYER3, etc)
try: try:
body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW")
@@ -2729,7 +2734,7 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
except Exception as e: except Exception as e:
self.report({"ERROR"}, f"Failed to enable profile editing: {str(e)}") self.report({"ERROR"}, f"Failed to enable profile editing: {str(e)}")
return {"CANCELLED"} return {"CANCELLED"}
# Otherwise, try to enter item mode and edit SweptAreaSolid # Otherwise, try to enter item mode and edit SweptAreaSolid
return self.enter_item_edit_mode(context, obj) return self.enter_item_edit_mode(context, obj)
@@ -2740,7 +2745,7 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
if not props.representation_obj: if not props.representation_obj:
bpy.ops.bim.import_representation_items() bpy.ops.bim.import_representation_items()
props = tool.Geometry.get_geometry_props() props = tool.Geometry.get_geometry_props()
# Find the SweptAreaSolid item # Find the SweptAreaSolid item
swept_solid_obj = None swept_solid_obj = None
for item_obj_data in props.item_objs: for item_obj_data in props.item_objs:
@@ -2754,17 +2759,17 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
break break
except: except:
continue continue
if not swept_solid_obj: if not swept_solid_obj:
self.report({"INFO"}, "No editable SweptAreaSolid item found") self.report({"INFO"}, "No editable SweptAreaSolid item found")
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
return {"CANCELLED"} return {"CANCELLED"}
# Select and activate the swept solid object # Select and activate the swept solid object
bpy.ops.object.select_all(action='DESELECT') bpy.ops.object.select_all(action="DESELECT")
swept_solid_obj.select_set(True) swept_solid_obj.select_set(True)
context.view_layer.objects.active = swept_solid_obj context.view_layer.objects.active = swept_solid_obj
# Import the profile to prepare for editing # Import the profile to prepare for editing
item = tool.Geometry.get_active_representation(swept_solid_obj) item = tool.Geometry.get_active_representation(swept_solid_obj)
if item and item.is_a("IfcSweptAreaSolid"): if item and item.is_a("IfcSweptAreaSolid"):
@@ -2774,16 +2779,17 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
self.report({"ERROR"}, "Couldn't import profile for editing") self.report({"ERROR"}, "Couldn't import profile for editing")
tool.Geometry.disable_item_mode() tool.Geometry.disable_item_mode()
return {"CANCELLED"} return {"CANCELLED"}
tool.Ifc.link(item, swept_solid_obj.data) tool.Ifc.link(item, swept_solid_obj.data)
tool.Blender.toggle_edit_mode(context) tool.Blender.toggle_edit_mode(context)
from bonsai.bim.module.model.decorator import ProfileDecorator from bonsai.bim.module.model.decorator import ProfileDecorator
ProfileDecorator.install(context) ProfileDecorator.install(context)
if not bpy.app.background: if not bpy.app.background:
tool.Blender.set_viewport_tool("bim.cad_tool") tool.Blender.set_viewport_tool("bim.cad_tool")
return {"FINISHED"} return {"FINISHED"}
except Exception as e: except Exception as e:
self.report({"ERROR"}, f"Failed to enter item edit mode: {str(e)}") self.report({"ERROR"}, f"Failed to enter item edit mode: {str(e)}")
@@ -2792,9 +2798,8 @@ class DirectProfileEdit(bpy.types.Operator, tool.Ifc.Operator):
except: except:
pass pass
return {"CANCELLED"} return {"CANCELLED"}
return {"CANCELLED"}
return {"CANCELLED"}
class FlipObject(bpy.types.Operator): class FlipObject(bpy.types.Operator):
+2 -2
View File
@@ -2009,11 +2009,11 @@ class Drawing(bonsai.core.tool.Drawing):
if matrix_world is None: if matrix_world is None:
# Use normalized camera matrix (without scale) for RCP compatibility # Use normalized camera matrix (without scale) for RCP compatibility
matrix_world = cls.get_camera_matrix(camera) matrix_world = cls.get_camera_matrix(camera)
# Check if this is an RCP (reflected ceiling plan) by checking for negative scale # Check if this is an RCP (reflected ceiling plan) by checking for negative scale
camera_scale = camera.matrix_world.to_scale() camera_scale = camera.matrix_world.to_scale()
is_rcp = camera_scale.x < 0 or camera_scale.y < 0 or camera_scale.z < 0 is_rcp = camera_scale.x < 0 or camera_scale.y < 0 or camera_scale.z < 0
# For RCP, the offset needs to be positive instead of negative # For RCP, the offset needs to be positive instead of negative
# because the Z axis is flipped 180° in get_camera_matrix # because the Z axis is flipped 180° in get_camera_matrix
offset_direction = 1 if is_rcp else -1 offset_direction = 1 if is_rcp else -1