From 75c98e5b0043ebbc9c14c88a00c70ca8162af1ad Mon Sep 17 00:00:00 2001 From: Andrej730 Date: Tue, 28 Mar 2023 16:53:09 +0500 Subject: [PATCH] Exiting current editing decorator if user switches from EDIT mode Switching from EDIT mode acts like canceling editing which seems intuitive. Now it's also possible to add similar thing to other use of decorators - beware of the possible Blender bug - mentioned it in `ProfileDecorator.install description` Added this change to editing of profiles, extrusion axis, roof and railing paths, extrusion profiles. --- .../blenderbim/bim/module/model/decorator.py | 13 ++++-- .../blenderbim/bim/module/model/profile.py | 40 +++++++++-------- .../blenderbim/bim/module/model/railing.py | 26 ++++++----- .../blenderbim/bim/module/model/roof.py | 33 +++++++++----- .../blenderbim/bim/module/model/slab.py | 44 +++++++++++-------- .../blenderbim/bim/module/profile/operator.py | 25 ++++++++--- 6 files changed, 111 insertions(+), 70 deletions(-) diff --git a/src/blenderbim/blenderbim/bim/module/model/decorator.py b/src/blenderbim/blenderbim/bim/module/model/decorator.py index 43ddffdd7c..4b72e162cf 100644 --- a/src/blenderbim/blenderbim/bim/module/model/decorator.py +++ b/src/blenderbim/blenderbim/bim/module/model/decorator.py @@ -42,12 +42,16 @@ class ProfileDecorator: installed = None @classmethod - def install(cls, context, get_custom_bmesh=None, draw_faces=False): + def install(cls, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None): + """Note that operators that change mesh in `exit_edit_mode_callback` can freeze blender. + The workaround is to move their code to function and use it for callback. + + Example: https://devtalk.blender.org/t/calling-operator-that-saves-bmesh-freezes-blender-forever/28595""" if cls.installed: cls.uninstall() handler = cls() cls.installed = SpaceView3D.draw_handler_add( - handler, (context, get_custom_bmesh, draw_faces), "WINDOW", "POST_VIEW" + handler, (context, get_custom_bmesh, draw_faces, exit_edit_mode_callback), "WINDOW", "POST_VIEW" ) @classmethod @@ -76,10 +80,13 @@ class ProfileDecorator: face_indices = [[v.index for v in f.verts] for f in traingulated_bm.faces] self.create_batch("TRIS", vertices_coords, faces_color, face_indices) - def __call__(self, context, get_custom_bmesh=None, draw_faces=False): + def __call__(self, context, get_custom_bmesh=None, draw_faces=False, exit_edit_mode_callback=None): obj = context.active_object if obj.mode != "EDIT": + if exit_edit_mode_callback: + ProfileDecorator.uninstall() + exit_edit_mode_callback() return if get_custom_bmesh: diff --git a/src/blenderbim/blenderbim/bim/module/model/profile.py b/src/blenderbim/blenderbim/bim/module/model/profile.py index 5e267ce3b7..63496d95dd 100644 --- a/src/blenderbim/blenderbim/bim/module/model/profile.py +++ b/src/blenderbim/blenderbim/bim/module/model/profile.py @@ -938,12 +938,32 @@ class EnableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator): tool.Model.import_axis([Vector((0, 0, 0)), direction * extrusion.Depth], obj=obj, position=position) bpy.ops.object.mode_set(mode="EDIT") - ProfileDecorator.install(context) + ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_axis(context)) if not bpy.app.background: bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") return {"FINISHED"} +def disable_editing_extrusion_axis(context): + ProfileDecorator.uninstall() + bpy.ops.object.mode_set(mode="OBJECT") + + obj = context.active_object + element = tool.Ifc.get_entity(obj) + body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + + blenderbim.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=body, + should_reload=True, + is_global=True, + should_sync_changes_first=False, + ) + return {"FINISHED"} + + class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_extrusion_axis" bl_label = "Disable Editing Extrusion Axis" @@ -954,23 +974,7 @@ class DisableEditingExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator): return context.selected_objects def _execute(self, context): - ProfileDecorator.uninstall() - bpy.ops.object.mode_set(mode="OBJECT") - - obj = context.active_object - element = tool.Ifc.get_entity(obj) - body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") - - blenderbim.core.geometry.switch_representation( - tool.Ifc, - tool.Geometry, - obj=obj, - representation=body, - should_reload=True, - is_global=True, - should_sync_changes_first=False, - ) - return {"FINISHED"} + return disable_editing_extrusion_axis() class EditExtrusionAxis(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/railing.py b/src/blenderbim/blenderbim/bim/module/model/railing.py index e93b5bf2e4..160be5aa19 100644 --- a/src/blenderbim/blenderbim/bim/module/model/railing.py +++ b/src/blenderbim/blenderbim/bim/module/model/railing.py @@ -364,26 +364,30 @@ class EnableEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): if bpy.context.object.mode != "EDIT": bpy.ops.object.mode_set(mode="EDIT") bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") - ProfileDecorator.install(context) + ProfileDecorator.install(context, exit_edit_mode_callback=lambda: cancel_editing_railing_path(context)) return {"FINISHED"} +def cancel_editing_railing_path(context): + obj = context.active_object + props = obj.BIMRailingProperties + + ProfileDecorator.uninstall() + props.is_editing_path = False + + update_railing_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") + return {"FINISHED"} + + class CancelEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.cancel_editing_railing_path" bl_label = "Cancel Editing Railing Path" bl_options = {"REGISTER"} def _execute(self, context): - obj = context.active_object - props = obj.BIMRailingProperties - - ProfileDecorator.uninstall() - props.is_editing_path = False - - update_railing_modifier_bmesh(context) - if bpy.context.object.mode == "EDIT": - bpy.ops.object.mode_set(mode="OBJECT") - return {"FINISHED"} + return cancel_editing_railing_path(context) class FinishEditingRailingPath(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/roof.py b/src/blenderbim/blenderbim/bim/module/model/roof.py index 76e74d170a..d3cacd9173 100644 --- a/src/blenderbim/blenderbim/bim/module/model/roof.py +++ b/src/blenderbim/blenderbim/bim/module/model/roof.py @@ -215,7 +215,7 @@ def generate_hiped_roof_bmesh(bm, mode="ANGLE", height=1.0, angle=pi / 18, mutat # find footprint edges for edge in bm.edges: - if all(float_is_zero(v.co.z-footprint_z) for v in edge.verts): + if all(float_is_zero(v.co.z - footprint_z) for v in edge.verts): footprint_edges.append(edge) footprint_verts.update(edge.verts) @@ -557,26 +557,35 @@ class EnableEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): tool.Blender.bmesh_join(main_bm, second_bm, callback=mark_preview_edges) return main_bm - ProfileDecorator.install(context, get_custom_bmesh, draw_faces=True) + ProfileDecorator.install( + context, + get_custom_bmesh, + draw_faces=True, + exit_edit_mode_callback=lambda: cancel_editing_roof_path(context), + ) return {"FINISHED"} +def cancel_editing_roof_path(context): + obj = context.active_object + props = obj.BIMRoofProperties + + ProfileDecorator.uninstall() + props.is_editing_path = False + + update_roof_modifier_bmesh(context) + if bpy.context.object.mode == "EDIT": + bpy.ops.object.mode_set(mode="OBJECT") + return {"FINISHED"} + + class CancelEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.cancel_editing_roof_path" bl_label = "Cancel Editing Roof Path" bl_options = {"REGISTER"} def _execute(self, context): - obj = context.active_object - props = obj.BIMRoofProperties - - ProfileDecorator.uninstall() - props.is_editing_path = False - - update_roof_modifier_bmesh(context) - if bpy.context.object.mode == "EDIT": - bpy.ops.object.mode_set(mode="OBJECT") - return {"FINISHED"} + return cancel_editing_roof_path(context) class FinishEditingRoofPath(bpy.types.Operator, tool.Ifc.Operator): diff --git a/src/blenderbim/blenderbim/bim/module/model/slab.py b/src/blenderbim/blenderbim/bim/module/model/slab.py index 8f0372dd30..234d23e37b 100644 --- a/src/blenderbim/blenderbim/bim/module/model/slab.py +++ b/src/blenderbim/blenderbim/bim/module/model/slab.py @@ -554,6 +554,26 @@ class DisableEditingSketchExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator return {"FINISHED"} +def disable_editing_extrusion_profile(context): + ProfileDecorator.uninstall() + bpy.ops.object.mode_set(mode="OBJECT") + + obj = context.active_object + element = tool.Ifc.get_entity(obj) + body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") + + blenderbim.core.geometry.switch_representation( + tool.Ifc, + tool.Geometry, + obj=obj, + representation=body, + should_reload=True, + is_global=True, + should_sync_changes_first=False, + ) + return {"FINISHED"} + + class DisableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_extrusion_profile" bl_label = "Disable Editing Extrusion Profile" @@ -564,23 +584,7 @@ class DisableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): return context.selected_objects def _execute(self, context): - ProfileDecorator.uninstall() - bpy.ops.object.mode_set(mode="OBJECT") - - obj = context.active_object - element = tool.Ifc.get_entity(obj) - body = ifcopenshell.util.representation.get_representation(element, "Model", "Body", "MODEL_VIEW") - - blenderbim.core.geometry.switch_representation( - tool.Ifc, - tool.Geometry, - obj=obj, - representation=body, - should_reload=True, - is_global=True, - should_sync_changes_first=False, - ) - return {"FINISHED"} + return disable_editing_extrusion_profile(context) class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): @@ -611,7 +615,7 @@ class EnableEditingExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): tool.Model.import_profile(extrusion.SweptArea, obj=obj, position=position) bpy.ops.object.mode_set(mode="EDIT") - ProfileDecorator.install(context) + ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context)) if not bpy.app.background: bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") return {"FINISHED"} @@ -648,7 +652,9 @@ class EditExtrusionProfile(bpy.types.Operator, tool.Ifc.Operator): self.layout.label(text="INVALID PROFILE") bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") - ProfileDecorator.install(context) + ProfileDecorator.install( + context, exit_edit_mode_callback=lambda: disable_editing_extrusion_profile(context) + ) bpy.ops.object.mode_set(mode="EDIT") return diff --git a/src/blenderbim/blenderbim/bim/module/profile/operator.py b/src/blenderbim/blenderbim/bim/module/profile/operator.py index 68a2b07537..b7f496a042 100644 --- a/src/blenderbim/blenderbim/bim/module/profile/operator.py +++ b/src/blenderbim/blenderbim/bim/module/profile/operator.py @@ -23,6 +23,7 @@ import blenderbim.tool as tool import blenderbim.bim.module.model.profile as model_profile from blenderbim.bim.module.model.decorator import ProfileDecorator from blenderbim.bim.module.profile.prop import generate_thumbnail_for_active_profile +from blenderbim.bim.module.profile.data import refresh class LoadProfiles(bpy.types.Operator): @@ -138,21 +139,29 @@ class EnableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): bpy.context.scene.collection.objects.link(obj) bpy.context.view_layer.objects.active = obj bpy.ops.object.mode_set(mode="EDIT") - ProfileDecorator.install(context) + ProfileDecorator.install(context, exit_edit_mode_callback=lambda: disable_editing_arbitrary_profile(context)) bpy.ops.wm.tool_set_by_id(tool.Blender.get_viewport_context(), name="bim.cad_tool") +def disable_editing_arbitrary_profile(context): + obj = context.active_object + if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE": + ProfileDecorator.uninstall() + bpy.ops.object.mode_set(mode="OBJECT") + bpy.data.objects.remove(obj) + + # need to update profile manager ui + # if this was called from decorator + refresh() + + class DisableEditingArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): bl_idname = "bim.disable_editing_arbitrary_profile" bl_label = "Disable Editing Arbitrary Profile" bl_options = {"REGISTER", "UNDO"} def _execute(self, context): - obj = context.active_object - if obj and obj.data and obj.data.BIMMeshProperties.subshape_type == "PROFILE": - ProfileDecorator.uninstall() - bpy.ops.object.mode_set(mode="OBJECT") - bpy.data.objects.remove(obj) + return disable_editing_arbitrary_profile(context) class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): @@ -176,7 +185,9 @@ class EditArbitraryProfile(bpy.types.Operator, tool.Ifc.Operator): self.layout.label(text="INVALID PROFILE: " + indices[1]) bpy.context.window_manager.popup_menu(msg, title="Error", icon="ERROR") - ProfileDecorator.install(context) + ProfileDecorator.install( + context, exit_edit_mode_callback=lambda: disable_editing_arbitrary_profile(context) + ) bpy.ops.object.mode_set(mode="EDIT") return