From 89f3520240e81a6dc9539638e91f20b22722a9ff Mon Sep 17 00:00:00 2001 From: HellAholic Date: Mon, 16 Mar 2026 13:13:43 +0100 Subject: [PATCH 1/2] Ensure build volume initialized before arrange Fix a race where BuildVolume dimensions could remain zero because globalContainerStackChanged/activeQualityChanged fire before BuildVolume is constructed. Call _onStackChanged() during initialization and re-trigger it from _onEngineCreated() (with a warning) if the volume AABB is still None so dimensions are populated and rebuild can succeed. In CuraApplication._arrangeAll, handle a None build-volume bounding box by logging a warning, requesting a rebuild, and aborting arrange to avoid comparing against an uninitialized volume. --- cura/BuildVolume.py | 13 +++++++++++++ cura/CuraApplication.py | 11 ++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index a102d7bf480..5cb8db4f692 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -139,6 +139,13 @@ def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = # Therefore this works. self._machine_manager.activeQualityChanged.connect(self._onStackChanged) + # globalContainerStackChanged and activeQualityChanged both fire during MachineErrorChecker.initialize() + # (inside getMachineManager()), which runs *before* BuildVolume is constructed. As a result, + # _onStackChanged is never triggered for the initial machine setup and _width/_height/_depth + # remain 0. Explicitly queue the timer here so _onStackChangeTimerFinished always runs once + # after construction to populate the dimensions regardless of signal ordering. + self._onStackChanged() + # Enable and disable extruder self._machine_manager.extruderChanged.connect(self.updateNodeBoundaryCheck) @@ -699,6 +706,12 @@ def _onStackChangeTimerFinished(self) -> None: def _onEngineCreated(self) -> None: self._engine_ready = True self.rebuild() + if self._volume_aabb is None: + # rebuild() returned early because _width/_height/_depth were still 0 — the stack-change + # timer from __init__ may not have fired yet, or the container stack was not ready. + # Re-queue it so dimensions are fetched and rebuild() is retried after a short delay. + Logger.warning("BuildVolume: _volume_aabb is still None after engine created; re-triggering stack refresh.") + self._onStackChanged() def _onSettingChangeTimerFinished(self) -> None: if not self._global_container_stack: diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index df78cdec7b0..1d74eed3b8d 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1589,7 +1589,16 @@ def _arrangeAll(self, *, grid_arrangement: bool) -> None: if node.callDecoration("getBuildPlateNumber") == active_build_plate: # Skip nodes that are too big bounding_box = node.getBoundingBox() - if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth: + volume_bounding_box = self._volume.getBoundingBox() + if volume_bounding_box is None: + # Build volume has not been initialised yet (race condition: rebuild() was never + # triggered because globalContainerStackChanged fired before BuildVolume was + # constructed). Trigger a rebuild now and bail out; the user can retry arrange + # once the volume is ready. + Logger.warning("_arrangeAll: build volume bounding box is None — rebuild not yet triggered. Requesting rebuild and aborting arrange.") + self._volume.rebuild() + return + if bounding_box is None or bounding_box.width < volume_bounding_box.width or bounding_box.depth < volume_bounding_box.depth: # Arrange only the unlocked nodes and keep the locked ones in place if node.getSetting(SceneNodeSettings.LockPosition): locked_nodes.append(node) From 1106bd31b1e88a35a0fb46ddfd03e63f6e63b632 Mon Sep 17 00:00:00 2001 From: HellAholic Date: Mon, 16 Mar 2026 13:55:31 +0100 Subject: [PATCH 2/2] Simplify comments and clarify rebuild warnings Clean up and shorten explanatory comments in BuildVolume related to queuing the stack-change timer, and simplify the warning text when rebuild() returns early or the volume bounding box is missing. These edits reduce verbose explanations about signal ordering/race conditions while keeping behavior unchanged. --- cura/BuildVolume.py | 9 ++------- cura/CuraApplication.py | 5 +---- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/cura/BuildVolume.py b/cura/BuildVolume.py index 5cb8db4f692..b247c77c999 100755 --- a/cura/BuildVolume.py +++ b/cura/BuildVolume.py @@ -139,10 +139,7 @@ def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = # Therefore this works. self._machine_manager.activeQualityChanged.connect(self._onStackChanged) - # globalContainerStackChanged and activeQualityChanged both fire during MachineErrorChecker.initialize() - # (inside getMachineManager()), which runs *before* BuildVolume is constructed. As a result, - # _onStackChanged is never triggered for the initial machine setup and _width/_height/_depth - # remain 0. Explicitly queue the timer here so _onStackChangeTimerFinished always runs once + # Explicitly queue the timer here so _onStackChangeTimerFinished always runs once # after construction to populate the dimensions regardless of signal ordering. self._onStackChanged() @@ -707,9 +704,7 @@ def _onEngineCreated(self) -> None: self._engine_ready = True self.rebuild() if self._volume_aabb is None: - # rebuild() returned early because _width/_height/_depth were still 0 — the stack-change - # timer from __init__ may not have fired yet, or the container stack was not ready. - # Re-queue it so dimensions are fetched and rebuild() is retried after a short delay. + # rebuild() returned early. Re-queue it so dimensions are fetched and rebuild() is retried after a short delay. Logger.warning("BuildVolume: _volume_aabb is still None after engine created; re-triggering stack refresh.") self._onStackChanged() diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py index 1d74eed3b8d..61d9c75ed67 100755 --- a/cura/CuraApplication.py +++ b/cura/CuraApplication.py @@ -1591,11 +1591,8 @@ def _arrangeAll(self, *, grid_arrangement: bool) -> None: bounding_box = node.getBoundingBox() volume_bounding_box = self._volume.getBoundingBox() if volume_bounding_box is None: - # Build volume has not been initialised yet (race condition: rebuild() was never - # triggered because globalContainerStackChanged fired before BuildVolume was - # constructed). Trigger a rebuild now and bail out; the user can retry arrange - # once the volume is ready. Logger.warning("_arrangeAll: build volume bounding box is None — rebuild not yet triggered. Requesting rebuild and aborting arrange.") + # Trigger a rebuild now and bail out; the user can retry arrange once the volume is ready. self._volume.rebuild() return if bounding_box is None or bounding_box.width < volume_bounding_box.width or bounding_box.depth < volume_bounding_box.depth: