From 3a6cf0e5ae4482ec65b6115541b0531914feaf9b Mon Sep 17 00:00:00 2001 From: Dion Moult Date: Sat, 15 Jun 2024 16:40:53 +1000 Subject: [PATCH] Fix #4854. Make IFC edit mode toggle dynamic to prevent editing uneditable things. --- .../blenderbim/bim/module/geometry/data.py | 35 ++++++++++++++++--- .../bim/module/geometry/operator.py | 17 +++------ .../blenderbim/bim/module/geometry/prop.py | 18 +++++----- src/blenderbim/blenderbim/tool/blender.py | 28 ++++++++++----- 4 files changed, 61 insertions(+), 37 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/geometry/data.py b/src/blenderbim/blenderbim/bim/module/geometry/data.py index a65a49283b..f44493e54b 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/data.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/data.py @@ -24,6 +24,7 @@ from mathutils import Vector def refresh(): + ViewportData.is_loaded = False PlacementData.is_loaded = False DerivedCoordinatesData.is_loaded = False RepresentationsData.is_loaded = False @@ -31,6 +32,28 @@ def refresh(): ConnectionsData.is_loaded = False +class ViewportData: + data = {} + is_loaded = False + + @classmethod + def load(cls): + cls.is_loaded = True + cls.data = {"mode": cls.mode()} + + @classmethod + def mode(cls): + obj = bpy.context.active_object + obj_mode = [("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0)] + mesh_modes = [ + ("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0), + ("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1), + ] + if not obj or not tool.Blender.is_editable(obj): + return obj_mode + return mesh_modes + + class RepresentationsData: data = {} is_loaded = False @@ -334,11 +357,13 @@ class PlacementData: obj = bpy.context.active_object if obj and props.has_blender_offset: xyz = cls.original_xyz(obj) - cls.data.update({ - "original_x": str(xyz[0]), - "original_y": str(xyz[1]), - "original_z": str(xyz[2]), - }) + cls.data.update( + { + "original_x": str(xyz[0]), + "original_y": str(xyz[1]), + "original_z": str(xyz[2]), + } + ) cls.is_loaded = True @classmethod diff --git a/src/blenderbim/blenderbim/bim/module/geometry/operator.py b/src/blenderbim/blenderbim/bim/module/geometry/operator.py index c33bfeaec1..5b9ea31939 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/operator.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/operator.py @@ -1531,7 +1531,7 @@ class OverrideModeSetEdit(bpy.types.Operator): return IfcStore.execute_ifc_operator(self, context) def _execute(self, context): - selected_objs = context.selected_objects or ([context.active_object] if context.active_object else []) + selected_objs = tool.Blender.get_selected_objects() active_obj = context.active_object if context.active_object: @@ -1542,23 +1542,13 @@ class OverrideModeSetEdit(bpy.types.Operator): return bpy.ops.bim.enable_editing_boundary_geometry() for obj in selected_objs: - if not obj: - continue - obj_supports_edit_mode, message = tool.Blender.object_supports_edit_mode(obj) - if not obj_supports_edit_mode: - self.report({"INFO"}, message) + if not tool.Blender.is_editable(obj): + self.report({"INFO"}, f"Object cannot be edited: {obj.name}") obj.select_set(False) continue element = tool.Ifc.get_entity(obj) if not element: continue - usage_type = tool.Model.get_usage_type(element) - if usage_type is not None and usage_type not in ("PROFILE", "LAYER3"): - # Parametric objects shall not be edited as meshes as they - # can be modified to be incompatible with the parametric - # constraints. - obj.select_set(False) - continue representation = tool.Geometry.get_active_representation(obj) if not representation: continue @@ -1566,6 +1556,7 @@ class OverrideModeSetEdit(bpy.types.Operator): obj.select_set(False) continue + usage_type = tool.Model.get_usage_type(element) is_profile = True representation_class = tool.Geometry.get_ifc_representation_class(element, representation) if usage_type == "PROFILE": diff --git a/src/blenderbim/blenderbim/bim/module/geometry/prop.py b/src/blenderbim/blenderbim/bim/module/geometry/prop.py index 83e1069b7e..c7e0ab5362 100644 --- a/src/blenderbim/blenderbim/bim/module/geometry/prop.py +++ b/src/blenderbim/blenderbim/bim/module/geometry/prop.py @@ -19,7 +19,7 @@ import bpy import blenderbim.tool as tool from blenderbim.bim.prop import StrProperty, Attribute -from blenderbim.bim.module.geometry.data import RepresentationsData +from blenderbim.bim.module.geometry.data import RepresentationsData, ViewportData from bpy.types import PropertyGroup from bpy.props import ( PointerProperty, @@ -48,6 +48,12 @@ def update_mode(self, context): bpy.ops.bim.override_mode_set_edit("INVOKE_DEFAULT") +def get_mode(self, context): + if not ViewportData.is_loaded: + ViewportData.load() + return ViewportData.data["mode"] + + def get_styles(self, context): # postponed import to avoid circular import from blenderbim.bim.module.material.data import MaterialsData @@ -129,13 +135,5 @@ class BIMGeometryProperties(PropertyGroup): # Navisworks workaround should_force_triangulation: BoolProperty(name="Force Triangulation", default=False) is_changing_mode: BoolProperty(name="Is Changing Mode", default=False) - mode: EnumProperty( - default="OBJECT", - items=[ - ("OBJECT", "IFC Object Mode", "", "OBJECT_DATAMODE", 0), - ("EDIT", "IFC Edit Mode", "", "EDITMODE_HLT", 1), - ], - name="IFC Interaction Mode", - update=update_mode, - ) + mode: EnumProperty(items=get_mode, name="IFC Interaction Mode", update=update_mode) representation_from_object: PointerProperty(type=bpy.types.Object) diff --git a/src/blenderbim/blenderbim/tool/blender.py b/src/blenderbim/blenderbim/tool/blender.py index 46a435eccb..2bd43c1f39 100644 --- a/src/blenderbim/blenderbim/tool/blender.py +++ b/src/blenderbim/blenderbim/tool/blender.py @@ -131,8 +131,12 @@ class Blender(blenderbim.core.tool.Blender): @classmethod def get_selected_objects(cls) -> set[bpy.types.Object]: if bpy.context.selected_objects: - return set(bpy.context.selected_objects + [bpy.context.active_object]) - return set([bpy.context.active_object]) + if active_obj := bpy.context.active_object: + return set(bpy.context.selected_objects + [active_obj]) + return set(bpy.context.selected_objects) + if active_obj := bpy.context.active_object: + return {active_obj} + return set() @classmethod def create_ifc_object( @@ -666,8 +670,7 @@ class Blender(blenderbim.core.tool.Blender): return bpy.ops.object.mode_set(mode="EDIT", toggle=True) elif ao.type in cls.OBJECT_TYPES_THAT_SUPPORT_EDIT_GPENCIL_MODE: return bpy.ops.object.mode_set(mode="EDIT_GPENCIL", toggle=True) - else: - return {"CANCELLED"} + return {"CANCELLED"} @classmethod def is_object_an_ifc_class(cls, obj: bpy.types.Object, classes: Iterable[str]) -> bool: @@ -766,12 +769,19 @@ class Blender(blenderbim.core.tool.Blender): return collections_mapping @classmethod - def object_supports_edit_mode(cls, obj): - if not obj.data: - return False, "Can't Edit Empty Object" + def is_editable(cls, obj): + if obj.type not in cls.OBJECT_TYPES_THAT_SUPPORT_EDIT_MODE: + return False + if not (element := tool.Ifc.get_entity(obj)): + return True if obj in bpy.context.scene.BIMProjectProperties.clipping_planes_objs: - return False, "Can't Edit Clipping Plane Geometry" - return True, "" + return False + usage_type = tool.Model.get_usage_type(element) + if usage_type in ("LAYER1", "LAYER2"): + # At the moment, these type types of parametric elements (walls, + # and "blocks") cannot be edited as a mesh-like object. + return False + return True class Modifier: @classmethod