diff --git a/conandata.yml b/conandata.yml index c79189c3c8..1182dc248b 100644 --- a/conandata.yml +++ b/conandata.yml @@ -1,16 +1,16 @@ -version: "5.12.0-beta.1" +version: "5.12.0-beta.2" commit: "unknown" requirements: - - "cura_resources/5.12.0-beta.1" - - "uranium/5.12.0-beta.1" - - "curaengine/5.12.0-beta.1" - - "cura_binary_data/5.12.0-beta.1" - - "fdm_materials/5.12.0-beta.1" + - "cura_resources/5.12.0-beta.2" + - "uranium/5.12.0-beta.2" + - "curaengine/5.12.0-beta.2" + - "cura_binary_data/5.12.0-beta.2" + - "fdm_materials/5.12.0-beta.2" - "dulcificum/5.10.0" - "pysavitar/5.11.0-alpha.0" - "pynest2d/5.10.0" requirements_internal: - - "fdm_materials/5.12.0-beta.1" + - "fdm_materials/5.12.0-beta.2" - "cura_private_data/5.12.0-alpha.0@internal/testing" requirements_enterprise: - "native_cad_plugin/2.0.0" diff --git a/plugins/3MFReader/ThreeMFWorkspaceReader.py b/plugins/3MFReader/ThreeMFWorkspaceReader.py index 5d46be92c7..522579161d 100755 --- a/plugins/3MFReader/ThreeMFWorkspaceReader.py +++ b/plugins/3MFReader/ThreeMFWorkspaceReader.py @@ -12,6 +12,7 @@ import xml.etree.ElementTree as ET from UM.Math.AxisAlignedBox import AxisAlignedBox from UM.Math.Vector import Vector +from UM.Operations.AddSceneNodeOperation import AddSceneNodeOperation from UM.Util import parseBool from UM.Workspace.WorkspaceReader import WorkspaceReader from UM.Application import Application @@ -145,12 +146,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader): self._machine_info = None self._user_settings: Dict[str, Dict[str, Any]] = {} + self._deferred_node_file_name: Optional[str] = None def _clearState(self): self._id_mapping = {} self._old_new_materials = {} self._machine_info = None self._user_settings = {} + self._deferred_node_file_name = None def clearOpenAsUcp(self): self._is_ucp = None @@ -907,39 +910,23 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Defer machine activation to allow container signals to propagate Logger.debug("Workspace loading is notifying rest of the code of changes...") + # Store file name for deferred node loading after machine activation + self._deferred_node_file_name = file_name Application.getInstance().callLater(self._finalizeMachineActivation, global_stack, extruder_stack_dict) else: # For UCP files, just activate machine and apply user settings (no materials) Logger.debug("Workspace loading is notifying rest of the code of changes...") + # Store file name for deferred node loading after machine activation + self._deferred_node_file_name = file_name Application.getInstance().callLater(self._finalizeUcpActivation, global_stack, extruder_stack_dict) - # Load all the nodes / mesh data of the workspace - nodes = self._3mf_mesh_reader.read(file_name) - if nodes is None: - nodes = [] - - if self._is_ucp: - # We might be on a different printer than the one this project was made on. - # The offset to the printers' center isn't saved; instead, try to just fit everything on the buildplate. - full_extents = None - for node in nodes: - extents = node.getMeshData().getExtents() if node.getMeshData() else None - if extents is not None: - pos = node.getPosition() - node_box = AxisAlignedBox(extents.minimum + pos, extents.maximum + pos) - if full_extents is None: - full_extents = node_box - else: - full_extents = full_extents + node_box - if full_extents and full_extents.isValid(): - for node in nodes: - pos = node.getPosition() - node.setPosition(Vector(pos.x - full_extents.center.x, pos.y, pos.z - full_extents.center.z)) + # Defer node loading until after machine activation to ensure correct build volume + # Return empty list for now; nodes will be loaded and added to scene in finalize callbacks + nodes = [] base_file_name = os.path.basename(file_name) self.setWorkspaceName(base_file_name) - self._is_ucp = None return nodes, self._loadMetadata(file_name) @staticmethod @@ -1304,7 +1291,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader): def _finalizeMachineActivation(self, global_stack: GlobalStack, extruder_stack_dict: Dict[str, ExtruderStack]): """Complete machine activation by activating the machine and then applying materials.""" # First activate the machine - self._updateActiveMachine(global_stack, is_ucp = False) + self._updateActiveMachine(global_stack, is_ucp = self._is_ucp) # Then apply materials in another deferred call to ensure machine is fully activated # and ContainerTree has been accessed (which triggers lazy loading) @@ -1317,10 +1304,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Trigger a re-validation to pick up the newly applied changes global_stack.containersChanged.emit(global_stack.getTop()) + # Load and add nodes to scene after machine activation + if self._deferred_node_file_name: + self._loadAndAddNodesToScene(self._deferred_node_file_name, is_ucp = self._is_ucp) + self._deferred_node_file_name = None + self._is_ucp = None + def _finalizeUcpActivation(self, global_stack: GlobalStack, extruder_stack_dict: Dict[str, ExtruderStack]): """Complete UCP file activation by activating the machine and then applying user settings.""" # First activate the machine - self._updateActiveMachine(global_stack, is_ucp = True) + self._updateActiveMachine(global_stack, is_ucp = self._is_ucp) # Then apply UCP user settings in another deferred call Application.getInstance().callLater(self._applyUcpUserSettings, global_stack, extruder_stack_dict) @@ -1332,6 +1325,44 @@ class ThreeMFWorkspaceReader(WorkspaceReader): # Trigger a re-validation to pick up the newly applied settings global_stack.containersChanged.emit(global_stack.getTop()) + # Load and add nodes to scene after machine activation + if self._deferred_node_file_name: + self._loadAndAddNodesToScene(self._deferred_node_file_name, is_ucp = self._is_ucp) + self._deferred_node_file_name = None + self._is_ucp = None + + def _loadAndAddNodesToScene(self, file_name: str, is_ucp: bool): + """Load nodes from 3MF file and add them to the scene.""" + # Load all the nodes / mesh data of the workspace + nodes = self._3mf_mesh_reader.read(file_name) + if nodes is None: + nodes = [] + + if is_ucp: + # We might be on a different printer than the one this project was made on. + # The offset to the printers' center isn't saved; instead, try to just fit everything on the buildplate. + full_extents = None + for node in nodes: + extents = node.getMeshData().getExtents() if node.getMeshData() else None + if extents is not None: + pos = node.getPosition() + node_box = AxisAlignedBox(extents.minimum + pos, extents.maximum + pos) + if full_extents is None: + full_extents = node_box + else: + full_extents = full_extents + node_box + if full_extents and full_extents.isValid(): + for node in nodes: + pos = node.getPosition() + node.setPosition(Vector(pos.x - full_extents.center.x, pos.y, pos.z - full_extents.center.z)) + + # Add nodes to the scene + scene = Application.getInstance().getController().getScene() + for node in nodes: + op = AddSceneNodeOperation(node, scene.getRoot()) + op.push() + scene.sceneChanged.emit(node) + def _settingIsFromMissingPackage(self, key, value): # Check if the key and value pair is from the missing package for package in self._dialog.missingPackages: diff --git a/plugins/3MFReader/WorkspaceDialog.py b/plugins/3MFReader/WorkspaceDialog.py index a23e840201..01a741054c 100644 --- a/plugins/3MFReader/WorkspaceDialog.py +++ b/plugins/3MFReader/WorkspaceDialog.py @@ -406,7 +406,7 @@ class WorkspaceDialog(QObject): marketplace_plugin.showInstallMissingPackageDialog(self._missing_package_metadata, self.showMissingMaterialsWarning) # type: ignore def getResult(self) -> Dict[str, Optional[str]]: - if "machine" in self._result and self.updatableMachinesModel.count <= 1: + if "machine" in self._result and self.updatableMachinesModel.count <= 1 and not self._is_ucp: self._result["machine"] = None if "quality_changes" in self._result and not self._has_quality_changes_conflict: self._result["quality_changes"] = None diff --git a/plugins/3MFReader/WorkspaceDialog.qml b/plugins/3MFReader/WorkspaceDialog.qml index adb070e862..128b8c021b 100644 --- a/plugins/3MFReader/WorkspaceDialog.qml +++ b/plugins/3MFReader/WorkspaceDialog.qml @@ -120,7 +120,7 @@ UM.Dialog comboboxTitle: catalog.i18nc("@action:label", "Open With") comboboxTooltipText: catalog.i18nc("@info:tooltip", "Printer settings will be updated to match the settings saved with the project.") - comboboxVisible: workspaceDialog.visible && manager.updatableMachinesModel.count > 1 + comboboxVisible: workspaceDialog.visible && (manager.isUcp ? manager.updatableMachinesModel.count >= 1 : manager.updatableMachinesModel.count > 1) combobox: Cura.MachineSelector { id: machineSelector diff --git a/resources/conandata.yml b/resources/conandata.yml index 5155b57dfe..bd69a386a4 100644 --- a/resources/conandata.yml +++ b/resources/conandata.yml @@ -1 +1 @@ -version: "5.12.0-beta.1" +version: "5.12.0-beta.2"