CAM: Fix infinite recompute loop when ToolBit properties use expressions
A bug in the ToolBit model caused an infinite recompute loop and UI freeze when properties such as Diameter, Flutes, or CuttingEdgeHeight were set to expressions. The visual representation update was being triggered during document recompute, which could recursively trigger further recomputes. This fix defers visual updates by queuing them and processing only after the document recompute completes, using a document observer. The observer is cleaned up after use and on object deletion, preventing memory leaks and repeated recompute cycles. src/Mod/CAM/Path/Tool/toolbit/models/base.py: - ToolBitRecomputeObserver: Document observer class that triggers queued visual updates after recompute completes via slotRecomputedDocument. - _queue_visual_update: Queues a visual update to be processed after document recompute. - _setup_recompute_observer: Registers the document observer for recompute completion. - _process_queued_visual_update: Processes the queued visual update and cleans up the observer. - onChanged: Now queues visual updates instead of calling them directly. - onDelete: Cleans up any pending document observer before object removal.
This commit is contained in:
committed by
Chris Hennes
parent
df7ee72171
commit
871ab6df64
@@ -44,6 +44,24 @@ from ..util import to_json, format_value
|
||||
ToolBitView = LazyLoader("Path.Tool.toolbit.ui.view", globals(), "Path.Tool.toolbit.ui.view")
|
||||
|
||||
|
||||
class ToolBitRecomputeObserver:
|
||||
"""Document observer that triggers queued visual updates after recompute completes."""
|
||||
|
||||
def __init__(self, toolbit_proxy):
|
||||
self.toolbit_proxy = toolbit_proxy
|
||||
|
||||
def slotRecomputedDocument(self, doc):
|
||||
"""Called when document recompute is finished."""
|
||||
# Only process updates for the correct document
|
||||
if doc != self.toolbit_proxy.obj.Document:
|
||||
return
|
||||
|
||||
# Process any queued visual updates
|
||||
if self.toolbit_proxy and hasattr(self.toolbit_proxy, "_process_queued_visual_update"):
|
||||
Path.Log.debug("Document recompute finished, processing queued visual update")
|
||||
self.toolbit_proxy._process_queued_visual_update()
|
||||
|
||||
|
||||
PropertyGroupShape = "Shape"
|
||||
|
||||
if False:
|
||||
@@ -559,15 +577,19 @@ class ToolBit(Asset, ABC):
|
||||
new_value = obj.getPropertyByName(prop)
|
||||
Path.Log.debug(
|
||||
f"Shape parameter '{prop}' changed to {new_value}. "
|
||||
f"Updating visual representation."
|
||||
f"Queuing visual representation update."
|
||||
)
|
||||
self._tool_bit_shape.set_parameter(prop, new_value)
|
||||
self._update_visual_representation()
|
||||
self._queue_visual_update()
|
||||
finally:
|
||||
self._in_update = False
|
||||
|
||||
def onDelete(self, obj, arg2=None):
|
||||
Path.Log.track(obj.Label)
|
||||
# Clean up any pending observer
|
||||
if hasattr(self, "_recompute_observer"):
|
||||
FreeCAD.removeDocumentObserver(self._recompute_observer)
|
||||
del self._recompute_observer
|
||||
self._removeBitBody()
|
||||
obj.Document.removeObject(obj.Name)
|
||||
|
||||
@@ -761,6 +783,37 @@ class ToolBit(Asset, ABC):
|
||||
if material_value in ("HSS", "Carbide") and self.obj.Material != material_value:
|
||||
PathUtil.setProperty(self.obj, "Material", material_value)
|
||||
|
||||
def _queue_visual_update(self):
|
||||
"""Queue a visual update to be processed after document recompute is complete."""
|
||||
if not hasattr(self, "_visual_update_queued"):
|
||||
self._visual_update_queued = False
|
||||
|
||||
if not self._visual_update_queued:
|
||||
self._visual_update_queued = True
|
||||
Path.Log.debug(f"Queuing visual update for {self.obj.Label}")
|
||||
|
||||
# Set up a document observer to process the update after recompute
|
||||
self._setup_recompute_observer()
|
||||
|
||||
def _setup_recompute_observer(self):
|
||||
"""Set up a document observer to process queued visual updates after recompute."""
|
||||
if not hasattr(self, "_recompute_observer"):
|
||||
Path.Log.debug(f"Setting up recompute observer for {self.obj.Label}")
|
||||
self._recompute_observer = ToolBitRecomputeObserver(self)
|
||||
FreeCAD.addDocumentObserver(self._recompute_observer)
|
||||
|
||||
def _process_queued_visual_update(self):
|
||||
"""Process the queued visual update."""
|
||||
if hasattr(self, "_visual_update_queued") and self._visual_update_queued:
|
||||
self._visual_update_queued = False
|
||||
Path.Log.debug(f"Processing queued visual update for {self.obj.Label}")
|
||||
self._update_visual_representation()
|
||||
|
||||
# Clean up the observer
|
||||
if hasattr(self, "_recompute_observer"):
|
||||
FreeCAD.removeDocumentObserver(self._recompute_observer)
|
||||
del self._recompute_observer
|
||||
|
||||
def _update_visual_representation(self):
|
||||
"""
|
||||
Updates the visual representation of the tool bit based on the current
|
||||
|
||||
Reference in New Issue
Block a user