mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-07 06:57:28 -06:00
Merge branch 'master' into feature_sync_button
This commit is contained in:
commit
13e18c9d54
568 changed files with 6406 additions and 6604 deletions
11
.github/ISSUE_TEMPLATE.md
vendored
11
.github/ISSUE_TEMPLATE.md
vendored
|
@ -1,9 +1,12 @@
|
||||||
<!--
|
<!--
|
||||||
The following template is useful for filing new issues. Processing an issue will go much faster when this is filled out.
|
The following template is useful for filing new issues. Processing an issue will go much faster when this is filled out, and issues which do not use this template will be removed.
|
||||||
Before filing, please check if the issue already exists (either open or closed).
|
|
||||||
|
|
||||||
It is also helpful to attach a project (.3MF) file and Cura log file so we can debug issues quicker.
|
Before filing, PLEASE check if the issue already exists (either open or closed) by using the search bar on the issues page. If it does, comment there. Even if it's closed, we can reopen it based on your comment.
|
||||||
Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations.
|
|
||||||
|
Also, please note the application version in the title of the issue. For example: "[3.2.1] Cannot connect to 3rd-party printer". Please do not write thigns like "Request:" or "[BUG]" in the title; this is what labels are for.
|
||||||
|
|
||||||
|
It is also helpful to attach a project (.3mf or .curaproject) file and Cura log file so we can debug issues quicker.
|
||||||
|
Information about how to find the log file can be found at https://github.com/Ultimaker/Cura/wiki/Cura-Preferences-and-Settings-Locations. To upload a project, we recommend http://wetransfer.com, but other file hosts like Google Drive or Dropbox work well too.
|
||||||
|
|
||||||
Thank you for using Cura!
|
Thank you for using Cura!
|
||||||
-->
|
-->
|
||||||
|
|
|
@ -826,6 +826,7 @@ class BuildVolume(SceneNode):
|
||||||
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
|
offset_y = extruder.getProperty("machine_nozzle_offset_y", "value")
|
||||||
if offset_y is None:
|
if offset_y is None:
|
||||||
offset_y = 0
|
offset_y = 0
|
||||||
|
offset_y = -offset_y #Y direction of g-code is the inverse of Y direction of Cura's scene space.
|
||||||
result[extruder_id] = []
|
result[extruder_id] = []
|
||||||
|
|
||||||
for polygon in machine_disallowed_polygons:
|
for polygon in machine_disallowed_polygons:
|
||||||
|
@ -936,8 +937,8 @@ class BuildVolume(SceneNode):
|
||||||
# stack.
|
# stack.
|
||||||
#
|
#
|
||||||
# \return A sequence of setting values, one for each extruder.
|
# \return A sequence of setting values, one for each extruder.
|
||||||
def _getSettingFromAllExtruders(self, setting_key, property = "value"):
|
def _getSettingFromAllExtruders(self, setting_key):
|
||||||
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, property)
|
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
|
||||||
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
|
||||||
for i in range(len(all_values)):
|
for i in range(len(all_values)):
|
||||||
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
|
if not all_values[i] and (all_types[i] == "int" or all_types[i] == "float"):
|
||||||
|
@ -950,7 +951,7 @@ class BuildVolume(SceneNode):
|
||||||
# not part of the collision radius, such as bed adhesion (skirt/brim/raft)
|
# not part of the collision radius, such as bed adhesion (skirt/brim/raft)
|
||||||
# and travel avoid distance.
|
# and travel avoid distance.
|
||||||
def _getEdgeDisallowedSize(self):
|
def _getEdgeDisallowedSize(self):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack or not self._global_container_stack.extruders:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
container_stack = self._global_container_stack
|
container_stack = self._global_container_stack
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
#Type hinting.
|
#Type hinting.
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject
|
||||||
from PyQt5.QtNetwork import QLocalServer
|
from PyQt5.QtNetwork import QLocalServer
|
||||||
from PyQt5.QtNetwork import QLocalSocket
|
from PyQt5.QtNetwork import QLocalSocket
|
||||||
|
|
||||||
|
@ -52,13 +54,23 @@ from UM.Settings.SettingDefinition import SettingDefinition, DefinitionPropertyT
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
from cura.Settings.MachineNameValidator import MachineNameValidator
|
from cura.Settings.MachineNameValidator import MachineNameValidator
|
||||||
from cura.Settings.ProfilesModel import ProfilesModel
|
|
||||||
from cura.Settings.MaterialsModel import MaterialsModel
|
from cura.Machines.Models.BuildPlateModel import BuildPlateModel
|
||||||
from cura.Settings.QualityAndUserProfilesModel import QualityAndUserProfilesModel
|
from cura.Machines.Models.NozzleModel import NozzleModel
|
||||||
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||||
|
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
||||||
|
|
||||||
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||||
|
|
||||||
|
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
||||||
|
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
||||||
|
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
|
||||||
|
|
||||||
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
from cura.Settings.SettingInheritanceManager import SettingInheritanceManager
|
||||||
from cura.Settings.UserProfilesModel import UserProfilesModel
|
|
||||||
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
from cura.Settings.SimpleModeSettingsManager import SimpleModeSettingsManager
|
||||||
|
|
||||||
|
from cura.Machines.VariantManager import VariantManager
|
||||||
|
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||||
|
|
||||||
from . import PlatformPhysics
|
from . import PlatformPhysics
|
||||||
from . import BuildVolume
|
from . import BuildVolume
|
||||||
|
@ -71,17 +83,14 @@ from . import CameraImageProvider
|
||||||
from . import MachineActionManager
|
from . import MachineActionManager
|
||||||
|
|
||||||
from cura.Settings.MachineManager import MachineManager
|
from cura.Settings.MachineManager import MachineManager
|
||||||
from cura.Settings.MaterialManager import MaterialManager
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Settings.UserChangesModel import UserChangesModel
|
from cura.Settings.UserChangesModel import UserChangesModel
|
||||||
from cura.Settings.ExtrudersModel import ExtrudersModel
|
from cura.Settings.ExtrudersModel import ExtrudersModel
|
||||||
from cura.Settings.ContainerSettingsModel import ContainerSettingsModel
|
|
||||||
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisibilityHandler
|
||||||
from cura.Settings.QualitySettingsModel import QualitySettingsModel
|
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||||
from cura.Settings.ContainerManager import ContainerManager
|
from cura.Settings.ContainerManager import ContainerManager
|
||||||
|
|
||||||
from cura.ObjectsModel import ObjectsModel
|
from cura.ObjectsModel import ObjectsModel
|
||||||
from cura.BuildPlateModel import BuildPlateModel
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtProperty, QEvent, Q_ENUMS
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
@ -216,6 +225,7 @@ class CuraApplication(QtApplication):
|
||||||
self._material_manager = None
|
self._material_manager = None
|
||||||
self._object_manager = None
|
self._object_manager = None
|
||||||
self._build_plate_model = None
|
self._build_plate_model = None
|
||||||
|
self._multi_build_plate_model = None
|
||||||
self._setting_inheritance_manager = None
|
self._setting_inheritance_manager = None
|
||||||
self._simple_mode_settings_manager = None
|
self._simple_mode_settings_manager = None
|
||||||
self._cura_scene_controller = None
|
self._cura_scene_controller = None
|
||||||
|
@ -233,6 +243,8 @@ class CuraApplication(QtApplication):
|
||||||
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
|
if kwargs["parsed_command_line"].get("trigger_early_crash", False):
|
||||||
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
assert not "This crash is triggered by the trigger_early_crash command line argument."
|
||||||
|
|
||||||
|
self._variant_manager = None
|
||||||
|
|
||||||
self.default_theme = "cura-light"
|
self.default_theme = "cura-light"
|
||||||
|
|
||||||
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
|
||||||
|
@ -287,21 +299,25 @@ class CuraApplication(QtApplication):
|
||||||
# Since they are empty, they should never be serialized and instead just programmatically created.
|
# Since they are empty, they should never be serialized and instead just programmatically created.
|
||||||
# We need them to simplify the switching between materials.
|
# We need them to simplify the switching between materials.
|
||||||
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
empty_container = ContainerRegistry.getInstance().getEmptyInstanceContainer()
|
||||||
|
self.empty_container = empty_container
|
||||||
|
|
||||||
empty_definition_changes_container = copy.deepcopy(empty_container)
|
empty_definition_changes_container = copy.deepcopy(empty_container)
|
||||||
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
|
empty_definition_changes_container.setMetaDataEntry("id", "empty_definition_changes")
|
||||||
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
empty_definition_changes_container.addMetaDataEntry("type", "definition_changes")
|
||||||
ContainerRegistry.getInstance().addContainer(empty_definition_changes_container)
|
ContainerRegistry.getInstance().addContainer(empty_definition_changes_container)
|
||||||
|
self.empty_definition_changes_container = empty_definition_changes_container
|
||||||
|
|
||||||
empty_variant_container = copy.deepcopy(empty_container)
|
empty_variant_container = copy.deepcopy(empty_container)
|
||||||
empty_variant_container.setMetaDataEntry("id", "empty_variant")
|
empty_variant_container.setMetaDataEntry("id", "empty_variant")
|
||||||
empty_variant_container.addMetaDataEntry("type", "variant")
|
empty_variant_container.addMetaDataEntry("type", "variant")
|
||||||
ContainerRegistry.getInstance().addContainer(empty_variant_container)
|
ContainerRegistry.getInstance().addContainer(empty_variant_container)
|
||||||
|
self.empty_variant_container = empty_variant_container
|
||||||
|
|
||||||
empty_material_container = copy.deepcopy(empty_container)
|
empty_material_container = copy.deepcopy(empty_container)
|
||||||
empty_material_container.setMetaDataEntry("id", "empty_material")
|
empty_material_container.setMetaDataEntry("id", "empty_material")
|
||||||
empty_material_container.addMetaDataEntry("type", "material")
|
empty_material_container.addMetaDataEntry("type", "material")
|
||||||
ContainerRegistry.getInstance().addContainer(empty_material_container)
|
ContainerRegistry.getInstance().addContainer(empty_material_container)
|
||||||
|
self.empty_material_container = empty_material_container
|
||||||
|
|
||||||
empty_quality_container = copy.deepcopy(empty_container)
|
empty_quality_container = copy.deepcopy(empty_container)
|
||||||
empty_quality_container.setMetaDataEntry("id", "empty_quality")
|
empty_quality_container.setMetaDataEntry("id", "empty_quality")
|
||||||
|
@ -310,12 +326,14 @@ class CuraApplication(QtApplication):
|
||||||
empty_quality_container.addMetaDataEntry("type", "quality")
|
empty_quality_container.addMetaDataEntry("type", "quality")
|
||||||
empty_quality_container.addMetaDataEntry("supported", False)
|
empty_quality_container.addMetaDataEntry("supported", False)
|
||||||
ContainerRegistry.getInstance().addContainer(empty_quality_container)
|
ContainerRegistry.getInstance().addContainer(empty_quality_container)
|
||||||
|
self.empty_quality_container = empty_quality_container
|
||||||
|
|
||||||
empty_quality_changes_container = copy.deepcopy(empty_container)
|
empty_quality_changes_container = copy.deepcopy(empty_container)
|
||||||
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
|
empty_quality_changes_container.setMetaDataEntry("id", "empty_quality_changes")
|
||||||
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
empty_quality_changes_container.addMetaDataEntry("type", "quality_changes")
|
||||||
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
|
empty_quality_changes_container.addMetaDataEntry("quality_type", "not_supported")
|
||||||
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
ContainerRegistry.getInstance().addContainer(empty_quality_changes_container)
|
||||||
|
self.empty_quality_changes_container = empty_quality_changes_container
|
||||||
|
|
||||||
with ContainerRegistry.getInstance().lockFile():
|
with ContainerRegistry.getInstance().lockFile():
|
||||||
ContainerRegistry.getInstance().loadAllMetadata()
|
ContainerRegistry.getInstance().loadAllMetadata()
|
||||||
|
@ -381,6 +399,9 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
||||||
|
|
||||||
|
self._quality_profile_drop_down_menu_model = None
|
||||||
|
self._custom_quality_profile_drop_down_menu_model = None
|
||||||
|
|
||||||
CuraApplication.Created = True
|
CuraApplication.Created = True
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
|
@ -523,8 +544,6 @@ class CuraApplication(QtApplication):
|
||||||
has_user_interaction = True
|
has_user_interaction = True
|
||||||
return has_user_interaction
|
return has_user_interaction
|
||||||
|
|
||||||
onDiscardOrKeepProfileChangesClosed = pyqtSignal() # Used to notify other managers that the dialog was closed
|
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def discardOrKeepProfileChangesClosed(self, option):
|
def discardOrKeepProfileChangesClosed(self, option):
|
||||||
if option == "discard":
|
if option == "discard":
|
||||||
|
@ -547,7 +566,6 @@ class CuraApplication(QtApplication):
|
||||||
user_global_container.update()
|
user_global_container.update()
|
||||||
|
|
||||||
# notify listeners that quality has changed (after user selected discard or keep)
|
# notify listeners that quality has changed (after user selected discard or keep)
|
||||||
self.onDiscardOrKeepProfileChangesClosed.emit()
|
|
||||||
self.getMachineManager().activeQualityChanged.emit()
|
self.getMachineManager().activeQualityChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
|
@ -723,6 +741,20 @@ class CuraApplication(QtApplication):
|
||||||
def run(self):
|
def run(self):
|
||||||
self.preRun()
|
self.preRun()
|
||||||
|
|
||||||
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
self._variant_manager = VariantManager(container_registry)
|
||||||
|
self._variant_manager.initialize()
|
||||||
|
|
||||||
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
|
self._material_manager = MaterialManager(container_registry, parent = self)
|
||||||
|
self._material_manager.initialize()
|
||||||
|
|
||||||
|
from cura.Machines.QualityManager import QualityManager
|
||||||
|
self._quality_manager = QualityManager(container_registry, parent = self)
|
||||||
|
self._quality_manager.initialize()
|
||||||
|
|
||||||
|
self._machine_manager = MachineManager(self)
|
||||||
|
|
||||||
# Check if we should run as single instance or not
|
# Check if we should run as single instance or not
|
||||||
self._setUpSingleInstanceServer()
|
self._setUpSingleInstanceServer()
|
||||||
|
|
||||||
|
@ -816,7 +848,7 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
def getMachineManager(self, *args) -> MachineManager:
|
def getMachineManager(self, *args) -> MachineManager:
|
||||||
if self._machine_manager is None:
|
if self._machine_manager is None:
|
||||||
self._machine_manager = MachineManager.createMachineManager()
|
self._machine_manager = MachineManager(self)
|
||||||
return self._machine_manager
|
return self._machine_manager
|
||||||
|
|
||||||
def getExtruderManager(self, *args):
|
def getExtruderManager(self, *args):
|
||||||
|
@ -824,20 +856,32 @@ class CuraApplication(QtApplication):
|
||||||
self._extruder_manager = ExtruderManager.createExtruderManager()
|
self._extruder_manager = ExtruderManager.createExtruderManager()
|
||||||
return self._extruder_manager
|
return self._extruder_manager
|
||||||
|
|
||||||
|
def getVariantManager(self, *args):
|
||||||
|
return self._variant_manager
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
def getMaterialManager(self, *args):
|
def getMaterialManager(self, *args):
|
||||||
if self._material_manager is None:
|
|
||||||
self._material_manager = MaterialManager.createMaterialManager()
|
|
||||||
return self._material_manager
|
return self._material_manager
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
|
def getQualityManager(self, *args):
|
||||||
|
return self._quality_manager
|
||||||
|
|
||||||
def getObjectsModel(self, *args):
|
def getObjectsModel(self, *args):
|
||||||
if self._object_manager is None:
|
if self._object_manager is None:
|
||||||
self._object_manager = ObjectsModel.createObjectsModel()
|
self._object_manager = ObjectsModel.createObjectsModel()
|
||||||
return self._object_manager
|
return self._object_manager
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
|
def getMultiBuildPlateModel(self, *args):
|
||||||
|
if self._multi_build_plate_model is None:
|
||||||
|
self._multi_build_plate_model = MultiBuildPlateModel(self)
|
||||||
|
return self._multi_build_plate_model
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
def getBuildPlateModel(self, *args):
|
def getBuildPlateModel(self, *args):
|
||||||
if self._build_plate_model is None:
|
if self._build_plate_model is None:
|
||||||
self._build_plate_model = BuildPlateModel.createBuildPlateModel()
|
self._build_plate_model = BuildPlateModel(self)
|
||||||
|
|
||||||
return self._build_plate_model
|
return self._build_plate_model
|
||||||
|
|
||||||
def getCuraSceneController(self, *args):
|
def getCuraSceneController(self, *args):
|
||||||
|
@ -875,6 +919,16 @@ class CuraApplication(QtApplication):
|
||||||
def getPrintInformation(self):
|
def getPrintInformation(self):
|
||||||
return self._print_information
|
return self._print_information
|
||||||
|
|
||||||
|
def getQualityProfilesDropDownMenuModel(self, *args, **kwargs):
|
||||||
|
if self._quality_profile_drop_down_menu_model is None:
|
||||||
|
self._quality_profile_drop_down_menu_model = QualityProfilesDropDownMenuModel(self)
|
||||||
|
return self._quality_profile_drop_down_menu_model
|
||||||
|
|
||||||
|
def getCustomQualityProfilesDropDownMenuModel(self, *args, **kwargs):
|
||||||
|
if self._custom_quality_profile_drop_down_menu_model is None:
|
||||||
|
self._custom_quality_profile_drop_down_menu_model = CustomQualityProfilesDropDownMenuModel(self)
|
||||||
|
return self._custom_quality_profile_drop_down_menu_model
|
||||||
|
|
||||||
## Registers objects for the QML engine to use.
|
## Registers objects for the QML engine to use.
|
||||||
#
|
#
|
||||||
# \param engine The QML engine.
|
# \param engine The QML engine.
|
||||||
|
@ -889,27 +943,34 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
|
||||||
|
|
||||||
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 2, "SceneController", self.getCuraSceneController)
|
qmlRegisterSingletonType(CuraSceneController, "Cura", 1, 0, "SceneController", self.getCuraSceneController)
|
||||||
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
qmlRegisterSingletonType(ExtruderManager, "Cura", 1, 0, "ExtruderManager", self.getExtruderManager)
|
||||||
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
qmlRegisterSingletonType(MachineManager, "Cura", 1, 0, "MachineManager", self.getMachineManager)
|
||||||
qmlRegisterSingletonType(MaterialManager, "Cura", 1, 0, "MaterialManager", self.getMaterialManager)
|
|
||||||
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
qmlRegisterSingletonType(SettingInheritanceManager, "Cura", 1, 0, "SettingInheritanceManager", self.getSettingInheritanceManager)
|
||||||
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 2, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
|
||||||
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
|
||||||
|
|
||||||
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 2, "ObjectsModel", self.getObjectsModel)
|
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
|
||||||
qmlRegisterSingletonType(BuildPlateModel, "Cura", 1, 2, "BuildPlateModel", self.getBuildPlateModel)
|
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
|
||||||
|
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
|
||||||
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
|
||||||
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
|
||||||
qmlRegisterType(ContainerSettingsModel, "Cura", 1, 0, "ContainerSettingsModel")
|
|
||||||
qmlRegisterSingletonType(ProfilesModel, "Cura", 1, 0, "ProfilesModel", ProfilesModel.createProfilesModel)
|
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||||
qmlRegisterType(MaterialsModel, "Cura", 1, 0, "MaterialsModel")
|
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
||||||
qmlRegisterType(QualityAndUserProfilesModel, "Cura", 1, 0, "QualityAndUserProfilesModel")
|
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
||||||
qmlRegisterType(UserProfilesModel, "Cura", 1, 0, "UserProfilesModel")
|
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||||
|
|
||||||
|
qmlRegisterSingletonType(QualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
|
"QualityProfilesDropDownMenuModel", self.getQualityProfilesDropDownMenuModel)
|
||||||
|
qmlRegisterSingletonType(CustomQualityProfilesDropDownMenuModel, "Cura", 1, 0,
|
||||||
|
"CustomQualityProfilesDropDownMenuModel", self.getCustomQualityProfilesDropDownMenuModel)
|
||||||
|
qmlRegisterType(NozzleModel, "Cura", 1, 0, "NozzleModel")
|
||||||
|
|
||||||
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
qmlRegisterType(MaterialSettingsVisibilityHandler, "Cura", 1, 0, "MaterialSettingsVisibilityHandler")
|
||||||
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
qmlRegisterType(QualitySettingsModel, "Cura", 1, 0, "QualitySettingsModel")
|
||||||
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
qmlRegisterType(MachineNameValidator, "Cura", 1, 0, "MachineNameValidator")
|
||||||
qmlRegisterType(UserChangesModel, "Cura", 1, 1, "UserChangesModel")
|
qmlRegisterType(UserChangesModel, "Cura", 1, 0, "UserChangesModel")
|
||||||
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
|
qmlRegisterSingletonType(ContainerManager, "Cura", 1, 0, "ContainerManager", ContainerManager.createContainerManager)
|
||||||
|
|
||||||
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
# As of Qt5.7, it is necessary to get rid of any ".." in the path for the singleton to work.
|
||||||
|
@ -991,7 +1052,7 @@ class CuraApplication(QtApplication):
|
||||||
count = 0
|
count = 0
|
||||||
scene_bounding_box = None
|
scene_bounding_box = None
|
||||||
is_block_slicing_node = False
|
is_block_slicing_node = False
|
||||||
active_build_plate = self.getBuildPlateModel().activeBuildPlate
|
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if (
|
if (
|
||||||
not issubclass(type(node), CuraSceneNode) or
|
not issubclass(type(node), CuraSceneNode) or
|
||||||
|
@ -1240,7 +1301,7 @@ class CuraApplication(QtApplication):
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def arrangeAll(self):
|
def arrangeAll(self):
|
||||||
nodes = []
|
nodes = []
|
||||||
active_build_plate = self.getBuildPlateModel().activeBuildPlate
|
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
|
||||||
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
|
||||||
if not isinstance(node, SceneNode):
|
if not isinstance(node, SceneNode):
|
||||||
continue
|
continue
|
||||||
|
@ -1389,7 +1450,7 @@ class CuraApplication(QtApplication):
|
||||||
group_decorator = GroupDecorator()
|
group_decorator = GroupDecorator()
|
||||||
group_node.addDecorator(group_decorator)
|
group_node.addDecorator(group_decorator)
|
||||||
group_node.addDecorator(ConvexHullDecorator())
|
group_node.addDecorator(ConvexHullDecorator())
|
||||||
group_node.addDecorator(BuildPlateDecorator(self.getBuildPlateModel().activeBuildPlate))
|
group_node.addDecorator(BuildPlateDecorator(self.getMultiBuildPlateModel().activeBuildPlate))
|
||||||
group_node.setParent(self.getController().getScene().getRoot())
|
group_node.setParent(self.getController().getScene().getRoot())
|
||||||
group_node.setSelectable(True)
|
group_node.setSelectable(True)
|
||||||
center = Selection.getSelectionCenter()
|
center = Selection.getSelectionCenter()
|
||||||
|
@ -1534,7 +1595,7 @@ class CuraApplication(QtApplication):
|
||||||
arrange_objects_on_load = (
|
arrange_objects_on_load = (
|
||||||
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
|
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
|
||||||
not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
|
not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
|
||||||
target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
target_build_plate = self.getMultiBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
|
||||||
|
|
||||||
root = self.getController().getScene().getRoot()
|
root = self.getController().getScene().getRoot()
|
||||||
fixed_nodes = []
|
fixed_nodes = []
|
||||||
|
|
49
cura/Machines/ContainerNode.py
Normal file
49
cura/Machines/ContainerNode.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
|
|
||||||
|
##
|
||||||
|
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
||||||
|
#
|
||||||
|
# ContainerNode is a multi-purpose class. It has two main purposes:
|
||||||
|
# 1. It encapsulates an InstanceContainer. It contains that InstanceContainer's
|
||||||
|
# - metadata (Always)
|
||||||
|
# - container (lazy-loaded when needed)
|
||||||
|
# 2. It also serves as a node in a hierarchical InstanceContainer lookup table/tree.
|
||||||
|
# This is used in Variant, Material, and Quality Managers.
|
||||||
|
#
|
||||||
|
class ContainerNode:
|
||||||
|
__slots__ = ("metadata", "container", "children_map")
|
||||||
|
|
||||||
|
def __init__(self, metadata: Optional[dict] = None):
|
||||||
|
self.metadata = metadata
|
||||||
|
self.container = None
|
||||||
|
self.children_map = OrderedDict()
|
||||||
|
|
||||||
|
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
||||||
|
return self.children_map.get(child_key)
|
||||||
|
|
||||||
|
def getContainer(self) -> "InstanceContainer":
|
||||||
|
if self.metadata is None:
|
||||||
|
raise RuntimeError("Cannot get container for a ContainerNode without metadata")
|
||||||
|
|
||||||
|
if self.container is None:
|
||||||
|
container_id = self.metadata["id"]
|
||||||
|
Logger.log("i", "Lazy-loading container [%s]", container_id)
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||||
|
if not container_list:
|
||||||
|
raise RuntimeError("Failed to lazy-load container [%s], cannot find it" % container_id)
|
||||||
|
self.container = container_list[0]
|
||||||
|
|
||||||
|
return self.container
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "%s[%s]" % (self.__class__.__name__, self.metadata.get("id"))
|
26
cura/Machines/MaterialGroup.py
Normal file
26
cura/Machines/MaterialGroup.py
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# A MaterialGroup represents a group of material InstanceContainers that are derived from a single material profile.
|
||||||
|
# The main InstanceContainer which has the ID of the material profile file name is called the "root_material". For
|
||||||
|
# example: "generic_abs" is the root material (ID) of "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4",
|
||||||
|
# and "generic_abs_ultimaker3" and "generic_abs_ultimaker3_AA_0.4" are derived materials of "generic_abs".
|
||||||
|
#
|
||||||
|
# Using "generic_abs" as an example, the MaterialGroup for "generic_abs" will contain the following information:
|
||||||
|
# - name: "generic_abs", root_material_id
|
||||||
|
# - root_material_node: MaterialNode of "generic_abs"
|
||||||
|
# - derived_material_node_list: A list of MaterialNodes that are derived from "generic_abs",
|
||||||
|
# so "generic_abs_ultimaker3", "generic_abs_ultimaker3_AA_0.4", etc.
|
||||||
|
#
|
||||||
|
class MaterialGroup:
|
||||||
|
__slots__ = ("name", "root_material_node", "derived_material_node_list")
|
||||||
|
|
||||||
|
def __init__(self, name: str):
|
||||||
|
self.name = name
|
||||||
|
self.root_material_node = None
|
||||||
|
self.derived_material_node_list = []
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "%s[%s]" % (self.__class__.__name__, self.name)
|
479
cura/Machines/MaterialManager.py
Normal file
479
cura/Machines/MaterialManager.py
Normal file
|
@ -0,0 +1,479 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from collections import defaultdict, OrderedDict
|
||||||
|
import copy
|
||||||
|
import uuid
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from .MaterialNode import MaterialNode
|
||||||
|
from .MaterialGroup import MaterialGroup
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# MaterialManager maintains a number of maps and trees for material lookup.
|
||||||
|
# The models GUI and QML use are now only dependent on the MaterialManager. That means as long as the data in
|
||||||
|
# MaterialManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
|
||||||
|
#
|
||||||
|
# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
|
||||||
|
# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
|
||||||
|
# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
|
||||||
|
# because it's simple.
|
||||||
|
#
|
||||||
|
class MaterialManager(QObject):
|
||||||
|
|
||||||
|
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
||||||
|
|
||||||
|
def __init__(self, container_registry, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._container_registry = container_registry # type: ContainerRegistry
|
||||||
|
|
||||||
|
self._fallback_materials_map = dict() # material_type -> generic material metadata
|
||||||
|
self._material_group_map = dict() # root_material_id -> MaterialGroup
|
||||||
|
self._diameter_machine_variant_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
|
||||||
|
|
||||||
|
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
||||||
|
# because the generic material ids are used in qualities and definitions, while the specific diameter material is meant
|
||||||
|
# i.e. generic_pla -> generic_pla_175
|
||||||
|
self._material_diameter_map = defaultdict(dict) # root_material_id -> approximate diameter str -> root_material_id for that diameter
|
||||||
|
self._diameter_material_map = dict() # material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||||
|
|
||||||
|
# This is used in Legacy UM3 send material function and the material management page.
|
||||||
|
self._guid_material_groups_map = defaultdict(list) # GUID -> a list of material_groups
|
||||||
|
|
||||||
|
# The machine definition ID for the non-machine-specific materials.
|
||||||
|
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
|
||||||
|
self._default_machine_definition_id = "fdmprinter"
|
||||||
|
self._default_approximate_diameter_for_quality_search = "3"
|
||||||
|
|
||||||
|
# When a material gets added/imported, there can be more than one InstanceContainers. In those cases, we don't
|
||||||
|
# want to react on every container/metadata changed signal. The timer here is to buffer it a bit so we don't
|
||||||
|
# react too many time.
|
||||||
|
self._update_timer = QTimer(self)
|
||||||
|
self._update_timer.setInterval(300)
|
||||||
|
self._update_timer.setSingleShot(True)
|
||||||
|
self._update_timer.timeout.connect(self._updateMaps)
|
||||||
|
|
||||||
|
self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
|
||||||
|
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||||
|
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
# Find all materials and put them in a matrix for quick search.
|
||||||
|
material_metadata_list = self._container_registry.findContainersMetadata(type = "material")
|
||||||
|
|
||||||
|
self._material_group_map = dict()
|
||||||
|
|
||||||
|
# Map #1
|
||||||
|
# root_material_id -> MaterialGroup
|
||||||
|
for material_metadata in material_metadata_list:
|
||||||
|
material_id = material_metadata["id"]
|
||||||
|
# We don't store empty material in the lookup tables
|
||||||
|
if material_id == "empty_material":
|
||||||
|
continue
|
||||||
|
|
||||||
|
root_material_id = material_metadata.get("base_file")
|
||||||
|
if root_material_id not in self._material_group_map:
|
||||||
|
self._material_group_map[root_material_id] = MaterialGroup(root_material_id)
|
||||||
|
group = self._material_group_map[root_material_id]
|
||||||
|
|
||||||
|
# We only add root materials here
|
||||||
|
if material_id == root_material_id:
|
||||||
|
group.root_material_node = MaterialNode(material_metadata)
|
||||||
|
else:
|
||||||
|
new_node = MaterialNode(material_metadata)
|
||||||
|
group.derived_material_node_list.append(new_node)
|
||||||
|
# Order this map alphabetically so it's easier to navigate in a debugger
|
||||||
|
self._material_group_map = OrderedDict(sorted(self._material_group_map.items(), key = lambda x: x[0]))
|
||||||
|
|
||||||
|
# Map #1.5
|
||||||
|
# GUID -> material group list
|
||||||
|
self._guid_material_groups_map = defaultdict(list)
|
||||||
|
for root_material_id, material_group in self._material_group_map.items():
|
||||||
|
guid = material_group.root_material_node.metadata["GUID"]
|
||||||
|
self._guid_material_groups_map[guid].append(material_group)
|
||||||
|
|
||||||
|
# Map #2
|
||||||
|
# Lookup table for material type -> fallback material metadata, only for read-only materials
|
||||||
|
grouped_by_type_dict = dict()
|
||||||
|
for root_material_id, material_node in self._material_group_map.items():
|
||||||
|
if not self._container_registry.isReadOnly(root_material_id):
|
||||||
|
continue
|
||||||
|
material_type = material_node.root_material_node.metadata["material"]
|
||||||
|
if material_type not in grouped_by_type_dict:
|
||||||
|
grouped_by_type_dict[material_type] = {"generic": None,
|
||||||
|
"others": []}
|
||||||
|
brand = material_node.root_material_node.metadata["brand"]
|
||||||
|
if brand.lower() == "generic":
|
||||||
|
to_add = True
|
||||||
|
if material_type in grouped_by_type_dict:
|
||||||
|
diameter = material_node.root_material_node.metadata.get("approximate_diameter")
|
||||||
|
if diameter != self._default_approximate_diameter_for_quality_search:
|
||||||
|
to_add = False # don't add if it's not the default diameter
|
||||||
|
if to_add:
|
||||||
|
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
|
||||||
|
self._fallback_materials_map = grouped_by_type_dict
|
||||||
|
|
||||||
|
# Map #3
|
||||||
|
# There can be multiple material profiles for the same material with different diameters, such as "generic_pla"
|
||||||
|
# and "generic_pla_175". This is inconvenient when we do material-specific quality lookup because a quality can
|
||||||
|
# be for either "generic_pla" or "generic_pla_175", but not both. This map helps to get the correct material ID
|
||||||
|
# for quality search.
|
||||||
|
self._material_diameter_map = defaultdict(dict)
|
||||||
|
self._diameter_material_map = dict()
|
||||||
|
|
||||||
|
# Group the material IDs by the same name, material, brand, and color but with different diameters.
|
||||||
|
material_group_dict = dict()
|
||||||
|
keys_to_fetch = ("name", "material", "brand", "color")
|
||||||
|
for root_material_id, machine_node in self._material_group_map.items():
|
||||||
|
if not self._container_registry.isReadOnly(root_material_id):
|
||||||
|
continue
|
||||||
|
|
||||||
|
root_material_metadata = machine_node.root_material_node.metadata
|
||||||
|
|
||||||
|
key_data = []
|
||||||
|
for key in keys_to_fetch:
|
||||||
|
key_data.append(root_material_metadata.get(key))
|
||||||
|
key_data = tuple(key_data)
|
||||||
|
|
||||||
|
if key_data not in material_group_dict:
|
||||||
|
material_group_dict[key_data] = dict()
|
||||||
|
approximate_diameter = root_material_metadata.get("approximate_diameter")
|
||||||
|
material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
|
||||||
|
|
||||||
|
# Map [root_material_id][diameter] -> root_material_id for this diameter
|
||||||
|
for data_dict in material_group_dict.values():
|
||||||
|
for root_material_id1 in data_dict.values():
|
||||||
|
if root_material_id1 in self._material_diameter_map:
|
||||||
|
continue
|
||||||
|
diameter_map = data_dict
|
||||||
|
for root_material_id2 in data_dict.values():
|
||||||
|
self._material_diameter_map[root_material_id2] = diameter_map
|
||||||
|
|
||||||
|
default_root_material_id = data_dict.get(self._default_approximate_diameter_for_quality_search)
|
||||||
|
if default_root_material_id is None:
|
||||||
|
default_root_material_id = list(data_dict.values())[0] # no default diameter present, just take "the" only one
|
||||||
|
for root_material_id in data_dict.values():
|
||||||
|
self._diameter_material_map[root_material_id] = default_root_material_id
|
||||||
|
|
||||||
|
# Map #4
|
||||||
|
# "machine" -> "variant_name" -> "root material ID" -> specific material InstanceContainer
|
||||||
|
# Construct the "machine" -> "variant" -> "root material ID" -> specific material InstanceContainer
|
||||||
|
self._diameter_machine_variant_material_map = dict()
|
||||||
|
for material_metadata in material_metadata_list:
|
||||||
|
# We don't store empty material in the lookup tables
|
||||||
|
if material_metadata["id"] == "empty_material":
|
||||||
|
continue
|
||||||
|
|
||||||
|
root_material_id = material_metadata["base_file"]
|
||||||
|
definition = material_metadata["definition"]
|
||||||
|
approximate_diameter = material_metadata["approximate_diameter"]
|
||||||
|
|
||||||
|
if approximate_diameter not in self._diameter_machine_variant_material_map:
|
||||||
|
self._diameter_machine_variant_material_map[approximate_diameter] = {}
|
||||||
|
|
||||||
|
machine_variant_material_map = self._diameter_machine_variant_material_map[approximate_diameter]
|
||||||
|
if definition not in machine_variant_material_map:
|
||||||
|
machine_variant_material_map[definition] = MaterialNode()
|
||||||
|
|
||||||
|
machine_node = machine_variant_material_map[definition]
|
||||||
|
variant_name = material_metadata.get("variant_name")
|
||||||
|
if not variant_name:
|
||||||
|
# if there is no variant, this material is for the machine, so put its metadata in the machine node.
|
||||||
|
machine_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
||||||
|
else:
|
||||||
|
# this material is variant-specific, so we save it in a variant-specific node under the
|
||||||
|
# machine-specific node
|
||||||
|
if variant_name not in machine_node.children_map:
|
||||||
|
machine_node.children_map[variant_name] = MaterialNode()
|
||||||
|
|
||||||
|
variant_node = machine_node.children_map[variant_name]
|
||||||
|
if root_material_id not in variant_node.material_map:
|
||||||
|
variant_node.material_map[root_material_id] = MaterialNode(material_metadata)
|
||||||
|
else:
|
||||||
|
# Sanity check: make sure we don't have duplicated variant-specific materials for the same machine
|
||||||
|
raise RuntimeError("Found duplicate variant name [%s] for machine [%s] in material [%s]" %
|
||||||
|
(variant_name, definition, material_metadata["id"]))
|
||||||
|
|
||||||
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
|
def _updateMaps(self):
|
||||||
|
self.initialize()
|
||||||
|
|
||||||
|
def _onContainerMetadataChanged(self, container):
|
||||||
|
self._onContainerChanged(container)
|
||||||
|
|
||||||
|
def _onContainerChanged(self, container):
|
||||||
|
container_type = container.getMetaDataEntry("type")
|
||||||
|
if container_type != "material":
|
||||||
|
return
|
||||||
|
|
||||||
|
# update the maps
|
||||||
|
self._update_timer.start()
|
||||||
|
|
||||||
|
def getMaterialGroup(self, root_material_id: str) -> Optional[MaterialGroup]:
|
||||||
|
return self._material_group_map.get(root_material_id)
|
||||||
|
|
||||||
|
def getRootMaterialIDForDiameter(self, root_material_id: str, approximate_diameter: str) -> str:
|
||||||
|
return self._material_diameter_map.get(root_material_id).get(approximate_diameter, root_material_id)
|
||||||
|
|
||||||
|
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
||||||
|
return self._diameter_material_map.get(root_material_id)
|
||||||
|
|
||||||
|
def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
|
||||||
|
return self._guid_material_groups_map.get(guid)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
|
||||||
|
#
|
||||||
|
def getAvailableMaterials(self, machine_definition_id: str, extruder_variant_name: Optional[str],
|
||||||
|
diameter: float) -> dict:
|
||||||
|
# round the diameter to get the approximate diameter
|
||||||
|
rounded_diameter = str(round(diameter))
|
||||||
|
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
||||||
|
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s])", diameter, rounded_diameter)
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
# If there are variant materials, get the variant material
|
||||||
|
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
|
||||||
|
machine_node = machine_variant_material_map.get(machine_definition_id)
|
||||||
|
default_machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
|
||||||
|
variant_node = None
|
||||||
|
if extruder_variant_name is not None and machine_node is not None:
|
||||||
|
variant_node = machine_node.getChildNode(extruder_variant_name)
|
||||||
|
|
||||||
|
nodes_to_check = [variant_node, machine_node, default_machine_node]
|
||||||
|
|
||||||
|
# Fallback mechanism of finding materials:
|
||||||
|
# 1. variant-specific material
|
||||||
|
# 2. machine-specific material
|
||||||
|
# 3. generic material (for fdmprinter)
|
||||||
|
material_id_metadata_dict = dict()
|
||||||
|
for node in nodes_to_check:
|
||||||
|
if node is not None:
|
||||||
|
for material_id, node in node.material_map.items():
|
||||||
|
if material_id not in material_id_metadata_dict:
|
||||||
|
material_id_metadata_dict[material_id] = node
|
||||||
|
|
||||||
|
return material_id_metadata_dict
|
||||||
|
|
||||||
|
#
|
||||||
|
# A convenience function to get available materials for the given machine with the extruder position.
|
||||||
|
#
|
||||||
|
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||||
|
extruder_stack: "ExtruderStack") -> Optional[dict]:
|
||||||
|
machine_definition_id = machine.definition.getId()
|
||||||
|
variant_name = None
|
||||||
|
if extruder_stack.variant.getId() != "empty_variant":
|
||||||
|
variant_name = extruder_stack.variant.getName()
|
||||||
|
diameter = extruder_stack.approximateMaterialDiameter
|
||||||
|
|
||||||
|
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
|
||||||
|
return self.getAvailableMaterials(machine_definition_id, variant_name, diameter)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets MaterialNode for the given extruder and machine with the given material name.
|
||||||
|
# Returns None if:
|
||||||
|
# 1. the given machine doesn't have materials;
|
||||||
|
# 2. cannot find any material InstanceContainers with the given settings.
|
||||||
|
#
|
||||||
|
def getMaterialNode(self, machine_definition_id: str, extruder_variant_name: Optional[str],
|
||||||
|
diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
|
||||||
|
# round the diameter to get the approximate diameter
|
||||||
|
rounded_diameter = str(round(diameter))
|
||||||
|
if rounded_diameter not in self._diameter_machine_variant_material_map:
|
||||||
|
Logger.log("i", "Cannot find materials with diameter [%s] (rounded to [%s]) for root material id [%s]",
|
||||||
|
diameter, rounded_diameter, root_material_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
# If there are variant materials, get the variant material
|
||||||
|
machine_variant_material_map = self._diameter_machine_variant_material_map[rounded_diameter]
|
||||||
|
machine_node = machine_variant_material_map.get(machine_definition_id)
|
||||||
|
variant_node = None
|
||||||
|
|
||||||
|
# Fallback for "fdmprinter" if the machine-specific materials cannot be found
|
||||||
|
if machine_node is None:
|
||||||
|
machine_node = machine_variant_material_map.get(self._default_machine_definition_id)
|
||||||
|
if machine_node is not None and extruder_variant_name is not None:
|
||||||
|
variant_node = machine_node.getChildNode(extruder_variant_name)
|
||||||
|
|
||||||
|
# Fallback mechanism of finding materials:
|
||||||
|
# 1. variant-specific material
|
||||||
|
# 2. machine-specific material
|
||||||
|
# 3. generic material (for fdmprinter)
|
||||||
|
nodes_to_check = [variant_node, machine_node,
|
||||||
|
machine_variant_material_map.get(self._default_machine_definition_id)]
|
||||||
|
|
||||||
|
material_node = None
|
||||||
|
for node in nodes_to_check:
|
||||||
|
if node is not None:
|
||||||
|
material_node = node.material_map.get(root_material_id)
|
||||||
|
if material_node:
|
||||||
|
break
|
||||||
|
|
||||||
|
return material_node
|
||||||
|
|
||||||
|
#
|
||||||
|
# Used by QualityManager. Built-in quality profiles may be based on generic material IDs such as "generic_pla".
|
||||||
|
# For materials such as ultimaker_pla_orange, no quality profiles may be found, so we should fall back to use
|
||||||
|
# the generic material IDs to search for qualities.
|
||||||
|
#
|
||||||
|
# An example would be, suppose we have machine with preferred material set to "filo3d_pla" (1.75mm), but its
|
||||||
|
# extruders only use 2.85mm materials, then we won't be able to find the preferred material for this machine.
|
||||||
|
# A fallback would be to fetch a generic material of the same type "PLA" as "filo3d_pla", and in this case it will
|
||||||
|
# be "generic_pla". This function is intended to get a generic fallback material for the given material type.
|
||||||
|
#
|
||||||
|
# This function returns the generic root material ID for the given material type, where material types are "PLA",
|
||||||
|
# "ABS", etc.
|
||||||
|
#
|
||||||
|
def getFallbackMaterialIdByMaterialType(self, material_type: str) -> Optional[str]:
|
||||||
|
# For safety
|
||||||
|
if material_type not in self._fallback_materials_map:
|
||||||
|
Logger.log("w", "The material type [%s] does not have a fallback material" % material_type)
|
||||||
|
return None
|
||||||
|
fallback_material = self._fallback_materials_map[material_type]
|
||||||
|
if fallback_material:
|
||||||
|
return self.getRootMaterialIDWithoutDiameter(fallback_material["id"])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getDefaultMaterial(self, global_stack: "GlobalStack", extruder_variant_name: str) -> Optional["MaterialNode"]:
|
||||||
|
node = None
|
||||||
|
machine_definition = global_stack.definition
|
||||||
|
if parseBool(machine_definition.getMetaDataEntry("has_materials", False)):
|
||||||
|
material_diameter = machine_definition.getProperty("material_diameter", "value")
|
||||||
|
if isinstance(material_diameter, SettingFunction):
|
||||||
|
material_diameter = material_diameter(global_stack)
|
||||||
|
approximate_material_diameter = str(round(material_diameter))
|
||||||
|
root_material_id = machine_definition.getMetaDataEntry("preferred_material")
|
||||||
|
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_material_diameter)
|
||||||
|
node = self.getMaterialNode(machine_definition.getId(), extruder_variant_name,
|
||||||
|
material_diameter, root_material_id)
|
||||||
|
return node
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for GUI
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Sets the new name for the given material.
|
||||||
|
#
|
||||||
|
@pyqtSlot("QVariant", str)
|
||||||
|
def setMaterialName(self, material_node: "MaterialNode", name: str):
|
||||||
|
root_material_id = material_node.metadata["base_file"]
|
||||||
|
if self._container_registry.isReadOnly(root_material_id):
|
||||||
|
Logger.log("w", "Cannot set name of read-only container %s.", root_material_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
|
material_group.root_material_node.getContainer().setName(name)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Removes the given material.
|
||||||
|
#
|
||||||
|
@pyqtSlot("QVariant")
|
||||||
|
def removeMaterial(self, material_node: "MaterialNode"):
|
||||||
|
root_material_id = material_node.metadata["base_file"]
|
||||||
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
|
if not material_group:
|
||||||
|
Logger.log("d", "Unable to remove the material with id %s, because it doesn't exist.", root_material_id)
|
||||||
|
return
|
||||||
|
|
||||||
|
nodes_to_remove = [material_group.root_material_node] + material_group.derived_material_node_list
|
||||||
|
for node in nodes_to_remove:
|
||||||
|
self._container_registry.removeContainer(node.metadata["id"])
|
||||||
|
|
||||||
|
#
|
||||||
|
# Creates a duplicate of a material, which has the same GUID and base_file metadata.
|
||||||
|
# Returns the root material ID of the duplicated material if successful.
|
||||||
|
#
|
||||||
|
@pyqtSlot("QVariant", result = str)
|
||||||
|
def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
|
||||||
|
root_material_id = material_node.metadata["base_file"]
|
||||||
|
|
||||||
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
|
if not material_group:
|
||||||
|
Logger.log("i", "Unable to duplicate the material with id %s, because it doesn't exist.", root_material_id)
|
||||||
|
return None
|
||||||
|
|
||||||
|
base_container = material_group.root_material_node.getContainer()
|
||||||
|
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
# Create a new ID & container to hold the data.
|
||||||
|
new_containers = []
|
||||||
|
if new_base_id is None:
|
||||||
|
new_base_id = self._container_registry.uniqueName(base_container.getId())
|
||||||
|
new_base_container = copy.deepcopy(base_container)
|
||||||
|
new_base_container.getMetaData()["id"] = new_base_id
|
||||||
|
new_base_container.getMetaData()["base_file"] = new_base_id
|
||||||
|
if new_metadata is not None:
|
||||||
|
for key, value in new_metadata.items():
|
||||||
|
new_base_container.getMetaData()[key] = value
|
||||||
|
new_containers.append(new_base_container)
|
||||||
|
|
||||||
|
# Clone all of them.
|
||||||
|
for node in material_group.derived_material_node_list:
|
||||||
|
container_to_copy = node.getContainer()
|
||||||
|
# Create unique IDs for every clone.
|
||||||
|
new_id = new_base_id
|
||||||
|
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
|
||||||
|
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
|
||||||
|
if container_to_copy.getMetaDataEntry("variant_name"):
|
||||||
|
variant_name = container_to_copy.getMetaDataEntry("variant_name")
|
||||||
|
new_id += "_" + variant_name.replace(" ", "_")
|
||||||
|
|
||||||
|
new_container = copy.deepcopy(container_to_copy)
|
||||||
|
new_container.getMetaData()["id"] = new_id
|
||||||
|
new_container.getMetaData()["base_file"] = new_base_id
|
||||||
|
if new_metadata is not None:
|
||||||
|
for key, value in new_metadata.items():
|
||||||
|
new_container.getMetaData()[key] = value
|
||||||
|
|
||||||
|
new_containers.append(new_container)
|
||||||
|
|
||||||
|
for container_to_add in new_containers:
|
||||||
|
container_to_add.setDirty(True)
|
||||||
|
self._container_registry.addContainer(container_to_add)
|
||||||
|
return new_base_id
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
||||||
|
#
|
||||||
|
@pyqtSlot(result = str)
|
||||||
|
def createMaterial(self) -> str:
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("cura")
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
|
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
|
||||||
|
root_material_id = "generic_pla"
|
||||||
|
root_material_id = self.getRootMaterialIDForDiameter(root_material_id, approximate_diameter)
|
||||||
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
|
|
||||||
|
# Create a new ID & container to hold the data.
|
||||||
|
new_id = self._container_registry.uniqueName("custom_material")
|
||||||
|
new_metadata = {"name": catalog.i18nc("@label", "Custom Material"),
|
||||||
|
"brand": catalog.i18nc("@label", "Custom"),
|
||||||
|
"GUID": str(uuid.uuid4()),
|
||||||
|
}
|
||||||
|
|
||||||
|
self.duplicateMaterial(material_group.root_material_node,
|
||||||
|
new_base_id = new_id,
|
||||||
|
new_metadata = new_metadata)
|
||||||
|
return new_id
|
21
cura/Machines/MaterialNode.py
Normal file
21
cura/Machines/MaterialNode.py
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .ContainerNode import ContainerNode
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# A MaterialNode is a node in the material lookup tree/map/table. It contains 2 (extra) fields:
|
||||||
|
# - material_map: a one-to-one map of "material_root_id" to material_node.
|
||||||
|
# - children_map: the key-value map for child nodes of this node. This is used in a lookup tree.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
class MaterialNode(ContainerNode):
|
||||||
|
__slots__ = ("material_map", "children_map")
|
||||||
|
|
||||||
|
def __init__(self, metadata: Optional[dict] = None):
|
||||||
|
super().__init__(metadata = metadata)
|
||||||
|
self.material_map = {} # material_root_id -> material_node
|
||||||
|
self.children_map = {} # mapping for the child nodes
|
46
cura/Machines/Models/BaseMaterialsModel.py
Normal file
46
cura/Machines/Models/BaseMaterialsModel.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is the base model class for GenericMaterialsModel and BrandMaterialsModel
|
||||||
|
# Those 2 models are used by the material drop down menu to show generic materials and branded materials separately.
|
||||||
|
# The extruder position defined here is being used to bound a menu to the correct extruder. This is used in the top
|
||||||
|
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
||||||
|
#
|
||||||
|
class BaseMaterialsModel(ListModel):
|
||||||
|
RootMaterialIdRole = Qt.UserRole + 1
|
||||||
|
IdRole = Qt.UserRole + 2
|
||||||
|
NameRole = Qt.UserRole + 3
|
||||||
|
BrandRole = Qt.UserRole + 4
|
||||||
|
MaterialRole = Qt.UserRole + 5
|
||||||
|
ColorRole = Qt.UserRole + 6
|
||||||
|
ContainerNodeRole = Qt.UserRole + 7
|
||||||
|
|
||||||
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
||||||
|
self.addRoleName(self.IdRole, "id")
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.BrandRole, "brand")
|
||||||
|
self.addRoleName(self.MaterialRole, "material")
|
||||||
|
self.addRoleName(self.ColorRole, "color_name")
|
||||||
|
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||||
|
|
||||||
|
self._extruder_position = 0
|
||||||
|
|
||||||
|
def setExtruderPosition(self, position: int):
|
||||||
|
if self._extruder_position != position:
|
||||||
|
self._extruder_position = position
|
||||||
|
self.extruderPositionChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
|
||||||
|
def extruderPosition(self) -> int:
|
||||||
|
return self._extruder_positoin
|
131
cura/Machines/Models/BrandMaterialsModel.py
Normal file
131
cura/Machines/Models/BrandMaterialsModel.py
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
from .BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This is an intermediate model to group materials with different colours for a same brand and type.
|
||||||
|
#
|
||||||
|
class MaterialsModelGroupedByType(ListModel):
|
||||||
|
NameRole = Qt.UserRole + 1
|
||||||
|
ColorsRole = Qt.UserRole + 2
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.ColorsRole, "colors")
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This model is used to show branded materials in the material drop down menu.
|
||||||
|
# The structure of the menu looks like this:
|
||||||
|
# Brand -> Material Type -> list of materials
|
||||||
|
#
|
||||||
|
# To illustrate, a branded material menu may look like this:
|
||||||
|
# Ultimaker -> PLA -> Yellow PLA
|
||||||
|
# -> Black PLA
|
||||||
|
# -> ...
|
||||||
|
# -> ABS -> White ABS
|
||||||
|
# ...
|
||||||
|
#
|
||||||
|
class BrandMaterialsModel(ListModel):
|
||||||
|
NameRole = Qt.UserRole + 1
|
||||||
|
MaterialsRole = Qt.UserRole + 2
|
||||||
|
|
||||||
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.MaterialsRole, "materials")
|
||||||
|
|
||||||
|
self._extruder_position = 0
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||||
|
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||||
|
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
self._extruder_manager.activeExtruderChanged.connect(self._update)
|
||||||
|
self._material_manager.materialsUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def setExtruderPosition(self, position: int):
|
||||||
|
if self._extruder_position != position:
|
||||||
|
self._extruder_position = position
|
||||||
|
self.extruderPositionChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(int, fset = setExtruderPosition, notify = extruderPositionChanged)
|
||||||
|
def extruderPosition(self) -> int:
|
||||||
|
return self._extruder_position
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
if global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
extruder_position = str(self._extruder_position)
|
||||||
|
if extruder_position not in global_stack.extruders:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
extruder_stack = global_stack.extruders[str(self._extruder_position)]
|
||||||
|
|
||||||
|
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
||||||
|
extruder_stack)
|
||||||
|
if available_material_dict is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
brand_item_list = []
|
||||||
|
brand_group_dict = {}
|
||||||
|
for root_material_id, container_node in available_material_dict.items():
|
||||||
|
metadata = container_node.metadata
|
||||||
|
brand = metadata["brand"]
|
||||||
|
# Only add results for generic materials
|
||||||
|
if brand.lower() == "generic":
|
||||||
|
continue
|
||||||
|
|
||||||
|
if brand not in brand_group_dict:
|
||||||
|
brand_group_dict[brand] = {}
|
||||||
|
|
||||||
|
material_type = metadata["material"]
|
||||||
|
if material_type not in brand_group_dict[brand]:
|
||||||
|
brand_group_dict[brand][material_type] = []
|
||||||
|
|
||||||
|
item = {"root_material_id": root_material_id,
|
||||||
|
"id": metadata["id"],
|
||||||
|
"name": metadata["name"],
|
||||||
|
"brand": metadata["brand"],
|
||||||
|
"material": metadata["material"],
|
||||||
|
"color_name": metadata["color_name"],
|
||||||
|
"container_node": container_node
|
||||||
|
}
|
||||||
|
brand_group_dict[brand][material_type].append(item)
|
||||||
|
|
||||||
|
for brand, material_dict in brand_group_dict.items():
|
||||||
|
brand_item = {"name": brand,
|
||||||
|
"materials": MaterialsModelGroupedByType(self)}
|
||||||
|
|
||||||
|
material_type_item_list = []
|
||||||
|
for material_type, material_list in material_dict.items():
|
||||||
|
material_type_item = {"name": material_type,
|
||||||
|
"colors": BaseMaterialsModel(self)}
|
||||||
|
material_type_item["colors"].clear()
|
||||||
|
material_type_item["colors"].setItems(material_list)
|
||||||
|
|
||||||
|
material_type_item_list.append(material_type_item)
|
||||||
|
|
||||||
|
brand_item["materials"].setItems(material_type_item_list)
|
||||||
|
|
||||||
|
brand_item_list.append(brand_item)
|
||||||
|
|
||||||
|
self.setItems(brand_item_list)
|
49
cura/Machines/Models/BuildPlateModel.py
Normal file
49
cura/Machines/Models/BuildPlateModel.py
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from cura.Machines.VariantManager import VariantType
|
||||||
|
|
||||||
|
|
||||||
|
class BuildPlateModel(ListModel):
|
||||||
|
NameRole = Qt.UserRole + 1
|
||||||
|
ContainerNodeRole = Qt.UserRole + 2
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||||
|
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._variant_manager = self._application._variant_manager
|
||||||
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
global_stack = self._machine_manager._global_container_stack
|
||||||
|
if not global_stack:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
has_variants = parseBool(global_stack.getMetaDataEntry("has_variant_buildplates", False))
|
||||||
|
if not has_variants:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
variant_dict = self._variant_manager.getVariantNodes(global_stack, variant_type = VariantType.BUILD_PLATE)
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
for name, variant_node in variant_dict.items():
|
||||||
|
item = {"name": name,
|
||||||
|
"container_node": variant_node}
|
||||||
|
item_list.append(item)
|
||||||
|
self.setItems(item_list)
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This model is used for the custom profile items in the profile drop down menu.
|
||||||
|
#
|
||||||
|
class CustomQualityProfilesDropDownMenuModel(QualityProfilesDropDownMenuModel):
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
Logger.log("d", "Updating %s ...", self.__class__.__name__)
|
||||||
|
|
||||||
|
active_global_stack = self._machine_manager.activeMachine
|
||||||
|
if active_global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
Logger.log("d", "No active GlobalStack, set %s as empty.", self.__class__.__name__)
|
||||||
|
return
|
||||||
|
|
||||||
|
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(active_global_stack)
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
for key in sorted(quality_changes_group_dict):
|
||||||
|
quality_changes_group = quality_changes_group_dict[key]
|
||||||
|
|
||||||
|
item = {"name": quality_changes_group.name,
|
||||||
|
"layer_height": "",
|
||||||
|
"layer_height_without_unit": "",
|
||||||
|
"available": quality_changes_group.is_available,
|
||||||
|
"quality_changes_group": quality_changes_group}
|
||||||
|
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
60
cura/Machines/Models/GenericMaterialsModel.py
Normal file
60
cura/Machines/Models/GenericMaterialsModel.py
Normal file
|
@ -0,0 +1,60 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from .BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
|
||||||
|
class GenericMaterialsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||||
|
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||||
|
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
self._extruder_manager.activeExtruderChanged.connect(self._update)
|
||||||
|
self._material_manager.materialsUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
if global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
extruder_position = str(self._extruder_position)
|
||||||
|
if extruder_position not in global_stack.extruders:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
extruder_stack = global_stack.extruders[extruder_position]
|
||||||
|
|
||||||
|
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
||||||
|
extruder_stack)
|
||||||
|
if available_material_dict is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
for root_material_id, container_node in available_material_dict.items():
|
||||||
|
metadata = container_node.metadata
|
||||||
|
# Only add results for generic materials
|
||||||
|
if metadata["brand"].lower() != "generic":
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = {"root_material_id": root_material_id,
|
||||||
|
"id": metadata["id"],
|
||||||
|
"name": metadata["name"],
|
||||||
|
"brand": metadata["brand"],
|
||||||
|
"material": metadata["material"],
|
||||||
|
"color_name": metadata["color_name"],
|
||||||
|
"container_node": container_node
|
||||||
|
}
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
# Sort the item list by material name alphabetically
|
||||||
|
item_list = sorted(item_list, key = lambda d: d["name"])
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
101
cura/Machines/Models/MaterialManagementModel.py
Normal file
101
cura/Machines/Models/MaterialManagementModel.py
Normal file
|
@ -0,0 +1,101 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, pyqtProperty
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This model is for the Material management page.
|
||||||
|
#
|
||||||
|
class MaterialManagementModel(ListModel):
|
||||||
|
RootMaterialIdRole = Qt.UserRole + 1
|
||||||
|
DisplayNameRole = Qt.UserRole + 2
|
||||||
|
BrandRole = Qt.UserRole + 3
|
||||||
|
MaterialTypeRole = Qt.UserRole + 4
|
||||||
|
ColorNameRole = Qt.UserRole + 5
|
||||||
|
ColorCodeRole = Qt.UserRole + 6
|
||||||
|
ContainerNodeRole = Qt.UserRole + 7
|
||||||
|
ContainerIdRole = Qt.UserRole + 8
|
||||||
|
|
||||||
|
DescriptionRole = Qt.UserRole + 9
|
||||||
|
AdhesionInfoRole = Qt.UserRole + 10
|
||||||
|
ApproximateDiameterRole = Qt.UserRole + 11
|
||||||
|
GuidRole = Qt.UserRole + 12
|
||||||
|
DensityRole = Qt.UserRole + 13
|
||||||
|
DiameterRole = Qt.UserRole + 14
|
||||||
|
IsReadOnlyRole = Qt.UserRole + 15
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
||||||
|
self.addRoleName(self.DisplayNameRole, "name")
|
||||||
|
self.addRoleName(self.BrandRole, "brand")
|
||||||
|
self.addRoleName(self.MaterialTypeRole, "material")
|
||||||
|
self.addRoleName(self.ColorNameRole, "color_name")
|
||||||
|
self.addRoleName(self.ColorCodeRole, "color_code")
|
||||||
|
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||||
|
self.addRoleName(self.ContainerIdRole, "container_id")
|
||||||
|
|
||||||
|
self.addRoleName(self.DescriptionRole, "description")
|
||||||
|
self.addRoleName(self.AdhesionInfoRole, "adhesion_info")
|
||||||
|
self.addRoleName(self.ApproximateDiameterRole, "approximate_diameter")
|
||||||
|
self.addRoleName(self.GuidRole, "guid")
|
||||||
|
self.addRoleName(self.DensityRole, "density")
|
||||||
|
self.addRoleName(self.DiameterRole, "diameter")
|
||||||
|
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||||
|
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||||
|
self._material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||||
|
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
self._extruder_manager.activeExtruderChanged.connect(self._update)
|
||||||
|
self._material_manager.materialsUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
if global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
active_extruder_stack = self._machine_manager.activeStack
|
||||||
|
|
||||||
|
available_material_dict = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack,
|
||||||
|
active_extruder_stack)
|
||||||
|
if available_material_dict is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
material_list = []
|
||||||
|
for root_material_id, container_node in available_material_dict.items():
|
||||||
|
keys_to_fetch = ("name",
|
||||||
|
"brand",
|
||||||
|
"material",
|
||||||
|
"color_name",
|
||||||
|
"color_code",
|
||||||
|
"description",
|
||||||
|
"adhesion_info",
|
||||||
|
"approximate_diameter",)
|
||||||
|
|
||||||
|
item = {"root_material_id": container_node.metadata["base_file"],
|
||||||
|
"container_node": container_node,
|
||||||
|
"guid": container_node.metadata["GUID"],
|
||||||
|
"container_id": container_node.metadata["id"],
|
||||||
|
"density": container_node.metadata.get("properties", {}).get("density", ""),
|
||||||
|
"diameter": container_node.metadata.get("properties", {}).get("diameter", ""),
|
||||||
|
"is_read_only": self._container_registry.isReadOnly(container_node.metadata["id"]),
|
||||||
|
}
|
||||||
|
|
||||||
|
for key in keys_to_fetch:
|
||||||
|
item[key] = container_node.metadata.get(key, "")
|
||||||
|
|
||||||
|
material_list.append(item)
|
||||||
|
|
||||||
|
material_list = sorted(material_list, key = lambda k: (k["brand"].lower(), k["name"]))
|
||||||
|
self.setItems(material_list)
|
|
@ -1,24 +1,32 @@
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Qt.ListModel import ListModel
|
|
||||||
from UM.Scene.Selection import Selection
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
from UM.Scene.Selection import Selection
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
class BuildPlateModel(ListModel):
|
#
|
||||||
|
# This is the model for multi build plate feature.
|
||||||
|
# This has nothing to do with the build plate types you can choose on the sidebar for a machine.
|
||||||
|
#
|
||||||
|
class MultiBuildPlateModel(ListModel):
|
||||||
|
|
||||||
maxBuildPlateChanged = pyqtSignal()
|
maxBuildPlateChanged = pyqtSignal()
|
||||||
activeBuildPlateChanged = pyqtSignal()
|
activeBuildPlateChanged = pyqtSignal()
|
||||||
selectionChanged = pyqtSignal()
|
selectionChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, parent = None):
|
||||||
super().__init__()
|
super().__init__(parent)
|
||||||
Application.getInstance().getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._application.getController().getScene().sceneChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||||
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
Selection.selectionChanged.connect(self._updateSelectedObjectBuildPlateNumbers)
|
||||||
|
|
||||||
self._max_build_plate = 1 # default
|
self._max_build_plate = 1 # default
|
||||||
self._active_build_plate = -1
|
self._active_build_plate = -1
|
||||||
self._selection_build_plates = []
|
|
||||||
|
|
||||||
def setMaxBuildPlate(self, max_build_plate):
|
def setMaxBuildPlate(self, max_build_plate):
|
||||||
self._max_build_plate = max_build_plate
|
self._max_build_plate = max_build_plate
|
||||||
|
@ -37,10 +45,6 @@ class BuildPlateModel(ListModel):
|
||||||
def activeBuildPlate(self):
|
def activeBuildPlate(self):
|
||||||
return self._active_build_plate
|
return self._active_build_plate
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def createBuildPlateModel():
|
|
||||||
return BuildPlateModel()
|
|
||||||
|
|
||||||
def _updateSelectedObjectBuildPlateNumbers(self, *args):
|
def _updateSelectedObjectBuildPlateNumbers(self, *args):
|
||||||
result = set()
|
result = set()
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
56
cura/Machines/Models/NozzleModel.py
Normal file
56
cura/Machines/Models/NozzleModel.py
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
|
||||||
|
class NozzleModel(ListModel):
|
||||||
|
IdRole = Qt.UserRole + 1
|
||||||
|
HotendNameRole = Qt.UserRole + 2
|
||||||
|
ContainerNodeRole = Qt.UserRole + 3
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.IdRole, "id")
|
||||||
|
self.addRoleName(self.HotendNameRole, "hotend_name")
|
||||||
|
self.addRoleName(self.ContainerNodeRole, "container_node")
|
||||||
|
|
||||||
|
Application.getInstance().globalContainerStackChanged.connect(self._update)
|
||||||
|
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
|
||||||
|
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
|
||||||
|
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
self.items.clear()
|
||||||
|
|
||||||
|
variant_manager = Application.getInstance()._variant_manager
|
||||||
|
active_global_stack = Application.getInstance().getMachineManager()._global_container_stack
|
||||||
|
if active_global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
has_variants = parseBool(active_global_stack.getMetaDataEntry("has_variants", False))
|
||||||
|
if not has_variants:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
variant_node_dict = variant_manager.getVariantNodes(active_global_stack)
|
||||||
|
if not variant_node_dict:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
for hotend_name, container_node in sorted(variant_node_dict.items(), key = lambda i: i[0]):
|
||||||
|
item = {"id": hotend_name,
|
||||||
|
"hotend_name": hotend_name,
|
||||||
|
"container_node": container_node
|
||||||
|
}
|
||||||
|
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
122
cura/Machines/Models/QualityManagementModel.py
Normal file
122
cura/Machines/Models/QualityManagementModel.py
Normal file
|
@ -0,0 +1,122 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||||||
|
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This the QML model for the quality management page.
|
||||||
|
#
|
||||||
|
class QualityManagementModel(ListModel):
|
||||||
|
NameRole = Qt.UserRole + 1
|
||||||
|
IsReadOnlyRole = Qt.UserRole + 2
|
||||||
|
QualityGroupRole = Qt.UserRole + 3
|
||||||
|
QualityChangesGroupRole = Qt.UserRole + 4
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.IsReadOnlyRole, "is_read_only")
|
||||||
|
self.addRoleName(self.QualityGroupRole, "quality_group")
|
||||||
|
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||||
|
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
self._container_registry = CuraApplication.getInstance().getContainerRegistry()
|
||||||
|
self._machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||||
|
self._extruder_manager = CuraApplication.getInstance().getExtruderManager()
|
||||||
|
self._quality_manager = CuraApplication.getInstance().getQualityManager()
|
||||||
|
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._update)
|
||||||
|
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
|
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||||
|
quality_changes_group_dict = self._quality_manager.getQualityChangesGroups(global_stack)
|
||||||
|
|
||||||
|
available_quality_types = set(quality_type for quality_type, quality_group in quality_group_dict.items()
|
||||||
|
if quality_group.is_available)
|
||||||
|
if not available_quality_types and not quality_changes_group_dict:
|
||||||
|
# Nothing to show
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
# Create quality group items
|
||||||
|
for quality_group in quality_group_dict.values():
|
||||||
|
if not quality_group.is_available:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = {"name": quality_group.name,
|
||||||
|
"is_read_only": True,
|
||||||
|
"quality_group": quality_group,
|
||||||
|
"quality_changes_group": None}
|
||||||
|
item_list.append(item)
|
||||||
|
# Sort by quality names
|
||||||
|
item_list = sorted(item_list, key = lambda x: x["name"])
|
||||||
|
|
||||||
|
# Create quality_changes group items
|
||||||
|
quality_changes_item_list = []
|
||||||
|
for quality_changes_group in quality_changes_group_dict.values():
|
||||||
|
if quality_changes_group.quality_type not in available_quality_types:
|
||||||
|
continue
|
||||||
|
quality_group = quality_group_dict[quality_changes_group.quality_type]
|
||||||
|
item = {"name": quality_changes_group.name,
|
||||||
|
"is_read_only": False,
|
||||||
|
"quality_group": quality_group,
|
||||||
|
"quality_changes_group": quality_changes_group}
|
||||||
|
quality_changes_item_list.append(item)
|
||||||
|
|
||||||
|
# Sort quality_changes items by names and append to the item list
|
||||||
|
quality_changes_item_list = sorted(quality_changes_item_list, key = lambda x: x["name"])
|
||||||
|
item_list += quality_changes_item_list
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
||||||
|
|
||||||
|
# TODO: Duplicated code here from InstanceContainersModel. Refactor and remove this later.
|
||||||
|
#
|
||||||
|
## Gets a list of the possible file filters that the plugins have
|
||||||
|
# registered they can read or write. The convenience meta-filters
|
||||||
|
# "All Supported Types" and "All Files" are added when listing
|
||||||
|
# readers, but not when listing writers.
|
||||||
|
#
|
||||||
|
# \param io_type \type{str} name of the needed IO type
|
||||||
|
# \return A list of strings indicating file name filters for a file
|
||||||
|
# dialog.
|
||||||
|
@pyqtSlot(str, result = "QVariantList")
|
||||||
|
def getFileNameFilters(self, io_type):
|
||||||
|
from UM.i18n import i18nCatalog
|
||||||
|
catalog = i18nCatalog("uranium")
|
||||||
|
#TODO: This function should be in UM.Resources!
|
||||||
|
filters = []
|
||||||
|
all_types = []
|
||||||
|
for plugin_id, meta_data in self._getIOPlugins(io_type):
|
||||||
|
for io_plugin in meta_data[io_type]:
|
||||||
|
filters.append(io_plugin["description"] + " (*." + io_plugin["extension"] + ")")
|
||||||
|
all_types.append("*.{0}".format(io_plugin["extension"]))
|
||||||
|
|
||||||
|
if "_reader" in io_type:
|
||||||
|
# if we're listing readers, add the option to show all supported files as the default option
|
||||||
|
filters.insert(0, catalog.i18nc("@item:inlistbox", "All Supported Types ({0})", " ".join(all_types)))
|
||||||
|
filters.append(catalog.i18nc("@item:inlistbox", "All Files (*)")) # Also allow arbitrary files, if the user so prefers.
|
||||||
|
return filters
|
||||||
|
|
||||||
|
## Gets a list of profile reader or writer plugins
|
||||||
|
# \return List of tuples of (plugin_id, meta_data).
|
||||||
|
def _getIOPlugins(self, io_type):
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
pr = PluginRegistry.getInstance()
|
||||||
|
active_plugin_ids = pr.getActivePlugins()
|
||||||
|
|
||||||
|
result = []
|
||||||
|
for plugin_id in active_plugin_ids:
|
||||||
|
meta_data = pr.getMetaData(plugin_id)
|
||||||
|
if io_type in meta_data:
|
||||||
|
result.append( (plugin_id, meta_data) )
|
||||||
|
return result
|
106
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal file
106
cura/Machines/Models/QualityProfilesDropDownMenuModel.py
Normal file
|
@ -0,0 +1,106 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import Qt
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
from cura.Machines.QualityManager import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
||||||
|
#
|
||||||
|
class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
|
NameRole = Qt.UserRole + 1
|
||||||
|
QualityTypeRole = Qt.UserRole + 2
|
||||||
|
LayerHeightRole = Qt.UserRole + 3
|
||||||
|
LayerHeightUnitRole = Qt.UserRole + 4
|
||||||
|
AvailableRole = Qt.UserRole + 5
|
||||||
|
QualityGroupRole = Qt.UserRole + 6
|
||||||
|
QualityChangesGroupRole = Qt.UserRole + 7
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.NameRole, "name")
|
||||||
|
self.addRoleName(self.QualityTypeRole, "quality_type")
|
||||||
|
self.addRoleName(self.LayerHeightRole, "layer_height")
|
||||||
|
self.addRoleName(self.LayerHeightUnitRole, "layer_height_unit")
|
||||||
|
self.addRoleName(self.AvailableRole, "available")
|
||||||
|
self.addRoleName(self.QualityGroupRole, "quality_group")
|
||||||
|
self.addRoleName(self.QualityChangesGroupRole, "quality_changes_group")
|
||||||
|
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
self._quality_manager = Application.getInstance().getQualityManager()
|
||||||
|
|
||||||
|
self._application.globalContainerStackChanged.connect(self._update)
|
||||||
|
self._machine_manager.activeQualityGroupChanged.connect(self._update)
|
||||||
|
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._layer_height_unit = "" # This is cached
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
Logger.log("d", "Updating quality profile model ...")
|
||||||
|
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
if global_stack is None:
|
||||||
|
self.setItems([])
|
||||||
|
Logger.log("d", "No active GlobalStack, set quality profile model as empty.")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Check for material compatibility
|
||||||
|
if not self._machine_manager.activeMaterialsCompatible():
|
||||||
|
Logger.log("d", "No active material compatibility, set quality profile model as empty.")
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
quality_group_dict = self._quality_manager.getQualityGroups(global_stack)
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
for key in sorted(quality_group_dict):
|
||||||
|
quality_group = quality_group_dict[key]
|
||||||
|
|
||||||
|
layer_height = self._fetchLayerHeight(quality_group)
|
||||||
|
|
||||||
|
item = {"name": quality_group.name,
|
||||||
|
"quality_type": quality_group.quality_type,
|
||||||
|
"layer_height": layer_height,
|
||||||
|
"layer_height_unit": self._layer_height_unit,
|
||||||
|
"available": quality_group.is_available,
|
||||||
|
"quality_group": quality_group}
|
||||||
|
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
# Sort items based on layer_height
|
||||||
|
item_list = sorted(item_list, key = lambda x: x["layer_height"])
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
||||||
|
|
||||||
|
def _fetchLayerHeight(self, quality_group: "QualityGroup"):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
if not self._layer_height_unit:
|
||||||
|
unit = global_stack.definition.getProperty("layer_height", "unit")
|
||||||
|
if not unit:
|
||||||
|
unit = ""
|
||||||
|
self._layer_height_unit = unit
|
||||||
|
|
||||||
|
default_layer_height = global_stack.definition.getProperty("layer_height", "value")
|
||||||
|
|
||||||
|
# Get layer_height from the quality profile for the GlobalStack
|
||||||
|
container = quality_group.node_for_global.getContainer()
|
||||||
|
|
||||||
|
layer_height = default_layer_height
|
||||||
|
if container.hasProperty("layer_height", "value"):
|
||||||
|
layer_height = container.getProperty("layer_height", "value")
|
||||||
|
else:
|
||||||
|
# Look for layer_height in the GlobalStack from material -> definition
|
||||||
|
container = global_stack.definition
|
||||||
|
if container.hasProperty("layer_height", "value"):
|
||||||
|
layer_height = container.getProperty("layer_height", "value")
|
||||||
|
return float(layer_height)
|
159
cura/Machines/Models/QualitySettingsModel.py
Normal file
159
cura/Machines/Models/QualitySettingsModel.py
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# This model is used to show details settings of the selected quality in the quality management page.
|
||||||
|
#
|
||||||
|
class QualitySettingsModel(ListModel):
|
||||||
|
KeyRole = Qt.UserRole + 1
|
||||||
|
LabelRole = Qt.UserRole + 2
|
||||||
|
UnitRole = Qt.UserRole + 3
|
||||||
|
ProfileValueRole = Qt.UserRole + 4
|
||||||
|
ProfileValueSourceRole = Qt.UserRole + 5
|
||||||
|
UserValueRole = Qt.UserRole + 6
|
||||||
|
CategoryRole = Qt.UserRole + 7
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent = parent)
|
||||||
|
|
||||||
|
self.addRoleName(self.KeyRole, "key")
|
||||||
|
self.addRoleName(self.LabelRole, "label")
|
||||||
|
self.addRoleName(self.UnitRole, "unit")
|
||||||
|
self.addRoleName(self.ProfileValueRole, "profile_value")
|
||||||
|
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
|
||||||
|
self.addRoleName(self.UserValueRole, "user_value")
|
||||||
|
self.addRoleName(self.CategoryRole, "category")
|
||||||
|
|
||||||
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._quality_manager = self._application.getQualityManager()
|
||||||
|
|
||||||
|
self._selected_position = "" # empty string means GlobalStack
|
||||||
|
# strings such as "0", "1", etc. mean extruder positions
|
||||||
|
self._selected_quality_item = None # The selected quality in the quality management page
|
||||||
|
self._i18n_catalog = None
|
||||||
|
|
||||||
|
self._quality_manager.qualitiesUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
selectedPositionChanged = pyqtSignal()
|
||||||
|
selectedQualityItemChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def setSelectedPosition(self, selected_position):
|
||||||
|
if selected_position != self._selected_position:
|
||||||
|
self._selected_position = selected_position
|
||||||
|
self.selectedPositionChanged.emit()
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
@pyqtProperty(str, fset = setSelectedPosition, notify = selectedPositionChanged)
|
||||||
|
def selectedPosition(self):
|
||||||
|
return self._selected_position
|
||||||
|
|
||||||
|
def setSelectedQualityItem(self, selected_quality_item):
|
||||||
|
if selected_quality_item != self._selected_quality_item:
|
||||||
|
self._selected_quality_item = selected_quality_item
|
||||||
|
self.selectedQualityItemChanged.emit()
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
@pyqtProperty("QVariantMap", fset = setSelectedQualityItem, notify = selectedQualityItemChanged)
|
||||||
|
def selectedQualityItem(self):
|
||||||
|
return self._selected_quality_item
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
if self._selected_quality_item is None:
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
items = []
|
||||||
|
|
||||||
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
|
definition_container = global_container_stack.definition
|
||||||
|
|
||||||
|
quality_group = self._selected_quality_item["quality_group"]
|
||||||
|
quality_changes_group = self._selected_quality_item["quality_changes_group"]
|
||||||
|
|
||||||
|
if self._selected_position == "":
|
||||||
|
quality_node = quality_group.node_for_global
|
||||||
|
else:
|
||||||
|
quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
|
||||||
|
settings_keys = quality_group.getAllKeys()
|
||||||
|
quality_containers = [quality_node.getContainer()]
|
||||||
|
|
||||||
|
# Here, if the user has selected a quality changes, then "quality_changes_group" will not be None, and we fetch
|
||||||
|
# the settings in that quality_changes_group.
|
||||||
|
if quality_changes_group is not None:
|
||||||
|
if self._selected_position == "":
|
||||||
|
quality_changes_node = quality_changes_group.node_for_global
|
||||||
|
else:
|
||||||
|
quality_changes_node = quality_changes_group.nodes_for_extruders.get(self._selected_position)
|
||||||
|
if quality_changes_node is not None: # it can be None if number of extruders are changed during runtime
|
||||||
|
try:
|
||||||
|
quality_containers.insert(0, quality_changes_node.getContainer())
|
||||||
|
except:
|
||||||
|
# FIXME: This is to prevent incomplete update of QualityManager
|
||||||
|
Logger.logException("d", "Failed to get container for quality changes node %s", quality_changes_node)
|
||||||
|
return
|
||||||
|
settings_keys.update(quality_changes_group.getAllKeys())
|
||||||
|
|
||||||
|
# We iterate over all definitions instead of settings in a quality/qualtiy_changes group is because in the GUI,
|
||||||
|
# the settings are grouped together by categories, and we had to go over all the definitions to figure out
|
||||||
|
# which setting belongs in which category.
|
||||||
|
current_category = ""
|
||||||
|
for definition in definition_container.findDefinitions():
|
||||||
|
if definition.type == "category":
|
||||||
|
current_category = definition.label
|
||||||
|
if self._i18n_catalog:
|
||||||
|
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
|
||||||
|
continue
|
||||||
|
|
||||||
|
profile_value = None
|
||||||
|
profile_value_source = ""
|
||||||
|
for quality_container in quality_containers:
|
||||||
|
new_value = quality_container.getProperty(definition.key, "value")
|
||||||
|
|
||||||
|
if new_value is not None:
|
||||||
|
profile_value_source = quality_container.getMetaDataEntry("type")
|
||||||
|
profile_value = new_value
|
||||||
|
|
||||||
|
# Global tab should use resolve (if there is one)
|
||||||
|
if self._selected_position == "":
|
||||||
|
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
|
||||||
|
if resolve_value is not None and definition.key in settings_keys:
|
||||||
|
profile_value = resolve_value
|
||||||
|
|
||||||
|
if profile_value is not None:
|
||||||
|
break
|
||||||
|
|
||||||
|
if not self._selected_position:
|
||||||
|
user_value = global_container_stack.userChanges.getProperty(definition.key, "value")
|
||||||
|
else:
|
||||||
|
extruder_stack = global_container_stack.extruders[self._selected_position]
|
||||||
|
user_value = extruder_stack.userChanges.getProperty(definition.key, "value")
|
||||||
|
|
||||||
|
if profile_value is None and user_value is None:
|
||||||
|
continue
|
||||||
|
|
||||||
|
label = definition.label
|
||||||
|
if self._i18n_catalog:
|
||||||
|
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
||||||
|
|
||||||
|
items.append({
|
||||||
|
"key": definition.key,
|
||||||
|
"label": label,
|
||||||
|
"unit": definition.unit,
|
||||||
|
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
||||||
|
"profile_value_source": profile_value_source,
|
||||||
|
"user_value": "" if user_value is None else str(user_value),
|
||||||
|
"category": current_category
|
||||||
|
})
|
||||||
|
|
||||||
|
self.setItems(items)
|
0
cura/Machines/Models/__init__.py
Normal file
0
cura/Machines/Models/__init__.py
Normal file
53
cura/Machines/QualityChangesGroup.py
Normal file
53
cura/Machines/QualityChangesGroup.py
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
from .QualityGroup import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
|
class QualityChangesGroup(QualityGroup):
|
||||||
|
|
||||||
|
def __init__(self, name: str, quality_type: str, parent = None):
|
||||||
|
super().__init__(name, quality_type, parent)
|
||||||
|
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||||
|
|
||||||
|
def addNode(self, node: "QualityNode"):
|
||||||
|
# TODO: in 3.2 and earlier, a quality_changes container may have a field called "extruder" which contains the
|
||||||
|
# extruder definition ID it belongs to. But, in fact, we only need to know the following things:
|
||||||
|
# 1. which machine a custom profile is suitable for,
|
||||||
|
# 2. if this profile is for the GlobalStack,
|
||||||
|
# 3. if this profile is for an ExtruderStack and which one (the position).
|
||||||
|
#
|
||||||
|
# So, it is preferred to have a field like this:
|
||||||
|
# extruder_position = 1
|
||||||
|
# instead of this:
|
||||||
|
# extruder = custom_extruder_1
|
||||||
|
#
|
||||||
|
# An upgrade needs to be done if we want to do it this way. Before that, we use the extruder's definition
|
||||||
|
# to figure out its position.
|
||||||
|
#
|
||||||
|
extruder_definition_id = node.metadata.get("extruder")
|
||||||
|
if extruder_definition_id:
|
||||||
|
metadata_list = self._container_registry.findDefinitionContainersMetadata(id = extruder_definition_id)
|
||||||
|
if not metadata_list:
|
||||||
|
raise RuntimeError("%s cannot get metadata for extruder definition [%s]" %
|
||||||
|
(self, extruder_definition_id))
|
||||||
|
extruder_definition_metadata = metadata_list[0]
|
||||||
|
extruder_position = str(extruder_definition_metadata["position"])
|
||||||
|
|
||||||
|
if extruder_position in self.nodes_for_extruders:
|
||||||
|
raise RuntimeError("%s tries to overwrite the existing nodes_for_extruders position [%s] %s with %s" %
|
||||||
|
(self, extruder_position, self.node_for_global, node))
|
||||||
|
|
||||||
|
self.nodes_for_extruders[extruder_position] = node
|
||||||
|
|
||||||
|
else:
|
||||||
|
# This is a quality_changes for the GlobalStack
|
||||||
|
if self.node_for_global is not None:
|
||||||
|
raise RuntimeError("%s tries to overwrite the existing node_for_global %s with %s" %
|
||||||
|
(self, self.node_for_global, node))
|
||||||
|
self.node_for_global = node
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return "%s[<%s>, available = %s]" % (self.__class__.__name__, self.name, self.is_available)
|
50
cura/Machines/QualityGroup.py
Normal file
50
cura/Machines/QualityGroup.py
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional, List
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
|
||||||
|
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
|
||||||
|
# must be applied to all stacks in a machine, although each stack can have different containers. Use an Ultimaker 3
|
||||||
|
# as an example, suppose we choose quality type "normal", the actual InstanceContainers on each stack may look
|
||||||
|
# as below:
|
||||||
|
# GlobalStack ExtruderStack 1 ExtruderStack 2
|
||||||
|
# quality container: um3_global_normal um3_aa04_pla_normal um3_aa04_abs_normal
|
||||||
|
#
|
||||||
|
# This QualityGroup is mainly used in quality and quality_changes to group the containers that can be applied to
|
||||||
|
# a machine, so when a quality/custom quality is selected, the container can be directly applied to each stack instead
|
||||||
|
# of looking them up again.
|
||||||
|
#
|
||||||
|
class QualityGroup(QObject):
|
||||||
|
|
||||||
|
def __init__(self, name: str, quality_type: str, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self.name = name
|
||||||
|
self.node_for_global = None # type: Optional["QualityGroup"]
|
||||||
|
self.nodes_for_extruders = dict() # position str -> QualityGroup
|
||||||
|
self.quality_type = quality_type
|
||||||
|
self.is_available = False
|
||||||
|
|
||||||
|
@pyqtSlot(result = str)
|
||||||
|
def getName(self) -> str:
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def getAllKeys(self) -> set:
|
||||||
|
result = set()
|
||||||
|
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
||||||
|
if node is None:
|
||||||
|
continue
|
||||||
|
result.update(node.getContainer().getAllKeys())
|
||||||
|
return result
|
||||||
|
|
||||||
|
def getAllNodes(self) -> List["QualityGroup"]:
|
||||||
|
result = []
|
||||||
|
if self.node_for_global is not None:
|
||||||
|
result.append(self.node_for_global)
|
||||||
|
for extruder_node in self.nodes_for_extruders.values():
|
||||||
|
result.append(extruder_node)
|
||||||
|
return result
|
491
cura/Machines/QualityManager.py
Normal file
491
cura/Machines/QualityManager.py
Normal file
|
@ -0,0 +1,491 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Optional
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Util import parseBool
|
||||||
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
from .QualityGroup import QualityGroup
|
||||||
|
from .QualityNode import QualityNode
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
from .QualityChangesGroup import QualityChangesGroup
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Similar to MaterialManager, QualityManager maintains a number of maps and trees for quality profile lookup.
|
||||||
|
# The models GUI and QML use are now only dependent on the QualityManager. That means as long as the data in
|
||||||
|
# QualityManager gets updated correctly, the GUI models should be updated correctly too, and the same goes for GUI.
|
||||||
|
#
|
||||||
|
# For now, updating the lookup maps and trees here is very simple: we discard the old data completely and recreate them
|
||||||
|
# again. This means the update is exactly the same as initialization. There are performance concerns about this approach
|
||||||
|
# but so far the creation of the tables and maps is very fast and there is no noticeable slowness, we keep it like this
|
||||||
|
# because it's simple.
|
||||||
|
#
|
||||||
|
class QualityManager(QObject):
|
||||||
|
|
||||||
|
qualitiesUpdated = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, container_registry, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
self._material_manager = self._application.getMaterialManager()
|
||||||
|
self._container_registry = container_registry
|
||||||
|
|
||||||
|
self._empty_quality_container = self._application.empty_quality_container
|
||||||
|
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
||||||
|
|
||||||
|
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||||
|
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||||
|
|
||||||
|
self._default_machine_definition_id = "fdmprinter"
|
||||||
|
|
||||||
|
self._container_registry.containerMetaDataChanged.connect(self._onContainerMetadataChanged)
|
||||||
|
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||||
|
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||||
|
|
||||||
|
# When a custom quality gets added/imported, there can be more than one InstanceContainers. In those cases,
|
||||||
|
# we don't want to react on every container/metadata changed signal. The timer here is to buffer it a bit so
|
||||||
|
# we don't react too many time.
|
||||||
|
self._update_timer = QTimer(self)
|
||||||
|
self._update_timer.setInterval(300)
|
||||||
|
self._update_timer.setSingleShot(True)
|
||||||
|
self._update_timer.timeout.connect(self._updateMaps)
|
||||||
|
|
||||||
|
def initialize(self):
|
||||||
|
# Initialize the lookup tree for quality profiles with following structure:
|
||||||
|
# <machine> -> <variant> -> <material>
|
||||||
|
# -> <material>
|
||||||
|
|
||||||
|
self._machine_variant_material_quality_type_to_quality_dict = {} # for quality lookup
|
||||||
|
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
||||||
|
|
||||||
|
quality_metadata_list = self._container_registry.findContainersMetadata(type = "quality")
|
||||||
|
for metadata in quality_metadata_list:
|
||||||
|
if metadata["id"] == "empty_quality":
|
||||||
|
continue
|
||||||
|
|
||||||
|
definition_id = metadata["definition"]
|
||||||
|
quality_type = metadata["quality_type"]
|
||||||
|
|
||||||
|
root_material_id = metadata.get("material")
|
||||||
|
variant_name = metadata.get("variant")
|
||||||
|
is_global_quality = metadata.get("global_quality", False)
|
||||||
|
is_global_quality = is_global_quality or (root_material_id is None and variant_name is None)
|
||||||
|
|
||||||
|
# Sanity check: material+variant and is_global_quality cannot be present at the same time
|
||||||
|
if is_global_quality and (root_material_id or variant_name):
|
||||||
|
raise RuntimeError("Quality profile [%s] contains invalid data: it is a global quality but contains 'material' and 'nozzle' info." % metadata["id"])
|
||||||
|
|
||||||
|
if definition_id not in self._machine_variant_material_quality_type_to_quality_dict:
|
||||||
|
self._machine_variant_material_quality_type_to_quality_dict[definition_id] = QualityNode()
|
||||||
|
machine_node = self._machine_variant_material_quality_type_to_quality_dict[definition_id]
|
||||||
|
|
||||||
|
if is_global_quality:
|
||||||
|
# For global qualities, save data in the machine node
|
||||||
|
machine_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if variant_name is not None:
|
||||||
|
# If variant_name is specified in the quality/quality_changes profile, check if material is specified,
|
||||||
|
# too.
|
||||||
|
if variant_name not in machine_node.children_map:
|
||||||
|
machine_node.children_map[variant_name] = QualityNode()
|
||||||
|
variant_node = machine_node.children_map[variant_name]
|
||||||
|
|
||||||
|
if root_material_id is None:
|
||||||
|
# If only variant_name is specified but material is not, add the quality/quality_changes metadata
|
||||||
|
# into the current variant node.
|
||||||
|
variant_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
else:
|
||||||
|
# If only variant_name and material are both specified, go one level deeper: create a material node
|
||||||
|
# under the current variant node, and then add the quality/quality_changes metadata into the
|
||||||
|
# material node.
|
||||||
|
if root_material_id not in variant_node.children_map:
|
||||||
|
variant_node.children_map[root_material_id] = QualityNode()
|
||||||
|
material_node = variant_node.children_map[root_material_id]
|
||||||
|
|
||||||
|
material_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# If variant_name is not specified, check if material is specified.
|
||||||
|
if root_material_id is not None:
|
||||||
|
if root_material_id not in machine_node.children_map:
|
||||||
|
machine_node.children_map[root_material_id] = QualityNode()
|
||||||
|
material_node = machine_node.children_map[root_material_id]
|
||||||
|
|
||||||
|
material_node.addQualityMetadata(quality_type, metadata)
|
||||||
|
|
||||||
|
# Initialize the lookup tree for quality_changes profiles with following structure:
|
||||||
|
# <machine> -> <quality_type> -> <name>
|
||||||
|
quality_changes_metadata_list = self._container_registry.findContainersMetadata(type = "quality_changes")
|
||||||
|
for metadata in quality_changes_metadata_list:
|
||||||
|
if metadata["id"] == "empty_quality_changes":
|
||||||
|
continue
|
||||||
|
|
||||||
|
machine_definition_id = metadata["definition"]
|
||||||
|
quality_type = metadata["quality_type"]
|
||||||
|
|
||||||
|
if machine_definition_id not in self._machine_quality_type_to_quality_changes_dict:
|
||||||
|
self._machine_quality_type_to_quality_changes_dict[machine_definition_id] = QualityNode()
|
||||||
|
machine_node = self._machine_quality_type_to_quality_changes_dict[machine_definition_id]
|
||||||
|
machine_node.addQualityChangesMetadata(quality_type, metadata)
|
||||||
|
|
||||||
|
Logger.log("d", "Lookup tables updated.")
|
||||||
|
self.qualitiesUpdated.emit()
|
||||||
|
|
||||||
|
def _updateMaps(self):
|
||||||
|
self.initialize()
|
||||||
|
|
||||||
|
def _onContainerMetadataChanged(self, container):
|
||||||
|
self._onContainerChanged(container)
|
||||||
|
|
||||||
|
def _onContainerChanged(self, container):
|
||||||
|
container_type = container.getMetaDataEntry("type")
|
||||||
|
if container_type not in ("quality", "quality_changes"):
|
||||||
|
return
|
||||||
|
|
||||||
|
# update the cache table
|
||||||
|
self._update_timer.start()
|
||||||
|
|
||||||
|
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
|
||||||
|
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
|
||||||
|
used_extruders = set()
|
||||||
|
# TODO: This will change after the Machine refactoring
|
||||||
|
for i in range(machine.getProperty("machine_extruder_count", "value")):
|
||||||
|
used_extruders.add(str(i))
|
||||||
|
|
||||||
|
# Update the "is_available" flag for each quality group.
|
||||||
|
for quality_group in quality_group_list:
|
||||||
|
is_available = True
|
||||||
|
if quality_group.node_for_global is None:
|
||||||
|
is_available = False
|
||||||
|
if is_available:
|
||||||
|
for position in used_extruders:
|
||||||
|
if position not in quality_group.nodes_for_extruders:
|
||||||
|
is_available = False
|
||||||
|
break
|
||||||
|
|
||||||
|
quality_group.is_available = is_available
|
||||||
|
|
||||||
|
# Returns a dict of "custom profile name" -> QualityChangesGroup
|
||||||
|
def getQualityChangesGroups(self, machine: "GlobalStack") -> dict:
|
||||||
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
|
||||||
|
|
||||||
|
machine_node = self._machine_quality_type_to_quality_changes_dict.get(machine_definition_id)
|
||||||
|
if not machine_node:
|
||||||
|
Logger.log("i", "Cannot find node for machine def [%s] in QualityChanges lookup table", machine_definition_id)
|
||||||
|
return dict()
|
||||||
|
|
||||||
|
# Update availability for each QualityChangesGroup:
|
||||||
|
# A custom profile is always available as long as the quality_type it's based on is available
|
||||||
|
quality_group_dict = self.getQualityGroups(machine)
|
||||||
|
available_quality_type_list = [qt for qt, qg in quality_group_dict.items() if qg.is_available]
|
||||||
|
|
||||||
|
# Iterate over all quality_types in the machine node
|
||||||
|
quality_changes_group_dict = dict()
|
||||||
|
for quality_type, quality_changes_node in machine_node.quality_type_map.items():
|
||||||
|
for quality_changes_name, quality_changes_group in quality_changes_node.children_map.items():
|
||||||
|
quality_changes_group_dict[quality_changes_name] = quality_changes_group
|
||||||
|
quality_changes_group.is_available = quality_type in available_quality_type_list
|
||||||
|
|
||||||
|
return quality_changes_group_dict
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets all quality groups for the given machine. Both available and none available ones will be included.
|
||||||
|
# It returns a dictionary with "quality_type"s as keys and "QualityGroup"s as values.
|
||||||
|
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
|
||||||
|
# For more details, see QualityGroup.
|
||||||
|
#
|
||||||
|
def getQualityGroups(self, machine: "GlobalStack") -> dict:
|
||||||
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
|
||||||
|
|
||||||
|
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
|
||||||
|
has_variant_materials = parseBool(machine.getMetaDataEntry("has_variant_materials", False))
|
||||||
|
|
||||||
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
|
# (1) the machine-specific node
|
||||||
|
# (2) the generic node
|
||||||
|
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||||
|
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
||||||
|
nodes_to_check = [machine_node, default_machine_node]
|
||||||
|
|
||||||
|
# Iterate over all quality_types in the machine node
|
||||||
|
quality_group_dict = {}
|
||||||
|
for node in nodes_to_check:
|
||||||
|
if node and node.quality_type_map:
|
||||||
|
# Only include global qualities
|
||||||
|
if has_variant_materials:
|
||||||
|
quality_node = list(node.quality_type_map.values())[0]
|
||||||
|
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
||||||
|
if not is_global_quality:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
|
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||||
|
quality_group.node_for_global = quality_node
|
||||||
|
quality_group_dict[quality_type] = quality_group
|
||||||
|
break
|
||||||
|
|
||||||
|
# Iterate over all extruders to find quality containers for each extruder
|
||||||
|
for position, extruder in machine.extruders.items():
|
||||||
|
variant_name = None
|
||||||
|
if extruder.variant.getId() != "empty_variant":
|
||||||
|
variant_name = extruder.variant.getName()
|
||||||
|
|
||||||
|
# This is a list of root material IDs to use for searching for suitable quality profiles.
|
||||||
|
# The root material IDs in this list are in prioritized order.
|
||||||
|
root_material_id_list = []
|
||||||
|
has_material = False # flag indicating whether this extruder has a material assigned
|
||||||
|
if extruder.material.getId() != "empty_material":
|
||||||
|
has_material = True
|
||||||
|
root_material_id = extruder.material.getMetaDataEntry("base_file")
|
||||||
|
# Convert possible generic_pla_175 -> generic_pla
|
||||||
|
root_material_id = self._material_manager.getRootMaterialIDWithoutDiameter(root_material_id)
|
||||||
|
root_material_id_list.append(root_material_id)
|
||||||
|
|
||||||
|
# Also try to get the fallback material
|
||||||
|
material_type = extruder.material.getMetaDataEntry("material")
|
||||||
|
fallback_root_material_id = self._material_manager.getFallbackMaterialIdByMaterialType(material_type)
|
||||||
|
if fallback_root_material_id:
|
||||||
|
root_material_id_list.append(fallback_root_material_id)
|
||||||
|
|
||||||
|
# Here we construct a list of nodes we want to look for qualities with the highest priority first.
|
||||||
|
# The use case is that, when we look for qualities for a machine, we first want to search in the following
|
||||||
|
# order:
|
||||||
|
# 1. machine-variant-and-material-specific qualities if exist
|
||||||
|
# 2. machine-variant-specific qualities if exist
|
||||||
|
# 3. machine-material-specific qualities if exist
|
||||||
|
# 4. machine-specific qualities if exist
|
||||||
|
# 5. generic qualities if exist
|
||||||
|
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
|
||||||
|
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
|
||||||
|
# qualities from there.
|
||||||
|
nodes_to_check = []
|
||||||
|
|
||||||
|
if variant_name:
|
||||||
|
# In this case, we have both a specific variant and a specific material
|
||||||
|
variant_node = machine_node.getChildNode(variant_name)
|
||||||
|
if variant_node and has_material:
|
||||||
|
for root_material_id in root_material_id_list:
|
||||||
|
material_node = variant_node.getChildNode(root_material_id)
|
||||||
|
if material_node:
|
||||||
|
nodes_to_check.append(material_node)
|
||||||
|
break
|
||||||
|
nodes_to_check.append(variant_node)
|
||||||
|
|
||||||
|
# In this case, we only have a specific material but NOT a variant
|
||||||
|
if has_material:
|
||||||
|
for root_material_id in root_material_id_list:
|
||||||
|
material_node = machine_node.getChildNode(root_material_id)
|
||||||
|
if material_node:
|
||||||
|
nodes_to_check.append(material_node)
|
||||||
|
break
|
||||||
|
|
||||||
|
nodes_to_check += [machine_node, default_machine_node]
|
||||||
|
for node in nodes_to_check:
|
||||||
|
if node and node.quality_type_map:
|
||||||
|
if has_variant_materials:
|
||||||
|
# Only include variant qualities; skip non global qualities
|
||||||
|
quality_node = list(node.quality_type_map.values())[0]
|
||||||
|
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
||||||
|
if is_global_quality:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
|
if quality_type not in quality_group_dict:
|
||||||
|
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||||
|
quality_group_dict[quality_type] = quality_group
|
||||||
|
|
||||||
|
quality_group = quality_group_dict[quality_type]
|
||||||
|
quality_group.nodes_for_extruders[position] = quality_node
|
||||||
|
break
|
||||||
|
|
||||||
|
# Update availabilities for each quality group
|
||||||
|
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
||||||
|
|
||||||
|
return quality_group_dict
|
||||||
|
|
||||||
|
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
|
||||||
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
|
||||||
|
|
||||||
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
|
# (1) the machine-specific node
|
||||||
|
# (2) the generic node
|
||||||
|
machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||||
|
default_machine_node = self._machine_variant_material_quality_type_to_quality_dict.get(
|
||||||
|
self._default_machine_definition_id)
|
||||||
|
nodes_to_check = [machine_node, default_machine_node]
|
||||||
|
|
||||||
|
# Iterate over all quality_types in the machine node
|
||||||
|
quality_group_dict = dict()
|
||||||
|
for node in nodes_to_check:
|
||||||
|
if node and node.quality_type_map:
|
||||||
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
|
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
||||||
|
quality_group.node_for_global = quality_node
|
||||||
|
quality_group_dict[quality_type] = quality_group
|
||||||
|
break
|
||||||
|
|
||||||
|
return quality_group_dict
|
||||||
|
|
||||||
|
#
|
||||||
|
# Methods for GUI
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Remove the given quality changes group.
|
||||||
|
#
|
||||||
|
@pyqtSlot(QObject)
|
||||||
|
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
||||||
|
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||||
|
for node in quality_changes_group.getAllNodes():
|
||||||
|
self._container_registry.removeContainer(node.metadata["id"])
|
||||||
|
|
||||||
|
#
|
||||||
|
# Rename a set of quality changes containers. Returns the new name.
|
||||||
|
#
|
||||||
|
@pyqtSlot(QObject, str, result = str)
|
||||||
|
def renameQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", new_name: str) -> str:
|
||||||
|
Logger.log("i", "Renaming QualityChangesGroup[%s] to [%s]", quality_changes_group.name, new_name)
|
||||||
|
if new_name == quality_changes_group.name:
|
||||||
|
Logger.log("i", "QualityChangesGroup name [%s] unchanged.", quality_changes_group.name)
|
||||||
|
return new_name
|
||||||
|
|
||||||
|
new_name = self._container_registry.uniqueName(new_name)
|
||||||
|
for node in quality_changes_group.getAllNodes():
|
||||||
|
node.getContainer().setName(new_name)
|
||||||
|
|
||||||
|
quality_changes_group.name = new_name
|
||||||
|
|
||||||
|
self._application.getMachineManager().activeQualityChanged.emit()
|
||||||
|
self._application.getMachineManager().activeQualityGroupChanged.emit()
|
||||||
|
|
||||||
|
return new_name
|
||||||
|
|
||||||
|
#
|
||||||
|
# Duplicates the given quality.
|
||||||
|
#
|
||||||
|
@pyqtSlot(str, "QVariantMap")
|
||||||
|
def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
|
||||||
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
|
||||||
|
return
|
||||||
|
|
||||||
|
quality_group = quality_model_item["quality_group"]
|
||||||
|
quality_changes_group = quality_model_item["quality_changes_group"]
|
||||||
|
if quality_changes_group is None:
|
||||||
|
# create global quality changes only
|
||||||
|
new_quality_changes = self._createQualityChanges(quality_group.quality_type, quality_changes_name,
|
||||||
|
global_stack, extruder_id = None)
|
||||||
|
self._container_registry.addContainer(new_quality_changes)
|
||||||
|
else:
|
||||||
|
new_name = self._container_registry.uniqueName(quality_changes_name)
|
||||||
|
for node in quality_changes_group.getAllNodes():
|
||||||
|
container = node.getContainer()
|
||||||
|
new_id = self._container_registry.uniqueName(container.getId())
|
||||||
|
self._container_registry.addContainer(container.duplicate(new_id, new_name))
|
||||||
|
|
||||||
|
## Create quality changes containers from the user containers in the active stacks.
|
||||||
|
#
|
||||||
|
# This will go through the global and extruder stacks and create quality_changes containers from
|
||||||
|
# the user containers in each stack. These then replace the quality_changes containers in the
|
||||||
|
# stack and clear the user settings.
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def createQualityChanges(self, base_name):
|
||||||
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
|
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
if not global_stack:
|
||||||
|
return
|
||||||
|
|
||||||
|
active_quality_name = machine_manager.activeQualityOrQualityChangesName
|
||||||
|
if active_quality_name == "":
|
||||||
|
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
|
||||||
|
return
|
||||||
|
|
||||||
|
machine_manager.blurSettings.emit()
|
||||||
|
if base_name is None or base_name == "":
|
||||||
|
base_name = active_quality_name
|
||||||
|
unique_name = self._container_registry.uniqueName(base_name)
|
||||||
|
|
||||||
|
# Go through the active stacks and create quality_changes containers from the user containers.
|
||||||
|
stack_list = [global_stack] + list(global_stack.extruders.values())
|
||||||
|
for stack in stack_list:
|
||||||
|
user_container = stack.userChanges
|
||||||
|
quality_container = stack.quality
|
||||||
|
quality_changes_container = stack.qualityChanges
|
||||||
|
if not quality_container or not quality_changes_container:
|
||||||
|
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
||||||
|
continue
|
||||||
|
|
||||||
|
extruder_definition_id = None
|
||||||
|
if isinstance(stack, ExtruderStack):
|
||||||
|
extruder_definition_id = stack.definition.getId()
|
||||||
|
quality_type = quality_container.getMetaDataEntry("quality_type")
|
||||||
|
new_changes = self._createQualityChanges(quality_type, unique_name, global_stack, extruder_definition_id)
|
||||||
|
from cura.Settings.ContainerManager import ContainerManager
|
||||||
|
ContainerManager.getInstance()._performMerge(new_changes, quality_changes_container, clear_settings = False)
|
||||||
|
ContainerManager.getInstance()._performMerge(new_changes, user_container)
|
||||||
|
|
||||||
|
self._container_registry.addContainer(new_changes)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Create a quality changes container with the given setup.
|
||||||
|
#
|
||||||
|
def _createQualityChanges(self, quality_type: str, new_name: str, machine: "GlobalStack",
|
||||||
|
extruder_id: Optional[str]) -> "InstanceContainer":
|
||||||
|
base_id = machine.definition.getId() if extruder_id is None else extruder_id
|
||||||
|
new_id = base_id + "_" + new_name
|
||||||
|
new_id = new_id.lower().replace(" ", "_")
|
||||||
|
new_id = self._container_registry.uniqueName(new_id)
|
||||||
|
|
||||||
|
# Create a new quality_changes container for the quality.
|
||||||
|
quality_changes = InstanceContainer(new_id)
|
||||||
|
quality_changes.setName(new_name)
|
||||||
|
quality_changes.addMetaDataEntry("type", "quality_changes")
|
||||||
|
quality_changes.addMetaDataEntry("quality_type", quality_type)
|
||||||
|
|
||||||
|
# If we are creating a container for an extruder, ensure we add that to the container
|
||||||
|
if extruder_id is not None:
|
||||||
|
quality_changes.addMetaDataEntry("extruder", extruder_id)
|
||||||
|
|
||||||
|
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
||||||
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine)
|
||||||
|
quality_changes.setDefinition(machine_definition_id)
|
||||||
|
|
||||||
|
quality_changes.addMetaDataEntry("setting_version", self._application.SettingVersion)
|
||||||
|
return quality_changes
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets the machine definition ID that can be used to search for Quality containers that are suitable for the given
|
||||||
|
# machine. The rule is as follows:
|
||||||
|
# 1. By default, the machine definition ID for quality container search will be "fdmprinter", which is the generic
|
||||||
|
# machine.
|
||||||
|
# 2. If a machine has its own machine quality (with "has_machine_quality = True"), we should use the given machine's
|
||||||
|
# own machine definition ID for quality search.
|
||||||
|
# Example: for an Ultimaker 3, the definition ID should be "ultimaker3".
|
||||||
|
# 3. When condition (2) is met, AND the machine has "quality_definition" defined in its definition file, then the
|
||||||
|
# definition ID specified in "quality_definition" should be used.
|
||||||
|
# Example: for an Ultimaker 3 Extended, it has "quality_definition = ultimaker3". This means Ultimaker 3 Extended
|
||||||
|
# shares the same set of qualities profiles as Ultimaker 3.
|
||||||
|
#
|
||||||
|
def getMachineDefinitionIDForQualitySearch(machine: "GlobalStack", default_definition_id: str = "fdmprinter") -> str:
|
||||||
|
machine_definition_id = default_definition_id
|
||||||
|
if parseBool(machine.getMetaDataEntry("has_machine_quality", False)):
|
||||||
|
# Only use the machine's own quality definition ID if this machine has machine quality.
|
||||||
|
machine_definition_id = machine.getMetaDataEntry("quality_definition")
|
||||||
|
if machine_definition_id is None:
|
||||||
|
machine_definition_id = machine.definition.getId()
|
||||||
|
|
||||||
|
return machine_definition_id
|
35
cura/Machines/QualityNode.py
Normal file
35
cura/Machines/QualityNode.py
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from .ContainerNode import ContainerNode
|
||||||
|
from .QualityChangesGroup import QualityChangesGroup
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# QualityNode is used for BOTH quality and quality_changes containers.
|
||||||
|
#
|
||||||
|
class QualityNode(ContainerNode):
|
||||||
|
|
||||||
|
def __init__(self, metadata: Optional[dict] = None):
|
||||||
|
super().__init__(metadata = metadata)
|
||||||
|
self.quality_type_map = {} # quality_type -> QualityNode for InstanceContainer
|
||||||
|
|
||||||
|
def addQualityMetadata(self, quality_type: str, metadata: dict):
|
||||||
|
if quality_type not in self.quality_type_map:
|
||||||
|
self.quality_type_map[quality_type] = QualityNode(metadata)
|
||||||
|
|
||||||
|
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
|
||||||
|
return self.quality_type_map.get(quality_type)
|
||||||
|
|
||||||
|
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
|
||||||
|
if quality_type not in self.quality_type_map:
|
||||||
|
self.quality_type_map[quality_type] = QualityNode()
|
||||||
|
quality_type_node = self.quality_type_map[quality_type]
|
||||||
|
|
||||||
|
name = metadata["name"]
|
||||||
|
if name not in quality_type_node.children_map:
|
||||||
|
quality_type_node.children_map[name] = QualityChangesGroup(name, quality_type)
|
||||||
|
quality_changes_group = quality_type_node.children_map[name]
|
||||||
|
quality_changes_group.addNode(QualityNode(metadata))
|
111
cura/Machines/VariantManager.py
Normal file
111
cura/Machines/VariantManager.py
Normal file
|
@ -0,0 +1,111 @@
|
||||||
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
|
from collections import OrderedDict
|
||||||
|
from typing import Optional, TYPE_CHECKING
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
|
|
||||||
|
|
||||||
|
class VariantType(Enum):
|
||||||
|
BUILD_PLATE = "buildplate"
|
||||||
|
NOZZLE = "nozzle"
|
||||||
|
|
||||||
|
|
||||||
|
ALL_VARIANT_TYPES = (VariantType.BUILD_PLATE, VariantType.NOZZLE)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# VariantManager is THE place to look for a specific variant. It maintains a variant lookup table with the following
|
||||||
|
# structure:
|
||||||
|
#
|
||||||
|
# [machine_definition_id] -> [variant_type] -> [variant_name] -> ContainerNode(metadata / container)
|
||||||
|
# Example: "ultimaker3" -> "buildplate" -> "Glass" (if present) -> ContainerNode
|
||||||
|
# -> ...
|
||||||
|
# -> "nozzle" -> "AA 0.4"
|
||||||
|
# -> "BB 0.8"
|
||||||
|
# -> ...
|
||||||
|
#
|
||||||
|
# Note that the "container" field is not loaded in the beginning because it would defeat the purpose of lazy-loading.
|
||||||
|
# A container is loaded when getVariant() is called to load a variant InstanceContainer.
|
||||||
|
#
|
||||||
|
class VariantManager:
|
||||||
|
|
||||||
|
def __init__(self, container_registry):
|
||||||
|
self._container_registry = container_registry # type: ContainerRegistry
|
||||||
|
|
||||||
|
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
|
||||||
|
|
||||||
|
self._exclude_variant_id_list = ["empty_variant"]
|
||||||
|
|
||||||
|
#
|
||||||
|
# Initializes the VariantManager including:
|
||||||
|
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
|
||||||
|
#
|
||||||
|
def initialize(self):
|
||||||
|
self._machine_to_variant_dict_map = OrderedDict()
|
||||||
|
|
||||||
|
# Cache all variants from the container registry to a variant map for better searching and organization.
|
||||||
|
variant_metadata_list = self._container_registry.findContainersMetadata(type = "variant")
|
||||||
|
for variant_metadata in variant_metadata_list:
|
||||||
|
if variant_metadata["id"] in self._exclude_variant_id_list:
|
||||||
|
Logger.log("d", "Exclude variant [%s]", variant_metadata["id"])
|
||||||
|
continue
|
||||||
|
|
||||||
|
variant_name = variant_metadata["name"]
|
||||||
|
variant_definition = variant_metadata["definition"]
|
||||||
|
if variant_definition not in self._machine_to_variant_dict_map:
|
||||||
|
self._machine_to_variant_dict_map[variant_definition] = OrderedDict()
|
||||||
|
for variant_type in ALL_VARIANT_TYPES:
|
||||||
|
self._machine_to_variant_dict_map[variant_definition][variant_type] = dict()
|
||||||
|
|
||||||
|
variant_type = variant_metadata["hardware_type"]
|
||||||
|
variant_type = VariantType(variant_type)
|
||||||
|
variant_dict = self._machine_to_variant_dict_map[variant_definition][variant_type]
|
||||||
|
if variant_name in variant_dict:
|
||||||
|
# ERROR: duplicated variant name.
|
||||||
|
raise RuntimeError("Found duplicated variant name [%s], type [%s] for machine [%s]" %
|
||||||
|
(variant_name, variant_type, variant_definition))
|
||||||
|
|
||||||
|
variant_dict[variant_name] = ContainerNode(metadata = variant_metadata)
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets the variant InstanceContainer with the given information.
|
||||||
|
# Almost the same as getVariantMetadata() except that this returns an InstanceContainer if present.
|
||||||
|
#
|
||||||
|
def getVariantNode(self, machine_definition_id: str, variant_name: str,
|
||||||
|
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> Optional["ContainerNode"]:
|
||||||
|
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
|
||||||
|
|
||||||
|
def getVariantNodes(self, machine: "GlobalStack",
|
||||||
|
variant_type: Optional["VariantType"] = VariantType.NOZZLE) -> dict:
|
||||||
|
machine_definition_id = machine.definition.getId()
|
||||||
|
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets the default variant for the given machine definition.
|
||||||
|
#
|
||||||
|
def getDefaultVariantNode(self, machine_definition: "DefinitionContainer",
|
||||||
|
variant_type: VariantType) -> Optional["ContainerNode"]:
|
||||||
|
machine_definition_id = machine_definition.getId()
|
||||||
|
preferred_variant_name = None
|
||||||
|
if variant_type == VariantType.BUILD_PLATE:
|
||||||
|
if parseBool(machine_definition.getMetaDataEntry("has_variant_buildplates", False)):
|
||||||
|
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_buildplate_name")
|
||||||
|
else:
|
||||||
|
if parseBool(machine_definition.getMetaDataEntry("has_variants", False)):
|
||||||
|
preferred_variant_name = machine_definition.getMetaDataEntry("preferred_variant_name")
|
||||||
|
|
||||||
|
node = None
|
||||||
|
if preferred_variant_name:
|
||||||
|
node = self.getVariantNode(machine_definition_id, preferred_variant_name, variant_type)
|
||||||
|
return node
|
0
cura/Machines/__init__.py
Normal file
0
cura/Machines/__init__.py
Normal file
|
@ -19,6 +19,8 @@ class ObjectsModel(ListModel):
|
||||||
|
|
||||||
self._build_plate_number = -1
|
self._build_plate_number = -1
|
||||||
|
|
||||||
|
self._stacks_have_errors = None # type:Optional[bool]
|
||||||
|
|
||||||
def setActiveBuildPlate(self, nr):
|
def setActiveBuildPlate(self, nr):
|
||||||
self._build_plate_number = nr
|
self._build_plate_number = nr
|
||||||
self._update()
|
self._update()
|
||||||
|
@ -67,3 +69,11 @@ class ObjectsModel(ListModel):
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def createObjectsModel():
|
def createObjectsModel():
|
||||||
return ObjectsModel()
|
return ObjectsModel()
|
||||||
|
|
||||||
|
## Check if none of the model's stacks contain error states
|
||||||
|
# The setting applied for the settings per model
|
||||||
|
def stacksHaveErrors(self) -> bool:
|
||||||
|
return bool(self._stacks_have_errors)
|
||||||
|
|
||||||
|
def setStacksHaveErrors(self, value):
|
||||||
|
self._stacks_have_errors = value
|
|
@ -1,27 +1,22 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty
|
from typing import Dict
|
||||||
from UM.FlameProfiler import pyqtSlot
|
import math
|
||||||
|
import os.path
|
||||||
|
import unicodedata
|
||||||
|
import json
|
||||||
|
import re # To create abbreviations for printer names.
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QObject, pyqtSignal, pyqtProperty, pyqtSlot
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.Duration import Duration
|
from UM.Qt.Duration import Duration
|
||||||
from UM.Preferences import Preferences
|
from UM.Preferences import Preferences
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
|
||||||
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
import math
|
|
||||||
import os.path
|
|
||||||
import unicodedata
|
|
||||||
import json
|
|
||||||
import re #To create abbreviations for printer names.
|
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
|
## A class for processing and calculating minimum, current and maximum print time as well as managing the job name
|
||||||
|
@ -76,16 +71,19 @@ class PrintInformation(QObject):
|
||||||
self._active_build_plate = 0
|
self._active_build_plate = 0
|
||||||
self._initVariablesWithBuildPlate(self._active_build_plate)
|
self._initVariablesWithBuildPlate(self._active_build_plate)
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._updateJobName)
|
self._application = Application.getInstance()
|
||||||
Application.getInstance().fileLoaded.connect(self.setBaseName)
|
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
|
||||||
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
|
|
||||||
Application.getInstance().workspaceLoaded.connect(self.setProjectName)
|
self._application.globalContainerStackChanged.connect(self._updateJobName)
|
||||||
|
self._application.globalContainerStackChanged.connect(self.setToZeroPrintInformation)
|
||||||
|
self._application.fileLoaded.connect(self.setBaseName)
|
||||||
|
self._application.workspaceLoaded.connect(self.setProjectName)
|
||||||
|
self._multi_build_plate_model.activeBuildPlateChanged.connect(self._onActiveBuildPlateChanged)
|
||||||
|
|
||||||
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
Preferences.getInstance().preferenceChanged.connect(self._onPreferencesChanged)
|
||||||
|
|
||||||
self._active_material_container = None
|
self._application.getMachineManager().rootMaterialChanged.connect(self._onActiveMaterialsChanged)
|
||||||
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._onActiveMaterialChanged)
|
self._onActiveMaterialsChanged()
|
||||||
self._onActiveMaterialChanged()
|
|
||||||
|
|
||||||
self._material_amounts = []
|
self._material_amounts = []
|
||||||
|
|
||||||
|
@ -200,11 +198,10 @@ class PrintInformation(QObject):
|
||||||
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
|
self._current_print_time[build_plate_number].setDuration(total_estimated_time)
|
||||||
|
|
||||||
def _calculateInformation(self, build_plate_number):
|
def _calculateInformation(self, build_plate_number):
|
||||||
if Application.getInstance().getGlobalContainerStack() is None:
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
# Material amount is sent as an amount of mm^3, so calculate length from that
|
|
||||||
radius = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value") / 2
|
|
||||||
self._material_lengths[build_plate_number] = []
|
self._material_lengths[build_plate_number] = []
|
||||||
self._material_weights[build_plate_number] = []
|
self._material_weights[build_plate_number] = []
|
||||||
self._material_costs[build_plate_number] = []
|
self._material_costs[build_plate_number] = []
|
||||||
|
@ -212,18 +209,17 @@ class PrintInformation(QObject):
|
||||||
|
|
||||||
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
|
material_preference_values = json.loads(Preferences.getInstance().getValue("cura/material_settings"))
|
||||||
|
|
||||||
extruder_stacks = list(ExtruderManager.getInstance().getMachineExtruders(Application.getInstance().getGlobalContainerStack().getId()))
|
extruder_stacks = global_stack.extruders
|
||||||
for index, amount in enumerate(self._material_amounts):
|
for position, extruder_stack in extruder_stacks.items():
|
||||||
|
index = int(position)
|
||||||
|
if index >= len(self._material_amounts):
|
||||||
|
continue
|
||||||
|
amount = self._material_amounts[index]
|
||||||
## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
|
## Find the right extruder stack. As the list isn't sorted because it's a annoying generator, we do some
|
||||||
# list comprehension filtering to solve this for us.
|
# list comprehension filtering to solve this for us.
|
||||||
material = None
|
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
|
||||||
if extruder_stacks: # Multi extrusion machine
|
material = extruder_stack.findContainer({"type": "material"})
|
||||||
extruder_stack = [extruder for extruder in extruder_stacks if extruder.getMetaDataEntry("position") == str(index)][0]
|
radius = extruder_stack.getProperty("material_diameter", "value") / 2
|
||||||
density = extruder_stack.getMetaDataEntry("properties", {}).get("density", 0)
|
|
||||||
material = extruder_stack.findContainer({"type": "material"})
|
|
||||||
else: # Machine with no extruder stacks
|
|
||||||
density = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("properties", {}).get("density", 0)
|
|
||||||
material = Application.getInstance().getGlobalContainerStack().findContainer({"type": "material"})
|
|
||||||
|
|
||||||
weight = float(amount) * float(density) / 1000
|
weight = float(amount) * float(density) / 1000
|
||||||
cost = 0
|
cost = 0
|
||||||
|
@ -242,6 +238,7 @@ class PrintInformation(QObject):
|
||||||
else:
|
else:
|
||||||
cost = 0
|
cost = 0
|
||||||
|
|
||||||
|
# Material amount is sent as an amount of mm^3, so calculate length from that
|
||||||
if radius != 0:
|
if radius != 0:
|
||||||
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
|
length = round((amount / (math.pi * radius ** 2)) / 1000, 2)
|
||||||
else:
|
else:
|
||||||
|
@ -260,25 +257,11 @@ class PrintInformation(QObject):
|
||||||
if preference != "cura/material_settings":
|
if preference != "cura/material_settings":
|
||||||
return
|
return
|
||||||
|
|
||||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
||||||
self._calculateInformation(build_plate_number)
|
self._calculateInformation(build_plate_number)
|
||||||
|
|
||||||
def _onActiveMaterialChanged(self):
|
|
||||||
if self._active_material_container:
|
|
||||||
try:
|
|
||||||
self._active_material_container.metaDataChanged.disconnect(self._onMaterialMetaDataChanged)
|
|
||||||
except TypeError: #pyQtSignal gives a TypeError when disconnecting from something that is already disconnected.
|
|
||||||
pass
|
|
||||||
|
|
||||||
active_material_id = Application.getInstance().getMachineManager().activeMaterialId
|
|
||||||
active_material_containers = ContainerRegistry.getInstance().findInstanceContainers(id = active_material_id)
|
|
||||||
|
|
||||||
if active_material_containers:
|
|
||||||
self._active_material_container = active_material_containers[0]
|
|
||||||
self._active_material_container.metaDataChanged.connect(self._onMaterialMetaDataChanged)
|
|
||||||
|
|
||||||
def _onActiveBuildPlateChanged(self):
|
def _onActiveBuildPlateChanged(self):
|
||||||
new_active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
new_active_build_plate = self._multi_build_plate_model.activeBuildPlate
|
||||||
if new_active_build_plate != self._active_build_plate:
|
if new_active_build_plate != self._active_build_plate:
|
||||||
self._active_build_plate = new_active_build_plate
|
self._active_build_plate = new_active_build_plate
|
||||||
|
|
||||||
|
@ -290,8 +273,8 @@ class PrintInformation(QObject):
|
||||||
self.materialNamesChanged.emit()
|
self.materialNamesChanged.emit()
|
||||||
self.currentPrintTimeChanged.emit()
|
self.currentPrintTimeChanged.emit()
|
||||||
|
|
||||||
def _onMaterialMetaDataChanged(self, *args, **kwargs):
|
def _onActiveMaterialsChanged(self, *args, **kwargs):
|
||||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
||||||
self._calculateInformation(build_plate_number)
|
self._calculateInformation(build_plate_number)
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
|
@ -396,7 +379,9 @@ class PrintInformation(QObject):
|
||||||
return result
|
return result
|
||||||
|
|
||||||
# Simulate message with zero time duration
|
# Simulate message with zero time duration
|
||||||
def setToZeroPrintInformation(self, build_plate):
|
def setToZeroPrintInformation(self, build_plate = None):
|
||||||
|
if build_plate is None:
|
||||||
|
build_plate = self._active_build_plate
|
||||||
|
|
||||||
# Construct the 0-time message
|
# Construct the 0-time message
|
||||||
temp_message = {}
|
temp_message = {}
|
||||||
|
|
|
@ -219,6 +219,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
reply.uploadProgress.connect(onProgress)
|
reply.uploadProgress.connect(onProgress)
|
||||||
self._registerOnFinishedCallback(reply, onFinished)
|
self._registerOnFinishedCallback(reply, onFinished)
|
||||||
|
|
||||||
|
|
||||||
|
return reply
|
||||||
|
|
||||||
def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
|
def postForm(self, target: str, header_data: str, body_data: bytes, onFinished: Optional[Callable[[Any, QNetworkReply], None]], onProgress: Callable = None) -> None:
|
||||||
post_part = QHttpPart()
|
post_part = QHttpPart()
|
||||||
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
post_part.setHeader(QNetworkRequest.ContentDispositionHeader, header_data)
|
||||||
|
|
|
@ -108,32 +108,32 @@ class PrinterOutputModel(QObject):
|
||||||
self._head_position = Vector(x, y, z)
|
self._head_position = Vector(x, y, z)
|
||||||
self.headPositionChanged.emit()
|
self.headPositionChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty("long", "long", "long")
|
@pyqtProperty(float, float, float)
|
||||||
@pyqtProperty("long", "long", "long", "long")
|
@pyqtProperty(float, float, float, float)
|
||||||
def setHeadPosition(self, x, y, z, speed = 3000):
|
def setHeadPosition(self, x, y, z, speed = 3000):
|
||||||
self.updateHeadPosition(x, y, z)
|
self.updateHeadPosition(x, y, z)
|
||||||
self._controller.setHeadPosition(self, x, y, z, speed)
|
self._controller.setHeadPosition(self, x, y, z, speed)
|
||||||
|
|
||||||
@pyqtProperty("long")
|
@pyqtProperty(float)
|
||||||
@pyqtProperty("long", "long")
|
@pyqtProperty(float, float)
|
||||||
def setHeadX(self, x, speed = 3000):
|
def setHeadX(self, x, speed = 3000):
|
||||||
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
|
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
|
||||||
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
|
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
|
||||||
|
|
||||||
@pyqtProperty("long")
|
@pyqtProperty(float)
|
||||||
@pyqtProperty("long", "long")
|
@pyqtProperty(float, float)
|
||||||
def setHeadY(self, y, speed = 3000):
|
def setHeadY(self, y, speed = 3000):
|
||||||
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
|
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
|
||||||
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
|
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
|
||||||
|
|
||||||
@pyqtProperty("long")
|
@pyqtProperty(float)
|
||||||
@pyqtProperty("long", "long")
|
@pyqtProperty(float, float)
|
||||||
def setHeadZ(self, z, speed = 3000):
|
def setHeadZ(self, z, speed = 3000):
|
||||||
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
|
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
|
||||||
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
|
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
|
||||||
|
|
||||||
@pyqtSlot("long", "long", "long")
|
@pyqtSlot(float, float, float)
|
||||||
@pyqtSlot("long", "long", "long", "long")
|
@pyqtSlot(float, float, float, float)
|
||||||
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
|
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
|
||||||
self._controller.moveHead(self, x, y, z, speed)
|
self._controller.moveHead(self, x, y, z, speed)
|
||||||
|
|
||||||
|
|
|
@ -1,332 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
# This collects a lot of quality and quality changes related code which was split between ContainerManager
|
|
||||||
# and the MachineManager and really needs to usable from both.
|
|
||||||
from typing import Any, Dict, List, Optional, TYPE_CHECKING
|
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainerInterface
|
|
||||||
|
|
||||||
class QualityManager:
|
|
||||||
|
|
||||||
## Get the singleton instance for this class.
|
|
||||||
@classmethod
|
|
||||||
def getInstance(cls) -> "QualityManager":
|
|
||||||
# Note: Explicit use of class name to prevent issues with inheritance.
|
|
||||||
if not QualityManager.__instance:
|
|
||||||
QualityManager.__instance = cls()
|
|
||||||
return QualityManager.__instance
|
|
||||||
|
|
||||||
__instance = None # type: "QualityManager"
|
|
||||||
|
|
||||||
## Find a quality by name for a specific machine definition and materials.
|
|
||||||
#
|
|
||||||
# \param quality_name
|
|
||||||
# \param machine_definition (Optional) \type{DefinitionContainerInterface} If nothing is
|
|
||||||
# specified then the currently selected machine definition is used.
|
|
||||||
# \param material_containers_metadata If nothing is specified then the
|
|
||||||
# current set of selected materials is used.
|
|
||||||
# \return the matching quality container \type{InstanceContainer}
|
|
||||||
def findQualityByName(self, quality_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None) -> Optional[InstanceContainer]:
|
|
||||||
criteria = {"type": "quality", "name": quality_name}
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
|
|
||||||
|
|
||||||
# Fall back to using generic materials and qualities if nothing could be found.
|
|
||||||
if not result and material_containers_metadata and len(material_containers_metadata) == 1:
|
|
||||||
basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
|
||||||
|
|
||||||
return result[0] if result else None
|
|
||||||
|
|
||||||
## Find a quality changes container by name.
|
|
||||||
#
|
|
||||||
# \param quality_changes_name \type{str} the name of the quality changes container.
|
|
||||||
# \param machine_definition (Optional) \type{DefinitionContainer} If nothing is
|
|
||||||
# specified then the currently selected machine definition is used..
|
|
||||||
# \return the matching quality changes containers \type{List[InstanceContainer]}
|
|
||||||
def findQualityChangesByName(self, quality_changes_name: str, machine_definition: Optional["DefinitionContainerInterface"] = None):
|
|
||||||
if not machine_definition:
|
|
||||||
global_stack = Application.getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return [] #No stack, so no current definition could be found, so there are no quality changes either.
|
|
||||||
machine_definition = global_stack.definition
|
|
||||||
|
|
||||||
result = self.findAllQualityChangesForMachine(machine_definition)
|
|
||||||
result = [quality_change for quality_change in result if quality_change.getName() == quality_changes_name]
|
|
||||||
return result
|
|
||||||
|
|
||||||
## Fetch the list of available quality types for this combination of machine definition and materials.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer}
|
|
||||||
# \param material_containers \type{List[InstanceContainer]}
|
|
||||||
# \return \type{List[str]}
|
|
||||||
def findAllQualityTypesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[str]:
|
|
||||||
# Determine the common set of quality types which can be
|
|
||||||
# applied to all of the materials for this machine.
|
|
||||||
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
|
|
||||||
common_quality_types = set(quality_type_dict.keys())
|
|
||||||
for material_container in material_containers[1:]:
|
|
||||||
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
|
|
||||||
common_quality_types.intersection_update(set(next_quality_type_dict.keys()))
|
|
||||||
|
|
||||||
return list(common_quality_types)
|
|
||||||
|
|
||||||
def findAllQualitiesForMachineAndMaterials(self, machine_definition: "DefinitionContainerInterface", material_containers: List[InstanceContainer]) -> List[InstanceContainer]:
|
|
||||||
# Determine the common set of quality types which can be
|
|
||||||
# applied to all of the materials for this machine.
|
|
||||||
quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_containers[0])
|
|
||||||
qualities = set(quality_type_dict.values())
|
|
||||||
for material_container in material_containers[1:]:
|
|
||||||
next_quality_type_dict = self.__fetchQualityTypeDictForMaterial(machine_definition, material_container)
|
|
||||||
qualities.intersection_update(set(next_quality_type_dict.values()))
|
|
||||||
|
|
||||||
return list(qualities)
|
|
||||||
|
|
||||||
## Fetches a dict of quality types names to quality profiles for a combination of machine and material.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
|
||||||
# \param material \type{InstanceContainer} the material.
|
|
||||||
# \return \type{Dict[str, InstanceContainer]} the dict of suitable quality type names mapping to qualities.
|
|
||||||
def __fetchQualityTypeDictForMaterial(self, machine_definition: "DefinitionContainerInterface", material: InstanceContainer) -> Dict[str, InstanceContainer]:
|
|
||||||
qualities = self.findAllQualitiesForMachineMaterial(machine_definition, material)
|
|
||||||
quality_type_dict = {}
|
|
||||||
for quality in qualities:
|
|
||||||
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
|
|
||||||
return quality_type_dict
|
|
||||||
|
|
||||||
## Find a quality container by quality type.
|
|
||||||
#
|
|
||||||
# \param quality_type \type{str} the name of the quality type to search for.
|
|
||||||
# \param machine_definition (Optional) \type{InstanceContainer} If nothing is
|
|
||||||
# specified then the currently selected machine definition is used.
|
|
||||||
# \param material_containers_metadata If nothing is specified then the
|
|
||||||
# current set of selected materials is used.
|
|
||||||
# \return the matching quality container \type{InstanceContainer}
|
|
||||||
def findQualityByQualityType(self, quality_type: str, machine_definition: Optional["DefinitionContainerInterface"] = None, material_containers_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs) -> InstanceContainer:
|
|
||||||
criteria = kwargs
|
|
||||||
criteria["type"] = "quality"
|
|
||||||
if quality_type:
|
|
||||||
criteria["quality_type"] = quality_type
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, material_containers_metadata, **criteria)
|
|
||||||
# Fall back to using generic materials and qualities if nothing could be found.
|
|
||||||
if not result and material_containers_metadata and len(material_containers_metadata) == 1:
|
|
||||||
basic_materials = self._getBasicMaterialMetadatas(material_containers_metadata[0])
|
|
||||||
if basic_materials:
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
|
||||||
return result[0] if result else None
|
|
||||||
|
|
||||||
## Find all suitable qualities for a combination of machine and material.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
|
||||||
# \param material_container \type{InstanceContainer} the material.
|
|
||||||
# \return \type{List[InstanceContainer]} the list of suitable qualities.
|
|
||||||
def findAllQualitiesForMachineMaterial(self, machine_definition: "DefinitionContainerInterface", material_container: InstanceContainer) -> List[InstanceContainer]:
|
|
||||||
criteria = {"type": "quality"}
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, [material_container.getMetaData()], **criteria)
|
|
||||||
if not result:
|
|
||||||
basic_materials = self._getBasicMaterialMetadatas(material_container.getMetaData())
|
|
||||||
if basic_materials:
|
|
||||||
result = self._getFilteredContainersForStack(machine_definition, basic_materials, **criteria)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
## Find all quality changes for a machine.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer} the machine definition.
|
|
||||||
# \return \type{List[InstanceContainer]} the list of quality changes
|
|
||||||
def findAllQualityChangesForMachine(self, machine_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
|
|
||||||
if machine_definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
definition_id = machine_definition.getId()
|
|
||||||
else:
|
|
||||||
definition_id = "fdmprinter"
|
|
||||||
|
|
||||||
filter_dict = { "type": "quality_changes", "definition": definition_id }
|
|
||||||
quality_changes_list = ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
|
||||||
return quality_changes_list
|
|
||||||
|
|
||||||
def findAllExtruderDefinitionsForMachine(self, machine_definition: "DefinitionContainerInterface") -> List["DefinitionContainerInterface"]:
|
|
||||||
filter_dict = { "machine": machine_definition.getId() }
|
|
||||||
return ContainerRegistry.getInstance().findDefinitionContainers(**filter_dict)
|
|
||||||
|
|
||||||
## Find all quality changes for a given extruder.
|
|
||||||
#
|
|
||||||
# \param extruder_definition The extruder to find the quality changes for.
|
|
||||||
# \return The list of quality changes for the given extruder.
|
|
||||||
def findAllQualityChangesForExtruder(self, extruder_definition: "DefinitionContainerInterface") -> List[InstanceContainer]:
|
|
||||||
filter_dict = {"type": "quality_changes", "extruder": extruder_definition.getId()}
|
|
||||||
return ContainerRegistry.getInstance().findInstanceContainers(**filter_dict)
|
|
||||||
|
|
||||||
## Find all usable qualities for a machine and extruders.
|
|
||||||
#
|
|
||||||
# Finds all of the qualities for this combination of machine and extruders.
|
|
||||||
# Only one quality per quality type is returned. i.e. if there are 2 qualities with quality_type=normal
|
|
||||||
# then only one of then is returned (at random).
|
|
||||||
#
|
|
||||||
# \param global_container_stack \type{GlobalStack} the global machine definition
|
|
||||||
# \param extruder_stacks \type{List[ExtruderStack]} the list of extruder stacks
|
|
||||||
# \return \type{List[InstanceContainer]} the list of the matching qualities. The quality profiles
|
|
||||||
# return come from the first extruder in the given list of extruders.
|
|
||||||
def findAllUsableQualitiesForMachineAndExtruders(self, global_container_stack: "GlobalStack", extruder_stacks: List["ExtruderStack"]) -> List[InstanceContainer]:
|
|
||||||
global_machine_definition = global_container_stack.getBottom()
|
|
||||||
|
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
|
||||||
active_stack_id = machine_manager.activeStackId
|
|
||||||
|
|
||||||
materials = []
|
|
||||||
|
|
||||||
for stack in extruder_stacks:
|
|
||||||
if stack.getId() == active_stack_id and machine_manager.newMaterial:
|
|
||||||
materials.append(machine_manager.newMaterial)
|
|
||||||
else:
|
|
||||||
materials.append(stack.material)
|
|
||||||
|
|
||||||
quality_types = self.findAllQualityTypesForMachineAndMaterials(global_machine_definition, materials)
|
|
||||||
|
|
||||||
# Map the list of quality_types to InstanceContainers
|
|
||||||
qualities = self.findAllQualitiesForMachineMaterial(global_machine_definition, materials[0])
|
|
||||||
quality_type_dict = {}
|
|
||||||
for quality in qualities:
|
|
||||||
quality_type_dict[quality.getMetaDataEntry("quality_type")] = quality
|
|
||||||
|
|
||||||
return [quality_type_dict[quality_type] for quality_type in quality_types]
|
|
||||||
|
|
||||||
## Fetch more basic versions of a material.
|
|
||||||
#
|
|
||||||
# This tries to find a generic or basic version of the given material.
|
|
||||||
# \param material_container \type{Dict[str, Any]} The metadata of a
|
|
||||||
# material to find the basic versions of.
|
|
||||||
# \return \type{List[Dict[str, Any]]} A list of the metadata of basic
|
|
||||||
# materials, or an empty list if none could be found.
|
|
||||||
def _getBasicMaterialMetadatas(self, material_container: Dict[str, Any]) -> List[Dict[str, Any]]:
|
|
||||||
if "definition" not in material_container:
|
|
||||||
definition_id = "fdmprinter"
|
|
||||||
else:
|
|
||||||
material_container_definition = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = material_container["definition"])
|
|
||||||
if not material_container_definition:
|
|
||||||
definition_id = "fdmprinter"
|
|
||||||
else:
|
|
||||||
material_container_definition = material_container_definition[0]
|
|
||||||
if "has_machine_quality" not in material_container_definition:
|
|
||||||
definition_id = "fdmprinter"
|
|
||||||
else:
|
|
||||||
definition_id = material_container_definition.get("quality_definition", material_container_definition["id"])
|
|
||||||
|
|
||||||
base_material = material_container.get("material")
|
|
||||||
if base_material:
|
|
||||||
# There is a basic material specified
|
|
||||||
criteria = {
|
|
||||||
"type": "material",
|
|
||||||
"name": base_material,
|
|
||||||
"definition": definition_id,
|
|
||||||
"variant": material_container.get("variant")
|
|
||||||
}
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
|
|
||||||
return containers
|
|
||||||
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _getFilteredContainers(self, **kwargs):
|
|
||||||
return self._getFilteredContainersForStack(None, None, **kwargs)
|
|
||||||
|
|
||||||
def _getFilteredContainersForStack(self, machine_definition: "DefinitionContainerInterface" = None, material_metadata: Optional[List[Dict[str, Any]]] = None, **kwargs):
|
|
||||||
# Fill in any default values.
|
|
||||||
if machine_definition is None:
|
|
||||||
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
|
||||||
quality_definition_id = machine_definition.getMetaDataEntry("quality_definition")
|
|
||||||
if quality_definition_id is not None:
|
|
||||||
machine_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = quality_definition_id)[0]
|
|
||||||
|
|
||||||
if not material_metadata:
|
|
||||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
|
||||||
if active_stacks:
|
|
||||||
material_metadata = [stack.material.getMetaData() for stack in active_stacks]
|
|
||||||
|
|
||||||
criteria = kwargs
|
|
||||||
filter_by_material = False
|
|
||||||
|
|
||||||
machine_definition = self.getParentMachineDefinition(machine_definition)
|
|
||||||
criteria["definition"] = machine_definition.getId()
|
|
||||||
found_containers_with_machine_definition = ContainerRegistry.getInstance().findInstanceContainersMetadata(**criteria)
|
|
||||||
whole_machine_definition = self.getWholeMachineDefinition(machine_definition)
|
|
||||||
if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
definition_id = machine_definition.getMetaDataEntry("quality_definition", whole_machine_definition.getId())
|
|
||||||
criteria["definition"] = definition_id
|
|
||||||
|
|
||||||
filter_by_material = whole_machine_definition.getMetaDataEntry("has_materials")
|
|
||||||
# only fall back to "fdmprinter" when there is no container for this machine
|
|
||||||
elif not found_containers_with_machine_definition:
|
|
||||||
criteria["definition"] = "fdmprinter"
|
|
||||||
|
|
||||||
# Stick the material IDs in a set
|
|
||||||
material_ids = set()
|
|
||||||
|
|
||||||
for material_instance in material_metadata:
|
|
||||||
if material_instance is not None:
|
|
||||||
# Add the parent material too.
|
|
||||||
for basic_material in self._getBasicMaterialMetadatas(material_instance):
|
|
||||||
material_ids.add(basic_material["id"])
|
|
||||||
material_ids.add(material_instance["id"])
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(**criteria)
|
|
||||||
|
|
||||||
result = []
|
|
||||||
for container in containers:
|
|
||||||
# If the machine specifies we should filter by material, exclude containers that do not match any active material.
|
|
||||||
if filter_by_material and container.getMetaDataEntry("material") not in material_ids and "global_quality" not in kwargs:
|
|
||||||
continue
|
|
||||||
result.append(container)
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
## Get the parent machine definition of a machine definition.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
|
||||||
# an extruder definition.
|
|
||||||
# \return \type{DefinitionContainer} the parent machine definition. If the given machine
|
|
||||||
# definition doesn't have a parent then it is simply returned.
|
|
||||||
def getParentMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
|
||||||
|
|
||||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
|
||||||
if machine_entry is None:
|
|
||||||
# We have a normal (whole) machine defintion
|
|
||||||
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
|
||||||
if quality_definition is not None:
|
|
||||||
parent_machine_definition = container_registry.findDefinitionContainers(id = quality_definition)[0]
|
|
||||||
return self.getParentMachineDefinition(parent_machine_definition)
|
|
||||||
else:
|
|
||||||
return machine_definition
|
|
||||||
else:
|
|
||||||
# This looks like an extruder. Find the rest of the machine.
|
|
||||||
whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
|
|
||||||
parent_machine = self.getParentMachineDefinition(whole_machine)
|
|
||||||
if whole_machine is parent_machine:
|
|
||||||
# This extruder already belongs to a 'parent' machine def.
|
|
||||||
return machine_definition
|
|
||||||
else:
|
|
||||||
# Look up the corresponding extruder definition in the parent machine definition.
|
|
||||||
extruder_position = machine_definition.getMetaDataEntry("position")
|
|
||||||
parent_extruder_id = parent_machine.getMetaDataEntry("machine_extruder_trains")[extruder_position]
|
|
||||||
return container_registry.findDefinitionContainers(id = parent_extruder_id)[0]
|
|
||||||
|
|
||||||
## Get the whole/global machine definition from an extruder definition.
|
|
||||||
#
|
|
||||||
# \param machine_definition \type{DefinitionContainer} This may be a normal machine definition or
|
|
||||||
# an extruder definition.
|
|
||||||
# \return \type{DefinitionContainerInterface}
|
|
||||||
def getWholeMachineDefinition(self, machine_definition: "DefinitionContainerInterface") -> "DefinitionContainerInterface":
|
|
||||||
machine_entry = machine_definition.getMetaDataEntry("machine")
|
|
||||||
if machine_entry is None:
|
|
||||||
# This already is a 'global' machine definition.
|
|
||||||
return machine_definition
|
|
||||||
else:
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
|
||||||
whole_machine = container_registry.findDefinitionContainers(id = machine_entry)[0]
|
|
||||||
return whole_machine
|
|
|
@ -68,7 +68,7 @@ class ConvexHullNode(SceneNode):
|
||||||
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
ConvexHullNode.shader.setUniformValue("u_opacity", 0.6)
|
||||||
|
|
||||||
if self.getParent():
|
if self.getParent():
|
||||||
if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate:
|
if self.getMeshData() and isinstance(self._node, SceneNode) and self._node.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate:
|
||||||
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
|
renderer.queueNode(self, transparent = True, shader = ConvexHullNode.shader, backface_cull = True, sort = -8)
|
||||||
if self._convex_hull_head_mesh:
|
if self._convex_hull_head_mesh:
|
||||||
renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
|
renderer.queueNode(self, shader = ConvexHullNode.shader, transparent = True, mesh = self._convex_hull_head_mesh, backface_cull = True, sort = -8)
|
||||||
|
|
|
@ -4,7 +4,7 @@ from PyQt5.QtCore import Qt, pyqtSlot, QObject
|
||||||
from PyQt5.QtWidgets import QApplication
|
from PyQt5.QtWidgets import QApplication
|
||||||
|
|
||||||
from cura.ObjectsModel import ObjectsModel
|
from cura.ObjectsModel import ObjectsModel
|
||||||
from cura.BuildPlateModel import BuildPlateModel
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||||
|
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
@ -16,11 +16,11 @@ from UM.Signal import Signal
|
||||||
class CuraSceneController(QObject):
|
class CuraSceneController(QObject):
|
||||||
activeBuildPlateChanged = Signal()
|
activeBuildPlateChanged = Signal()
|
||||||
|
|
||||||
def __init__(self, objects_model: ObjectsModel, build_plate_model: BuildPlateModel):
|
def __init__(self, objects_model: ObjectsModel, multi_build_plate_model: MultiBuildPlateModel):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._objects_model = objects_model
|
self._objects_model = objects_model
|
||||||
self._build_plate_model = build_plate_model
|
self._multi_build_plate_model = multi_build_plate_model
|
||||||
self._active_build_plate = -1
|
self._active_build_plate = -1
|
||||||
|
|
||||||
self._last_selected_index = 0
|
self._last_selected_index = 0
|
||||||
|
@ -41,9 +41,9 @@ class CuraSceneController(QObject):
|
||||||
self._max_build_plate = max_build_plate
|
self._max_build_plate = max_build_plate
|
||||||
changed = True
|
changed = True
|
||||||
if changed:
|
if changed:
|
||||||
self._build_plate_model.setMaxBuildPlate(self._max_build_plate)
|
self._multi_build_plate_model.setMaxBuildPlate(self._max_build_plate)
|
||||||
build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
|
build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
|
||||||
self._build_plate_model.setItems(build_plates)
|
self._multi_build_plate_model.setItems(build_plates)
|
||||||
if self._active_build_plate > self._max_build_plate:
|
if self._active_build_plate > self._max_build_plate:
|
||||||
build_plate_number = 0
|
build_plate_number = 0
|
||||||
if self._last_selected_index >= 0: # go to the buildplate of the item you last selected
|
if self._last_selected_index >= 0: # go to the buildplate of the item you last selected
|
||||||
|
@ -104,12 +104,12 @@ class CuraSceneController(QObject):
|
||||||
self._active_build_plate = nr
|
self._active_build_plate = nr
|
||||||
Selection.clear()
|
Selection.clear()
|
||||||
|
|
||||||
self._build_plate_model.setActiveBuildPlate(nr)
|
self._multi_build_plate_model.setActiveBuildPlate(nr)
|
||||||
self._objects_model.setActiveBuildPlate(nr)
|
self._objects_model.setActiveBuildPlate(nr)
|
||||||
self.activeBuildPlateChanged.emit()
|
self.activeBuildPlateChanged.emit()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def createCuraSceneController():
|
def createCuraSceneController():
|
||||||
objects_model = Application.getInstance().getObjectsModel()
|
objects_model = Application.getInstance().getObjectsModel()
|
||||||
build_plate_model = Application.getInstance().getBuildPlateModel()
|
multi_build_plate_model = Application.getInstance().getMultiBuildPlateModel()
|
||||||
return CuraSceneController(objects_model = objects_model, build_plate_model = build_plate_model)
|
return CuraSceneController(objects_model = objects_model, multi_build_plate_model = multi_build_plate_model)
|
||||||
|
|
|
@ -20,10 +20,10 @@ class CuraSceneNode(SceneNode):
|
||||||
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
return self._outside_buildarea or self.callDecoration("getBuildPlateNumber") < 0
|
||||||
|
|
||||||
def isVisible(self):
|
def isVisible(self):
|
||||||
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate
|
return super().isVisible() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
def isSelectable(self) -> bool:
|
def isSelectable(self) -> bool:
|
||||||
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getBuildPlateModel().activeBuildPlate
|
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
||||||
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
||||||
|
|
|
@ -1,16 +1,14 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2017 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import copy
|
|
||||||
import os.path
|
import os.path
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, List, Union
|
from typing import Dict, Union
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl, QVariant
|
from PyQt5.QtCore import QObject, QUrl, QVariant
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from UM.Util import parseBool
|
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.SaveFile import SaveFile
|
from UM.SaveFile import SaveFile
|
||||||
|
@ -22,7 +20,6 @@ from UM.Application import Application
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
|
|
||||||
from UM.MimeTypeDatabase import MimeTypeNotFoundError
|
from UM.MimeTypeDatabase import MimeTypeNotFoundError
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
@ -30,9 +27,11 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
## Manager class that contains common actions to deal with containers in Cura.
|
## Manager class that contains common actions to deal with containers in Cura.
|
||||||
#
|
#
|
||||||
# This is primarily intended as a class to be able to perform certain actions
|
# This is primarily intended as a class to be able to perform certain actions
|
||||||
|
@ -42,154 +41,12 @@ class ContainerManager(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._application = Application.getInstance()
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
self._container_registry = ContainerRegistry.getInstance()
|
||||||
self._machine_manager = Application.getInstance().getMachineManager()
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
self._material_manager = self._application.getMaterialManager()
|
||||||
self._container_name_filters = {}
|
self._container_name_filters = {}
|
||||||
|
|
||||||
## Create a duplicate of the specified container
|
|
||||||
#
|
|
||||||
# This will create and add a duplicate of the container corresponding
|
|
||||||
# to the container ID.
|
|
||||||
#
|
|
||||||
# \param container_id \type{str} The ID of the container to duplicate.
|
|
||||||
#
|
|
||||||
# \return The ID of the new container, or an empty string if duplication failed.
|
|
||||||
@pyqtSlot(str, result = str)
|
|
||||||
def duplicateContainer(self, container_id):
|
|
||||||
#TODO: It should be able to duplicate a container of which only the metadata is known.
|
|
||||||
containers = self._container_registry.findContainers(id = container_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could duplicate container %s because it was not found.", container_id)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
container = containers[0]
|
|
||||||
new_container = self.duplicateContainerInstance(container)
|
|
||||||
return new_container.getId()
|
|
||||||
|
|
||||||
## Create a duplicate of the given container instance
|
|
||||||
#
|
|
||||||
# This will create and add a duplicate of the container that was passed.
|
|
||||||
#
|
|
||||||
# \param container \type{ContainerInterface} The container to duplicate.
|
|
||||||
#
|
|
||||||
# \return The duplicated container, or None if duplication failed.
|
|
||||||
def duplicateContainerInstance(self, container):
|
|
||||||
new_container = None
|
|
||||||
new_name = self._container_registry.uniqueName(container.getName())
|
|
||||||
# Only InstanceContainer has a duplicate method at the moment.
|
|
||||||
# So fall back to serialize/deserialize when no duplicate method exists.
|
|
||||||
if hasattr(container, "duplicate"):
|
|
||||||
new_container = container.duplicate(new_name)
|
|
||||||
else:
|
|
||||||
new_container = container.__class__(new_name)
|
|
||||||
new_container.deserialize(container.serialize())
|
|
||||||
new_container.setName(new_name)
|
|
||||||
|
|
||||||
# TODO: we probably don't want to add it to the registry here!
|
|
||||||
if new_container:
|
|
||||||
self._container_registry.addContainer(new_container)
|
|
||||||
|
|
||||||
return new_container
|
|
||||||
|
|
||||||
## Change the name of a specified container to a new name.
|
|
||||||
#
|
|
||||||
# \param container_id \type{str} The ID of the container to change the name of.
|
|
||||||
# \param new_id \type{str} The new ID of the container.
|
|
||||||
# \param new_name \type{str} The new name of the specified container.
|
|
||||||
#
|
|
||||||
# \return True if successful, False if not.
|
|
||||||
@pyqtSlot(str, str, str, result = bool)
|
|
||||||
def renameContainer(self, container_id, new_id, new_name):
|
|
||||||
containers = self._container_registry.findContainers(id = container_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could rename container %s because it was not found.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
container = containers[0]
|
|
||||||
# First, remove the container from the registry. This will clean up any files related to the container.
|
|
||||||
self._container_registry.removeContainer(container_id)
|
|
||||||
|
|
||||||
# Ensure we have a unique name for the container
|
|
||||||
new_name = self._container_registry.uniqueName(new_name)
|
|
||||||
|
|
||||||
# Then, update the name and ID of the container
|
|
||||||
container.setName(new_name)
|
|
||||||
container._id = new_id # TODO: Find a nicer way to set a new, unique ID
|
|
||||||
|
|
||||||
# Finally, re-add the container so it will be properly serialized again.
|
|
||||||
self._container_registry.addContainer(container)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Remove the specified container.
|
|
||||||
#
|
|
||||||
# \param container_id \type{str} The ID of the container to remove.
|
|
||||||
#
|
|
||||||
# \return True if the container was successfully removed, False if not.
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def removeContainer(self, container_id):
|
|
||||||
containers = self._container_registry.findContainers(id = container_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not remove container %s because it was not found.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._container_registry.removeContainer(containers[0].getId())
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Merge a container with another.
|
|
||||||
#
|
|
||||||
# This will try to merge one container into the other, by going through the container
|
|
||||||
# and setting the right properties on the other container.
|
|
||||||
#
|
|
||||||
# \param merge_into_id \type{str} The ID of the container to merge into.
|
|
||||||
# \param merge_id \type{str} The ID of the container to merge.
|
|
||||||
#
|
|
||||||
# \return True if successfully merged, False if not.
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def mergeContainers(self, merge_into_id, merge_id):
|
|
||||||
containers = self._container_registry.findContainers(id = merge_into_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could merge into container %s because it was not found.", merge_into_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
merge_into = containers[0]
|
|
||||||
|
|
||||||
containers = self._container_registry.findContainers(id = merge_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not merge container %s because it was not found", merge_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
merge = containers[0]
|
|
||||||
|
|
||||||
if not isinstance(merge, type(merge_into)):
|
|
||||||
Logger.log("w", "Cannot merge two containers of different types")
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._performMerge(merge_into, merge)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Clear the contents of a container.
|
|
||||||
#
|
|
||||||
# \param container_id \type{str} The ID of the container to clear.
|
|
||||||
#
|
|
||||||
# \return True if successful, False if not.
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def clearContainer(self, container_id):
|
|
||||||
if self._container_registry.isReadOnly(container_id):
|
|
||||||
Logger.log("w", "Cannot clear read-only container %s", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
containers = self._container_registry.findContainers(id = container_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could clear container %s because it was not found.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
containers[0].clear()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
@pyqtSlot(str, str, result=str)
|
@pyqtSlot(str, str, result=str)
|
||||||
def getContainerMetaDataEntry(self, container_id, entry_name):
|
def getContainerMetaDataEntry(self, container_id, entry_name):
|
||||||
metadatas = self._container_registry.findContainersMetadata(id = container_id)
|
metadatas = self._container_registry.findContainersMetadata(id = container_id)
|
||||||
|
@ -211,18 +68,15 @@ class ContainerManager(QObject):
|
||||||
# \param entry_value The new value of the entry.
|
# \param entry_value The new value of the entry.
|
||||||
#
|
#
|
||||||
# \return True if successful, False if not.
|
# \return True if successful, False if not.
|
||||||
@pyqtSlot(str, str, str, result = bool)
|
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||||
def setContainerMetaDataEntry(self, container_id, entry_name, entry_value):
|
@pyqtSlot("QVariant", str, str)
|
||||||
if self._container_registry.isReadOnly(container_id):
|
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
|
||||||
Logger.log("w", "Cannot set metadata of read-only container %s.", container_id)
|
root_material_id = container_node.metadata["base_file"]
|
||||||
|
if self._container_registry.isReadOnly(root_material_id):
|
||||||
|
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
containers = self._container_registry.findContainers(id = container_id) #We need the complete container, since we need to know whether the container is read-only or not.
|
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not set metadata of container %s because it was not found.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
container = containers[0]
|
|
||||||
|
|
||||||
entries = entry_name.split("/")
|
entries = entry_name.split("/")
|
||||||
entry_name = entries.pop()
|
entry_name = entries.pop()
|
||||||
|
@ -230,7 +84,7 @@ class ContainerManager(QObject):
|
||||||
sub_item_changed = False
|
sub_item_changed = False
|
||||||
if entries:
|
if entries:
|
||||||
root_name = entries.pop(0)
|
root_name = entries.pop(0)
|
||||||
root = container.getMetaDataEntry(root_name)
|
root = material_group.root_material_node.metadata.get(root_name)
|
||||||
|
|
||||||
item = root
|
item = root
|
||||||
for _ in range(len(entries)):
|
for _ in range(len(entries)):
|
||||||
|
@ -243,12 +97,11 @@ class ContainerManager(QObject):
|
||||||
entry_name = root_name
|
entry_name = root_name
|
||||||
entry_value = root
|
entry_value = root
|
||||||
|
|
||||||
|
container = material_group.root_material_node.getContainer()
|
||||||
container.setMetaDataEntry(entry_name, entry_value)
|
container.setMetaDataEntry(entry_name, entry_value)
|
||||||
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
|
if sub_item_changed: #If it was only a sub-item that has changed then the setMetaDataEntry won't correctly notice that something changed, and we must manually signal that the metadata changed.
|
||||||
container.metaDataChanged.emit(container)
|
container.metaDataChanged.emit(container)
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Set a setting property of the specified container.
|
## Set a setting property of the specified container.
|
||||||
#
|
#
|
||||||
# This will set the specified property of the specified setting of the container
|
# This will set the specified property of the specified setting of the container
|
||||||
|
@ -306,60 +159,6 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
return container.getProperty(setting_key, property_name)
|
return container.getProperty(setting_key, property_name)
|
||||||
|
|
||||||
## Set the name of the specified container.
|
|
||||||
@pyqtSlot(str, str, result = bool)
|
|
||||||
def setContainerName(self, container_id, new_name):
|
|
||||||
if self._container_registry.isReadOnly(container_id):
|
|
||||||
Logger.log("w", "Cannot set name of read-only container %s.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
containers = self._container_registry.findContainers(id = container_id) #We need to get the full container, not just metadata, since we need to know whether it's read-only.
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not set name of container %s because it was not found.", container_id)
|
|
||||||
return False
|
|
||||||
|
|
||||||
containers[0].setName(new_name)
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Find instance containers matching certain criteria.
|
|
||||||
#
|
|
||||||
# This effectively forwards to
|
|
||||||
# ContainerRegistry::findInstanceContainersMetadata.
|
|
||||||
#
|
|
||||||
# \param criteria A dict of key - value pairs to search for.
|
|
||||||
#
|
|
||||||
# \return A list of container IDs that match the given criteria.
|
|
||||||
@pyqtSlot("QVariantMap", result = "QVariantList")
|
|
||||||
def findInstanceContainers(self, criteria):
|
|
||||||
return [entry["id"] for entry in self._container_registry.findInstanceContainersMetadata(**criteria)]
|
|
||||||
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def isContainerUsed(self, container_id):
|
|
||||||
Logger.log("d", "Checking if container %s is currently used", container_id)
|
|
||||||
# check if this is a material container. If so, check if any material with the same base is being used by any
|
|
||||||
# stacks.
|
|
||||||
container_ids_to_check = [container_id]
|
|
||||||
container_results = self._container_registry.findInstanceContainersMetadata(id = container_id, type = "material")
|
|
||||||
if container_results:
|
|
||||||
this_container = container_results[0]
|
|
||||||
material_base_file = this_container["id"]
|
|
||||||
if "base_file" in this_container:
|
|
||||||
material_base_file = this_container["base_file"]
|
|
||||||
# check all material container IDs with the same base
|
|
||||||
material_containers = self._container_registry.findInstanceContainersMetadata(base_file = material_base_file,
|
|
||||||
type = "material")
|
|
||||||
if material_containers:
|
|
||||||
container_ids_to_check = [container["id"] for container in material_containers]
|
|
||||||
|
|
||||||
all_stacks = self._container_registry.findContainerStacks()
|
|
||||||
for stack in all_stacks:
|
|
||||||
for used_container_id in container_ids_to_check:
|
|
||||||
if used_container_id in [child.getId() for child in stack.getContainers()]:
|
|
||||||
Logger.log("d", "The container is in use by %s", stack.getId())
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def makeUniqueName(self, original_name):
|
def makeUniqueName(self, original_name):
|
||||||
return self._container_registry.uniqueName(original_name)
|
return self._container_registry.uniqueName(original_name)
|
||||||
|
@ -399,7 +198,7 @@ class ContainerManager(QObject):
|
||||||
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
|
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
|
||||||
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||||
if not container_id or not file_type or not file_url_or_string:
|
if not container_id or not file_type or not file_url_or_string:
|
||||||
return { "status": "error", "message": "Invalid arguments"}
|
return {"status": "error", "message": "Invalid arguments"}
|
||||||
|
|
||||||
if isinstance(file_url_or_string, QUrl):
|
if isinstance(file_url_or_string, QUrl):
|
||||||
file_url = file_url_or_string.toLocalFile()
|
file_url = file_url_or_string.toLocalFile()
|
||||||
|
@ -407,20 +206,20 @@ class ContainerManager(QObject):
|
||||||
file_url = file_url_or_string
|
file_url = file_url_or_string
|
||||||
|
|
||||||
if not file_url:
|
if not file_url:
|
||||||
return { "status": "error", "message": "Invalid path"}
|
return {"status": "error", "message": "Invalid path"}
|
||||||
|
|
||||||
mime_type = None
|
mime_type = None
|
||||||
if not file_type in self._container_name_filters:
|
if file_type not in self._container_name_filters:
|
||||||
try:
|
try:
|
||||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
||||||
except MimeTypeNotFoundError:
|
except MimeTypeNotFoundError:
|
||||||
return { "status": "error", "message": "Unknown File Type" }
|
return {"status": "error", "message": "Unknown File Type"}
|
||||||
else:
|
else:
|
||||||
mime_type = self._container_name_filters[file_type]["mime"]
|
mime_type = self._container_name_filters[file_type]["mime"]
|
||||||
|
|
||||||
containers = self._container_registry.findContainers(id = container_id)
|
containers = self._container_registry.findContainers(id = container_id)
|
||||||
if not containers:
|
if not containers:
|
||||||
return { "status": "error", "message": "Container not found"}
|
return {"status": "error", "message": "Container not found"}
|
||||||
container = containers[0]
|
container = containers[0]
|
||||||
|
|
||||||
if Platform.isOSX() and "." in file_url:
|
if Platform.isOSX() and "." in file_url:
|
||||||
|
@ -437,12 +236,12 @@ class ContainerManager(QObject):
|
||||||
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
result = QMessageBox.question(None, catalog.i18nc("@title:window", "File Already Exists"),
|
||||||
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
|
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_url))
|
||||||
if result == QMessageBox.No:
|
if result == QMessageBox.No:
|
||||||
return { "status": "cancelled", "message": "User cancelled"}
|
return {"status": "cancelled", "message": "User cancelled"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
contents = container.serialize()
|
contents = container.serialize()
|
||||||
except NotImplementedError:
|
except NotImplementedError:
|
||||||
return { "status": "error", "message": "Unable to serialize container"}
|
return {"status": "error", "message": "Unable to serialize container"}
|
||||||
|
|
||||||
if contents is None:
|
if contents is None:
|
||||||
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
|
return {"status": "error", "message": "Serialization returned None. Unable to write to file"}
|
||||||
|
@ -450,7 +249,7 @@ class ContainerManager(QObject):
|
||||||
with SaveFile(file_url, "w") as f:
|
with SaveFile(file_url, "w") as f:
|
||||||
f.write(contents)
|
f.write(contents)
|
||||||
|
|
||||||
return { "status": "success", "message": "Succesfully exported container", "path": file_url}
|
return {"status": "success", "message": "Successfully exported container", "path": file_url}
|
||||||
|
|
||||||
## Imports a profile from a file
|
## Imports a profile from a file
|
||||||
#
|
#
|
||||||
|
@ -461,7 +260,7 @@ class ContainerManager(QObject):
|
||||||
@pyqtSlot(QUrl, result = "QVariantMap")
|
@pyqtSlot(QUrl, result = "QVariantMap")
|
||||||
def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||||
if not file_url_or_string:
|
if not file_url_or_string:
|
||||||
return { "status": "error", "message": "Invalid path"}
|
return {"status": "error", "message": "Invalid path"}
|
||||||
|
|
||||||
if isinstance(file_url_or_string, QUrl):
|
if isinstance(file_url_or_string, QUrl):
|
||||||
file_url = file_url_or_string.toLocalFile()
|
file_url = file_url_or_string.toLocalFile()
|
||||||
|
@ -469,16 +268,16 @@ class ContainerManager(QObject):
|
||||||
file_url = file_url_or_string
|
file_url = file_url_or_string
|
||||||
|
|
||||||
if not file_url or not os.path.exists(file_url):
|
if not file_url or not os.path.exists(file_url):
|
||||||
return { "status": "error", "message": "Invalid path" }
|
return {"status": "error", "message": "Invalid path"}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_url)
|
||||||
except MimeTypeNotFoundError:
|
except MimeTypeNotFoundError:
|
||||||
return { "status": "error", "message": "Could not determine mime type of file" }
|
return {"status": "error", "message": "Could not determine mime type of file"}
|
||||||
|
|
||||||
container_type = self._container_registry.getContainerForMimeType(mime_type)
|
container_type = self._container_registry.getContainerForMimeType(mime_type)
|
||||||
if not container_type:
|
if not container_type:
|
||||||
return { "status": "error", "message": "Could not find a container to handle the specified file."}
|
return {"status": "error", "message": "Could not find a container to handle the specified file."}
|
||||||
|
|
||||||
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
|
container_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_url)))
|
||||||
container_id = self._container_registry.uniqueName(container_id)
|
container_id = self._container_registry.uniqueName(container_id)
|
||||||
|
@ -489,7 +288,7 @@ class ContainerManager(QObject):
|
||||||
with open(file_url, "rt", encoding = "utf-8") as f:
|
with open(file_url, "rt", encoding = "utf-8") as f:
|
||||||
container.deserialize(f.read())
|
container.deserialize(f.read())
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
return { "status": "error", "message": "Permission denied when trying to read the file"}
|
return {"status": "error", "message": "Permission denied when trying to read the file"}
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
return {"status": "error", "message": str(ex)}
|
return {"status": "error", "message": str(ex)}
|
||||||
|
|
||||||
|
@ -497,7 +296,7 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
self._container_registry.addContainer(container)
|
self._container_registry.addContainer(container)
|
||||||
|
|
||||||
return { "status": "success", "message": "Successfully imported container {0}".format(container.getName()) }
|
return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
|
||||||
|
|
||||||
## Update the current active quality changes container with the settings from the user container.
|
## Update the current active quality changes container with the settings from the user container.
|
||||||
#
|
#
|
||||||
|
@ -522,7 +321,7 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
self._performMerge(quality_changes, stack.getTop())
|
self._performMerge(quality_changes, stack.getTop())
|
||||||
|
|
||||||
self._machine_manager.activeQualityChanged.emit()
|
self._machine_manager.activeQualityChangesGroupChanged.emit()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -542,407 +341,37 @@ class ContainerManager(QObject):
|
||||||
for container in send_emits_containers:
|
for container in send_emits_containers:
|
||||||
container.sendPostponedEmits()
|
container.sendPostponedEmits()
|
||||||
|
|
||||||
## Create quality changes containers from the user containers in the active stacks.
|
|
||||||
#
|
|
||||||
# This will go through the global and extruder stacks and create quality_changes containers from
|
|
||||||
# the user containers in each stack. These then replace the quality_changes containers in the
|
|
||||||
# stack and clear the user settings.
|
|
||||||
#
|
|
||||||
# \return \type{bool} True if the operation was successfully, False if not.
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def createQualityChanges(self, base_name):
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return False
|
|
||||||
|
|
||||||
active_quality_name = self._machine_manager.activeQualityName
|
|
||||||
if active_quality_name == "":
|
|
||||||
Logger.log("w", "No quality container found in stack %s, cannot create profile", global_stack.getId())
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._machine_manager.blurSettings.emit()
|
|
||||||
if base_name is None or base_name == "":
|
|
||||||
base_name = active_quality_name
|
|
||||||
unique_name = self._container_registry.uniqueName(base_name)
|
|
||||||
|
|
||||||
# Go through the active stacks and create quality_changes containers from the user containers.
|
|
||||||
for stack in ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks():
|
|
||||||
user_container = stack.getTop()
|
|
||||||
quality_container = stack.quality
|
|
||||||
quality_changes_container = stack.qualityChanges
|
|
||||||
if not quality_container or not quality_changes_container:
|
|
||||||
Logger.log("w", "No quality or quality changes container found in stack %s, ignoring it", stack.getId())
|
|
||||||
continue
|
|
||||||
|
|
||||||
extruder_id = None if stack is global_stack else QualityManager.getInstance().getParentMachineDefinition(stack.getBottom()).getId()
|
|
||||||
new_changes = self._createQualityChanges(quality_container, unique_name,
|
|
||||||
Application.getInstance().getGlobalContainerStack().getBottom(),
|
|
||||||
extruder_id)
|
|
||||||
self._performMerge(new_changes, quality_changes_container, clear_settings = False)
|
|
||||||
self._performMerge(new_changes, user_container)
|
|
||||||
|
|
||||||
self._container_registry.addContainer(new_changes)
|
|
||||||
stack.replaceContainer(stack.getContainerIndex(quality_changes_container), new_changes)
|
|
||||||
|
|
||||||
self._machine_manager.activeQualityChanged.emit()
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Remove all quality changes containers matching a specified name.
|
|
||||||
#
|
|
||||||
# This will search for quality_changes containers matching the supplied name and remove them.
|
|
||||||
# Note that if the machine specifies that qualities should be filtered by machine and/or material
|
|
||||||
# only the containers related to the active machine/material are removed.
|
|
||||||
#
|
|
||||||
# \param quality_name The name of the quality changes to remove.
|
|
||||||
#
|
|
||||||
# \return \type{bool} True if successful, False if not.
|
|
||||||
@pyqtSlot(str, result = bool)
|
|
||||||
def removeQualityChanges(self, quality_name):
|
|
||||||
Logger.log("d", "Attempting to remove the quality change containers with name %s", quality_name)
|
|
||||||
containers_found = False
|
|
||||||
|
|
||||||
if not quality_name:
|
|
||||||
return containers_found # Without a name we will never find a container to remove.
|
|
||||||
|
|
||||||
# If the container that is being removed is the currently active quality, set another quality as the active quality
|
|
||||||
activate_quality = quality_name == self._machine_manager.activeQualityName
|
|
||||||
activate_quality_type = None
|
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack or not quality_name:
|
|
||||||
return ""
|
|
||||||
machine_definition = QualityManager.getInstance().getParentMachineDefinition(global_stack.getBottom())
|
|
||||||
|
|
||||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_name, machine_definition):
|
|
||||||
containers_found = True
|
|
||||||
if activate_quality and not activate_quality_type:
|
|
||||||
activate_quality_type = container.getMetaDataEntry("quality")
|
|
||||||
self._container_registry.removeContainer(container.getId())
|
|
||||||
|
|
||||||
if not containers_found:
|
|
||||||
Logger.log("d", "Unable to remove quality containers, as we did not find any by the name of %s", quality_name)
|
|
||||||
|
|
||||||
elif activate_quality:
|
|
||||||
definition_id = "fdmprinter" if not self._machine_manager.filterQualityByMachine else self._machine_manager.activeDefinitionId
|
|
||||||
containers = self._container_registry.findInstanceContainersMetadata(type = "quality", definition = definition_id, quality_type = activate_quality_type)
|
|
||||||
if containers:
|
|
||||||
self._machine_manager.setActiveQuality(containers[0]["id"])
|
|
||||||
self._machine_manager.activeQualityChanged.emit()
|
|
||||||
|
|
||||||
return containers_found
|
|
||||||
|
|
||||||
## Rename a set of quality changes containers.
|
|
||||||
#
|
|
||||||
# This will search for quality_changes containers matching the supplied name and rename them.
|
|
||||||
# Note that if the machine specifies that qualities should be filtered by machine and/or material
|
|
||||||
# only the containers related to the active machine/material are renamed.
|
|
||||||
#
|
|
||||||
# \param quality_name The name of the quality changes containers to rename.
|
|
||||||
# \param new_name The new name of the quality changes.
|
|
||||||
#
|
|
||||||
# \return True if successful, False if not.
|
|
||||||
@pyqtSlot(str, str, result = bool)
|
|
||||||
def renameQualityChanges(self, quality_name, new_name):
|
|
||||||
Logger.log("d", "User requested QualityChanges container rename of %s to %s", quality_name, new_name)
|
|
||||||
if not quality_name or not new_name:
|
|
||||||
return False
|
|
||||||
|
|
||||||
if quality_name == new_name:
|
|
||||||
Logger.log("w", "Unable to rename %s to %s, because they are the same.", quality_name, new_name)
|
|
||||||
return True
|
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return False
|
|
||||||
|
|
||||||
self._machine_manager.blurSettings.emit()
|
|
||||||
|
|
||||||
new_name = self._container_registry.uniqueName(new_name)
|
|
||||||
|
|
||||||
container_registry = self._container_registry
|
|
||||||
|
|
||||||
containers_to_rename = self._container_registry.findInstanceContainersMetadata(type = "quality_changes", name = quality_name)
|
|
||||||
|
|
||||||
for container in containers_to_rename:
|
|
||||||
stack_id = global_stack.getId()
|
|
||||||
if "extruder" in container:
|
|
||||||
stack_id = container["extruder"]
|
|
||||||
container_registry.renameContainer(container["id"], new_name, self._createUniqueId(stack_id, new_name))
|
|
||||||
|
|
||||||
if not containers_to_rename:
|
|
||||||
Logger.log("e", "Unable to rename %s, because we could not find the profile", quality_name)
|
|
||||||
|
|
||||||
self._machine_manager.activeQualityChanged.emit()
|
|
||||||
return True
|
|
||||||
|
|
||||||
## Duplicate a specified set of quality or quality_changes containers.
|
|
||||||
#
|
|
||||||
# This will search for containers matching the specified name. If the container is a "quality" type container, a new
|
|
||||||
# quality_changes container will be created with the specified quality as base. If the container is a "quality_changes"
|
|
||||||
# container, it is simply duplicated and renamed.
|
|
||||||
#
|
|
||||||
# \param quality_name The name of the quality to duplicate.
|
|
||||||
#
|
|
||||||
# \return A string containing the name of the duplicated containers, or an empty string if it failed.
|
|
||||||
@pyqtSlot(str, str, result = str)
|
|
||||||
def duplicateQualityOrQualityChanges(self, quality_name, base_name):
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack or not quality_name:
|
|
||||||
return ""
|
|
||||||
machine_definition = global_stack.definition
|
|
||||||
|
|
||||||
active_stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
|
||||||
if active_stacks is None:
|
|
||||||
return ""
|
|
||||||
material_metadatas = [stack.material.getMetaData() for stack in active_stacks]
|
|
||||||
|
|
||||||
result = self._duplicateQualityOrQualityChangesForMachineType(quality_name, base_name,
|
|
||||||
QualityManager.getInstance().getParentMachineDefinition(machine_definition),
|
|
||||||
material_metadatas)
|
|
||||||
return result[0].getName() if result else ""
|
|
||||||
|
|
||||||
## Duplicate a quality or quality changes profile specific to a machine type
|
|
||||||
#
|
|
||||||
# \param quality_name The name of the quality or quality changes container to duplicate.
|
|
||||||
# \param base_name The desired name for the new container.
|
|
||||||
# \param machine_definition The machine with the specific machine type.
|
|
||||||
# \param material_metadatas Metadata of materials
|
|
||||||
# \return List of duplicated quality profiles.
|
|
||||||
def _duplicateQualityOrQualityChangesForMachineType(self, quality_name: str, base_name: str, machine_definition: DefinitionContainer, material_metadatas: List[Dict[str, Any]]) -> List[InstanceContainer]:
|
|
||||||
Logger.log("d", "Attempting to duplicate the quality %s", quality_name)
|
|
||||||
|
|
||||||
if base_name is None:
|
|
||||||
base_name = quality_name
|
|
||||||
# Try to find a Quality with the name.
|
|
||||||
container = QualityManager.getInstance().findQualityByName(quality_name, machine_definition, material_metadatas)
|
|
||||||
if container:
|
|
||||||
Logger.log("d", "We found a quality to duplicate.")
|
|
||||||
return self._duplicateQualityForMachineType(container, base_name, machine_definition)
|
|
||||||
Logger.log("d", "We found a quality_changes to duplicate.")
|
|
||||||
# Assume it is a quality changes.
|
|
||||||
return self._duplicateQualityChangesForMachineType(quality_name, base_name, machine_definition)
|
|
||||||
|
|
||||||
# Duplicate a quality profile
|
|
||||||
def _duplicateQualityForMachineType(self, quality_container, base_name, machine_definition) -> List[InstanceContainer]:
|
|
||||||
if base_name is None:
|
|
||||||
base_name = quality_container.getName()
|
|
||||||
new_name = self._container_registry.uniqueName(base_name)
|
|
||||||
|
|
||||||
new_change_instances = []
|
|
||||||
|
|
||||||
# Handle the global stack first.
|
|
||||||
global_changes = self._createQualityChanges(quality_container, new_name, machine_definition, None)
|
|
||||||
new_change_instances.append(global_changes)
|
|
||||||
self._container_registry.addContainer(global_changes)
|
|
||||||
|
|
||||||
# Handle the extruders if present.
|
|
||||||
extruders = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
|
||||||
if extruders:
|
|
||||||
for extruder_id in extruders:
|
|
||||||
extruder = extruders[extruder_id]
|
|
||||||
new_changes = self._createQualityChanges(quality_container, new_name, machine_definition, extruder)
|
|
||||||
new_change_instances.append(new_changes)
|
|
||||||
self._container_registry.addContainer(new_changes)
|
|
||||||
|
|
||||||
return new_change_instances
|
|
||||||
|
|
||||||
# Duplicate a quality changes container
|
|
||||||
def _duplicateQualityChangesForMachineType(self, quality_changes_name, base_name, machine_definition) -> List[InstanceContainer]:
|
|
||||||
new_change_instances = []
|
|
||||||
for container in QualityManager.getInstance().findQualityChangesByName(quality_changes_name,
|
|
||||||
machine_definition):
|
|
||||||
base_id = container.getMetaDataEntry("extruder")
|
|
||||||
if not base_id:
|
|
||||||
base_id = container.getDefinition().getId()
|
|
||||||
new_unique_id = self._createUniqueId(base_id, base_name)
|
|
||||||
new_container = container.duplicate(new_unique_id, base_name)
|
|
||||||
new_change_instances.append(new_container)
|
|
||||||
self._container_registry.addContainer(new_container)
|
|
||||||
|
|
||||||
return new_change_instances
|
|
||||||
|
|
||||||
## Create a duplicate of a material, which has the same GUID and base_file metadata
|
|
||||||
#
|
|
||||||
# \return \type{str} the id of the newly created container.
|
|
||||||
@pyqtSlot(str, result = str)
|
|
||||||
def duplicateMaterial(self, material_id: str) -> str:
|
|
||||||
original = self._container_registry.findContainersMetadata(id = material_id)
|
|
||||||
if not original:
|
|
||||||
Logger.log("d", "Unable to duplicate the material with id %s, because it doesn't exist.", material_id)
|
|
||||||
return ""
|
|
||||||
original = original[0]
|
|
||||||
|
|
||||||
base_container_id = original.get("base_file")
|
|
||||||
base_container = self._container_registry.findContainers(id = base_container_id)
|
|
||||||
if not base_container:
|
|
||||||
Logger.log("d", "Unable to duplicate the material with id {material_id}, because base_file {base_container_id} doesn't exist.".format(material_id = material_id, base_container_id = base_container_id))
|
|
||||||
return ""
|
|
||||||
base_container = base_container[0]
|
|
||||||
|
|
||||||
#We'll copy all containers with the same base.
|
|
||||||
#This way the correct variant and machine still gets assigned when loading the copy of the material.
|
|
||||||
containers_to_copy = self._container_registry.findInstanceContainers(base_file = base_container_id)
|
|
||||||
|
|
||||||
# Ensure all settings are saved.
|
|
||||||
Application.getInstance().saveSettings()
|
|
||||||
|
|
||||||
# Create a new ID & container to hold the data.
|
|
||||||
new_containers = []
|
|
||||||
new_base_id = self._container_registry.uniqueName(base_container.getId())
|
|
||||||
new_base_container = copy.deepcopy(base_container)
|
|
||||||
new_base_container.getMetaData()["id"] = new_base_id
|
|
||||||
new_base_container.getMetaData()["base_file"] = new_base_id
|
|
||||||
new_containers.append(new_base_container)
|
|
||||||
|
|
||||||
#Clone all of them.
|
|
||||||
clone_of_original = None #Keeping track of which one is the clone of the original material, since we need to return that.
|
|
||||||
for container_to_copy in containers_to_copy:
|
|
||||||
#Create unique IDs for every clone.
|
|
||||||
current_id = container_to_copy.getId()
|
|
||||||
new_id = new_base_id
|
|
||||||
if container_to_copy.getMetaDataEntry("definition") != "fdmprinter":
|
|
||||||
new_id += "_" + container_to_copy.getMetaDataEntry("definition")
|
|
||||||
if container_to_copy.getMetaDataEntry("variant"):
|
|
||||||
variant = self._container_registry.findContainers(id = container_to_copy.getMetaDataEntry("variant"))[0]
|
|
||||||
new_id += "_" + variant.getName().replace(" ", "_")
|
|
||||||
if current_id == material_id:
|
|
||||||
clone_of_original = new_id
|
|
||||||
|
|
||||||
new_container = copy.deepcopy(container_to_copy)
|
|
||||||
new_container.getMetaData()["id"] = new_id
|
|
||||||
new_container.getMetaData()["base_file"] = new_base_id
|
|
||||||
new_containers.append(new_container)
|
|
||||||
|
|
||||||
for container_to_add in new_containers:
|
|
||||||
container_to_add.setDirty(True)
|
|
||||||
ContainerRegistry.getInstance().addContainer(container_to_add)
|
|
||||||
return self._getMaterialContainerIdForActiveMachine(clone_of_original)
|
|
||||||
|
|
||||||
## Create a duplicate of a material or it's original entry
|
|
||||||
#
|
|
||||||
# \return \type{str} the id of the newly created container.
|
|
||||||
@pyqtSlot(str, result = str)
|
|
||||||
def duplicateOriginalMaterial(self, material_id):
|
|
||||||
|
|
||||||
# check if the given material has a base file (i.e. was shipped by default)
|
|
||||||
base_file = self.getContainerMetaDataEntry(material_id, "base_file")
|
|
||||||
|
|
||||||
if base_file == "":
|
|
||||||
# there is no base file, so duplicate by ID
|
|
||||||
return self.duplicateMaterial(material_id)
|
|
||||||
else:
|
|
||||||
# there is a base file, so duplicate the original material
|
|
||||||
return self.duplicateMaterial(base_file)
|
|
||||||
|
|
||||||
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
|
|
||||||
#
|
|
||||||
# \return \type{str} the id of the newly created container.
|
|
||||||
@pyqtSlot(result = str)
|
|
||||||
def createMaterial(self) -> str:
|
|
||||||
# Ensure all settings are saved.
|
|
||||||
Application.getInstance().saveSettings()
|
|
||||||
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
approximate_diameter = str(round(global_stack.getProperty("material_diameter", "value")))
|
|
||||||
containers = self._container_registry.findInstanceContainersMetadata(id = "generic_pla*", approximate_diameter = approximate_diameter)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because it cannot be found for the material diameter for this machine.")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
base_file = containers[0].get("base_file")
|
|
||||||
containers = self._container_registry.findInstanceContainers(id = base_file)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("d", "Unable to create a new material by cloning Generic PLA, because the base file for Generic PLA for this machine can not be found.")
|
|
||||||
return ""
|
|
||||||
|
|
||||||
# Create a new ID & container to hold the data.
|
|
||||||
new_id = self._container_registry.uniqueName("custom_material")
|
|
||||||
container_type = type(containers[0]) # Always XMLMaterialProfile, since we specifically clone the base_file
|
|
||||||
duplicated_container = container_type(new_id)
|
|
||||||
|
|
||||||
# Instead of duplicating we load the data from the basefile again.
|
|
||||||
# This ensures that the inheritance goes well and all "cut up" subclasses of the xmlMaterial profile
|
|
||||||
# are also correctly created.
|
|
||||||
with open(containers[0].getPath(), encoding="utf-8") as f:
|
|
||||||
duplicated_container.deserialize(f.read())
|
|
||||||
|
|
||||||
duplicated_container.setMetaDataEntry("GUID", str(uuid.uuid4()))
|
|
||||||
duplicated_container.setMetaDataEntry("brand", catalog.i18nc("@label", "Custom"))
|
|
||||||
# We're defaulting to PLA, as machines with material profiles don't like material types they don't know.
|
|
||||||
# TODO: This is a hack, the only reason this is in now is to bandaid the problem as we're close to a release!
|
|
||||||
duplicated_container.setMetaDataEntry("material", "PLA")
|
|
||||||
duplicated_container.setName(catalog.i18nc("@label", "Custom Material"))
|
|
||||||
|
|
||||||
self._container_registry.addContainer(duplicated_container)
|
|
||||||
return self._getMaterialContainerIdForActiveMachine(new_id)
|
|
||||||
|
|
||||||
## Find the id of a material container based on the new material
|
|
||||||
# Utilty function that is shared between duplicateMaterial and createMaterial
|
|
||||||
#
|
|
||||||
# \param base_file \type{str} the id of the created container.
|
|
||||||
def _getMaterialContainerIdForActiveMachine(self, base_file):
|
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return base_file
|
|
||||||
|
|
||||||
has_machine_materials = parseBool(global_stack.getMetaDataEntry("has_machine_materials", default = False))
|
|
||||||
has_variant_materials = parseBool(global_stack.getMetaDataEntry("has_variant_materials", default = False))
|
|
||||||
has_variants = parseBool(global_stack.getMetaDataEntry("has_variants", default = False))
|
|
||||||
if has_machine_materials or has_variant_materials:
|
|
||||||
if has_variants:
|
|
||||||
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId(), variant = self._machine_manager.activeVariantId)
|
|
||||||
else:
|
|
||||||
materials = self._container_registry.findInstanceContainersMetadata(type = "material", base_file = base_file, definition = global_stack.getBottom().getId())
|
|
||||||
|
|
||||||
if materials:
|
|
||||||
return materials[0]["id"]
|
|
||||||
|
|
||||||
Logger.log("w", "Unable to find a suitable container based on %s for the current machine.", base_file)
|
|
||||||
return "" # do not activate a new material if a container can not be found
|
|
||||||
|
|
||||||
return base_file
|
|
||||||
|
|
||||||
## Get a list of materials that have the same GUID as the reference material
|
## Get a list of materials that have the same GUID as the reference material
|
||||||
#
|
#
|
||||||
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
# \param material_id \type{str} the id of the material for which to get the linked materials.
|
||||||
# \return \type{list} a list of names of materials with the same GUID
|
# \return \type{list} a list of names of materials with the same GUID
|
||||||
@pyqtSlot(str, result = "QStringList")
|
@pyqtSlot("QVariant", result = "QStringList")
|
||||||
def getLinkedMaterials(self, material_id: str):
|
def getLinkedMaterials(self, material_node):
|
||||||
containers = self._container_registry.findInstanceContainersMetadata(id = material_id)
|
guid = material_node.metadata["GUID"]
|
||||||
if not containers:
|
|
||||||
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't exist.", material_id)
|
|
||||||
return []
|
|
||||||
|
|
||||||
material_container = containers[0]
|
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
||||||
material_base_file = material_container.get("base_file", "")
|
|
||||||
material_guid = material_container.get("GUID", "")
|
|
||||||
if not material_guid:
|
|
||||||
Logger.log("d", "Unable to find materials linked to material with id %s, because it doesn't have a GUID.", material_id)
|
|
||||||
return []
|
|
||||||
|
|
||||||
containers = self._container_registry.findInstanceContainersMetadata(type = "material", GUID = material_guid)
|
|
||||||
linked_material_names = []
|
linked_material_names = []
|
||||||
for container in containers:
|
if material_group_list:
|
||||||
if container["id"] in [material_id, material_base_file] or container.get("base_file") != container["id"]:
|
for material_group in material_group_list:
|
||||||
continue
|
linked_material_names.append(material_group.root_material_node.metadata["name"])
|
||||||
|
|
||||||
linked_material_names.append(container["name"])
|
|
||||||
return linked_material_names
|
return linked_material_names
|
||||||
|
|
||||||
## Unlink a material from all other materials by creating a new GUID
|
## Unlink a material from all other materials by creating a new GUID
|
||||||
# \param material_id \type{str} the id of the material to create a new GUID for.
|
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot("QVariant")
|
||||||
def unlinkMaterial(self, material_id: str):
|
def unlinkMaterial(self, material_node):
|
||||||
containers = self._container_registry.findInstanceContainers(id=material_id)
|
# Get the material group
|
||||||
if not containers:
|
material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
|
||||||
Logger.log("d", "Unable to make the material with id %s unique, because it doesn't exist.", material_id)
|
|
||||||
return ""
|
|
||||||
|
|
||||||
containers[0].setMetaDataEntry("GUID", str(uuid.uuid4()))
|
# Generate a new GUID
|
||||||
|
new_guid = str(uuid.uuid4())
|
||||||
|
|
||||||
|
# Update the GUID
|
||||||
|
# NOTE: We only need to set the root material container because XmlMaterialProfile.setMetaDataEntry() will
|
||||||
|
# take care of the derived containers too
|
||||||
|
container = material_group.root_material_node.getContainer()
|
||||||
|
container.setMetaDataEntry("GUID", new_guid)
|
||||||
|
|
||||||
## Get the singleton instance for this class.
|
## Get the singleton instance for this class.
|
||||||
@classmethod
|
@classmethod
|
||||||
|
@ -1015,81 +444,6 @@ class ContainerManager(QObject):
|
||||||
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
||||||
self._container_name_filters[name_filter] = entry
|
self._container_name_filters[name_filter] = entry
|
||||||
|
|
||||||
## Creates a unique ID for a container by prefixing the name with the stack ID.
|
|
||||||
#
|
|
||||||
# This method creates a unique ID for a container by prefixing it with a specified stack ID.
|
|
||||||
# This is done to ensure we have an easily identified ID for quality changes, which have the
|
|
||||||
# same name across several stacks.
|
|
||||||
#
|
|
||||||
# \param stack_id The ID of the stack to prepend.
|
|
||||||
# \param container_name The name of the container that we are creating a unique ID for.
|
|
||||||
#
|
|
||||||
# \return Container name prefixed with stack ID, in lower case with spaces replaced by underscores.
|
|
||||||
def _createUniqueId(self, stack_id, container_name):
|
|
||||||
result = stack_id + "_" + container_name
|
|
||||||
result = result.lower()
|
|
||||||
result.replace(" ", "_")
|
|
||||||
return result
|
|
||||||
|
|
||||||
## Create a quality changes container for a specified quality container.
|
|
||||||
#
|
|
||||||
# \param quality_container The quality container to create a changes container for.
|
|
||||||
# \param new_name The name of the new quality_changes container.
|
|
||||||
# \param machine_definition The machine definition this quality changes container is specific to.
|
|
||||||
# \param extruder_id
|
|
||||||
#
|
|
||||||
# \return A new quality_changes container with the specified container as base.
|
|
||||||
def _createQualityChanges(self, quality_container, new_name, machine_definition, extruder_id):
|
|
||||||
base_id = machine_definition.getId() if extruder_id is None else extruder_id
|
|
||||||
|
|
||||||
# Create a new quality_changes container for the quality.
|
|
||||||
quality_changes = InstanceContainer(self._createUniqueId(base_id, new_name))
|
|
||||||
quality_changes.setName(new_name)
|
|
||||||
quality_changes.addMetaDataEntry("type", "quality_changes")
|
|
||||||
quality_changes.addMetaDataEntry("quality_type", quality_container.getMetaDataEntry("quality_type"))
|
|
||||||
|
|
||||||
# If we are creating a container for an extruder, ensure we add that to the container
|
|
||||||
if extruder_id is not None:
|
|
||||||
quality_changes.addMetaDataEntry("extruder", extruder_id)
|
|
||||||
|
|
||||||
# If the machine specifies qualities should be filtered, ensure we match the current criteria.
|
|
||||||
if not machine_definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
quality_changes.setDefinition("fdmprinter")
|
|
||||||
else:
|
|
||||||
quality_changes.setDefinition(QualityManager.getInstance().getParentMachineDefinition(machine_definition).getId())
|
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
quality_changes.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
|
||||||
return quality_changes
|
|
||||||
|
|
||||||
|
|
||||||
## Import profiles from a list of file_urls.
|
|
||||||
# Each QUrl item must end with .curaprofile, or it will not be imported.
|
|
||||||
#
|
|
||||||
# \param QVariant<QUrl>, essentially a list with QUrl objects.
|
|
||||||
# \return Dict with keys status, text
|
|
||||||
@pyqtSlot("QVariantList", result="QVariantMap")
|
|
||||||
def importProfiles(self, file_urls):
|
|
||||||
status = "ok"
|
|
||||||
results = {"ok": [], "error": []}
|
|
||||||
for file_url in file_urls:
|
|
||||||
if not file_url.isValid():
|
|
||||||
continue
|
|
||||||
path = file_url.toLocalFile()
|
|
||||||
if not path:
|
|
||||||
continue
|
|
||||||
if not path.endswith(".curaprofile"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
single_result = self._container_registry.importProfile(path)
|
|
||||||
if single_result["status"] == "error":
|
|
||||||
status = "error"
|
|
||||||
results[single_result["status"]].append(single_result["message"])
|
|
||||||
|
|
||||||
return {
|
|
||||||
"status": status,
|
|
||||||
"message": "\n".join(results["ok"] + results["error"])}
|
|
||||||
|
|
||||||
## Import single profile, file_url does not have to end with curaprofile
|
## Import single profile, file_url does not have to end with curaprofile
|
||||||
@pyqtSlot(QUrl, result="QVariantMap")
|
@pyqtSlot(QUrl, result="QVariantMap")
|
||||||
def importProfile(self, file_url):
|
def importProfile(self, file_url):
|
||||||
|
@ -1100,11 +454,13 @@ class ContainerManager(QObject):
|
||||||
return
|
return
|
||||||
return self._container_registry.importProfile(path)
|
return self._container_registry.importProfile(path)
|
||||||
|
|
||||||
@pyqtSlot("QVariantList", QUrl, str)
|
@pyqtSlot(QObject, QUrl, str)
|
||||||
def exportProfile(self, instance_id: str, file_url: QUrl, file_type: str) -> None:
|
def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
|
||||||
if not file_url.isValid():
|
if not file_url.isValid():
|
||||||
return
|
return
|
||||||
path = file_url.toLocalFile()
|
path = file_url.toLocalFile()
|
||||||
if not path:
|
if not path:
|
||||||
return
|
return
|
||||||
self._container_registry.exportProfile(instance_id, path, file_type)
|
|
||||||
|
container_list = [n.getContainer() for n in quality_changes_group.getAllNodes()]
|
||||||
|
self._container_registry.exportQualityProfile(container_list, path, file_type)
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Qt.ListModel import ListModel
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot, QUrl
|
|
||||||
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
|
||||||
|
|
||||||
class ContainerSettingsModel(ListModel):
|
|
||||||
LabelRole = Qt.UserRole + 1
|
|
||||||
CategoryRole = Qt.UserRole + 2
|
|
||||||
UnitRole = Qt.UserRole + 3
|
|
||||||
ValuesRole = Qt.UserRole + 4
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.addRoleName(self.LabelRole, "label")
|
|
||||||
self.addRoleName(self.CategoryRole, "category")
|
|
||||||
self.addRoleName(self.UnitRole, "unit")
|
|
||||||
self.addRoleName(self.ValuesRole, "values")
|
|
||||||
|
|
||||||
self._container_ids = []
|
|
||||||
self._containers = []
|
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
|
||||||
if property_name == "value":
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
items = []
|
|
||||||
|
|
||||||
if len(self._container_ids) == 0:
|
|
||||||
return
|
|
||||||
|
|
||||||
keys = []
|
|
||||||
for container in self._containers:
|
|
||||||
keys = keys + list(container.getAllKeys())
|
|
||||||
|
|
||||||
keys = list(set(keys)) # remove duplicate keys
|
|
||||||
|
|
||||||
for key in keys:
|
|
||||||
definition = None
|
|
||||||
category = None
|
|
||||||
values = []
|
|
||||||
for container in self._containers:
|
|
||||||
instance = container.getInstance(key)
|
|
||||||
if instance:
|
|
||||||
definition = instance.definition
|
|
||||||
|
|
||||||
# Traverse up to find the category
|
|
||||||
category = definition
|
|
||||||
while category.type != "category":
|
|
||||||
category = category.parent
|
|
||||||
|
|
||||||
value = container.getProperty(key, "value")
|
|
||||||
if type(value) == SettingFunction:
|
|
||||||
values.append("=\u0192")
|
|
||||||
else:
|
|
||||||
values.append(container.getProperty(key, "value"))
|
|
||||||
else:
|
|
||||||
values.append("")
|
|
||||||
|
|
||||||
items.append({
|
|
||||||
"key": key,
|
|
||||||
"values": values,
|
|
||||||
"label": definition.label,
|
|
||||||
"unit": definition.unit,
|
|
||||||
"category": category.label
|
|
||||||
})
|
|
||||||
items.sort(key = lambda k: (k["category"], k["key"]))
|
|
||||||
self.setItems(items)
|
|
||||||
|
|
||||||
## Set the ids of the containers which have the settings this model should list.
|
|
||||||
# Also makes sure the model updates when the containers have property changes
|
|
||||||
def setContainers(self, container_ids):
|
|
||||||
for container in self._containers:
|
|
||||||
container.propertyChanged.disconnect(self._onPropertyChanged)
|
|
||||||
|
|
||||||
self._container_ids = container_ids
|
|
||||||
self._containers = []
|
|
||||||
|
|
||||||
for container_id in self._container_ids:
|
|
||||||
containers = ContainerRegistry.getInstance().findContainers(id = container_id)
|
|
||||||
if containers:
|
|
||||||
containers[0].propertyChanged.connect(self._onPropertyChanged)
|
|
||||||
self._containers.append(containers[0])
|
|
||||||
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
containersChanged = pyqtSignal()
|
|
||||||
@pyqtProperty("QVariantList", fset = setContainers, notify = containersChanged)
|
|
||||||
def containers(self):
|
|
||||||
return self.container_ids
|
|
|
@ -25,14 +25,15 @@ from UM.Resources import Resources
|
||||||
|
|
||||||
from . import ExtruderStack
|
from . import ExtruderStack
|
||||||
from . import GlobalStack
|
from . import GlobalStack
|
||||||
from .ContainerManager import ContainerManager
|
|
||||||
from .ExtruderManager import ExtruderManager
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
class CuraContainerRegistry(ContainerRegistry):
|
class CuraContainerRegistry(ContainerRegistry):
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
|
@ -102,7 +103,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
# \param instance_ids \type{list} the IDs of the profiles to export.
|
# \param instance_ids \type{list} the IDs of the profiles to export.
|
||||||
# \param file_name \type{str} the full path and filename to export to.
|
# \param file_name \type{str} the full path and filename to export to.
|
||||||
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
# \param file_type \type{str} the file type with the format "<description> (*.<extension>)"
|
||||||
def exportProfile(self, instance_ids, file_name, file_type):
|
def exportQualityProfile(self, container_list, file_name, file_type):
|
||||||
# Parse the fileType to deduce what plugin can save the file format.
|
# Parse the fileType to deduce what plugin can save the file format.
|
||||||
# fileType has the format "<description> (*.<extension>)"
|
# fileType has the format "<description> (*.<extension>)"
|
||||||
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
split = file_type.rfind(" (*.") # Find where the description ends and the extension starts.
|
||||||
|
@ -121,31 +122,10 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
catalog.i18nc("@label Don't translate the XML tag <filename>!", "The file <filename>{0}</filename> already exists. Are you sure you want to overwrite it?").format(file_name))
|
||||||
if result == QMessageBox.No:
|
if result == QMessageBox.No:
|
||||||
return
|
return
|
||||||
found_containers = []
|
|
||||||
extruder_positions = []
|
|
||||||
for instance_id in instance_ids:
|
|
||||||
containers = ContainerRegistry.getInstance().findInstanceContainers(id = instance_id)
|
|
||||||
if containers:
|
|
||||||
found_containers.append(containers[0])
|
|
||||||
|
|
||||||
# Determine the position of the extruder of this container
|
|
||||||
extruder_id = containers[0].getMetaDataEntry("extruder", "")
|
|
||||||
if extruder_id == "":
|
|
||||||
# Global stack
|
|
||||||
extruder_positions.append(-1)
|
|
||||||
else:
|
|
||||||
extruder_containers = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = extruder_id)
|
|
||||||
if extruder_containers:
|
|
||||||
extruder_positions.append(int(extruder_containers[0].get("position", 0)))
|
|
||||||
else:
|
|
||||||
extruder_positions.append(0)
|
|
||||||
# Ensure the profiles are always exported in order (global, extruder 0, extruder 1, ...)
|
|
||||||
found_containers = [containers for (positions, containers) in sorted(zip(extruder_positions, found_containers))]
|
|
||||||
|
|
||||||
profile_writer = self._findProfileWriter(extension, description)
|
profile_writer = self._findProfileWriter(extension, description)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
success = profile_writer.write(file_name, found_containers)
|
success = profile_writer.write(file_name, container_list)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
Logger.log("e", "Failed to export profile to %s: %s", file_name, str(e))
|
||||||
m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)),
|
m = Message(catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to export profile to <filename>{0}</filename>: <message>{1}</message>", file_name, str(e)),
|
||||||
|
@ -289,7 +269,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
|
|
||||||
elif profile_index < len(machine_extruders) + 1:
|
elif profile_index < len(machine_extruders) + 1:
|
||||||
# This is assumed to be an extruder profile
|
# This is assumed to be an extruder profile
|
||||||
extruder_id = Application.getInstance().getMachineManager().getQualityDefinitionId(machine_extruders[profile_index - 1].getBottom())
|
extruder_id = machine_extruders[profile_index - 1].definition.getId()
|
||||||
if not profile.getMetaDataEntry("extruder"):
|
if not profile.getMetaDataEntry("extruder"):
|
||||||
profile.addMetaDataEntry("extruder", extruder_id)
|
profile.addMetaDataEntry("extruder", extruder_id)
|
||||||
else:
|
else:
|
||||||
|
@ -348,39 +328,16 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
return catalog.i18nc("@info:status", "Profile is missing a quality type.")
|
||||||
|
|
||||||
quality_type_criteria = {"quality_type": quality_type}
|
quality_type_criteria = {"quality_type": quality_type}
|
||||||
if self._machineHasOwnQualities():
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
profile.setDefinition(self._activeQualityDefinition().getId())
|
definition_id = getMachineDefinitionIDForQualitySearch(global_stack)
|
||||||
if self._machineHasOwnMaterials():
|
profile.setDefinition(definition_id)
|
||||||
active_material_id = self._activeMaterialId()
|
|
||||||
if active_material_id and active_material_id != "empty": # only update if there is an active material
|
|
||||||
profile.addMetaDataEntry("material", active_material_id)
|
|
||||||
quality_type_criteria["material"] = active_material_id
|
|
||||||
|
|
||||||
quality_type_criteria["definition"] = profile.getDefinition().getId()
|
|
||||||
|
|
||||||
else:
|
|
||||||
profile.setDefinition("fdmprinter")
|
|
||||||
quality_type_criteria["definition"] = "fdmprinter"
|
|
||||||
|
|
||||||
machine_definition = Application.getInstance().getGlobalContainerStack().getBottom()
|
|
||||||
del quality_type_criteria["definition"]
|
|
||||||
|
|
||||||
# materials = None
|
|
||||||
|
|
||||||
if "material" in quality_type_criteria:
|
|
||||||
# materials = ContainerRegistry.getInstance().findInstanceContainers(id = quality_type_criteria["material"])
|
|
||||||
del quality_type_criteria["material"]
|
|
||||||
|
|
||||||
# Do not filter quality containers here with materials because we are trying to import a profile, so it should
|
|
||||||
# NOT be restricted by the active materials on the current machine.
|
|
||||||
materials = None
|
|
||||||
|
|
||||||
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
# Check to make sure the imported profile actually makes sense in context of the current configuration.
|
||||||
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
# This prevents issues where importing a "draft" profile for a machine without "draft" qualities would report as
|
||||||
# successfully imported but then fail to show up.
|
# successfully imported but then fail to show up.
|
||||||
from cura.QualityManager import QualityManager
|
quality_manager = CuraApplication.getInstance()._quality_manager
|
||||||
qualities = QualityManager.getInstance()._getFilteredContainersForStack(machine_definition, materials, **quality_type_criteria)
|
quality_group_dict = quality_manager.getQualityGroupsForMachineDefinition(global_stack)
|
||||||
if not qualities:
|
if quality_type not in quality_group_dict:
|
||||||
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
return catalog.i18nc("@info:status", "Could not find a quality type {0} for the current configuration.", quality_type)
|
||||||
|
|
||||||
ContainerRegistry.getInstance().addContainer(profile)
|
ContainerRegistry.getInstance().addContainer(profile)
|
||||||
|
@ -400,18 +357,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
result.append( (plugin_id, meta_data) )
|
result.append( (plugin_id, meta_data) )
|
||||||
return result
|
return result
|
||||||
|
|
||||||
## Get the definition to use to select quality profiles for the active machine
|
|
||||||
# \return the active quality definition object or None if there is no quality definition
|
|
||||||
def _activeQualityDefinition(self):
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if global_container_stack:
|
|
||||||
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(global_container_stack.getBottom())
|
|
||||||
definition = self.findDefinitionContainers(id = definition_id)[0]
|
|
||||||
|
|
||||||
if definition:
|
|
||||||
return definition
|
|
||||||
return None
|
|
||||||
|
|
||||||
## Returns true if the current machine requires its own materials
|
## Returns true if the current machine requires its own materials
|
||||||
# \return True if the current machine requires its own materials
|
# \return True if the current machine requires its own materials
|
||||||
def _machineHasOwnMaterials(self):
|
def _machineHasOwnMaterials(self):
|
||||||
|
@ -507,8 +452,6 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
extruder_stack.setDefinition(extruder_definition)
|
extruder_stack.setDefinition(extruder_definition)
|
||||||
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
extruder_stack.addMetaDataEntry("position", extruder_definition.getMetaDataEntry("position"))
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
|
|
||||||
# create a new definition_changes container for the extruder stack
|
# create a new definition_changes container for the extruder stack
|
||||||
definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
|
definition_changes_id = self.uniqueName(extruder_stack.getId() + "_settings") if create_new_ids else extruder_stack.getId() + "_settings"
|
||||||
definition_changes_name = definition_changes_id
|
definition_changes_name = definition_changes_id
|
||||||
|
@ -567,26 +510,28 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
self.addContainer(user_container)
|
self.addContainer(user_container)
|
||||||
extruder_stack.setUserChanges(user_container)
|
extruder_stack.setUserChanges(user_container)
|
||||||
|
|
||||||
variant_id = "default"
|
application = CuraApplication.getInstance()
|
||||||
|
empty_variant = application.empty_variant_container
|
||||||
|
empty_material = application.empty_material_container
|
||||||
|
empty_quality = application.empty_quality_container
|
||||||
|
|
||||||
if machine.variant.getId() not in ("empty", "empty_variant"):
|
if machine.variant.getId() not in ("empty", "empty_variant"):
|
||||||
variant_id = machine.variant.getId()
|
variant = machine.variant
|
||||||
else:
|
else:
|
||||||
variant_id = "empty_variant"
|
variant = empty_variant
|
||||||
extruder_stack.setVariantById(variant_id)
|
extruder_stack.variant = variant
|
||||||
|
|
||||||
material_id = "default"
|
|
||||||
if machine.material.getId() not in ("empty", "empty_material"):
|
if machine.material.getId() not in ("empty", "empty_material"):
|
||||||
material_id = machine.material.getId()
|
material = machine.material
|
||||||
else:
|
else:
|
||||||
material_id = "empty_material"
|
material = empty_material
|
||||||
extruder_stack.setMaterialById(material_id)
|
extruder_stack.material = material
|
||||||
|
|
||||||
quality_id = "default"
|
|
||||||
if machine.quality.getId() not in ("empty", "empty_quality"):
|
if machine.quality.getId() not in ("empty", "empty_quality"):
|
||||||
quality_id = machine.quality.getId()
|
quality = machine.quality
|
||||||
else:
|
else:
|
||||||
quality_id = "empty_quality"
|
quality = empty_quality
|
||||||
extruder_stack.setQualityById(quality_id)
|
extruder_stack.quality = quality
|
||||||
|
|
||||||
machine_quality_changes = machine.qualityChanges
|
machine_quality_changes = machine.qualityChanges
|
||||||
if new_global_quality_changes is not None:
|
if new_global_quality_changes is not None:
|
||||||
|
@ -598,7 +543,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
extruder_quality_changes_container = extruder_quality_changes_container[0]
|
extruder_quality_changes_container = extruder_quality_changes_container[0]
|
||||||
|
|
||||||
quality_changes_id = extruder_quality_changes_container.getId()
|
quality_changes_id = extruder_quality_changes_container.getId()
|
||||||
extruder_stack.setQualityChangesById(quality_changes_id)
|
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
|
||||||
else:
|
else:
|
||||||
# Some extruder quality_changes containers can be created at runtime as files in the qualities
|
# Some extruder quality_changes containers can be created at runtime as files in the qualities
|
||||||
# folder. Those files won't be loaded in the registry immediately. So we also need to search
|
# folder. Those files won't be loaded in the registry immediately. So we also need to search
|
||||||
|
@ -607,7 +552,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
if extruder_quality_changes_container:
|
if extruder_quality_changes_container:
|
||||||
quality_changes_id = extruder_quality_changes_container.getId()
|
quality_changes_id = extruder_quality_changes_container.getId()
|
||||||
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
|
extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
|
||||||
extruder_stack.setQualityChangesById(quality_changes_id)
|
extruder_stack.qualityChanges = self.findInstanceContainers(id = quality_changes_id)[0]
|
||||||
else:
|
else:
|
||||||
# if we still cannot find a quality changes container for the extruder, create a new one
|
# if we still cannot find a quality changes container for the extruder, create a new one
|
||||||
container_name = machine_quality_changes.getName()
|
container_name = machine_quality_changes.getName()
|
||||||
|
@ -642,7 +587,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
||||||
|
|
||||||
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
|
machine_quality_changes.removeInstance(qc_setting_key, postpone_emit=True)
|
||||||
else:
|
else:
|
||||||
extruder_stack.setQualityChangesById("empty_quality_changes")
|
extruder_stack.qualityChanges = self.findInstanceContainers(id = "empty_quality_changes")[0]
|
||||||
|
|
||||||
self.addContainer(extruder_stack)
|
self.addContainer(extruder_stack)
|
||||||
|
|
||||||
|
|
|
@ -83,20 +83,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
|
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
|
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the quality changes container by an ID.
|
|
||||||
#
|
|
||||||
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
|
||||||
#
|
|
||||||
# \param new_quality_changes_id The ID of the new quality changes container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setQualityChangesById(self, new_quality_changes_id: str) -> None:
|
|
||||||
quality_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_changes_id)
|
|
||||||
if quality_changes:
|
|
||||||
self.setQualityChanges(quality_changes[0])
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_changes_id))
|
|
||||||
|
|
||||||
## Get the quality changes container.
|
## Get the quality changes container.
|
||||||
#
|
#
|
||||||
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -110,31 +96,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
|
def setQuality(self, new_quality: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the quality container by an ID.
|
|
||||||
#
|
|
||||||
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
|
||||||
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
|
||||||
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultQuality
|
|
||||||
# for details.
|
|
||||||
#
|
|
||||||
# \param new_quality_id The ID of the new quality container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setQualityById(self, new_quality_id: str) -> None:
|
|
||||||
quality = self._empty_quality
|
|
||||||
if new_quality_id == "default":
|
|
||||||
new_quality = self.findDefaultQuality()
|
|
||||||
if new_quality:
|
|
||||||
quality = new_quality
|
|
||||||
else:
|
|
||||||
qualities = ContainerRegistry.getInstance().findInstanceContainers(id = new_quality_id)
|
|
||||||
if qualities:
|
|
||||||
quality = qualities[0]
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_quality_id))
|
|
||||||
|
|
||||||
self.setQuality(quality)
|
|
||||||
|
|
||||||
## Get the quality container.
|
## Get the quality container.
|
||||||
#
|
#
|
||||||
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -148,31 +109,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
|
def setMaterial(self, new_material: InstanceContainer, postpone_emit = False) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
||||||
|
|
||||||
## Set the material container by an ID.
|
|
||||||
#
|
|
||||||
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
|
||||||
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
|
||||||
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultMaterial
|
|
||||||
# for details.
|
|
||||||
#
|
|
||||||
# \param new_material_id The ID of the new material container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setMaterialById(self, new_material_id: str) -> None:
|
|
||||||
material = self._empty_material
|
|
||||||
if new_material_id == "default":
|
|
||||||
new_material = self.findDefaultMaterial()
|
|
||||||
if new_material:
|
|
||||||
material = new_material
|
|
||||||
else:
|
|
||||||
materials = ContainerRegistry.getInstance().findInstanceContainers(id = new_material_id)
|
|
||||||
if materials:
|
|
||||||
material = materials[0]
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_material_id))
|
|
||||||
|
|
||||||
self.setMaterial(material)
|
|
||||||
|
|
||||||
## Get the material container.
|
## Get the material container.
|
||||||
#
|
#
|
||||||
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -186,31 +122,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setVariant(self, new_variant: InstanceContainer) -> None:
|
def setVariant(self, new_variant: InstanceContainer) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
|
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
|
||||||
|
|
||||||
## Set the variant container by an ID.
|
|
||||||
#
|
|
||||||
# This will search for the specified container and set it. If no container was found, an error will be raised.
|
|
||||||
# There is a special value for ID, which is "default". The "default" value indicates the quality should be set
|
|
||||||
# to whatever the machine definition specifies as "preferred" container, or a fallback value. See findDefaultVariant
|
|
||||||
# for details.
|
|
||||||
#
|
|
||||||
# \param new_variant_id The ID of the new variant container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setVariantById(self, new_variant_id: str) -> None:
|
|
||||||
variant = self._empty_variant
|
|
||||||
if new_variant_id == "default":
|
|
||||||
new_variant = self.findDefaultVariantBuildplate() if self.getMetaDataEntry("type") == "machine" else self.findDefaultVariant()
|
|
||||||
if new_variant:
|
|
||||||
variant = new_variant
|
|
||||||
else:
|
|
||||||
variants = ContainerRegistry.getInstance().findInstanceContainers(id = new_variant_id)
|
|
||||||
if variants:
|
|
||||||
variant = variants[0]
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_variant_id))
|
|
||||||
|
|
||||||
self.setVariant(variant)
|
|
||||||
|
|
||||||
## Get the variant container.
|
## Get the variant container.
|
||||||
#
|
#
|
||||||
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -224,18 +135,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
|
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
|
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
|
||||||
|
|
||||||
## Set the definition changes container by an ID.
|
|
||||||
#
|
|
||||||
# \param new_definition_changes_id The ID of the new definition changes container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setDefinitionChangesById(self, new_definition_changes_id: str) -> None:
|
|
||||||
new_definition_changes = ContainerRegistry.getInstance().findInstanceContainers(id = new_definition_changes_id)
|
|
||||||
if new_definition_changes:
|
|
||||||
self.setDefinitionChanges(new_definition_changes[0])
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_changes_id))
|
|
||||||
|
|
||||||
## Get the definition changes container.
|
## Get the definition changes container.
|
||||||
#
|
#
|
||||||
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -249,18 +148,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
|
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
|
||||||
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
|
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
|
||||||
|
|
||||||
## Set the definition container by an ID.
|
|
||||||
#
|
|
||||||
# \param new_definition_id The ID of the new definition container.
|
|
||||||
#
|
|
||||||
# \throws Exceptions.InvalidContainerError Raised when no container could be found with the specified ID.
|
|
||||||
def setDefinitionById(self, new_definition_id: str) -> None:
|
|
||||||
new_definition = ContainerRegistry.getInstance().findDefinitionContainers(id = new_definition_id)
|
|
||||||
if new_definition:
|
|
||||||
self.setDefinition(new_definition[0])
|
|
||||||
else:
|
|
||||||
raise Exceptions.InvalidContainerError("Could not find container with id {id}".format(id = new_definition_id))
|
|
||||||
|
|
||||||
## Get the definition container.
|
## Get the definition container.
|
||||||
#
|
#
|
||||||
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||||
|
@ -348,6 +235,10 @@ class CuraContainerStack(ContainerStack):
|
||||||
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
|
elif container != self._empty_instance_container and container.getMetaDataEntry("type") != expected_type:
|
||||||
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
|
raise Exceptions.InvalidContainerError("Cannot replace container at index {index} with a container that is not of {type} type, but {actual_type} type.".format(index = index, type = expected_type, actual_type = container.getMetaDataEntry("type")))
|
||||||
|
|
||||||
|
current_container = self._containers[index]
|
||||||
|
if current_container.getId() == container.getId():
|
||||||
|
return
|
||||||
|
|
||||||
super().replaceContainer(index, container, postpone_emit)
|
super().replaceContainer(index, container, postpone_emit)
|
||||||
|
|
||||||
## Overridden from ContainerStack
|
## Overridden from ContainerStack
|
||||||
|
@ -391,243 +282,6 @@ class CuraContainerStack(ContainerStack):
|
||||||
|
|
||||||
self._containers = new_containers
|
self._containers = new_containers
|
||||||
|
|
||||||
## Find the variant that should be used as "default" variant.
|
|
||||||
#
|
|
||||||
# This will search for variants that match the current definition and pick the preferred one,
|
|
||||||
# if specified by the machine definition.
|
|
||||||
#
|
|
||||||
# The following criteria are used to find the default variant:
|
|
||||||
# - If the machine definition does not have a metadata entry "has_variants" set to True, return None
|
|
||||||
# - The definition of the variant should be the same as the machine definition for this stack.
|
|
||||||
# - The container should have a metadata entry "type" with value "variant".
|
|
||||||
# - If the machine definition has a metadata entry "preferred_variant", filter the variant IDs based on that.
|
|
||||||
#
|
|
||||||
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
|
|
||||||
#
|
|
||||||
# \note This method assumes the stack has a valid machine definition.
|
|
||||||
def findDefaultVariant(self) -> Optional[ContainerInterface]:
|
|
||||||
definition = self._getMachineDefinition()
|
|
||||||
# has_variants can be overridden in other containers and stacks.
|
|
||||||
# In the case of UM2, it is overridden in the GlobalStack
|
|
||||||
if not self.getMetaDataEntry("has_variants"):
|
|
||||||
# If the machine does not use variants, we should never set a variant.
|
|
||||||
return None
|
|
||||||
|
|
||||||
# First add any variant. Later, overwrite with preference if the preference is valid.
|
|
||||||
variant = None
|
|
||||||
definition_id = self._findInstanceContainerDefinitionId(definition)
|
|
||||||
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant")
|
|
||||||
if variants:
|
|
||||||
variant = variants[0]
|
|
||||||
|
|
||||||
preferred_variant_id = definition.getMetaDataEntry("preferred_variant")
|
|
||||||
if preferred_variant_id:
|
|
||||||
preferred_variants = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_id, definition = definition_id, type = "variant")
|
|
||||||
if preferred_variants:
|
|
||||||
variant = preferred_variants[0]
|
|
||||||
else:
|
|
||||||
Logger.log("w", "The preferred variant \"{variant}\" of stack {stack} does not exist or is not a variant.", variant = preferred_variant_id, stack = self.id)
|
|
||||||
# And leave it at the default variant.
|
|
||||||
|
|
||||||
if variant:
|
|
||||||
return variant
|
|
||||||
|
|
||||||
Logger.log("w", "Could not find a valid default variant for stack {stack}", stack = self.id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
## Find the global variant that should be used as "default". This is used for the buildplates.
|
|
||||||
#
|
|
||||||
# This will search for variants that match the current definition and pick the preferred one,
|
|
||||||
# if specified by the machine definition.
|
|
||||||
#
|
|
||||||
# The following criteria are used to find the default global variant:
|
|
||||||
# - If the machine definition does not have a metadata entry "has_variant_buildplates" set to True, return None
|
|
||||||
# - The definition of the variant should be the same as the machine definition for this stack.
|
|
||||||
# - The container should have a metadata entry "type" with value "variant" and "hardware_type" with value "buildplate".
|
|
||||||
# - If the machine definition has a metadata entry "preferred_variant_buildplate", filter the variant IDs based on that.
|
|
||||||
#
|
|
||||||
# \return The container that should be used as default, or None if nothing was found or the machine does not use variants.
|
|
||||||
#
|
|
||||||
# \note This method assumes the stack has a valid machine definition.
|
|
||||||
def findDefaultVariantBuildplate(self) -> Optional[ContainerInterface]:
|
|
||||||
definition = self._getMachineDefinition()
|
|
||||||
# has_variant_buildplates can be overridden in other containers and stacks.
|
|
||||||
# In the case of UM2, it is overridden in the GlobalStack
|
|
||||||
if not self.getMetaDataEntry("has_variant_buildplates"):
|
|
||||||
# If the machine does not use variants, we should never set a variant.
|
|
||||||
return None
|
|
||||||
|
|
||||||
# First add any variant. Later, overwrite with preference if the preference is valid.
|
|
||||||
variant = None
|
|
||||||
definition_id = self._findInstanceContainerDefinitionId(definition)
|
|
||||||
variants = ContainerRegistry.getInstance().findInstanceContainers(definition = definition_id, type = "variant", hardware_type = "buildplate")
|
|
||||||
if variants:
|
|
||||||
variant = variants[0]
|
|
||||||
|
|
||||||
preferred_variant_buildplate_id = definition.getMetaDataEntry("preferred_variant_buildplate")
|
|
||||||
if preferred_variant_buildplate_id:
|
|
||||||
preferred_variant_buildplates = ContainerRegistry.getInstance().findInstanceContainers(id = preferred_variant_buildplate_id, definition = definition_id, type = "variant")
|
|
||||||
if preferred_variant_buildplates:
|
|
||||||
variant = preferred_variant_buildplates[0]
|
|
||||||
else:
|
|
||||||
Logger.log("w", "The preferred variant buildplate \"{variant}\" of stack {stack} does not exist or is not a variant.",
|
|
||||||
variant = preferred_variant_buildplate_id, stack = self.id)
|
|
||||||
# And leave it at the default variant.
|
|
||||||
|
|
||||||
if variant:
|
|
||||||
return variant
|
|
||||||
|
|
||||||
Logger.log("w", "Could not find a valid default buildplate variant for stack {stack}", stack = self.id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
## Find the material that should be used as "default" material.
|
|
||||||
#
|
|
||||||
# This will search for materials that match the current definition and pick the preferred one,
|
|
||||||
# if specified by the machine definition.
|
|
||||||
#
|
|
||||||
# The following criteria are used to find the default material:
|
|
||||||
# - If the machine definition does not have a metadata entry "has_materials" set to True, return None
|
|
||||||
# - If the machine definition has a metadata entry "has_machine_materials", the definition of the material should
|
|
||||||
# be the same as the machine definition for this stack. Otherwise, the definition should be "fdmprinter".
|
|
||||||
# - The container should have a metadata entry "type" with value "material".
|
|
||||||
# - The material should have an approximate diameter that matches the machine
|
|
||||||
# - If the machine definition has a metadata entry "has_variants" and set to True, the "variant" metadata entry of
|
|
||||||
# the material should be the same as the ID of the variant in the stack. Only applies if "has_machine_materials" is also True.
|
|
||||||
# - If the stack currently has a material set, try to find a material that matches the current material by name.
|
|
||||||
# - Otherwise, if the machine definition has a metadata entry "preferred_material", try to find a material that matches the specified ID.
|
|
||||||
#
|
|
||||||
# \return The container that should be used as default, or None if nothing was found or the machine does not use materials.
|
|
||||||
def findDefaultMaterial(self) -> Optional[ContainerInterface]:
|
|
||||||
definition = self._getMachineDefinition()
|
|
||||||
if not definition.getMetaDataEntry("has_materials"):
|
|
||||||
# Machine does not use materials, never try to set it.
|
|
||||||
return None
|
|
||||||
|
|
||||||
search_criteria = {"type": "material"}
|
|
||||||
if definition.getMetaDataEntry("has_machine_materials"):
|
|
||||||
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_variants"):
|
|
||||||
search_criteria["variant"] = self.variant.id
|
|
||||||
else:
|
|
||||||
search_criteria["definition"] = "fdmprinter"
|
|
||||||
|
|
||||||
if self.material != self._empty_material:
|
|
||||||
search_criteria["name"] = self.material.name
|
|
||||||
else:
|
|
||||||
preferred_material = definition.getMetaDataEntry("preferred_material")
|
|
||||||
if preferred_material:
|
|
||||||
search_criteria["id"] = preferred_material
|
|
||||||
|
|
||||||
approximate_material_diameter = str(round(self.getProperty("material_diameter", "value")))
|
|
||||||
search_criteria["approximate_diameter"] = approximate_material_diameter
|
|
||||||
|
|
||||||
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
|
||||||
if not materials:
|
|
||||||
Logger.log("w", "The preferred material \"{material}\" could not be found for stack {stack}", material = preferred_material, stack = self.id)
|
|
||||||
# We failed to find any materials matching the specified criteria, drop some specific criteria and try to find
|
|
||||||
# a material that sort-of matches what we want.
|
|
||||||
search_criteria.pop("variant", None)
|
|
||||||
search_criteria.pop("id", None)
|
|
||||||
search_criteria.pop("name", None)
|
|
||||||
materials = ContainerRegistry.getInstance().findInstanceContainers(**search_criteria)
|
|
||||||
|
|
||||||
if not materials:
|
|
||||||
Logger.log("w", "Could not find a valid material for stack {stack}", stack = self.id)
|
|
||||||
return None
|
|
||||||
|
|
||||||
for material in materials:
|
|
||||||
# Prefer a read-only material
|
|
||||||
if ContainerRegistry.getInstance().isReadOnly(material.getId()):
|
|
||||||
return material
|
|
||||||
|
|
||||||
return materials[0]
|
|
||||||
|
|
||||||
|
|
||||||
## Find the quality that should be used as "default" quality.
|
|
||||||
#
|
|
||||||
# This will search for qualities that match the current definition and pick the preferred one,
|
|
||||||
# if specified by the machine definition.
|
|
||||||
#
|
|
||||||
# \return The container that should be used as default, or None if nothing was found.
|
|
||||||
def findDefaultQuality(self) -> Optional[ContainerInterface]:
|
|
||||||
definition = self._getMachineDefinition()
|
|
||||||
registry = ContainerRegistry.getInstance()
|
|
||||||
material_container = self.material if self.material.getId() not in (self._empty_material.getId(), self._empty_instance_container.getId()) else None
|
|
||||||
|
|
||||||
search_criteria = {"type": "quality"}
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_materials") and material_container:
|
|
||||||
search_criteria["material"] = material_container.id
|
|
||||||
else:
|
|
||||||
search_criteria["definition"] = "fdmprinter"
|
|
||||||
|
|
||||||
if self.quality != self._empty_quality:
|
|
||||||
search_criteria["name"] = self.quality.name
|
|
||||||
else:
|
|
||||||
preferred_quality = definition.getMetaDataEntry("preferred_quality")
|
|
||||||
if preferred_quality:
|
|
||||||
search_criteria["id"] = preferred_quality
|
|
||||||
|
|
||||||
containers = registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
if "material" in search_criteria:
|
|
||||||
# First check if we can solve our material not found problem by checking if we can find quality containers
|
|
||||||
# that are assigned to the parents of this material profile.
|
|
||||||
try:
|
|
||||||
inherited_files = material_container.getInheritedFiles()
|
|
||||||
except AttributeError: # Material_container does not support inheritance.
|
|
||||||
inherited_files = []
|
|
||||||
|
|
||||||
if inherited_files:
|
|
||||||
for inherited_file in inherited_files:
|
|
||||||
# Extract the ID from the path we used to load the file.
|
|
||||||
search_criteria["material"] = os.path.basename(inherited_file).split(".")[0]
|
|
||||||
containers = registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
# We still weren't able to find a quality for this specific material.
|
|
||||||
# Try to find qualities for a generic version of the material.
|
|
||||||
material_search_criteria = {"type": "material", "material": material_container.getMetaDataEntry("material"), "color_name": "Generic"}
|
|
||||||
if definition.getMetaDataEntry("has_machine_quality"):
|
|
||||||
if self.material != self._empty_instance_container:
|
|
||||||
material_search_criteria["definition"] = material_container.getMetaDataEntry("definition")
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_variants"):
|
|
||||||
material_search_criteria["variant"] = material_container.getMetaDataEntry("variant")
|
|
||||||
else:
|
|
||||||
material_search_criteria["definition"] = self._findInstanceContainerDefinitionId(definition)
|
|
||||||
|
|
||||||
if definition.getMetaDataEntry("has_variants") and self.variant != self._empty_instance_container:
|
|
||||||
material_search_criteria["variant"] = self.variant.id
|
|
||||||
else:
|
|
||||||
material_search_criteria["definition"] = "fdmprinter"
|
|
||||||
material_containers = registry.findInstanceContainersMetadata(**material_search_criteria)
|
|
||||||
# Try all materials to see if there is a quality profile available.
|
|
||||||
for material_container in material_containers:
|
|
||||||
search_criteria["material"] = material_container["id"]
|
|
||||||
|
|
||||||
containers = registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
if "name" in search_criteria or "id" in search_criteria:
|
|
||||||
# If a quality by this name can not be found, try a wider set of search criteria
|
|
||||||
search_criteria.pop("name", None)
|
|
||||||
search_criteria.pop("id", None)
|
|
||||||
|
|
||||||
containers = registry.findInstanceContainers(**search_criteria)
|
|
||||||
if containers:
|
|
||||||
return containers[0]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
## protected:
|
## protected:
|
||||||
|
|
||||||
# Helper to make sure we emit a PyQt signal on container changes.
|
# Helper to make sure we emit a PyQt signal on container changes.
|
||||||
|
|
|
@ -1,15 +1,18 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
# Copyright (c) 2018 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from typing import Optional
|
||||||
|
|
||||||
|
from UM.Logger import Logger
|
||||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
|
from cura.Machines.VariantManager import VariantType
|
||||||
from .GlobalStack import GlobalStack
|
from .GlobalStack import GlobalStack
|
||||||
from .ExtruderStack import ExtruderStack
|
from .ExtruderStack import ExtruderStack
|
||||||
from typing import Optional
|
|
||||||
|
|
||||||
|
|
||||||
## Contains helper functions to create new machines.
|
## Contains helper functions to create new machines.
|
||||||
|
@ -22,7 +25,13 @@ class CuraStackBuilder:
|
||||||
# \return The new global stack or None if an error occurred.
|
# \return The new global stack or None if an error occurred.
|
||||||
@classmethod
|
@classmethod
|
||||||
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
variant_manager = application.getVariantManager()
|
||||||
|
material_manager = application.getMaterialManager()
|
||||||
|
quality_manager = application.getQualityManager()
|
||||||
registry = ContainerRegistry.getInstance()
|
registry = ContainerRegistry.getInstance()
|
||||||
|
|
||||||
definitions = registry.findDefinitionContainers(id = definition_id)
|
definitions = registry.findDefinitionContainers(id = definition_id)
|
||||||
if not definitions:
|
if not definitions:
|
||||||
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
|
Logger.log("w", "Definition {definition} was not found!", definition = definition_id)
|
||||||
|
@ -30,7 +39,21 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
machine_definition = definitions[0]
|
machine_definition = definitions[0]
|
||||||
|
|
||||||
generated_name = registry.createUniqueName("machine", "", name, machine_definition.name)
|
# get variant container for the global stack
|
||||||
|
global_variant_container = application.empty_variant_container
|
||||||
|
global_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.BUILD_PLATE)
|
||||||
|
if global_variant_node:
|
||||||
|
global_variant_container = global_variant_node.getContainer()
|
||||||
|
|
||||||
|
# get variant container for extruders
|
||||||
|
extruder_variant_container = application.empty_variant_container
|
||||||
|
extruder_variant_node = variant_manager.getDefaultVariantNode(machine_definition, VariantType.NOZZLE)
|
||||||
|
extruder_variant_name = None
|
||||||
|
if extruder_variant_node:
|
||||||
|
extruder_variant_container = extruder_variant_node.getContainer()
|
||||||
|
extruder_variant_name = extruder_variant_container.getName()
|
||||||
|
|
||||||
|
generated_name = registry.createUniqueName("machine", "", name, machine_definition.getName())
|
||||||
# Make sure the new name does not collide with any definition or (quality) profile
|
# Make sure the new name does not collide with any definition or (quality) profile
|
||||||
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
|
# createUniqueName() only looks at other stacks, but not at definitions or quality profiles
|
||||||
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
|
# Note that we don't go for uniqueName() immediately because that function matches with ignore_case set to true
|
||||||
|
@ -40,49 +63,55 @@ class CuraStackBuilder:
|
||||||
new_global_stack = cls.createGlobalStack(
|
new_global_stack = cls.createGlobalStack(
|
||||||
new_stack_id = generated_name,
|
new_stack_id = generated_name,
|
||||||
definition = machine_definition,
|
definition = machine_definition,
|
||||||
quality = "default",
|
variant_container = global_variant_container,
|
||||||
material = "default",
|
material_container = application.empty_material_container,
|
||||||
variant = "default",
|
quality_container = application.empty_quality_container,
|
||||||
)
|
)
|
||||||
|
|
||||||
new_global_stack.setName(generated_name)
|
new_global_stack.setName(generated_name)
|
||||||
|
|
||||||
extruder_definition = registry.findDefinitionContainers(machine = machine_definition.getId())
|
# get material container for extruders
|
||||||
|
material_container = application.empty_material_container
|
||||||
|
material_node = material_manager.getDefaultMaterial(new_global_stack, extruder_variant_name)
|
||||||
|
if material_node:
|
||||||
|
material_container = material_node.getContainer()
|
||||||
|
|
||||||
if not extruder_definition:
|
# Create ExtruderStacks
|
||||||
# create extruder stack for single extrusion machines that have no separate extruder definition files
|
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
||||||
extruder_definition = registry.findDefinitionContainers(id = "fdmextruder")[0]
|
|
||||||
new_extruder_id = registry.uniqueName(machine_definition.getName() + " " + extruder_definition.id)
|
for position, extruder_definition_id in extruder_dict.items():
|
||||||
|
# Sanity check: make sure that the positions in the extruder definitions are same as in the machine
|
||||||
|
# definition
|
||||||
|
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
||||||
|
position_in_extruder_def = extruder_definition.getMetaDataEntry("position")
|
||||||
|
if position_in_extruder_def != position:
|
||||||
|
raise RuntimeError("Extruder position [%s] defined in extruder definition [%s] is not the same as in machine definition [%s] position [%s]" %
|
||||||
|
(position_in_extruder_def, extruder_definition_id, definition_id, position))
|
||||||
|
|
||||||
|
new_extruder_id = registry.uniqueName(extruder_definition_id)
|
||||||
new_extruder = cls.createExtruderStack(
|
new_extruder = cls.createExtruderStack(
|
||||||
new_extruder_id,
|
new_extruder_id,
|
||||||
definition = extruder_definition,
|
extruder_definition = extruder_definition,
|
||||||
machine_definition_id = machine_definition.getId(),
|
machine_definition_id = definition_id,
|
||||||
quality = "default",
|
position = position,
|
||||||
material = "default",
|
variant_container = extruder_variant_container,
|
||||||
variant = "default",
|
material_container = material_container,
|
||||||
next_stack = new_global_stack
|
quality_container = application.empty_quality_container,
|
||||||
|
global_stack = new_global_stack,
|
||||||
)
|
)
|
||||||
|
new_extruder.setNextStack(new_global_stack)
|
||||||
new_global_stack.addExtruder(new_extruder)
|
new_global_stack.addExtruder(new_extruder)
|
||||||
registry.addContainer(new_extruder)
|
registry.addContainer(new_extruder)
|
||||||
else:
|
|
||||||
# create extruder stack for each found extruder definition
|
|
||||||
for extruder_definition in registry.findDefinitionContainers(machine = machine_definition.id):
|
|
||||||
position = extruder_definition.getMetaDataEntry("position", None)
|
|
||||||
if not position:
|
|
||||||
Logger.log("w", "Extruder definition %s specifies no position metadata entry.", extruder_definition.id)
|
|
||||||
|
|
||||||
new_extruder_id = registry.uniqueName(extruder_definition.id)
|
preferred_quality_type = machine_definition.getMetaDataEntry("preferred_quality_type")
|
||||||
new_extruder = cls.createExtruderStack(
|
quality_group_dict = quality_manager.getQualityGroups(new_global_stack)
|
||||||
new_extruder_id,
|
quality_group = quality_group_dict.get(preferred_quality_type)
|
||||||
definition = extruder_definition,
|
|
||||||
machine_definition_id = machine_definition.getId(),
|
new_global_stack.quality = quality_group.node_for_global.getContainer()
|
||||||
quality = "default",
|
for position, extruder_stack in new_global_stack.extruders.items():
|
||||||
material = "default",
|
if position in quality_group.nodes_for_extruders:
|
||||||
variant = "default",
|
extruder_stack.quality = quality_group.nodes_for_extruders[position].getContainer()
|
||||||
next_stack = new_global_stack
|
else:
|
||||||
)
|
extruder_stack.quality = application.empty_quality_container
|
||||||
new_global_stack.addExtruder(new_extruder)
|
|
||||||
registry.addContainer(new_extruder)
|
|
||||||
|
|
||||||
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
|
# Register the global stack after the extruder stacks are created. This prevents the registry from adding another
|
||||||
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
|
# extruder stack because the global stack didn't have one yet (which is enforced since Cura 3.1).
|
||||||
|
@ -100,43 +129,27 @@ class CuraStackBuilder:
|
||||||
#
|
#
|
||||||
# \return A new Global stack instance with the specified parameters.
|
# \return A new Global stack instance with the specified parameters.
|
||||||
@classmethod
|
@classmethod
|
||||||
def createExtruderStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, machine_definition_id: str, **kwargs) -> ExtruderStack:
|
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
|
||||||
stack = ExtruderStack(new_stack_id)
|
position: int,
|
||||||
stack.setName(definition.getName())
|
variant_container, material_container, quality_container, global_stack) -> ExtruderStack:
|
||||||
stack.setDefinition(definition)
|
|
||||||
stack.addMetaDataEntry("position", definition.getMetaDataEntry("position"))
|
|
||||||
|
|
||||||
if "next_stack" in kwargs:
|
|
||||||
# Add stacks before containers are added, since they may trigger a setting update.
|
|
||||||
stack.setNextStack(kwargs["next_stack"])
|
|
||||||
|
|
||||||
user_container = InstanceContainer(new_stack_id + "_user")
|
|
||||||
user_container.addMetaDataEntry("type", "user")
|
|
||||||
user_container.addMetaDataEntry("extruder", new_stack_id)
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
application = CuraApplication.getInstance()
|
||||||
user_container.setDefinition(machine_definition_id)
|
|
||||||
|
|
||||||
stack.setUserChanges(user_container)
|
stack = ExtruderStack(new_stack_id, parent = global_stack)
|
||||||
|
stack.setName(extruder_definition.getName())
|
||||||
|
stack.setDefinition(extruder_definition)
|
||||||
|
|
||||||
# Important! The order here matters, because that allows the stack to
|
stack.addMetaDataEntry("position", position)
|
||||||
# assume the material and variant have already been set.
|
|
||||||
if "definition_changes" in kwargs:
|
|
||||||
stack.setDefinitionChangesById(kwargs["definition_changes"])
|
|
||||||
else:
|
|
||||||
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
|
|
||||||
|
|
||||||
if "variant" in kwargs:
|
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
|
||||||
stack.setVariantById(kwargs["variant"])
|
is_global_stack = False)
|
||||||
|
|
||||||
if "material" in kwargs:
|
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
|
||||||
stack.setMaterialById(kwargs["material"])
|
stack.variant = variant_container
|
||||||
|
stack.material = material_container
|
||||||
if "quality" in kwargs:
|
stack.quality = quality_container
|
||||||
stack.setQualityById(kwargs["quality"])
|
stack.qualityChanges = application.empty_quality_changes_container
|
||||||
|
stack.userChanges = user_container
|
||||||
if "quality_changes" in kwargs:
|
|
||||||
stack.setQualityChangesById(kwargs["quality_changes"])
|
|
||||||
|
|
||||||
# Only add the created containers to the registry after we have set all the other
|
# Only add the created containers to the registry after we have set all the other
|
||||||
# properties. This makes the create operation more transactional, since any problems
|
# properties. This makes the create operation more transactional, since any problems
|
||||||
|
@ -153,44 +166,48 @@ class CuraStackBuilder:
|
||||||
#
|
#
|
||||||
# \return A new Global stack instance with the specified parameters.
|
# \return A new Global stack instance with the specified parameters.
|
||||||
@classmethod
|
@classmethod
|
||||||
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface, **kwargs) -> GlobalStack:
|
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
||||||
|
variant_container, material_container, quality_container) -> GlobalStack:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
|
||||||
stack = GlobalStack(new_stack_id)
|
stack = GlobalStack(new_stack_id)
|
||||||
stack.setDefinition(definition)
|
stack.setDefinition(definition)
|
||||||
|
|
||||||
user_container = InstanceContainer(new_stack_id + "_user")
|
# Create user container
|
||||||
user_container.addMetaDataEntry("type", "user")
|
user_container = cls.createUserChangesContainer(new_stack_id + "_user", definition.getId(), new_stack_id,
|
||||||
user_container.addMetaDataEntry("machine", new_stack_id)
|
is_global_stack = True)
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
user_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
|
||||||
user_container.setDefinition(definition.getId())
|
|
||||||
|
|
||||||
stack.setUserChanges(user_container)
|
stack.definitionChanges = cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings")
|
||||||
|
stack.variant = variant_container
|
||||||
# Important! The order here matters, because that allows the stack to
|
stack.material = material_container
|
||||||
# assume the material and variant have already been set.
|
stack.quality = quality_container
|
||||||
if "definition_changes" in kwargs:
|
stack.qualityChanges = application.empty_quality_changes_container
|
||||||
stack.setDefinitionChangesById(kwargs["definition_changes"])
|
stack.userChanges = user_container
|
||||||
else:
|
|
||||||
stack.setDefinitionChanges(cls.createDefinitionChangesContainer(stack, new_stack_id + "_settings"))
|
|
||||||
|
|
||||||
if "variant" in kwargs:
|
|
||||||
stack.setVariantById(kwargs["variant"])
|
|
||||||
|
|
||||||
if "material" in kwargs:
|
|
||||||
stack.setMaterialById(kwargs["material"])
|
|
||||||
|
|
||||||
if "quality" in kwargs:
|
|
||||||
stack.setQualityById(kwargs["quality"])
|
|
||||||
|
|
||||||
if "quality_changes" in kwargs:
|
|
||||||
stack.setQualityChangesById(kwargs["quality_changes"])
|
|
||||||
|
|
||||||
ContainerRegistry.getInstance().addContainer(user_container)
|
ContainerRegistry.getInstance().addContainer(user_container)
|
||||||
|
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def createDefinitionChangesContainer(cls, container_stack, container_name, container_index = None):
|
def createUserChangesContainer(cls, container_name: str, definition_id: str, stack_id: str,
|
||||||
|
is_global_stack: bool) -> "InstanceContainer":
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
|
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
|
||||||
|
|
||||||
|
container = InstanceContainer(unique_container_name)
|
||||||
|
container.setDefinition(definition_id)
|
||||||
|
container.addMetaDataEntry("type", "user")
|
||||||
|
container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
|
||||||
|
|
||||||
|
metadata_key_to_add = "machine" if is_global_stack else "extruder"
|
||||||
|
container.addMetaDataEntry(metadata_key_to_add, stack_id)
|
||||||
|
|
||||||
|
return container
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def createDefinitionChangesContainer(cls, container_stack, container_name):
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
|
unique_container_name = ContainerRegistry.getInstance().uniqueName(container_name)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from UM.Scene.Selection import Selection
|
||||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||||
from UM.Settings.SettingFunction import SettingFunction
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
from UM.Settings.SettingInstance import SettingInstance
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||||
from typing import Optional, List, TYPE_CHECKING, Union
|
from typing import Optional, List, TYPE_CHECKING, Union
|
||||||
|
@ -30,28 +31,22 @@ class ExtruderManager(QObject):
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self._application = Application.getInstance()
|
||||||
|
|
||||||
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||||
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
self._active_extruder_index = -1 # Indicates the index of the active extruder stack. -1 means no active extruder stack
|
||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = []
|
||||||
self._global_container_stack_definition_id = None
|
|
||||||
self._addCurrentMachineExtruders()
|
self._addCurrentMachineExtruders()
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self.__globalContainerStackChanged)
|
#Application.getInstance().globalContainerStackChanged.connect(self._globalContainerStackChanged)
|
||||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||||
|
|
||||||
## Signal to notify other components when the list of extruders for a machine definition changes.
|
## Signal to notify other components when the list of extruders for a machine definition changes.
|
||||||
extrudersChanged = pyqtSignal(QVariant)
|
extrudersChanged = pyqtSignal(QVariant)
|
||||||
|
|
||||||
## Signal to notify other components when the global container stack is switched to a definition
|
|
||||||
# that has different extruders than the previous global container stack
|
|
||||||
globalContainerStackDefinitionChanged = pyqtSignal()
|
|
||||||
|
|
||||||
## Notify when the user switches the currently active extruder.
|
## Notify when the user switches the currently active extruder.
|
||||||
activeExtruderChanged = pyqtSignal()
|
activeExtruderChanged = pyqtSignal()
|
||||||
|
|
||||||
## The signal notifies subscribers if extruders are added
|
|
||||||
extrudersAdded = pyqtSignal()
|
|
||||||
|
|
||||||
## Gets the unique identifier of the currently active extruder stack.
|
## Gets the unique identifier of the currently active extruder stack.
|
||||||
#
|
#
|
||||||
# The currently active extruder stack is the stack that is currently being
|
# The currently active extruder stack is the stack that is currently being
|
||||||
|
@ -184,6 +179,7 @@ class ExtruderManager(QObject):
|
||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = []
|
||||||
self.selectedObjectExtrudersChanged.emit()
|
self.selectedObjectExtrudersChanged.emit()
|
||||||
|
|
||||||
|
@pyqtSlot(result = QObject)
|
||||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
|
||||||
|
@ -371,12 +367,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
return result[:machine_extruder_count]
|
return result[:machine_extruder_count]
|
||||||
|
|
||||||
def __globalContainerStackChanged(self) -> None:
|
def _globalContainerStackChanged(self) -> None:
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if global_container_stack and global_container_stack.getBottom() and global_container_stack.getBottom().getId() != self._global_container_stack_definition_id:
|
|
||||||
self._global_container_stack_definition_id = global_container_stack.getBottom().getId()
|
|
||||||
self.globalContainerStackDefinitionChanged.emit()
|
|
||||||
|
|
||||||
# If the global container changed, the machine changed and might have extruders that were not registered yet
|
# If the global container changed, the machine changed and might have extruders that were not registered yet
|
||||||
self._addCurrentMachineExtruders()
|
self._addCurrentMachineExtruders()
|
||||||
|
|
||||||
|
@ -384,7 +375,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
## Adds the extruders of the currently active machine.
|
## Adds the extruders of the currently active machine.
|
||||||
def _addCurrentMachineExtruders(self) -> None:
|
def _addCurrentMachineExtruders(self) -> None:
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
extruders_changed = False
|
extruders_changed = False
|
||||||
|
|
||||||
if global_stack:
|
if global_stack:
|
||||||
|
@ -404,20 +395,82 @@ class ExtruderManager(QObject):
|
||||||
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
self._extruder_trains[global_stack_id][extruder_train.getMetaDataEntry("position")] = extruder_train
|
||||||
|
|
||||||
# regardless of what the next stack is, we have to set it again, because of signal routing. ???
|
# regardless of what the next stack is, we have to set it again, because of signal routing. ???
|
||||||
|
extruder_train.setParent(global_stack)
|
||||||
extruder_train.setNextStack(global_stack)
|
extruder_train.setNextStack(global_stack)
|
||||||
extruders_changed = True
|
extruders_changed = True
|
||||||
|
|
||||||
# FIX: We have to remove those settings here because we know that those values have been copied to all
|
self._fixMaterialDiameterAndNozzleSize(global_stack, extruder_trains)
|
||||||
# the extruders at this point.
|
|
||||||
for key in ("material_diameter", "machine_nozzle_size"):
|
|
||||||
if global_stack.definitionChanges.hasProperty(key, "value"):
|
|
||||||
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
|
||||||
|
|
||||||
if extruders_changed:
|
if extruders_changed:
|
||||||
self.extrudersChanged.emit(global_stack_id)
|
self.extrudersChanged.emit(global_stack_id)
|
||||||
self.extrudersAdded.emit()
|
|
||||||
self.setActiveExtruderIndex(0)
|
self.setActiveExtruderIndex(0)
|
||||||
|
|
||||||
|
#
|
||||||
|
# This function tries to fix the problem with per-extruder-settable nozzle size and material diameter problems
|
||||||
|
# in early versions (3.0 - 3.2.1).
|
||||||
|
#
|
||||||
|
# In earlier versions, "nozzle size" and "material diameter" are only applicable to the complete machine, so all
|
||||||
|
# extruders share the same values. In this case, "nozzle size" and "material diameter" are saved in the
|
||||||
|
# GlobalStack's DefinitionChanges container.
|
||||||
|
#
|
||||||
|
# Later, we could have different "nozzle size" for each extruder, but "material diameter" could only be set for
|
||||||
|
# the entire machine. In this case, "nozzle size" should be saved in each ExtruderStack's DefinitionChanges, but
|
||||||
|
# "material diameter" still remains in the GlobalStack's DefinitionChanges.
|
||||||
|
#
|
||||||
|
# Lateer, both "nozzle size" and "material diameter" are settable per-extruder, and both settings should be saved
|
||||||
|
# in the ExtruderStack's DefinitionChanges.
|
||||||
|
#
|
||||||
|
# There were some bugs in upgrade so the data weren't saved correct as described above. This function tries fix
|
||||||
|
# this.
|
||||||
|
#
|
||||||
|
# One more thing is about material diameter and single-extrusion machines. Most single-extrusion machines don't
|
||||||
|
# specifically define their extruder definition, so they reuse "fdmextruder", but for those machines, they may
|
||||||
|
# define "material diameter = 1.75" in their machine definition, but in "fdmextruder", it's still "2.85". This
|
||||||
|
# causes a problem with incorrect default values.
|
||||||
|
#
|
||||||
|
# This is also fixed here in this way: If no "material diameter" is specified, it will look for the default value
|
||||||
|
# in both the Extruder's definition and the Global's definition. If 2 values don't match, we will use the value
|
||||||
|
# from the Global definition by setting it in the Extruder's DefinitionChanges container.
|
||||||
|
#
|
||||||
|
def _fixMaterialDiameterAndNozzleSize(self, global_stack, extruder_stack_list):
|
||||||
|
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
|
||||||
|
|
||||||
|
extruder_positions_to_update = set()
|
||||||
|
for extruder_stack in extruder_stack_list:
|
||||||
|
for key in keys_to_copy:
|
||||||
|
# Only copy the value when this extruder doesn't have the value.
|
||||||
|
if extruder_stack.definitionChanges.hasProperty(key, "value"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
setting_value_in_global_def_changes = global_stack.definitionChanges.getProperty(key, "value")
|
||||||
|
setting_value_in_global_def = global_stack.definition.getProperty(key, "value")
|
||||||
|
setting_value = setting_value_in_global_def
|
||||||
|
if setting_value_in_global_def_changes is not None:
|
||||||
|
setting_value = setting_value_in_global_def_changes
|
||||||
|
if setting_value == extruder_stack.definition.getProperty(key, "value"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
setting_definition = global_stack.getSettingDefinition(key)
|
||||||
|
new_instance = SettingInstance(setting_definition, extruder_stack.definitionChanges)
|
||||||
|
new_instance.setProperty("value", setting_value)
|
||||||
|
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||||
|
extruder_stack.definitionChanges.addInstance(new_instance)
|
||||||
|
extruder_stack.definitionChanges.setDirty(True)
|
||||||
|
|
||||||
|
# Make sure the material diameter is up to date for the extruder stack.
|
||||||
|
if key == "material_diameter":
|
||||||
|
position = int(extruder_stack.getMetaDataEntry("position"))
|
||||||
|
extruder_positions_to_update.add(position)
|
||||||
|
|
||||||
|
# We have to remove those settings here because we know that those values have been copied to all
|
||||||
|
# the extruders at this point.
|
||||||
|
for key in keys_to_copy:
|
||||||
|
if global_stack.definitionChanges.hasProperty(key, "value"):
|
||||||
|
global_stack.definitionChanges.removeInstance(key, postpone_emit = True)
|
||||||
|
|
||||||
|
# Update material diameter for extruders
|
||||||
|
for position in extruder_positions_to_update:
|
||||||
|
self.updateMaterialForDiameter(position, global_stack = global_stack)
|
||||||
|
|
||||||
## Get all extruder values for a certain setting.
|
## Get all extruder values for a certain setting.
|
||||||
#
|
#
|
||||||
# This is exposed to SettingFunction so it can be used in value functions.
|
# This is exposed to SettingFunction so it can be used in value functions.
|
||||||
|
@ -503,10 +556,11 @@ class ExtruderManager(QObject):
|
||||||
return ExtruderManager.getExtruderValues(key)
|
return ExtruderManager.getExtruderValues(key)
|
||||||
|
|
||||||
## Updates the material container to a material that matches the material diameter set for the printer
|
## Updates the material container to a material that matches the material diameter set for the printer
|
||||||
def updateMaterialForDiameter(self, extruder_position: int):
|
def updateMaterialForDiameter(self, extruder_position: int, global_stack = None):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
if not global_stack:
|
||||||
|
return
|
||||||
|
|
||||||
if not global_stack.getMetaDataEntry("has_materials", False):
|
if not global_stack.getMetaDataEntry("has_materials", False):
|
||||||
return
|
return
|
||||||
|
|
|
@ -3,16 +3,16 @@
|
||||||
|
|
||||||
from typing import Any, TYPE_CHECKING, Optional
|
from typing import Any, TYPE_CHECKING, Optional
|
||||||
|
|
||||||
from UM.Application import Application
|
from PyQt5.QtCore import pyqtProperty
|
||||||
|
|
||||||
from UM.Decorators import override
|
from UM.Decorators import override
|
||||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||||
from UM.Settings.ContainerStack import ContainerStack
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
from UM.Settings.Interfaces import ContainerInterface, PropertyEvaluationContext
|
||||||
from UM.Settings.SettingInstance import SettingInstance
|
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
from .CuraContainerStack import CuraContainerStack
|
from .CuraContainerStack import CuraContainerStack, _ContainerIndexes
|
||||||
from .ExtruderManager import ExtruderManager
|
from .ExtruderManager import ExtruderManager
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
|
@ -34,7 +34,7 @@ class ExtruderStack(CuraContainerStack):
|
||||||
#
|
#
|
||||||
# This will set the next stack and ensure that we register this stack as an extruder.
|
# This will set the next stack and ensure that we register this stack as an extruder.
|
||||||
@override(ContainerStack)
|
@override(ContainerStack)
|
||||||
def setNextStack(self, stack: ContainerStack) -> None:
|
def setNextStack(self, stack: CuraContainerStack) -> None:
|
||||||
super().setNextStack(stack)
|
super().setNextStack(stack)
|
||||||
stack.addExtruder(self)
|
stack.addExtruder(self)
|
||||||
self.addMetaDataEntry("machine", stack.id)
|
self.addMetaDataEntry("machine", stack.id)
|
||||||
|
@ -42,75 +42,6 @@ class ExtruderStack(CuraContainerStack):
|
||||||
# For backward compatibility: Register the extruder with the Extruder Manager
|
# For backward compatibility: Register the extruder with the Extruder Manager
|
||||||
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
ExtruderManager.getInstance().registerExtruder(self, stack.id)
|
||||||
|
|
||||||
# Now each machine will have at least one extruder stack. If this is the first extruder, the extruder-specific
|
|
||||||
# settings such as nozzle size and material diameter should be moved from the machine's definition_changes to
|
|
||||||
# the this extruder's definition_changes.
|
|
||||||
#
|
|
||||||
# We do this here because it is tooooo expansive to do it in the version upgrade: During the version upgrade,
|
|
||||||
# when we are upgrading a definition_changes container file, there is NO guarantee that other files such as
|
|
||||||
# machine an extruder stack files are upgraded before this, so we cannot read those files assuming they are in
|
|
||||||
# the latest format.
|
|
||||||
#
|
|
||||||
# MORE:
|
|
||||||
# For single-extrusion machines, nozzle size is saved in the global stack, so the nozzle size value should be
|
|
||||||
# carried to the first extruder.
|
|
||||||
# For material diameter, it was supposed to be applied to all extruders, so its value should be copied to all
|
|
||||||
# extruders.
|
|
||||||
|
|
||||||
keys_to_copy = ["material_diameter", "machine_nozzle_size"] # these will be copied over to all extruders
|
|
||||||
|
|
||||||
for key in keys_to_copy:
|
|
||||||
# Only copy the value when this extruder doesn't have the value.
|
|
||||||
if self.definitionChanges.hasProperty(key, "value"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# WARNING: this might be very dangerous and should be refactored ASAP!
|
|
||||||
#
|
|
||||||
# We cannot add a setting definition of "material_diameter" into the extruder's definition at runtime
|
|
||||||
# because all other machines which uses "fdmextruder" as the extruder definition will be affected.
|
|
||||||
#
|
|
||||||
# The problem is that single extrusion machines have their default material diameter defined in the global
|
|
||||||
# definitions. Now we automatically create an extruder stack for those machines using "fdmextruder"
|
|
||||||
# definition, which doesn't have the specific "material_diameter" and "machine_nozzle_size" defined for
|
|
||||||
# each machine. This results in wrong values which can be found in the MachineSettings dialog.
|
|
||||||
#
|
|
||||||
# To solve this, we put "material_diameter" back into the "fdmextruder" definition because modifying it in
|
|
||||||
# the extruder definition will affect all machines which uses the "fdmextruder" definition. Moreover, now
|
|
||||||
# we also check the value defined in the machine definition. If present, the value defined in the global
|
|
||||||
# stack's definition changes container will be copied. Otherwise, we will check if the default values in the
|
|
||||||
# machine definition and the extruder definition are the same, and if not, the default value in the machine
|
|
||||||
# definition will be copied to the extruder stack's definition changes.
|
|
||||||
#
|
|
||||||
setting_value_in_global_def_changes = stack.definitionChanges.getProperty(key, "value")
|
|
||||||
setting_value_in_global_def = stack.definition.getProperty(key, "value")
|
|
||||||
setting_value = setting_value_in_global_def
|
|
||||||
if setting_value_in_global_def_changes is not None:
|
|
||||||
setting_value = setting_value_in_global_def_changes
|
|
||||||
if setting_value == self.definition.getProperty(key, "value"):
|
|
||||||
continue
|
|
||||||
|
|
||||||
setting_definition = stack.getSettingDefinition(key)
|
|
||||||
new_instance = SettingInstance(setting_definition, self.definitionChanges)
|
|
||||||
new_instance.setProperty("value", setting_value)
|
|
||||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
|
||||||
self.definitionChanges.addInstance(new_instance)
|
|
||||||
self.definitionChanges.setDirty(True)
|
|
||||||
|
|
||||||
# Make sure the material diameter is up to date for the extruder stack.
|
|
||||||
if key == "material_diameter":
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
|
||||||
position = self.getMetaDataEntry("position", "0")
|
|
||||||
func = lambda p = position: CuraApplication.getInstance().getExtruderManager().updateMaterialForDiameter(p)
|
|
||||||
machine_manager.machine_extruder_material_update_dict[stack.getId()].append(func)
|
|
||||||
|
|
||||||
# NOTE: We cannot remove the setting from the global stack's definition changes container because for
|
|
||||||
# material diameter, it needs to be applied to all extruders, but here we don't know how many extruders
|
|
||||||
# a machine actually has and how many extruders has already been loaded for that machine, so we have to
|
|
||||||
# keep this setting for any remaining extruders that haven't been loaded yet.
|
|
||||||
#
|
|
||||||
# Those settings will be removed in ExtruderManager which knows all those info.
|
|
||||||
|
|
||||||
@override(ContainerStack)
|
@override(ContainerStack)
|
||||||
def getNextStack(self) -> Optional["GlobalStack"]:
|
def getNextStack(self) -> Optional["GlobalStack"]:
|
||||||
return super().getNextStack()
|
return super().getNextStack()
|
||||||
|
@ -119,6 +50,29 @@ class ExtruderStack(CuraContainerStack):
|
||||||
def getLoadingPriority(cls) -> int:
|
def getLoadingPriority(cls) -> int:
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
## Return the filament diameter that the machine requires.
|
||||||
|
#
|
||||||
|
# If the machine has no requirement for the diameter, -1 is returned.
|
||||||
|
# \return The filament diameter for the printer
|
||||||
|
@property
|
||||||
|
def materialDiameter(self) -> float:
|
||||||
|
context = PropertyEvaluationContext(self)
|
||||||
|
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
|
||||||
|
|
||||||
|
return self.getProperty("material_diameter", "value", context = context)
|
||||||
|
|
||||||
|
## Return the approximate filament diameter that the machine requires.
|
||||||
|
#
|
||||||
|
# The approximate material diameter is the material diameter rounded to
|
||||||
|
# the nearest millimetre.
|
||||||
|
#
|
||||||
|
# If the machine has no requirement for the diameter, -1 is returned.
|
||||||
|
#
|
||||||
|
# \return The approximate filament diameter for the printer
|
||||||
|
@pyqtProperty(float)
|
||||||
|
def approximateMaterialDiameter(self) -> float:
|
||||||
|
return round(float(self.materialDiameter))
|
||||||
|
|
||||||
## Overridden from ContainerStack
|
## Overridden from ContainerStack
|
||||||
#
|
#
|
||||||
# It will perform a few extra checks when trying to get properties.
|
# It will perform a few extra checks when trying to get properties.
|
||||||
|
@ -187,11 +141,6 @@ class ExtruderStack(CuraContainerStack):
|
||||||
if has_global_dependencies:
|
if has_global_dependencies:
|
||||||
self.getNextStack().propertiesChanged.emit(key, properties)
|
self.getNextStack().propertiesChanged.emit(key, properties)
|
||||||
|
|
||||||
def findDefaultVariant(self):
|
|
||||||
# The default variant is defined in the machine stack and/or definition, so use the machine stack to find
|
|
||||||
# the default variant.
|
|
||||||
return self.getNextStack().findDefaultVariant()
|
|
||||||
|
|
||||||
|
|
||||||
extruder_stack_mime = MimeType(
|
extruder_stack_mime = MimeType(
|
||||||
name = "application/x-cura-extruderstack",
|
name = "application/x-cura-extruderstack",
|
||||||
|
|
|
@ -125,21 +125,6 @@ class GlobalStack(CuraContainerStack):
|
||||||
def setNextStack(self, next_stack: ContainerStack) -> None:
|
def setNextStack(self, next_stack: ContainerStack) -> None:
|
||||||
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
||||||
|
|
||||||
## Gets the approximate filament diameter that the machine requires.
|
|
||||||
#
|
|
||||||
# The approximate material diameter is the material diameter rounded to
|
|
||||||
# the nearest millimetre.
|
|
||||||
#
|
|
||||||
# If the machine has no requirement for the diameter, -1 is returned.
|
|
||||||
#
|
|
||||||
# \return The approximate filament diameter for the printer, as a string.
|
|
||||||
@pyqtProperty(str)
|
|
||||||
def approximateMaterialDiameter(self) -> str:
|
|
||||||
material_diameter = self.definition.getProperty("material_diameter", "value")
|
|
||||||
if material_diameter is None:
|
|
||||||
return "-1"
|
|
||||||
return str(round(float(material_diameter))) #Round, then convert back to string.
|
|
||||||
|
|
||||||
# protected:
|
# protected:
|
||||||
|
|
||||||
# Determine whether or not we should try to get the "resolve" property instead of the
|
# Determine whether or not we should try to get the "resolve" property instead of the
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,57 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot #To expose data to QML.
|
|
||||||
|
|
||||||
from cura.Settings.ContainerManager import ContainerManager
|
|
||||||
from UM.Logger import Logger
|
|
||||||
from UM.Message import Message #To create a warning message about material diameter.
|
|
||||||
from UM.i18n import i18nCatalog #Translated strings.
|
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
|
||||||
|
|
||||||
## Handles material-related data, processing requests to change them and
|
|
||||||
# providing data for the GUI.
|
|
||||||
#
|
|
||||||
# TODO: Move material-related managing over from the machine manager to here.
|
|
||||||
class MaterialManager(QObject):
|
|
||||||
## Creates the global values for the material manager to use.
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
#Material diameter changed warning message.
|
|
||||||
self._material_diameter_warning_message = Message(catalog.i18nc("@info:status Has a cancel button next to it.",
|
|
||||||
"The selected material diameter causes the material to become incompatible with the current printer."), title = catalog.i18nc("@info:title", "Incompatible Material"))
|
|
||||||
self._material_diameter_warning_message.addAction("Undo", catalog.i18nc("@action:button", "Undo"), None, catalog.i18nc("@action", "Undo changing the material diameter."))
|
|
||||||
self._material_diameter_warning_message.actionTriggered.connect(self._materialWarningMessageAction)
|
|
||||||
|
|
||||||
## Creates an instance of the MaterialManager.
|
|
||||||
#
|
|
||||||
# This should only be called by PyQt to create the singleton instance of
|
|
||||||
# this class.
|
|
||||||
@staticmethod
|
|
||||||
def createMaterialManager(engine = None, script_engine = None):
|
|
||||||
return MaterialManager()
|
|
||||||
|
|
||||||
@pyqtSlot(str, str)
|
|
||||||
def showMaterialWarningMessage(self, material_id, previous_diameter):
|
|
||||||
self._material_diameter_warning_message.previous_diameter = previous_diameter #Make sure that the undo button can properly undo the action.
|
|
||||||
self._material_diameter_warning_message.material_id = material_id
|
|
||||||
self._material_diameter_warning_message.show()
|
|
||||||
|
|
||||||
## Called when clicking "undo" on the warning dialogue for disappeared
|
|
||||||
# materials.
|
|
||||||
#
|
|
||||||
# This executes the undo action, restoring the material diameter.
|
|
||||||
#
|
|
||||||
# \param button The identifier of the button that was pressed.
|
|
||||||
def _materialWarningMessageAction(self, message, button):
|
|
||||||
if button == "Undo":
|
|
||||||
container_manager = ContainerManager.getInstance()
|
|
||||||
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "properties/diameter", self._material_diameter_warning_message.previous_diameter)
|
|
||||||
approximate_previous_diameter = str(round(float(self._material_diameter_warning_message.previous_diameter)))
|
|
||||||
container_manager.setContainerMetaDataEntry(self._material_diameter_warning_message.material_id, "approximate_diameter", approximate_previous_diameter)
|
|
||||||
container_manager.setContainerProperty(self._material_diameter_warning_message.material_id, "material_diameter", "value", self._material_diameter_warning_message.previous_diameter);
|
|
||||||
message.hide()
|
|
||||||
else:
|
|
||||||
Logger.log("w", "Unknown button action for material diameter warning message: {action}".format(action = button))
|
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
import UM.Settings.Models.SettingVisibilityHandler
|
import UM.Settings.Models.SettingVisibilityHandler
|
||||||
|
|
||||||
|
|
||||||
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
|
class MaterialSettingsVisibilityHandler(UM.Settings.Models.SettingVisibilityHandler.SettingVisibilityHandler):
|
||||||
def __init__(self, parent = None, *args, **kwargs):
|
def __init__(self, parent = None, *args, **kwargs):
|
||||||
super().__init__(parent = parent, *args, **kwargs)
|
super().__init__(parent = parent, *args, **kwargs)
|
||||||
|
|
|
@ -1,37 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from typing import Any, List
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry #To listen for changes to the materials.
|
|
||||||
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel #We're extending this class.
|
|
||||||
|
|
||||||
## A model that shows a list of currently valid materials.
|
|
||||||
class MaterialsModel(InstanceContainersModel):
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
ContainerRegistry.getInstance().containerMetaDataChanged.connect(self._onContainerMetaDataChanged)
|
|
||||||
|
|
||||||
## Called when the metadata of the container was changed.
|
|
||||||
#
|
|
||||||
# This makes sure that we only update when it was a material that changed.
|
|
||||||
#
|
|
||||||
# \param container The container whose metadata was changed.
|
|
||||||
def _onContainerMetaDataChanged(self, container):
|
|
||||||
if container.getMetaDataEntry("type") == "material": #Only need to update if a material was changed.
|
|
||||||
self._container_change_timer.start()
|
|
||||||
|
|
||||||
def _onContainerChanged(self, container):
|
|
||||||
if container.getMetaDataEntry("type", "") == "material":
|
|
||||||
super()._onContainerChanged(container)
|
|
||||||
|
|
||||||
## Group brand together
|
|
||||||
def _sortKey(self, item) -> List[Any]:
|
|
||||||
result = []
|
|
||||||
result.append(item["metadata"]["brand"])
|
|
||||||
result.append(item["metadata"]["material"])
|
|
||||||
result.append(item["metadata"]["name"])
|
|
||||||
result.append(item["metadata"]["color_name"])
|
|
||||||
result.append(item["metadata"]["id"])
|
|
||||||
result.extend(super()._sortKey(item))
|
|
||||||
return result
|
|
|
@ -1,222 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from collections import OrderedDict
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
from UM.Settings.Models.InstanceContainersModel import InstanceContainersModel
|
|
||||||
|
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
|
|
||||||
from typing import List, TYPE_CHECKING
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
|
||||||
|
|
||||||
|
|
||||||
## QML Model for listing the current list of valid quality profiles.
|
|
||||||
#
|
|
||||||
class ProfilesModel(InstanceContainersModel):
|
|
||||||
LayerHeightRole = Qt.UserRole + 1001
|
|
||||||
LayerHeightWithoutUnitRole = Qt.UserRole + 1002
|
|
||||||
AvailableRole = Qt.UserRole + 1003
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
self.addRoleName(self.LayerHeightRole, "layer_height")
|
|
||||||
self.addRoleName(self.LayerHeightWithoutUnitRole, "layer_height_without_unit")
|
|
||||||
self.addRoleName(self.AvailableRole, "available")
|
|
||||||
|
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._update)
|
|
||||||
Application.getInstance().getMachineManager().activeVariantChanged.connect(self._update)
|
|
||||||
Application.getInstance().getMachineManager().activeStackChanged.connect(self._update)
|
|
||||||
Application.getInstance().getMachineManager().activeMaterialChanged.connect(self._update)
|
|
||||||
|
|
||||||
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
|
|
||||||
|
|
||||||
# Factory function, used by QML
|
|
||||||
@staticmethod
|
|
||||||
def createProfilesModel(engine, js_engine):
|
|
||||||
return ProfilesModel.getInstance()
|
|
||||||
|
|
||||||
## Get the singleton instance for this class.
|
|
||||||
@classmethod
|
|
||||||
def getInstance(cls) -> "ProfilesModel":
|
|
||||||
# Note: Explicit use of class name to prevent issues with inheritance.
|
|
||||||
if not ProfilesModel.__instance:
|
|
||||||
ProfilesModel.__instance = cls()
|
|
||||||
return ProfilesModel.__instance
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def hasInstance(cls) -> bool:
|
|
||||||
return ProfilesModel.__instance is not None
|
|
||||||
|
|
||||||
__instance = None # type: "ProfilesModel"
|
|
||||||
|
|
||||||
## Fetch the list of containers to display.
|
|
||||||
#
|
|
||||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
|
||||||
def _fetchInstanceContainers(self):
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if global_container_stack is None:
|
|
||||||
return {}, {}
|
|
||||||
global_stack_definition = global_container_stack.definition
|
|
||||||
|
|
||||||
# Get the list of extruders and place the selected extruder at the front of the list.
|
|
||||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
|
||||||
materials = [extruder.material for extruder in extruder_stacks]
|
|
||||||
|
|
||||||
# Fetch the list of usable qualities across all extruders.
|
|
||||||
# The actual list of quality profiles come from the first extruder in the extruder list.
|
|
||||||
result = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
|
||||||
|
|
||||||
# The usable quality types are set
|
|
||||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in result])
|
|
||||||
|
|
||||||
# Fetch all qualities available for this machine and the materials selected in extruders
|
|
||||||
all_qualities = QualityManager.getInstance().findAllQualitiesForMachineAndMaterials(global_stack_definition, materials)
|
|
||||||
|
|
||||||
# If in the all qualities there is some of them that are not available due to incompatibility with materials
|
|
||||||
# we also add it so that they will appear in the slide quality bar. However in recomputeItems will be marked as
|
|
||||||
# not available so they will be shown in gray
|
|
||||||
for quality in all_qualities:
|
|
||||||
if quality.getMetaDataEntry("quality_type") not in quality_type_set:
|
|
||||||
result.append(quality)
|
|
||||||
|
|
||||||
if len(result) > 1 and self._empty_quality in result:
|
|
||||||
result.remove(self._empty_quality)
|
|
||||||
|
|
||||||
return {item.getId(): item for item in result}, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
|
|
||||||
|
|
||||||
## Re-computes the items in this model, and adds the layer height role.
|
|
||||||
def _recomputeItems(self):
|
|
||||||
# Some globals that we can re-use.
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if global_container_stack is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
|
||||||
container_registry = ContainerRegistry.getInstance()
|
|
||||||
|
|
||||||
# Get a list of usable/available qualities for this machine and material
|
|
||||||
qualities = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
|
||||||
|
|
||||||
unit = global_container_stack.getBottom().getProperty("layer_height", "unit")
|
|
||||||
if not unit:
|
|
||||||
unit = ""
|
|
||||||
|
|
||||||
# group all quality items according to quality_types, so we know which profile suits the currently
|
|
||||||
# active machine and material, and later yield the right ones.
|
|
||||||
tmp_all_quality_items = OrderedDict()
|
|
||||||
for item in super()._recomputeItems():
|
|
||||||
profiles = container_registry.findContainersMetadata(id = item["id"])
|
|
||||||
if not profiles or "quality_type" not in profiles[0]:
|
|
||||||
quality_type = ""
|
|
||||||
else:
|
|
||||||
quality_type = profiles[0]["quality_type"]
|
|
||||||
|
|
||||||
if quality_type not in tmp_all_quality_items:
|
|
||||||
tmp_all_quality_items[quality_type] = {"suitable_container": None, "all_containers": []}
|
|
||||||
|
|
||||||
tmp_all_quality_items[quality_type]["all_containers"].append(item)
|
|
||||||
if tmp_all_quality_items[quality_type]["suitable_container"] is None:
|
|
||||||
tmp_all_quality_items[quality_type]["suitable_container"] = item
|
|
||||||
|
|
||||||
# reverse the ordering (finest first, coarsest last)
|
|
||||||
all_quality_items = OrderedDict()
|
|
||||||
for key in reversed(tmp_all_quality_items.keys()):
|
|
||||||
all_quality_items[key] = tmp_all_quality_items[key]
|
|
||||||
|
|
||||||
# First the suitable containers are set in the model
|
|
||||||
containers = []
|
|
||||||
for data_item in all_quality_items.values():
|
|
||||||
suitable_item = data_item["suitable_container"]
|
|
||||||
if suitable_item is not None:
|
|
||||||
containers.append(suitable_item)
|
|
||||||
|
|
||||||
# Once the suitable containers are collected, the rest of the containers are appended
|
|
||||||
for data_item in all_quality_items.values():
|
|
||||||
for item in data_item["all_containers"]:
|
|
||||||
if item not in containers:
|
|
||||||
containers.append(item)
|
|
||||||
|
|
||||||
# Now all the containers are set
|
|
||||||
for item in containers:
|
|
||||||
profile = container_registry.findContainers(id = item["id"])
|
|
||||||
|
|
||||||
# When for some reason there is no profile container in the registry
|
|
||||||
if not profile:
|
|
||||||
self._setItemLayerHeight(item, "", "")
|
|
||||||
item["available"] = False
|
|
||||||
yield item
|
|
||||||
continue
|
|
||||||
|
|
||||||
profile = profile[0]
|
|
||||||
|
|
||||||
# When there is a profile but it's an empty quality should. It's shown in the list (they are "Not Supported" profiles)
|
|
||||||
if profile.getId() == "empty_quality":
|
|
||||||
self._setItemLayerHeight(item, "", "")
|
|
||||||
item["available"] = True
|
|
||||||
yield item
|
|
||||||
continue
|
|
||||||
|
|
||||||
item["available"] = profile in qualities
|
|
||||||
|
|
||||||
# Easy case: This profile defines its own layer height.
|
|
||||||
if profile.hasProperty("layer_height", "value"):
|
|
||||||
self._setItemLayerHeight(item, profile.getProperty("layer_height", "value"), unit)
|
|
||||||
yield item
|
|
||||||
continue
|
|
||||||
|
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
|
||||||
|
|
||||||
# Quality-changes profile that has no value for layer height. Get the corresponding quality profile and ask that profile.
|
|
||||||
quality_type = profile.getMetaDataEntry("quality_type", None)
|
|
||||||
if quality_type:
|
|
||||||
quality_results = machine_manager.determineQualityAndQualityChangesForQualityType(quality_type)
|
|
||||||
for quality_result in quality_results:
|
|
||||||
if quality_result["stack"] is global_container_stack:
|
|
||||||
quality = quality_result["quality"]
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
# No global container stack in the results:
|
|
||||||
if quality_results:
|
|
||||||
# Take any of the extruders.
|
|
||||||
quality = quality_results[0]["quality"]
|
|
||||||
else:
|
|
||||||
quality = None
|
|
||||||
if quality and quality.hasProperty("layer_height", "value"):
|
|
||||||
self._setItemLayerHeight(item, quality.getProperty("layer_height", "value"), unit)
|
|
||||||
yield item
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Quality has no value for layer height either. Get the layer height from somewhere lower in the stack.
|
|
||||||
skip_until_container = global_container_stack.material
|
|
||||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No material in stack.
|
|
||||||
skip_until_container = global_container_stack.variant
|
|
||||||
if not skip_until_container or skip_until_container == ContainerRegistry.getInstance().getEmptyInstanceContainer(): # No variant in stack.
|
|
||||||
skip_until_container = global_container_stack.getBottom()
|
|
||||||
self._setItemLayerHeight(item, global_container_stack.getRawProperty("layer_height", "value", skip_until_container = skip_until_container.getId()), unit) # Fall through to the currently loaded material.
|
|
||||||
yield item
|
|
||||||
|
|
||||||
## Get a list of extruder stacks with the active extruder at the front of the list.
|
|
||||||
@staticmethod
|
|
||||||
def _getOrderedExtruderStacksList() -> List["ExtruderStack"]:
|
|
||||||
extruder_manager = ExtruderManager.getInstance()
|
|
||||||
extruder_stacks = extruder_manager.getActiveExtruderStacks()
|
|
||||||
active_extruder = extruder_manager.getActiveExtruderStack()
|
|
||||||
|
|
||||||
if active_extruder in extruder_stacks:
|
|
||||||
extruder_stacks.remove(active_extruder)
|
|
||||||
extruder_stacks = [active_extruder] + extruder_stacks
|
|
||||||
|
|
||||||
return extruder_stacks
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _setItemLayerHeight(item, value, unit):
|
|
||||||
item["layer_height"] = str(value) + unit
|
|
||||||
item["layer_height_without_unit"] = str(value)
|
|
|
@ -1,54 +0,0 @@
|
||||||
# Copyright (c) 2016 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
|
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
from cura.Settings.ProfilesModel import ProfilesModel
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
|
|
||||||
|
|
||||||
## QML Model for listing the current list of valid quality and quality changes profiles.
|
|
||||||
#
|
|
||||||
class QualityAndUserProfilesModel(ProfilesModel):
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
self._empty_quality = ContainerRegistry.getInstance().findInstanceContainers(id = "empty_quality")[0]
|
|
||||||
|
|
||||||
## Fetch the list of containers to display.
|
|
||||||
#
|
|
||||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
|
||||||
def _fetchInstanceContainers(self):
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_container_stack:
|
|
||||||
return {}, {}
|
|
||||||
|
|
||||||
# Fetch the list of quality changes.
|
|
||||||
quality_manager = QualityManager.getInstance()
|
|
||||||
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
|
|
||||||
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
|
|
||||||
|
|
||||||
extruder_manager = ExtruderManager.getInstance()
|
|
||||||
active_extruder = extruder_manager.getActiveExtruderStack()
|
|
||||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
|
||||||
|
|
||||||
# Fetch the list of usable qualities across all extruders.
|
|
||||||
# The actual list of quality profiles come from the first extruder in the extruder list.
|
|
||||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
|
||||||
|
|
||||||
# Filter the quality_change by the list of available quality_types
|
|
||||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
|
||||||
# Also show custom profiles based on "Not Supported" quality profile
|
|
||||||
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
|
|
||||||
filtered_quality_changes = {qc.getId(): qc for qc in quality_changes_list if
|
|
||||||
qc.getMetaDataEntry("quality_type") in quality_type_set and
|
|
||||||
qc.getMetaDataEntry("extruder") is not None and
|
|
||||||
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
|
|
||||||
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
|
|
||||||
|
|
||||||
result = filtered_quality_changes
|
|
||||||
for q in quality_list:
|
|
||||||
if q.getId() != "empty_quality":
|
|
||||||
result[q.getId()] = q
|
|
||||||
return result, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
|
|
|
@ -1,249 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, Qt
|
|
||||||
|
|
||||||
from UM.Logger import Logger
|
|
||||||
import UM.Qt
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
import os
|
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
|
||||||
|
|
||||||
|
|
||||||
class QualitySettingsModel(UM.Qt.ListModel.ListModel):
|
|
||||||
KeyRole = Qt.UserRole + 1
|
|
||||||
LabelRole = Qt.UserRole + 2
|
|
||||||
UnitRole = Qt.UserRole + 3
|
|
||||||
ProfileValueRole = Qt.UserRole + 4
|
|
||||||
ProfileValueSourceRole = Qt.UserRole + 5
|
|
||||||
UserValueRole = Qt.UserRole + 6
|
|
||||||
CategoryRole = Qt.UserRole + 7
|
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent = parent)
|
|
||||||
|
|
||||||
self._container_registry = ContainerRegistry.getInstance()
|
|
||||||
|
|
||||||
self._extruder_id = None
|
|
||||||
self._extruder_definition_id = None
|
|
||||||
self._quality_id = None
|
|
||||||
self._material_id = None
|
|
||||||
self._i18n_catalog = None
|
|
||||||
|
|
||||||
self.addRoleName(self.KeyRole, "key")
|
|
||||||
self.addRoleName(self.LabelRole, "label")
|
|
||||||
self.addRoleName(self.UnitRole, "unit")
|
|
||||||
self.addRoleName(self.ProfileValueRole, "profile_value")
|
|
||||||
self.addRoleName(self.ProfileValueSourceRole, "profile_value_source")
|
|
||||||
self.addRoleName(self.UserValueRole, "user_value")
|
|
||||||
self.addRoleName(self.CategoryRole, "category")
|
|
||||||
|
|
||||||
self._empty_quality = self._container_registry.findInstanceContainers(id = "empty_quality")[0]
|
|
||||||
|
|
||||||
def setExtruderId(self, extruder_id):
|
|
||||||
if extruder_id != self._extruder_id:
|
|
||||||
self._extruder_id = extruder_id
|
|
||||||
self._update()
|
|
||||||
self.extruderIdChanged.emit()
|
|
||||||
|
|
||||||
extruderIdChanged = pyqtSignal()
|
|
||||||
@pyqtProperty(str, fset = setExtruderId, notify = extruderIdChanged)
|
|
||||||
def extruderId(self):
|
|
||||||
return self._extruder_id
|
|
||||||
|
|
||||||
def setExtruderDefinition(self, extruder_definition):
|
|
||||||
if extruder_definition != self._extruder_definition_id:
|
|
||||||
self._extruder_definition_id = extruder_definition
|
|
||||||
self._update()
|
|
||||||
self.extruderDefinitionChanged.emit()
|
|
||||||
|
|
||||||
extruderDefinitionChanged = pyqtSignal()
|
|
||||||
@pyqtProperty(str, fset = setExtruderDefinition, notify = extruderDefinitionChanged)
|
|
||||||
def extruderDefinition(self):
|
|
||||||
return self._extruder_definition_id
|
|
||||||
|
|
||||||
def setQuality(self, quality):
|
|
||||||
if quality != self._quality_id:
|
|
||||||
self._quality_id = quality
|
|
||||||
self._update()
|
|
||||||
self.qualityChanged.emit()
|
|
||||||
|
|
||||||
qualityChanged = pyqtSignal()
|
|
||||||
@pyqtProperty(str, fset = setQuality, notify = qualityChanged)
|
|
||||||
def quality(self):
|
|
||||||
return self._quality_id
|
|
||||||
|
|
||||||
def setMaterial(self, material):
|
|
||||||
if material != self._material_id:
|
|
||||||
self._material_id = material
|
|
||||||
self._update()
|
|
||||||
self.materialChanged.emit()
|
|
||||||
|
|
||||||
materialChanged = pyqtSignal()
|
|
||||||
@pyqtProperty(str, fset = setMaterial, notify = materialChanged)
|
|
||||||
def material(self):
|
|
||||||
return self._material_id
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
if not self._quality_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
items = []
|
|
||||||
|
|
||||||
definition_container = Application.getInstance().getGlobalContainerStack().getBottom()
|
|
||||||
|
|
||||||
containers = self._container_registry.findInstanceContainers(id = self._quality_id)
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not find a quality container with id %s", self._quality_id)
|
|
||||||
return
|
|
||||||
|
|
||||||
quality_container = None
|
|
||||||
quality_changes_container = None
|
|
||||||
|
|
||||||
if containers[0].getMetaDataEntry("type") == "quality":
|
|
||||||
quality_container = containers[0]
|
|
||||||
else:
|
|
||||||
quality_changes_container = containers[0]
|
|
||||||
|
|
||||||
if quality_changes_container.getMetaDataEntry("quality_type") == self._empty_quality.getMetaDataEntry("quality_type"):
|
|
||||||
quality_container = self._empty_quality
|
|
||||||
else:
|
|
||||||
criteria = {
|
|
||||||
"type": "quality",
|
|
||||||
"quality_type": quality_changes_container.getMetaDataEntry("quality_type"),
|
|
||||||
"definition": quality_changes_container.getDefinition().getId()
|
|
||||||
}
|
|
||||||
|
|
||||||
quality_container = self._container_registry.findInstanceContainers(**criteria)
|
|
||||||
if not quality_container:
|
|
||||||
Logger.log("w", "Could not find a quality container matching quality changes %s", quality_changes_container.getId())
|
|
||||||
return
|
|
||||||
|
|
||||||
quality_container = quality_container[0]
|
|
||||||
|
|
||||||
quality_type = quality_container.getMetaDataEntry("quality_type")
|
|
||||||
|
|
||||||
if quality_type == "not_supported":
|
|
||||||
containers = []
|
|
||||||
else:
|
|
||||||
definition_id = Application.getInstance().getMachineManager().getQualityDefinitionId(quality_container.getDefinition())
|
|
||||||
definition = quality_container.getDefinition()
|
|
||||||
|
|
||||||
# Check if the definition container has a translation file.
|
|
||||||
definition_suffix = ContainerRegistry.getMimeTypeForContainer(type(definition)).preferredSuffix
|
|
||||||
catalog = i18nCatalog(os.path.basename(definition_id + "." + definition_suffix))
|
|
||||||
if catalog.hasTranslationLoaded():
|
|
||||||
self._i18n_catalog = catalog
|
|
||||||
|
|
||||||
for file_name in quality_container.getDefinition().getInheritedFiles():
|
|
||||||
catalog = i18nCatalog(os.path.basename(file_name))
|
|
||||||
if catalog.hasTranslationLoaded():
|
|
||||||
self._i18n_catalog = catalog
|
|
||||||
|
|
||||||
criteria = {"type": "quality", "quality_type": quality_type, "definition": definition_id}
|
|
||||||
|
|
||||||
if self._material_id and self._material_id != "empty_material":
|
|
||||||
criteria["material"] = self._material_id
|
|
||||||
|
|
||||||
criteria["extruder"] = self._extruder_id
|
|
||||||
|
|
||||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
|
||||||
if not containers:
|
|
||||||
# Try again, this time without extruder
|
|
||||||
new_criteria = criteria.copy()
|
|
||||||
new_criteria.pop("extruder")
|
|
||||||
containers = self._container_registry.findInstanceContainers(**new_criteria)
|
|
||||||
|
|
||||||
if not containers and "material" in criteria:
|
|
||||||
# Try again, this time without material
|
|
||||||
criteria.pop("material", None)
|
|
||||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
|
||||||
|
|
||||||
if not containers:
|
|
||||||
# Try again, this time without material or extruder
|
|
||||||
criteria.pop("extruder") # "material" has already been popped
|
|
||||||
containers = self._container_registry.findInstanceContainers(**criteria)
|
|
||||||
|
|
||||||
if not containers:
|
|
||||||
Logger.log("w", "Could not find any quality containers matching the search criteria %s" % str(criteria))
|
|
||||||
return
|
|
||||||
|
|
||||||
if quality_changes_container:
|
|
||||||
if quality_type == "not_supported":
|
|
||||||
criteria = {"type": "quality_changes", "quality_type": quality_type, "name": quality_changes_container.getName()}
|
|
||||||
else:
|
|
||||||
criteria = {"type": "quality_changes", "quality_type": quality_type, "definition": definition_id, "name": quality_changes_container.getName()}
|
|
||||||
if self._extruder_definition_id != "":
|
|
||||||
extruder_definitions = self._container_registry.findDefinitionContainers(id = self._extruder_definition_id)
|
|
||||||
if extruder_definitions:
|
|
||||||
criteria["extruder"] = Application.getInstance().getMachineManager().getQualityDefinitionId(extruder_definitions[0])
|
|
||||||
criteria["name"] = quality_changes_container.getName()
|
|
||||||
else:
|
|
||||||
criteria["extruder"] = None
|
|
||||||
|
|
||||||
changes = self._container_registry.findInstanceContainers(**criteria)
|
|
||||||
if changes:
|
|
||||||
containers.extend(changes)
|
|
||||||
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
|
|
||||||
current_category = ""
|
|
||||||
for definition in definition_container.findDefinitions():
|
|
||||||
if definition.type == "category":
|
|
||||||
current_category = definition.label
|
|
||||||
if self._i18n_catalog:
|
|
||||||
current_category = self._i18n_catalog.i18nc(definition.key + " label", definition.label)
|
|
||||||
continue
|
|
||||||
|
|
||||||
profile_value = None
|
|
||||||
profile_value_source = ""
|
|
||||||
for container in containers:
|
|
||||||
new_value = container.getProperty(definition.key, "value")
|
|
||||||
|
|
||||||
if new_value is not None:
|
|
||||||
profile_value_source = container.getMetaDataEntry("type")
|
|
||||||
profile_value = new_value
|
|
||||||
|
|
||||||
# Global tab should use resolve (if there is one)
|
|
||||||
if not self._extruder_id:
|
|
||||||
resolve_value = global_container_stack.getProperty(definition.key, "resolve")
|
|
||||||
if resolve_value is not None and profile_value is not None and profile_value_source != "quality_changes":
|
|
||||||
profile_value = resolve_value
|
|
||||||
|
|
||||||
user_value = None
|
|
||||||
if not self._extruder_id:
|
|
||||||
user_value = global_container_stack.getTop().getProperty(definition.key, "value")
|
|
||||||
else:
|
|
||||||
extruder_stack = self._container_registry.findContainerStacks(id = self._extruder_id)
|
|
||||||
if extruder_stack:
|
|
||||||
user_value = extruder_stack[0].getTop().getProperty(definition.key, "value")
|
|
||||||
|
|
||||||
if profile_value is None and user_value is None:
|
|
||||||
continue
|
|
||||||
|
|
||||||
settable_per_extruder = global_container_stack.getProperty(definition.key, "settable_per_extruder")
|
|
||||||
# If a setting is not settable per extruder (global) and we're looking at an extruder tab, don't show this value.
|
|
||||||
if self._extruder_id != "" and not settable_per_extruder:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# If a setting is settable per extruder (not global) and we're looking at global tab, don't show this value.
|
|
||||||
if self._extruder_id == "" and settable_per_extruder:
|
|
||||||
continue
|
|
||||||
|
|
||||||
label = definition.label
|
|
||||||
if self._i18n_catalog:
|
|
||||||
label = self._i18n_catalog.i18nc(definition.key + " label", label)
|
|
||||||
|
|
||||||
items.append({
|
|
||||||
"key": definition.key,
|
|
||||||
"label": label,
|
|
||||||
"unit": definition.unit,
|
|
||||||
"profile_value": "" if profile_value is None else str(profile_value), # it is for display only
|
|
||||||
"profile_value_source": profile_value_source,
|
|
||||||
"user_value": "" if user_value is None else str(user_value),
|
|
||||||
"category": current_category
|
|
||||||
})
|
|
||||||
|
|
||||||
self.setItems(items)
|
|
|
@ -3,12 +3,14 @@
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||||
from UM.Signal import Signal, signalemitter
|
from UM.Signal import Signal, signalemitter
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Settings.Validator import ValidatorState
|
||||||
|
from PyQt5.QtCore import QTimer
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
|
|
||||||
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
|
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
|
||||||
|
@ -38,6 +40,10 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
|
self._extruder_stack = ExtruderManager.getInstance().getExtruderStack(0).getId()
|
||||||
|
|
||||||
self._is_non_printing_mesh = False
|
self._is_non_printing_mesh = False
|
||||||
|
self._error_check_timer = QTimer()
|
||||||
|
self._error_check_timer.setInterval(250)
|
||||||
|
self._error_check_timer.setSingleShot(True)
|
||||||
|
self._error_check_timer.timeout.connect(self._checkStackForErrors)
|
||||||
|
|
||||||
self._stack.propertyChanged.connect(self._onSettingChanged)
|
self._stack.propertyChanged.connect(self._onSettingChanged)
|
||||||
|
|
||||||
|
@ -93,9 +99,21 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||||
# Trigger slice/need slicing if the value has changed.
|
# Trigger slice/need slicing if the value has changed.
|
||||||
if property_name == "value":
|
if property_name == "value":
|
||||||
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
|
self._is_non_printing_mesh = any(bool(self._stack.getProperty(setting, "value")) for setting in self._non_printing_mesh_settings)
|
||||||
|
if not self._is_non_printing_mesh:
|
||||||
|
# self._error_check_timer.start()
|
||||||
|
self._checkStackForErrors()
|
||||||
|
Application.getInstance().getBackend().needsSlicing()
|
||||||
|
Application.getInstance().getBackend().tickle()
|
||||||
|
|
||||||
Application.getInstance().getBackend().needsSlicing()
|
def _checkStackForErrors(self):
|
||||||
Application.getInstance().getBackend().tickle()
|
hasErrors = False;
|
||||||
|
for key in self._stack.getAllKeys():
|
||||||
|
validation_state = self._stack.getProperty(key, "validationState")
|
||||||
|
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
||||||
|
Logger.log("w", "Setting Per Object %s is not valid.", key)
|
||||||
|
hasErrors = True
|
||||||
|
break
|
||||||
|
Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
|
||||||
|
|
||||||
## Makes sure that the stack upon which the container stack is placed is
|
## Makes sure that the stack upon which the container stack is placed is
|
||||||
# kept up to date.
|
# kept up to date.
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
# Copyright (c) 2017 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
|
||||||
|
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
from cura.Settings.ProfilesModel import ProfilesModel
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
|
||||||
|
|
||||||
## QML Model for listing the current list of valid quality changes profiles.
|
|
||||||
#
|
|
||||||
class UserProfilesModel(ProfilesModel):
|
|
||||||
def __init__(self, parent = None):
|
|
||||||
super().__init__(parent)
|
|
||||||
|
|
||||||
#Need to connect to the metaDataChanged signal of the active materials.
|
|
||||||
self.__current_extruders = []
|
|
||||||
self.__current_materials = []
|
|
||||||
|
|
||||||
Application.getInstance().getExtruderManager().extrudersChanged.connect(self.__onExtrudersChanged)
|
|
||||||
self.__onExtrudersChanged()
|
|
||||||
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
|
|
||||||
for material in self.__current_materials:
|
|
||||||
material.metaDataChanged.connect(self._onContainerChanged)
|
|
||||||
|
|
||||||
self._empty_quality = ContainerRegistry.getInstance().findContainers(id = "empty_quality")[0]
|
|
||||||
|
|
||||||
## Fetch the list of containers to display.
|
|
||||||
#
|
|
||||||
# See UM.Settings.Models.InstanceContainersModel._fetchInstanceContainers().
|
|
||||||
def _fetchInstanceContainers(self):
|
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
|
||||||
if not global_container_stack:
|
|
||||||
return {}, {}
|
|
||||||
|
|
||||||
# Fetch the list of quality changes.
|
|
||||||
quality_manager = QualityManager.getInstance()
|
|
||||||
machine_definition = quality_manager.getParentMachineDefinition(global_container_stack.definition)
|
|
||||||
quality_changes_list = quality_manager.findAllQualityChangesForMachine(machine_definition)
|
|
||||||
|
|
||||||
extruder_manager = ExtruderManager.getInstance()
|
|
||||||
active_extruder = extruder_manager.getActiveExtruderStack()
|
|
||||||
extruder_stacks = self._getOrderedExtruderStacksList()
|
|
||||||
|
|
||||||
# Fetch the list of usable qualities across all extruders.
|
|
||||||
# The actual list of quality profiles come from the first extruder in the extruder list.
|
|
||||||
quality_list = quality_manager.findAllUsableQualitiesForMachineAndExtruders(global_container_stack, extruder_stacks)
|
|
||||||
|
|
||||||
# Filter the quality_change by the list of available quality_types
|
|
||||||
quality_type_set = set([x.getMetaDataEntry("quality_type") for x in quality_list])
|
|
||||||
quality_type_set.add(self._empty_quality.getMetaDataEntry("quality_type"))
|
|
||||||
|
|
||||||
filtered_quality_changes = {qc.getId():qc for qc in quality_changes_list if
|
|
||||||
qc.getMetaDataEntry("quality_type") in quality_type_set and
|
|
||||||
qc.getMetaDataEntry("extruder") is not None and
|
|
||||||
(qc.getMetaDataEntry("extruder") == active_extruder.definition.getMetaDataEntry("quality_definition") or
|
|
||||||
qc.getMetaDataEntry("extruder") == active_extruder.definition.getId())}
|
|
||||||
|
|
||||||
return filtered_quality_changes, {} #Only return true profiles for now, no metadata. The quality manager is not able to get only metadata yet.
|
|
||||||
|
|
||||||
## Called when a container changed on an extruder stack.
|
|
||||||
#
|
|
||||||
# If it's the material we need to connect to the metaDataChanged signal of
|
|
||||||
# that.
|
|
||||||
def __onContainerChanged(self, new_container):
|
|
||||||
#Careful not to update when a quality or quality changes profile changed!
|
|
||||||
#If you then update you're going to have an infinite recursion because the update may change the container.
|
|
||||||
if new_container.getMetaDataEntry("type") == "material":
|
|
||||||
for material in self.__current_materials:
|
|
||||||
material.metaDataChanged.disconnect(self._onContainerChanged)
|
|
||||||
self.__current_materials = [extruder.material for extruder in self.__current_extruders]
|
|
||||||
for material in self.__current_materials:
|
|
||||||
material.metaDataChanged.connect(self._onContainerChanged)
|
|
||||||
|
|
||||||
## Called when the current set of extruders change.
|
|
||||||
#
|
|
||||||
# This makes sure that we are listening to the signal for when the
|
|
||||||
# materials change.
|
|
||||||
def __onExtrudersChanged(self):
|
|
||||||
for extruder in self.__current_extruders:
|
|
||||||
extruder.containersChanged.disconnect(self.__onContainerChanged)
|
|
||||||
self.__current_extruders = Application.getInstance().getExtruderManager().getExtruderStacks()
|
|
||||||
for extruder in self.__current_extruders:
|
|
||||||
extruder.containersChanged.connect(self.__onContainerChanged)
|
|
|
@ -4,26 +4,28 @@
|
||||||
import os.path
|
import os.path
|
||||||
import zipfile
|
import zipfile
|
||||||
|
|
||||||
|
import numpy
|
||||||
|
|
||||||
|
import Savitar
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Math.Matrix import Matrix
|
from UM.Math.Matrix import Matrix
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||||
from UM.Mesh.MeshReader import MeshReader
|
from UM.Mesh.MeshReader import MeshReader
|
||||||
from UM.Scene.GroupDecorator import GroupDecorator
|
from UM.Scene.GroupDecorator import GroupDecorator
|
||||||
|
|
||||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||||
from UM.Application import Application
|
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||||
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
|
||||||
|
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
|
||||||
|
|
||||||
MYPY = False
|
MYPY = False
|
||||||
|
|
||||||
import Savitar
|
|
||||||
import numpy
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not MYPY:
|
if not MYPY:
|
||||||
import xml.etree.cElementTree as ET
|
import xml.etree.cElementTree as ET
|
||||||
|
@ -77,7 +79,7 @@ class ThreeMFReader(MeshReader):
|
||||||
self._object_count += 1
|
self._object_count += 1
|
||||||
node_name = "Object %s" % self._object_count
|
node_name = "Object %s" % self._object_count
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
um_node = CuraSceneNode()
|
um_node = CuraSceneNode()
|
||||||
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
um_node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||||
|
@ -120,8 +122,8 @@ class ThreeMFReader(MeshReader):
|
||||||
um_node.callDecoration("setActiveExtruder", default_stack.getId())
|
um_node.callDecoration("setActiveExtruder", default_stack.getId())
|
||||||
|
|
||||||
# Get the definition & set it
|
# Get the definition & set it
|
||||||
definition = QualityManager.getInstance().getParentMachineDefinition(global_container_stack.getBottom())
|
definition_id = getMachineDefinitionIDForQualitySearch(global_container_stack)
|
||||||
um_node.callDecoration("getStack").getTop().setDefinition(definition.getId())
|
um_node.callDecoration("getStack").getTop().setDefinition(definition_id)
|
||||||
|
|
||||||
setting_container = um_node.callDecoration("getStack").getTop()
|
setting_container = um_node.callDecoration("getStack").getTop()
|
||||||
|
|
||||||
|
|
|
@ -23,7 +23,6 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
from cura.Settings.CuraContainerStack import _ContainerIndexes
|
||||||
from cura.QualityManager import QualityManager
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
|
@ -855,10 +854,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
if machine_extruder_count is not None:
|
if machine_extruder_count is not None:
|
||||||
extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
|
extruder_stacks_in_use = extruder_stacks[:machine_extruder_count]
|
||||||
|
|
||||||
available_quality = QualityManager.getInstance().findAllUsableQualitiesForMachineAndExtruders(global_stack,
|
quality_manager = CuraApplication.getInstance()._quality_manager
|
||||||
extruder_stacks_in_use)
|
all_quality_groups = quality_manager.getQualityGroups(global_stack)
|
||||||
|
available_quality_types = [qt for qt, qg in all_quality_groups.items() if qg.is_available]
|
||||||
if not has_not_supported:
|
if not has_not_supported:
|
||||||
has_not_supported = not available_quality
|
has_not_supported = not available_quality_types
|
||||||
|
|
||||||
quality_has_been_changed = False
|
quality_has_been_changed = False
|
||||||
|
|
||||||
|
@ -872,8 +872,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
# The machine in the project has non-empty quality and there are usable qualities for this machine.
|
# The machine in the project has non-empty quality and there are usable qualities for this machine.
|
||||||
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
|
# We need to check if the current quality_type is still usable for this machine, if not, then the quality
|
||||||
# will be reset to the "preferred quality" if present, otherwise "normal".
|
# will be reset to the "preferred quality" if present, otherwise "normal".
|
||||||
available_quality_types = [q.getMetaDataEntry("quality_type") for q in available_quality]
|
|
||||||
|
|
||||||
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
|
if global_stack.quality.getMetaDataEntry("quality_type") not in available_quality_types:
|
||||||
# We are here because the quality_type specified in the project is not supported any more,
|
# We are here because the quality_type specified in the project is not supported any more,
|
||||||
# so we need to switch it to the "preferred quality" if present, otherwise "normal".
|
# so we need to switch it to the "preferred quality" if present, otherwise "normal".
|
||||||
|
@ -947,7 +945,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
for extruder_stack in extruder_stacks_in_use:
|
for extruder_stack in extruder_stacks_in_use:
|
||||||
|
|
||||||
# If the quality is different in the stacks, then the quality in the global stack is trusted
|
# If the quality is different in the stacks, then the quality in the global stack is trusted
|
||||||
if extruder_stack.quality.getMetaDataEntry("quality_type", None) != global_stack.quality.getMetaDataEntry("quality_type", None):
|
if extruder_stack.quality.getMetaDataEntry("quality_type") != global_stack.quality.getMetaDataEntry("quality_type"):
|
||||||
search_criteria = {"id": global_stack.quality.getId(),
|
search_criteria = {"id": global_stack.quality.getId(),
|
||||||
"type": "quality",
|
"type": "quality",
|
||||||
"definition": definition_id}
|
"definition": definition_id}
|
||||||
|
@ -1084,10 +1082,13 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
CuraApplication.getInstance().getMachineManager().activeQualityChanged.emit()
|
||||||
|
|
||||||
# Actually change the active machine.
|
# Actually change the active machine.
|
||||||
Application.getInstance().setGlobalContainerStack(global_stack)
|
#
|
||||||
|
# This is scheduled for later is because it depends on the Variant/Material/Qualitiy Managers to have the latest
|
||||||
# Notify everything/one that is to notify about changes.
|
# data, but those managers will only update upon a container/container metadata changed signal. Because this
|
||||||
global_stack.containersChanged.emit(global_stack.getTop())
|
# function is running on the main thread (Qt thread), although those "changed" signals have been emitted, but
|
||||||
|
# they won't take effect until this function is done.
|
||||||
|
# To solve this, we schedule _updateActiveMachine() for later so it will have the latest data.
|
||||||
|
CuraApplication.getInstance().callLater(self._updateActiveMachine, global_stack)
|
||||||
|
|
||||||
# Load all the nodes / meshdata of the workspace
|
# Load all the nodes / meshdata of the workspace
|
||||||
nodes = self._3mf_mesh_reader.read(file_name)
|
nodes = self._3mf_mesh_reader.read(file_name)
|
||||||
|
@ -1100,6 +1101,14 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
self.setWorkspaceName(base_file_name)
|
self.setWorkspaceName(base_file_name)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
def _updateActiveMachine(self, global_stack):
|
||||||
|
# Actually change the active machine.
|
||||||
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
|
machine_manager.setActiveMachine(global_stack.getId())
|
||||||
|
|
||||||
|
# Notify everything/one that is to notify about changes.
|
||||||
|
global_stack.containersChanged.emit(global_stack.getTop())
|
||||||
|
|
||||||
## HACK: Replaces the material container in the given stack with a newly created material container.
|
## HACK: Replaces the material container in the given stack with a newly created material container.
|
||||||
# This function is used when the user chooses to resolve material conflicts by creating new ones.
|
# This function is used when the user chooses to resolve material conflicts by creating new ones.
|
||||||
def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
|
def _replaceStackMaterialWithNew(self, stack, old_new_material_dict):
|
||||||
|
|
|
@ -68,7 +68,7 @@ class ThreeMFWriter(MeshWriter):
|
||||||
if not isinstance(um_node, SceneNode):
|
if not isinstance(um_node, SceneNode):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
active_build_plate_nr = CuraApplication.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_nr = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
|
if um_node.callDecoration("getBuildPlateNumber") != active_build_plate_nr:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -70,7 +70,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# Workaround to disable layer view processing if layer view is not active.
|
# Workaround to disable layer view processing if layer view is not active.
|
||||||
self._layer_view_active = False
|
self._layer_view_active = False
|
||||||
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||||
Application.getInstance().getBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
|
Application.getInstance().getMultiBuildPlateModel().activeBuildPlateChanged.connect(self._onActiveViewChanged)
|
||||||
self._onActiveViewChanged()
|
self._onActiveViewChanged()
|
||||||
self._stored_layer_data = []
|
self._stored_layer_data = []
|
||||||
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||||
|
@ -88,7 +88,6 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
#
|
#
|
||||||
self._global_container_stack = None
|
self._global_container_stack = None
|
||||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||||
Application.getInstance().getExtruderManager().extrudersAdded.connect(self._onGlobalStackChanged)
|
|
||||||
self._onGlobalStackChanged()
|
self._onGlobalStackChanged()
|
||||||
|
|
||||||
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
|
Application.getInstance().stacksValidationFinished.connect(self._onStackErrorCheckFinished)
|
||||||
|
@ -207,7 +206,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
self._scene.gcode_dict = {}
|
self._scene.gcode_dict = {}
|
||||||
|
|
||||||
# see if we really have to slice
|
# see if we really have to slice
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
||||||
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
||||||
num_objects = self._numObjects()
|
num_objects = self._numObjects()
|
||||||
|
@ -497,7 +496,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
node.getParent().removeChild(node)
|
node.getParent().removeChild(node)
|
||||||
|
|
||||||
def markSliceAll(self):
|
def markSliceAll(self):
|
||||||
for build_plate_number in range(Application.getInstance().getBuildPlateModel().maxBuildPlate + 1):
|
for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
|
||||||
if build_plate_number not in self._build_plates_to_be_sliced:
|
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||||
|
|
||||||
|
@ -582,7 +581,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
Logger.log("d", "Slicing took %s seconds", time() - self._slice_start_time )
|
||||||
|
|
||||||
# See if we need to process the sliced layers job.
|
# See if we need to process the sliced layers job.
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
|
if self._layer_view_active and (self._process_layers_job is None or not self._process_layers_job.isRunning()) and active_build_plate == self._start_slice_job_build_plate:
|
||||||
self._startProcessSlicedLayersJob(active_build_plate)
|
self._startProcessSlicedLayersJob(active_build_plate)
|
||||||
# self._onActiveViewChanged()
|
# self._onActiveViewChanged()
|
||||||
|
@ -702,7 +701,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
application = Application.getInstance()
|
application = Application.getInstance()
|
||||||
view = application.getController().getActiveView()
|
view = application.getController().getActiveView()
|
||||||
if view:
|
if view:
|
||||||
active_build_plate = application.getBuildPlateModel().activeBuildPlate
|
active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
|
||||||
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||||
self._layer_view_active = True
|
self._layer_view_active = True
|
||||||
# There is data and we're not slicing at the moment
|
# There is data and we're not slicing at the moment
|
||||||
|
|
|
@ -12,6 +12,6 @@ class ProcessGCodeLayerJob(Job):
|
||||||
self._message = message
|
self._message = message
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = self._scene.gcode_dict[active_build_plate_id]
|
gcode_list = self._scene.gcode_dict[active_build_plate_id]
|
||||||
gcode_list.append(self._message.data.decode("utf-8", "replace"))
|
gcode_list.append(self._message.data.decode("utf-8", "replace"))
|
||||||
|
|
|
@ -136,6 +136,11 @@ class StartSliceJob(Job):
|
||||||
self.setResult(StartJobResult.MaterialIncompatible)
|
self.setResult(StartJobResult.MaterialIncompatible)
|
||||||
return
|
return
|
||||||
|
|
||||||
|
# Validate settings per selectable model
|
||||||
|
if Application.getInstance().getObjectsModel().stacksHaveErrors():
|
||||||
|
self.setResult(StartJobResult.ObjectSettingError)
|
||||||
|
return
|
||||||
|
|
||||||
# Don't slice if there is a per object setting with an error value.
|
# Don't slice if there is a per object setting with an error value.
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
if node.isSelectable():
|
if node.isSelectable():
|
||||||
|
|
|
@ -437,7 +437,7 @@ class FlavorParser:
|
||||||
scene_node.addDecorator(gcode_list_decorator)
|
scene_node.addDecorator(gcode_list_decorator)
|
||||||
|
|
||||||
# gcode_dict stores gcode_lists for a number of build plates.
|
# gcode_dict stores gcode_lists for a number of build plates.
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_dict = {active_build_plate_id: gcode_list}
|
gcode_dict = {active_build_plate_id: gcode_list}
|
||||||
Application.getInstance().getController().getScene().gcode_dict = gcode_dict
|
Application.getInstance().getController().getScene().gcode_dict = gcode_dict
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,7 @@ class GCodeWriter(MeshWriter):
|
||||||
Logger.log("e", "GCodeWriter does not support non-text mode.")
|
Logger.log("e", "GCodeWriter does not support non-text mode.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
scene = Application.getInstance().getController().getScene()
|
scene = Application.getInstance().getController().getScene()
|
||||||
gcode_dict = getattr(scene, "gcode_dict")
|
gcode_dict = getattr(scene, "gcode_dict")
|
||||||
if not gcode_dict:
|
if not gcode_dict:
|
||||||
|
|
|
@ -25,7 +25,20 @@ UM.TooltipArea
|
||||||
|
|
||||||
onClicked:
|
onClicked:
|
||||||
{
|
{
|
||||||
addedSettingsModel.setVisible(model.key, checked);
|
// Important first set visible and then subscribe
|
||||||
|
// otherwise the setting is not yet in list
|
||||||
|
// For unsubscribe is important first remove the subscription and then
|
||||||
|
// set as invisible
|
||||||
|
if(checked)
|
||||||
|
{
|
||||||
|
addedSettingsModel.setVisible(model.key, checked);
|
||||||
|
UM.ActiveTool.triggerActionWithData("subscribeForSettingValidation", model.key)
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
|
||||||
|
addedSettingsModel.setVisible(model.key, checked);
|
||||||
|
}
|
||||||
UM.ActiveTool.forceUpdate();
|
UM.ActiveTool.forceUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -240,7 +240,10 @@ Item {
|
||||||
width: Math.round(UM.Theme.getSize("setting").height / 2)
|
width: Math.round(UM.Theme.getSize("setting").height / 2)
|
||||||
height: UM.Theme.getSize("setting").height
|
height: UM.Theme.getSize("setting").height
|
||||||
|
|
||||||
onClicked: addedSettingsModel.setVisible(model.key, false)
|
onClicked: {
|
||||||
|
addedSettingsModel.setVisible(model.key, false)
|
||||||
|
UM.ActiveTool.triggerActionWithData("unsubscribeForSettingValidation", model.key)
|
||||||
|
}
|
||||||
|
|
||||||
style: ButtonStyle
|
style: ButtonStyle
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,7 +10,10 @@ from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||||
from cura.Settings.ExtruderManager import ExtruderManager
|
from cura.Settings.ExtruderManager import ExtruderManager
|
||||||
from UM.Settings.SettingInstance import SettingInstance
|
from UM.Settings.SettingInstance import SettingInstance
|
||||||
from UM.Event import Event
|
from UM.Event import Event
|
||||||
|
from UM.Settings.Validator import ValidatorState
|
||||||
|
from UM.Logger import Logger
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QTimer
|
||||||
|
|
||||||
## This tool allows the user to add & change settings per node in the scene.
|
## This tool allows the user to add & change settings per node in the scene.
|
||||||
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
|
# The settings per object are kept in a ContainerStack, which is linked to a node by decorator.
|
||||||
|
@ -34,6 +37,12 @@ class PerObjectSettingsTool(Tool):
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
Selection.selectionChanged.connect(self._updateEnabled)
|
Selection.selectionChanged.connect(self._updateEnabled)
|
||||||
|
|
||||||
|
self._scene = Application.getInstance().getController().getScene()
|
||||||
|
|
||||||
|
self._error_check_timer = QTimer()
|
||||||
|
self._error_check_timer.setInterval(250)
|
||||||
|
self._error_check_timer.setSingleShot(True)
|
||||||
|
self._error_check_timer.timeout.connect(self._updateStacksHaveErrors)
|
||||||
|
|
||||||
def event(self, event):
|
def event(self, event):
|
||||||
super().event(event)
|
super().event(event)
|
||||||
|
@ -142,3 +151,65 @@ class PerObjectSettingsTool(Tool):
|
||||||
else:
|
else:
|
||||||
self._single_model_selected = True
|
self._single_model_selected = True
|
||||||
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
|
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, self._advanced_mode and self._single_model_selected)
|
||||||
|
|
||||||
|
|
||||||
|
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||||
|
if property_name == "validationState":
|
||||||
|
# self._error_check_timer.start()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _updateStacksHaveErrors(self) -> None:
|
||||||
|
return
|
||||||
|
# self._checkStacksHaveErrors()
|
||||||
|
|
||||||
|
|
||||||
|
def _checkStacksHaveErrors(self):
|
||||||
|
|
||||||
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
|
|
||||||
|
# valdiate only objects which can be selected because the settings per object
|
||||||
|
# can be applied only for them
|
||||||
|
if not node.isSelectable():
|
||||||
|
continue
|
||||||
|
|
||||||
|
hasErrors = self._checkStackForErrors(node.callDecoration("getStack"))
|
||||||
|
Application.getInstance().getObjectsModel().setStacksHaveErrors(hasErrors)
|
||||||
|
|
||||||
|
#If any of models has an error then no reason check next objects on the build plate
|
||||||
|
if hasErrors:
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
def _checkStackForErrors(self, stack):
|
||||||
|
print("checking for errors")
|
||||||
|
if stack is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for key in stack.getAllKeys():
|
||||||
|
validation_state = stack.getProperty(key, "validationState")
|
||||||
|
if validation_state in (ValidatorState.Exception, ValidatorState.MaximumError, ValidatorState.MinimumError):
|
||||||
|
Logger.log("w", "Setting Per Object %s is not valid.", key)
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def subscribeForSettingValidation(self, setting_name):
|
||||||
|
selected_object = Selection.getSelectedObject(0)
|
||||||
|
stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
|
||||||
|
if not stack:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
settings = stack.getTop()
|
||||||
|
setting_instance = settings.getInstance(setting_name)
|
||||||
|
if setting_instance:
|
||||||
|
setting_instance.propertyChanged.connect(self._onPropertyChanged)
|
||||||
|
|
||||||
|
def unsubscribeForSettingValidation(self, setting_name):
|
||||||
|
selected_object = Selection.getSelectedObject(0)
|
||||||
|
stack = selected_object.callDecoration("getStack") # Don't try to get the active extruder since it may be None anyway.
|
||||||
|
if not stack:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
settings = stack.getTop()
|
||||||
|
setting_instance = settings.getInstance(setting_name)
|
||||||
|
if setting_instance:
|
||||||
|
setting_instance.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
|
|
|
@ -62,7 +62,7 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
return
|
return
|
||||||
|
|
||||||
# get gcode list for the active build plate
|
# get gcode list for the active build plate
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
if not gcode_list:
|
if not gcode_list:
|
||||||
return
|
return
|
||||||
|
|
|
@ -93,7 +93,7 @@ class SimulationPass(RenderPass):
|
||||||
self.bind()
|
self.bind()
|
||||||
|
|
||||||
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
|
tool_handle_batch = RenderBatch(self._tool_handle_shader, type = RenderBatch.RenderType.Overlay, backface_cull = True)
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
head_position = None # Indicates the current position of the print head
|
head_position = None # Indicates the current position of the print head
|
||||||
nozzle_node = None
|
nozzle_node = None
|
||||||
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ class SupportEraser(Tool):
|
||||||
move_vector = Vector(0, 5, 0)
|
move_vector = Vector(0, 5, 0)
|
||||||
node.setPosition(move_vector)
|
node.setPosition(move_vector)
|
||||||
|
|
||||||
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
|
|
||||||
node.addDecorator(SettingOverrideDecorator())
|
node.addDecorator(SettingOverrideDecorator())
|
||||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||||
|
|
|
@ -77,11 +77,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||||
|
|
||||||
|
self._latest_reply_handler = None
|
||||||
|
|
||||||
|
|
||||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
|
|
||||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
|
|
||||||
if not gcode_list:
|
if not gcode_list:
|
||||||
|
@ -147,7 +150,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
|
|
||||||
parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, compressed_gcode))
|
parts.append(self._createFormPart("name=\"file\"; filename=\"%s\"" % file_name, compressed_gcode))
|
||||||
|
|
||||||
self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
|
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify=activePrinterChanged)
|
@pyqtProperty(QObject, notify=activePrinterChanged)
|
||||||
def activePrinter(self) -> Optional["PrinterOutputModel"]:
|
def activePrinter(self) -> Optional["PrinterOutputModel"]:
|
||||||
|
@ -187,6 +190,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self._sending_gcode = False
|
self._sending_gcode = False
|
||||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
Application.getInstance().getController().setActiveStage("PrepareStage")
|
||||||
|
|
||||||
|
# After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
|
||||||
|
# the "reply" should be disconnected
|
||||||
|
if self._latest_reply_handler:
|
||||||
|
self._latest_reply_handler.disconnect()
|
||||||
|
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def openPrintJobControlPanel(self):
|
def openPrintJobControlPanel(self):
|
||||||
Logger.log("d", "Opening print job control panel...")
|
Logger.log("d", "Opening print job control panel...")
|
||||||
|
@ -396,7 +405,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
color = material_data["color"]
|
color = material_data["color"]
|
||||||
brand = material_data["brand"]
|
brand = material_data["brand"]
|
||||||
material_type = material_data["material"]
|
material_type = material_data["material"]
|
||||||
name = "Unknown"
|
name = "Empty" if material_data["material"] == "empty" else "Unknown"
|
||||||
|
|
||||||
material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
material = MaterialOutputModel(guid=material_data["guid"], type=material_type,
|
||||||
brand=brand, color=color, name=name)
|
brand=brand, color=color, name=name)
|
||||||
|
|
|
@ -184,7 +184,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||||
self.writeStarted.emit(self)
|
self.writeStarted.emit(self)
|
||||||
|
|
||||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
|
|
||||||
if not gcode_list:
|
if not gcode_list:
|
||||||
|
|
|
@ -264,6 +264,7 @@ Rectangle
|
||||||
case "wait_for_configuration":
|
case "wait_for_configuration":
|
||||||
return catalog.i18nc("@label:status", "Reserved")
|
return catalog.i18nc("@label:status", "Reserved")
|
||||||
case "wait_cleanup":
|
case "wait_cleanup":
|
||||||
|
case "wait_user_action":
|
||||||
return catalog.i18nc("@label:status", "Finished")
|
return catalog.i18nc("@label:status", "Finished")
|
||||||
case "pre_print":
|
case "pre_print":
|
||||||
case "sent_to_printer":
|
case "sent_to_printer":
|
||||||
|
@ -278,6 +279,7 @@ Rectangle
|
||||||
case "aborted":
|
case "aborted":
|
||||||
return catalog.i18nc("@label:status", "Print aborted");
|
return catalog.i18nc("@label:status", "Print aborted");
|
||||||
default:
|
default:
|
||||||
|
// If print job has unknown status show printer.status
|
||||||
return printerStatusText(printer);
|
return printerStatusText(printer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -99,7 +99,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||||
|
|
||||||
# find the G-code for the active build plate to print
|
# find the G-code for the active build plate to print
|
||||||
active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
|
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
|
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
|
||||||
gcode_list = gcode_dict[active_build_plate_id]
|
gcode_list = gcode_dict[active_build_plate_id]
|
||||||
|
|
||||||
|
|
|
@ -3,14 +3,9 @@
|
||||||
|
|
||||||
import configparser #To parse preference files.
|
import configparser #To parse preference files.
|
||||||
import io #To serialise the preference files afterwards.
|
import io #To serialise the preference files afterwards.
|
||||||
import os
|
|
||||||
from urllib.parse import quote_plus
|
|
||||||
|
|
||||||
from UM.Resources import Resources
|
|
||||||
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
|
from UM.VersionUpgrade import VersionUpgrade #We're inheriting from this.
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
|
||||||
|
|
||||||
|
|
||||||
# a list of all legacy "Not Supported" quality profiles
|
# a list of all legacy "Not Supported" quality profiles
|
||||||
_OLD_NOT_SUPPORTED_PROFILES = [
|
_OLD_NOT_SUPPORTED_PROFILES = [
|
||||||
|
@ -130,7 +125,6 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||||
parser.write(output)
|
parser.write(output)
|
||||||
return [filename], [output.getvalue()]
|
return [filename], [output.getvalue()]
|
||||||
|
|
||||||
|
|
||||||
## Upgrades a container stack from version 3.0 to 3.1.
|
## Upgrades a container stack from version 3.0 to 3.1.
|
||||||
#
|
#
|
||||||
# \param serialised The serialised form of a container stack.
|
# \param serialised The serialised form of a container stack.
|
||||||
|
@ -172,71 +166,3 @@ class VersionUpgrade30to31(VersionUpgrade):
|
||||||
output = io.StringIO()
|
output = io.StringIO()
|
||||||
parser.write(output)
|
parser.write(output)
|
||||||
return [filename], [output.getvalue()]
|
return [filename], [output.getvalue()]
|
||||||
|
|
||||||
def _getSingleExtrusionMachineQualityChanges(self, quality_changes_container):
|
|
||||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
|
||||||
quality_changes_containers = []
|
|
||||||
|
|
||||||
for item in os.listdir(quality_changes_dir):
|
|
||||||
file_path = os.path.join(quality_changes_dir, item)
|
|
||||||
if not os.path.isfile(file_path):
|
|
||||||
continue
|
|
||||||
|
|
||||||
parser = configparser.ConfigParser(interpolation = None)
|
|
||||||
try:
|
|
||||||
parser.read([file_path])
|
|
||||||
except:
|
|
||||||
# skip, it is not a valid stack file
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not parser.has_option("metadata", "type"):
|
|
||||||
continue
|
|
||||||
if "quality_changes" != parser["metadata"]["type"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not parser.has_option("general", "name"):
|
|
||||||
continue
|
|
||||||
if quality_changes_container["general"]["name"] != parser["general"]["name"]:
|
|
||||||
continue
|
|
||||||
|
|
||||||
quality_changes_containers.append(parser)
|
|
||||||
|
|
||||||
return quality_changes_containers
|
|
||||||
|
|
||||||
def _createExtruderQualityChangesForSingleExtrusionMachine(self, filename, global_quality_changes):
|
|
||||||
suffix = "_" + quote_plus(global_quality_changes["general"]["name"].lower())
|
|
||||||
machine_name = os.path.os.path.basename(filename).replace(".inst.cfg", "").replace(suffix, "")
|
|
||||||
|
|
||||||
# Why is this here?!
|
|
||||||
# When we load a .curaprofile file the deserialize will trigger a version upgrade, creating a dangling file.
|
|
||||||
# This file can be recognized by it's lack of a machine name in the target filename.
|
|
||||||
# So when we detect that situation here, we don't create the file and return.
|
|
||||||
if machine_name == "":
|
|
||||||
return
|
|
||||||
|
|
||||||
new_filename = machine_name + "_" + "fdmextruder" + suffix
|
|
||||||
|
|
||||||
extruder_quality_changes_parser = configparser.ConfigParser(interpolation = None)
|
|
||||||
extruder_quality_changes_parser.add_section("general")
|
|
||||||
extruder_quality_changes_parser["general"]["version"] = str(2)
|
|
||||||
extruder_quality_changes_parser["general"]["name"] = global_quality_changes["general"]["name"]
|
|
||||||
extruder_quality_changes_parser["general"]["definition"] = global_quality_changes["general"]["definition"]
|
|
||||||
|
|
||||||
# check renamed definition
|
|
||||||
if extruder_quality_changes_parser["general"]["definition"] in _RENAMED_DEFINITION_DICT:
|
|
||||||
extruder_quality_changes_parser["general"]["definition"] = _RENAMED_DEFINITION_DICT[extruder_quality_changes_parser["general"]["definition"]]
|
|
||||||
|
|
||||||
extruder_quality_changes_parser.add_section("metadata")
|
|
||||||
extruder_quality_changes_parser["metadata"]["quality_type"] = global_quality_changes["metadata"]["quality_type"]
|
|
||||||
extruder_quality_changes_parser["metadata"]["type"] = global_quality_changes["metadata"]["type"]
|
|
||||||
extruder_quality_changes_parser["metadata"]["setting_version"] = str(4)
|
|
||||||
extruder_quality_changes_parser["metadata"]["extruder"] = "fdmextruder"
|
|
||||||
|
|
||||||
extruder_quality_changes_output = io.StringIO()
|
|
||||||
extruder_quality_changes_parser.write(extruder_quality_changes_output)
|
|
||||||
extruder_quality_changes_filename = quote_plus(new_filename) + ".inst.cfg"
|
|
||||||
|
|
||||||
quality_changes_dir = Resources.getPath(CuraApplication.ResourceTypes.QualityInstanceContainer)
|
|
||||||
|
|
||||||
with open(os.path.join(quality_changes_dir, extruder_quality_changes_filename), "w", encoding = "utf-8") as f:
|
|
||||||
f.write(extruder_quality_changes_output.getvalue())
|
|
||||||
|
|
|
@ -48,18 +48,35 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
|
|
||||||
## Overridden from InstanceContainer
|
## Overridden from InstanceContainer
|
||||||
# set the meta data for all machine / variant combinations
|
# set the meta data for all machine / variant combinations
|
||||||
def setMetaDataEntry(self, key, value):
|
#
|
||||||
|
# The "apply_to_all" flag indicates whether this piece of metadata should be applied to all material containers
|
||||||
|
# or just this specific container.
|
||||||
|
# For example, when you change the material name, you want to apply it to all its derived containers, but for
|
||||||
|
# some specific settings, they should only be applied to a machine/variant-specific container.
|
||||||
|
#
|
||||||
|
def setMetaDataEntry(self, key, value, apply_to_all = True):
|
||||||
registry = ContainerRegistry.getInstance()
|
registry = ContainerRegistry.getInstance()
|
||||||
if registry.isReadOnly(self.getId()):
|
if registry.isReadOnly(self.getId()):
|
||||||
return
|
return
|
||||||
|
|
||||||
super().setMetaDataEntry(key, value)
|
# Prevent recursion
|
||||||
|
if not apply_to_all:
|
||||||
|
super().setMetaDataEntry(key, value)
|
||||||
|
return
|
||||||
|
|
||||||
basefile = self.getMetaDataEntry("base_file", self.getId()) #if basefile is self.getId, this is a basefile.
|
# Get the MaterialGroup
|
||||||
# Update all containers that share basefile
|
material_manager = CuraApplication.getInstance().getMaterialManager()
|
||||||
for container in registry.findInstanceContainers(base_file = basefile):
|
root_material_id = self.getMetaDataEntry("base_file") #if basefile is self.getId, this is a basefile.
|
||||||
if container.getMetaDataEntry(key, None) != value: # Prevent recursion
|
material_group = material_manager.getMaterialGroup(root_material_id)
|
||||||
container.setMetaDataEntry(key, value)
|
|
||||||
|
# Update the root material container
|
||||||
|
root_material_container = material_group.root_material_node.getContainer()
|
||||||
|
root_material_container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||||
|
|
||||||
|
# Update all containers derived from it
|
||||||
|
for node in material_group.derived_material_node_list:
|
||||||
|
container = node.getContainer()
|
||||||
|
container.setMetaDataEntry(key, value, apply_to_all = False)
|
||||||
|
|
||||||
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
## Overridden from InstanceContainer, similar to setMetaDataEntry.
|
||||||
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
# without this function the setName would only set the name of the specific nozzle / material / machine combination container
|
||||||
|
@ -190,6 +207,8 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
machine_container_map = {}
|
machine_container_map = {}
|
||||||
machine_nozzle_map = {}
|
machine_nozzle_map = {}
|
||||||
|
|
||||||
|
variant_manager = CuraApplication.getInstance()._variant_manager
|
||||||
|
|
||||||
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
|
all_containers = registry.findInstanceContainers(GUID = self.getMetaDataEntry("GUID"), base_file = self.getId())
|
||||||
for container in all_containers:
|
for container in all_containers:
|
||||||
definition_id = container.getDefinition().getId()
|
definition_id = container.getDefinition().getId()
|
||||||
|
@ -202,9 +221,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if definition_id not in machine_nozzle_map:
|
if definition_id not in machine_nozzle_map:
|
||||||
machine_nozzle_map[definition_id] = {}
|
machine_nozzle_map[definition_id] = {}
|
||||||
|
|
||||||
variant = container.getMetaDataEntry("variant")
|
variant_name = container.getMetaDataEntry("variant_name")
|
||||||
if variant:
|
if variant_name:
|
||||||
machine_nozzle_map[definition_id][variant] = container
|
machine_nozzle_map[definition_id][variant_name] = variant_manager.getVariantNode(definition_id,
|
||||||
|
variant_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
machine_container_map[definition_id] = container
|
machine_container_map[definition_id] = container
|
||||||
|
@ -236,16 +256,12 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
self._addSettingElement(builder, instance)
|
self._addSettingElement(builder, instance)
|
||||||
|
|
||||||
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
||||||
for hotend_id, hotend in machine_nozzle_map[definition_id].items():
|
for hotend_name, variant_node in machine_nozzle_map[definition_id].items():
|
||||||
variant_containers = registry.findInstanceContainersMetadata(id = hotend.getMetaDataEntry("variant"))
|
|
||||||
if not variant_containers:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# The hotend identifier is not the containers name, but its "name".
|
# The hotend identifier is not the containers name, but its "name".
|
||||||
builder.start("hotend", {"id": variant_containers[0]["name"]})
|
builder.start("hotend", {"id": hotend_name})
|
||||||
|
|
||||||
# Compatible is a special case, as it's added as a meta data entry (instead of an instance).
|
# Compatible is a special case, as it's added as a meta data entry (instead of an instance).
|
||||||
compatible = hotend.getMetaDataEntry("compatible")
|
compatible = variant_node.metadata.get("compatible")
|
||||||
if compatible is not None:
|
if compatible is not None:
|
||||||
builder.start("setting", {"key": "hardware compatible"})
|
builder.start("setting", {"key": "hardware compatible"})
|
||||||
if compatible:
|
if compatible:
|
||||||
|
@ -254,7 +270,7 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
builder.data("no")
|
builder.data("no")
|
||||||
builder.end("setting")
|
builder.end("setting")
|
||||||
|
|
||||||
for instance in hotend.findInstances():
|
for instance in variant_node.getContainer().findInstances():
|
||||||
if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
|
if container.getInstance(instance.definition.key) and container.getProperty(instance.definition.key, "value") == instance.value:
|
||||||
# If the settings match that of the machine profile, just skip since we inherit the machine profile.
|
# If the settings match that of the machine profile, just skip since we inherit the machine profile.
|
||||||
continue
|
continue
|
||||||
|
@ -590,14 +606,10 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
if buildplate_id is None:
|
if buildplate_id is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
|
from cura.Machines.VariantManager import VariantType
|
||||||
id = buildplate_id)
|
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||||
if not variant_containers:
|
variant_node = variant_manager.getVariantNode(machine_id, buildplate_id)
|
||||||
# It is not really properly defined what "ID" is so also search for variants by name.
|
if not variant_node:
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(
|
|
||||||
definition = machine_id, name = buildplate_id)
|
|
||||||
|
|
||||||
if not variant_containers:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
buildplate_compatibility = machine_compatibility
|
buildplate_compatibility = machine_compatibility
|
||||||
|
@ -618,16 +630,14 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
|
|
||||||
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
hotends = machine.iterfind("./um:hotend", self.__namespaces)
|
||||||
for hotend in hotends:
|
for hotend in hotends:
|
||||||
hotend_id = hotend.get("id")
|
# The "id" field for hotends in material profiles are actually
|
||||||
if hotend_id is None:
|
hotend_name = hotend.get("id")
|
||||||
|
if hotend_name is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
|
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||||
if not variant_containers:
|
variant_node = variant_manager.getVariantNode(machine_id, hotend_name)
|
||||||
# It is not really properly defined what "ID" is so also search for variants by name.
|
if not variant_node:
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
|
|
||||||
|
|
||||||
if not variant_containers:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
hotend_compatibility = machine_compatibility
|
hotend_compatibility = machine_compatibility
|
||||||
|
@ -643,20 +653,20 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
else:
|
else:
|
||||||
Logger.log("d", "Unsupported material setting %s", key)
|
Logger.log("d", "Unsupported material setting %s", key)
|
||||||
|
|
||||||
new_hotend_id = self.getId() + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
|
new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
|
||||||
|
|
||||||
# Same as machine compatibility, keep the derived material containers consistent with the parent material
|
# Same as machine compatibility, keep the derived material containers consistent with the parent material
|
||||||
if ContainerRegistry.getInstance().isLoaded(new_hotend_id):
|
if ContainerRegistry.getInstance().isLoaded(new_hotend_specific_material_id):
|
||||||
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_id)[0]
|
new_hotend_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_specific_material_id)[0]
|
||||||
is_new_material = False
|
is_new_material = False
|
||||||
else:
|
else:
|
||||||
new_hotend_material = XmlMaterialProfile(new_hotend_id)
|
new_hotend_material = XmlMaterialProfile(new_hotend_specific_material_id)
|
||||||
is_new_material = True
|
is_new_material = True
|
||||||
|
|
||||||
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
new_hotend_material.setMetaData(copy.deepcopy(self.getMetaData()))
|
||||||
new_hotend_material.getMetaData()["id"] = new_hotend_id
|
new_hotend_material.getMetaData()["id"] = new_hotend_specific_material_id
|
||||||
new_hotend_material.getMetaData()["name"] = self.getName()
|
new_hotend_material.getMetaData()["name"] = self.getName()
|
||||||
new_hotend_material.getMetaData()["variant"] = variant_containers[0]["id"]
|
new_hotend_material.getMetaData()["variant_name"] = hotend_name
|
||||||
new_hotend_material.setDefinition(machine_id)
|
new_hotend_material.setDefinition(machine_id)
|
||||||
# Don't use setMetadata, as that overrides it for all materials with same base file
|
# Don't use setMetadata, as that overrides it for all materials with same base file
|
||||||
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
new_hotend_material.getMetaData()["compatible"] = hotend_compatibility
|
||||||
|
@ -833,39 +843,30 @@ class XmlMaterialProfile(InstanceContainer):
|
||||||
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
|
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_map["buildplate_recommended"]
|
||||||
|
|
||||||
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
|
for hotend in machine.iterfind("./um:hotend", cls.__namespaces):
|
||||||
hotend_id = hotend.get("id")
|
hotend_name = hotend.get("id")
|
||||||
if hotend_id is None:
|
if hotend_name is None:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = hotend_id)
|
|
||||||
if not variant_containers:
|
|
||||||
# It is not really properly defined what "ID" is so also search for variants by name.
|
|
||||||
variant_containers = ContainerRegistry.getInstance().findInstanceContainersMetadata(definition = machine_id, name = hotend_id)
|
|
||||||
|
|
||||||
hotend_compatibility = machine_compatibility
|
hotend_compatibility = machine_compatibility
|
||||||
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
||||||
key = entry.get("key")
|
key = entry.get("key")
|
||||||
if key == "hardware compatible":
|
if key == "hardware compatible":
|
||||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
||||||
|
|
||||||
new_hotend_id = container_id + "_" + machine_id + "_" + hotend_id.replace(" ", "_")
|
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
|
||||||
|
|
||||||
# Same as machine compatibility, keep the derived material containers consistent with the parent material
|
# Same as machine compatibility, keep the derived material containers consistent with the parent material
|
||||||
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_id)
|
found_materials = ContainerRegistry.getInstance().findInstanceContainersMetadata(id = new_hotend_specific_material_id)
|
||||||
if found_materials:
|
if found_materials:
|
||||||
new_hotend_material_metadata = found_materials[0]
|
new_hotend_material_metadata = found_materials[0]
|
||||||
else:
|
else:
|
||||||
new_hotend_material_metadata = {}
|
new_hotend_material_metadata = {}
|
||||||
|
|
||||||
new_hotend_material_metadata.update(base_metadata)
|
new_hotend_material_metadata.update(base_metadata)
|
||||||
if variant_containers:
|
new_hotend_material_metadata["variant_name"] = hotend_name
|
||||||
new_hotend_material_metadata["variant"] = variant_containers[0]["id"]
|
|
||||||
else:
|
|
||||||
new_hotend_material_metadata["variant"] = hotend_id
|
|
||||||
_with_missing_variants.append(new_hotend_material_metadata)
|
|
||||||
new_hotend_material_metadata["compatible"] = hotend_compatibility
|
new_hotend_material_metadata["compatible"] = hotend_compatibility
|
||||||
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
|
new_hotend_material_metadata["machine_manufacturer"] = machine_manufacturer
|
||||||
new_hotend_material_metadata["id"] = new_hotend_id
|
new_hotend_material_metadata["id"] = new_hotend_specific_material_id
|
||||||
new_hotend_material_metadata["definition"] = machine_id
|
new_hotend_material_metadata["definition"] = machine_id
|
||||||
if buildplate_map["buildplate_compatible"]:
|
if buildplate_map["buildplate_compatible"]:
|
||||||
new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
|
new_hotend_material_metadata["buildplate_compatible"] = buildplate_map["buildplate_compatible"]
|
||||||
|
@ -992,21 +993,3 @@ def _indent(elem, level = 0):
|
||||||
# before the last }
|
# before the last }
|
||||||
def _tag_without_namespace(element):
|
def _tag_without_namespace(element):
|
||||||
return element.tag[element.tag.rfind("}") + 1:]
|
return element.tag[element.tag.rfind("}") + 1:]
|
||||||
|
|
||||||
#While loading XML profiles, some of these profiles don't know what variant
|
|
||||||
#they belong to. We'd like to search by the machine ID and the variant's
|
|
||||||
#name, but we don't know the variant's ID. Not all variants have been loaded
|
|
||||||
#yet so we can't run a filter on the name and machine. The ID is unknown
|
|
||||||
#so we can't lazily load the variant either. So we have to wait until all
|
|
||||||
#the rest is loaded properly and then assign the correct variant to the
|
|
||||||
#material files that were missing it.
|
|
||||||
_with_missing_variants = []
|
|
||||||
def _fillMissingVariants():
|
|
||||||
registry = ContainerRegistry.getInstance()
|
|
||||||
for variant_metadata in _with_missing_variants:
|
|
||||||
variants = registry.findContainersMetadata(definition = variant_metadata["definition"], name = variant_metadata["variant"])
|
|
||||||
if not variants:
|
|
||||||
Logger.log("w", "Could not find variant for variant-specific material {material_id}.".format(material_id = variant_metadata["id"]))
|
|
||||||
continue
|
|
||||||
variant_metadata["variant"] = variants[0]["id"]
|
|
||||||
ContainerRegistry.allMetadataLoaded.connect(_fillMissingVariants)
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"platform": "anycubic_i3_mega_platform.stl",
|
"platform": "anycubic_i3_mega_platform.stl",
|
||||||
"has_materials": false,
|
"has_materials": false,
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"preferred_quality": "*normal*"
|
"preferred_quality_type": "normal"
|
||||||
},
|
},
|
||||||
|
|
||||||
"overrides":
|
"overrides":
|
||||||
|
|
|
@ -12,16 +12,14 @@
|
||||||
"platform": "builder_premium_platform.stl",
|
"platform": "builder_premium_platform.stl",
|
||||||
"platform_offset": [-126, -36, 117],
|
"platform_offset": [-126, -36, 117],
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"preferred_quality": "*Normal*",
|
"preferred_quality_type": "normal",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
"0": "builder_premium_large_rear",
|
"0": "builder_premium_large_rear",
|
||||||
"1": "builder_premium_large_front"
|
"1": "builder_premium_large_front"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Builder Premium Large" },
|
"machine_name": { "default_value": "Builder Premium Large" },
|
||||||
"machine_heated_bed": { "default_value": true },
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
@ -36,7 +34,7 @@
|
||||||
"default_material_print_temperature": { "value": "215" },
|
"default_material_print_temperature": { "value": "215" },
|
||||||
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
||||||
"material_standby_temperature": { "value": "material_print_temperature" },
|
"material_standby_temperature": { "value": "material_print_temperature" },
|
||||||
|
|
||||||
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
||||||
"switch_extruder_retraction_speed": {"default_value": 15 },
|
"switch_extruder_retraction_speed": {"default_value": 15 },
|
||||||
"switch_extruder_prime_speed": {"default_value": 15 },
|
"switch_extruder_prime_speed": {"default_value": 15 },
|
||||||
|
@ -58,9 +56,9 @@
|
||||||
"prime_tower_wipe_enabled": { "default_value": false },
|
"prime_tower_wipe_enabled": { "default_value": false },
|
||||||
"prime_tower_min_volume": { "default_value": 50 },
|
"prime_tower_min_volume": { "default_value": 50 },
|
||||||
"dual_pre_wipe": { "default_value": false },
|
"dual_pre_wipe": { "default_value": false },
|
||||||
|
|
||||||
"prime_blob_enable": { "enabled": true },
|
"prime_blob_enable": { "enabled": true },
|
||||||
|
|
||||||
"acceleration_enabled": { "value": "True" },
|
"acceleration_enabled": { "value": "True" },
|
||||||
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
||||||
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
||||||
|
@ -71,7 +69,7 @@
|
||||||
"acceleration_travel": { "value": "acceleration_print" },
|
"acceleration_travel": { "value": "acceleration_print" },
|
||||||
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
||||||
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
||||||
|
|
||||||
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
||||||
"cool_min_layer_time": { "default_value": 10 },
|
"cool_min_layer_time": { "default_value": 10 },
|
||||||
|
|
||||||
|
@ -84,9 +82,9 @@
|
||||||
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
||||||
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
||||||
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
||||||
|
|
||||||
"wall_thickness": { "value": "1.2" },
|
"wall_thickness": { "value": "1.2" },
|
||||||
|
|
||||||
"retraction_amount": { "default_value": 3 },
|
"retraction_amount": { "default_value": 3 },
|
||||||
"retraction_speed": { "default_value": 15 },
|
"retraction_speed": { "default_value": 15 },
|
||||||
"retraction_retract_speed": { "default_value": 15 },
|
"retraction_retract_speed": { "default_value": 15 },
|
||||||
|
@ -113,4 +111,4 @@
|
||||||
},
|
},
|
||||||
"machine_extruder_count": { "default_value": 2 }
|
"machine_extruder_count": { "default_value": 2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,16 +12,14 @@
|
||||||
"platform": "builder_premium_platform.stl",
|
"platform": "builder_premium_platform.stl",
|
||||||
"platform_offset": [-126, -36, 117],
|
"platform_offset": [-126, -36, 117],
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"preferred_quality": "*Normal*",
|
"preferred_quality_type": "normal",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
"0": "builder_premium_medium_rear",
|
"0": "builder_premium_medium_rear",
|
||||||
"1": "builder_premium_medium_front"
|
"1": "builder_premium_medium_front"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Builder Premium Medium" },
|
"machine_name": { "default_value": "Builder Premium Medium" },
|
||||||
"machine_heated_bed": { "default_value": true },
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
@ -36,7 +34,7 @@
|
||||||
"default_material_print_temperature": { "value": "215" },
|
"default_material_print_temperature": { "value": "215" },
|
||||||
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
||||||
"material_standby_temperature": { "value": "material_print_temperature" },
|
"material_standby_temperature": { "value": "material_print_temperature" },
|
||||||
|
|
||||||
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
||||||
"switch_extruder_retraction_speed": {"default_value": 15 },
|
"switch_extruder_retraction_speed": {"default_value": 15 },
|
||||||
"switch_extruder_prime_speed": {"default_value": 15 },
|
"switch_extruder_prime_speed": {"default_value": 15 },
|
||||||
|
@ -58,9 +56,9 @@
|
||||||
"prime_tower_wipe_enabled": { "default_value": false },
|
"prime_tower_wipe_enabled": { "default_value": false },
|
||||||
"prime_tower_min_volume": { "default_value": 50 },
|
"prime_tower_min_volume": { "default_value": 50 },
|
||||||
"dual_pre_wipe": { "default_value": false },
|
"dual_pre_wipe": { "default_value": false },
|
||||||
|
|
||||||
"prime_blob_enable": { "enabled": true },
|
"prime_blob_enable": { "enabled": true },
|
||||||
|
|
||||||
"acceleration_enabled": { "value": "True" },
|
"acceleration_enabled": { "value": "True" },
|
||||||
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
||||||
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
||||||
|
@ -71,7 +69,7 @@
|
||||||
"acceleration_travel": { "value": "acceleration_print" },
|
"acceleration_travel": { "value": "acceleration_print" },
|
||||||
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
||||||
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
||||||
|
|
||||||
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
||||||
"cool_min_layer_time": { "default_value": 10 },
|
"cool_min_layer_time": { "default_value": 10 },
|
||||||
|
|
||||||
|
@ -84,9 +82,9 @@
|
||||||
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
||||||
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
||||||
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
||||||
|
|
||||||
"wall_thickness": { "value": "1.2" },
|
"wall_thickness": { "value": "1.2" },
|
||||||
|
|
||||||
"retraction_amount": { "default_value": 3 },
|
"retraction_amount": { "default_value": 3 },
|
||||||
"retraction_speed": { "default_value": 15 },
|
"retraction_speed": { "default_value": 15 },
|
||||||
"retraction_retract_speed": { "default_value": 15 },
|
"retraction_retract_speed": { "default_value": 15 },
|
||||||
|
@ -113,4 +111,4 @@
|
||||||
},
|
},
|
||||||
"machine_extruder_count": { "default_value": 2 }
|
"machine_extruder_count": { "default_value": 2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,16 +11,14 @@
|
||||||
"platform": "builder_premium_platform.stl",
|
"platform": "builder_premium_platform.stl",
|
||||||
"platform_offset": [-126, -36, 117],
|
"platform_offset": [-126, -36, 117],
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"preferred_quality": "*Normal*",
|
"preferred_quality_type": "normal",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
"0": "builder_premium_small_rear",
|
"0": "builder_premium_small_rear",
|
||||||
"1": "builder_premium_small_front"
|
"1": "builder_premium_small_front"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Builder Premium Small" },
|
"machine_name": { "default_value": "Builder Premium Small" },
|
||||||
"machine_heated_bed": { "default_value": true },
|
"machine_heated_bed": { "default_value": true },
|
||||||
|
@ -35,7 +33,7 @@
|
||||||
"default_material_print_temperature": { "value": "215" },
|
"default_material_print_temperature": { "value": "215" },
|
||||||
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
|
||||||
"material_standby_temperature": { "value": "material_print_temperature" },
|
"material_standby_temperature": { "value": "material_print_temperature" },
|
||||||
|
|
||||||
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
"switch_extruder_retraction_speeds": {"default_value": 15 },
|
||||||
"switch_extruder_retraction_speed": {"default_value": 15 },
|
"switch_extruder_retraction_speed": {"default_value": 15 },
|
||||||
"switch_extruder_prime_speed": {"default_value": 15 },
|
"switch_extruder_prime_speed": {"default_value": 15 },
|
||||||
|
@ -57,9 +55,9 @@
|
||||||
"prime_tower_wipe_enabled": { "default_value": false },
|
"prime_tower_wipe_enabled": { "default_value": false },
|
||||||
"prime_tower_min_volume": { "default_value": 50 },
|
"prime_tower_min_volume": { "default_value": 50 },
|
||||||
"dual_pre_wipe": { "default_value": false },
|
"dual_pre_wipe": { "default_value": false },
|
||||||
|
|
||||||
"prime_blob_enable": { "enabled": true },
|
"prime_blob_enable": { "enabled": true },
|
||||||
|
|
||||||
"acceleration_enabled": { "value": "True" },
|
"acceleration_enabled": { "value": "True" },
|
||||||
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
"acceleration_layer_0": { "value": "acceleration_topbottom" },
|
||||||
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
|
||||||
|
@ -70,7 +68,7 @@
|
||||||
"acceleration_travel": { "value": "acceleration_print" },
|
"acceleration_travel": { "value": "acceleration_print" },
|
||||||
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
|
||||||
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
|
||||||
|
|
||||||
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
"cool_fan_full_at_height": { "value": "layer_height_0 + 2 * layer_height" },
|
||||||
"cool_min_layer_time": { "default_value": 10 },
|
"cool_min_layer_time": { "default_value": 10 },
|
||||||
|
|
||||||
|
@ -83,9 +81,9 @@
|
||||||
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
|
||||||
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
|
||||||
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
|
||||||
|
|
||||||
"wall_thickness": { "value": "1.2" },
|
"wall_thickness": { "value": "1.2" },
|
||||||
|
|
||||||
"retraction_amount": { "default_value": 3 },
|
"retraction_amount": { "default_value": 3 },
|
||||||
"retraction_speed": { "default_value": 15 },
|
"retraction_speed": { "default_value": 15 },
|
||||||
"retraction_retract_speed": { "default_value": 15 },
|
"retraction_retract_speed": { "default_value": 15 },
|
||||||
|
@ -112,4 +110,4 @@
|
||||||
},
|
},
|
||||||
"machine_extruder_count": { "default_value": 2 }
|
"machine_extruder_count": { "default_value": 2 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
"metadata": {
|
"metadata": {
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"author": "Scheepers",
|
"author": "Scheepers",
|
||||||
"manufacturer": "Cartesio bv",
|
"manufacturer": "MaukCC",
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
|
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
|
@ -14,10 +14,10 @@
|
||||||
"has_variant_materials": true,
|
"has_variant_materials": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
|
|
||||||
"variants_name": "Nozzle size",
|
"variants_name": "Tool",
|
||||||
"preferred_variant": "*0.8*",
|
"preferred_variant_name": "0.8 mm",
|
||||||
"preferred_material": "*pla*",
|
"preferred_material": "generic_pla",
|
||||||
"preferred_quality": "*normal*",
|
"preferred_quality_type": "normal",
|
||||||
|
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
|
@ -44,7 +44,7 @@
|
||||||
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
|
||||||
"material_print_temp_wait": { "default_value": false },
|
"material_print_temp_wait": { "default_value": false },
|
||||||
"material_bed_temp_wait": { "default_value": false },
|
"material_bed_temp_wait": { "default_value": false },
|
||||||
"prime_tower_enable": { "default_value": true },
|
"prime_tower_enable": { "default_value": false },
|
||||||
"prime_tower_wall_thickness": { "resolve": 0.7 },
|
"prime_tower_wall_thickness": { "resolve": 0.7 },
|
||||||
"prime_tower_size": { "value": 24.0 },
|
"prime_tower_size": { "value": 24.0 },
|
||||||
"prime_tower_position_x": { "value": 125 },
|
"prime_tower_position_x": { "value": 125 },
|
||||||
|
@ -55,15 +55,16 @@
|
||||||
[[215, 135], [-215, 135], [-215, 75], [215, 75]]
|
[[215, 135], [-215, 135], [-215, 75], [215, 75]]
|
||||||
]},
|
]},
|
||||||
"machine_start_gcode": {
|
"machine_start_gcode": {
|
||||||
"default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM140 S{material_bed_temperature_layer_0}\nM104 S120 T1\nM104 S120 T2\nM104 S120 T3\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 S21 T1\nM104 S21 T2\nM104 S21 T3\n\nM117 Printing .....\n"
|
"default_value": "\nM92 E159 ;2288 for V5 extruder\n\nM140 S{material_bed_temperature_layer_0}\nM104 T1 S120\nM104 T2 S120\nM104 T3 S120\n\nG21\nG90\nM42 S255 P13 ;chamber lights\nM42 S255 P12 ;fume extraction\nM204 S300 ;default acceleration\nM205 X10 ;default jerk\n\nM117 Homing Y ......\nG28 Y\nM117 Homing X ......\nG28 X\nM117 Homing Z ......\nG28 Z F100\nG1 Z10 F600\nG1 X70 Y20 F9000;go to wipe point\n\nM190 S{material_bed_temperature_layer_0}\n\nM117 Heating for 50 sec.\nG4 S20\nM117 Heating for 30 sec.\nG4 S20\nM117 Heating for 10 sec.\nM300 S1200 P1000\nG4 S9\n\nM117 purging nozzle....\nT0\nG92 E0;set E\nG1 E10 F100\nG92 E0\nG1 E-1 F600\n\nM117 wiping nozzle....\nG1 X1 Y24 F3000\nG1 X70 F9000\nG1 Z10 F900\n\nM104 T1 S21\nM104 T2 S21\nM104 T3 S21\n\nM117 Printing .....\n"
|
||||||
},
|
},
|
||||||
"machine_end_gcode": {
|
"machine_end_gcode": {
|
||||||
"default_value": "; -- END GCODE --\nM117 cooling down....\nM106 S255\nM140 S5\nM104 S5 T0\nM104 S5 T1\nM104 S5 T2\nM104 S5 T3\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\nM117 Finished.\n; -- end of GCODE --"
|
"default_value": "; -- END GCODE --\nM117 cooling down....\nM106 S255\nM140 S5\nM104 T0 S5\nM104 T1 S5\nM104 T2 S5\nM104 T3 S5\n\nG91\nG1 Z1 F900\nG90\n\nG1 X20.0 Y260.0 F6000\nG4 S7\nM84\nG4 S90\nM107\nM42 P12 S0\nM42 P13 S0\nM84\nT0\nM117 Finished.\n; -- end of GCODE --"
|
||||||
},
|
},
|
||||||
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
"layer_height": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
||||||
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
"layer_height_0": { "maximum_value": "(0.8 * min(extruderValues('machine_nozzle_size')))" },
|
||||||
"retraction_extra_prime_amount": { "minimum_value_warning": "-2.0" },
|
"retraction_extra_prime_amount": { "minimum_value_warning": "-2.0" },
|
||||||
"optimize_wall_printing_order": { "default_value": true },
|
"optimize_wall_printing_order": { "default_value": true },
|
||||||
|
"material_initial_print_temperature": {"maximum_value_warning": "material_print_temperature + 15" },
|
||||||
"machine_nozzle_heat_up_speed": {"default_value": 20},
|
"machine_nozzle_heat_up_speed": {"default_value": 20},
|
||||||
"machine_nozzle_cool_down_speed": {"default_value": 20},
|
"machine_nozzle_cool_down_speed": {"default_value": 20},
|
||||||
"machine_min_cool_heat_time_window": {"default_value": 5}
|
"machine_min_cool_heat_time_window": {"default_value": 5}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"author": "Michael Wildermuth",
|
"author": "Michael Wildermuth",
|
||||||
"manufacturer": "Creality3D",
|
"manufacturer": "Creality3D",
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"preferred_quality": "*Draft*"
|
"preferred_quality_type": "draft"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_width": {
|
"machine_width": {
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
"default_value": 60
|
"default_value": 60
|
||||||
},
|
},
|
||||||
"speed_travel": {
|
"speed_travel": {
|
||||||
"default_value": 100
|
"value": "100"
|
||||||
},
|
},
|
||||||
"retraction_amount": {
|
"retraction_amount": {
|
||||||
"default_value": 3.5
|
"default_value": 3.5
|
||||||
|
|
|
@ -42,7 +42,7 @@
|
||||||
"default_value": "elliptic"
|
"default_value": "elliptic"
|
||||||
},
|
},
|
||||||
"machine_gcode_flavor": {
|
"machine_gcode_flavor": {
|
||||||
"default_value": "RepRap"
|
"default_value": "RepRap (RepRap)"
|
||||||
},
|
},
|
||||||
"machine_start_gcode": {
|
"machine_start_gcode": {
|
||||||
"default_value": ";Gcode by Cura\nG90\nG28\nM109 S100\nG29\nM104 S{material_print_temperature_layer_0}\nG0 X0 Y-85\nG0 Z0.26\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
|
"default_value": ";Gcode by Cura\nG90\nG28\nM109 S100\nG29\nM104 S{material_print_temperature_layer_0}\nG0 X0 Y-85\nG0 Z0.26\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E6\nG92 E0\nG1 F200 E-3.5\nG0 Z0.15\nG0 X10\nG0 Z3\nG1 F6000\n"
|
||||||
|
|
|
@ -13,18 +13,18 @@
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"variants_name": "Head",
|
"variants_name": "Head",
|
||||||
"preferred_variant": "*lite04*",
|
"preferred_variant_name": "Lite 0.4 mm",
|
||||||
"preferred_material": "*fabtotum_pla*",
|
"preferred_material": "fabtotum_pla",
|
||||||
"supports_usb_connection": false
|
"supports_usb_connection": false
|
||||||
},
|
},
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "FABtotum Personal Fabricator" },
|
"machine_name": { "default_value": "FABtotum Personal Fabricator" },
|
||||||
"machine_start_gcode": {
|
"machine_start_gcode": {
|
||||||
"default_value": ";Layer height: {layer_height}\n;Walls: {wall_thickness}\n;Fill: {infill_sparse_density}\n;Top\\Bottom Thickness: {top_bottom_thickness}\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG4 S1 ;1 millisecond pause to buffer the bep bep \nM728 ;FAB bep bep (start the print, go check the oozing and skirt lines adesion) \nG4 S1 ;1 second pause to reach the printer (run fast)\nG92 E0 ;zero the extruded length \nG1 F200 E35 ;slowly extrude 35mm of filament to clean the nozzle and build up extrusion pressure \nG92 E0 ;zero the extruded length again \nG1 F{speed_travel} ;Set travel speed \n;print"
|
"default_value": ";Layer height: {layer_height}\n;Walls: {wall_thickness}\n;Fill: {infill_sparse_density}\n;Top\\Bottom Thickness: {top_bottom_thickness}\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nG4 S1 ;1 millisecond pause to buffer the bep bep \nM300 S2 ;FAB bep bep (start the print, go check the oozing and skirt lines adesion) \nG4 S1 ;1 second pause to reach the printer (run fast)\nG92 E0 ;zero the extruded length \nG1 F200 E35 ;slowly extrude 35mm of filament to clean the nozzle and build up extrusion pressure \nG92 E0 ;zero the extruded length again \n;print"
|
||||||
},
|
},
|
||||||
"machine_end_gcode": {
|
"machine_end_gcode": {
|
||||||
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM728 ;FAB bep bep (end print)"
|
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM300 S2 ;FAB bep bep (end print)"
|
||||||
},
|
},
|
||||||
"gantry_height": { "default_value": 55 },
|
"gantry_height": { "default_value": 55 },
|
||||||
"machine_width": { "default_value": 214 },
|
"machine_width": { "default_value": 214 },
|
||||||
|
|
|
@ -11,8 +11,8 @@
|
||||||
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
|
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj;application/x3g",
|
||||||
"visible": false,
|
"visible": false,
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"preferred_material": "*generic_pla*",
|
"preferred_material": "generic_pla",
|
||||||
"preferred_quality": "*normal*",
|
"preferred_quality_type": "normal",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
"0": "fdmextruder"
|
"0": "fdmextruder"
|
||||||
|
@ -2137,6 +2137,7 @@
|
||||||
"default_value": 1.5,
|
"default_value": 1.5,
|
||||||
"value": "line_width * 2",
|
"value": "line_width * 2",
|
||||||
"minimum_value": "0",
|
"minimum_value": "0",
|
||||||
|
"minimum_value_warning": "line_width * 1.5",
|
||||||
"maximum_value_warning": "10",
|
"maximum_value_warning": "10",
|
||||||
"enabled": "retraction_enable",
|
"enabled": "retraction_enable",
|
||||||
"settable_per_mesh": false,
|
"settable_per_mesh": false,
|
||||||
|
|
|
@ -10,11 +10,10 @@
|
||||||
"category": "Other",
|
"category": "Other",
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
||||||
|
"has_machine_quality": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"variants_name": "Hotend",
|
"variants_name": "Hotend",
|
||||||
"preferred_variant": "*0.5mm E3D (Default)*"
|
"preferred_variant_name": "0.5mm E3D (Default)"
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|
|
@ -11,12 +11,13 @@
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
"platform": "gmax_1-5_xt-plus_s3d_full model_150707.stl",
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
|
"has_machine_quality": true,
|
||||||
"variants_name": "Hotend",
|
"variants_name": "Hotend",
|
||||||
"preferred_variant": "*0.5mm E3D (Default)*",
|
"preferred_variant_name": "0.5mm E3D (Default)",
|
||||||
"machine_extruder_trains": {
|
"machine_extruder_trains": {
|
||||||
"0": "gmax15plus_dual_extruder_0",
|
"0": "gmax15plus_dual_extruder_0",
|
||||||
"1": "gmax15plus_dual_extruder_1"
|
"1": "gmax15plus_dual_extruder_1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
"overrides": {
|
"overrides": {
|
||||||
|
|
|
@ -9,9 +9,8 @@
|
||||||
"platform": "imade3d_jellybox_platform.stl",
|
"platform": "imade3d_jellybox_platform.stl",
|
||||||
"platform_offset": [ 0, -0.3, 0],
|
"platform_offset": [ 0, -0.3, 0],
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"preferred_variant": "*0.4*",
|
"preferred_variant_name": "0.4 mm",
|
||||||
"preferred_material": "*generic_pla*",
|
"preferred_quality_type": "fast",
|
||||||
"preferred_quality": "*fast*",
|
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"has_machine_materials": true,
|
"has_machine_materials": true,
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"platform": "malyan_m200_platform.stl",
|
"platform": "malyan_m200_platform.stl",
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"preferred_quality": "*normal*",
|
"preferred_quality_type": "normal",
|
||||||
"supports_usb_connection": true,
|
"supports_usb_connection": true,
|
||||||
"visible": true,
|
"visible": true,
|
||||||
"first_start_actions": ["MachineSettingsAction"],
|
"first_start_actions": ["MachineSettingsAction"],
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"preferred_quality": "*normal*",
|
"preferred_quality_type": "normal",
|
||||||
"visible": true
|
"visible": true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
"has_materials": false,
|
"has_materials": false,
|
||||||
"has_machine_quality": true,
|
"has_machine_quality": true,
|
||||||
"platform": "tevo_blackwidow.stl",
|
"platform": "tevo_blackwidow.stl",
|
||||||
"preferred_quality": "*normal*"
|
"preferred_quality_type": "normal"
|
||||||
},
|
},
|
||||||
"overrides":
|
"overrides":
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,12 +20,10 @@
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"machine_name": { "default_value": "Ultimaker 2" },
|
"machine_name": { "default_value": "Ultimaker 2" },
|
||||||
"machine_start_gcode" : {
|
"machine_start_gcode" : {
|
||||||
"default_value": "",
|
|
||||||
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"G21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nG28 Z0 ;move Z to bottom endstops\\nG28 X0 Y0 ;move X/Y to endstops\\nG1 X15 Y0 F4000 ;move X/Y to front of printer\\nG1 Z15.0 F9000 ;move the platform to 15mm\\nG92 E0 ;zero the extruded length\\nG1 F200 E10 ;extrude 10 mm of feed stock\\nG92 E0 ;zero the extruded length again\\nG1 F9000\\n;Put printing message on LCD screen\\nM117 Printing...\""
|
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"G21 ;metric values\\nG90 ;absolute positioning\\nM82 ;set extruder to absolute mode\\nM107 ;start with the fan off\\nG28 Z0 ;move Z to bottom endstops\\nG28 X0 Y0 ;move X/Y to endstops\\nG1 X15 Y0 F4000 ;move X/Y to front of printer\\nG1 Z15.0 F9000 ;move the platform to 15mm\\nG92 E0 ;zero the extruded length\\nG1 F200 E10 ;extrude 10 mm of feed stock\\nG92 E0 ;zero the extruded length again\\nG1 F9000\\n;Put printing message on LCD screen\\nM117 Printing...\""
|
||||||
},
|
},
|
||||||
"machine_end_gcode" : {
|
"machine_end_gcode" : {
|
||||||
"default_value": "",
|
"value": "\";Version _2.6 of the firmware can abort the print too early if the file ends\\n;too soon. However if the file hasn't ended yet because there are comments at\\n;the end of the file, it won't abort yet. Therefore we have to put at least 512\\n;bytes at the end of the g-code so that the file is not yet finished by the\\n;time that the motion planner gets flushed. With firmware version _3.3 this\\n;should be fixed, so this comment wouldn't be necessary any more. Now we have\\n;to pad this text to make precisely 512 bytes.\" if machine_gcode_flavor == \"UltiGCode\" else \"M104 S0 ;extruder heater off\\nM140 S0 ;heated bed heater off (if you have it)\\nG91 ;relative positioning\\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\\nM84 ;steppers off\\nG90 ;absolute positioning\\n;Version _2.6 of the firmware can abort the print too early if the file ends\\n;too soon. However if the file hasn't ended yet because there are comments at\\n;the end of the file, it won't abort yet. Therefore we have to put at least 512\\n;bytes at the end of the g-code so that the file is not yet finished by the\\n;time that the motion planner gets flushed. With firmware version _3.3 this\\n;should be fixed, so this comment wouldn't be necessary any more. Now we have\\n;to pad this text to make precisely 512 bytes.\""
|
||||||
"value": "\"\" if machine_gcode_flavor == \"UltiGCode\" else \"M104 S0 ;extruder heater off\\nM140 S0 ;heated bed heater off (if you have it)\\nG91 ;relative positioning\\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\\nG1 Z+0.5 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\\nM84 ;steppers off\\nG90 ;absolute positioning\""
|
|
||||||
},
|
},
|
||||||
"machine_width": {
|
"machine_width": {
|
||||||
"default_value": 223
|
"default_value": 223
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"file_formats": "text/x-gcode",
|
"file_formats": "text/x-gcode",
|
||||||
"platform": "ultimaker2_platform.obj",
|
"platform": "ultimaker2_platform.obj",
|
||||||
"platform_texture": "Ultimaker2Plusbackplate.png",
|
"platform_texture": "Ultimaker2Plusbackplate.png",
|
||||||
"preferred_variant": "*0.4*",
|
"preferred_variant_name": "0.4 mm",
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"has_machine_materials": true,
|
"has_machine_materials": true,
|
||||||
|
|
|
@ -15,8 +15,8 @@
|
||||||
"has_machine_materials": true,
|
"has_machine_materials": true,
|
||||||
"has_variant_materials": true,
|
"has_variant_materials": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"preferred_variant": "*aa04*",
|
"preferred_variant_name": "AA 0.4",
|
||||||
"preferred_quality": "*Normal*",
|
"preferred_quality_type": "normal",
|
||||||
"variants_name": "Print core",
|
"variants_name": "Print core",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
|
@ -123,7 +123,6 @@
|
||||||
"raft_margin": { "value": "10" },
|
"raft_margin": { "value": "10" },
|
||||||
"raft_surface_layers": { "value": "1" },
|
"raft_surface_layers": { "value": "1" },
|
||||||
"retraction_amount": { "value": "2" },
|
"retraction_amount": { "value": "2" },
|
||||||
"retraction_combing": { "default_value": "noskin" },
|
|
||||||
"retraction_count_max": { "value": "10" },
|
"retraction_count_max": { "value": "10" },
|
||||||
"retraction_extrusion_window": { "value": "1" },
|
"retraction_extrusion_window": { "value": "1" },
|
||||||
"retraction_hop": { "value": "2" },
|
"retraction_hop": { "value": "2" },
|
||||||
|
@ -154,6 +153,7 @@
|
||||||
"travel_avoid_distance": { "value": "3" },
|
"travel_avoid_distance": { "value": "3" },
|
||||||
"wall_0_inset": { "value": "0" },
|
"wall_0_inset": { "value": "0" },
|
||||||
"wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" },
|
"wall_line_width_x": { "value": "round(wall_line_width * 0.3 / 0.35, 2)" },
|
||||||
"wall_thickness": { "value": "1" }
|
"wall_thickness": { "value": "1" },
|
||||||
|
"zig_zaggify_infill": { "value": "True" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
"has_variant_materials": true,
|
"has_variant_materials": true,
|
||||||
"has_materials": true,
|
"has_materials": true,
|
||||||
"has_variants": true,
|
"has_variants": true,
|
||||||
"preferred_variant": "*aa04*",
|
"preferred_variant_name": "AA 0.4",
|
||||||
"variants_name": "Print core",
|
"variants_name": "Print core",
|
||||||
"machine_extruder_trains":
|
"machine_extruder_trains":
|
||||||
{
|
{
|
||||||
|
|
|
@ -16,17 +16,10 @@
|
||||||
"machine_nozzle_offset_x": { "default_value": 0.0 },
|
"machine_nozzle_offset_x": { "default_value": 0.0 },
|
||||||
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
||||||
"machine_extruder_start_code": {
|
"machine_extruder_start_code": {
|
||||||
"default_value": "\n;start extruder_0\n\nM117 printing...\n"
|
"default_value": "\n;start T0\n\nM104 T0 S{material_print_temperature_layer_0}\nG1 X65 Y35 F9000 ; go to wipe position\nM109 T0 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X45 Y15 F3000; wipe\nG1 X55 F9000\nG1 Y35 F6000; wipe again\n\nM117 printing...\n"
|
||||||
},
|
},
|
||||||
"machine_extruder_end_code": {
|
"machine_extruder_end_code": {
|
||||||
"default_value": "\nM104 T0 S120\n;end extruder_0\nM117 temp is {material_print_temp}\n"
|
"default_value": "\nM104 T0 S{material_standby_temperature}\nG1 X65 Y35 F9000 ; go to wipe position\nM109 T0 R{material_standby_temperature}; wait for temp\nG1 X45 Y15 F3000; wipe\nG1 X55 F9000\nG1 Y35 F6000; wipe again\n\n;end T0\n\n"
|
||||||
},
|
}
|
||||||
|
|
||||||
"machine_extruder_start_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_start_pos_x": { "default_value": 24 },
|
|
||||||
"machine_extruder_start_pos_y": { "default_value": 16 },
|
|
||||||
"machine_extruder_end_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_end_pos_x": { "default_value": 48 },
|
|
||||||
"machine_extruder_end_pos_y": { "default_value": 16 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,17 +16,10 @@
|
||||||
"machine_nozzle_offset_x": { "default_value": 24.0 },
|
"machine_nozzle_offset_x": { "default_value": 24.0 },
|
||||||
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
||||||
"machine_extruder_start_code": {
|
"machine_extruder_start_code": {
|
||||||
"default_value": "\n;start extruder_1\n\nM117 printing...\n"
|
"default_value": "\n;start T1\n\nM104 T1 S{material_print_temperature_layer_0}\nG1 X41 Y35 F9000 ; go to wipe position\nM109 T1 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X21 Y15 F3000; wipe\nG1 X34 F9000\nG1 Y35 F6000; wipe again\n\nM117 printing...\n"
|
||||||
},
|
},
|
||||||
"machine_extruder_end_code": {
|
"machine_extruder_end_code": {
|
||||||
"default_value": "\nM104 T1 S120\n;end extruder_1\n"
|
"default_value": "\nM104 T1 S{material_standby_temperature}\nG1 X41 Y35 F9000 ; go to wipe position\nM109 T1 R{material_standby_temperature}; wait for temp\nG1 X21 Y15 F3000; wipe\nG1 X31 F9000\nG1 Y35 F6000; wipe again\n\n;end T1\n\n"
|
||||||
},
|
}
|
||||||
|
|
||||||
"machine_extruder_start_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_start_pos_x": { "default_value": 48 },
|
|
||||||
"machine_extruder_start_pos_y": { "default_value": 16 },
|
|
||||||
"machine_extruder_end_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_end_pos_x": { "default_value": 24 },
|
|
||||||
"machine_extruder_end_pos_y": { "default_value": 16 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,20 +13,13 @@
|
||||||
"default_value": 2,
|
"default_value": 2,
|
||||||
"maximum_value": "3"
|
"maximum_value": "3"
|
||||||
},
|
},
|
||||||
"machine_nozzle_offset_x": { "default_value": 0.0 },
|
"machine_nozzle_offset_x": { "default_value": 24.0 },
|
||||||
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
"machine_nozzle_offset_y": { "default_value": -100.0 },
|
||||||
"machine_extruder_start_code": {
|
"machine_extruder_start_code": {
|
||||||
"default_value": "\n;start extruder_2\n\nM117 printing...\n"
|
"default_value": "\n;start T2\n\nM104 T2 S{material_print_temperature_layer_0}\nG1 X41 Y215 F9000 ; go to wipe position\nM109 T2 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X21 Y235 F3000; wipe\nG1 X31 F9000\nG1 Y215 F6000; wipe again\n\nM117 printing...\n"
|
||||||
},
|
},
|
||||||
"machine_extruder_end_code": {
|
"machine_extruder_end_code": {
|
||||||
"default_value": "\nM104 T2 S120\n;end extruder_2\n"
|
"default_value": "\nM104 T2 S{material_standby_temperature}\nG1 X41 Y215 F9000 ; go to wipe position\nM109 T2 R{material_standby_temperature}; wait for temp\nG1 X21 Y235 F3000; wipe\nG1 X31 F9000\nG1 Y215 F6000; wipe again\n\n;end T2\n\n"
|
||||||
},
|
}
|
||||||
|
|
||||||
"machine_extruder_start_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_start_pos_x": { "default_value": 24 },
|
|
||||||
"machine_extruder_start_pos_y": { "default_value": 309 },
|
|
||||||
"machine_extruder_end_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_end_pos_x": { "default_value": 48 },
|
|
||||||
"machine_extruder_end_pos_y": { "default_value": 309 }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,19 +14,12 @@
|
||||||
"maximum_value": "3"
|
"maximum_value": "3"
|
||||||
},
|
},
|
||||||
"machine_nozzle_offset_x": { "default_value": 0.0 },
|
"machine_nozzle_offset_x": { "default_value": 0.0 },
|
||||||
"machine_nozzle_offset_y": { "default_value": 0.0 },
|
"machine_nozzle_offset_y": { "default_value": -100.0 },
|
||||||
"machine_extruder_start_code": {
|
"machine_extruder_start_code": {
|
||||||
"default_value": "\n;start extruder_3\n\nM117 printing...\n"
|
"default_value": "\n;start T3\n\nM104 T3 S{material_print_temperature_layer_0}\nG1 X65 Y215 F9000 ; go to wipe position\nM109 T3 S{material_print_temperature_layer_0}; wait for temp\nG1 E10 F300; prime\nG92 E0\nG1 X45 Y235 F3000; wipe\nG1 X55 F9000\nG1 Y215 F6000; wipe again\n\nM117 printing...\n"
|
||||||
},
|
},
|
||||||
"machine_extruder_end_code": {
|
"machine_extruder_end_code": {
|
||||||
"default_value": "\nM104 T3 S120\n;end extruder_3\n"
|
"default_value": "\nM104 T3 S{material_standby_temperature}\nG1 X65 Y215 F9000 ; go to wipe position\nM109 T3 R{material_standby_temperature}; wait for temp\nG1 X45 Y235 F3000; wipe\nG1 X55 F9000\nG1 Y215 F6000; wipe again\n\n;end T3\n\n"
|
||||||
},
|
}
|
||||||
|
|
||||||
"machine_extruder_start_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_start_pos_x": { "default_value": 48 },
|
|
||||||
"machine_extruder_start_pos_y": { "default_value": 309 },
|
|
||||||
"machine_extruder_end_pos_abs": { "default_value": true },
|
|
||||||
"machine_extruder_end_pos_x": { "default_value": 24 },
|
|
||||||
"machine_extruder_end_pos_y": { "default_value": 309}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,22 @@
|
||||||
# Cura
|
# Cura
|
||||||
# Copyright (C) 2017 Ultimaker
|
# Copyright (C) 2018 Ultimaker
|
||||||
# This file is distributed under the same license as the Cura package.
|
# This file is distributed under the same license as the Cura package.
|
||||||
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
|
# Ruben Dulek <r.dulek@ultimaker.com>, 2018.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Cura 3.1\n"
|
"Project-Id-Version: Cura 3.2\n"
|
||||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
"POT-Creation-Date: 2018-01-29 16:53+0000\n"
|
||||||
"PO-Revision-Date: 2017-11-28 23:48+0800\n"
|
"PO-Revision-Date: 2018-02-04 11:18+0800\n"
|
||||||
"Last-Translator: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
"Last-Translator: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
||||||
"Language-Team: TEAM\n"
|
"Language-Team: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_TW\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
"X-Generator: Poedit 2.0.4\n"
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
|
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.py:26
|
||||||
msgctxt "@action"
|
msgctxt "@action"
|
||||||
|
@ -189,7 +189,7 @@ msgstr "印表機韌體"
|
||||||
#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
|
#: /home/ruben/Projects/Cura/plugins/PrepareStage/__init__.py:12
|
||||||
msgctxt "@item:inmenu"
|
msgctxt "@item:inmenu"
|
||||||
msgid "Prepare"
|
msgid "Prepare"
|
||||||
msgstr ""
|
msgstr "準備"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
|
#: /home/ruben/Projects/Cura/plugins/RemovableDriveOutputDevice/RemovableDriveOutputDevice.py:23
|
||||||
msgctxt "@action:button Preceded by 'Ready to'."
|
msgctxt "@action:button Preceded by 'Ready to'."
|
||||||
|
@ -562,7 +562,7 @@ msgstr "使用瀏覽器開啟列印作業介面。"
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:239
|
||||||
msgctxt "@label Printer name"
|
msgctxt "@label Printer name"
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr ""
|
msgstr "未知"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/NetworkClusterPrinterOutputDevice.py:505
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -597,7 +597,7 @@ msgstr "透過網路連接"
|
||||||
#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
|
#: /home/ruben/Projects/Cura/plugins/MonitorStage/__init__.py:12
|
||||||
msgctxt "@item:inmenu"
|
msgctxt "@item:inmenu"
|
||||||
msgid "Monitor"
|
msgid "Monitor"
|
||||||
msgstr ""
|
msgstr "監控"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
|
#: /home/ruben/Projects/Cura/plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerJob.py:66
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -624,7 +624,7 @@ msgstr "無法存取更新資訊。"
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:579
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
|
msgid "SolidWorks reported errors, while opening your file. We recommend to solve these issues inside SolidWorks itself."
|
||||||
msgstr ""
|
msgstr "SolidWorks 在開啟檔案時回報了錯誤。建議在 SolidWorks 內部解決這些問題。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:591
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -633,6 +633,9 @@ msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
" Thanks!."
|
" Thanks!."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"在你的繪圖找不到模型。請你再次檢查它的內容並確認裡面有一個零件或組件。\n"
|
||||||
|
"\n"
|
||||||
|
"謝謝。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksReader.py:595
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -641,6 +644,9 @@ msgid ""
|
||||||
"\n"
|
"\n"
|
||||||
"Sorry!"
|
"Sorry!"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"在你的繪圖找到了超過一個的零件或組件。我們目前只支援一個零件或組件的繪圖。\n"
|
||||||
|
"\n"
|
||||||
|
"抱歉!"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:25
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
|
@ -655,7 +661,7 @@ msgstr "SolidWorks 組件檔案"
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:33
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
msgid "SolidWorks drawing file"
|
msgid "SolidWorks drawing file"
|
||||||
msgstr ""
|
msgstr "SolidWorks 繪圖檔案"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:48
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -666,6 +672,11 @@ msgid ""
|
||||||
"With kind regards\n"
|
"With kind regards\n"
|
||||||
" - Thomas Karl Pietrowski"
|
" - Thomas Karl Pietrowski"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"親愛的客戶,\n"
|
||||||
|
"我們無法在您的系統上找到有效安裝的 SolidWorks。這表示未安裝 SolidWorks,或者您沒有擁有有效的授權。請確認 SolidWorks 本身是可以正常執行的,或是聯絡您的技術部門處理。\n"
|
||||||
|
"\n"
|
||||||
|
"順頌 時祺\n"
|
||||||
|
" - Thomas Karl Pietrowski"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/__init__.py:57
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -676,6 +687,11 @@ msgid ""
|
||||||
"With kind regards\n"
|
"With kind regards\n"
|
||||||
" - Thomas Karl Pietrowski"
|
" - Thomas Karl Pietrowski"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"親愛的客戶,\n"
|
||||||
|
"您目前正在 Windows 以外的作業系統上執行此外掛。此外掛只能在有安裝有效授權 SolidWorks 的 Windows 上執行。請在有安裝 SolidWorks 的 windows 電腦上安裝此外掛。\n"
|
||||||
|
"\n"
|
||||||
|
"順頌 時祺\n"
|
||||||
|
" - Thomas Karl Pietrowski"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:70
|
||||||
msgid "Configure"
|
msgid "Configure"
|
||||||
|
@ -683,7 +699,7 @@ msgstr "設定"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksDialogHandler.py:71
|
||||||
msgid "Installation guide for SolidWorks macro"
|
msgid "Installation guide for SolidWorks macro"
|
||||||
msgstr ""
|
msgstr "SolidWorks 巨集的安裝指南"
|
||||||
|
|
||||||
# Added manually to fix a string that was changed after string freeze.
|
# Added manually to fix a string that was changed after string freeze.
|
||||||
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/SimulationView/__init__.py:14
|
||||||
|
@ -708,7 +724,7 @@ msgstr "修改 G-Code 檔案"
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:43
|
||||||
msgctxt "@info"
|
msgctxt "@info"
|
||||||
msgid "Cura collects anonymized usage statistics."
|
msgid "Cura collects anonymized usage statistics."
|
||||||
msgstr ""
|
msgstr "Cura 以匿名方式蒐集使用狀況統計資料。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:46
|
||||||
msgctxt "@info:title"
|
msgctxt "@info:title"
|
||||||
|
@ -718,22 +734,22 @@ msgstr "收集資料中"
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:48
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Allow"
|
msgid "Allow"
|
||||||
msgstr ""
|
msgstr "允許"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:49
|
||||||
msgctxt "@action:tooltip"
|
msgctxt "@action:tooltip"
|
||||||
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
|
msgid "Allow Cura to send anonymized usage statistics to help prioritize future improvements to Cura. Some of your preferences and settings are sent, the Cura version and a hash of the models you're slicing."
|
||||||
msgstr ""
|
msgstr "允許 Cura 以匿名方式傳送使用狀況統計資料,用來協助 Cura 的未來改善工作。你的部份偏好設定和參數,Cura 的版本及你切片模型的雜湊值會被傳送。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:50
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Disable"
|
msgid "Disable"
|
||||||
msgstr ""
|
msgstr "關閉"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
|
#: /home/ruben/Projects/Cura/plugins/SliceInfoPlugin/SliceInfo.py:51
|
||||||
msgctxt "@action:tooltip"
|
msgctxt "@action:tooltip"
|
||||||
msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
|
msgid "Don't allow Cura to send anonymized usage statistics. You can enable it again in the preferences."
|
||||||
msgstr ""
|
msgstr "不允許 Cura 傳送匿名的使用狀況統計資料。你可以在偏好設定中再次啟用此功能。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/LegacyProfileReader/__init__.py:14
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
|
@ -743,7 +759,7 @@ msgstr "Cura 15.04 列印參數"
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
|
#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/__init__.py:15
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
msgid "Blender file"
|
msgid "Blender file"
|
||||||
msgstr ""
|
msgstr "Blender 檔案"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
|
#: /home/ruben/Projects/Cura/plugins/CuraBlenderPlugin/CadIntegrationUtils/CommonReader.py:199
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -751,6 +767,8 @@ msgid ""
|
||||||
"Could not export using \"{}\" quality!\n"
|
"Could not export using \"{}\" quality!\n"
|
||||||
"Felt back to \"{}\"."
|
"Felt back to \"{}\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"無法使用 \"{}\" 品質導出!\n"
|
||||||
|
"覆蓋回 \"{}\"。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/GCodeProfileReader/__init__.py:14
|
||||||
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
|
#: /home/ruben/Projects/Cura/plugins/GCodeReader/__init__.py:14
|
||||||
|
@ -937,12 +955,12 @@ msgstr "Cura 列印參數"
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
|
#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:12
|
||||||
msgctxt "@item:inmenu"
|
msgctxt "@item:inmenu"
|
||||||
msgid "Profile Assistant"
|
msgid "Profile Assistant"
|
||||||
msgstr ""
|
msgstr "參數助手"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
|
#: /home/ruben/Projects/Cura/plugins/CuraPrintProfileCreator/__init__.py:17
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
msgid "Profile Assistant"
|
msgid "Profile Assistant"
|
||||||
msgstr ""
|
msgstr "參數助手"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
|
#: /home/ruben/Projects/Cura/plugins/3MFWriter/__init__.py:30
|
||||||
msgctxt "@item:inlistbox"
|
msgctxt "@item:inlistbox"
|
||||||
|
@ -1140,13 +1158,13 @@ msgstr "無法從 <filename>{0}</filename> 匯入列印參數:<message>{1}</me
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:status Don't translate the XML tags <filename> or <message>!"
|
msgctxt "@info:status Don't translate the XML tags <filename> or <message>!"
|
||||||
msgid "This profile <filename>{0}</filename> contains incorrect data, could not import it."
|
msgid "This profile <filename>{0}</filename> contains incorrect data, could not import it."
|
||||||
msgstr ""
|
msgstr "此列印參數 <filename>{0}</filename> 含有錯誤的資料,無法導入。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
|
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:240
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:status Don't translate the XML tags <filename> or <message>!"
|
msgctxt "@info:status Don't translate the XML tags <filename> or <message>!"
|
||||||
msgid "The machine defined in profile <filename>{0}</filename> doesn't match with your current machine, could not import it."
|
msgid "The machine defined in profile <filename>{0}</filename> doesn't match with your current machine, could not import it."
|
||||||
msgstr ""
|
msgstr "列印參數 <filename>{0}</filename> 中的機器設定與你目前的機器不相符,無法導入。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
|
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:274
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -1158,7 +1176,7 @@ msgstr "已成功匯入列印參數 {0}"
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
msgid "File {0} does not contain any valid profile."
|
msgid "File {0} does not contain any valid profile."
|
||||||
msgstr ""
|
msgstr "檔案 {0} 內未含有效的列印參數。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
|
#: /home/ruben/Projects/Cura/cura/Settings/CuraContainerRegistry.py:280
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -1186,7 +1204,7 @@ msgstr "無法為目前設定找到品質類型 {0}。"
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Group #{group_nr}"
|
msgid "Group #{group_nr}"
|
||||||
msgstr ""
|
msgstr "群組 #{group_nr}"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
|
#: /home/ruben/Projects/Cura/cura/BuildVolume.py:100
|
||||||
msgctxt "@info:status"
|
msgctxt "@info:status"
|
||||||
|
@ -1246,6 +1264,9 @@ msgid ""
|
||||||
" <p>Please use the \"Send report\" button to post a bug report automatically to our servers</p>\n"
|
" <p>Please use the \"Send report\" button to post a bug report automatically to our servers</p>\n"
|
||||||
" "
|
" "
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"<p><b>發生致命錯誤。請將錯誤報告傳送給我們以便修正此問題。</p></b>\n"
|
||||||
|
" <p>請使用\"送出報告\"按鈕自動發送一份問題報告給我們的伺服器</p>\n"
|
||||||
|
" "
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:102
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:102
|
||||||
msgctxt "@title:groupbox"
|
msgctxt "@title:groupbox"
|
||||||
|
@ -1260,32 +1281,32 @@ msgstr "未知"
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:112
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:112
|
||||||
msgctxt "@label Cura version number"
|
msgctxt "@label Cura version number"
|
||||||
msgid "Cura version"
|
msgid "Cura version"
|
||||||
msgstr ""
|
msgstr "Cura 版本"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:113
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:113
|
||||||
msgctxt "@label Type of platform"
|
msgctxt "@label Type of platform"
|
||||||
msgid "Platform"
|
msgid "Platform"
|
||||||
msgstr ""
|
msgstr "平台"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:114
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:114
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Qt version"
|
msgid "Qt version"
|
||||||
msgstr ""
|
msgstr "Qt 版本"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:115
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:115
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "PyQt version"
|
msgid "PyQt version"
|
||||||
msgstr ""
|
msgstr "PyQt 版本"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:116
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:116
|
||||||
msgctxt "@label OpenGL version"
|
msgctxt "@label OpenGL version"
|
||||||
msgid "OpenGL"
|
msgid "OpenGL"
|
||||||
msgstr ""
|
msgstr "OpenGL"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:133
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:133
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "not yet initialised<br/>"
|
msgid "not yet initialised<br/>"
|
||||||
msgstr ""
|
msgstr "尚未初始化<br/>"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:136
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:136
|
||||||
#, python-brace-format
|
#, python-brace-format
|
||||||
|
@ -1308,7 +1329,7 @@ msgstr "<li>OpenGL 渲染器:{renderer}</li>"
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:147
|
||||||
msgctxt "@title:groupbox"
|
msgctxt "@title:groupbox"
|
||||||
msgid "Error traceback"
|
msgid "Error traceback"
|
||||||
msgstr ""
|
msgstr "錯誤追溯"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
|
#: /home/ruben/Projects/Cura/cura/CrashHandler.py:214
|
||||||
msgctxt "@title:groupbox"
|
msgctxt "@title:groupbox"
|
||||||
|
@ -1519,7 +1540,7 @@ msgstr "噴頭孔徑"
|
||||||
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
|
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:393
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Compatible material diameter"
|
msgid "Compatible material diameter"
|
||||||
msgstr ""
|
msgstr "相容的耗材直徑"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
|
#: /home/ruben/Projects/Cura/plugins/MachineSettingsAction/MachineSettingsAction.qml:395
|
||||||
msgctxt "@tooltip"
|
msgctxt "@tooltip"
|
||||||
|
@ -1664,12 +1685,12 @@ msgstr "類型"
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:233
|
||||||
msgctxt "@label Printer name"
|
msgctxt "@label Printer name"
|
||||||
msgid "Ultimaker 3"
|
msgid "Ultimaker 3"
|
||||||
msgstr ""
|
msgstr "Ultimaker 3"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:236
|
||||||
msgctxt "@label Printer name"
|
msgctxt "@label Printer name"
|
||||||
msgid "Ultimaker 3 Extended"
|
msgid "Ultimaker 3 Extended"
|
||||||
msgstr ""
|
msgstr "Ultimaker 3 Extended"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/DiscoverUM3Action.qml:252
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -1736,7 +1757,7 @@ msgstr "%1 未設定成管理一組連線的 Ultimaker 3 印表機的主機"
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/ClusterMonitorItem.qml:55
|
||||||
msgctxt "@label link to connect manager"
|
msgctxt "@label link to connect manager"
|
||||||
msgid "Add/Remove printers"
|
msgid "Add/Remove printers"
|
||||||
msgstr ""
|
msgstr "新增/移除印表機"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/OpenPanelButton.qml:14
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
|
@ -1776,7 +1797,7 @@ msgstr "與印表機的連接中斷"
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:47
|
||||||
msgctxt "@label Printer status"
|
msgctxt "@label Printer status"
|
||||||
msgid "Unknown"
|
msgid "Unknown"
|
||||||
msgstr ""
|
msgstr "未知"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
|
#: /home/ruben/Projects/Cura/plugins/UM3NetworkPrinting/PrinterInfoBlock.qml:257
|
||||||
msgctxt "@label:status"
|
msgctxt "@label:status"
|
||||||
|
@ -1872,62 +1893,62 @@ msgstr "啟用設定"
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:21
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "SolidWorks: Export wizard"
|
msgid "SolidWorks: Export wizard"
|
||||||
msgstr ""
|
msgstr "SolidWorks: 導出精靈"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:45
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:140
|
||||||
msgctxt "@action:label"
|
msgctxt "@action:label"
|
||||||
msgid "Quality:"
|
msgid "Quality:"
|
||||||
msgstr ""
|
msgstr "品質:"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:78
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:179
|
||||||
msgctxt "@option:curaSolidworksStlQuality"
|
msgctxt "@option:curaSolidworksStlQuality"
|
||||||
msgid "Fine (3D-printing)"
|
msgid "Fine (3D-printing)"
|
||||||
msgstr ""
|
msgstr "精細 (3D-printing)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:79
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:180
|
||||||
msgctxt "@option:curaSolidworksStlQuality"
|
msgctxt "@option:curaSolidworksStlQuality"
|
||||||
msgid "Coarse (3D-printing)"
|
msgid "Coarse (3D-printing)"
|
||||||
msgstr ""
|
msgstr "粗糙 (3D-printing)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:80
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:181
|
||||||
msgctxt "@option:curaSolidworksStlQuality"
|
msgctxt "@option:curaSolidworksStlQuality"
|
||||||
msgid "Fine (SolidWorks)"
|
msgid "Fine (SolidWorks)"
|
||||||
msgstr ""
|
msgstr "精細 (SolidWorks)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:81
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:182
|
||||||
msgctxt "@option:curaSolidworksStlQuality"
|
msgctxt "@option:curaSolidworksStlQuality"
|
||||||
msgid "Coarse (SolidWorks)"
|
msgid "Coarse (SolidWorks)"
|
||||||
msgstr ""
|
msgstr "粗糙 (SolidWorks)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:94
|
||||||
msgctxt "@text:window"
|
msgctxt "@text:window"
|
||||||
msgid "Show this dialog again"
|
msgid "Show this dialog again"
|
||||||
msgstr ""
|
msgstr "再次顯示這個對話框"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:104
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Continue"
|
msgid "Continue"
|
||||||
msgstr ""
|
msgstr "繼續"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksWizard.qml:116
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Abort"
|
msgid "Abort"
|
||||||
msgstr ""
|
msgstr "取消"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:21
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "How to install Cura SolidWorks macro"
|
msgid "How to install Cura SolidWorks macro"
|
||||||
msgstr ""
|
msgstr "如何安裝 Cura SolidWorks 巨集"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:62
|
||||||
msgctxt "@description:label"
|
msgctxt "@description:label"
|
||||||
msgid "Steps:"
|
msgid "Steps:"
|
||||||
msgstr ""
|
msgstr "步驟:"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:140
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -1935,101 +1956,103 @@ msgid ""
|
||||||
"Open the directory\n"
|
"Open the directory\n"
|
||||||
"with macro and icon"
|
"with macro and icon"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
"使用巨集和圖示\n"
|
||||||
|
"開啟目錄"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:160
|
||||||
msgctxt "@description:label"
|
msgctxt "@description:label"
|
||||||
msgid "Instructions:"
|
msgid "Instructions:"
|
||||||
msgstr ""
|
msgstr "操作說明:"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:202
|
||||||
msgctxt "@action:playpause"
|
msgctxt "@action:playpause"
|
||||||
msgid "Play"
|
msgid "Play"
|
||||||
msgstr ""
|
msgstr "播放"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:206
|
||||||
msgctxt "@action:playpause"
|
msgctxt "@action:playpause"
|
||||||
msgid "Pause"
|
msgid "Pause"
|
||||||
msgstr ""
|
msgstr "暫停"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:268
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Previous Step"
|
msgid "Previous Step"
|
||||||
msgstr ""
|
msgstr "前一步"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:283
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Done"
|
msgid "Done"
|
||||||
msgstr ""
|
msgstr "完成"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksMacroTutorial.qml:287
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Next Step"
|
msgid "Next Step"
|
||||||
msgstr ""
|
msgstr "下一步"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:21
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "SolidWorks plugin: Configuration"
|
msgid "SolidWorks plugin: Configuration"
|
||||||
msgstr ""
|
msgstr "SolidWorks 外掛:設定"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:39
|
||||||
msgctxt "@title:tab"
|
msgctxt "@title:tab"
|
||||||
msgid "Conversion settings"
|
msgid "Conversion settings"
|
||||||
msgstr ""
|
msgstr "轉換設定"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:66
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "First choice:"
|
msgid "First choice:"
|
||||||
msgstr ""
|
msgstr "第一選擇:"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:86
|
||||||
msgctxt "@text:menu"
|
msgctxt "@text:menu"
|
||||||
msgid "Latest installed version (Recommended)"
|
msgid "Latest installed version (Recommended)"
|
||||||
msgstr ""
|
msgstr "最新安裝的版本(建議)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:95
|
||||||
msgctxt "@text:menu"
|
msgctxt "@text:menu"
|
||||||
msgid "Default version"
|
msgid "Default version"
|
||||||
msgstr ""
|
msgstr "預設的版本"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:193
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Show wizard before opening SolidWorks files"
|
msgid "Show wizard before opening SolidWorks files"
|
||||||
msgstr ""
|
msgstr "開啟 SolidWorks 檔案時顯示精靈"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:203
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Automatically rotate opened file into normed orientation"
|
msgid "Automatically rotate opened file into normed orientation"
|
||||||
msgstr ""
|
msgstr "自動將開啟的檔案旋轉到正常方向"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:210
|
||||||
msgctxt "@title:tab"
|
msgctxt "@title:tab"
|
||||||
msgid "Installation(s)"
|
msgid "Installation(s)"
|
||||||
msgstr ""
|
msgstr "安裝"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:284
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "COM service found"
|
msgid "COM service found"
|
||||||
msgstr ""
|
msgstr "找到 COM 服務"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:295
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Executable found"
|
msgid "Executable found"
|
||||||
msgstr ""
|
msgstr "找到可執行檔案"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:306
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "COM starting"
|
msgid "COM starting"
|
||||||
msgstr ""
|
msgstr "COM 啟動中"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:317
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Revision number"
|
msgid "Revision number"
|
||||||
msgstr ""
|
msgstr "版本號碼"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:328
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Functions available"
|
msgid "Functions available"
|
||||||
msgstr ""
|
msgstr "可用功能"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
|
#: /home/ruben/Projects/Cura/plugins/CuraSolidWorksPlugin/SolidWorksConfiguration.qml:341
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
|
#: /home/ruben/Projects/Cura/resources/qml/WorkspaceSummaryDialog.qml:264
|
||||||
|
@ -2215,32 +2238,32 @@ msgstr "平滑"
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:38
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Mesh Type"
|
msgid "Mesh Type"
|
||||||
msgstr ""
|
msgstr "網格類型"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:69
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Normal model"
|
msgid "Normal model"
|
||||||
msgstr ""
|
msgstr "普通模型"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:76
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Print as support"
|
msgid "Print as support"
|
||||||
msgstr ""
|
msgstr "做為支撐"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:84
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Don't support overlap with other models"
|
msgid "Don't support overlap with other models"
|
||||||
msgstr ""
|
msgstr "不支援與其他模型重疊"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:92
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Modify settings for overlap with other models"
|
msgid "Modify settings for overlap with other models"
|
||||||
msgstr ""
|
msgstr "修改其他模型的重疊設定"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:100
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Modify settings for infill of other models"
|
msgid "Modify settings for infill of other models"
|
||||||
msgstr ""
|
msgstr "修改其他模型的填充設定"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
|
#: /home/ruben/Projects/Cura/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml:333
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -2742,7 +2765,7 @@ msgstr "保留"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:222
|
#: /home/ruben/Projects/Cura/resources/qml/DiscardOrKeepProfileChangesDialog.qml:222
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Create New Profile"
|
msgid "Create New Profile"
|
||||||
msgstr "創建新的列印參數"
|
msgstr "建立新的列印參數"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:44
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialView.qml:44
|
||||||
msgctxt "@title"
|
msgctxt "@title"
|
||||||
|
@ -3098,27 +3121,27 @@ msgstr "(匿名)發送列印資訊"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:674
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Experimental"
|
msgid "Experimental"
|
||||||
msgstr ""
|
msgstr "實驗功能"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:680
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Use multi build plate functionality"
|
msgid "Use multi build plate functionality"
|
||||||
msgstr ""
|
msgstr "使用多列印平台功能"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:685
|
||||||
msgctxt "@option:check"
|
msgctxt "@option:check"
|
||||||
msgid "Use multi build plate functionality (restart required)"
|
msgid "Use multi build plate functionality (restart required)"
|
||||||
msgstr ""
|
msgstr "使用多列印平台功能(需重啟軟體)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:694
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
|
msgid "Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)"
|
||||||
msgstr ""
|
msgstr "新載入的模型要擺放在列印平台上嗎?必需與多列印平台功能一起使用(實驗功能)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/GeneralPage.qml:699
|
||||||
msgctxt "@option:check"
|
msgctxt "@option:check"
|
||||||
msgid "Do not arrange objects on load"
|
msgid "Do not arrange objects on load"
|
||||||
msgstr ""
|
msgstr "載入時不要擺放物件"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MachinesPage.qml:15
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:514
|
||||||
|
@ -3189,7 +3212,7 @@ msgstr "自訂列印參數"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:64
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:64
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "創建"
|
msgstr "建立"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:80
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:80
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3246,7 +3269,7 @@ msgstr "重命名列印參數"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:271
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:271
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
msgid "Create Profile"
|
msgid "Create Profile"
|
||||||
msgstr "創建列印參數"
|
msgstr "建立列印參數"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:285
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/ProfilesPage.qml:285
|
||||||
msgctxt "@title:window"
|
msgctxt "@title:window"
|
||||||
|
@ -3287,7 +3310,7 @@ msgstr "印表機:%1"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:150
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Create"
|
msgid "Create"
|
||||||
msgstr "創建"
|
msgstr "建立"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
|
#: /home/ruben/Projects/Cura/resources/qml/Preferences/MaterialsPage.qml:160
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
|
@ -3531,7 +3554,7 @@ msgstr "影響因素"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:156
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
|
msgid "This setting is always shared between all extruders. Changing it here will change the value for all extruders."
|
||||||
msgstr ""
|
msgstr "這個設定是所有擠出機共用的。修改它會同時更動到所有擠出機的值。"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
|
#: /home/ruben/Projects/Cura/resources/qml/Settings/SettingItem.qml:159
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3582,7 +3605,7 @@ msgstr "00 小時 00 分"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
|
#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:359
|
||||||
msgctxt "@tooltip"
|
msgctxt "@tooltip"
|
||||||
msgid "Time specification"
|
msgid "Time specification"
|
||||||
msgstr ""
|
msgstr "時間規格"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
|
#: /home/ruben/Projects/Cura/resources/qml/Sidebar.qml:441
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
|
@ -3639,12 +3662,12 @@ msgstr "檢視(&V)"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:37
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Camera position"
|
msgid "&Camera position"
|
||||||
msgstr ""
|
msgstr "視角位置(&C)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/ViewMenu.qml:52
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Build plate"
|
msgid "&Build plate"
|
||||||
msgstr ""
|
msgstr "列印平台(&B)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
|
#: /home/ruben/Projects/Cura/resources/qml/Menus/NozzleMenu.qml:40
|
||||||
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
|
msgctxt "@title:menuitem %1 is the nozzle currently loaded in the printer"
|
||||||
|
@ -3812,27 +3835,27 @@ msgstr "退出(&Q)"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:114
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&3D View"
|
msgid "&3D View"
|
||||||
msgstr ""
|
msgstr "立體圖(&3)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:121
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Front View"
|
msgid "&Front View"
|
||||||
msgstr ""
|
msgstr "前視圖(&F)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:128
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Top View"
|
msgid "&Top View"
|
||||||
msgstr ""
|
msgstr "上視圖(&T)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:135
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Left Side View"
|
msgid "&Left Side View"
|
||||||
msgstr ""
|
msgstr "左視圖(&L)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:142
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "&Right Side View"
|
msgid "&Right Side View"
|
||||||
msgstr ""
|
msgstr "右視圖(&R)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:149
|
||||||
msgctxt "@action:inmenu"
|
msgctxt "@action:inmenu"
|
||||||
|
@ -3867,7 +3890,7 @@ msgstr "捨棄目前更改(&D)"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:197
|
||||||
msgctxt "@action:inmenu menubar:profile"
|
msgctxt "@action:inmenu menubar:profile"
|
||||||
msgid "&Create profile from current settings/overrides..."
|
msgid "&Create profile from current settings/overrides..."
|
||||||
msgstr "從目前設定 / 覆寫值創建列印參數(&C)…"
|
msgstr "從目前設定 / 覆寫值建立列印參數(&C)…"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:203
|
||||||
msgctxt "@action:inmenu menubar:profile"
|
msgctxt "@action:inmenu menubar:profile"
|
||||||
|
@ -3955,17 +3978,17 @@ msgstr "重新載入所有模型(&L)"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:351
|
||||||
msgctxt "@action:inmenu menubar:edit"
|
msgctxt "@action:inmenu menubar:edit"
|
||||||
msgid "Arrange All Models To All Build Plates"
|
msgid "Arrange All Models To All Build Plates"
|
||||||
msgstr ""
|
msgstr "將所有模型排列到所有列印平台上"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:358
|
||||||
msgctxt "@action:inmenu menubar:edit"
|
msgctxt "@action:inmenu menubar:edit"
|
||||||
msgid "Arrange All Models"
|
msgid "Arrange All Models"
|
||||||
msgstr "編位所有的模型"
|
msgstr "排列所有模型"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:366
|
||||||
msgctxt "@action:inmenu menubar:edit"
|
msgctxt "@action:inmenu menubar:edit"
|
||||||
msgid "Arrange Selection"
|
msgid "Arrange Selection"
|
||||||
msgstr "為所選模型編位"
|
msgstr "排列所選模型"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:373
|
||||||
msgctxt "@action:inmenu menubar:edit"
|
msgctxt "@action:inmenu menubar:edit"
|
||||||
|
@ -4015,7 +4038,7 @@ msgstr "安裝外掛..."
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
|
#: /home/ruben/Projects/Cura/resources/qml/Actions.qml:438
|
||||||
msgctxt "@action:inmenu menubar:view"
|
msgctxt "@action:inmenu menubar:view"
|
||||||
msgid "Expand/Collapse Sidebar"
|
msgid "Expand/Collapse Sidebar"
|
||||||
msgstr ""
|
msgstr "展開/收合側邊欄"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
|
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:26
|
||||||
msgctxt "@label:PrintjobStatus"
|
msgctxt "@label:PrintjobStatus"
|
||||||
|
@ -4050,12 +4073,12 @@ msgstr "切片無法使用"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
|
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Slice current printjob"
|
msgid "Slice current printjob"
|
||||||
msgstr ""
|
msgstr "對目前列印工作進行切片"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
|
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:171
|
||||||
msgctxt "@info:tooltip"
|
msgctxt "@info:tooltip"
|
||||||
msgid "Cancel slicing process"
|
msgid "Cancel slicing process"
|
||||||
msgstr ""
|
msgstr "取消進行中的切片程序"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
|
#: /home/ruben/Projects/Cura/resources/qml/SaveButton.qml:183
|
||||||
msgctxt "@label:Printjob"
|
msgctxt "@label:Printjob"
|
||||||
|
@ -4111,7 +4134,7 @@ msgstr "另存為(&A)…"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:139
|
||||||
msgctxt "@title:menu menubar:file"
|
msgctxt "@title:menu menubar:file"
|
||||||
msgid "Save &Project..."
|
msgid "Save &Project..."
|
||||||
msgstr ""
|
msgstr "儲存專案...(&P)"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
|
#: /home/ruben/Projects/Cura/resources/qml/Cura.qml:162
|
||||||
msgctxt "@title:menu menubar:toplevel"
|
msgctxt "@title:menu menubar:toplevel"
|
||||||
|
@ -4349,7 +4372,7 @@ msgstr "耗材"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:352
|
||||||
msgctxt "@label"
|
msgctxt "@label"
|
||||||
msgid "Check compatibility"
|
msgid "Check compatibility"
|
||||||
msgstr ""
|
msgstr "檢查相容性"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
#: /home/ruben/Projects/Cura/resources/qml/SidebarHeader.qml:372
|
||||||
msgctxt "@tooltip"
|
msgctxt "@tooltip"
|
||||||
|
@ -4359,17 +4382,17 @@ msgstr "點擊查看 Ultimaker.com 上的耗材相容性。"
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
|
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:211
|
||||||
msgctxt "@option:check"
|
msgctxt "@option:check"
|
||||||
msgid "See only current build plate"
|
msgid "See only current build plate"
|
||||||
msgstr ""
|
msgstr "只顯示目前的列印平台"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
|
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:227
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Arrange to all build plates"
|
msgid "Arrange to all build plates"
|
||||||
msgstr ""
|
msgstr "擺放到所有的列印平台"
|
||||||
|
|
||||||
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
|
#: /home/ruben/Projects/Cura/resources/qml/ObjectsList.qml:247
|
||||||
msgctxt "@action:button"
|
msgctxt "@action:button"
|
||||||
msgid "Arrange current build plate"
|
msgid "Arrange current build plate"
|
||||||
msgstr ""
|
msgstr "擺放到目前的列印平台"
|
||||||
|
|
||||||
#: MachineSettingsAction/plugin.json
|
#: MachineSettingsAction/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4444,7 +4467,7 @@ msgstr "更新日誌"
|
||||||
#: ProfileFlattener/plugin.json
|
#: ProfileFlattener/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Create a flattend quality changes profile."
|
msgid "Create a flattend quality changes profile."
|
||||||
msgstr "創建一份合併品質變化列印參數。"
|
msgstr "建立一份合併品質變化列印參數。"
|
||||||
|
|
||||||
#: ProfileFlattener/plugin.json
|
#: ProfileFlattener/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4464,22 +4487,22 @@ msgstr "USB 連線列印"
|
||||||
#: PrepareStage/plugin.json
|
#: PrepareStage/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides a prepare stage in Cura."
|
msgid "Provides a prepare stage in Cura."
|
||||||
msgstr ""
|
msgstr "在 cura 提供一個準備介面。"
|
||||||
|
|
||||||
#: PrepareStage/plugin.json
|
#: PrepareStage/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Prepare Stage"
|
msgid "Prepare Stage"
|
||||||
msgstr ""
|
msgstr "準備介面"
|
||||||
|
|
||||||
#: CuraLiveScriptingPlugin/plugin.json
|
#: CuraLiveScriptingPlugin/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides an edit window for direct script editing."
|
msgid "Provides an edit window for direct script editing."
|
||||||
msgstr ""
|
msgstr "提供一個直接編輯描述檔的編輯視窗。"
|
||||||
|
|
||||||
#: CuraLiveScriptingPlugin/plugin.json
|
#: CuraLiveScriptingPlugin/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Live scripting tool"
|
msgid "Live scripting tool"
|
||||||
msgstr ""
|
msgstr "即時描述檔工具"
|
||||||
|
|
||||||
#: RemovableDriveOutputDevice/plugin.json
|
#: RemovableDriveOutputDevice/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4504,12 +4527,12 @@ msgstr "UM3 網路連接"
|
||||||
#: MonitorStage/plugin.json
|
#: MonitorStage/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Provides a monitor stage in Cura."
|
msgid "Provides a monitor stage in Cura."
|
||||||
msgstr ""
|
msgstr "在 cura 提供一個監控介面。"
|
||||||
|
|
||||||
#: MonitorStage/plugin.json
|
#: MonitorStage/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Monitor Stage"
|
msgid "Monitor Stage"
|
||||||
msgstr ""
|
msgstr "監控介面"
|
||||||
|
|
||||||
#: FirmwareUpdateChecker/plugin.json
|
#: FirmwareUpdateChecker/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4524,7 +4547,7 @@ msgstr "韌體更新檢查"
|
||||||
#: CuraSolidWorksPlugin/plugin.json
|
#: CuraSolidWorksPlugin/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
|
msgid "Gives you the possibility to open certain files using SolidWorks itself. Conversion is done by this plugin and additional optimizations."
|
||||||
msgstr ""
|
msgstr "讓你可以使用 SolidWorks 開啟某些檔案。此外掛會完成轉換和最佳化的工作。"
|
||||||
|
|
||||||
#: CuraSolidWorksPlugin/plugin.json
|
#: CuraSolidWorksPlugin/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
|
@ -4594,12 +4617,12 @@ msgstr "舊版 Cura 列印參數讀取器"
|
||||||
#: CuraBlenderPlugin/plugin.json
|
#: CuraBlenderPlugin/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Helps to open Blender files directly in Cura."
|
msgid "Helps to open Blender files directly in Cura."
|
||||||
msgstr ""
|
msgstr "協助你直接在 Cura 中打開 Blender 檔案。"
|
||||||
|
|
||||||
#: CuraBlenderPlugin/plugin.json
|
#: CuraBlenderPlugin/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Blender Integration (experimental)"
|
msgid "Blender Integration (experimental)"
|
||||||
msgstr ""
|
msgstr "Blender 整合(實驗功能)"
|
||||||
|
|
||||||
#: GCodeProfileReader/plugin.json
|
#: GCodeProfileReader/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
@ -4764,12 +4787,12 @@ msgstr "Cura 列印參數寫入器"
|
||||||
#: CuraPrintProfileCreator/plugin.json
|
#: CuraPrintProfileCreator/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
|
msgid "Allows material manufacturers to create new material and quality profiles using a drop-in UI."
|
||||||
msgstr ""
|
msgstr "允許耗材製造商使用下拉式 UI 建立新的耗材和品質設定參數。"
|
||||||
|
|
||||||
#: CuraPrintProfileCreator/plugin.json
|
#: CuraPrintProfileCreator/plugin.json
|
||||||
msgctxt "name"
|
msgctxt "name"
|
||||||
msgid "Print Profile Assistant"
|
msgid "Print Profile Assistant"
|
||||||
msgstr ""
|
msgstr "列印參數設定助手"
|
||||||
|
|
||||||
#: 3MFWriter/plugin.json
|
#: 3MFWriter/plugin.json
|
||||||
msgctxt "description"
|
msgctxt "description"
|
||||||
|
|
|
@ -1,21 +1,21 @@
|
||||||
# Cura JSON setting files
|
# Cura JSON setting files
|
||||||
# Copyright (C) 2017 Ultimaker
|
# Copyright (C) 2018 Ultimaker
|
||||||
# This file is distributed under the same license as the Cura package.
|
# This file is distributed under the same license as the Cura package.
|
||||||
# Ruben Dulek <r.dulek@ultimaker.com>, 2017.
|
# Ruben Dulek <r.dulek@ultimaker.com>, 2018.
|
||||||
#
|
#
|
||||||
msgid ""
|
msgid ""
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Project-Id-Version: Cura 3.1\n"
|
"Project-Id-Version: Cura 3.2\n"
|
||||||
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
"Report-Msgid-Bugs-To: r.dulek@ultimaker.com\n"
|
||||||
"POT-Creation-Date: 2017-08-02 16:53+0000\n"
|
"POT-Creation-Date: 2018-01-29 16:53+0000\n"
|
||||||
"PO-Revision-Date: 2017-11-25 00:31+0800\n"
|
"PO-Revision-Date: 2018-02-01 20:58+0800\n"
|
||||||
"Last-Translator: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
"Last-Translator: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
||||||
"Language-Team: TEAM\n"
|
"Language-Team: Zhang Heh Ji <dinowchang@gmail.com>\n"
|
||||||
"Language: zh_TW\n"
|
"Language: zh_TW\n"
|
||||||
"MIME-Version: 1.0\n"
|
"MIME-Version: 1.0\n"
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
"X-Generator: Poedit 2.0.4\n"
|
"X-Generator: Poedit 2.0.6\n"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_settings label"
|
msgctxt "machine_settings label"
|
||||||
|
@ -353,12 +353,12 @@ msgstr "Repetier"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_firmware_retract label"
|
msgctxt "machine_firmware_retract label"
|
||||||
msgid "Firmware Retraction"
|
msgid "Firmware Retraction"
|
||||||
msgstr ""
|
msgstr "韌體回抽"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_firmware_retract description"
|
msgctxt "machine_firmware_retract description"
|
||||||
msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
|
msgid "Whether to use firmware retract commands (G10/G11) instead of using the E property in G1 commands to retract the material."
|
||||||
msgstr ""
|
msgstr "是否使用韌體回抽命令(G10/G11)取代 G1 命令的 E 參數來回抽線材。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "machine_disallowed_areas label"
|
msgctxt "machine_disallowed_areas label"
|
||||||
|
@ -1053,12 +1053,12 @@ msgstr "全部填充"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "filter_out_tiny_gaps label"
|
msgctxt "filter_out_tiny_gaps label"
|
||||||
msgid "Filter Out Tiny Gaps"
|
msgid "Filter Out Tiny Gaps"
|
||||||
msgstr ""
|
msgstr "濾除微小間隙"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "filter_out_tiny_gaps description"
|
msgctxt "filter_out_tiny_gaps description"
|
||||||
msgid "Filter out tiny gaps to reduce blobs on outside of model."
|
msgid "Filter out tiny gaps to reduce blobs on outside of model."
|
||||||
msgstr ""
|
msgstr "濾除微小間隙以減少模型外側的斑點 。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "fill_outline_gaps label"
|
msgctxt "fill_outline_gaps label"
|
||||||
|
@ -1443,7 +1443,7 @@ msgstr "填充 X 軸偏移"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_offset_x description"
|
msgctxt "infill_offset_x description"
|
||||||
msgid "The infill pattern is moved this distance along the X axis."
|
msgid "The infill pattern is moved this distance along the X axis."
|
||||||
msgstr ""
|
msgstr "填充樣式在 X 軸方向平移此距離。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_offset_y label"
|
msgctxt "infill_offset_y label"
|
||||||
|
@ -1453,7 +1453,7 @@ msgstr "填充 Y 軸偏移"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_offset_y description"
|
msgctxt "infill_offset_y description"
|
||||||
msgid "The infill pattern is moved this distance along the Y axis."
|
msgid "The infill pattern is moved this distance along the Y axis."
|
||||||
msgstr ""
|
msgstr "填充樣式在 Y 軸方向平移此距離。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "sub_div_rad_add label"
|
msgctxt "sub_div_rad_add label"
|
||||||
|
@ -1473,7 +1473,7 @@ msgstr "填充重疊百分比"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_overlap description"
|
msgctxt "infill_overlap description"
|
||||||
msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
|
msgid "The amount of overlap between the infill and the walls as a percentage of the infill line width. A slight overlap allows the walls to connect firmly to the infill."
|
||||||
msgstr ""
|
msgstr "填充與牆壁的重疊量佔填充線寬的百分比。輕微的重疊能讓填充與牆壁牢固地連接。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_overlap_mm label"
|
msgctxt "infill_overlap_mm label"
|
||||||
|
@ -1493,7 +1493,7 @@ msgstr "表層重疊百分比"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skin_overlap description"
|
msgctxt "skin_overlap description"
|
||||||
msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
|
msgid "The amount of overlap between the skin and the walls as a percentage of the skin line width. A slight overlap allows the walls to connect firmly to the skin. This is a percentage of the average line widths of the skin lines and the innermost wall."
|
||||||
msgstr ""
|
msgstr "表層與牆壁的重疊量佔表層線寬的百分比。輕微的重疊能讓填充與表層牢固地連接。這是表層線寬和最內層牆壁線寬平均的百分比。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "skin_overlap_mm label"
|
msgctxt "skin_overlap_mm label"
|
||||||
|
@ -1723,7 +1723,7 @@ msgstr "列印平台溫度"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_bed_temperature description"
|
msgctxt "material_bed_temperature description"
|
||||||
msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
|
msgid "The temperature used for the heated build plate. If this is 0, the bed temperature will not be adjusted."
|
||||||
msgstr ""
|
msgstr "設定列印平台的溫度。如果設定為 0,就不會加熱列印平台。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_bed_temperature_layer_0 label"
|
msgctxt "material_bed_temperature_layer_0 label"
|
||||||
|
@ -4267,82 +4267,82 @@ msgstr "實驗性!"
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_enable label"
|
msgctxt "support_tree_enable label"
|
||||||
msgid "Tree Support"
|
msgid "Tree Support"
|
||||||
msgstr ""
|
msgstr "樹狀支撐"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_enable description"
|
msgctxt "support_tree_enable description"
|
||||||
msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
|
msgid "Generate a tree-like support with branches that support your print. This may reduce material usage and print time, but greatly increases slicing time."
|
||||||
msgstr ""
|
msgstr "產生帶有樹枝樹狀支撐結構支撐你的模型。這可能可以減少耗材的使用和列印的時間,但會大大的增加切片時間。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_angle label"
|
msgctxt "support_tree_angle label"
|
||||||
msgid "Tree Support Branch Angle"
|
msgid "Tree Support Branch Angle"
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝角度"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_angle description"
|
msgctxt "support_tree_angle description"
|
||||||
msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
|
msgid "The angle of the branches. Use a lower angle to make them more vertical and more stable. Use a higher angle to be able to have more reach."
|
||||||
msgstr ""
|
msgstr "樹枝的角度。使用較小的角度讓樹枝較垂直且較平穩。使用較大的角度則可以支撐較大的範圍。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_distance label"
|
msgctxt "support_tree_branch_distance label"
|
||||||
msgid "Tree Support Branch Distance"
|
msgid "Tree Support Branch Distance"
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝距離"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_distance description"
|
msgctxt "support_tree_branch_distance description"
|
||||||
msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
|
msgid "How far apart the branches need to be when they touch the model. Making this distance small will cause the tree support to touch the model at more points, causing better overhang but making support harder to remove."
|
||||||
msgstr ""
|
msgstr "樹支與模型接觸的點與點之間的間隔距離。較小的距離會讓支撐和模型有較多的接觸點,會有較佳的懸垂但支撐也較難移除。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_diameter label"
|
msgctxt "support_tree_branch_diameter label"
|
||||||
msgid "Tree Support Branch Diameter"
|
msgid "Tree Support Branch Diameter"
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝直徑"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_diameter description"
|
msgctxt "support_tree_branch_diameter description"
|
||||||
msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
|
msgid "The diameter of the thinnest branches of tree support. Thicker branches are more sturdy. Branches towards the base will be thicker than this."
|
||||||
msgstr ""
|
msgstr "樹狀支撐中最細樹枝的直徑。越粗的樹枝越堅固。底部的樹枝會比這更粗。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_diameter_angle label"
|
msgctxt "support_tree_branch_diameter_angle label"
|
||||||
msgid "Tree Support Branch Diameter Angle"
|
msgid "Tree Support Branch Diameter Angle"
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝外徑角度"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_branch_diameter_angle description"
|
msgctxt "support_tree_branch_diameter_angle description"
|
||||||
msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
|
msgid "The angle of the branches' diameter as they gradually become thicker towards the bottom. An angle of 0 will cause the branches to have uniform thickness over their length. A bit of an angle can increase stability of the tree support."
|
||||||
msgstr ""
|
msgstr "樹枝向底部逐漸變粗時,外徑變化的角度。設為 0 可讓整條樹枝的粗細一致, 而有點角度可增加樹狀支撐的穩定性。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_collision_resolution label"
|
msgctxt "support_tree_collision_resolution label"
|
||||||
msgid "Tree Support Collision Resolution"
|
msgid "Tree Support Collision Resolution"
|
||||||
msgstr ""
|
msgstr "樹狀支撐碰撞計算精度"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_collision_resolution description"
|
msgctxt "support_tree_collision_resolution description"
|
||||||
msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
|
msgid "Resolution to compute collisions with to avoid hitting the model. Setting this lower will produce more accurate trees that fail less often, but increases slicing time dramatically."
|
||||||
msgstr ""
|
msgstr "計算避免碰撞模型的計算精度。設定較低的值可產生較精確的樹狀支撐,這樣的支撐問題較少但會嚴重的增加切片所需的時間。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_wall_thickness label"
|
msgctxt "support_tree_wall_thickness label"
|
||||||
msgid "Tree Support Wall Thickness"
|
msgid "Tree Support Wall Thickness"
|
||||||
msgstr ""
|
msgstr "樹狀支撐牆厚"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_wall_thickness description"
|
msgctxt "support_tree_wall_thickness description"
|
||||||
msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
|
msgid "The thickness of the walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝的牆壁厚度。較厚的牆壁需要花較長的時間列印,但較不易倒下。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_wall_count label"
|
msgctxt "support_tree_wall_count label"
|
||||||
msgid "Tree Support Wall Line Count"
|
msgid "Tree Support Wall Line Count"
|
||||||
msgstr ""
|
msgstr "樹狀支撐牆壁線條數量"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "support_tree_wall_count description"
|
msgctxt "support_tree_wall_count description"
|
||||||
msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
|
msgid "The number of walls of the branches of tree support. Thicker walls take longer to print but don't fall over as easily."
|
||||||
msgstr ""
|
msgstr "樹狀支撐樹枝牆壁的圈數。較厚的牆壁需要花較長的時間列印,但較不易倒下。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "slicing_tolerance label"
|
msgctxt "slicing_tolerance label"
|
||||||
|
@ -4417,12 +4417,12 @@ msgstr "當頂部表層採用線條或鋸齒狀的列印樣式時使用的整數
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_enable_travel_optimization label"
|
msgctxt "infill_enable_travel_optimization label"
|
||||||
msgid "Infill Travel Optimization"
|
msgid "Infill Travel Optimization"
|
||||||
msgstr ""
|
msgstr "填充空跑最佳化"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "infill_enable_travel_optimization description"
|
msgctxt "infill_enable_travel_optimization description"
|
||||||
msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
|
msgid "When enabled, the order in which the infill lines are printed is optimized to reduce the distance travelled. The reduction in travel time achieved very much depends on the model being sliced, infill pattern, density, etc. Note that, for some models that have many small areas of infill, the time to slice the model may be greatly increased."
|
||||||
msgstr ""
|
msgstr "當功能啟用時,填充線條的列印順序會對降低空跑距離做最佳化。所能減少的空跑時間取決於模型、填充樣式、填充密度等。請注意,對於有很多小型填充區域的模型,切片時間可能會大量增加。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "material_flow_dependent_temperature label"
|
msgctxt "material_flow_dependent_temperature label"
|
||||||
|
@ -5056,42 +5056,42 @@ msgstr "噴頭和水平下行線之間的距離。較大的間隙會讓斜下行
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_enabled label"
|
msgctxt "adaptive_layer_height_enabled label"
|
||||||
msgid "Use adaptive layers"
|
msgid "Use adaptive layers"
|
||||||
msgstr ""
|
msgstr "使用適應層高"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_enabled description"
|
msgctxt "adaptive_layer_height_enabled description"
|
||||||
msgid "Adaptive layers computes the layer heights depending on the shape of the model."
|
msgid "Adaptive layers computes the layer heights depending on the shape of the model."
|
||||||
msgstr ""
|
msgstr "適應層高會依據模型的形狀計算列印的層高。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation label"
|
msgctxt "adaptive_layer_height_variation label"
|
||||||
msgid "Adaptive layers maximum variation"
|
msgid "Adaptive layers maximum variation"
|
||||||
msgstr ""
|
msgstr "適應層高最大變化量"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation description"
|
msgctxt "adaptive_layer_height_variation description"
|
||||||
msgid "The maximum allowed height different from the base layer height in mm."
|
msgid "The maximum allowed height different from the base layer height in mm."
|
||||||
msgstr ""
|
msgstr "允許與層高基礎值相差的最大值(以毫米為單位)。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation_step label"
|
msgctxt "adaptive_layer_height_variation_step label"
|
||||||
msgid "Adaptive layers variation step size"
|
msgid "Adaptive layers variation step size"
|
||||||
msgstr ""
|
msgstr "適應層高變化幅度"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_variation_step description"
|
msgctxt "adaptive_layer_height_variation_step description"
|
||||||
msgid "The difference in height of the next layer height compared to the previous one."
|
msgid "The difference in height of the next layer height compared to the previous one."
|
||||||
msgstr ""
|
msgstr "下一列印層與前一列印層的層高差。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_threshold label"
|
msgctxt "adaptive_layer_height_threshold label"
|
||||||
msgid "Adaptive layers threshold"
|
msgid "Adaptive layers threshold"
|
||||||
msgstr ""
|
msgstr "適應層高門檻值"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "adaptive_layer_height_threshold description"
|
msgctxt "adaptive_layer_height_threshold description"
|
||||||
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
|
msgid "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer."
|
||||||
msgstr ""
|
msgstr "決定是否使用較小層高的門檻值。此值會與一層中最陡坡度的 tan 值做比較。"
|
||||||
|
|
||||||
#: fdmprinter.def.json
|
#: fdmprinter.def.json
|
||||||
msgctxt "command_line_settings label"
|
msgctxt "command_line_settings label"
|
||||||
|
|
|
@ -8,120 +8,71 @@ weight = 2
|
||||||
layer_height
|
layer_height
|
||||||
layer_height_0
|
layer_height_0
|
||||||
line_width
|
line_width
|
||||||
|
wall_line_width
|
||||||
|
wall_line_width_0
|
||||||
|
wall_line_width_x
|
||||||
|
skin_line_width
|
||||||
|
infill_line_width
|
||||||
|
initial_layer_line_width_factor
|
||||||
|
|
||||||
[shell]
|
[shell]
|
||||||
wall_extruder_nr
|
wall_extruder_nr
|
||||||
|
wall_0_extruder_nr
|
||||||
|
wall_x_extruder_nr
|
||||||
wall_thickness
|
wall_thickness
|
||||||
wall_0_wipe_dist
|
wall_line_count
|
||||||
roofing_extruder_nr
|
|
||||||
roofing_layer_count
|
|
||||||
top_bottom_extruder_nr
|
top_bottom_extruder_nr
|
||||||
top_bottom_thickness
|
top_bottom_thickness
|
||||||
top_bottom_pattern
|
top_thickness
|
||||||
top_bottom_pattern_0
|
top_layers
|
||||||
skin_angles
|
bottom_thickness
|
||||||
wall_0_inset
|
bottom_layers
|
||||||
optimize_wall_printing_order
|
optimize_wall_printing_order
|
||||||
outer_inset_first
|
|
||||||
alternate_extra_perimeter
|
|
||||||
travel_compensate_overlapping_walls_enabled
|
|
||||||
fill_perimeter_gaps
|
fill_perimeter_gaps
|
||||||
filter_out_tiny_gaps
|
|
||||||
fill_outline_gaps
|
|
||||||
xy_offset
|
xy_offset
|
||||||
xy_offset_layer_0
|
|
||||||
z_seam_type
|
|
||||||
z_seam_x
|
|
||||||
z_seam_y
|
|
||||||
z_seam_corner
|
|
||||||
z_seam_relative
|
|
||||||
skin_no_small_gaps_heuristic
|
|
||||||
skin_outline_count
|
|
||||||
ironing_enabled
|
ironing_enabled
|
||||||
ironing_only_highest_layer
|
|
||||||
ironing_pattern
|
|
||||||
ironing_line_spacing
|
|
||||||
ironing_flow
|
|
||||||
ironing_inset
|
|
||||||
speed_ironing
|
|
||||||
acceleration_ironing
|
|
||||||
jerk_ironing
|
|
||||||
|
|
||||||
[infill]
|
[infill]
|
||||||
infill_extruder_nr
|
infill_extruder_nr
|
||||||
infill_sparse_density
|
infill_sparse_density
|
||||||
|
infill_line_distance
|
||||||
infill_pattern
|
infill_pattern
|
||||||
zig_zaggify_infill
|
|
||||||
infill_angles
|
|
||||||
infill_offset_x
|
|
||||||
infill_offset_y
|
|
||||||
sub_div_rad_add
|
|
||||||
infill_overlap
|
infill_overlap
|
||||||
skin_overlap
|
|
||||||
infill_wipe_dist
|
|
||||||
infill_sparse_thickness
|
infill_sparse_thickness
|
||||||
gradual_infill_steps
|
gradual_infill_steps
|
||||||
gradual_infill_step_height
|
|
||||||
infill_before_walls
|
|
||||||
min_infill_area
|
|
||||||
skin_preshrink
|
|
||||||
expand_skins_expand_distance
|
|
||||||
max_skin_angle_for_expansion
|
|
||||||
|
|
||||||
[material]
|
[material]
|
||||||
default_material_print_temperature
|
|
||||||
material_print_temperature
|
material_print_temperature
|
||||||
material_print_temperature_layer_0
|
material_print_temperature_layer_0
|
||||||
material_initial_print_temperature
|
material_initial_print_temperature
|
||||||
material_final_print_temperature
|
material_final_print_temperature
|
||||||
material_extrusion_cool_down_speed
|
|
||||||
default_material_bed_temperature
|
|
||||||
material_bed_temperature
|
material_bed_temperature
|
||||||
material_bed_temperature_layer_0
|
material_bed_temperature_layer_0
|
||||||
material_diameter
|
|
||||||
material_adhesion_tendency
|
|
||||||
material_surface_energy
|
|
||||||
material_flow
|
|
||||||
retraction_enable
|
retraction_enable
|
||||||
retract_at_layer_change
|
retract_at_layer_change
|
||||||
retraction_amount
|
retraction_amount
|
||||||
retraction_speed
|
retraction_speed
|
||||||
retraction_extra_prime_amount
|
|
||||||
retraction_min_travel
|
|
||||||
retraction_count_max
|
|
||||||
retraction_extrusion_window
|
|
||||||
material_standby_temperature
|
material_standby_temperature
|
||||||
switch_extruder_retraction_amount
|
|
||||||
switch_extruder_retraction_speeds
|
|
||||||
|
|
||||||
[speed]
|
[speed]
|
||||||
speed_print
|
speed_print
|
||||||
|
speed_infill
|
||||||
|
speed_wall
|
||||||
|
speed_wall_0
|
||||||
|
speed_wall_x
|
||||||
|
speed_topbottom
|
||||||
|
speed_support
|
||||||
|
speed_prime_tower
|
||||||
speed_travel
|
speed_travel
|
||||||
speed_layer_0
|
speed_layer_0
|
||||||
skirt_brim_speed
|
skirt_brim_speed
|
||||||
max_feedrate_z_override
|
|
||||||
speed_slowdown_layers
|
|
||||||
speed_equalize_flow_enabled
|
|
||||||
speed_equalize_flow_max
|
|
||||||
acceleration_enabled
|
acceleration_enabled
|
||||||
acceleration_print
|
|
||||||
acceleration_travel
|
|
||||||
acceleration_layer_0
|
|
||||||
acceleration_skirt_brim
|
|
||||||
jerk_enabled
|
jerk_enabled
|
||||||
jerk_print
|
|
||||||
jerk_travel
|
|
||||||
jerk_layer_0
|
|
||||||
jerk_skirt_brim
|
|
||||||
|
|
||||||
[travel]
|
[travel]
|
||||||
retraction_combing
|
retraction_combing
|
||||||
travel_retract_before_outer_wall
|
|
||||||
travel_avoid_other_parts
|
travel_avoid_other_parts
|
||||||
travel_avoid_distance
|
travel_avoid_distance
|
||||||
start_layers_at_same_position
|
|
||||||
layer_start_x
|
|
||||||
layer_start_y
|
|
||||||
retraction_hop_enabled
|
retraction_hop_enabled
|
||||||
retraction_hop_only_when_collides
|
retraction_hop_only_when_collides
|
||||||
retraction_hop
|
retraction_hop
|
||||||
|
@ -130,9 +81,12 @@ retraction_hop_after_extruder_switch
|
||||||
[cooling]
|
[cooling]
|
||||||
cool_fan_enabled
|
cool_fan_enabled
|
||||||
cool_fan_speed
|
cool_fan_speed
|
||||||
|
cool_fan_speed_min
|
||||||
|
cool_fan_speed_max
|
||||||
cool_min_layer_time_fan_speed_max
|
cool_min_layer_time_fan_speed_max
|
||||||
cool_fan_speed_0
|
cool_fan_speed_0
|
||||||
cool_fan_full_at_height
|
cool_fan_full_at_height
|
||||||
|
cool_fan_full_layer
|
||||||
cool_min_layer_time
|
cool_min_layer_time
|
||||||
cool_min_speed
|
cool_min_speed
|
||||||
cool_lift_head
|
cool_lift_head
|
||||||
|
@ -140,115 +94,45 @@ cool_lift_head
|
||||||
[support]
|
[support]
|
||||||
support_enable
|
support_enable
|
||||||
support_extruder_nr
|
support_extruder_nr
|
||||||
|
support_infill_extruder_nr
|
||||||
|
support_extruder_nr_layer_0
|
||||||
|
support_interface_extruder_nr
|
||||||
support_type
|
support_type
|
||||||
support_angle
|
support_angle
|
||||||
support_pattern
|
support_pattern
|
||||||
support_connect_zigzags
|
|
||||||
support_infill_rate
|
support_infill_rate
|
||||||
support_z_distance
|
|
||||||
support_xy_distance
|
|
||||||
support_xy_overrides_z
|
|
||||||
support_xy_distance_overhang
|
|
||||||
support_bottom_stair_step_height
|
|
||||||
support_bottom_stair_step_width
|
|
||||||
support_join_distance
|
|
||||||
support_offset
|
support_offset
|
||||||
support_infill_sparse_thickness
|
support_infill_sparse_thickness
|
||||||
gradual_support_infill_steps
|
gradual_support_infill_steps
|
||||||
gradual_support_infill_step_height
|
gradual_support_infill_step_height
|
||||||
support_interface_enable
|
support_interface_enable
|
||||||
support_interface_height
|
support_roof_enable
|
||||||
support_interface_skip_height
|
support_bottom_enable
|
||||||
support_interface_density
|
|
||||||
support_interface_pattern
|
|
||||||
support_use_towers
|
|
||||||
support_tower_diameter
|
|
||||||
support_minimal_diameter
|
|
||||||
support_tower_roof_angle
|
|
||||||
support_mesh_drop_down
|
|
||||||
|
|
||||||
[platform_adhesion]
|
[platform_adhesion]
|
||||||
prime_blob_enable
|
prime_blob_enable
|
||||||
extruder_prime_pos_x
|
|
||||||
extruder_prime_pos_y
|
|
||||||
adhesion_type
|
adhesion_type
|
||||||
adhesion_extruder_nr
|
adhesion_extruder_nr
|
||||||
skirt_line_count
|
skirt_line_count
|
||||||
skirt_gap
|
|
||||||
skirt_brim_minimal_length
|
|
||||||
brim_width
|
brim_width
|
||||||
|
brim_line_count
|
||||||
brim_outside_only
|
brim_outside_only
|
||||||
raft_margin
|
|
||||||
raft_smoothing
|
|
||||||
raft_airgap
|
|
||||||
layer_0_z_overlap
|
|
||||||
raft_surface_layers
|
|
||||||
raft_surface_thickness
|
|
||||||
raft_surface_line_width
|
|
||||||
raft_surface_line_spacing
|
|
||||||
raft_interface_thickness
|
|
||||||
raft_interface_line_width
|
|
||||||
raft_interface_line_spacing
|
|
||||||
raft_base_thickness
|
|
||||||
raft_base_line_width
|
|
||||||
raft_base_line_spacing
|
|
||||||
raft_speed
|
|
||||||
raft_acceleration
|
|
||||||
raft_jerk
|
|
||||||
raft_fan_speed
|
|
||||||
|
|
||||||
[dual]
|
[dual]
|
||||||
prime_tower_enable
|
prime_tower_enable
|
||||||
prime_tower_size
|
|
||||||
prime_tower_min_volume
|
|
||||||
prime_tower_position_x
|
prime_tower_position_x
|
||||||
prime_tower_position_y
|
prime_tower_position_y
|
||||||
prime_tower_flow
|
|
||||||
prime_tower_wipe_enabled
|
|
||||||
dual_pre_wipe
|
|
||||||
prime_tower_purge_volume
|
prime_tower_purge_volume
|
||||||
ooze_shield_enabled
|
|
||||||
ooze_shield_angle
|
|
||||||
ooze_shield_dist
|
|
||||||
|
|
||||||
[meshfix]
|
[meshfix]
|
||||||
meshfix_union_all
|
|
||||||
meshfix_union_all_remove_holes
|
|
||||||
meshfix_extensive_stitching
|
|
||||||
meshfix_keep_open_polygons
|
|
||||||
multiple_mesh_overlap
|
|
||||||
carve_multiple_volumes
|
|
||||||
alternate_carve_order
|
|
||||||
remove_empty_first_layers
|
|
||||||
|
|
||||||
[blackmagic]
|
[blackmagic]
|
||||||
print_sequence
|
print_sequence
|
||||||
infill_mesh
|
|
||||||
infill_mesh_order
|
|
||||||
cutting_mesh
|
|
||||||
mold_enabled
|
|
||||||
mold_width
|
|
||||||
mold_roof_height
|
|
||||||
mold_angle
|
|
||||||
support_mesh
|
|
||||||
anti_overhang_mesh
|
|
||||||
magic_mesh_surface_mode
|
magic_mesh_surface_mode
|
||||||
magic_spiralize
|
magic_spiralize
|
||||||
smooth_spiralized_contours
|
smooth_spiralized_contours
|
||||||
relative_extrusion
|
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
infill_enable_travel_optimization
|
conical_overhang_enabled
|
||||||
material_flow_dependent_temperature
|
support_conical_enabled
|
||||||
material_flow_temp_graph
|
adaptive_layer_height_enabled
|
||||||
meshfix_maximum_resolution
|
|
||||||
roofing_angles
|
|
||||||
roofing_pattern
|
|
||||||
slicing_tolerance
|
|
||||||
support_tree_angle
|
|
||||||
support_tree_branch_diameter
|
|
||||||
support_tree_branch_diameter_angle
|
|
||||||
support_tree_branch_distance
|
|
||||||
support_tree_collision_resolution
|
|
||||||
support_tree_enable
|
|
||||||
support_tree_wall_thickness
|
|
||||||
|
|
|
@ -9,30 +9,28 @@ layer_height
|
||||||
|
|
||||||
[shell]
|
[shell]
|
||||||
wall_thickness
|
wall_thickness
|
||||||
|
wall_line_count
|
||||||
top_bottom_thickness
|
top_bottom_thickness
|
||||||
z_seam_x
|
top_thickness
|
||||||
z_seam_y
|
top_layers
|
||||||
|
bottom_thickness
|
||||||
|
bottom_layers
|
||||||
|
xy_offset
|
||||||
|
|
||||||
[infill]
|
[infill]
|
||||||
infill_sparse_density
|
infill_sparse_density
|
||||||
gradual_infill_steps
|
infill_pattern
|
||||||
|
|
||||||
[material]
|
[material]
|
||||||
material_print_temperature
|
material_print_temperature
|
||||||
material_bed_temperature
|
material_bed_temperature
|
||||||
material_diameter
|
|
||||||
material_flow
|
|
||||||
retraction_enable
|
retraction_enable
|
||||||
|
|
||||||
[speed]
|
[speed]
|
||||||
speed_print
|
speed_print
|
||||||
speed_travel
|
|
||||||
acceleration_print
|
|
||||||
acceleration_travel
|
|
||||||
jerk_print
|
|
||||||
jerk_travel
|
|
||||||
|
|
||||||
[travel]
|
[travel]
|
||||||
|
retraction_hop_enabled
|
||||||
|
|
||||||
[cooling]
|
[cooling]
|
||||||
cool_fan_enabled
|
cool_fan_enabled
|
||||||
|
@ -41,26 +39,20 @@ cool_fan_enabled
|
||||||
support_enable
|
support_enable
|
||||||
support_extruder_nr
|
support_extruder_nr
|
||||||
support_type
|
support_type
|
||||||
|
support_angle
|
||||||
|
|
||||||
[platform_adhesion]
|
[platform_adhesion]
|
||||||
|
prime_blob_enable
|
||||||
adhesion_type
|
adhesion_type
|
||||||
adhesion_extruder_nr
|
adhesion_extruder_nr
|
||||||
brim_width
|
|
||||||
raft_airgap
|
|
||||||
layer_0_z_overlap
|
|
||||||
raft_surface_layers
|
|
||||||
|
|
||||||
[dual]
|
[dual]
|
||||||
prime_tower_enable
|
prime_tower_enable
|
||||||
prime_tower_size
|
|
||||||
prime_tower_position_x
|
prime_tower_position_x
|
||||||
prime_tower_position_y
|
prime_tower_position_y
|
||||||
|
|
||||||
[meshfix]
|
[meshfix]
|
||||||
|
|
||||||
[blackmagic]
|
[blackmagic]
|
||||||
print_sequence
|
|
||||||
infill_mesh
|
|
||||||
cutting_mesh
|
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
|
|
|
@ -8,15 +8,33 @@ weight = 3
|
||||||
layer_height
|
layer_height
|
||||||
layer_height_0
|
layer_height_0
|
||||||
line_width
|
line_width
|
||||||
|
wall_line_width
|
||||||
|
wall_line_width_0
|
||||||
|
wall_line_width_x
|
||||||
|
skin_line_width
|
||||||
|
infill_line_width
|
||||||
|
skirt_brim_line_width
|
||||||
|
support_line_width
|
||||||
|
support_interface_line_width
|
||||||
|
support_roof_line_width
|
||||||
|
support_bottom_line_width
|
||||||
|
prime_tower_line_width
|
||||||
|
initial_layer_line_width_factor
|
||||||
|
|
||||||
[shell]
|
[shell]
|
||||||
wall_extruder_nr
|
wall_extruder_nr
|
||||||
|
wall_0_extruder_nr
|
||||||
|
wall_x_extruder_nr
|
||||||
wall_thickness
|
wall_thickness
|
||||||
|
wall_line_count
|
||||||
wall_0_wipe_dist
|
wall_0_wipe_dist
|
||||||
roofing_extruder_nr
|
|
||||||
roofing_layer_count
|
roofing_layer_count
|
||||||
top_bottom_extruder_nr
|
top_bottom_extruder_nr
|
||||||
top_bottom_thickness
|
top_bottom_thickness
|
||||||
|
top_thickness
|
||||||
|
top_layers
|
||||||
|
bottom_thickness
|
||||||
|
bottom_layers
|
||||||
top_bottom_pattern
|
top_bottom_pattern
|
||||||
top_bottom_pattern_0
|
top_bottom_pattern_0
|
||||||
skin_angles
|
skin_angles
|
||||||
|
@ -25,6 +43,8 @@ optimize_wall_printing_order
|
||||||
outer_inset_first
|
outer_inset_first
|
||||||
alternate_extra_perimeter
|
alternate_extra_perimeter
|
||||||
travel_compensate_overlapping_walls_enabled
|
travel_compensate_overlapping_walls_enabled
|
||||||
|
travel_compensate_overlapping_walls_0_enabled
|
||||||
|
travel_compensate_overlapping_walls_x_enabled
|
||||||
fill_perimeter_gaps
|
fill_perimeter_gaps
|
||||||
filter_out_tiny_gaps
|
filter_out_tiny_gaps
|
||||||
fill_outline_gaps
|
fill_outline_gaps
|
||||||
|
@ -50,6 +70,7 @@ jerk_ironing
|
||||||
[infill]
|
[infill]
|
||||||
infill_extruder_nr
|
infill_extruder_nr
|
||||||
infill_sparse_density
|
infill_sparse_density
|
||||||
|
infill_line_distance
|
||||||
infill_pattern
|
infill_pattern
|
||||||
zig_zaggify_infill
|
zig_zaggify_infill
|
||||||
infill_angles
|
infill_angles
|
||||||
|
@ -57,7 +78,9 @@ infill_offset_x
|
||||||
infill_offset_y
|
infill_offset_y
|
||||||
sub_div_rad_add
|
sub_div_rad_add
|
||||||
infill_overlap
|
infill_overlap
|
||||||
|
infill_overlap_mm
|
||||||
skin_overlap
|
skin_overlap
|
||||||
|
skin_overlap_mm
|
||||||
infill_wipe_dist
|
infill_wipe_dist
|
||||||
infill_sparse_thickness
|
infill_sparse_thickness
|
||||||
gradual_infill_steps
|
gradual_infill_steps
|
||||||
|
@ -65,8 +88,13 @@ gradual_infill_step_height
|
||||||
infill_before_walls
|
infill_before_walls
|
||||||
min_infill_area
|
min_infill_area
|
||||||
skin_preshrink
|
skin_preshrink
|
||||||
|
top_skin_preshrink
|
||||||
|
bottom_skin_preshrink
|
||||||
expand_skins_expand_distance
|
expand_skins_expand_distance
|
||||||
|
top_skin_expand_distance
|
||||||
|
bottom_skin_expand_distance
|
||||||
max_skin_angle_for_expansion
|
max_skin_angle_for_expansion
|
||||||
|
min_skin_width_for_expansion
|
||||||
|
|
||||||
[material]
|
[material]
|
||||||
default_material_print_temperature
|
default_material_print_temperature
|
||||||
|
@ -75,17 +103,19 @@ material_print_temperature_layer_0
|
||||||
material_initial_print_temperature
|
material_initial_print_temperature
|
||||||
material_final_print_temperature
|
material_final_print_temperature
|
||||||
material_extrusion_cool_down_speed
|
material_extrusion_cool_down_speed
|
||||||
default_material_bed_temperature
|
|
||||||
material_bed_temperature
|
material_bed_temperature
|
||||||
material_bed_temperature_layer_0
|
material_bed_temperature_layer_0
|
||||||
material_diameter
|
material_diameter
|
||||||
material_adhesion_tendency
|
material_adhesion_tendency
|
||||||
material_surface_energy
|
material_surface_energy
|
||||||
material_flow
|
material_flow
|
||||||
|
material_flow_layer_0
|
||||||
retraction_enable
|
retraction_enable
|
||||||
retract_at_layer_change
|
retract_at_layer_change
|
||||||
retraction_amount
|
retraction_amount
|
||||||
retraction_speed
|
retraction_speed
|
||||||
|
retraction_retract_speed
|
||||||
|
retraction_prime_speed
|
||||||
retraction_extra_prime_amount
|
retraction_extra_prime_amount
|
||||||
retraction_min_travel
|
retraction_min_travel
|
||||||
retraction_count_max
|
retraction_count_max
|
||||||
|
@ -93,11 +123,25 @@ retraction_extrusion_window
|
||||||
material_standby_temperature
|
material_standby_temperature
|
||||||
switch_extruder_retraction_amount
|
switch_extruder_retraction_amount
|
||||||
switch_extruder_retraction_speeds
|
switch_extruder_retraction_speeds
|
||||||
|
switch_extruder_retraction_speed
|
||||||
|
switch_extruder_prime_speed
|
||||||
|
|
||||||
[speed]
|
[speed]
|
||||||
speed_print
|
speed_print
|
||||||
|
speed_infill
|
||||||
|
speed_wall
|
||||||
|
speed_wall_0
|
||||||
|
speed_wall_x
|
||||||
|
speed_roofing
|
||||||
|
speed_topbottom
|
||||||
|
speed_support
|
||||||
|
speed_support_infill
|
||||||
|
speed_support_interface
|
||||||
|
speed_prime_tower
|
||||||
speed_travel
|
speed_travel
|
||||||
speed_layer_0
|
speed_layer_0
|
||||||
|
speed_print_layer_0
|
||||||
|
speed_travel_layer_0
|
||||||
skirt_brim_speed
|
skirt_brim_speed
|
||||||
max_feedrate_z_override
|
max_feedrate_z_override
|
||||||
speed_slowdown_layers
|
speed_slowdown_layers
|
||||||
|
@ -105,13 +149,37 @@ speed_equalize_flow_enabled
|
||||||
speed_equalize_flow_max
|
speed_equalize_flow_max
|
||||||
acceleration_enabled
|
acceleration_enabled
|
||||||
acceleration_print
|
acceleration_print
|
||||||
|
acceleration_infill
|
||||||
|
acceleration_wall
|
||||||
|
acceleration_wall_0
|
||||||
|
acceleration_wall_x
|
||||||
|
acceleration_roofing
|
||||||
|
acceleration_topbottom
|
||||||
|
acceleration_support
|
||||||
|
acceleration_support_infill
|
||||||
|
acceleration_support_interface
|
||||||
|
acceleration_prime_tower
|
||||||
acceleration_travel
|
acceleration_travel
|
||||||
acceleration_layer_0
|
acceleration_layer_0
|
||||||
|
acceleration_print_layer_0
|
||||||
|
acceleration_travel_layer_0
|
||||||
acceleration_skirt_brim
|
acceleration_skirt_brim
|
||||||
jerk_enabled
|
jerk_enabled
|
||||||
jerk_print
|
jerk_print
|
||||||
|
jerk_infill
|
||||||
|
jerk_wall
|
||||||
|
jerk_wall_0
|
||||||
|
jerk_wall_x
|
||||||
|
jerk_roofing
|
||||||
|
jerk_topbottom
|
||||||
|
jerk_support
|
||||||
|
jerk_support_infill
|
||||||
|
jerk_support_interface
|
||||||
|
jerk_prime_tower
|
||||||
jerk_travel
|
jerk_travel
|
||||||
jerk_layer_0
|
jerk_layer_0
|
||||||
|
jerk_print_layer_0
|
||||||
|
jerk_travel_layer_0
|
||||||
jerk_skirt_brim
|
jerk_skirt_brim
|
||||||
|
|
||||||
[travel]
|
[travel]
|
||||||
|
@ -130,9 +198,12 @@ retraction_hop_after_extruder_switch
|
||||||
[cooling]
|
[cooling]
|
||||||
cool_fan_enabled
|
cool_fan_enabled
|
||||||
cool_fan_speed
|
cool_fan_speed
|
||||||
|
cool_fan_speed_min
|
||||||
|
cool_fan_speed_max
|
||||||
cool_min_layer_time_fan_speed_max
|
cool_min_layer_time_fan_speed_max
|
||||||
cool_fan_speed_0
|
cool_fan_speed_0
|
||||||
cool_fan_full_at_height
|
cool_fan_full_at_height
|
||||||
|
cool_fan_full_layer
|
||||||
cool_min_layer_time
|
cool_min_layer_time
|
||||||
cool_min_speed
|
cool_min_speed
|
||||||
cool_lift_head
|
cool_lift_head
|
||||||
|
@ -140,12 +211,21 @@ cool_lift_head
|
||||||
[support]
|
[support]
|
||||||
support_enable
|
support_enable
|
||||||
support_extruder_nr
|
support_extruder_nr
|
||||||
|
support_infill_extruder_nr
|
||||||
|
support_extruder_nr_layer_0
|
||||||
|
support_interface_extruder_nr
|
||||||
|
support_roof_extruder_nr
|
||||||
|
support_bottom_extruder_nr
|
||||||
support_type
|
support_type
|
||||||
support_angle
|
support_angle
|
||||||
support_pattern
|
support_pattern
|
||||||
|
zig_zaggify_support
|
||||||
support_connect_zigzags
|
support_connect_zigzags
|
||||||
support_infill_rate
|
support_infill_rate
|
||||||
|
support_line_distance
|
||||||
support_z_distance
|
support_z_distance
|
||||||
|
support_top_distance
|
||||||
|
support_bottom_distance
|
||||||
support_xy_distance
|
support_xy_distance
|
||||||
support_xy_overrides_z
|
support_xy_overrides_z
|
||||||
support_xy_distance_overhang
|
support_xy_distance_overhang
|
||||||
|
@ -157,9 +237,15 @@ support_infill_sparse_thickness
|
||||||
gradual_support_infill_steps
|
gradual_support_infill_steps
|
||||||
gradual_support_infill_step_height
|
gradual_support_infill_step_height
|
||||||
support_interface_enable
|
support_interface_enable
|
||||||
|
support_roof_enable
|
||||||
|
support_bottom_enable
|
||||||
support_interface_height
|
support_interface_height
|
||||||
|
support_roof_height
|
||||||
|
support_bottom_height
|
||||||
support_interface_skip_height
|
support_interface_skip_height
|
||||||
support_interface_density
|
support_interface_density
|
||||||
|
support_roof_density
|
||||||
|
support_bottom_density
|
||||||
support_interface_pattern
|
support_interface_pattern
|
||||||
support_use_towers
|
support_use_towers
|
||||||
support_tower_diameter
|
support_tower_diameter
|
||||||
|
@ -169,19 +255,17 @@ support_mesh_drop_down
|
||||||
|
|
||||||
[platform_adhesion]
|
[platform_adhesion]
|
||||||
prime_blob_enable
|
prime_blob_enable
|
||||||
extruder_prime_pos_x
|
|
||||||
extruder_prime_pos_y
|
|
||||||
adhesion_type
|
adhesion_type
|
||||||
adhesion_extruder_nr
|
adhesion_extruder_nr
|
||||||
skirt_line_count
|
skirt_line_count
|
||||||
skirt_gap
|
skirt_gap
|
||||||
skirt_brim_minimal_length
|
skirt_brim_minimal_length
|
||||||
brim_width
|
brim_width
|
||||||
|
brim_line_count
|
||||||
brim_outside_only
|
brim_outside_only
|
||||||
raft_margin
|
raft_margin
|
||||||
raft_smoothing
|
raft_smoothing
|
||||||
raft_airgap
|
raft_airgap
|
||||||
layer_0_z_overlap
|
|
||||||
raft_surface_layers
|
raft_surface_layers
|
||||||
raft_surface_thickness
|
raft_surface_thickness
|
||||||
raft_surface_line_width
|
raft_surface_line_width
|
||||||
|
@ -199,8 +283,10 @@ raft_fan_speed
|
||||||
|
|
||||||
[dual]
|
[dual]
|
||||||
prime_tower_enable
|
prime_tower_enable
|
||||||
|
prime_tower_circular
|
||||||
prime_tower_size
|
prime_tower_size
|
||||||
prime_tower_min_volume
|
prime_tower_min_volume
|
||||||
|
prime_tower_wall_thickness
|
||||||
prime_tower_position_x
|
prime_tower_position_x
|
||||||
prime_tower_position_y
|
prime_tower_position_y
|
||||||
prime_tower_flow
|
prime_tower_flow
|
||||||
|
@ -238,8 +324,25 @@ smooth_spiralized_contours
|
||||||
relative_extrusion
|
relative_extrusion
|
||||||
|
|
||||||
[experimental]
|
[experimental]
|
||||||
|
support_tree_enable
|
||||||
|
support_tree_angle
|
||||||
|
support_tree_branch_distance
|
||||||
|
support_tree_branch_diameter
|
||||||
|
support_tree_branch_diameter_angle
|
||||||
|
support_tree_collision_resolution
|
||||||
|
support_tree_wall_thickness
|
||||||
|
support_tree_wall_count
|
||||||
|
slicing_tolerance
|
||||||
|
roofing_line_width
|
||||||
|
roofing_pattern
|
||||||
|
roofing_angles
|
||||||
|
infill_enable_travel_optimization
|
||||||
|
material_flow_dependent_temperature
|
||||||
|
material_flow_temp_graph
|
||||||
|
meshfix_maximum_resolution
|
||||||
support_skip_some_zags
|
support_skip_some_zags
|
||||||
support_skip_zag_per_mm
|
support_skip_zag_per_mm
|
||||||
|
support_zag_skip_count
|
||||||
draft_shield_enabled
|
draft_shield_enabled
|
||||||
draft_shield_dist
|
draft_shield_dist
|
||||||
draft_shield_height_limitation
|
draft_shield_height_limitation
|
||||||
|
@ -267,19 +370,34 @@ infill_hollow
|
||||||
magic_fuzzy_skin_enabled
|
magic_fuzzy_skin_enabled
|
||||||
magic_fuzzy_skin_thickness
|
magic_fuzzy_skin_thickness
|
||||||
magic_fuzzy_skin_point_density
|
magic_fuzzy_skin_point_density
|
||||||
|
magic_fuzzy_skin_point_dist
|
||||||
flow_rate_max_extrusion_offset
|
flow_rate_max_extrusion_offset
|
||||||
flow_rate_extrusion_offset_factor
|
flow_rate_extrusion_offset_factor
|
||||||
infill_enable_travel_optimization
|
wireframe_enabled
|
||||||
material_flow_dependent_temperature
|
wireframe_height
|
||||||
material_flow_temp_graph
|
wireframe_roof_inset
|
||||||
meshfix_maximum_resolution
|
wireframe_printspeed
|
||||||
roofing_angles
|
wireframe_printspeed_bottom
|
||||||
roofing_pattern
|
wireframe_printspeed_up
|
||||||
slicing_tolerance
|
wireframe_printspeed_down
|
||||||
support_tree_angle
|
wireframe_printspeed_flat
|
||||||
support_tree_branch_diameter
|
wireframe_flow
|
||||||
support_tree_branch_diameter_angle
|
wireframe_flow_connection
|
||||||
support_tree_branch_distance
|
wireframe_flow_flat
|
||||||
support_tree_collision_resolution
|
wireframe_top_delay
|
||||||
support_tree_enable
|
wireframe_bottom_delay
|
||||||
support_tree_wall_thickness
|
wireframe_flat_delay
|
||||||
|
wireframe_up_half_speed
|
||||||
|
wireframe_top_jump
|
||||||
|
wireframe_fall_down
|
||||||
|
wireframe_drag_along
|
||||||
|
wireframe_strategy
|
||||||
|
wireframe_straight_before_down
|
||||||
|
wireframe_roof_fall_down
|
||||||
|
wireframe_roof_drag_along
|
||||||
|
wireframe_roof_outer_delay
|
||||||
|
wireframe_nozzle_clearance
|
||||||
|
adaptive_layer_height_enabled
|
||||||
|
adaptive_layer_height_variation
|
||||||
|
adaptive_layer_height_variation_step
|
||||||
|
adaptive_layer_height_threshold
|
||||||
|
|
|
@ -69,7 +69,6 @@ Item
|
||||||
property alias configureSettingVisibility: configureSettingVisibilityAction
|
property alias configureSettingVisibility: configureSettingVisibilityAction
|
||||||
|
|
||||||
property alias browsePlugins: browsePluginsAction
|
property alias browsePlugins: browsePluginsAction
|
||||||
property alias configurePlugins: configurePluginsAction
|
|
||||||
|
|
||||||
UM.I18nCatalog{id: catalog; name:"cura"}
|
UM.I18nCatalog{id: catalog; name:"cura"}
|
||||||
|
|
||||||
|
@ -173,7 +172,7 @@ Item
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: updateProfileAction;
|
id: updateProfileAction;
|
||||||
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && !Cura.MachineManager.isReadOnly(Cura.MachineManager.activeQualityId)
|
enabled: !Cura.MachineManager.stacksHaveErrors && Cura.MachineManager.hasUserSettings && Cura.MachineManager.activeQualityChangesGroup != null
|
||||||
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings/overrides");
|
text: catalog.i18nc("@action:inmenu menubar:profile","&Update profile with current settings/overrides");
|
||||||
onTriggered: Cura.ContainerManager.updateQualityChanges();
|
onTriggered: Cura.ContainerManager.updateQualityChanges();
|
||||||
}
|
}
|
||||||
|
@ -237,6 +236,16 @@ Item
|
||||||
onTriggered: CuraActions.deleteSelection();
|
onTriggered: CuraActions.deleteSelection();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Action //Also add backspace as the same function as delete because on Macintosh keyboards the button called "delete" is actually a backspace, and the user expects it to function as a delete.
|
||||||
|
{
|
||||||
|
id: backspaceSelectionAction
|
||||||
|
text: catalog.i18ncp("@action:inmenu menubar:edit", "Delete &Selected Model", "Delete &Selected Models", UM.Selection.selectionCount)
|
||||||
|
enabled: UM.Controller.toolsEnabled && UM.Selection.hasSelection
|
||||||
|
iconName: "edit-delete"
|
||||||
|
shortcut: StandardKey.Backspace
|
||||||
|
onTriggered: CuraActions.deleteSelection()
|
||||||
|
}
|
||||||
|
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: centerSelectionAction;
|
id: centerSelectionAction;
|
||||||
|
@ -425,13 +434,6 @@ Item
|
||||||
iconName: "plugins_browse"
|
iconName: "plugins_browse"
|
||||||
}
|
}
|
||||||
|
|
||||||
Action
|
|
||||||
{
|
|
||||||
id: configurePluginsAction
|
|
||||||
text: catalog.i18nc("@action:menu", "Installed plugins...");
|
|
||||||
iconName: "plugins_configure"
|
|
||||||
}
|
|
||||||
|
|
||||||
Action
|
Action
|
||||||
{
|
{
|
||||||
id: expandSidebarAction;
|
id: expandSidebarAction;
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue