mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
adding option of opening model as UCP or normal project file
CURA-11403
This commit is contained in:
parent
c817f11c02
commit
f3c49e494e
6 changed files with 187 additions and 35 deletions
|
@ -1,6 +1,6 @@
|
||||||
# Copyright (c) 2023 UltiMaker
|
# Copyright (c) 2023 UltiMaker
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
import zipfile
|
||||||
from typing import List, cast
|
from typing import List, cast
|
||||||
|
|
||||||
from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
from PyQt6.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
||||||
|
@ -33,6 +33,7 @@ from cura.Operations.SetBuildPlateNumberOperation import SetBuildPlateNumberOper
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
|
||||||
|
USER_SETTINGS_PATH = "Cura/user-settings.json"
|
||||||
|
|
||||||
class CuraActions(QObject):
|
class CuraActions(QObject):
|
||||||
def __init__(self, parent: QObject = None) -> None:
|
def __init__(self, parent: QObject = None) -> None:
|
||||||
|
@ -195,6 +196,13 @@ class CuraActions(QObject):
|
||||||
operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
|
operation.addOperation(SetObjectExtruderOperation(node, extruder_id))
|
||||||
operation.push()
|
operation.push()
|
||||||
|
|
||||||
|
@pyqtSlot(str, result = bool)
|
||||||
|
def isProjectUcp(self, file_url) -> bool:
|
||||||
|
file_name = QUrl(file_url).toLocalFile()
|
||||||
|
archive = zipfile.ZipFile(file_name, "r")
|
||||||
|
cura_file_names = [name for name in archive.namelist() if name.startswith("Cura/")]
|
||||||
|
return USER_SETTINGS_PATH in cura_file_names
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
|
def setBuildPlateForSelection(self, build_plate_nr: int) -> None:
|
||||||
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
|
Logger.log("d", "Setting build plate number... %d" % build_plate_nr)
|
||||||
|
|
|
@ -1979,6 +1979,18 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
openProjectFile = pyqtSignal(QUrl, bool, arguments = ["project_file", "add_to_recent_files"]) # Emitted when a project file is about to open.
|
openProjectFile = pyqtSignal(QUrl, bool, arguments = ["project_file", "add_to_recent_files"]) # Emitted when a project file is about to open.
|
||||||
|
|
||||||
|
@pyqtSlot(QUrl, bool)
|
||||||
|
def readLocalUcpFile(self, file: QUrl, add_to_recent_files: bool = True):
|
||||||
|
|
||||||
|
file_name = QUrl(file).toLocalFile()
|
||||||
|
workspace_reader = self.getWorkspaceFileHandler()
|
||||||
|
if workspace_reader is None:
|
||||||
|
Logger.log("w", "Workspace reader not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
workspace_reader.getReaderForFile(file_name).setOpenAsUcp(True)
|
||||||
|
workspace_reader.readLocalFile(file, add_to_recent_files)
|
||||||
|
|
||||||
@pyqtSlot(QUrl, str, bool)
|
@pyqtSlot(QUrl, str, bool)
|
||||||
@pyqtSlot(QUrl, str)
|
@pyqtSlot(QUrl, str)
|
||||||
@pyqtSlot(QUrl)
|
@pyqtSlot(QUrl)
|
||||||
|
|
|
@ -117,6 +117,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
self._supported_extensions = [".3mf"]
|
self._supported_extensions = [".3mf"]
|
||||||
self._dialog = WorkspaceDialog()
|
self._dialog = WorkspaceDialog()
|
||||||
self._3mf_mesh_reader = None
|
self._3mf_mesh_reader = None
|
||||||
|
self._is_ucp = False
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
# suffixes registered with the MimeTypes don't start with a dot '.'
|
# suffixes registered with the MimeTypes don't start with a dot '.'
|
||||||
|
@ -153,6 +154,9 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
self._load_profile = False
|
self._load_profile = False
|
||||||
self._user_settings = {}
|
self._user_settings = {}
|
||||||
|
|
||||||
|
def setOpenAsUcp(self, openAsUcp: bool):
|
||||||
|
self._is_ucp = openAsUcp
|
||||||
|
|
||||||
def getNewId(self, old_id: str):
|
def getNewId(self, old_id: str):
|
||||||
"""Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
"""Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
||||||
|
|
||||||
|
@ -242,7 +246,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
# Read definition containers
|
# Read definition containers
|
||||||
#
|
#
|
||||||
machine_definition_id = None
|
machine_definition_id = None
|
||||||
updatable_machines = None if is_ucp else []
|
updatable_machines = None if self._is_ucp else []
|
||||||
machine_definition_container_count = 0
|
machine_definition_container_count = 0
|
||||||
extruder_definition_container_count = 0
|
extruder_definition_container_count = 0
|
||||||
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
|
||||||
|
@ -609,7 +613,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
|
|
||||||
# Load the user specifically exported settings
|
# Load the user specifically exported settings
|
||||||
self._dialog.exportedSettingModel.clear()
|
self._dialog.exportedSettingModel.clear()
|
||||||
if is_ucp:
|
if self._is_ucp:
|
||||||
try:
|
try:
|
||||||
self._user_settings = json.loads(archive.open("Cura/user-settings.json").read().decode("utf-8"))
|
self._user_settings = json.loads(archive.open("Cura/user-settings.json").read().decode("utf-8"))
|
||||||
any_extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
any_extruder_stack = ExtruderManager.getInstance().getExtruderStack(0)
|
||||||
|
@ -658,8 +662,8 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
self._dialog.setVariantType(variant_type_name)
|
self._dialog.setVariantType(variant_type_name)
|
||||||
self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
|
self._dialog.setHasObjectsOnPlate(Application.getInstance().platformActivity)
|
||||||
self._dialog.setMissingPackagesMetadata(missing_package_metadata)
|
self._dialog.setMissingPackagesMetadata(missing_package_metadata)
|
||||||
self._dialog.setHasVisibleSelectSameProfileChanged(is_ucp)
|
self._dialog.setHasVisibleSelectSameProfileChanged(self._is_ucp)
|
||||||
self._dialog.setAllowCreatemachine(not is_ucp)
|
self._dialog.setAllowCreatemachine(not self._is_ucp)
|
||||||
self._dialog.show()
|
self._dialog.show()
|
||||||
|
|
||||||
|
|
||||||
|
@ -701,7 +705,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
if self._dialog.getResult() == {}:
|
if self._dialog.getResult() == {}:
|
||||||
return WorkspaceReader.PreReadResult.cancelled
|
return WorkspaceReader.PreReadResult.cancelled
|
||||||
|
|
||||||
self._load_profile = not is_ucp or (self._dialog.selectSameProfileChecked and self._dialog.isCompatibleMachine)
|
self._load_profile = not self._is_ucp
|
||||||
|
|
||||||
self._resolve_strategies = self._dialog.getResult()
|
self._resolve_strategies = self._dialog.getResult()
|
||||||
#
|
#
|
||||||
|
@ -717,7 +721,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
if key not in containers_found_dict or strategy is not None:
|
if key not in containers_found_dict or strategy is not None:
|
||||||
continue
|
continue
|
||||||
self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new"
|
self._resolve_strategies[key] = "override" if containers_found_dict[key] else "new"
|
||||||
|
self._is_ucp = False
|
||||||
return WorkspaceReader.PreReadResult.accepted
|
return WorkspaceReader.PreReadResult.accepted
|
||||||
|
|
||||||
@call_on_qt_thread
|
@call_on_qt_thread
|
||||||
|
|
|
@ -135,16 +135,13 @@ class ThreeMFWriter(MeshWriter):
|
||||||
stack = um_node.callDecoration("getStack")
|
stack = um_node.callDecoration("getStack")
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
changed_setting_keys = stack.getTop().getAllKeys()
|
changed_setting_keys = stack.getTop().getAllKeys()
|
||||||
|
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
||||||
if exported_settings is None:
|
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
changed_setting_keys.add("extruder_nr")
|
||||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
# Get values for all changed settings & save them.
|
||||||
changed_setting_keys.add("extruder_nr")
|
for key in changed_setting_keys:
|
||||||
|
savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
|
||||||
# Get values for all changed settings & save them.
|
if exported_settings is not None:
|
||||||
for key in changed_setting_keys:
|
|
||||||
savitar_node.setSetting("cura:" + key, str(stack.getProperty(key, "value")))
|
|
||||||
else:
|
|
||||||
# We want to export only the specified settings
|
# We want to export only the specified settings
|
||||||
if um_node.getName() in exported_settings:
|
if um_node.getName() in exported_settings:
|
||||||
model_exported_settings = exported_settings[um_node.getName()]
|
model_exported_settings = exported_settings[um_node.getName()]
|
||||||
|
|
|
@ -701,24 +701,34 @@ UM.MainWindow
|
||||||
|
|
||||||
if (hasProjectFile)
|
if (hasProjectFile)
|
||||||
{
|
{
|
||||||
var projectFile = projectFileUrlList[0];
|
var projectFile = projectFileUrlList[0]
|
||||||
|
var is_ucp = CuraActions.isProjectUcp(projectFile);
|
||||||
// check preference
|
print("this file is ucp", is_ucp);
|
||||||
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
|
if (is_ucp)
|
||||||
if (choice == "open_as_project")
|
|
||||||
{
|
{
|
||||||
openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
|
askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = projectFile;
|
||||||
|
askOpenAsProjectOrUcpOrImportModelsDialog.addToRecent = true;
|
||||||
|
askOpenAsProjectOrUcpOrImportModelsDialog.show();
|
||||||
}
|
}
|
||||||
else if (choice == "open_as_model")
|
else
|
||||||
{
|
{
|
||||||
openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
|
// check preference
|
||||||
}
|
var choice = UM.Preferences.getValue("cura/choice_on_open_project");
|
||||||
else // always ask
|
if (choice == "open_as_project")
|
||||||
{
|
{
|
||||||
// ask whether to open as project or as models
|
openFilesIncludingProjectsDialog.loadProjectFile(projectFile);
|
||||||
askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
|
}
|
||||||
askOpenAsProjectOrModelsDialog.addToRecent = true;
|
else if (choice == "open_as_model")
|
||||||
askOpenAsProjectOrModelsDialog.show();
|
{
|
||||||
|
openFilesIncludingProjectsDialog.loadModelFiles([projectFile].slice());
|
||||||
|
}
|
||||||
|
else // always ask
|
||||||
|
{
|
||||||
|
// ask whether to open as project or as models
|
||||||
|
askOpenAsProjectOrModelsDialog.fileUrl = projectFile;
|
||||||
|
askOpenAsProjectOrModelsDialog.addToRecent = true;
|
||||||
|
askOpenAsProjectOrModelsDialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -769,14 +779,31 @@ UM.MainWindow
|
||||||
id: askOpenAsProjectOrModelsDialog
|
id: askOpenAsProjectOrModelsDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AskOpenAsProjectOrUcpOrImportModel
|
||||||
|
{
|
||||||
|
id: askOpenAsProjectOrUcpOrImportModelsDialog
|
||||||
|
}
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
target: CuraApplication
|
target: CuraApplication
|
||||||
function onOpenProjectFile(project_file, add_to_recent_files)
|
function onOpenProjectFile(project_file, add_to_recent_files)
|
||||||
{
|
{
|
||||||
askOpenAsProjectOrModelsDialog.fileUrl = project_file;
|
var is_ucp = CuraActions.isProjectUcp(project_file);
|
||||||
askOpenAsProjectOrModelsDialog.addToRecent = add_to_recent_files;
|
print("this file is ucp", is_ucp);
|
||||||
askOpenAsProjectOrModelsDialog.show();
|
if (is_ucp)
|
||||||
|
{
|
||||||
|
|
||||||
|
askOpenAsProjectOrUcpOrImportModelsDialog.fileUrl = project_file;
|
||||||
|
askOpenAsProjectOrUcpOrImportModelsDialog.addToRecent = add_to_recent_files;
|
||||||
|
askOpenAsProjectOrUcpOrImportModelsDialog.show();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
askOpenAsProjectOrModelsDialog.fileUrl = project_file;
|
||||||
|
askOpenAsProjectOrModelsDialog.addToRecent = add_to_recent_files;
|
||||||
|
askOpenAsProjectOrModelsDialog.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
104
resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml
Normal file
104
resources/qml/Dialogs/AskOpenAsProjectOrUcpOrImportModel.qml
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright (c) 2022 Ultimaker B.V.
|
||||||
|
// Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
import QtQuick 2.2
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import QtQuick.Layouts 1.1
|
||||||
|
|
||||||
|
import UM 1.5 as UM
|
||||||
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
|
|
||||||
|
UM.Dialog
|
||||||
|
{
|
||||||
|
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
|
||||||
|
id: base
|
||||||
|
|
||||||
|
title: catalog.i18nc("@title:window", "Open Universal Cura Project (UCP) file")
|
||||||
|
width: UM.Theme.getSize("small_popup_dialog").width
|
||||||
|
height: UM.Theme.getSize("small_popup_dialog").height
|
||||||
|
backgroundColor: UM.Theme.getColor("main_background")
|
||||||
|
|
||||||
|
maximumHeight: height
|
||||||
|
maximumWidth: width
|
||||||
|
minimumHeight: maximumHeight
|
||||||
|
minimumWidth: maximumWidth
|
||||||
|
|
||||||
|
modality: Qt.WindowModal
|
||||||
|
|
||||||
|
property var fileUrl
|
||||||
|
property var addToRecent: true //Whether to add this file to the recent files list after reading it.
|
||||||
|
|
||||||
|
// load the entire project
|
||||||
|
function loadProjectFile() {
|
||||||
|
|
||||||
|
UM.WorkspaceFileHandler.readLocalFile(base.fileUrl, base.addToRecent);
|
||||||
|
|
||||||
|
base.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the project file as separated models
|
||||||
|
function loadModelFiles() {
|
||||||
|
CuraApplication.readLocalFile(base.fileUrl, "open_as_model", base.addToRecent)
|
||||||
|
|
||||||
|
base.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
// load the project file as Universal cura project
|
||||||
|
function loadUcpFiles() {
|
||||||
|
CuraApplication.readLocalUcpFile(base.fileUrl, base.addToRecent)
|
||||||
|
|
||||||
|
base.hide()
|
||||||
|
}
|
||||||
|
|
||||||
|
// override UM.Dialog accept
|
||||||
|
function accept () {
|
||||||
|
|
||||||
|
// when hitting 'enter', we always open as project unless open_as_model was explicitly stored as preference
|
||||||
|
if (openAsPreference == "open_as_model") {
|
||||||
|
loadModelFiles()
|
||||||
|
} else if (openAsPreference == "open_as_ucp"){
|
||||||
|
loadUcpFiles()
|
||||||
|
}else {
|
||||||
|
loadProjectFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Column
|
||||||
|
{
|
||||||
|
anchors.fill: parent
|
||||||
|
spacing: UM.Theme.getSize("default_margin").height
|
||||||
|
|
||||||
|
UM.Label
|
||||||
|
{
|
||||||
|
id: questionText
|
||||||
|
width: parent.width
|
||||||
|
text: catalog.i18nc("@text:window", "This is a Cura Universal project file. Would you like to open it as a Cura project or Cura Universal Project or import the models from it?")
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: loadProjectFile()
|
||||||
|
onRejected: loadModelFiles()
|
||||||
|
|
||||||
|
buttonSpacing: UM.Theme.getSize("thin_margin").width
|
||||||
|
|
||||||
|
rightButtons:
|
||||||
|
[
|
||||||
|
Cura.SecondaryButton
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:button", "Open as project")
|
||||||
|
onClicked: loadProjectFile()
|
||||||
|
},
|
||||||
|
Cura.PrimaryButton
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:button", "Open as UCP")
|
||||||
|
onClicked: loadUcpFiles()
|
||||||
|
},
|
||||||
|
Cura.SecondaryButton
|
||||||
|
{
|
||||||
|
text: catalog.i18nc("@action:button", "Import models")
|
||||||
|
onClicked: loadModelFiles()
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue