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