mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Merge branch 'master' into feature_firmware_updater
This commit is contained in:
commit
7c23a4e187
337 changed files with 88130 additions and 18157 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -15,6 +15,7 @@ LC_MESSAGES
|
||||||
.cache
|
.cache
|
||||||
*.qmlc
|
*.qmlc
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
|
.pytest_cache
|
||||||
|
|
||||||
#MacOS
|
#MacOS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
@ -25,6 +26,7 @@ LC_MESSAGES
|
||||||
*.lprof
|
*.lprof
|
||||||
*~
|
*~
|
||||||
*.qm
|
*.qm
|
||||||
|
.directory
|
||||||
.idea
|
.idea
|
||||||
cura.desktop
|
cura.desktop
|
||||||
|
|
||||||
|
|
75
Jenkinsfile
vendored
75
Jenkinsfile
vendored
|
@ -12,6 +12,30 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||||
|
|
||||||
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
|
// If any error occurs during building, we want to catch it and continue with the "finale" stage.
|
||||||
catchError {
|
catchError {
|
||||||
|
stage('Pre Checks') {
|
||||||
|
if (isUnix()) {
|
||||||
|
// Check shortcut keys
|
||||||
|
try {
|
||||||
|
sh """
|
||||||
|
echo 'Check for duplicate shortcut keys in all translation files.'
|
||||||
|
${env.CURA_ENVIRONMENT_PATH}/master/bin/python3 scripts/check_shortcut_keys.py
|
||||||
|
"""
|
||||||
|
} catch(e) {
|
||||||
|
currentBuild.result = "UNSTABLE"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check setting visibilities
|
||||||
|
try {
|
||||||
|
sh """
|
||||||
|
echo 'Check for duplicate shortcut keys in all translation files.'
|
||||||
|
${env.CURA_ENVIRONMENT_PATH}/master/bin/python3 scripts/check_setting_visibility.py
|
||||||
|
"""
|
||||||
|
} catch(e) {
|
||||||
|
currentBuild.result = "UNSTABLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Building and testing should happen in a subdirectory.
|
// Building and testing should happen in a subdirectory.
|
||||||
dir('build') {
|
dir('build') {
|
||||||
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
// Perform the "build". Since Uranium is Python code, this basically only ensures CMake is setup.
|
||||||
|
@ -28,10 +52,53 @@ parallel_nodes(['linux && cura', 'windows && cura']) {
|
||||||
|
|
||||||
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
// Try and run the unit tests. If this stage fails, we consider the build to be "unstable".
|
||||||
stage('Unit Test') {
|
stage('Unit Test') {
|
||||||
try {
|
if (isUnix()) {
|
||||||
make('test')
|
// For Linux to show everything
|
||||||
} catch(e) {
|
def branch = env.BRANCH_NAME
|
||||||
currentBuild.result = "UNSTABLE"
|
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
|
||||||
|
branch = "master"
|
||||||
|
}
|
||||||
|
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
||||||
|
|
||||||
|
try {
|
||||||
|
sh """
|
||||||
|
cd ..
|
||||||
|
export PYTHONPATH=.:"${uranium_dir}"
|
||||||
|
${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/pytest -x --verbose --full-trace --capture=no ./tests
|
||||||
|
"""
|
||||||
|
} catch(e) {
|
||||||
|
currentBuild.result = "UNSTABLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// For Windows
|
||||||
|
try {
|
||||||
|
// This also does code style checks.
|
||||||
|
bat 'ctest -V'
|
||||||
|
} catch(e) {
|
||||||
|
currentBuild.result = "UNSTABLE"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
stage('Code Style') {
|
||||||
|
if (isUnix()) {
|
||||||
|
// For Linux to show everything
|
||||||
|
def branch = env.BRANCH_NAME
|
||||||
|
if(!fileExists("${env.CURA_ENVIRONMENT_PATH}/${branch}")) {
|
||||||
|
branch = "master"
|
||||||
|
}
|
||||||
|
def uranium_dir = get_workspace_dir("Ultimaker/Uranium/${branch}")
|
||||||
|
|
||||||
|
try {
|
||||||
|
sh """
|
||||||
|
cd ..
|
||||||
|
export PYTHONPATH=.:"${uranium_dir}"
|
||||||
|
${env.CURA_ENVIRONMENT_PATH}/${branch}/bin/python3 run_mypy.py
|
||||||
|
"""
|
||||||
|
} catch(e) {
|
||||||
|
currentBuild.result = "UNSTABLE"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ function(cura_add_test)
|
||||||
if (NOT ${test_exists})
|
if (NOT ${test_exists})
|
||||||
add_test(
|
add_test(
|
||||||
NAME ${_NAME}
|
NAME ${_NAME}
|
||||||
COMMAND ${PYTHON_EXECUTABLE} -m pytest --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
|
COMMAND ${PYTHON_EXECUTABLE} -m pytest --verbose --full-trace --capture=no --no-print-log --junitxml=${CMAKE_BINARY_DIR}/junit-${_NAME}.xml ${_DIRECTORY}
|
||||||
)
|
)
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
|
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT LANG=C)
|
||||||
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
|
set_tests_properties(${_NAME} PROPERTIES ENVIRONMENT "PYTHONPATH=${_PYTHONPATH}")
|
||||||
|
|
|
@ -13,6 +13,6 @@ TryExec=@CMAKE_INSTALL_FULL_BINDIR@/cura
|
||||||
Icon=cura-icon
|
Icon=cura-icon
|
||||||
Terminal=false
|
Terminal=false
|
||||||
Type=Application
|
Type=Application
|
||||||
MimeType=application/sla;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
|
MimeType=model/stl;application/vnd.ms-3mfdocument;application/prs.wavefront-obj;image/bmp;image/gif;image/jpeg;image/png;model/x3d+xml;
|
||||||
Categories=Graphics;
|
Categories=Graphics;
|
||||||
Keywords=3D;Printing;Slicer;
|
Keywords=3D;Printing;Slicer;
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<glob-deleteall/>
|
<glob-deleteall/>
|
||||||
<glob pattern="*.3mf"/>
|
<glob pattern="*.3mf"/>
|
||||||
</mime-type>
|
</mime-type>
|
||||||
<mime-type type="application/sla">
|
<mime-type type="model/stl">
|
||||||
<comment>Computer-aided design and manufacturing format</comment>
|
<comment>Computer-aided design and manufacturing format</comment>
|
||||||
<icon name="unknown"/>
|
<icon name="unknown"/>
|
||||||
<glob-deleteall/>
|
<glob-deleteall/>
|
||||||
|
|
|
@ -28,7 +28,7 @@ import copy
|
||||||
|
|
||||||
from typing import List, Optional
|
from typing import List, Optional
|
||||||
|
|
||||||
# Setting for clearance around the prime
|
# Radius of disallowed area in mm around prime. I.e. how much distance to keep from prime position.
|
||||||
PRIME_CLEARANCE = 6.5
|
PRIME_CLEARANCE = 6.5
|
||||||
|
|
||||||
|
|
||||||
|
@ -479,8 +479,6 @@ class BuildVolume(SceneNode):
|
||||||
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
|
maximum = Vector(max_w - bed_adhesion_size - 1, max_h - self._raft_thickness - self._extra_z_clearance, max_d - disallowed_area_size + bed_adhesion_size - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
self._application.getController().getScene()._maximum_bounds = scale_to_max_bounds
|
|
||||||
|
|
||||||
self.updateNodeBoundaryCheck()
|
self.updateNodeBoundaryCheck()
|
||||||
|
|
||||||
def getBoundingBox(self) -> AxisAlignedBox:
|
def getBoundingBox(self) -> AxisAlignedBox:
|
||||||
|
@ -528,7 +526,7 @@ class BuildVolume(SceneNode):
|
||||||
def _onStackChanged(self):
|
def _onStackChanged(self):
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
extruder.propertyChanged.disconnect(self._onSettingPropertyChanged)
|
||||||
|
|
||||||
|
@ -536,7 +534,7 @@ class BuildVolume(SceneNode):
|
||||||
|
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
self._global_container_stack.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
|
extruder.propertyChanged.connect(self._onSettingPropertyChanged)
|
||||||
|
|
||||||
|
|
|
@ -19,5 +19,11 @@ class CameraImageProvider(QQuickImageProvider):
|
||||||
|
|
||||||
return image, QSize(15, 15)
|
return image, QSize(15, 15)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pass
|
try:
|
||||||
|
image = output_device.activeCamera.getImage()
|
||||||
|
|
||||||
|
return image, QSize(15, 15)
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
return QImage(), QSize(15, 15)
|
return QImage(), QSize(15, 15)
|
||||||
|
|
|
@ -13,6 +13,7 @@ from PyQt5.QtGui import QColor, QIcon
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
from PyQt5.QtQml import qmlRegisterUncreatableType, qmlRegisterSingletonType, qmlRegisterType
|
||||||
|
|
||||||
|
from UM.PluginError import PluginNotFoundError
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
from UM.Scene.Camera import Camera
|
from UM.Scene.Camera import Camera
|
||||||
from UM.Math.Vector import Vector
|
from UM.Math.Vector import Vector
|
||||||
|
@ -67,9 +68,9 @@ from cura.Machines.Models.NozzleModel import NozzleModel
|
||||||
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
from cura.Machines.Models.QualityProfilesDropDownMenuModel import QualityProfilesDropDownMenuModel
|
||||||
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
from cura.Machines.Models.CustomQualityProfilesDropDownMenuModel import CustomQualityProfilesDropDownMenuModel
|
||||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||||
from cura.Machines.Models.MaterialManagementModel import MaterialManagementModel
|
from cura.Machines.Models.FavoriteMaterialsModel import FavoriteMaterialsModel
|
||||||
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
from cura.Machines.Models.GenericMaterialsModel import GenericMaterialsModel
|
||||||
from cura.Machines.Models.BrandMaterialsModel import BrandMaterialsModel
|
from cura.Machines.Models.MaterialBrandsModel import MaterialBrandsModel
|
||||||
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
from cura.Machines.Models.QualityManagementModel import QualityManagementModel
|
||||||
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
from cura.Machines.Models.QualitySettingsModel import QualitySettingsModel
|
||||||
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
from cura.Machines.Models.MachineManagementModel import MachineManagementModel
|
||||||
|
@ -93,6 +94,7 @@ from . import CuraActions
|
||||||
from cura.Scene import ZOffsetDecorator
|
from cura.Scene import ZOffsetDecorator
|
||||||
from . import CuraSplashScreen
|
from . import CuraSplashScreen
|
||||||
from . import CameraImageProvider
|
from . import CameraImageProvider
|
||||||
|
from . import PrintJobPreviewImageProvider
|
||||||
from . import MachineActionManager
|
from . import MachineActionManager
|
||||||
|
|
||||||
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
from cura.TaskManagement.OnExitCallbackManager import OnExitCallbackManager
|
||||||
|
@ -112,7 +114,9 @@ from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from plugins.SliceInfoPlugin.SliceInfo import SliceInfo
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
|
from cura.Machines.QualityManager import QualityManager
|
||||||
|
from UM.Settings.EmptyInstanceContainer import EmptyInstanceContainer
|
||||||
|
|
||||||
|
|
||||||
numpy.seterr(all = "ignore")
|
numpy.seterr(all = "ignore")
|
||||||
|
@ -174,12 +178,12 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._machine_action_manager = None
|
self._machine_action_manager = None
|
||||||
|
|
||||||
self.empty_container = None
|
self.empty_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_definition_changes_container = None
|
self.empty_definition_changes_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_variant_container = None
|
self.empty_variant_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_material_container = None
|
self.empty_material_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_quality_container = None
|
self.empty_quality_container = None # type: EmptyInstanceContainer
|
||||||
self.empty_quality_changes_container = None
|
self.empty_quality_changes_container = None # type: EmptyInstanceContainer
|
||||||
|
|
||||||
self._variant_manager = None
|
self._variant_manager = None
|
||||||
self._material_manager = None
|
self._material_manager = None
|
||||||
|
@ -215,7 +219,6 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
self._message_box_callback = None
|
self._message_box_callback = None
|
||||||
self._message_box_callback_arguments = []
|
self._message_box_callback_arguments = []
|
||||||
self._preferred_mimetype = ""
|
|
||||||
self._i18n_catalog = None
|
self._i18n_catalog = None
|
||||||
|
|
||||||
self._currently_loading_files = []
|
self._currently_loading_files = []
|
||||||
|
@ -368,7 +371,7 @@ class CuraApplication(QtApplication):
|
||||||
# Add empty variant, material and quality containers.
|
# Add empty variant, material and quality containers.
|
||||||
# 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.
|
||||||
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container
|
self.empty_container = cura.Settings.cura_empty_instance_containers.empty_container # type: EmptyInstanceContainer
|
||||||
|
|
||||||
self._container_registry.addContainer(
|
self._container_registry.addContainer(
|
||||||
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
cura.Settings.cura_empty_instance_containers.empty_definition_changes_container)
|
||||||
|
@ -429,6 +432,7 @@ class CuraApplication(QtApplication):
|
||||||
# Readers & Writers:
|
# Readers & Writers:
|
||||||
"GCodeWriter",
|
"GCodeWriter",
|
||||||
"STLReader",
|
"STLReader",
|
||||||
|
"3MFWriter",
|
||||||
|
|
||||||
# Tools:
|
# Tools:
|
||||||
"CameraTool",
|
"CameraTool",
|
||||||
|
@ -481,7 +485,11 @@ class CuraApplication(QtApplication):
|
||||||
preferences.addPreference("view/filter_current_build_plate", False)
|
preferences.addPreference("view/filter_current_build_plate", False)
|
||||||
preferences.addPreference("cura/sidebar_collapsed", False)
|
preferences.addPreference("cura/sidebar_collapsed", False)
|
||||||
|
|
||||||
self._need_to_show_user_agreement = not self.getPreferences().getValue("general/accepted_user_agreement")
|
preferences.addPreference("cura/favorite_materials", "")
|
||||||
|
preferences.addPreference("cura/expanded_brands", "")
|
||||||
|
preferences.addPreference("cura/expanded_types", "")
|
||||||
|
|
||||||
|
self._need_to_show_user_agreement = not preferences.getValue("general/accepted_user_agreement")
|
||||||
|
|
||||||
for key in [
|
for key in [
|
||||||
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
"dialog_load_path", # dialog_save_path is in LocalFileOutputDevicePlugin
|
||||||
|
@ -495,15 +503,13 @@ class CuraApplication(QtApplication):
|
||||||
self.applicationShuttingDown.connect(self.saveSettings)
|
self.applicationShuttingDown.connect(self.saveSettings)
|
||||||
self.engineCreatedSignal.connect(self._onEngineCreated)
|
self.engineCreatedSignal.connect(self._onEngineCreated)
|
||||||
|
|
||||||
self.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
|
||||||
self._onGlobalContainerChanged()
|
|
||||||
|
|
||||||
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
self.getCuraSceneController().setActiveBuildPlate(0) # Initialize
|
||||||
|
|
||||||
CuraApplication.Created = True
|
CuraApplication.Created = True
|
||||||
|
|
||||||
def _onEngineCreated(self):
|
def _onEngineCreated(self):
|
||||||
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
|
||||||
|
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
|
||||||
|
|
||||||
@pyqtProperty(bool)
|
@pyqtProperty(bool)
|
||||||
def needToShowUserAgreement(self):
|
def needToShowUserAgreement(self):
|
||||||
|
@ -806,20 +812,20 @@ class CuraApplication(QtApplication):
|
||||||
self._machine_manager = MachineManager(self)
|
self._machine_manager = MachineManager(self)
|
||||||
return self._machine_manager
|
return self._machine_manager
|
||||||
|
|
||||||
def getExtruderManager(self, *args):
|
def getExtruderManager(self, *args) -> ExtruderManager:
|
||||||
if self._extruder_manager is None:
|
if self._extruder_manager is None:
|
||||||
self._extruder_manager = ExtruderManager()
|
self._extruder_manager = ExtruderManager()
|
||||||
return self._extruder_manager
|
return self._extruder_manager
|
||||||
|
|
||||||
def getVariantManager(self, *args):
|
def getVariantManager(self, *args) -> VariantManager:
|
||||||
return self._variant_manager
|
return self._variant_manager
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getMaterialManager(self, *args):
|
def getMaterialManager(self, *args) -> "MaterialManager":
|
||||||
return self._material_manager
|
return self._material_manager
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getQualityManager(self, *args):
|
def getQualityManager(self, *args) -> "QualityManager":
|
||||||
return self._quality_manager
|
return self._quality_manager
|
||||||
|
|
||||||
def getObjectsModel(self, *args):
|
def getObjectsModel(self, *args):
|
||||||
|
@ -828,23 +834,23 @@ class CuraApplication(QtApplication):
|
||||||
return self._object_manager
|
return self._object_manager
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getMultiBuildPlateModel(self, *args):
|
def getMultiBuildPlateModel(self, *args) -> MultiBuildPlateModel:
|
||||||
if self._multi_build_plate_model is None:
|
if self._multi_build_plate_model is None:
|
||||||
self._multi_build_plate_model = MultiBuildPlateModel(self)
|
self._multi_build_plate_model = MultiBuildPlateModel(self)
|
||||||
return self._multi_build_plate_model
|
return self._multi_build_plate_model
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getBuildPlateModel(self, *args):
|
def getBuildPlateModel(self, *args) -> BuildPlateModel:
|
||||||
if self._build_plate_model is None:
|
if self._build_plate_model is None:
|
||||||
self._build_plate_model = BuildPlateModel(self)
|
self._build_plate_model = BuildPlateModel(self)
|
||||||
return self._build_plate_model
|
return self._build_plate_model
|
||||||
|
|
||||||
def getCuraSceneController(self, *args):
|
def getCuraSceneController(self, *args) -> CuraSceneController:
|
||||||
if self._cura_scene_controller is None:
|
if self._cura_scene_controller is None:
|
||||||
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
|
self._cura_scene_controller = CuraSceneController.createCuraSceneController()
|
||||||
return self._cura_scene_controller
|
return self._cura_scene_controller
|
||||||
|
|
||||||
def getSettingInheritanceManager(self, *args):
|
def getSettingInheritanceManager(self, *args) -> SettingInheritanceManager:
|
||||||
if self._setting_inheritance_manager is None:
|
if self._setting_inheritance_manager is None:
|
||||||
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
|
||||||
return self._setting_inheritance_manager
|
return self._setting_inheritance_manager
|
||||||
|
@ -915,9 +921,9 @@ class CuraApplication(QtApplication):
|
||||||
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(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
|
||||||
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
|
||||||
qmlRegisterType(BrandMaterialsModel, "Cura", 1, 0, "BrandMaterialsModel")
|
qmlRegisterType(MaterialBrandsModel, "Cura", 1, 0, "MaterialBrandsModel")
|
||||||
qmlRegisterType(MaterialManagementModel, "Cura", 1, 0, "MaterialManagementModel")
|
|
||||||
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
qmlRegisterType(QualityManagementModel, "Cura", 1, 0, "QualityManagementModel")
|
||||||
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
qmlRegisterType(MachineManagementModel, "Cura", 1, 0, "MachineManagementModel")
|
||||||
|
|
||||||
|
@ -981,30 +987,14 @@ class CuraApplication(QtApplication):
|
||||||
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
self._camera_animation.setTarget(Selection.getSelectedObject(0).getWorldPosition())
|
||||||
self._camera_animation.start()
|
self._camera_animation.start()
|
||||||
|
|
||||||
def _onGlobalContainerChanged(self):
|
|
||||||
if self._global_container_stack is not None:
|
|
||||||
machine_file_formats = [file_type.strip() for file_type in self._global_container_stack.getMetaDataEntry("file_formats").split(";")]
|
|
||||||
new_preferred_mimetype = ""
|
|
||||||
if machine_file_formats:
|
|
||||||
new_preferred_mimetype = machine_file_formats[0]
|
|
||||||
|
|
||||||
if new_preferred_mimetype != self._preferred_mimetype:
|
|
||||||
self._preferred_mimetype = new_preferred_mimetype
|
|
||||||
self.preferredOutputMimetypeChanged.emit()
|
|
||||||
|
|
||||||
requestAddPrinter = pyqtSignal()
|
requestAddPrinter = pyqtSignal()
|
||||||
activityChanged = pyqtSignal()
|
activityChanged = pyqtSignal()
|
||||||
sceneBoundingBoxChanged = pyqtSignal()
|
sceneBoundingBoxChanged = pyqtSignal()
|
||||||
preferredOutputMimetypeChanged = pyqtSignal()
|
|
||||||
|
|
||||||
@pyqtProperty(bool, notify = activityChanged)
|
@pyqtProperty(bool, notify = activityChanged)
|
||||||
def platformActivity(self):
|
def platformActivity(self):
|
||||||
return self._platform_activity
|
return self._platform_activity
|
||||||
|
|
||||||
@pyqtProperty(str, notify=preferredOutputMimetypeChanged)
|
|
||||||
def preferredOutputMimetype(self):
|
|
||||||
return self._preferred_mimetype
|
|
||||||
|
|
||||||
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
|
@pyqtProperty(str, notify = sceneBoundingBoxChanged)
|
||||||
def getSceneBoundingBoxString(self):
|
def getSceneBoundingBoxString(self):
|
||||||
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
|
return self._i18n_catalog.i18nc("@info 'width', 'depth' and 'height' are variable names that must NOT be translated; just translate the format of ##x##x## mm.", "%(width).1f x %(depth).1f x %(height).1f mm") % {'width' : self._scene_bounding_box.width.item(), 'depth': self._scene_bounding_box.depth.item(), 'height' : self._scene_bounding_box.height.item()}
|
||||||
|
@ -1720,7 +1710,11 @@ class CuraApplication(QtApplication):
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def showMoreInformationDialogForAnonymousDataCollection(self):
|
def showMoreInformationDialogForAnonymousDataCollection(self):
|
||||||
cast(SliceInfo, self._plugin_registry.getPluginObject("SliceInfoPlugin")).showMoreInfoDialog()
|
try:
|
||||||
|
slice_info = self._plugin_registry.getPluginObject("SliceInfoPlugin")
|
||||||
|
slice_info.showMoreInfoDialog()
|
||||||
|
except PluginNotFoundError:
|
||||||
|
Logger.log("w", "Plugin SliceInfo was not found, so not able to show the info dialog.")
|
||||||
|
|
||||||
def addSidebarCustomMenuItem(self, menu_item: dict) -> None:
|
def addSidebarCustomMenuItem(self, menu_item: dict) -> None:
|
||||||
self._sidebar_custom_menu_items.append(menu_item)
|
self._sidebar_custom_menu_items.append(menu_item)
|
||||||
|
|
|
@ -21,7 +21,7 @@ class LayerPolygon:
|
||||||
__number_of_types = 11
|
__number_of_types = 11
|
||||||
|
|
||||||
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
|
||||||
|
|
||||||
## LayerPolygon, used in ProcessSlicedLayersJob
|
## LayerPolygon, used in ProcessSlicedLayersJob
|
||||||
# \param extruder
|
# \param extruder
|
||||||
# \param line_types array with line_types
|
# \param line_types array with line_types
|
||||||
|
|
|
@ -9,9 +9,6 @@ from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
from UM.Settings.InstanceContainer import InstanceContainer
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from cura.Machines.QualityGroup import QualityGroup
|
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
# A metadata / container combination. Use getContainer() to get the container corresponding to the metadata.
|
||||||
|
@ -24,29 +21,34 @@ if TYPE_CHECKING:
|
||||||
# This is used in Variant, Material, and Quality Managers.
|
# This is used in Variant, Material, and Quality Managers.
|
||||||
#
|
#
|
||||||
class ContainerNode:
|
class ContainerNode:
|
||||||
__slots__ = ("metadata", "container", "children_map")
|
__slots__ = ("_metadata", "_container", "children_map")
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||||
self.metadata = metadata
|
self._metadata = metadata
|
||||||
self.container = None
|
self._container = None # type: Optional[InstanceContainer]
|
||||||
self.children_map = OrderedDict() #type: OrderedDict[str, Union[QualityGroup, ContainerNode]]
|
self.children_map = OrderedDict() # type: ignore # This is because it's children are supposed to override it.
|
||||||
|
|
||||||
## Get an entry value from the metadata
|
## Get an entry value from the metadata
|
||||||
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
def getMetaDataEntry(self, entry: str, default: Any = None) -> Any:
|
||||||
if self.metadata is None:
|
if self._metadata is None:
|
||||||
return default
|
return default
|
||||||
return self.metadata.get(entry, default)
|
return self._metadata.get(entry, default)
|
||||||
|
|
||||||
|
def getMetadata(self) -> Dict[str, Any]:
|
||||||
|
if self._metadata is None:
|
||||||
|
return {}
|
||||||
|
return self._metadata
|
||||||
|
|
||||||
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
def getChildNode(self, child_key: str) -> Optional["ContainerNode"]:
|
||||||
return self.children_map.get(child_key)
|
return self.children_map.get(child_key)
|
||||||
|
|
||||||
def getContainer(self) -> Optional["InstanceContainer"]:
|
def getContainer(self) -> Optional["InstanceContainer"]:
|
||||||
if self.metadata is None:
|
if self._metadata is None:
|
||||||
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
|
Logger.log("e", "Cannot get container for a ContainerNode without metadata.")
|
||||||
return None
|
return None
|
||||||
|
|
||||||
if self.container is None:
|
if self._container is None:
|
||||||
container_id = self.metadata["id"]
|
container_id = self._metadata["id"]
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
container_list = ContainerRegistry.getInstance().findInstanceContainers(id = container_id)
|
||||||
if not container_list:
|
if not container_list:
|
||||||
|
@ -54,9 +56,9 @@ class ContainerNode:
|
||||||
error_message = ConfigurationErrorMessage.getInstance()
|
error_message = ConfigurationErrorMessage.getInstance()
|
||||||
error_message.addFaultyContainers(container_id)
|
error_message.addFaultyContainers(container_id)
|
||||||
return None
|
return None
|
||||||
self.container = container_list[0]
|
self._container = container_list[0]
|
||||||
|
|
||||||
return self.container
|
return self._container
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
|
return "%s[%s]" % (self.__class__.__name__, self.getMetaDataEntry("id"))
|
||||||
|
|
|
@ -50,7 +50,7 @@ class MachineErrorChecker(QObject):
|
||||||
self._error_check_timer.setInterval(100)
|
self._error_check_timer.setInterval(100)
|
||||||
self._error_check_timer.setSingleShot(True)
|
self._error_check_timer.setSingleShot(True)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
self._error_check_timer.timeout.connect(self._rescheduleCheck)
|
||||||
|
|
||||||
# Reconnect all signals when the active machine gets changed.
|
# Reconnect all signals when the active machine gets changed.
|
||||||
|
@ -62,7 +62,7 @@ class MachineErrorChecker(QObject):
|
||||||
|
|
||||||
self._onMachineChanged()
|
self._onMachineChanged()
|
||||||
|
|
||||||
def _onMachineChanged(self):
|
def _onMachineChanged(self) -> None:
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
|
self._global_stack.propertyChanged.disconnect(self.startErrorCheck)
|
||||||
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
self._global_stack.containersChanged.disconnect(self.startErrorCheck)
|
||||||
|
@ -94,7 +94,7 @@ class MachineErrorChecker(QObject):
|
||||||
return self._need_to_check or self._check_in_progress
|
return self._need_to_check or self._check_in_progress
|
||||||
|
|
||||||
# Starts the error check timer to schedule a new error check.
|
# Starts the error check timer to schedule a new error check.
|
||||||
def startErrorCheck(self, *args):
|
def startErrorCheck(self, *args) -> None:
|
||||||
if not self._check_in_progress:
|
if not self._check_in_progress:
|
||||||
self._need_to_check = True
|
self._need_to_check = True
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
|
@ -103,7 +103,7 @@ class MachineErrorChecker(QObject):
|
||||||
# This function is called by the timer to reschedule a new error check.
|
# This function is called by the timer to reschedule a new error check.
|
||||||
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
|
# If there is no check in progress, it will start a new one. If there is any, it sets the "_need_to_check" flag
|
||||||
# to notify the current check to stop and start a new one.
|
# to notify the current check to stop and start a new one.
|
||||||
def _rescheduleCheck(self):
|
def _rescheduleCheck(self) -> None:
|
||||||
if self._check_in_progress and not self._need_to_check:
|
if self._check_in_progress and not self._need_to_check:
|
||||||
self._need_to_check = True
|
self._need_to_check = True
|
||||||
self.needToWaitForResultChanged.emit()
|
self.needToWaitForResultChanged.emit()
|
||||||
|
@ -128,7 +128,7 @@ class MachineErrorChecker(QObject):
|
||||||
self._start_time = time.time()
|
self._start_time = time.time()
|
||||||
Logger.log("d", "New error check scheduled.")
|
Logger.log("d", "New error check scheduled.")
|
||||||
|
|
||||||
def _checkStack(self):
|
def _checkStack(self) -> None:
|
||||||
if self._need_to_check:
|
if self._need_to_check:
|
||||||
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
|
Logger.log("d", "Need to check for errors again. Discard the current progress and reschedule a check.")
|
||||||
self._check_in_progress = False
|
self._check_in_progress = False
|
||||||
|
@ -169,7 +169,7 @@ class MachineErrorChecker(QObject):
|
||||||
# Schedule the check for the next key
|
# Schedule the check for the next key
|
||||||
self._application.callLater(self._checkStack)
|
self._application.callLater(self._checkStack)
|
||||||
|
|
||||||
def _setResult(self, result: bool):
|
def _setResult(self, result: bool) -> None:
|
||||||
if result != self._has_errors:
|
if result != self._has_errors:
|
||||||
self._has_errors = result
|
self._has_errors = result
|
||||||
self.hasErrorUpdated.emit()
|
self.hasErrorUpdated.emit()
|
||||||
|
|
|
@ -24,8 +24,8 @@ class MaterialGroup:
|
||||||
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
|
def __init__(self, name: str, root_material_node: "MaterialNode") -> None:
|
||||||
self.name = name
|
self.name = name
|
||||||
self.is_read_only = False
|
self.is_read_only = False
|
||||||
self.root_material_node = root_material_node # type: MaterialNode
|
self.root_material_node = root_material_node # type: MaterialNode
|
||||||
self.derived_material_node_list = [] # type: List[MaterialNode]
|
self.derived_material_node_list = [] # type: List[MaterialNode]
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return "%s[%s]" % (self.__class__.__name__, self.name)
|
return "%s[%s]" % (self.__class__.__name__, self.name)
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
from collections import defaultdict, OrderedDict
|
from collections import defaultdict, OrderedDict
|
||||||
import copy
|
import copy
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Dict, Optional, TYPE_CHECKING
|
from typing import Dict, Optional, TYPE_CHECKING, Any, Set, List, cast, Tuple
|
||||||
|
|
||||||
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
from PyQt5.Qt import QTimer, QObject, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
@ -21,7 +21,6 @@ from .VariantType import VariantType
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
from UM.Settings.InstanceContainer import InstanceContainer
|
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
@ -39,24 +38,34 @@ if TYPE_CHECKING:
|
||||||
class MaterialManager(QObject):
|
class MaterialManager(QObject):
|
||||||
|
|
||||||
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
materialsUpdated = pyqtSignal() # Emitted whenever the material lookup tables are updated.
|
||||||
|
favoritesUpdated = pyqtSignal() # Emitted whenever the favorites are changed
|
||||||
|
|
||||||
def __init__(self, container_registry, parent = None):
|
def __init__(self, container_registry, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._application = Application.getInstance()
|
self._application = Application.getInstance()
|
||||||
self._container_registry = container_registry # type: ContainerRegistry
|
self._container_registry = container_registry # type: ContainerRegistry
|
||||||
|
|
||||||
self._fallback_materials_map = dict() # material_type -> generic material metadata
|
# Material_type -> generic material metadata
|
||||||
self._material_group_map = dict() # root_material_id -> MaterialGroup
|
self._fallback_materials_map = dict() # type: Dict[str, Dict[str, Any]]
|
||||||
self._diameter_machine_nozzle_buildplate_material_map = dict() # approximate diameter str -> dict(machine_definition_id -> MaterialNode)
|
|
||||||
|
# Root_material_id -> MaterialGroup
|
||||||
|
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||||
|
|
||||||
|
# Approximate diameter str
|
||||||
|
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
||||||
|
|
||||||
# We're using these two maps to convert between the specific diameter material id and the generic material id
|
# 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
|
# 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
|
# 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
|
# 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)
|
self._material_diameter_map = defaultdict(dict) # type: Dict[str, Dict[str, str]]
|
||||||
|
|
||||||
|
# Material id including diameter (generic_pla_175) -> material root id (generic_pla)
|
||||||
|
self._diameter_material_map = dict() # type: Dict[str, str]
|
||||||
|
|
||||||
# This is used in Legacy UM3 send material function and the material management page.
|
# 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
|
# GUID -> a list of material_groups
|
||||||
|
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||||
|
|
||||||
# The machine definition ID for the non-machine-specific materials.
|
# 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.
|
# This is used as the last fallback option if the given machine-specific material(s) cannot be found.
|
||||||
|
@ -75,13 +84,15 @@ class MaterialManager(QObject):
|
||||||
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
self._container_registry.containerAdded.connect(self._onContainerMetadataChanged)
|
||||||
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
self._container_registry.containerRemoved.connect(self._onContainerMetadataChanged)
|
||||||
|
|
||||||
def initialize(self):
|
self._favorites = set() # type: Set[str]
|
||||||
|
|
||||||
|
def initialize(self) -> None:
|
||||||
# Find all materials and put them in a matrix for quick search.
|
# Find all materials and put them in a matrix for quick search.
|
||||||
material_metadatas = {metadata["id"]: metadata for metadata in
|
material_metadatas = {metadata["id"]: metadata for metadata in
|
||||||
self._container_registry.findContainersMetadata(type = "material") if
|
self._container_registry.findContainersMetadata(type = "material") if
|
||||||
metadata.get("GUID")}
|
metadata.get("GUID")} # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
self._material_group_map = dict()
|
self._material_group_map = dict() # type: Dict[str, MaterialGroup]
|
||||||
|
|
||||||
# Map #1
|
# Map #1
|
||||||
# root_material_id -> MaterialGroup
|
# root_material_id -> MaterialGroup
|
||||||
|
@ -90,7 +101,7 @@ class MaterialManager(QObject):
|
||||||
if material_id == "empty_material":
|
if material_id == "empty_material":
|
||||||
continue
|
continue
|
||||||
|
|
||||||
root_material_id = material_metadata.get("base_file")
|
root_material_id = material_metadata.get("base_file", "")
|
||||||
if root_material_id not in self._material_group_map:
|
if root_material_id not in self._material_group_map:
|
||||||
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
|
||||||
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)
|
||||||
|
@ -106,26 +117,26 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
# Map #1.5
|
# Map #1.5
|
||||||
# GUID -> material group list
|
# GUID -> material group list
|
||||||
self._guid_material_groups_map = defaultdict(list)
|
self._guid_material_groups_map = defaultdict(list) # type: Dict[str, List[MaterialGroup]]
|
||||||
for root_material_id, material_group in self._material_group_map.items():
|
for root_material_id, material_group in self._material_group_map.items():
|
||||||
guid = material_group.root_material_node.metadata["GUID"]
|
guid = material_group.root_material_node.getMetaDataEntry("GUID", "")
|
||||||
self._guid_material_groups_map[guid].append(material_group)
|
self._guid_material_groups_map[guid].append(material_group)
|
||||||
|
|
||||||
# Map #2
|
# Map #2
|
||||||
# Lookup table for material type -> fallback material metadata, only for read-only materials
|
# Lookup table for material type -> fallback material metadata, only for read-only materials
|
||||||
grouped_by_type_dict = dict()
|
grouped_by_type_dict = dict() # type: Dict[str, Any]
|
||||||
material_types_without_fallback = set()
|
material_types_without_fallback = set()
|
||||||
for root_material_id, material_node in self._material_group_map.items():
|
for root_material_id, material_node in self._material_group_map.items():
|
||||||
material_type = material_node.root_material_node.metadata["material"]
|
material_type = material_node.root_material_node.getMetaDataEntry("material", "")
|
||||||
if material_type not in grouped_by_type_dict:
|
if material_type not in grouped_by_type_dict:
|
||||||
grouped_by_type_dict[material_type] = {"generic": None,
|
grouped_by_type_dict[material_type] = {"generic": None,
|
||||||
"others": []}
|
"others": []}
|
||||||
material_types_without_fallback.add(material_type)
|
material_types_without_fallback.add(material_type)
|
||||||
brand = material_node.root_material_node.metadata["brand"]
|
brand = material_node.root_material_node.getMetaDataEntry("brand", "")
|
||||||
if brand.lower() == "generic":
|
if brand.lower() == "generic":
|
||||||
to_add = True
|
to_add = True
|
||||||
if material_type in grouped_by_type_dict:
|
if material_type in grouped_by_type_dict:
|
||||||
diameter = material_node.root_material_node.metadata.get("approximate_diameter")
|
diameter = material_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||||
if diameter != self._default_approximate_diameter_for_quality_search:
|
if diameter != self._default_approximate_diameter_for_quality_search:
|
||||||
to_add = False # don't add if it's not the default diameter
|
to_add = False # don't add if it's not the default diameter
|
||||||
|
|
||||||
|
@ -134,7 +145,7 @@ class MaterialManager(QObject):
|
||||||
# - if it's in the list, it means that is a new material without fallback
|
# - if it's in the list, it means that is a new material without fallback
|
||||||
# - if it is not, then it is a custom material with a fallback material (parent)
|
# - if it is not, then it is a custom material with a fallback material (parent)
|
||||||
if material_type in material_types_without_fallback:
|
if material_type in material_types_without_fallback:
|
||||||
grouped_by_type_dict[material_type] = material_node.root_material_node.metadata
|
grouped_by_type_dict[material_type] = material_node.root_material_node._metadata
|
||||||
material_types_without_fallback.remove(material_type)
|
material_types_without_fallback.remove(material_type)
|
||||||
|
|
||||||
# Remove the materials that have no fallback materials
|
# Remove the materials that have no fallback materials
|
||||||
|
@ -151,15 +162,15 @@ class MaterialManager(QObject):
|
||||||
self._diameter_material_map = dict()
|
self._diameter_material_map = dict()
|
||||||
|
|
||||||
# Group the material IDs by the same name, material, brand, and color but with different diameters.
|
# Group the material IDs by the same name, material, brand, and color but with different diameters.
|
||||||
material_group_dict = dict()
|
material_group_dict = dict() # type: Dict[Tuple[Any], Dict[str, str]]
|
||||||
keys_to_fetch = ("name", "material", "brand", "color")
|
keys_to_fetch = ("name", "material", "brand", "color")
|
||||||
for root_material_id, machine_node in self._material_group_map.items():
|
for root_material_id, machine_node in self._material_group_map.items():
|
||||||
root_material_metadata = machine_node.root_material_node.metadata
|
root_material_metadata = machine_node.root_material_node._metadata
|
||||||
|
|
||||||
key_data = []
|
key_data_list = [] # type: List[Any]
|
||||||
for key in keys_to_fetch:
|
for key in keys_to_fetch:
|
||||||
key_data.append(root_material_metadata.get(key))
|
key_data_list.append(machine_node.root_material_node.getMetaDataEntry(key))
|
||||||
key_data = tuple(key_data)
|
key_data = cast(Tuple[Any], tuple(key_data_list)) # type: Tuple[Any]
|
||||||
|
|
||||||
# If the key_data doesn't exist, it doesn't matter if the material is read only...
|
# If the key_data doesn't exist, it doesn't matter if the material is read only...
|
||||||
if key_data not in material_group_dict:
|
if key_data not in material_group_dict:
|
||||||
|
@ -168,8 +179,8 @@ class MaterialManager(QObject):
|
||||||
# ...but if key_data exists, we just overwrite it if the material is read only, otherwise we skip it
|
# ...but if key_data exists, we just overwrite it if the material is read only, otherwise we skip it
|
||||||
if not machine_node.is_read_only:
|
if not machine_node.is_read_only:
|
||||||
continue
|
continue
|
||||||
approximate_diameter = root_material_metadata.get("approximate_diameter")
|
approximate_diameter = machine_node.root_material_node.getMetaDataEntry("approximate_diameter", "")
|
||||||
material_group_dict[key_data][approximate_diameter] = root_material_metadata["id"]
|
material_group_dict[key_data][approximate_diameter] = machine_node.root_material_node.getMetaDataEntry("id", "")
|
||||||
|
|
||||||
# Map [root_material_id][diameter] -> root_material_id for this diameter
|
# Map [root_material_id][diameter] -> root_material_id for this diameter
|
||||||
for data_dict in material_group_dict.values():
|
for data_dict in material_group_dict.values():
|
||||||
|
@ -188,13 +199,17 @@ class MaterialManager(QObject):
|
||||||
|
|
||||||
# Map #4
|
# Map #4
|
||||||
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
# "machine" -> "nozzle name" -> "buildplate name" -> "root material ID" -> specific material InstanceContainer
|
||||||
self._diameter_machine_nozzle_buildplate_material_map = dict()
|
self._diameter_machine_nozzle_buildplate_material_map = dict() # type: Dict[str, Dict[str, MaterialNode]]
|
||||||
for material_metadata in material_metadatas.values():
|
for material_metadata in material_metadatas.values():
|
||||||
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
self.__addMaterialMetadataIntoLookupTree(material_metadata)
|
||||||
|
|
||||||
|
favorites = self._application.getPreferences().getValue("cura/favorite_materials")
|
||||||
|
for item in favorites.split(";"):
|
||||||
|
self._favorites.add(item)
|
||||||
|
|
||||||
self.materialsUpdated.emit()
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
def __addMaterialMetadataIntoLookupTree(self, material_metadata: dict) -> None:
|
def __addMaterialMetadataIntoLookupTree(self, material_metadata: Dict[str, Any]) -> None:
|
||||||
material_id = material_metadata["id"]
|
material_id = material_metadata["id"]
|
||||||
|
|
||||||
# We don't store empty material in the lookup tables
|
# We don't store empty material in the lookup tables
|
||||||
|
@ -281,7 +296,7 @@ class MaterialManager(QObject):
|
||||||
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
|
return self._material_diameter_map.get(root_material_id, {}).get(approximate_diameter, root_material_id)
|
||||||
|
|
||||||
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
def getRootMaterialIDWithoutDiameter(self, root_material_id: str) -> str:
|
||||||
return self._diameter_material_map.get(root_material_id)
|
return self._diameter_material_map.get(root_material_id, "")
|
||||||
|
|
||||||
def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
|
def getMaterialGroupListByGUID(self, guid: str) -> Optional[list]:
|
||||||
return self._guid_material_groups_map.get(guid)
|
return self._guid_material_groups_map.get(guid)
|
||||||
|
@ -321,6 +336,7 @@ class MaterialManager(QObject):
|
||||||
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
machine_exclude_materials = machine_definition.getMetaDataEntry("exclude_materials", [])
|
||||||
|
|
||||||
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
material_id_metadata_dict = dict() # type: Dict[str, MaterialNode]
|
||||||
|
excluded_materials = set()
|
||||||
for current_node in nodes_to_check:
|
for current_node in nodes_to_check:
|
||||||
if current_node is None:
|
if current_node is None:
|
||||||
continue
|
continue
|
||||||
|
@ -329,20 +345,22 @@ class MaterialManager(QObject):
|
||||||
# Do not exclude other materials that are of the same type.
|
# Do not exclude other materials that are of the same type.
|
||||||
for material_id, node in current_node.material_map.items():
|
for material_id, node in current_node.material_map.items():
|
||||||
if material_id in machine_exclude_materials:
|
if material_id in machine_exclude_materials:
|
||||||
Logger.log("d", "Exclude material [%s] for machine [%s]",
|
excluded_materials.add(material_id)
|
||||||
material_id, machine_definition.getId())
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if material_id not in material_id_metadata_dict:
|
if material_id not in material_id_metadata_dict:
|
||||||
material_id_metadata_dict[material_id] = node
|
material_id_metadata_dict[material_id] = node
|
||||||
|
|
||||||
|
if excluded_materials:
|
||||||
|
Logger.log("d", "Exclude materials {excluded_materials} for machine {machine_definition_id}".format(excluded_materials = ", ".join(excluded_materials), machine_definition_id = machine_definition_id))
|
||||||
|
|
||||||
return material_id_metadata_dict
|
return material_id_metadata_dict
|
||||||
|
|
||||||
#
|
#
|
||||||
# A convenience function to get available materials for the given machine with the extruder position.
|
# A convenience function to get available materials for the given machine with the extruder position.
|
||||||
#
|
#
|
||||||
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
def getAvailableMaterialsForMachineExtruder(self, machine: "GlobalStack",
|
||||||
extruder_stack: "ExtruderStack") -> Optional[dict]:
|
extruder_stack: "ExtruderStack") -> Optional[Dict[str, MaterialNode]]:
|
||||||
buildplate_name = machine.getBuildplateName()
|
buildplate_name = machine.getBuildplateName()
|
||||||
nozzle_name = None
|
nozzle_name = None
|
||||||
if extruder_stack.variant.getId() != "empty_variant":
|
if extruder_stack.variant.getId() != "empty_variant":
|
||||||
|
@ -359,7 +377,7 @@ class MaterialManager(QObject):
|
||||||
# 2. cannot find any material InstanceContainers with the given settings.
|
# 2. cannot find any material InstanceContainers with the given settings.
|
||||||
#
|
#
|
||||||
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
|
def getMaterialNode(self, machine_definition_id: str, nozzle_name: Optional[str],
|
||||||
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["InstanceContainer"]:
|
buildplate_name: Optional[str], diameter: float, root_material_id: str) -> Optional["MaterialNode"]:
|
||||||
# round the diameter to get the approximate diameter
|
# round the diameter to get the approximate diameter
|
||||||
rounded_diameter = str(round(diameter))
|
rounded_diameter = str(round(diameter))
|
||||||
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
if rounded_diameter not in self._diameter_machine_nozzle_buildplate_material_map:
|
||||||
|
@ -368,7 +386,7 @@ class MaterialManager(QObject):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# If there are nozzle materials, get the nozzle-specific material
|
# If there are nozzle materials, get the nozzle-specific material
|
||||||
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter]
|
machine_nozzle_buildplate_material_map = self._diameter_machine_nozzle_buildplate_material_map[rounded_diameter] # type: Dict[str, MaterialNode]
|
||||||
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
machine_node = machine_nozzle_buildplate_material_map.get(machine_definition_id)
|
||||||
nozzle_node = None
|
nozzle_node = None
|
||||||
buildplate_node = None
|
buildplate_node = None
|
||||||
|
@ -417,7 +435,7 @@ class MaterialManager(QObject):
|
||||||
# Look at the guid to material dictionary
|
# Look at the guid to material dictionary
|
||||||
root_material_id = None
|
root_material_id = None
|
||||||
for material_group in self._guid_material_groups_map[material_guid]:
|
for material_group in self._guid_material_groups_map[material_guid]:
|
||||||
root_material_id = material_group.root_material_node.metadata["id"]
|
root_material_id = cast(str, material_group.root_material_node.getMetaDataEntry("id", ""))
|
||||||
break
|
break
|
||||||
|
|
||||||
if not root_material_id:
|
if not root_material_id:
|
||||||
|
@ -493,7 +511,7 @@ class MaterialManager(QObject):
|
||||||
# Sets the new name for the given material.
|
# Sets the new name for the given material.
|
||||||
#
|
#
|
||||||
@pyqtSlot("QVariant", str)
|
@pyqtSlot("QVariant", str)
|
||||||
def setMaterialName(self, material_node: "MaterialNode", name: str):
|
def setMaterialName(self, material_node: "MaterialNode", name: str) -> None:
|
||||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||||
if root_material_id is None:
|
if root_material_id is None:
|
||||||
return
|
return
|
||||||
|
@ -511,7 +529,7 @@ class MaterialManager(QObject):
|
||||||
# Removes the given material.
|
# Removes the given material.
|
||||||
#
|
#
|
||||||
@pyqtSlot("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def removeMaterial(self, material_node: "MaterialNode"):
|
def removeMaterial(self, material_node: "MaterialNode") -> None:
|
||||||
root_material_id = material_node.getMetaDataEntry("base_file")
|
root_material_id = material_node.getMetaDataEntry("base_file")
|
||||||
if root_material_id is not None:
|
if root_material_id is not None:
|
||||||
self.removeMaterialByRootId(root_material_id)
|
self.removeMaterialByRootId(root_material_id)
|
||||||
|
@ -521,8 +539,8 @@ class MaterialManager(QObject):
|
||||||
# Returns the root material ID of the duplicated material if successful.
|
# Returns the root material ID of the duplicated material if successful.
|
||||||
#
|
#
|
||||||
@pyqtSlot("QVariant", result = str)
|
@pyqtSlot("QVariant", result = str)
|
||||||
def duplicateMaterial(self, material_node, new_base_id = None, new_metadata = None) -> Optional[str]:
|
def duplicateMaterial(self, material_node: MaterialNode, new_base_id: Optional[str] = None, new_metadata: Dict[str, Any] = None) -> Optional[str]:
|
||||||
root_material_id = material_node.metadata["base_file"]
|
root_material_id = cast(str, material_node.getMetaDataEntry("base_file", ""))
|
||||||
|
|
||||||
material_group = self.getMaterialGroup(root_material_id)
|
material_group = self.getMaterialGroup(root_material_id)
|
||||||
if not material_group:
|
if not material_group:
|
||||||
|
@ -573,11 +591,17 @@ class MaterialManager(QObject):
|
||||||
for container_to_add in new_containers:
|
for container_to_add in new_containers:
|
||||||
container_to_add.setDirty(True)
|
container_to_add.setDirty(True)
|
||||||
self._container_registry.addContainer(container_to_add)
|
self._container_registry.addContainer(container_to_add)
|
||||||
|
|
||||||
|
|
||||||
|
# if the duplicated material was favorite then the new material should also be added to favorite.
|
||||||
|
if root_material_id in self.getFavorites():
|
||||||
|
self.addFavorite(new_base_id)
|
||||||
|
|
||||||
return new_base_id
|
return new_base_id
|
||||||
|
|
||||||
#
|
#
|
||||||
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
# Create a new material by cloning Generic PLA for the current material diameter and generate a new GUID.
|
||||||
#
|
# Returns the ID of the newly created material.
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
def createMaterial(self) -> str:
|
def createMaterial(self) -> str:
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
|
@ -608,3 +632,25 @@ class MaterialManager(QObject):
|
||||||
new_base_id = new_id,
|
new_base_id = new_id,
|
||||||
new_metadata = new_metadata)
|
new_metadata = new_metadata)
|
||||||
return new_id
|
return new_id
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def addFavorite(self, root_material_id: str) -> None:
|
||||||
|
self._favorites.add(root_material_id)
|
||||||
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def removeFavorite(self, root_material_id: str) -> None:
|
||||||
|
self._favorites.remove(root_material_id)
|
||||||
|
self.materialsUpdated.emit()
|
||||||
|
|
||||||
|
# Ensure all settings are saved.
|
||||||
|
self._application.getPreferences().setValue("cura/favorite_materials", ";".join(list(self._favorites)))
|
||||||
|
self._application.saveSettings()
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
|
def getFavorites(self):
|
||||||
|
return self._favorites
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 typing import Optional, Dict
|
from typing import Optional, Dict, Any
|
||||||
|
from collections import OrderedDict
|
||||||
from .ContainerNode import ContainerNode
|
from .ContainerNode import ContainerNode
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,6 +14,12 @@ from .ContainerNode import ContainerNode
|
||||||
class MaterialNode(ContainerNode):
|
class MaterialNode(ContainerNode):
|
||||||
__slots__ = ("material_map", "children_map")
|
__slots__ = ("material_map", "children_map")
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[dict] = None) -> None:
|
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||||
super().__init__(metadata = metadata)
|
super().__init__(metadata = metadata)
|
||||||
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
self.material_map = {} # type: Dict[str, MaterialNode] # material_root_id -> material_node
|
||||||
|
|
||||||
|
# We overide this as we want to indicate that MaterialNodes can only contain other material nodes.
|
||||||
|
self.children_map = OrderedDict() # type: OrderedDict[str, "MaterialNode"]
|
||||||
|
|
||||||
|
def getChildNode(self, child_key: str) -> Optional["MaterialNode"]:
|
||||||
|
return self.children_map.get(child_key)
|
|
@ -2,45 +2,60 @@
|
||||||
# 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 Qt, pyqtSignal, pyqtProperty
|
from PyQt5.QtCore import Qt, pyqtSignal, pyqtProperty
|
||||||
|
|
||||||
from UM.Application import Application
|
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
|
||||||
|
|
||||||
#
|
## This is the base model class for GenericMaterialsModel and MaterialBrandsModel.
|
||||||
# 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.
|
||||||
# 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
|
||||||
# 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
|
||||||
# bar menu "Settings" -> "Extruder nr" -> "Material" -> this menu
|
|
||||||
#
|
|
||||||
class BaseMaterialsModel(ListModel):
|
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()
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._application = Application.getInstance()
|
|
||||||
self._machine_manager = self._application.getMachineManager()
|
|
||||||
|
|
||||||
self.addRoleName(self.RootMaterialIdRole, "root_material_id")
|
from cura.CuraApplication import CuraApplication
|
||||||
self.addRoleName(self.IdRole, "id")
|
|
||||||
self.addRoleName(self.NameRole, "name")
|
self._application = CuraApplication.getInstance()
|
||||||
self.addRoleName(self.BrandRole, "brand")
|
|
||||||
self.addRoleName(self.MaterialRole, "material")
|
# Make these managers available to all material models
|
||||||
self.addRoleName(self.ColorRole, "color_name")
|
self._container_registry = self._application.getInstance().getContainerRegistry()
|
||||||
self.addRoleName(self.ContainerNodeRole, "container_node")
|
self._machine_manager = self._application.getMachineManager()
|
||||||
|
self._material_manager = self._application.getMaterialManager()
|
||||||
|
|
||||||
|
# Update the stack and the model data when the machine changes
|
||||||
|
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
||||||
|
|
||||||
|
# Update this model when switching machines
|
||||||
|
self._machine_manager.activeStackChanged.connect(self._update)
|
||||||
|
|
||||||
|
# Update this model when list of materials changes
|
||||||
|
self._material_manager.materialsUpdated.connect(self._update)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "root_material_id")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "id")
|
||||||
|
self.addRoleName(Qt.UserRole + 3, "GUID")
|
||||||
|
self.addRoleName(Qt.UserRole + 4, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 5, "brand")
|
||||||
|
self.addRoleName(Qt.UserRole + 6, "description")
|
||||||
|
self.addRoleName(Qt.UserRole + 7, "material")
|
||||||
|
self.addRoleName(Qt.UserRole + 8, "color_name")
|
||||||
|
self.addRoleName(Qt.UserRole + 9, "color_code")
|
||||||
|
self.addRoleName(Qt.UserRole + 10, "density")
|
||||||
|
self.addRoleName(Qt.UserRole + 11, "diameter")
|
||||||
|
self.addRoleName(Qt.UserRole + 12, "approximate_diameter")
|
||||||
|
self.addRoleName(Qt.UserRole + 13, "adhesion_info")
|
||||||
|
self.addRoleName(Qt.UserRole + 14, "is_read_only")
|
||||||
|
self.addRoleName(Qt.UserRole + 15, "container_node")
|
||||||
|
self.addRoleName(Qt.UserRole + 16, "is_favorite")
|
||||||
|
|
||||||
self._extruder_position = 0
|
self._extruder_position = 0
|
||||||
self._extruder_stack = None
|
self._extruder_stack = None
|
||||||
# Update the stack and the model data when the machine changes
|
|
||||||
self._machine_manager.globalContainerChanged.connect(self._updateExtruderStack)
|
self._available_materials = None
|
||||||
|
self._favorite_ids = None
|
||||||
|
|
||||||
def _updateExtruderStack(self):
|
def _updateExtruderStack(self):
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
@ -65,8 +80,55 @@ class BaseMaterialsModel(ListModel):
|
||||||
def extruderPosition(self) -> int:
|
def extruderPosition(self) -> int:
|
||||||
return self._extruder_position
|
return self._extruder_position
|
||||||
|
|
||||||
#
|
## This is an abstract method that needs to be implemented by the specific
|
||||||
# This is an abstract method that needs to be implemented by
|
# models themselves.
|
||||||
#
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
## This method is used by all material models in the beginning of the
|
||||||
|
# _update() method in order to prevent errors. It's the same in all models
|
||||||
|
# so it's placed here for easy access.
|
||||||
|
def _canUpdate(self):
|
||||||
|
global_stack = self._machine_manager.activeMachine
|
||||||
|
|
||||||
|
if global_stack is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
extruder_position = str(self._extruder_position)
|
||||||
|
|
||||||
|
if extruder_position not in global_stack.extruders:
|
||||||
|
return False
|
||||||
|
|
||||||
|
extruder_stack = global_stack.extruders[extruder_position]
|
||||||
|
|
||||||
|
self._available_materials = self._material_manager.getAvailableMaterialsForMachineExtruder(global_stack, extruder_stack)
|
||||||
|
if self._available_materials is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
## This is another convenience function which is shared by all material
|
||||||
|
# models so it's put here to avoid having so much duplicated code.
|
||||||
|
def _createMaterialItem(self, root_material_id, container_node):
|
||||||
|
metadata = container_node.getMetadata()
|
||||||
|
item = {
|
||||||
|
"root_material_id": root_material_id,
|
||||||
|
"id": metadata["id"],
|
||||||
|
"container_id": metadata["id"], # TODO: Remove duplicate in material manager qml
|
||||||
|
"GUID": metadata["GUID"],
|
||||||
|
"name": metadata["name"],
|
||||||
|
"brand": metadata["brand"],
|
||||||
|
"description": metadata["description"],
|
||||||
|
"material": metadata["material"],
|
||||||
|
"color_name": metadata["color_name"],
|
||||||
|
"color_code": metadata.get("color_code", ""),
|
||||||
|
"density": metadata.get("properties", {}).get("density", ""),
|
||||||
|
"diameter": metadata.get("properties", {}).get("diameter", ""),
|
||||||
|
"approximate_diameter": metadata["approximate_diameter"],
|
||||||
|
"adhesion_info": metadata["adhesion_info"],
|
||||||
|
"is_read_only": self._container_registry.isReadOnly(metadata["id"]),
|
||||||
|
"container_node": container_node,
|
||||||
|
"is_favorite": root_material_id in self._favorite_ids
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
|
||||||
|
|
|
@ -1,157 +0,0 @@
|
||||||
# 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 UM.Logger import Logger
|
|
||||||
from cura.Machines.Models.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
|
|
||||||
self._extruder_stack = None
|
|
||||||
|
|
||||||
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._updateExtruderStack)
|
|
||||||
self._machine_manager.activeStackChanged.connect(self._update) #Update when switching machines.
|
|
||||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def _updateExtruderStack(self):
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
|
||||||
if global_stack is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
if self._extruder_stack is not None:
|
|
||||||
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
|
|
||||||
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
|
|
||||||
if self._extruder_stack is not None:
|
|
||||||
self._extruder_stack.pyqtContainersChanged.connect(self._update)
|
|
||||||
# Force update the model when the extruder stack changes
|
|
||||||
self._update()
|
|
||||||
|
|
||||||
def setExtruderPosition(self, position: int):
|
|
||||||
if self._extruder_stack is None or self._extruder_position != position:
|
|
||||||
self._extruder_position = position
|
|
||||||
self._updateExtruderStack()
|
|
||||||
self.extruderPositionChanged.emit()
|
|
||||||
|
|
||||||
@pyqtProperty(int, fset=setExtruderPosition, notify=extruderPositionChanged)
|
|
||||||
def extruderPosition(self) -> int:
|
|
||||||
return self._extruder_position
|
|
||||||
|
|
||||||
def _update(self):
|
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
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
|
|
||||||
|
|
||||||
# Do not include the materials from a to-be-removed package
|
|
||||||
if bool(metadata.get("removed", False)):
|
|
||||||
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()
|
|
||||||
|
|
||||||
# Sort materials by name
|
|
||||||
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
|
||||||
material_type_item["colors"].setItems(material_list)
|
|
||||||
|
|
||||||
material_type_item_list.append(material_type_item)
|
|
||||||
|
|
||||||
# Sort material type by name
|
|
||||||
material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
|
|
||||||
brand_item["materials"].setItems(material_type_item_list)
|
|
||||||
|
|
||||||
brand_item_list.append(brand_item)
|
|
||||||
|
|
||||||
# Sort brand by name
|
|
||||||
brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
|
|
||||||
self.setItems(brand_item_list)
|
|
42
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
42
cura/Machines/Models/FavoriteMaterialsModel.py
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
# 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.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
class FavoriteMaterialsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
|
||||||
|
# Perform standard check and reset if the check fails
|
||||||
|
if not self._canUpdate():
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get updated list of favorites
|
||||||
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
|
|
||||||
|
item_list = []
|
||||||
|
|
||||||
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
|
metadata = container_node.getMetadata()
|
||||||
|
|
||||||
|
# Do not include the materials from a to-be-removed package
|
||||||
|
if bool(metadata.get("removed", False)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Only add results for favorite materials
|
||||||
|
if root_material_id not in self._favorite_ids:
|
||||||
|
continue
|
||||||
|
|
||||||
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
|
item_list.append(item)
|
||||||
|
|
||||||
|
# Sort the item list alphabetically by name
|
||||||
|
item_list = sorted(item_list, key = lambda d: d["brand"].upper())
|
||||||
|
|
||||||
|
self.setItems(item_list)
|
|
@ -4,63 +4,39 @@
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
|
||||||
class GenericMaterialsModel(BaseMaterialsModel):
|
class GenericMaterialsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
def __init__(self, parent = None):
|
def __init__(self, parent = None):
|
||||||
super().__init__(parent)
|
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.activeStackChanged.connect(self._update) #Update when switching machines.
|
|
||||||
self._material_manager.materialsUpdated.connect(self._update) #Update when the list of materials changes.
|
|
||||||
self._update()
|
self._update()
|
||||||
|
|
||||||
def _update(self):
|
def _update(self):
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
|
|
||||||
global_stack = self._machine_manager.activeMachine
|
# Perform standard check and reset if the check fails
|
||||||
if global_stack is None:
|
if not self._canUpdate():
|
||||||
self.setItems([])
|
self.setItems([])
|
||||||
return
|
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,
|
# Get updated list of favorites
|
||||||
extruder_stack)
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
if available_material_dict is None:
|
|
||||||
self.setItems([])
|
|
||||||
return
|
|
||||||
|
|
||||||
item_list = []
|
item_list = []
|
||||||
for root_material_id, container_node in available_material_dict.items():
|
|
||||||
metadata = container_node.metadata
|
|
||||||
|
|
||||||
# Only add results for generic materials
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
if metadata["brand"].lower() != "generic":
|
metadata = container_node.getMetadata()
|
||||||
continue
|
|
||||||
|
|
||||||
# Do not include the materials from a to-be-removed package
|
# Do not include the materials from a to-be-removed package
|
||||||
if bool(metadata.get("removed", False)):
|
if bool(metadata.get("removed", False)):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
item = {"root_material_id": root_material_id,
|
# Only add results for generic materials
|
||||||
"id": metadata["id"],
|
if metadata["brand"].lower() != "generic":
|
||||||
"name": metadata["name"],
|
continue
|
||||||
"brand": metadata["brand"],
|
|
||||||
"material": metadata["material"],
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
"color_name": metadata["color_name"],
|
|
||||||
"container_node": container_node
|
|
||||||
}
|
|
||||||
item_list.append(item)
|
item_list.append(item)
|
||||||
|
|
||||||
# Sort the item list by material name alphabetically
|
# Sort the item list alphabetically by name
|
||||||
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
item_list = sorted(item_list, key = lambda d: d["name"].upper())
|
||||||
|
|
||||||
self.setItems(item_list)
|
self.setItems(item_list)
|
||||||
|
|
107
cura/Machines/Models/MaterialBrandsModel.py
Normal file
107
cura/Machines/Models/MaterialBrandsModel.py
Normal file
|
@ -0,0 +1,107 @@
|
||||||
|
# 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 UM.Logger import Logger
|
||||||
|
from cura.Machines.Models.BaseMaterialsModel import BaseMaterialsModel
|
||||||
|
|
||||||
|
class MaterialTypesModel(ListModel):
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "brand")
|
||||||
|
self.addRoleName(Qt.UserRole + 3, "colors")
|
||||||
|
|
||||||
|
class MaterialBrandsModel(BaseMaterialsModel):
|
||||||
|
|
||||||
|
extruderPositionChanged = pyqtSignal()
|
||||||
|
|
||||||
|
def __init__(self, parent = None):
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
self.addRoleName(Qt.UserRole + 1, "name")
|
||||||
|
self.addRoleName(Qt.UserRole + 2, "material_types")
|
||||||
|
|
||||||
|
self._update()
|
||||||
|
|
||||||
|
def _update(self):
|
||||||
|
|
||||||
|
# Perform standard check and reset if the check fails
|
||||||
|
if not self._canUpdate():
|
||||||
|
self.setItems([])
|
||||||
|
return
|
||||||
|
|
||||||
|
# Get updated list of favorites
|
||||||
|
self._favorite_ids = self._material_manager.getFavorites()
|
||||||
|
|
||||||
|
brand_item_list = []
|
||||||
|
brand_group_dict = {}
|
||||||
|
|
||||||
|
# Part 1: Generate the entire tree of brands -> material types -> spcific materials
|
||||||
|
for root_material_id, container_node in self._available_materials.items():
|
||||||
|
# Do not include the materials from a to-be-removed package
|
||||||
|
if bool(container_node.getMetaDataEntry("removed", False)):
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Add brands we haven't seen yet to the dict, skipping generics
|
||||||
|
brand = container_node.getMetaDataEntry("brand", "")
|
||||||
|
if brand.lower() == "generic":
|
||||||
|
continue
|
||||||
|
if brand not in brand_group_dict:
|
||||||
|
brand_group_dict[brand] = {}
|
||||||
|
|
||||||
|
# Add material types we haven't seen yet to the dict
|
||||||
|
material_type = container_node.getMetaDataEntry("material", "")
|
||||||
|
if material_type not in brand_group_dict[brand]:
|
||||||
|
brand_group_dict[brand][material_type] = []
|
||||||
|
|
||||||
|
# Now handle the individual materials
|
||||||
|
item = self._createMaterialItem(root_material_id, container_node)
|
||||||
|
brand_group_dict[brand][material_type].append(item)
|
||||||
|
|
||||||
|
# Part 2: Organize the tree into models
|
||||||
|
#
|
||||||
|
# Normally, the structure of the menu looks like this:
|
||||||
|
# Brand -> Material Type -> Specific Material
|
||||||
|
#
|
||||||
|
# To illustrate, a branded material menu may look like this:
|
||||||
|
# Ultimaker ┳ PLA ┳ Yellow PLA
|
||||||
|
# ┃ ┣ Black PLA
|
||||||
|
# ┃ ┗ ...
|
||||||
|
# ┃
|
||||||
|
# ┗ ABS ┳ White ABS
|
||||||
|
# ┗ ...
|
||||||
|
for brand, material_dict in brand_group_dict.items():
|
||||||
|
|
||||||
|
material_type_item_list = []
|
||||||
|
brand_item = {
|
||||||
|
"name": brand,
|
||||||
|
"material_types": MaterialTypesModel(self)
|
||||||
|
}
|
||||||
|
|
||||||
|
for material_type, material_list in material_dict.items():
|
||||||
|
material_type_item = {
|
||||||
|
"name": material_type,
|
||||||
|
"brand": brand,
|
||||||
|
"colors": BaseMaterialsModel(self)
|
||||||
|
}
|
||||||
|
material_type_item["colors"].clear()
|
||||||
|
|
||||||
|
# Sort materials by name
|
||||||
|
material_list = sorted(material_list, key = lambda x: x["name"].upper())
|
||||||
|
material_type_item["colors"].setItems(material_list)
|
||||||
|
|
||||||
|
material_type_item_list.append(material_type_item)
|
||||||
|
|
||||||
|
# Sort material type by name
|
||||||
|
material_type_item_list = sorted(material_type_item_list, key = lambda x: x["name"].upper())
|
||||||
|
brand_item["material_types"].setItems(material_type_item_list)
|
||||||
|
|
||||||
|
brand_item_list.append(brand_item)
|
||||||
|
|
||||||
|
# Sort brand by name
|
||||||
|
brand_item_list = sorted(brand_item_list, key = lambda x: x["name"].upper())
|
||||||
|
self.setItems(brand_item_list)
|
|
@ -1,104 +0,0 @@
|
||||||
# Copyright (c) 2018 Ultimaker B.V.
|
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
|
||||||
|
|
||||||
from UM.Logger import Logger
|
|
||||||
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):
|
|
||||||
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))
|
|
||||||
|
|
||||||
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"].upper(), k["name"].upper()))
|
|
||||||
self.setItems(material_list)
|
|
|
@ -6,10 +6,10 @@ from PyQt5.QtCore import Qt
|
||||||
from UM.Application import Application
|
from UM.Application import Application
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Qt.ListModel import ListModel
|
from UM.Qt.ListModel import ListModel
|
||||||
|
from UM.Settings.SettingFunction import SettingFunction
|
||||||
|
|
||||||
from cura.Machines.QualityManager import QualityGroup
|
from cura.Machines.QualityManager import QualityGroup
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
# QML Model for all built-in quality profiles. This model is used for the drop-down quality menu.
|
||||||
#
|
#
|
||||||
|
@ -106,4 +106,8 @@ class QualityProfilesDropDownMenuModel(ListModel):
|
||||||
container = global_stack.definition
|
container = global_stack.definition
|
||||||
if container and container.hasProperty("layer_height", "value"):
|
if container and container.hasProperty("layer_height", "value"):
|
||||||
layer_height = container.getProperty("layer_height", "value")
|
layer_height = container.getProperty("layer_height", "value")
|
||||||
|
|
||||||
|
if isinstance(layer_height, SettingFunction):
|
||||||
|
layer_height = layer_height(global_stack)
|
||||||
|
|
||||||
return float(layer_height)
|
return float(layer_height)
|
||||||
|
|
|
@ -58,7 +58,7 @@ class SettingVisibilityPresetsModel(ListModel):
|
||||||
break
|
break
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def _populate(self):
|
def _populate(self) -> None:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
items = []
|
items = []
|
||||||
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
|
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
|
||||||
|
@ -147,7 +147,7 @@ class SettingVisibilityPresetsModel(ListModel):
|
||||||
def activePreset(self) -> str:
|
def activePreset(self) -> str:
|
||||||
return self._active_preset_item["id"]
|
return self._active_preset_item["id"]
|
||||||
|
|
||||||
def _onPreferencesChanged(self, name: str):
|
def _onPreferencesChanged(self, name: str) -> None:
|
||||||
if name != "general/visible_settings":
|
if name != "general/visible_settings":
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -17,16 +17,16 @@ class QualityChangesGroup(QualityGroup):
|
||||||
super().__init__(name, quality_type, parent)
|
super().__init__(name, quality_type, parent)
|
||||||
self._container_registry = Application.getInstance().getContainerRegistry()
|
self._container_registry = Application.getInstance().getContainerRegistry()
|
||||||
|
|
||||||
def addNode(self, node: "QualityNode"):
|
def addNode(self, node: "QualityNode") -> None:
|
||||||
extruder_position = node.getMetaDataEntry("position")
|
extruder_position = node.getMetaDataEntry("position")
|
||||||
|
|
||||||
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
|
if extruder_position is None and self.node_for_global is not None or extruder_position in self.nodes_for_extruders: #We would be overwriting another node.
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
|
ConfigurationErrorMessage.getInstance().addFaultyContainers(node.getMetaDataEntry("id"))
|
||||||
return
|
return
|
||||||
|
|
||||||
if extruder_position is None: #Then we're a global quality changes profile.
|
if extruder_position is None: # Then we're a global quality changes profile.
|
||||||
self.node_for_global = node
|
self.node_for_global = node
|
||||||
else: #This is an extruder's quality changes profile.
|
else: # This is an extruder's quality changes profile.
|
||||||
self.nodes_for_extruders[extruder_position] = node
|
self.nodes_for_extruders[extruder_position] = node
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
|
|
|
@ -6,6 +6,7 @@ from typing import Dict, Optional, List, Set
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
from cura.Machines.ContainerNode import ContainerNode
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# A QualityGroup represents a group of containers that must be applied to each ContainerStack when it's used.
|
# 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
|
# Some concrete examples are Quality and QualityChanges: when we select quality type "normal", this quality type
|
||||||
|
@ -34,7 +35,7 @@ class QualityGroup(QObject):
|
||||||
return self.name
|
return self.name
|
||||||
|
|
||||||
def getAllKeys(self) -> Set[str]:
|
def getAllKeys(self) -> Set[str]:
|
||||||
result = set() #type: Set[str]
|
result = set() # type: Set[str]
|
||||||
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
for node in [self.node_for_global] + list(self.nodes_for_extruders.values()):
|
||||||
if node is None:
|
if node is None:
|
||||||
continue
|
continue
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 typing import TYPE_CHECKING, Optional, cast
|
from typing import TYPE_CHECKING, Optional, cast, Dict, List
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, QTimer, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
|
@ -20,6 +20,8 @@ if TYPE_CHECKING:
|
||||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
from .QualityChangesGroup import QualityChangesGroup
|
from .QualityChangesGroup import QualityChangesGroup
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -36,17 +38,20 @@ class QualityManager(QObject):
|
||||||
|
|
||||||
qualitiesUpdated = pyqtSignal()
|
qualitiesUpdated = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, container_registry, parent = None):
|
def __init__(self, container_registry: "ContainerRegistry", parent = None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._application = Application.getInstance()
|
self._application = Application.getInstance() # type: CuraApplication
|
||||||
self._material_manager = self._application.getMaterialManager()
|
self._material_manager = self._application.getMaterialManager()
|
||||||
self._container_registry = container_registry
|
self._container_registry = container_registry
|
||||||
|
|
||||||
self._empty_quality_container = self._application.empty_quality_container
|
self._empty_quality_container = self._application.empty_quality_container
|
||||||
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
self._empty_quality_changes_container = self._application.empty_quality_changes_container
|
||||||
|
|
||||||
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # for quality lookup
|
# For quality lookup
|
||||||
self._machine_quality_type_to_quality_changes_dict = {} # for quality_changes lookup
|
self._machine_nozzle_buildplate_material_quality_type_to_quality_dict = {} # type: Dict[str, QualityNode]
|
||||||
|
|
||||||
|
# For quality_changes lookup
|
||||||
|
self._machine_quality_type_to_quality_changes_dict = {} # type: Dict[str, QualityNode]
|
||||||
|
|
||||||
self._default_machine_definition_id = "fdmprinter"
|
self._default_machine_definition_id = "fdmprinter"
|
||||||
|
|
||||||
|
@ -62,7 +67,7 @@ class QualityManager(QObject):
|
||||||
self._update_timer.setSingleShot(True)
|
self._update_timer.setSingleShot(True)
|
||||||
self._update_timer.timeout.connect(self._updateMaps)
|
self._update_timer.timeout.connect(self._updateMaps)
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
# Initialize the lookup tree for quality profiles with following structure:
|
# Initialize the lookup tree for quality profiles with following structure:
|
||||||
# <machine> -> <nozzle> -> <buildplate> -> <material>
|
# <machine> -> <nozzle> -> <buildplate> -> <material>
|
||||||
# <machine> -> <material>
|
# <machine> -> <material>
|
||||||
|
@ -133,13 +138,13 @@ class QualityManager(QObject):
|
||||||
Logger.log("d", "Lookup tables updated.")
|
Logger.log("d", "Lookup tables updated.")
|
||||||
self.qualitiesUpdated.emit()
|
self.qualitiesUpdated.emit()
|
||||||
|
|
||||||
def _updateMaps(self):
|
def _updateMaps(self) -> None:
|
||||||
self.initialize()
|
self.initialize()
|
||||||
|
|
||||||
def _onContainerMetadataChanged(self, container):
|
def _onContainerMetadataChanged(self, container: InstanceContainer) -> None:
|
||||||
self._onContainerChanged(container)
|
self._onContainerChanged(container)
|
||||||
|
|
||||||
def _onContainerChanged(self, container):
|
def _onContainerChanged(self, container: InstanceContainer) -> None:
|
||||||
container_type = container.getMetaDataEntry("type")
|
container_type = container.getMetaDataEntry("type")
|
||||||
if container_type not in ("quality", "quality_changes"):
|
if container_type not in ("quality", "quality_changes"):
|
||||||
return
|
return
|
||||||
|
@ -148,7 +153,7 @@ class QualityManager(QObject):
|
||||||
self._update_timer.start()
|
self._update_timer.start()
|
||||||
|
|
||||||
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
|
# Updates the given quality groups' availabilities according to which extruders are being used/ enabled.
|
||||||
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list):
|
def _updateQualityGroupsAvailability(self, machine: "GlobalStack", quality_group_list) -> None:
|
||||||
used_extruders = set()
|
used_extruders = set()
|
||||||
for i in range(machine.getProperty("machine_extruder_count", "value")):
|
for i in range(machine.getProperty("machine_extruder_count", "value")):
|
||||||
if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
|
if str(i) in machine.extruders and machine.extruders[str(i)].isEnabled:
|
||||||
|
@ -196,32 +201,42 @@ class QualityManager(QObject):
|
||||||
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
|
# Whether a QualityGroup is available can be unknown via the field QualityGroup.is_available.
|
||||||
# For more details, see QualityGroup.
|
# For more details, see QualityGroup.
|
||||||
#
|
#
|
||||||
def getQualityGroups(self, machine: "GlobalStack") -> dict:
|
def getQualityGroups(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||||
|
|
||||||
# This determines if we should only get the global qualities for the global stack and skip the global qualities for the extruder stacks
|
# 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))
|
has_machine_specific_qualities = machine.getHasMachineQuality()
|
||||||
|
|
||||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
# (1) the machine-specific node
|
# (1) the machine-specific node
|
||||||
# (2) the generic node
|
# (2) the generic node
|
||||||
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(machine_definition_id)
|
||||||
|
# Check if this machine has specific quality profiles for its extruders, if so, when looking up extruder
|
||||||
|
# qualities, we should not fall back to use the global qualities.
|
||||||
|
has_extruder_specific_qualities = False
|
||||||
|
if machine_node:
|
||||||
|
if machine_node.children_map:
|
||||||
|
has_extruder_specific_qualities = True
|
||||||
|
|
||||||
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
default_machine_node = self._machine_nozzle_buildplate_material_quality_type_to_quality_dict.get(self._default_machine_definition_id)
|
||||||
nodes_to_check = [machine_node, default_machine_node]
|
|
||||||
|
nodes_to_check = [] # type: List[QualityNode]
|
||||||
|
if machine_node is not None:
|
||||||
|
nodes_to_check.append(machine_node)
|
||||||
|
if default_machine_node is not None:
|
||||||
|
nodes_to_check.append(default_machine_node)
|
||||||
|
|
||||||
# Iterate over all quality_types in the machine node
|
# Iterate over all quality_types in the machine node
|
||||||
quality_group_dict = {}
|
quality_group_dict = {}
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
if node and node.quality_type_map:
|
if node and node.quality_type_map:
|
||||||
# Only include global qualities
|
quality_node = list(node.quality_type_map.values())[0]
|
||||||
if has_variant_materials:
|
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||||
quality_node = list(node.quality_type_map.values())[0]
|
if not is_global_quality:
|
||||||
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
continue
|
||||||
if not is_global_quality:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for quality_type, quality_node in node.quality_type_map.items():
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||||
quality_group.node_for_global = quality_node
|
quality_group.node_for_global = quality_node
|
||||||
quality_group_dict[quality_type] = quality_group
|
quality_group_dict[quality_type] = quality_group
|
||||||
break
|
break
|
||||||
|
@ -259,18 +274,25 @@ class QualityManager(QObject):
|
||||||
# 2. machine-nozzle-and-material-specific qualities if exist
|
# 2. machine-nozzle-and-material-specific qualities if exist
|
||||||
# 3. machine-nozzle-specific qualities if exist
|
# 3. machine-nozzle-specific qualities if exist
|
||||||
# 4. machine-material-specific qualities if exist
|
# 4. machine-material-specific qualities if exist
|
||||||
# 5. machine-specific qualities if exist
|
# 5. machine-specific global qualities if exist, otherwise generic global qualities
|
||||||
# 6. generic qualities if exist
|
# NOTE: We DO NOT fail back to generic global qualities if machine-specific global qualities exist.
|
||||||
|
# This is because when a machine defines its own global qualities such as Normal, Fine, etc.,
|
||||||
|
# it is intended to maintain those specific qualities ONLY. If we still fail back to the generic
|
||||||
|
# global qualities, there can be unimplemented quality types e.g. "coarse", and this is not
|
||||||
|
# correct.
|
||||||
# Each points above can be represented as a node in the lookup tree, so here we simply put those nodes into
|
# 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
|
# the list with priorities as the order. Later, we just need to loop over each node in this list and fetch
|
||||||
# qualities from there.
|
# qualities from there.
|
||||||
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id]
|
node_info_list_0 = [nozzle_name, buildplate_name, root_material_id] # type: List[Optional[str]]
|
||||||
nodes_to_check = []
|
nodes_to_check = []
|
||||||
|
|
||||||
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
|
# This function tries to recursively find the deepest (the most specific) branch and add those nodes to
|
||||||
# the search list in the order described above. So, by iterating over that search node list, we first look
|
# the search list in the order described above. So, by iterating over that search node list, we first look
|
||||||
# in the more specific branches and then the less specific (generic) ones.
|
# in the more specific branches and then the less specific (generic) ones.
|
||||||
def addNodesToCheck(node, nodes_to_check_list, node_info_list, node_info_idx):
|
def addNodesToCheck(node: Optional[QualityNode], nodes_to_check_list: List[QualityNode], node_info_list, node_info_idx: int) -> None:
|
||||||
|
if node is None:
|
||||||
|
return
|
||||||
|
|
||||||
if node_info_idx < len(node_info_list):
|
if node_info_idx < len(node_info_list):
|
||||||
node_name = node_info_list[node_info_idx]
|
node_name = node_info_list[node_info_idx]
|
||||||
if node_name is not None:
|
if node_name is not None:
|
||||||
|
@ -289,31 +311,44 @@ class QualityManager(QObject):
|
||||||
|
|
||||||
addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
|
addNodesToCheck(machine_node, nodes_to_check, node_info_list_0, 0)
|
||||||
|
|
||||||
nodes_to_check += [machine_node, default_machine_node]
|
# The last fall back will be the global qualities (either from the machine-specific node or the generic
|
||||||
for node in nodes_to_check:
|
# node), but we only use one. For details see the overview comments above.
|
||||||
|
|
||||||
|
if machine_node is not None and machine_node.quality_type_map:
|
||||||
|
nodes_to_check += [machine_node]
|
||||||
|
elif default_machine_node is not None:
|
||||||
|
nodes_to_check += [default_machine_node]
|
||||||
|
|
||||||
|
for node_idx, node in enumerate(nodes_to_check):
|
||||||
if node and node.quality_type_map:
|
if node and node.quality_type_map:
|
||||||
if has_variant_materials:
|
if has_extruder_specific_qualities:
|
||||||
# Only include variant qualities; skip non global qualities
|
# Only include variant qualities; skip non global qualities
|
||||||
quality_node = list(node.quality_type_map.values())[0]
|
quality_node = list(node.quality_type_map.values())[0]
|
||||||
is_global_quality = parseBool(quality_node.metadata.get("global_quality", False))
|
is_global_quality = parseBool(quality_node.getMetaDataEntry("global_quality", False))
|
||||||
if is_global_quality:
|
if is_global_quality:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for quality_type, quality_node in node.quality_type_map.items():
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
if quality_type not in quality_group_dict:
|
if quality_type not in quality_group_dict:
|
||||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||||
quality_group_dict[quality_type] = quality_group
|
quality_group_dict[quality_type] = quality_group
|
||||||
|
|
||||||
quality_group = quality_group_dict[quality_type]
|
quality_group = quality_group_dict[quality_type]
|
||||||
if position not in quality_group.nodes_for_extruders:
|
if position not in quality_group.nodes_for_extruders:
|
||||||
quality_group.nodes_for_extruders[position] = quality_node
|
quality_group.nodes_for_extruders[position] = quality_node
|
||||||
|
|
||||||
|
# If the machine has its own specific qualities, for extruders, it should skip the global qualities
|
||||||
|
# and use the material/variant specific qualities.
|
||||||
|
if has_extruder_specific_qualities:
|
||||||
|
if node_idx == len(nodes_to_check) - 1:
|
||||||
|
break
|
||||||
|
|
||||||
# Update availabilities for each quality group
|
# Update availabilities for each quality group
|
||||||
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
self._updateQualityGroupsAvailability(machine, quality_group_dict.values())
|
||||||
|
|
||||||
return quality_group_dict
|
return quality_group_dict
|
||||||
|
|
||||||
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> dict:
|
def getQualityGroupsForMachineDefinition(self, machine: "GlobalStack") -> Dict[str, QualityGroup]:
|
||||||
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
machine_definition_id = getMachineDefinitionIDForQualitySearch(machine.definition)
|
||||||
|
|
||||||
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
# To find the quality container for the GlobalStack, check in the following fall-back manner:
|
||||||
|
@ -329,7 +364,7 @@ class QualityManager(QObject):
|
||||||
for node in nodes_to_check:
|
for node in nodes_to_check:
|
||||||
if node and node.quality_type_map:
|
if node and node.quality_type_map:
|
||||||
for quality_type, quality_node in node.quality_type_map.items():
|
for quality_type, quality_node in node.quality_type_map.items():
|
||||||
quality_group = QualityGroup(quality_node.metadata["name"], quality_type)
|
quality_group = QualityGroup(quality_node.getMetaDataEntry("name", ""), quality_type)
|
||||||
quality_group.node_for_global = quality_node
|
quality_group.node_for_global = quality_node
|
||||||
quality_group_dict[quality_type] = quality_group
|
quality_group_dict[quality_type] = quality_group
|
||||||
break
|
break
|
||||||
|
@ -351,10 +386,21 @@ class QualityManager(QObject):
|
||||||
# Remove the given quality changes group.
|
# Remove the given quality changes group.
|
||||||
#
|
#
|
||||||
@pyqtSlot(QObject)
|
@pyqtSlot(QObject)
|
||||||
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup"):
|
def removeQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup") -> None:
|
||||||
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
Logger.log("i", "Removing quality changes group [%s]", quality_changes_group.name)
|
||||||
|
removed_quality_changes_ids = set()
|
||||||
for node in quality_changes_group.getAllNodes():
|
for node in quality_changes_group.getAllNodes():
|
||||||
self._container_registry.removeContainer(node.getMetaDataEntry("id"))
|
container_id = node.getMetaDataEntry("id")
|
||||||
|
self._container_registry.removeContainer(container_id)
|
||||||
|
removed_quality_changes_ids.add(container_id)
|
||||||
|
|
||||||
|
# Reset all machines that have activated this quality changes to empty.
|
||||||
|
for global_stack in self._container_registry.findContainerStacks(type = "machine"):
|
||||||
|
if global_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||||
|
global_stack.qualityChanges = self._empty_quality_changes_container
|
||||||
|
for extruder_stack in self._container_registry.findContainerStacks(type = "extruder_train"):
|
||||||
|
if extruder_stack.qualityChanges.getId() in removed_quality_changes_ids:
|
||||||
|
extruder_stack.qualityChanges = self._empty_quality_changes_container
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rename a set of quality changes containers. Returns the new name.
|
# Rename a set of quality changes containers. Returns the new name.
|
||||||
|
@ -383,7 +429,7 @@ class QualityManager(QObject):
|
||||||
# Duplicates the given quality.
|
# Duplicates the given quality.
|
||||||
#
|
#
|
||||||
@pyqtSlot(str, "QVariantMap")
|
@pyqtSlot(str, "QVariantMap")
|
||||||
def duplicateQualityChanges(self, quality_changes_name, quality_model_item):
|
def duplicateQualityChanges(self, quality_changes_name: str, quality_model_item) -> None:
|
||||||
global_stack = self._application.getGlobalContainerStack()
|
global_stack = self._application.getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
|
Logger.log("i", "No active global stack, cannot duplicate quality changes.")
|
||||||
|
@ -411,7 +457,7 @@ class QualityManager(QObject):
|
||||||
# the user containers in each stack. These then replace the quality_changes containers in the
|
# the user containers in each stack. These then replace the quality_changes containers in the
|
||||||
# stack and clear the user settings.
|
# stack and clear the user settings.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def createQualityChanges(self, base_name):
|
def createQualityChanges(self, base_name: str) -> None:
|
||||||
machine_manager = Application.getInstance().getMachineManager()
|
machine_manager = Application.getInstance().getMachineManager()
|
||||||
|
|
||||||
global_stack = machine_manager.activeMachine
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# 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 typing import Optional, Dict, cast
|
from typing import Optional, Dict, cast, Any
|
||||||
|
|
||||||
from .ContainerNode import ContainerNode
|
from .ContainerNode import ContainerNode
|
||||||
from .QualityChangesGroup import QualityChangesGroup
|
from .QualityChangesGroup import QualityChangesGroup
|
||||||
|
@ -12,18 +12,21 @@ from .QualityChangesGroup import QualityChangesGroup
|
||||||
#
|
#
|
||||||
class QualityNode(ContainerNode):
|
class QualityNode(ContainerNode):
|
||||||
|
|
||||||
def __init__(self, metadata: Optional[dict] = None) -> None:
|
def __init__(self, metadata: Optional[Dict[str, Any]] = None) -> None:
|
||||||
super().__init__(metadata = metadata)
|
super().__init__(metadata = metadata)
|
||||||
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
|
self.quality_type_map = {} # type: Dict[str, QualityNode] # quality_type -> QualityNode for InstanceContainer
|
||||||
|
|
||||||
def addQualityMetadata(self, quality_type: str, metadata: dict):
|
def getChildNode(self, child_key: str) -> Optional["QualityNode"]:
|
||||||
|
return self.children_map.get(child_key)
|
||||||
|
|
||||||
|
def addQualityMetadata(self, quality_type: str, metadata: Dict[str, Any]):
|
||||||
if quality_type not in self.quality_type_map:
|
if quality_type not in self.quality_type_map:
|
||||||
self.quality_type_map[quality_type] = QualityNode(metadata)
|
self.quality_type_map[quality_type] = QualityNode(metadata)
|
||||||
|
|
||||||
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
|
def getQualityNode(self, quality_type: str) -> Optional["QualityNode"]:
|
||||||
return self.quality_type_map.get(quality_type)
|
return self.quality_type_map.get(quality_type)
|
||||||
|
|
||||||
def addQualityChangesMetadata(self, quality_type: str, metadata: dict):
|
def addQualityChangesMetadata(self, quality_type: str, metadata: Dict[str, Any]):
|
||||||
if quality_type not in self.quality_type_map:
|
if quality_type not in self.quality_type_map:
|
||||||
self.quality_type_map[quality_type] = QualityNode()
|
self.quality_type_map[quality_type] = QualityNode()
|
||||||
quality_type_node = self.quality_type_map[quality_type]
|
quality_type_node = self.quality_type_map[quality_type]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING, Dict
|
||||||
|
|
||||||
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -36,11 +36,11 @@ if TYPE_CHECKING:
|
||||||
#
|
#
|
||||||
class VariantManager:
|
class VariantManager:
|
||||||
|
|
||||||
def __init__(self, container_registry):
|
def __init__(self, container_registry: ContainerRegistry) -> None:
|
||||||
self._container_registry = container_registry # type: ContainerRegistry
|
self._container_registry = container_registry
|
||||||
|
|
||||||
self._machine_to_variant_dict_map = dict() # <machine_type> -> <variant_dict>
|
self._machine_to_variant_dict_map = dict() # type: Dict[str, Dict["VariantType", Dict[str, ContainerNode]]]
|
||||||
self._machine_to_buildplate_dict_map = dict()
|
self._machine_to_buildplate_dict_map = dict() # type: Dict[str, Dict[str, ContainerNode]]
|
||||||
|
|
||||||
self._exclude_variant_id_list = ["empty_variant"]
|
self._exclude_variant_id_list = ["empty_variant"]
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ class VariantManager:
|
||||||
# Initializes the VariantManager including:
|
# Initializes the VariantManager including:
|
||||||
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
|
# - initializing the variant lookup table based on the metadata in ContainerRegistry.
|
||||||
#
|
#
|
||||||
def initialize(self):
|
def initialize(self) -> None:
|
||||||
self._machine_to_variant_dict_map = OrderedDict()
|
self._machine_to_variant_dict_map = OrderedDict()
|
||||||
self._machine_to_buildplate_dict_map = OrderedDict()
|
self._machine_to_buildplate_dict_map = OrderedDict()
|
||||||
|
|
||||||
|
@ -106,10 +106,10 @@ class VariantManager:
|
||||||
variant_node = variant_dict[variant_name]
|
variant_node = variant_dict[variant_name]
|
||||||
break
|
break
|
||||||
return variant_node
|
return variant_node
|
||||||
|
|
||||||
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
|
return self._machine_to_variant_dict_map[machine_definition_id].get(variant_type, {}).get(variant_name)
|
||||||
|
|
||||||
def getVariantNodes(self, machine: "GlobalStack",
|
def getVariantNodes(self, machine: "GlobalStack", variant_type: "VariantType") -> Dict[str, ContainerNode]:
|
||||||
variant_type: Optional["VariantType"] = None) -> dict:
|
|
||||||
machine_definition_id = machine.definition.getId()
|
machine_definition_id = machine.definition.getId()
|
||||||
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
|
return self._machine_to_variant_dict_map.get(machine_definition_id, {}).get(variant_type, {})
|
||||||
|
|
||||||
|
|
|
@ -300,7 +300,7 @@ class PrintInformation(QObject):
|
||||||
|
|
||||||
def _updateJobName(self):
|
def _updateJobName(self):
|
||||||
if self._base_name == "":
|
if self._base_name == "":
|
||||||
self._job_name = "unnamed"
|
self._job_name = "Untitled"
|
||||||
self._is_user_specified_job_name = False
|
self._is_user_specified_job_name = False
|
||||||
self.jobNameChanged.emit()
|
self.jobNameChanged.emit()
|
||||||
return
|
return
|
||||||
|
|
27
cura/PrintJobPreviewImageProvider.py
Normal file
27
cura/PrintJobPreviewImageProvider.py
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
from PyQt5.QtGui import QImage
|
||||||
|
from PyQt5.QtQuick import QQuickImageProvider
|
||||||
|
from PyQt5.QtCore import QSize
|
||||||
|
|
||||||
|
from UM.Application import Application
|
||||||
|
|
||||||
|
|
||||||
|
class PrintJobPreviewImageProvider(QQuickImageProvider):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__(QQuickImageProvider.Image)
|
||||||
|
|
||||||
|
## Request a new image.
|
||||||
|
def requestImage(self, id: str, size: QSize) -> QImage:
|
||||||
|
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
|
||||||
|
# increment, we need to strip that first.
|
||||||
|
uuid = id[id.find("/") + 1:]
|
||||||
|
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||||
|
if not hasattr(output_device, "printJobs"):
|
||||||
|
continue
|
||||||
|
|
||||||
|
for print_job in output_device.printJobs:
|
||||||
|
if print_job.key == uuid:
|
||||||
|
if print_job.getPreviewImage():
|
||||||
|
return print_job.getPreviewImage(), QSize(15, 15)
|
||||||
|
else:
|
||||||
|
return QImage(), QSize(15, 15)
|
||||||
|
return QImage(), QSize(15,15)
|
|
@ -13,36 +13,42 @@ class ConfigurationModel(QObject):
|
||||||
|
|
||||||
configurationChanged = pyqtSignal()
|
configurationChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._printer_type = None
|
self._printer_type = ""
|
||||||
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
|
self._extruder_configurations = [] # type: List[ExtruderConfigurationModel]
|
||||||
self._buildplate_configuration = None
|
self._buildplate_configuration = ""
|
||||||
|
|
||||||
def setPrinterType(self, printer_type):
|
def setPrinterType(self, printer_type):
|
||||||
self._printer_type = printer_type
|
self._printer_type = printer_type
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
|
@pyqtProperty(str, fset = setPrinterType, notify = configurationChanged)
|
||||||
def printerType(self):
|
def printerType(self) -> str:
|
||||||
return self._printer_type
|
return self._printer_type
|
||||||
|
|
||||||
def setExtruderConfigurations(self, extruder_configurations):
|
def setExtruderConfigurations(self, extruder_configurations: List["ExtruderConfigurationModel"]):
|
||||||
self._extruder_configurations = extruder_configurations
|
if self._extruder_configurations != extruder_configurations:
|
||||||
|
self._extruder_configurations = extruder_configurations
|
||||||
|
|
||||||
|
for extruder_configuration in self._extruder_configurations:
|
||||||
|
extruder_configuration.extruderConfigurationChanged.connect(self.configurationChanged)
|
||||||
|
|
||||||
|
self.configurationChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
|
@pyqtProperty("QVariantList", fset = setExtruderConfigurations, notify = configurationChanged)
|
||||||
def extruderConfigurations(self):
|
def extruderConfigurations(self):
|
||||||
return self._extruder_configurations
|
return self._extruder_configurations
|
||||||
|
|
||||||
def setBuildplateConfiguration(self, buildplate_configuration):
|
def setBuildplateConfiguration(self, buildplate_configuration: str) -> None:
|
||||||
self._buildplate_configuration = buildplate_configuration
|
self._buildplate_configuration = buildplate_configuration
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
@pyqtProperty(str, fset = setBuildplateConfiguration, notify = configurationChanged)
|
||||||
def buildplateConfiguration(self):
|
def buildplateConfiguration(self) -> str:
|
||||||
return self._buildplate_configuration
|
return self._buildplate_configuration
|
||||||
|
|
||||||
## This method is intended to indicate whether the configuration is valid or not.
|
## This method is intended to indicate whether the configuration is valid or not.
|
||||||
# The method checks if the mandatory fields are or not set
|
# The method checks if the mandatory fields are or not set
|
||||||
def isValid(self):
|
def isValid(self) -> bool:
|
||||||
if not self._extruder_configurations:
|
if not self._extruder_configurations:
|
||||||
return False
|
return False
|
||||||
for configuration in self._extruder_configurations:
|
for configuration in self._extruder_configurations:
|
||||||
|
|
|
@ -1,56 +1,67 @@
|
||||||
# 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 typing import Optional
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||||
|
|
||||||
|
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
|
||||||
|
|
||||||
|
|
||||||
class ExtruderConfigurationModel(QObject):
|
class ExtruderConfigurationModel(QObject):
|
||||||
|
|
||||||
extruderConfigurationChanged = pyqtSignal()
|
extruderConfigurationChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, position: int = -1) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._position = -1
|
self._position = position # type: int
|
||||||
self._material = None
|
self._material = None # type: Optional[MaterialOutputModel]
|
||||||
self._hotend_id = None
|
self._hotend_id = None # type: Optional[str]
|
||||||
|
|
||||||
def setPosition(self, position):
|
def setPosition(self, position: int) -> None:
|
||||||
self._position = position
|
self._position = position
|
||||||
|
|
||||||
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
@pyqtProperty(int, fset = setPosition, notify = extruderConfigurationChanged)
|
||||||
def position(self):
|
def position(self) -> int:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
def setMaterial(self, material):
|
def setMaterial(self, material: Optional[MaterialOutputModel]) -> None:
|
||||||
self._material = material
|
if self._hotend_id != material:
|
||||||
|
self._material = material
|
||||||
|
self.extruderConfigurationChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
@pyqtProperty(QObject, fset = setMaterial, notify = extruderConfigurationChanged)
|
||||||
def material(self):
|
def activeMaterial(self) -> Optional[MaterialOutputModel]:
|
||||||
return self._material
|
return self._material
|
||||||
|
|
||||||
def setHotendID(self, hotend_id):
|
@pyqtProperty(QObject, fset=setMaterial, notify=extruderConfigurationChanged)
|
||||||
self._hotend_id = hotend_id
|
def material(self) -> Optional[MaterialOutputModel]:
|
||||||
|
return self._material
|
||||||
|
|
||||||
|
def setHotendID(self, hotend_id: Optional[str]) -> None:
|
||||||
|
if self._hotend_id != hotend_id:
|
||||||
|
self._hotend_id = hotend_id
|
||||||
|
self.extruderConfigurationChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
|
@pyqtProperty(str, fset = setHotendID, notify = extruderConfigurationChanged)
|
||||||
def hotendID(self):
|
def hotendID(self) -> Optional[str]:
|
||||||
return self._hotend_id
|
return self._hotend_id
|
||||||
|
|
||||||
## This method is intended to indicate whether the configuration is valid or not.
|
## This method is intended to indicate whether the configuration is valid or not.
|
||||||
# The method checks if the mandatory fields are or not set
|
# The method checks if the mandatory fields are or not set
|
||||||
# At this moment is always valid since we allow to have empty material and variants.
|
# At this moment is always valid since we allow to have empty material and variants.
|
||||||
def isValid(self):
|
def isValid(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
message_chunks = []
|
message_chunks = []
|
||||||
message_chunks.append("Position: " + str(self._position))
|
message_chunks.append("Position: " + str(self._position))
|
||||||
message_chunks.append("-")
|
message_chunks.append("-")
|
||||||
message_chunks.append("Material: " + self.material.type if self.material else "empty")
|
message_chunks.append("Material: " + self.activeMaterial.type if self.activeMaterial else "empty")
|
||||||
message_chunks.append("-")
|
message_chunks.append("-")
|
||||||
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
message_chunks.append("HotendID: " + self.hotendID if self.hotendID else "empty")
|
||||||
return " ".join(message_chunks)
|
return " ".join(message_chunks)
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other) -> bool:
|
||||||
return hash(self) == hash(other)
|
return hash(self) == hash(other)
|
||||||
|
|
||||||
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
|
||||||
|
|
|
@ -12,64 +12,61 @@ if TYPE_CHECKING:
|
||||||
|
|
||||||
|
|
||||||
class ExtruderOutputModel(QObject):
|
class ExtruderOutputModel(QObject):
|
||||||
hotendIDChanged = pyqtSignal()
|
|
||||||
targetHotendTemperatureChanged = pyqtSignal()
|
targetHotendTemperatureChanged = pyqtSignal()
|
||||||
hotendTemperatureChanged = pyqtSignal()
|
hotendTemperatureChanged = pyqtSignal()
|
||||||
activeMaterialChanged = pyqtSignal()
|
|
||||||
extruderConfigurationChanged = pyqtSignal()
|
extruderConfigurationChanged = pyqtSignal()
|
||||||
isPreheatingChanged = pyqtSignal()
|
isPreheatingChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, printer: "PrinterOutputModel", position, parent=None) -> None:
|
def __init__(self, printer: "PrinterOutputModel", position: int, parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
self._printer = printer
|
self._printer = printer # type: PrinterOutputModel
|
||||||
self._position = position
|
self._position = position
|
||||||
self._target_hotend_temperature = 0 # type: float
|
self._target_hotend_temperature = 0.0 # type: float
|
||||||
self._hotend_temperature = 0 # type: float
|
self._hotend_temperature = 0.0 # type: float
|
||||||
self._hotend_id = ""
|
|
||||||
self._active_material = None # type: Optional[MaterialOutputModel]
|
|
||||||
self._extruder_configuration = ExtruderConfigurationModel()
|
|
||||||
self._extruder_configuration.position = self._position
|
|
||||||
|
|
||||||
self._is_preheating = False
|
self._is_preheating = False
|
||||||
|
|
||||||
def getPrinter(self):
|
# The extruder output model wraps the configuration model. This way we can use the same config model for jobs
|
||||||
|
# and extruders alike.
|
||||||
|
self._extruder_configuration = ExtruderConfigurationModel()
|
||||||
|
self._extruder_configuration.position = self._position
|
||||||
|
self._extruder_configuration.extruderConfigurationChanged.connect(self.extruderConfigurationChanged)
|
||||||
|
|
||||||
|
def getPrinter(self) -> "PrinterOutputModel":
|
||||||
return self._printer
|
return self._printer
|
||||||
|
|
||||||
def getPosition(self):
|
def getPosition(self) -> int:
|
||||||
return self._position
|
return self._position
|
||||||
|
|
||||||
# Does the printer support pre-heating the bed at all
|
# Does the printer support pre-heating the bed at all
|
||||||
@pyqtProperty(bool, constant=True)
|
@pyqtProperty(bool, constant=True)
|
||||||
def canPreHeatHotends(self):
|
def canPreHeatHotends(self) -> bool:
|
||||||
if self._printer:
|
if self._printer:
|
||||||
return self._printer.canPreHeatHotends
|
return self._printer.canPreHeatHotends
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = activeMaterialChanged)
|
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||||
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
def activeMaterial(self) -> Optional["MaterialOutputModel"]:
|
||||||
return self._active_material
|
return self._extruder_configuration.activeMaterial
|
||||||
|
|
||||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]):
|
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
|
||||||
if self._active_material != material:
|
self._extruder_configuration.setMaterial(material)
|
||||||
self._active_material = material
|
|
||||||
self._extruder_configuration.material = self._active_material
|
|
||||||
self.activeMaterialChanged.emit()
|
|
||||||
self.extruderConfigurationChanged.emit()
|
|
||||||
|
|
||||||
## Update the hotend temperature. This only changes it locally.
|
## Update the hotend temperature. This only changes it locally.
|
||||||
def updateHotendTemperature(self, temperature: float):
|
def updateHotendTemperature(self, temperature: float) -> None:
|
||||||
if self._hotend_temperature != temperature:
|
if self._hotend_temperature != temperature:
|
||||||
self._hotend_temperature = temperature
|
self._hotend_temperature = temperature
|
||||||
self.hotendTemperatureChanged.emit()
|
self.hotendTemperatureChanged.emit()
|
||||||
|
|
||||||
def updateTargetHotendTemperature(self, temperature: float):
|
def updateTargetHotendTemperature(self, temperature: float) -> None:
|
||||||
if self._target_hotend_temperature != temperature:
|
if self._target_hotend_temperature != temperature:
|
||||||
self._target_hotend_temperature = temperature
|
self._target_hotend_temperature = temperature
|
||||||
self.targetHotendTemperatureChanged.emit()
|
self.targetHotendTemperatureChanged.emit()
|
||||||
|
|
||||||
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
||||||
@pyqtSlot(float)
|
@pyqtSlot(float)
|
||||||
def setTargetHotendTemperature(self, temperature: float):
|
def setTargetHotendTemperature(self, temperature: float) -> None:
|
||||||
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
||||||
self.updateTargetHotendTemperature(temperature)
|
self.updateTargetHotendTemperature(temperature)
|
||||||
|
|
||||||
|
@ -81,30 +78,26 @@ class ExtruderOutputModel(QObject):
|
||||||
def hotendTemperature(self) -> float:
|
def hotendTemperature(self) -> float:
|
||||||
return self._hotend_temperature
|
return self._hotend_temperature
|
||||||
|
|
||||||
@pyqtProperty(str, notify = hotendIDChanged)
|
@pyqtProperty(str, notify = extruderConfigurationChanged)
|
||||||
def hotendID(self) -> str:
|
def hotendID(self) -> str:
|
||||||
return self._hotend_id
|
return self._extruder_configuration.hotendID
|
||||||
|
|
||||||
def updateHotendID(self, id: str):
|
def updateHotendID(self, hotend_id: str) -> None:
|
||||||
if self._hotend_id != id:
|
self._extruder_configuration.setHotendID(hotend_id)
|
||||||
self._hotend_id = id
|
|
||||||
self._extruder_configuration.hotendID = self._hotend_id
|
|
||||||
self.hotendIDChanged.emit()
|
|
||||||
self.extruderConfigurationChanged.emit()
|
|
||||||
|
|
||||||
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
@pyqtProperty(QObject, notify = extruderConfigurationChanged)
|
||||||
def extruderConfiguration(self):
|
def extruderConfiguration(self) -> Optional[ExtruderConfigurationModel]:
|
||||||
if self._extruder_configuration.isValid():
|
if self._extruder_configuration.isValid():
|
||||||
return self._extruder_configuration
|
return self._extruder_configuration
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def updateIsPreheating(self, pre_heating):
|
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||||
if self._is_preheating != pre_heating:
|
if self._is_preheating != pre_heating:
|
||||||
self._is_preheating = pre_heating
|
self._is_preheating = pre_heating
|
||||||
self.isPreheatingChanged.emit()
|
self.isPreheatingChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(bool, notify=isPreheatingChanged)
|
@pyqtProperty(bool, notify=isPreheatingChanged)
|
||||||
def isPreheating(self):
|
def isPreheating(self) -> bool:
|
||||||
return self._is_preheating
|
return self._is_preheating
|
||||||
|
|
||||||
## Pre-heats the extruder before printer.
|
## Pre-heats the extruder before printer.
|
||||||
|
@ -113,9 +106,9 @@ class ExtruderOutputModel(QObject):
|
||||||
# Celsius.
|
# Celsius.
|
||||||
# \param duration How long the bed should stay warm, in seconds.
|
# \param duration How long the bed should stay warm, in seconds.
|
||||||
@pyqtSlot(float, float)
|
@pyqtSlot(float, float)
|
||||||
def preheatHotend(self, temperature, duration):
|
def preheatHotend(self, temperature: float, duration: float) -> None:
|
||||||
self._printer._controller.preheatHotend(self, temperature, duration)
|
self._printer._controller.preheatHotend(self, temperature, duration)
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def cancelPreheatHotend(self):
|
def cancelPreheatHotend(self) -> None:
|
||||||
self._printer._controller.cancelPreheatHotend(self)
|
self._printer._controller.cancelPreheatHotend(self)
|
||||||
|
|
|
@ -53,21 +53,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
self._sending_gcode = False
|
self._sending_gcode = False
|
||||||
self._compressing_gcode = False
|
self._compressing_gcode = False
|
||||||
self._gcode = [] # type: List[str]
|
self._gcode = [] # type: List[str]
|
||||||
|
|
||||||
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
|
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
|
||||||
|
|
||||||
printer_type = self._properties.get(b"machine", b"").decode("utf-8")
|
|
||||||
printer_type_identifiers = {
|
|
||||||
"9066": "ultimaker3",
|
|
||||||
"9511": "ultimaker3_extended",
|
|
||||||
"9051": "ultimaker_s5"
|
|
||||||
}
|
|
||||||
self._printer_type = "Unknown"
|
|
||||||
for key, value in printer_type_identifiers.items():
|
|
||||||
if printer_type.startswith(key):
|
|
||||||
self._printer_type = value
|
|
||||||
break
|
|
||||||
|
|
||||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||||
raise NotImplementedError("requestWrite needs to be implemented")
|
raise NotImplementedError("requestWrite needs to be implemented")
|
||||||
|
|
||||||
|
@ -188,40 +175,55 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
if reply in self._kept_alive_multiparts:
|
if reply in self._kept_alive_multiparts:
|
||||||
del self._kept_alive_multiparts[reply]
|
del self._kept_alive_multiparts[reply]
|
||||||
|
|
||||||
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
def _validateManager(self) -> None:
|
||||||
if self._manager is None:
|
if self._manager is None:
|
||||||
self._createNetworkManager()
|
self._createNetworkManager()
|
||||||
assert (self._manager is not None)
|
assert (self._manager is not None)
|
||||||
|
|
||||||
|
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
|
self._validateManager()
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.put(request, data.encode())
|
if self._manager is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
reply = self._manager.put(request, data.encode())
|
||||||
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Could not find manager.")
|
||||||
|
|
||||||
|
def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
|
self._validateManager()
|
||||||
|
request = self._createEmptyRequest(target)
|
||||||
|
self._last_request_time = time()
|
||||||
|
if self._manager is not None:
|
||||||
|
reply = self._manager.deleteResource(request)
|
||||||
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Could not find manager.")
|
||||||
|
|
||||||
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||||
if self._manager is None:
|
self._validateManager()
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.get(request)
|
if self._manager is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
reply = self._manager.get(request)
|
||||||
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Could not find manager.")
|
||||||
|
|
||||||
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
if self._manager is None:
|
self._validateManager()
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target)
|
request = self._createEmptyRequest(target)
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
reply = self._manager.post(request, data)
|
if self._manager is not None:
|
||||||
if on_progress is not None:
|
reply = self._manager.post(request, data)
|
||||||
reply.uploadProgress.connect(on_progress)
|
if on_progress is not None:
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
reply.uploadProgress.connect(on_progress)
|
||||||
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Could not find manager.")
|
||||||
|
|
||||||
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
|
||||||
|
self._validateManager()
|
||||||
if self._manager is None:
|
|
||||||
self._createNetworkManager()
|
|
||||||
assert (self._manager is not None)
|
|
||||||
request = self._createEmptyRequest(target, content_type=None)
|
request = self._createEmptyRequest(target, content_type=None)
|
||||||
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)
|
||||||
for part in parts:
|
for part in parts:
|
||||||
|
@ -229,15 +231,18 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
self._last_request_time = time()
|
self._last_request_time = time()
|
||||||
|
|
||||||
reply = self._manager.post(request, multi_post_part)
|
if self._manager is not None:
|
||||||
|
reply = self._manager.post(request, multi_post_part)
|
||||||
|
|
||||||
self._kept_alive_multiparts[reply] = multi_post_part
|
self._kept_alive_multiparts[reply] = multi_post_part
|
||||||
|
|
||||||
if on_progress is not None:
|
if on_progress is not None:
|
||||||
reply.uploadProgress.connect(on_progress)
|
reply.uploadProgress.connect(on_progress)
|
||||||
self._registerOnFinishedCallback(reply, on_finished)
|
self._registerOnFinishedCallback(reply, on_finished)
|
||||||
|
|
||||||
return reply
|
return reply
|
||||||
|
else:
|
||||||
|
Logger.log("e", "Could not find manager.")
|
||||||
|
|
||||||
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
def postForm(self, target: str, header_data: str, body_data: bytes, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
|
||||||
post_part = QHttpPart()
|
post_part = QHttpPart()
|
||||||
|
@ -323,7 +328,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||||
|
|
||||||
@pyqtProperty(str, constant = True)
|
@pyqtProperty(str, constant = True)
|
||||||
def printerType(self) -> str:
|
def printerType(self) -> str:
|
||||||
return self._printer_type
|
return self._properties.get(b"printer_type", b"Unknown").decode("utf-8")
|
||||||
|
|
||||||
## IP adress of this printer
|
## IP adress of this printer
|
||||||
@pyqtProperty(str, constant = True)
|
@pyqtProperty(str, constant = True)
|
||||||
|
|
|
@ -2,11 +2,15 @@
|
||||||
# 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 pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
|
||||||
from typing import Optional, TYPE_CHECKING
|
from typing import Optional, TYPE_CHECKING, List
|
||||||
|
|
||||||
|
from PyQt5.QtCore import QUrl
|
||||||
|
from PyQt5.QtGui import QImage
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||||
|
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||||
|
|
||||||
|
|
||||||
class PrintJobOutputModel(QObject):
|
class PrintJobOutputModel(QObject):
|
||||||
|
@ -17,6 +21,9 @@ class PrintJobOutputModel(QObject):
|
||||||
keyChanged = pyqtSignal()
|
keyChanged = pyqtSignal()
|
||||||
assignedPrinterChanged = pyqtSignal()
|
assignedPrinterChanged = pyqtSignal()
|
||||||
ownerChanged = pyqtSignal()
|
ownerChanged = pyqtSignal()
|
||||||
|
configurationChanged = pyqtSignal()
|
||||||
|
previewImageChanged = pyqtSignal()
|
||||||
|
compatibleMachineFamiliesChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
|
||||||
super().__init__(parent)
|
super().__init__(parent)
|
||||||
|
@ -29,6 +36,48 @@ class PrintJobOutputModel(QObject):
|
||||||
self._assigned_printer = None # type: Optional[PrinterOutputModel]
|
self._assigned_printer = None # type: Optional[PrinterOutputModel]
|
||||||
self._owner = "" # Who started/owns the print job?
|
self._owner = "" # Who started/owns the print job?
|
||||||
|
|
||||||
|
self._configuration = None # type: Optional[ConfigurationModel]
|
||||||
|
self._compatible_machine_families = [] # type: List[str]
|
||||||
|
self._preview_image_id = 0
|
||||||
|
|
||||||
|
self._preview_image = None # type: Optional[QImage]
|
||||||
|
|
||||||
|
@pyqtProperty("QStringList", notify=compatibleMachineFamiliesChanged)
|
||||||
|
def compatibleMachineFamilies(self):
|
||||||
|
# Hack; Some versions of cluster will return a family more than once...
|
||||||
|
return set(self._compatible_machine_families)
|
||||||
|
|
||||||
|
def setCompatibleMachineFamilies(self, compatible_machine_families: List[str]) -> None:
|
||||||
|
if self._compatible_machine_families != compatible_machine_families:
|
||||||
|
self._compatible_machine_families = compatible_machine_families
|
||||||
|
self.compatibleMachineFamiliesChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(QUrl, notify=previewImageChanged)
|
||||||
|
def previewImageUrl(self):
|
||||||
|
self._preview_image_id += 1
|
||||||
|
# There is an image provider that is called "camera". In order to ensure that the image qml object, that
|
||||||
|
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
|
||||||
|
# as new (instead of relying on cached version and thus forces an update.
|
||||||
|
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
|
||||||
|
return QUrl(temp, QUrl.TolerantMode)
|
||||||
|
|
||||||
|
def getPreviewImage(self) -> Optional[QImage]:
|
||||||
|
return self._preview_image
|
||||||
|
|
||||||
|
def updatePreviewImage(self, preview_image: Optional[QImage]) -> None:
|
||||||
|
if self._preview_image != preview_image:
|
||||||
|
self._preview_image = preview_image
|
||||||
|
self.previewImageChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(QObject, notify=configurationChanged)
|
||||||
|
def configuration(self) -> Optional["ConfigurationModel"]:
|
||||||
|
return self._configuration
|
||||||
|
|
||||||
|
def updateConfiguration(self, configuration: Optional["ConfigurationModel"]) -> None:
|
||||||
|
if self._configuration != configuration:
|
||||||
|
self._configuration = configuration
|
||||||
|
self.configurationChanged.emit()
|
||||||
|
|
||||||
@pyqtProperty(str, notify=ownerChanged)
|
@pyqtProperty(str, notify=ownerChanged)
|
||||||
def owner(self):
|
def owner(self):
|
||||||
return self._owner
|
return self._owner
|
||||||
|
|
|
@ -37,7 +37,7 @@ class PrinterOutputModel(QObject):
|
||||||
self._controller = output_controller
|
self._controller = output_controller
|
||||||
self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
||||||
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
||||||
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
||||||
self._head_position = Vector(0, 0, 0)
|
self._head_position = Vector(0, 0, 0)
|
||||||
self._active_print_job = None # type: Optional[PrintJobOutputModel]
|
self._active_print_job = None # type: Optional[PrintJobOutputModel]
|
||||||
self._firmware_version = firmware_version
|
self._firmware_version = firmware_version
|
||||||
|
@ -45,9 +45,9 @@ class PrinterOutputModel(QObject):
|
||||||
self._is_preheating = False
|
self._is_preheating = False
|
||||||
self._printer_type = ""
|
self._printer_type = ""
|
||||||
self._buildplate_name = None
|
self._buildplate_name = None
|
||||||
# Update the printer configuration every time any of the extruders changes its configuration
|
|
||||||
for extruder in self._extruders:
|
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||||
extruder.extruderConfigurationChanged.connect(self._updateExtruderConfiguration)
|
self._extruders]
|
||||||
|
|
||||||
self._camera = None
|
self._camera = None
|
||||||
|
|
||||||
|
@ -295,8 +295,4 @@ class PrinterOutputModel(QObject):
|
||||||
def printerConfiguration(self):
|
def printerConfiguration(self):
|
||||||
if self._printer_configuration.isValid():
|
if self._printer_configuration.isValid():
|
||||||
return self._printer_configuration
|
return self._printer_configuration
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _updateExtruderConfiguration(self):
|
|
||||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in self._extruders]
|
|
||||||
self.configurationChanged.emit()
|
|
|
@ -13,23 +13,31 @@ from cura.Scene import ConvexHullNode
|
||||||
|
|
||||||
import numpy
|
import numpy
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, Any, Optional
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
|
|
||||||
|
|
||||||
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
|
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
|
||||||
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
|
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
|
||||||
class ConvexHullDecorator(SceneNodeDecorator):
|
class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._convex_hull_node = None
|
self._convex_hull_node = None # type: Optional["SceneNode"]
|
||||||
self._init2DConvexHullCache()
|
self._init2DConvexHullCache()
|
||||||
|
|
||||||
self._global_stack = None
|
self._global_stack = None # type: Optional[GlobalStack]
|
||||||
|
|
||||||
# Make sure the timer is created on the main thread
|
# Make sure the timer is created on the main thread
|
||||||
self._recompute_convex_hull_timer = None
|
self._recompute_convex_hull_timer = None # type: Optional[QTimer]
|
||||||
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
|
|
||||||
|
if Application.getInstance() is not None:
|
||||||
|
Application.getInstance().callLater(self.createRecomputeConvexHullTimer)
|
||||||
|
|
||||||
self._raft_thickness = 0.0
|
self._raft_thickness = 0.0
|
||||||
# For raft thickness, DRY
|
|
||||||
self._build_volume = Application.getInstance().getBuildVolume()
|
self._build_volume = Application.getInstance().getBuildVolume()
|
||||||
self._build_volume.raftThicknessChanged.connect(self._onChanged)
|
self._build_volume.raftThicknessChanged.connect(self._onChanged)
|
||||||
|
|
||||||
|
@ -39,13 +47,13 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
self._onGlobalStackChanged()
|
self._onGlobalStackChanged()
|
||||||
|
|
||||||
def createRecomputeConvexHullTimer(self):
|
def createRecomputeConvexHullTimer(self) -> None:
|
||||||
self._recompute_convex_hull_timer = QTimer()
|
self._recompute_convex_hull_timer = QTimer()
|
||||||
self._recompute_convex_hull_timer.setInterval(200)
|
self._recompute_convex_hull_timer.setInterval(200)
|
||||||
self._recompute_convex_hull_timer.setSingleShot(True)
|
self._recompute_convex_hull_timer.setSingleShot(True)
|
||||||
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
|
self._recompute_convex_hull_timer.timeout.connect(self.recomputeConvexHull)
|
||||||
|
|
||||||
def setNode(self, node):
|
def setNode(self, node: "SceneNode") -> None:
|
||||||
previous_node = self._node
|
previous_node = self._node
|
||||||
# Disconnect from previous node signals
|
# Disconnect from previous node signals
|
||||||
if previous_node is not None and node is not previous_node:
|
if previous_node is not None and node is not previous_node:
|
||||||
|
@ -63,14 +71,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo):
|
||||||
return ConvexHullDecorator()
|
return ConvexHullDecorator()
|
||||||
|
|
||||||
## Get the unmodified 2D projected convex hull of the node
|
## Get the unmodified 2D projected convex hull of the node (if any)
|
||||||
def getConvexHull(self):
|
def getConvexHull(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
hull = self._compute2DConvexHull()
|
hull = self._compute2DConvexHull()
|
||||||
|
|
||||||
if self._global_stack and self._node:
|
if self._global_stack and self._node and hull is not None:
|
||||||
# Parent can be None if node is just loaded.
|
# Parent can be None if node is just loaded.
|
||||||
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
|
if self._global_stack.getProperty("print_sequence", "value") == "one_at_a_time" and (self._node.getParent() is None or not self._node.getParent().callDecoration("isGroup")):
|
||||||
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
|
hull = hull.getMinkowskiHull(Polygon(numpy.array(self._global_stack.getProperty("machine_head_polygon", "value"), numpy.float32)))
|
||||||
|
@ -78,7 +86,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
return hull
|
return hull
|
||||||
|
|
||||||
## Get the convex hull of the node with the full head size
|
## Get the convex hull of the node with the full head size
|
||||||
def getConvexHullHeadFull(self):
|
def getConvexHullHeadFull(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -87,7 +95,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
## Get convex hull of the object + head size
|
## Get convex hull of the object + head size
|
||||||
# In case of printing all at once this is the same as the convex hull.
|
# In case of printing all at once this is the same as the convex hull.
|
||||||
# For one at the time this is area with intersection of mirrored head
|
# For one at the time this is area with intersection of mirrored head
|
||||||
def getConvexHullHead(self):
|
def getConvexHullHead(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -101,7 +109,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
## Get convex hull of the node
|
## Get convex hull of the node
|
||||||
# In case of printing all at once this is the same as the convex hull.
|
# In case of printing all at once this is the same as the convex hull.
|
||||||
# For one at the time this is the area without the head.
|
# For one at the time this is the area without the head.
|
||||||
def getConvexHullBoundary(self):
|
def getConvexHullBoundary(self) -> Optional[Polygon]:
|
||||||
if self._node is None:
|
if self._node is None:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -111,13 +119,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
return self._compute2DConvexHull()
|
return self._compute2DConvexHull()
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def recomputeConvexHullDelayed(self):
|
## The same as recomputeConvexHull, but using a timer if it was set.
|
||||||
|
def recomputeConvexHullDelayed(self) -> None:
|
||||||
if self._recompute_convex_hull_timer is not None:
|
if self._recompute_convex_hull_timer is not None:
|
||||||
self._recompute_convex_hull_timer.start()
|
self._recompute_convex_hull_timer.start()
|
||||||
else:
|
else:
|
||||||
self.recomputeConvexHull()
|
self.recomputeConvexHull()
|
||||||
|
|
||||||
def recomputeConvexHull(self):
|
def recomputeConvexHull(self) -> None:
|
||||||
controller = Application.getInstance().getController()
|
controller = Application.getInstance().getController()
|
||||||
root = controller.getScene().getRoot()
|
root = controller.getScene().getRoot()
|
||||||
if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node):
|
if self._node is None or controller.isToolOperationActive() or not self.__isDescendant(root, self._node):
|
||||||
|
@ -132,17 +141,17 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root)
|
hull_node = ConvexHullNode.ConvexHullNode(self._node, convex_hull, self._raft_thickness, root)
|
||||||
self._convex_hull_node = hull_node
|
self._convex_hull_node = hull_node
|
||||||
|
|
||||||
def _onSettingValueChanged(self, key, property_name):
|
def _onSettingValueChanged(self, key: str, property_name: str) -> None:
|
||||||
if property_name != "value": #Not the value that was changed.
|
if property_name != "value": # Not the value that was changed.
|
||||||
return
|
return
|
||||||
|
|
||||||
if key in self._affected_settings:
|
if key in self._affected_settings:
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
if key in self._influencing_settings:
|
if key in self._influencing_settings:
|
||||||
self._init2DConvexHullCache() #Invalidate the cache.
|
self._init2DConvexHullCache() # Invalidate the cache.
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
def _init2DConvexHullCache(self):
|
def _init2DConvexHullCache(self) -> None:
|
||||||
# Cache for the group code path in _compute2DConvexHull()
|
# Cache for the group code path in _compute2DConvexHull()
|
||||||
self._2d_convex_hull_group_child_polygon = None
|
self._2d_convex_hull_group_child_polygon = None
|
||||||
self._2d_convex_hull_group_result = None
|
self._2d_convex_hull_group_result = None
|
||||||
|
@ -152,7 +161,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
self._2d_convex_hull_mesh_world_transform = None
|
self._2d_convex_hull_mesh_world_transform = None
|
||||||
self._2d_convex_hull_mesh_result = None
|
self._2d_convex_hull_mesh_result = None
|
||||||
|
|
||||||
def _compute2DConvexHull(self):
|
def _compute2DConvexHull(self) -> Optional[Polygon]:
|
||||||
if self._node.callDecoration("isGroup"):
|
if self._node.callDecoration("isGroup"):
|
||||||
points = numpy.zeros((0, 2), dtype=numpy.int32)
|
points = numpy.zeros((0, 2), dtype=numpy.int32)
|
||||||
for child in self._node.getChildren():
|
for child in self._node.getChildren():
|
||||||
|
@ -179,8 +188,6 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
else:
|
else:
|
||||||
offset_hull = None
|
offset_hull = None
|
||||||
mesh = None
|
|
||||||
world_transform = None
|
|
||||||
if self._node.getMeshData():
|
if self._node.getMeshData():
|
||||||
mesh = self._node.getMeshData()
|
mesh = self._node.getMeshData()
|
||||||
world_transform = self._node.getWorldTransformation()
|
world_transform = self._node.getWorldTransformation()
|
||||||
|
@ -228,24 +235,33 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
|
|
||||||
return offset_hull
|
return offset_hull
|
||||||
|
|
||||||
def _getHeadAndFans(self):
|
def _getHeadAndFans(self) -> Polygon:
|
||||||
return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
|
if self._global_stack:
|
||||||
|
return Polygon(numpy.array(self._global_stack.getHeadAndFansCoordinates(), numpy.float32))
|
||||||
|
return Polygon()
|
||||||
|
|
||||||
def _compute2DConvexHeadFull(self):
|
def _compute2DConvexHeadFull(self) -> Optional[Polygon]:
|
||||||
return self._compute2DConvexHull().getMinkowskiHull(self._getHeadAndFans())
|
convex_hull = self._compute2DConvexHeadFull()
|
||||||
|
if convex_hull:
|
||||||
|
return convex_hull.getMinkowskiHull(self._getHeadAndFans())
|
||||||
|
return None
|
||||||
|
|
||||||
def _compute2DConvexHeadMin(self):
|
def _compute2DConvexHeadMin(self) -> Optional[Polygon]:
|
||||||
headAndFans = self._getHeadAndFans()
|
head_and_fans = self._getHeadAndFans()
|
||||||
mirrored = headAndFans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
|
mirrored = head_and_fans.mirror([0, 0], [0, 1]).mirror([0, 0], [1, 0]) # Mirror horizontally & vertically.
|
||||||
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
|
head_and_fans = self._getHeadAndFans().intersectionConvexHulls(mirrored)
|
||||||
|
|
||||||
# Min head hull is used for the push free
|
# Min head hull is used for the push free
|
||||||
min_head_hull = self._compute2DConvexHull().getMinkowskiHull(head_and_fans)
|
convex_hull = self._compute2DConvexHeadFull()
|
||||||
return min_head_hull
|
if convex_hull:
|
||||||
|
return convex_hull.getMinkowskiHull(head_and_fans)
|
||||||
|
return None
|
||||||
|
|
||||||
## Compensate given 2D polygon with adhesion margin
|
## Compensate given 2D polygon with adhesion margin
|
||||||
# \return 2D polygon with added margin
|
# \return 2D polygon with added margin
|
||||||
def _add2DAdhesionMargin(self, poly):
|
def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon:
|
||||||
|
if not self._global_stack:
|
||||||
|
return Polygon()
|
||||||
# Compensate for raft/skirt/brim
|
# Compensate for raft/skirt/brim
|
||||||
# Add extra margin depending on adhesion type
|
# Add extra margin depending on adhesion type
|
||||||
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
|
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
|
||||||
|
@ -263,7 +279,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
else:
|
else:
|
||||||
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")
|
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")
|
||||||
|
|
||||||
# adjust head_and_fans with extra margin
|
# Adjust head_and_fans with extra margin
|
||||||
if extra_margin > 0:
|
if extra_margin > 0:
|
||||||
extra_margin_polygon = Polygon.approximatedCircle(extra_margin)
|
extra_margin_polygon = Polygon.approximatedCircle(extra_margin)
|
||||||
poly = poly.getMinkowskiHull(extra_margin_polygon)
|
poly = poly.getMinkowskiHull(extra_margin_polygon)
|
||||||
|
@ -274,7 +290,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
# \param convex_hull Polygon of the original convex hull.
|
# \param convex_hull Polygon of the original convex hull.
|
||||||
# \return New Polygon instance that is offset with everything that
|
# \return New Polygon instance that is offset with everything that
|
||||||
# influences the collision area.
|
# influences the collision area.
|
||||||
def _offsetHull(self, convex_hull):
|
def _offsetHull(self, convex_hull: Polygon) -> Polygon:
|
||||||
horizontal_expansion = max(
|
horizontal_expansion = max(
|
||||||
self._getSettingProperty("xy_offset", "value"),
|
self._getSettingProperty("xy_offset", "value"),
|
||||||
self._getSettingProperty("xy_offset_layer_0", "value")
|
self._getSettingProperty("xy_offset_layer_0", "value")
|
||||||
|
@ -295,16 +311,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
else:
|
else:
|
||||||
return convex_hull
|
return convex_hull
|
||||||
|
|
||||||
def _onChanged(self, *args):
|
def _onChanged(self, *args) -> None:
|
||||||
self._raft_thickness = self._build_volume.getRaftThickness()
|
self._raft_thickness = self._build_volume.getRaftThickness()
|
||||||
if not args or args[0] == self._node:
|
if not args or args[0] == self._node:
|
||||||
self.recomputeConvexHullDelayed()
|
self.recomputeConvexHullDelayed()
|
||||||
|
|
||||||
def _onGlobalStackChanged(self):
|
def _onGlobalStackChanged(self) -> None:
|
||||||
if self._global_stack:
|
if self._global_stack:
|
||||||
self._global_stack.propertyChanged.disconnect(self._onSettingValueChanged)
|
self._global_stack.propertyChanged.disconnect(self._onSettingValueChanged)
|
||||||
self._global_stack.containersChanged.disconnect(self._onChanged)
|
self._global_stack.containersChanged.disconnect(self._onChanged)
|
||||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
extruder.propertyChanged.disconnect(self._onSettingValueChanged)
|
extruder.propertyChanged.disconnect(self._onSettingValueChanged)
|
||||||
|
|
||||||
|
@ -314,14 +330,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
self._global_stack.propertyChanged.connect(self._onSettingValueChanged)
|
self._global_stack.propertyChanged.connect(self._onSettingValueChanged)
|
||||||
self._global_stack.containersChanged.connect(self._onChanged)
|
self._global_stack.containersChanged.connect(self._onChanged)
|
||||||
|
|
||||||
extruders = ExtruderManager.getInstance().getMachineExtruders(self._global_stack.getId())
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
extruder.propertyChanged.connect(self._onSettingValueChanged)
|
extruder.propertyChanged.connect(self._onSettingValueChanged)
|
||||||
|
|
||||||
self._onChanged()
|
self._onChanged()
|
||||||
|
|
||||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||||
def _getSettingProperty(self, setting_key, prop = "value"):
|
def _getSettingProperty(self, setting_key: str, prop: str = "value") -> Any:
|
||||||
|
if not self._global_stack:
|
||||||
|
return None
|
||||||
per_mesh_stack = self._node.callDecoration("getStack")
|
per_mesh_stack = self._node.callDecoration("getStack")
|
||||||
if per_mesh_stack:
|
if per_mesh_stack:
|
||||||
return per_mesh_stack.getProperty(setting_key, prop)
|
return per_mesh_stack.getProperty(setting_key, prop)
|
||||||
|
@ -339,8 +357,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||||
# Limit_to_extruder is set. The global stack handles this then
|
# Limit_to_extruder is set. The global stack handles this then
|
||||||
return self._global_stack.getProperty(setting_key, prop)
|
return self._global_stack.getProperty(setting_key, prop)
|
||||||
|
|
||||||
## Returns true if node is a descendant or the same as the root node.
|
## Returns True if node is a descendant or the same as the root node.
|
||||||
def __isDescendant(self, root, node):
|
def __isDescendant(self, root: "SceneNode", node: "SceneNode") -> bool:
|
||||||
if node is None:
|
if node is None:
|
||||||
return False
|
return False
|
||||||
if root is node:
|
if root is node:
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
class GCodeListDecorator(SceneNodeDecorator):
|
class GCodeListDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._gcode_list = []
|
self._gcode_list = [] # type: List[str]
|
||||||
|
|
||||||
def getGCodeList(self):
|
def getGCodeList(self) -> List[str]:
|
||||||
return self._gcode_list
|
return self._gcode_list
|
||||||
|
|
||||||
def setGCodeList(self, list):
|
def setGCodeList(self, list: List[str]):
|
||||||
self._gcode_list = list
|
self._gcode_list = list
|
||||||
|
|
||||||
|
def __deepcopy__(self, memo) -> "GCodeListDecorator":
|
||||||
|
copied_decorator = GCodeListDecorator()
|
||||||
|
copied_decorator.setGCodeList(self.getGCodeList())
|
||||||
|
return copied_decorator
|
|
@ -2,11 +2,11 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||||
|
|
||||||
|
|
||||||
class SliceableObjectDecorator(SceneNodeDecorator):
|
class SliceableObjectDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
def isSliceable(self):
|
def isSliceable(self) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo) -> "SliceableObjectDecorator":
|
||||||
return type(self)()
|
return type(self)()
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||||
|
|
||||||
|
|
||||||
## A decorator that stores the amount an object has been moved below the platform.
|
## A decorator that stores the amount an object has been moved below the platform.
|
||||||
class ZOffsetDecorator(SceneNodeDecorator):
|
class ZOffsetDecorator(SceneNodeDecorator):
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self._z_offset = 0
|
self._z_offset = 0.
|
||||||
|
|
||||||
def setZOffset(self, offset):
|
def setZOffset(self, offset: float) -> None:
|
||||||
self._z_offset = offset
|
self._z_offset = offset
|
||||||
|
|
||||||
def getZOffset(self):
|
def getZOffset(self) -> float:
|
||||||
return self._z_offset
|
return self._z_offset
|
||||||
|
|
||||||
def __deepcopy__(self, memo):
|
def __deepcopy__(self, memo) -> "ZOffsetDecorator":
|
||||||
copied_decorator = ZOffsetDecorator()
|
copied_decorator = ZOffsetDecorator()
|
||||||
copied_decorator.setZOffset(self.getZOffset())
|
copied_decorator.setZOffset(self.getZOffset())
|
||||||
return copied_decorator
|
return copied_decorator
|
||||||
|
|
|
@ -4,12 +4,12 @@
|
||||||
import os
|
import os
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
import uuid
|
import uuid
|
||||||
from typing import Any
|
from typing import Dict, Union, Any, TYPE_CHECKING, List
|
||||||
from typing import Dict, Union, Optional
|
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, QUrl, QVariant
|
from PyQt5.QtCore import QObject, QUrl
|
||||||
from PyQt5.QtWidgets import QMessageBox
|
from PyQt5.QtWidgets import QMessageBox
|
||||||
|
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
@ -21,6 +21,18 @@ 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
|
||||||
|
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Machines.ContainerNode import ContainerNode
|
||||||
|
from cura.Machines.MaterialNode import MaterialNode
|
||||||
|
from cura.Machines.QualityChangesGroup import QualityChangesGroup
|
||||||
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
|
from cura.Settings.MachineManager import MachineManager
|
||||||
|
from cura.Machines.MaterialManager import MaterialManager
|
||||||
|
from cura.Machines.QualityManager import QualityManager
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
|
@ -31,20 +43,20 @@ catalog = i18nCatalog("cura")
|
||||||
# when a certain action happens. This can be done through this class.
|
# when a certain action happens. This can be done through this class.
|
||||||
class ContainerManager(QObject):
|
class ContainerManager(QObject):
|
||||||
|
|
||||||
def __init__(self, application):
|
def __init__(self, application: "CuraApplication") -> None:
|
||||||
if ContainerManager.__instance is not None:
|
if ContainerManager.__instance is not None:
|
||||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||||
ContainerManager.__instance = self
|
ContainerManager.__instance = self
|
||||||
|
|
||||||
super().__init__(parent = application)
|
super().__init__(parent = application)
|
||||||
|
|
||||||
self._application = application
|
self._application = application # type: CuraApplication
|
||||||
self._plugin_registry = self._application.getPluginRegistry()
|
self._plugin_registry = self._application.getPluginRegistry() # type: PluginRegistry
|
||||||
self._container_registry = self._application.getContainerRegistry()
|
self._container_registry = self._application.getContainerRegistry() # type: ContainerRegistry
|
||||||
self._machine_manager = self._application.getMachineManager()
|
self._machine_manager = self._application.getMachineManager() # type: MachineManager
|
||||||
self._material_manager = self._application.getMaterialManager()
|
self._material_manager = self._application.getMaterialManager() # type: MaterialManager
|
||||||
self._quality_manager = self._application.getQualityManager()
|
self._quality_manager = self._application.getQualityManager() # type: QualityManager
|
||||||
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
self._container_name_filters = {} # type: Dict[str, Dict[str, Any]]
|
||||||
|
|
||||||
@pyqtSlot(str, str, result=str)
|
@pyqtSlot(str, str, result=str)
|
||||||
def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str:
|
def getContainerMetaDataEntry(self, container_id: str, entry_names: str) -> str:
|
||||||
|
@ -69,21 +81,23 @@ class ContainerManager(QObject):
|
||||||
# by using "/" as a separator. For example, to change an entry "foo" in a
|
# by using "/" as a separator. For example, to change an entry "foo" in a
|
||||||
# dictionary entry "bar", you can specify "bar/foo" as entry name.
|
# dictionary entry "bar", you can specify "bar/foo" as entry name.
|
||||||
#
|
#
|
||||||
# \param container_id \type{str} The ID of the container to change.
|
# \param container_node \type{ContainerNode}
|
||||||
# \param entry_name \type{str} The name of the metadata entry to change.
|
# \param entry_name \type{str} The name of the metadata entry to change.
|
||||||
# \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.
|
|
||||||
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||||
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
||||||
@pyqtSlot("QVariant", str, str)
|
@pyqtSlot("QVariant", str, str)
|
||||||
def setContainerMetaDataEntry(self, container_node, entry_name, entry_value):
|
def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool:
|
||||||
root_material_id = container_node.metadata["base_file"]
|
root_material_id = container_node.getMetaDataEntry("base_file", "")
|
||||||
if self._container_registry.isReadOnly(root_material_id):
|
if self._container_registry.isReadOnly(root_material_id):
|
||||||
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
|
Logger.log("w", "Cannot set metadata of read-only container %s.", root_material_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
material_group = self._material_manager.getMaterialGroup(root_material_id)
|
||||||
|
if material_group is None:
|
||||||
|
Logger.log("w", "Unable to find material group for: %s.", root_material_id)
|
||||||
|
return False
|
||||||
|
|
||||||
entries = entry_name.split("/")
|
entries = entry_name.split("/")
|
||||||
entry_name = entries.pop()
|
entry_name = entries.pop()
|
||||||
|
@ -91,11 +105,11 @@ 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 = material_group.root_material_node.metadata.get(root_name)
|
root = material_group.root_material_node.getMetaDataEntry(root_name)
|
||||||
|
|
||||||
item = root
|
item = root
|
||||||
for _ in range(len(entries)):
|
for _ in range(len(entries)):
|
||||||
item = item.get(entries.pop(0), { })
|
item = item.get(entries.pop(0), {})
|
||||||
|
|
||||||
if item[entry_name] != entry_value:
|
if item[entry_name] != entry_value:
|
||||||
sub_item_changed = True
|
sub_item_changed = True
|
||||||
|
@ -109,9 +123,10 @@ class ContainerManager(QObject):
|
||||||
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
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
@pyqtSlot(str, result = str)
|
||||||
def makeUniqueName(self, original_name):
|
def makeUniqueName(self, original_name: str) -> str:
|
||||||
return self._container_registry.uniqueName(original_name)
|
return self._container_registry.uniqueName(original_name)
|
||||||
|
|
||||||
## Get a list of string that can be used as name filters for a Qt File Dialog
|
## Get a list of string that can be used as name filters for a Qt File Dialog
|
||||||
|
@ -125,7 +140,7 @@ class ContainerManager(QObject):
|
||||||
#
|
#
|
||||||
# \return A string list with name filters.
|
# \return A string list with name filters.
|
||||||
@pyqtSlot(str, result = "QStringList")
|
@pyqtSlot(str, result = "QStringList")
|
||||||
def getContainerNameFilters(self, type_name):
|
def getContainerNameFilters(self, type_name: str) -> List[str]:
|
||||||
if not self._container_name_filters:
|
if not self._container_name_filters:
|
||||||
self._updateContainerNameFilters()
|
self._updateContainerNameFilters()
|
||||||
|
|
||||||
|
@ -257,7 +272,7 @@ class ContainerManager(QObject):
|
||||||
#
|
#
|
||||||
# \return \type{bool} True if successful, False if not.
|
# \return \type{bool} True if successful, False if not.
|
||||||
@pyqtSlot(result = bool)
|
@pyqtSlot(result = bool)
|
||||||
def updateQualityChanges(self):
|
def updateQualityChanges(self) -> bool:
|
||||||
global_stack = self._machine_manager.activeMachine
|
global_stack = self._machine_manager.activeMachine
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return False
|
return False
|
||||||
|
@ -313,10 +328,10 @@ class ContainerManager(QObject):
|
||||||
# \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("QVariant", bool, result = "QStringList")
|
@pyqtSlot("QVariant", bool, result = "QStringList")
|
||||||
def getLinkedMaterials(self, material_node, exclude_self = False):
|
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False):
|
||||||
guid = material_node.metadata["GUID"]
|
guid = material_node.getMetaDataEntry("GUID", "")
|
||||||
|
|
||||||
self_root_material_id = material_node.metadata["base_file"]
|
self_root_material_id = material_node.getMetaDataEntry("base_file")
|
||||||
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
material_group_list = self._material_manager.getMaterialGroupListByGUID(guid)
|
||||||
|
|
||||||
linked_material_names = []
|
linked_material_names = []
|
||||||
|
@ -324,15 +339,19 @@ class ContainerManager(QObject):
|
||||||
for material_group in material_group_list:
|
for material_group in material_group_list:
|
||||||
if exclude_self and material_group.name == self_root_material_id:
|
if exclude_self and material_group.name == self_root_material_id:
|
||||||
continue
|
continue
|
||||||
linked_material_names.append(material_group.root_material_node.metadata["name"])
|
linked_material_names.append(material_group.root_material_node.getMetaDataEntry("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("QVariant")
|
@pyqtSlot("QVariant")
|
||||||
def unlinkMaterial(self, material_node):
|
def unlinkMaterial(self, material_node: "MaterialNode") -> None:
|
||||||
# Get the material group
|
# Get the material group
|
||||||
material_group = self._material_manager.getMaterialGroup(material_node.metadata["base_file"])
|
material_group = self._material_manager.getMaterialGroup(material_node.getMetaDataEntry("base_file", ""))
|
||||||
|
|
||||||
|
if material_group is None:
|
||||||
|
Logger.log("w", "Unable to find material group for %s", material_node)
|
||||||
|
return
|
||||||
|
|
||||||
# Generate a new GUID
|
# Generate a new GUID
|
||||||
new_guid = str(uuid.uuid4())
|
new_guid = str(uuid.uuid4())
|
||||||
|
@ -344,7 +363,7 @@ class ContainerManager(QObject):
|
||||||
if container is not None:
|
if container is not None:
|
||||||
container.setMetaDataEntry("GUID", new_guid)
|
container.setMetaDataEntry("GUID", new_guid)
|
||||||
|
|
||||||
def _performMerge(self, merge_into, merge, clear_settings = True):
|
def _performMerge(self, merge_into: InstanceContainer, merge: InstanceContainer, clear_settings: bool = True) -> None:
|
||||||
if merge == merge_into:
|
if merge == merge_into:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -400,7 +419,7 @@ class ContainerManager(QObject):
|
||||||
|
|
||||||
## 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: QUrl):
|
||||||
if not file_url.isValid():
|
if not file_url.isValid():
|
||||||
return
|
return
|
||||||
path = file_url.toLocalFile()
|
path = file_url.toLocalFile()
|
||||||
|
@ -409,7 +428,7 @@ class ContainerManager(QObject):
|
||||||
return self._container_registry.importProfile(path)
|
return self._container_registry.importProfile(path)
|
||||||
|
|
||||||
@pyqtSlot(QObject, QUrl, str)
|
@pyqtSlot(QObject, QUrl, str)
|
||||||
def exportQualityChangesGroup(self, quality_changes_group, file_url: QUrl, file_type: str):
|
def exportQualityChangesGroup(self, quality_changes_group: "QualityChangesGroup", file_url: QUrl, file_type: str) -> None:
|
||||||
if not file_url.isValid():
|
if not file_url.isValid():
|
||||||
return
|
return
|
||||||
path = file_url.toLocalFile()
|
path = file_url.toLocalFile()
|
||||||
|
|
|
@ -291,7 +291,7 @@ class CuraContainerStack(ContainerStack):
|
||||||
|
|
||||||
# Helper to make sure we emit a PyQt signal on container changes.
|
# Helper to make sure we emit a PyQt signal on container changes.
|
||||||
def _onContainersChanged(self, container: Any) -> None:
|
def _onContainersChanged(self, container: Any) -> None:
|
||||||
self.pyqtContainersChanged.emit()
|
Application.getInstance().callLater(self.pyqtContainersChanged.emit)
|
||||||
|
|
||||||
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
|
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
|
||||||
# and its properties rather than, for example, the extruder. Defaults to simply returning the definition property.
|
# and its properties rather than, for example, the extruder. Defaults to simply returning the definition property.
|
||||||
|
|
|
@ -15,6 +15,7 @@ from .ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
## Contains helper functions to create new machines.
|
## Contains helper functions to create new machines.
|
||||||
class CuraStackBuilder:
|
class CuraStackBuilder:
|
||||||
|
|
||||||
## Create a new instance of a machine.
|
## Create a new instance of a machine.
|
||||||
#
|
#
|
||||||
# \param name The name of the new machine.
|
# \param name The name of the new machine.
|
||||||
|
@ -26,7 +27,6 @@ class CuraStackBuilder:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
variant_manager = application.getVariantManager()
|
variant_manager = application.getVariantManager()
|
||||||
material_manager = application.getMaterialManager()
|
|
||||||
quality_manager = application.getQualityManager()
|
quality_manager = application.getQualityManager()
|
||||||
registry = application.getContainerRegistry()
|
registry = application.getContainerRegistry()
|
||||||
|
|
||||||
|
@ -46,16 +46,6 @@ class CuraStackBuilder:
|
||||||
if not global_variant_container:
|
if not global_variant_container:
|
||||||
global_variant_container = application.empty_variant_container
|
global_variant_container = application.empty_variant_container
|
||||||
|
|
||||||
# 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()
|
|
||||||
if not extruder_variant_container:
|
|
||||||
extruder_variant_container = application.empty_variant_container
|
|
||||||
extruder_variant_name = extruder_variant_container.getName()
|
|
||||||
|
|
||||||
generated_name = registry.createUniqueName("machine", "", name, machine_definition.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
|
||||||
|
@ -74,34 +64,8 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
# Create ExtruderStacks
|
# Create ExtruderStacks
|
||||||
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
extruder_dict = machine_definition.getMetaDataEntry("machine_extruder_trains")
|
||||||
|
for position in extruder_dict:
|
||||||
for position, extruder_definition_id in extruder_dict.items():
|
cls.createExtruderStackWithDefaultSetup(new_global_stack, position)
|
||||||
# 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:
|
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(extruder_definition_id)
|
|
||||||
return None #Don't return any container stack then, not the rest of the extruders either.
|
|
||||||
|
|
||||||
# get material container for extruders
|
|
||||||
material_container = application.empty_material_container
|
|
||||||
material_node = material_manager.getDefaultMaterial(new_global_stack, position, extruder_variant_name, extruder_definition = extruder_definition)
|
|
||||||
if material_node and material_node.getContainer():
|
|
||||||
material_container = material_node.getContainer()
|
|
||||||
|
|
||||||
new_extruder_id = registry.uniqueName(extruder_definition_id)
|
|
||||||
new_extruder = cls.createExtruderStack(
|
|
||||||
new_extruder_id,
|
|
||||||
extruder_definition = extruder_definition,
|
|
||||||
machine_definition_id = definition_id,
|
|
||||||
position = position,
|
|
||||||
variant_container = extruder_variant_container,
|
|
||||||
material_container = material_container,
|
|
||||||
quality_container = application.empty_quality_container
|
|
||||||
)
|
|
||||||
new_extruder.setNextStack(new_global_stack)
|
|
||||||
new_global_stack.addExtruder(new_extruder)
|
|
||||||
|
|
||||||
for new_extruder in new_global_stack.extruders.values(): #Only register the extruders if we're sure that all of them are correct.
|
for new_extruder in new_global_stack.extruders.values(): #Only register the extruders if we're sure that all of them are correct.
|
||||||
registry.addContainer(new_extruder)
|
registry.addContainer(new_extruder)
|
||||||
|
@ -136,19 +100,73 @@ class CuraStackBuilder:
|
||||||
|
|
||||||
return new_global_stack
|
return new_global_stack
|
||||||
|
|
||||||
|
## Create a default Extruder Stack
|
||||||
|
#
|
||||||
|
# \param global_stack The global stack this extruder refers to.
|
||||||
|
# \param extruder_position The position of the current extruder.
|
||||||
|
@classmethod
|
||||||
|
def createExtruderStackWithDefaultSetup(cls, global_stack: "GlobalStack", extruder_position: int) -> None:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = CuraApplication.getInstance()
|
||||||
|
variant_manager = application.getVariantManager()
|
||||||
|
material_manager = application.getMaterialManager()
|
||||||
|
registry = application.getContainerRegistry()
|
||||||
|
|
||||||
|
# get variant container for extruders
|
||||||
|
extruder_variant_container = application.empty_variant_container
|
||||||
|
extruder_variant_node = variant_manager.getDefaultVariantNode(global_stack.definition, VariantType.NOZZLE)
|
||||||
|
extruder_variant_name = None
|
||||||
|
if extruder_variant_node:
|
||||||
|
extruder_variant_container = extruder_variant_node.getContainer()
|
||||||
|
if not extruder_variant_container:
|
||||||
|
extruder_variant_container = application.empty_variant_container
|
||||||
|
extruder_variant_name = extruder_variant_container.getName()
|
||||||
|
|
||||||
|
extruder_definition_dict = global_stack.getMetaDataEntry("machine_extruder_trains")
|
||||||
|
extruder_definition_id = extruder_definition_dict[str(extruder_position)]
|
||||||
|
extruder_definition = registry.findDefinitionContainers(id = extruder_definition_id)[0]
|
||||||
|
|
||||||
|
# get material container for extruders
|
||||||
|
material_container = application.empty_material_container
|
||||||
|
material_node = material_manager.getDefaultMaterial(global_stack, extruder_position, extruder_variant_name,
|
||||||
|
extruder_definition = extruder_definition)
|
||||||
|
if material_node and material_node.getContainer():
|
||||||
|
material_container = material_node.getContainer()
|
||||||
|
|
||||||
|
new_extruder_id = registry.uniqueName(extruder_definition_id)
|
||||||
|
new_extruder = cls.createExtruderStack(
|
||||||
|
new_extruder_id,
|
||||||
|
extruder_definition = extruder_definition,
|
||||||
|
machine_definition_id = global_stack.definition.getId(),
|
||||||
|
position = extruder_position,
|
||||||
|
variant_container = extruder_variant_container,
|
||||||
|
material_container = material_container,
|
||||||
|
quality_container = application.empty_quality_container
|
||||||
|
)
|
||||||
|
new_extruder.setNextStack(global_stack)
|
||||||
|
global_stack.addExtruder(new_extruder)
|
||||||
|
|
||||||
|
registry.addContainer(new_extruder)
|
||||||
|
|
||||||
## Create a new Extruder stack
|
## Create a new Extruder stack
|
||||||
#
|
#
|
||||||
# \param new_stack_id The ID of the new stack.
|
# \param new_stack_id The ID of the new stack.
|
||||||
# \param definition The definition to base the new stack on.
|
# \param extruder_definition The definition to base the new stack on.
|
||||||
# \param machine_definition_id The ID of the machine definition to use for
|
# \param machine_definition_id The ID of the machine definition to use for the user container.
|
||||||
# the user container.
|
# \param position The position the extruder occupies in the machine.
|
||||||
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
# \param variant_container The variant selected for the current extruder.
|
||||||
|
# \param material_container The material selected for the current extruder.
|
||||||
|
# \param quality_container The quality selected for the current extruder.
|
||||||
#
|
#
|
||||||
# \return A new Global stack instance with the specified parameters.
|
# \return A new Extruder stack instance with the specified parameters.
|
||||||
@classmethod
|
@classmethod
|
||||||
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface, machine_definition_id: str,
|
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface,
|
||||||
|
machine_definition_id: str,
|
||||||
position: int,
|
position: int,
|
||||||
variant_container, material_container, quality_container) -> ExtruderStack:
|
variant_container: "InstanceContainer",
|
||||||
|
material_container: "InstanceContainer",
|
||||||
|
quality_container: "InstanceContainer") -> ExtruderStack:
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
registry = application.getContainerRegistry()
|
registry = application.getContainerRegistry()
|
||||||
|
@ -157,7 +175,7 @@ class CuraStackBuilder:
|
||||||
stack.setName(extruder_definition.getName())
|
stack.setName(extruder_definition.getName())
|
||||||
stack.setDefinition(extruder_definition)
|
stack.setDefinition(extruder_definition)
|
||||||
|
|
||||||
stack.setMetaDataEntry("position", position)
|
stack.setMetaDataEntry("position", str(position))
|
||||||
|
|
||||||
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
|
user_container = cls.createUserChangesContainer(new_stack_id + "_user", machine_definition_id, new_stack_id,
|
||||||
is_global_stack = False)
|
is_global_stack = False)
|
||||||
|
@ -183,9 +201,22 @@ class CuraStackBuilder:
|
||||||
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
||||||
#
|
#
|
||||||
# \return A new Global stack instance with the specified parameters.
|
# \return A new Global stack instance with the specified parameters.
|
||||||
|
|
||||||
|
## Create a new Global stack
|
||||||
|
#
|
||||||
|
# \param new_stack_id The ID of the new stack.
|
||||||
|
# \param definition The definition to base the new stack on.
|
||||||
|
# \param variant_container The variant selected for the current stack.
|
||||||
|
# \param material_container The material selected for the current stack.
|
||||||
|
# \param quality_container The quality selected for the current stack.
|
||||||
|
#
|
||||||
|
# \return A new Global stack instance with the specified parameters.
|
||||||
@classmethod
|
@classmethod
|
||||||
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
||||||
variant_container, material_container, quality_container) -> GlobalStack:
|
variant_container: "InstanceContainer",
|
||||||
|
material_container: "InstanceContainer",
|
||||||
|
quality_container: "InstanceContainer") -> GlobalStack:
|
||||||
|
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
application = CuraApplication.getInstance()
|
application = CuraApplication.getInstance()
|
||||||
registry = application.getContainerRegistry()
|
registry = application.getContainerRegistry()
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant # For communicating data and events to Qt.
|
||||||
from UM.FlameProfiler import pyqtSlot
|
from UM.FlameProfiler import pyqtSlot
|
||||||
|
|
||||||
import cura.CuraApplication #To get the global container stack to find the current machine.
|
import cura.CuraApplication # To get the global container stack to find the current machine.
|
||||||
|
from cura.Settings.GlobalStack import GlobalStack
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
from UM.Scene.SceneNode import SceneNode
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
@ -12,15 +13,13 @@ 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, Dict
|
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
from cura.Settings.GlobalStack import GlobalStack
|
|
||||||
|
|
||||||
|
|
||||||
## Manages all existing extruder stacks.
|
## Manages all existing extruder stacks.
|
||||||
|
@ -38,9 +37,13 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
self._application = cura.CuraApplication.CuraApplication.getInstance()
|
||||||
|
|
||||||
self._extruder_trains = {} # Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
# Per machine, a dictionary of extruder container stack IDs. Only for separately defined extruders.
|
||||||
|
self._extruder_trains = {} # type: Dict[str, Dict[str, "ExtruderStack"]]
|
||||||
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 = []
|
|
||||||
|
# TODO; I have no idea why this is a union of ID's and extruder stacks. This needs to be fixed at some point.
|
||||||
|
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
|
||||||
|
|
||||||
self._addCurrentMachineExtruders()
|
self._addCurrentMachineExtruders()
|
||||||
|
|
||||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||||
|
@ -68,7 +71,7 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
## Return extruder count according to extruder trains.
|
## Return extruder count according to extruder trains.
|
||||||
@pyqtProperty(int, notify = extrudersChanged)
|
@pyqtProperty(int, notify = extrudersChanged)
|
||||||
def extruderCount(self):
|
def extruderCount(self) -> int:
|
||||||
if not self._application.getGlobalContainerStack():
|
if not self._application.getGlobalContainerStack():
|
||||||
return 0 # No active machine, so no extruders.
|
return 0 # No active machine, so no extruders.
|
||||||
try:
|
try:
|
||||||
|
@ -79,28 +82,14 @@ class ExtruderManager(QObject):
|
||||||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||||
def extruderIds(self) -> Dict[str, str]:
|
def extruderIds(self) -> Dict[str, str]:
|
||||||
extruder_stack_ids = {}
|
extruder_stack_ids = {} # type: Dict[str, str]
|
||||||
|
|
||||||
global_container_stack = self._application.getGlobalContainerStack()
|
global_container_stack = self._application.getGlobalContainerStack()
|
||||||
if global_container_stack:
|
if global_container_stack:
|
||||||
global_stack_id = global_container_stack.getId()
|
extruder_stack_ids = {position: extruder.id for position, extruder in global_container_stack.extruders.items()}
|
||||||
|
|
||||||
if global_stack_id in self._extruder_trains:
|
|
||||||
for position in self._extruder_trains[global_stack_id]:
|
|
||||||
extruder_stack_ids[position] = self._extruder_trains[global_stack_id][position].getId()
|
|
||||||
|
|
||||||
return extruder_stack_ids
|
return extruder_stack_ids
|
||||||
|
|
||||||
@pyqtSlot(str, result = str)
|
|
||||||
def getQualityChangesIdByExtruderStackId(self, extruder_stack_id: str) -> str:
|
|
||||||
global_container_stack = self._application.getGlobalContainerStack()
|
|
||||||
if global_container_stack is not None:
|
|
||||||
for position in self._extruder_trains[global_container_stack.getId()]:
|
|
||||||
extruder = self._extruder_trains[global_container_stack.getId()][position]
|
|
||||||
if extruder.getId() == extruder_stack_id:
|
|
||||||
return extruder.qualityChanges.getId()
|
|
||||||
return ""
|
|
||||||
|
|
||||||
## Changes the active extruder by index.
|
## Changes the active extruder by index.
|
||||||
#
|
#
|
||||||
# \param index The index of the new active extruder.
|
# \param index The index of the new active extruder.
|
||||||
|
@ -117,9 +106,9 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \param index The index of the extruder whose name to get.
|
# \param index The index of the extruder whose name to get.
|
||||||
@pyqtSlot(int, result = str)
|
@pyqtSlot(int, result = str)
|
||||||
def getExtruderName(self, index):
|
def getExtruderName(self, index: int) -> str:
|
||||||
try:
|
try:
|
||||||
return list(self.getActiveExtruderStacks())[index].getName()
|
return self.getActiveExtruderStacks()[index].getName()
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
@ -128,12 +117,12 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
## Provides a list of extruder IDs used by the current selected objects.
|
## Provides a list of extruder IDs used by the current selected objects.
|
||||||
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
|
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
|
||||||
def selectedObjectExtruders(self) -> List[str]:
|
def selectedObjectExtruders(self) -> List[Union[str, "ExtruderStack"]]:
|
||||||
if not self._selected_object_extruders:
|
if not self._selected_object_extruders:
|
||||||
object_extruders = set()
|
object_extruders = set()
|
||||||
|
|
||||||
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
|
# First, build a list of the actual selected objects (including children of groups, excluding group nodes)
|
||||||
selected_nodes = []
|
selected_nodes = [] # type: List["SceneNode"]
|
||||||
for node in Selection.getAllSelectedObjects():
|
for node in Selection.getAllSelectedObjects():
|
||||||
if node.callDecoration("isGroup"):
|
if node.callDecoration("isGroup"):
|
||||||
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
for grouped_node in BreadthFirstIterator(node): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||||
|
@ -145,16 +134,15 @@ class ExtruderManager(QObject):
|
||||||
selected_nodes.append(node)
|
selected_nodes.append(node)
|
||||||
|
|
||||||
# Then, figure out which nodes are used by those selected nodes.
|
# Then, figure out which nodes are used by those selected nodes.
|
||||||
global_stack = self._application.getGlobalContainerStack()
|
current_extruder_trains = self.getActiveExtruderStacks()
|
||||||
current_extruder_trains = self._extruder_trains.get(global_stack.getId())
|
|
||||||
for node in selected_nodes:
|
for node in selected_nodes:
|
||||||
extruder = node.callDecoration("getActiveExtruder")
|
extruder = node.callDecoration("getActiveExtruder")
|
||||||
if extruder:
|
if extruder:
|
||||||
object_extruders.add(extruder)
|
object_extruders.add(extruder)
|
||||||
elif current_extruder_trains:
|
elif current_extruder_trains:
|
||||||
object_extruders.add(current_extruder_trains["0"].getId())
|
object_extruders.add(current_extruder_trains[0].getId())
|
||||||
|
|
||||||
self._selected_object_extruders = list(object_extruders)
|
self._selected_object_extruders = list(object_extruders) # type: List[Union[str, "ExtruderStack"]]
|
||||||
|
|
||||||
return self._selected_object_extruders
|
return self._selected_object_extruders
|
||||||
|
|
||||||
|
@ -163,19 +151,12 @@ class ExtruderManager(QObject):
|
||||||
# This will trigger a recalculation of the extruders used for the
|
# This will trigger a recalculation of the extruders used for the
|
||||||
# selection.
|
# selection.
|
||||||
def resetSelectedObjectExtruders(self) -> None:
|
def resetSelectedObjectExtruders(self) -> None:
|
||||||
self._selected_object_extruders = []
|
self._selected_object_extruders = [] # type: List[Union[str, "ExtruderStack"]]
|
||||||
self.selectedObjectExtrudersChanged.emit()
|
self.selectedObjectExtrudersChanged.emit()
|
||||||
|
|
||||||
@pyqtSlot(result = QObject)
|
@pyqtSlot(result = QObject)
|
||||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||||
global_container_stack = self._application.getGlobalContainerStack()
|
return self.getExtruderStack(self._active_extruder_index)
|
||||||
|
|
||||||
if global_container_stack:
|
|
||||||
if global_container_stack.getId() in self._extruder_trains:
|
|
||||||
if str(self._active_extruder_index) in self._extruder_trains[global_container_stack.getId()]:
|
|
||||||
return self._extruder_trains[global_container_stack.getId()][str(self._active_extruder_index)]
|
|
||||||
|
|
||||||
return None
|
|
||||||
|
|
||||||
## Get an extruder stack by index
|
## Get an extruder stack by index
|
||||||
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
||||||
|
@ -186,16 +167,7 @@ class ExtruderManager(QObject):
|
||||||
return self._extruder_trains[global_container_stack.getId()][str(index)]
|
return self._extruder_trains[global_container_stack.getId()][str(index)]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
## Get all extruder stacks
|
def registerExtruder(self, extruder_train: "ExtruderStack", machine_id: str) -> None:
|
||||||
def getExtruderStacks(self) -> List["ExtruderStack"]:
|
|
||||||
result = []
|
|
||||||
for i in range(self.extruderCount):
|
|
||||||
stack = self.getExtruderStack(i)
|
|
||||||
if stack:
|
|
||||||
result.append(stack)
|
|
||||||
return result
|
|
||||||
|
|
||||||
def registerExtruder(self, extruder_train, machine_id):
|
|
||||||
changed = False
|
changed = False
|
||||||
|
|
||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
|
@ -214,23 +186,20 @@ class ExtruderManager(QObject):
|
||||||
if changed:
|
if changed:
|
||||||
self.extrudersChanged.emit(machine_id)
|
self.extrudersChanged.emit(machine_id)
|
||||||
|
|
||||||
def getAllExtruderValues(self, setting_key):
|
|
||||||
return self.getAllExtruderSettings(setting_key, "value")
|
|
||||||
|
|
||||||
## Gets a property of a setting for all extruders.
|
## Gets a property of a setting for all extruders.
|
||||||
#
|
#
|
||||||
# \param setting_key \type{str} The setting to get the property of.
|
# \param setting_key \type{str} The setting to get the property of.
|
||||||
# \param property \type{str} The property to get.
|
# \param property \type{str} The property to get.
|
||||||
# \return \type{List} the list of results
|
# \return \type{List} the list of results
|
||||||
def getAllExtruderSettings(self, setting_key: str, prop: str):
|
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List:
|
||||||
result = []
|
result = []
|
||||||
for index in self.extruderIds:
|
|
||||||
extruder_stack_id = self.extruderIds[str(index)]
|
for extruder_stack in self.getActiveExtruderStacks():
|
||||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = extruder_stack_id)[0]
|
|
||||||
result.append(extruder_stack.getProperty(setting_key, prop))
|
result.append(extruder_stack.getProperty(setting_key, prop))
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def extruderValueWithDefault(self, value):
|
def extruderValueWithDefault(self, value: str) -> str:
|
||||||
machine_manager = self._application.getMachineManager()
|
machine_manager = self._application.getMachineManager()
|
||||||
if value == "-1":
|
if value == "-1":
|
||||||
return machine_manager.defaultExtruderPosition
|
return machine_manager.defaultExtruderPosition
|
||||||
|
@ -321,7 +290,7 @@ class ExtruderManager(QObject):
|
||||||
## Removes the container stack and user profile for the extruders for a specific machine.
|
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||||
#
|
#
|
||||||
# \param machine_id The machine to remove the extruders for.
|
# \param machine_id The machine to remove the extruders for.
|
||||||
def removeMachineExtruders(self, machine_id: str):
|
def removeMachineExtruders(self, machine_id: str) -> None:
|
||||||
for extruder in self.getMachineExtruders(machine_id):
|
for extruder in self.getMachineExtruders(machine_id):
|
||||||
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
||||||
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
||||||
|
@ -331,24 +300,11 @@ class ExtruderManager(QObject):
|
||||||
## Returns extruders for a specific machine.
|
## Returns extruders for a specific machine.
|
||||||
#
|
#
|
||||||
# \param machine_id The machine to get the extruders of.
|
# \param machine_id The machine to get the extruders of.
|
||||||
def getMachineExtruders(self, machine_id: str):
|
def getMachineExtruders(self, machine_id: str) -> List["ExtruderStack"]:
|
||||||
if machine_id not in self._extruder_trains:
|
if machine_id not in self._extruder_trains:
|
||||||
return []
|
return []
|
||||||
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
||||||
|
|
||||||
## Returns a list containing the global stack and active extruder stacks.
|
|
||||||
#
|
|
||||||
# The first element is the global container stack, followed by any extruder stacks.
|
|
||||||
# \return \type{List[ContainerStack]}
|
|
||||||
def getActiveGlobalAndExtruderStacks(self) -> Optional[List[Union["ExtruderStack", "GlobalStack"]]]:
|
|
||||||
global_stack = self._application.getGlobalContainerStack()
|
|
||||||
if not global_stack:
|
|
||||||
return None
|
|
||||||
|
|
||||||
result = [global_stack]
|
|
||||||
result.extend(self.getActiveExtruderStacks())
|
|
||||||
return result
|
|
||||||
|
|
||||||
## Returns the list of active extruder stacks, taking into account the machine extruder count.
|
## Returns the list of active extruder stacks, taking into account the machine extruder count.
|
||||||
#
|
#
|
||||||
# \return \type{List[ContainerStack]} a list of
|
# \return \type{List[ContainerStack]} a list of
|
||||||
|
@ -357,14 +313,11 @@ class ExtruderManager(QObject):
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
result = []
|
result_tuple_list = sorted(list(global_stack.extruders.items()), key = lambda x: int(x[0]))
|
||||||
if global_stack.getId() in self._extruder_trains:
|
result_list = [item[1] for item in result_tuple_list]
|
||||||
for extruder in sorted(self._extruder_trains[global_stack.getId()]):
|
|
||||||
result.append(self._extruder_trains[global_stack.getId()][extruder])
|
|
||||||
|
|
||||||
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
|
||||||
|
return result_list[:machine_extruder_count]
|
||||||
return result[:machine_extruder_count]
|
|
||||||
|
|
||||||
def _globalContainerStackChanged(self) -> None:
|
def _globalContainerStackChanged(self) -> None:
|
||||||
# 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
|
||||||
|
@ -406,10 +359,17 @@ class ExtruderManager(QObject):
|
||||||
|
|
||||||
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
# After 3.4, all single-extrusion machines have their own extruder definition files instead of reusing
|
||||||
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
# "fdmextruder". We need to check a machine here so its extruder definition is correct according to this.
|
||||||
def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack):
|
def _fixSingleExtrusionMachineExtruderDefinition(self, global_stack: "GlobalStack") -> None:
|
||||||
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
|
expected_extruder_definition_0_id = global_stack.getMetaDataEntry("machine_extruder_trains")["0"]
|
||||||
extruder_stack_0 = global_stack.extruders["0"]
|
extruder_stack_0 = global_stack.extruders.get("0")
|
||||||
if extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
|
|
||||||
|
if extruder_stack_0 is None:
|
||||||
|
Logger.log("i", "No extruder stack for global stack [%s], create one", global_stack.getId())
|
||||||
|
# Single extrusion machine without an ExtruderStack, create it
|
||||||
|
from cura.Settings.CuraStackBuilder import CuraStackBuilder
|
||||||
|
CuraStackBuilder.createExtruderStackWithDefaultSetup(global_stack, 0)
|
||||||
|
|
||||||
|
elif extruder_stack_0.definition.getId() != expected_extruder_definition_0_id:
|
||||||
Logger.log("e", "Single extruder printer [{printer}] expected extruder [{expected}], but got [{got}]. I'm making it [{expected}].".format(
|
Logger.log("e", "Single extruder printer [{printer}] expected extruder [{expected}], but got [{got}]. I'm making it [{expected}].".format(
|
||||||
printer = global_stack.getId(), expected = expected_extruder_definition_0_id, got = extruder_stack_0.definition.getId()))
|
printer = global_stack.getId(), expected = expected_extruder_definition_0_id, got = extruder_stack_0.definition.getId()))
|
||||||
container_registry = ContainerRegistry.getInstance()
|
container_registry = ContainerRegistry.getInstance()
|
||||||
|
@ -425,11 +385,11 @@ class ExtruderManager(QObject):
|
||||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||||
# If no extruder has the value, the list will contain the global value.
|
# If no extruder has the value, the list will contain the global value.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getExtruderValues(key):
|
def getExtruderValues(key: str) -> List[Any]:
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values.
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
if not extruder.isEnabled:
|
if not extruder.isEnabled:
|
||||||
continue
|
continue
|
||||||
# only include values from extruders that are "active" for the current machine instance
|
# only include values from extruders that are "active" for the current machine instance
|
||||||
|
@ -460,8 +420,8 @@ class ExtruderManager(QObject):
|
||||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||||
# If no extruder has the value, the list will contain the global value.
|
# If no extruder has the value, the list will contain the global value.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultExtruderValues(key):
|
def getDefaultExtruderValues(key: str) -> List[Any]:
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values.
|
||||||
context = PropertyEvaluationContext(global_stack)
|
context = PropertyEvaluationContext(global_stack)
|
||||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||||
context.context["override_operators"] = {
|
context.context["override_operators"] = {
|
||||||
|
@ -471,7 +431,7 @@ class ExtruderManager(QObject):
|
||||||
}
|
}
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for extruder in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
# only include values from extruders that are "active" for the current machine instance
|
# only include values from extruders that are "active" for the current machine instance
|
||||||
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
|
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
|
||||||
continue
|
continue
|
||||||
|
@ -504,7 +464,7 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \return String representing the extruder values
|
# \return String representing the extruder values
|
||||||
@pyqtSlot(str, result="QVariant")
|
@pyqtSlot(str, result="QVariant")
|
||||||
def getInstanceExtruderValues(self, key):
|
def getInstanceExtruderValues(self, key) -> List:
|
||||||
return ExtruderManager.getExtruderValues(key)
|
return ExtruderManager.getExtruderValues(key)
|
||||||
|
|
||||||
## Get the value for a setting from a specific extruder.
|
## Get the value for a setting from a specific extruder.
|
||||||
|
@ -517,7 +477,7 @@ class ExtruderManager(QObject):
|
||||||
# \return The value of the setting for the specified extruder or for the
|
# \return The value of the setting for the specified extruder or for the
|
||||||
# global stack if not found.
|
# global stack if not found.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getExtruderValue(extruder_index, key):
|
def getExtruderValue(extruder_index: int, key: str) -> Any:
|
||||||
if extruder_index == -1:
|
if extruder_index == -1:
|
||||||
extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||||
|
@ -528,7 +488,7 @@ class ExtruderManager(QObject):
|
||||||
value = value(extruder)
|
value = value(extruder)
|
||||||
else:
|
else:
|
||||||
# Just a value from global.
|
# Just a value from global.
|
||||||
value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value")
|
value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value")
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -542,7 +502,7 @@ class ExtruderManager(QObject):
|
||||||
# \return The value of the setting for the specified extruder or for the
|
# \return The value of the setting for the specified extruder or for the
|
||||||
# global stack if not found.
|
# global stack if not found.
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultExtruderValue(extruder_index, key):
|
def getDefaultExtruderValue(extruder_index: int, key: str) -> Any:
|
||||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||||
context = PropertyEvaluationContext(extruder)
|
context = PropertyEvaluationContext(extruder)
|
||||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||||
|
@ -557,7 +517,7 @@ class ExtruderManager(QObject):
|
||||||
if isinstance(value, SettingFunction):
|
if isinstance(value, SettingFunction):
|
||||||
value = value(extruder, context = context)
|
value = value(extruder, context = context)
|
||||||
else: # Just a value from global.
|
else: # Just a value from global.
|
||||||
value = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack().getProperty(key, "value", context = context)
|
value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value", context = context)
|
||||||
|
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
@ -569,8 +529,8 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \return The effective value
|
# \return The effective value
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getResolveOrValue(key):
|
def getResolveOrValue(key: str) -> Any:
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack())
|
||||||
resolved_value = global_stack.getProperty(key, "value")
|
resolved_value = global_stack.getProperty(key, "value")
|
||||||
|
|
||||||
return resolved_value
|
return resolved_value
|
||||||
|
@ -583,8 +543,8 @@ class ExtruderManager(QObject):
|
||||||
#
|
#
|
||||||
# \return The effective value
|
# \return The effective value
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getDefaultResolveOrValue(key):
|
def getDefaultResolveOrValue(key: str) -> Any:
|
||||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack())
|
||||||
context = PropertyEvaluationContext(global_stack)
|
context = PropertyEvaluationContext(global_stack)
|
||||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||||
context.context["override_operators"] = {
|
context.context["override_operators"] = {
|
||||||
|
|
|
@ -134,7 +134,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
# Link to new extruders
|
# Link to new extruders
|
||||||
self._active_machine_extruders = []
|
self._active_machine_extruders = []
|
||||||
extruder_manager = Application.getInstance().getExtruderManager()
|
extruder_manager = Application.getInstance().getExtruderManager()
|
||||||
for extruder in extruder_manager.getExtruderStacks():
|
for extruder in extruder_manager.getActiveExtruderStacks():
|
||||||
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
|
if extruder is None: #This extruder wasn't loaded yet. This happens asynchronously while this model is constructed from QML.
|
||||||
continue
|
continue
|
||||||
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
extruder.containersChanged.connect(self._onExtruderStackContainersChanged)
|
||||||
|
@ -171,7 +171,7 @@ class ExtrudersModel(UM.Qt.ListModel.ListModel):
|
||||||
# get machine extruder count for verification
|
# get machine extruder count for verification
|
||||||
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
|
|
||||||
for extruder in Application.getInstance().getExtruderManager().getMachineExtruders(global_container_stack.getId()):
|
for extruder in Application.getInstance().getExtruderManager().getActiveExtruderStacks():
|
||||||
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
position = extruder.getMetaDataEntry("position", default = "0") # Get the position
|
||||||
try:
|
try:
|
||||||
position = int(position)
|
position = int(position)
|
||||||
|
|
|
@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState
|
||||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Util import parseBool
|
||||||
|
|
||||||
import cura.CuraApplication
|
import cura.CuraApplication
|
||||||
|
|
||||||
from . import Exceptions
|
from . import Exceptions
|
||||||
|
@ -21,6 +23,7 @@ from .CuraContainerStack import CuraContainerStack
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.Settings.ExtruderStack import ExtruderStack
|
from cura.Settings.ExtruderStack import ExtruderStack
|
||||||
|
|
||||||
|
|
||||||
## Represents the Global or Machine stack and its related containers.
|
## Represents the Global or Machine stack and its related containers.
|
||||||
#
|
#
|
||||||
class GlobalStack(CuraContainerStack):
|
class GlobalStack(CuraContainerStack):
|
||||||
|
@ -61,6 +64,10 @@ class GlobalStack(CuraContainerStack):
|
||||||
name = self.variant.getName()
|
name = self.variant.getName()
|
||||||
return name
|
return name
|
||||||
|
|
||||||
|
@pyqtProperty(str, constant = True)
|
||||||
|
def preferred_output_file_formats(self) -> str:
|
||||||
|
return self.getMetaDataEntry("file_formats")
|
||||||
|
|
||||||
## Add an extruder to the list of extruders of this stack.
|
## Add an extruder to the list of extruders of this stack.
|
||||||
#
|
#
|
||||||
# \param extruder The extruder to add.
|
# \param extruder The extruder to add.
|
||||||
|
@ -184,6 +191,15 @@ class GlobalStack(CuraContainerStack):
|
||||||
def getHeadAndFansCoordinates(self):
|
def getHeadAndFansCoordinates(self):
|
||||||
return self.getProperty("machine_head_with_fans_polygon", "value")
|
return self.getProperty("machine_head_with_fans_polygon", "value")
|
||||||
|
|
||||||
|
def getHasMaterials(self) -> bool:
|
||||||
|
return parseBool(self.getMetaDataEntry("has_materials", False))
|
||||||
|
|
||||||
|
def getHasVariants(self) -> bool:
|
||||||
|
return parseBool(self.getMetaDataEntry("has_variants", False))
|
||||||
|
|
||||||
|
def getHasMachineQuality(self) -> bool:
|
||||||
|
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
||||||
|
|
||||||
|
|
||||||
## private:
|
## private:
|
||||||
global_stack_mime = MimeType(
|
global_stack_mime = MimeType(
|
||||||
|
|
|
@ -369,6 +369,7 @@ class MachineManager(QObject):
|
||||||
return
|
return
|
||||||
|
|
||||||
global_stack = containers[0]
|
global_stack = containers[0]
|
||||||
|
ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack)
|
||||||
if not global_stack.isValid():
|
if not global_stack.isValid():
|
||||||
# Mark global stack as invalid
|
# Mark global stack as invalid
|
||||||
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
|
ConfigurationErrorMessage.getInstance().addFaultyContainers(global_stack.getId())
|
||||||
|
@ -377,7 +378,7 @@ class MachineManager(QObject):
|
||||||
self._global_container_stack = global_stack
|
self._global_container_stack = global_stack
|
||||||
self._application.setGlobalContainerStack(global_stack)
|
self._application.setGlobalContainerStack(global_stack)
|
||||||
ExtruderManager.getInstance()._globalContainerStackChanged()
|
ExtruderManager.getInstance()._globalContainerStackChanged()
|
||||||
self._initMachineState(containers[0])
|
self._initMachineState(global_stack)
|
||||||
self._onGlobalContainerChanged()
|
self._onGlobalContainerChanged()
|
||||||
|
|
||||||
self.__emitChangedSignals()
|
self.__emitChangedSignals()
|
||||||
|
@ -387,7 +388,9 @@ class MachineManager(QObject):
|
||||||
# \param definition_id \type{str} definition id that needs to look for
|
# \param definition_id \type{str} definition id that needs to look for
|
||||||
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def getMachine(definition_id: str, metadata_filter: Dict[str, str] = None) -> Optional["GlobalStack"]:
|
def getMachine(definition_id: str, metadata_filter: Optional[Dict[str, str]] = None) -> Optional["GlobalStack"]:
|
||||||
|
if metadata_filter is None:
|
||||||
|
metadata_filter = {}
|
||||||
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||||
for machine in machines:
|
for machine in machines:
|
||||||
if machine.definition.getId() == definition_id:
|
if machine.definition.getId() == definition_id:
|
||||||
|
@ -414,7 +417,7 @@ class MachineManager(QObject):
|
||||||
|
|
||||||
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
# Not a very pretty solution, but the extruder manager doesn't really know how many extruders there are
|
||||||
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
machine_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
|
||||||
extruder_stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
extruder_stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
count = 1 # we start with the global stack
|
count = 1 # we start with the global stack
|
||||||
for stack in extruder_stacks:
|
for stack in extruder_stacks:
|
||||||
md = stack.getMetaData()
|
md = stack.getMetaData()
|
||||||
|
@ -437,7 +440,7 @@ class MachineManager(QObject):
|
||||||
if self._global_container_stack.getTop().findInstances():
|
if self._global_container_stack.getTop().findInstances():
|
||||||
return True
|
return True
|
||||||
|
|
||||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
if stack.getTop().findInstances():
|
if stack.getTop().findInstances():
|
||||||
return True
|
return True
|
||||||
|
@ -450,7 +453,7 @@ class MachineManager(QObject):
|
||||||
return 0
|
return 0
|
||||||
num_user_settings = 0
|
num_user_settings = 0
|
||||||
num_user_settings += len(self._global_container_stack.getTop().findInstances())
|
num_user_settings += len(self._global_container_stack.getTop().findInstances())
|
||||||
stacks = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
num_user_settings += len(stack.getTop().findInstances())
|
num_user_settings += len(stack.getTop().findInstances())
|
||||||
return num_user_settings
|
return num_user_settings
|
||||||
|
@ -475,7 +478,7 @@ class MachineManager(QObject):
|
||||||
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||||
stacks = [stack]
|
stacks = [stack]
|
||||||
else:
|
else:
|
||||||
stacks = ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())
|
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
|
|
||||||
for stack in stacks:
|
for stack in stacks:
|
||||||
if stack is not None:
|
if stack is not None:
|
||||||
|
@ -640,7 +643,7 @@ class MachineManager(QObject):
|
||||||
if self._active_container_stack is None or self._global_container_stack is None:
|
if self._active_container_stack is None or self._global_container_stack is None:
|
||||||
return
|
return
|
||||||
new_value = self._active_container_stack.getProperty(key, "value")
|
new_value = self._active_container_stack.getProperty(key, "value")
|
||||||
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId())]
|
extruder_stacks = [stack for stack in ExtruderManager.getInstance().getActiveExtruderStacks()]
|
||||||
|
|
||||||
# check in which stack the value has to be replaced
|
# check in which stack the value has to be replaced
|
||||||
for extruder_stack in extruder_stacks:
|
for extruder_stack in extruder_stacks:
|
||||||
|
@ -892,7 +895,11 @@ class MachineManager(QObject):
|
||||||
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
extruder_nr = node.callDecoration("getActiveExtruderPosition")
|
||||||
|
|
||||||
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
|
if extruder_nr is not None and int(extruder_nr) > extruder_count - 1:
|
||||||
node.callDecoration("setActiveExtruder", extruder_manager.getExtruderStack(extruder_count - 1).getId())
|
extruder = extruder_manager.getExtruderStack(extruder_count - 1)
|
||||||
|
if extruder is not None:
|
||||||
|
node.callDecoration("setActiveExtruder", extruder.getId())
|
||||||
|
else:
|
||||||
|
Logger.log("w", "Could not find extruder to set active.")
|
||||||
|
|
||||||
# Make sure one of the extruder stacks is active
|
# Make sure one of the extruder stacks is active
|
||||||
extruder_manager.setActiveExtruderIndex(0)
|
extruder_manager.setActiveExtruderIndex(0)
|
||||||
|
|
|
@ -35,10 +35,9 @@ class MachineNameValidator(QObject):
|
||||||
## Check if a specified machine name is allowed.
|
## Check if a specified machine name is allowed.
|
||||||
#
|
#
|
||||||
# \param name The machine name to check.
|
# \param name The machine name to check.
|
||||||
# \param position The current position of the cursor in the text box.
|
|
||||||
# \return ``QValidator.Invalid`` if it's disallowed, or
|
# \return ``QValidator.Invalid`` if it's disallowed, or
|
||||||
# ``QValidator.Acceptable`` if it's allowed.
|
# ``QValidator.Acceptable`` if it's allowed.
|
||||||
def validate(self, name, position):
|
def validate(self, name):
|
||||||
#Check for file name length of the current settings container (which is the longest file we're saving with the name).
|
#Check for file name length of the current settings container (which is the longest file we're saving with the name).
|
||||||
try:
|
try:
|
||||||
filename_max_length = os.statvfs(Resources.getDataStoragePath()).f_namemax
|
filename_max_length = os.statvfs(Resources.getDataStoragePath()).f_namemax
|
||||||
|
@ -54,7 +53,7 @@ class MachineNameValidator(QObject):
|
||||||
## Updates the validation state of a machine name text field.
|
## Updates the validation state of a machine name text field.
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def updateValidation(self, new_name):
|
def updateValidation(self, new_name):
|
||||||
is_valid = self.validate(new_name, 0)
|
is_valid = self.validate(new_name)
|
||||||
if is_valid == QValidator.Acceptable:
|
if is_valid == QValidator.Acceptable:
|
||||||
self.validation_regex = "^.*$" #Matches anything.
|
self.validation_regex = "^.*$" #Matches anything.
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -18,7 +18,7 @@ class SidebarCustomMenuItemsModel(ListModel):
|
||||||
self.addRoleName(self.name_role, "name")
|
self.addRoleName(self.name_role, "name")
|
||||||
self.addRoleName(self.actions_role, "actions")
|
self.addRoleName(self.actions_role, "actions")
|
||||||
self.addRoleName(self.menu_item_role, "menu_item")
|
self.addRoleName(self.menu_item_role, "menu_item")
|
||||||
self.addRoleName(self.menu_item_icon_name_role, "iconName")
|
self.addRoleName(self.menu_item_icon_name_role, "icon_name")
|
||||||
self._updateExtensionList()
|
self._updateExtensionList()
|
||||||
|
|
||||||
def _updateExtensionList(self)-> None:
|
def _updateExtensionList(self)-> None:
|
||||||
|
|
|
@ -43,7 +43,9 @@ class UserChangesModel(ListModel):
|
||||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
if not global_stack:
|
if not global_stack:
|
||||||
return
|
return
|
||||||
stacks = ExtruderManager.getInstance().getActiveGlobalAndExtruderStacks()
|
|
||||||
|
stacks = [global_stack]
|
||||||
|
stacks.extend(global_stack.extruders.values())
|
||||||
|
|
||||||
# Check if the definition container has a translation file and ensure it's loaded.
|
# Check if the definition container has a translation file and ensure it's loaded.
|
||||||
definition = global_stack.getBottom()
|
definition = global_stack.getBottom()
|
||||||
|
|
|
@ -225,7 +225,7 @@ class ThreeMFReader(MeshReader):
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||||
return []
|
return None
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -85,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
MimeTypeDatabase.addMimeType(
|
|
||||||
MimeType(
|
|
||||||
name="application/x-curaproject+xml",
|
|
||||||
comment="Cura Project File",
|
|
||||||
suffixes=["curaproject.3mf"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
self._supported_extensions = [".3mf"]
|
self._supported_extensions = [".3mf"]
|
||||||
self._dialog = WorkspaceDialog()
|
self._dialog = WorkspaceDialog()
|
||||||
self._3mf_mesh_reader = None
|
self._3mf_mesh_reader = None
|
||||||
|
@ -726,8 +718,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
nodes = []
|
nodes = []
|
||||||
|
|
||||||
base_file_name = os.path.basename(file_name)
|
base_file_name = os.path.basename(file_name)
|
||||||
if base_file_name.endswith(".curaproject.3mf"):
|
|
||||||
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
|
|
||||||
self.setWorkspaceName(base_file_name)
|
self.setWorkspaceName(base_file_name)
|
||||||
return nodes
|
return nodes
|
||||||
|
|
||||||
|
@ -944,7 +934,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||||
root_material_id)
|
root_material_id)
|
||||||
|
|
||||||
if material_node is not None and material_node.getContainer() is not None:
|
if material_node is not None and material_node.getContainer() is not None:
|
||||||
extruder_stack.material = material_node.getContainer()
|
extruder_stack.material = material_node.getContainer() # type: InstanceContainer
|
||||||
|
|
||||||
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
def _applyChangesToMachine(self, global_stack, extruder_stack_dict):
|
||||||
# Clear all first
|
# Clear all first
|
||||||
|
|
|
@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
|
|
||||||
def getMetaData() -> Dict:
|
def getMetaData() -> Dict:
|
||||||
# Workaround for osx not supporting double file extensions correctly.
|
workspace_extension = "3mf"
|
||||||
if Platform.isOSX():
|
|
||||||
workspace_extension = "3mf"
|
|
||||||
else:
|
|
||||||
workspace_extension = "curaproject.3mf"
|
|
||||||
|
|
||||||
metaData = {}
|
metaData = {}
|
||||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for reading 3MF files.",
|
"description": "Provides support for reading 3MF files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,11 +15,7 @@ from UM.Platform import Platform
|
||||||
i18n_catalog = i18nCatalog("uranium")
|
i18n_catalog = i18nCatalog("uranium")
|
||||||
|
|
||||||
def getMetaData():
|
def getMetaData():
|
||||||
# Workarround for osx not supporting double file extensions correctly.
|
workspace_extension = "3mf"
|
||||||
if Platform.isOSX():
|
|
||||||
workspace_extension = "3mf"
|
|
||||||
else:
|
|
||||||
workspace_extension = "curaproject.3mf"
|
|
||||||
|
|
||||||
metaData = {}
|
metaData = {}
|
||||||
|
|
||||||
|
@ -36,7 +32,7 @@ def getMetaData():
|
||||||
"output": [{
|
"output": [{
|
||||||
"extension": workspace_extension,
|
"extension": workspace_extension,
|
||||||
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
|
||||||
"mime_type": "application/x-curaproject+xml",
|
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
|
||||||
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for writing 3MF files.",
|
"description": "Provides support for writing 3MF files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,103 @@
|
||||||
|
[3.5.0]
|
||||||
|
*Monitor page
|
||||||
|
The monitor page of Ultimaker Cura has been remodeled for better consistency with the Cura Connect ‘Print jobs’ interface. This means less switching between interfaces, and more control from within Ultimaker Cura.
|
||||||
|
|
||||||
|
*Open recent projects
|
||||||
|
Project files can now be found in the ‘Open Recent’ menu.
|
||||||
|
|
||||||
|
*New tool hotkeys
|
||||||
|
New hotkeys have been assigned for quick toggling between the translate (T), scale (S), rotate (R) and mirror (M) tools.
|
||||||
|
|
||||||
|
*Project files use 3MF only
|
||||||
|
A 3MF extension is now used for project files. The ‘.curaproject’ extension is no longer used.
|
||||||
|
|
||||||
|
*Camera maximum zoom
|
||||||
|
The maximum zoom has been adjusted to scale with the size of the selected printer. This fixes third-party printers with huge build volumes to be correctly visible.
|
||||||
|
|
||||||
|
*Corrected width of layer number box
|
||||||
|
The layer number indicator in the layer view now displays numbers above 999 correctly.
|
||||||
|
|
||||||
|
*Materials preferences
|
||||||
|
This screen has been redesigned to improve user experience. Materials can now be set as a favorites, so they can be easily accessed in the material selection panel at the top-right of the screen.
|
||||||
|
|
||||||
|
*Installed packages checkmark
|
||||||
|
Packages that are already installed in the Toolbox are now have a checkmark for easy reference.
|
||||||
|
|
||||||
|
*Mac OSX save dialog
|
||||||
|
The save dialog has been restored to its native behavior and bugs have been fixed.
|
||||||
|
|
||||||
|
*Removed .gz extension
|
||||||
|
Saving compressed g-code files from the save dialog has been removed because of incompatibility with MacOS. If sending jobs over Wi-Fi, g-code is still compressed.
|
||||||
|
|
||||||
|
*Updates to Chinese translations
|
||||||
|
Improved and updated Chinese translations. Contributed by MarmaladeForMeat.
|
||||||
|
|
||||||
|
*Save project
|
||||||
|
Saving the project no longer triggers the project to reslice.
|
||||||
|
|
||||||
|
*File menu
|
||||||
|
The Save option in the file menu now saves project files. The export option now saves other types of files, such as STL.
|
||||||
|
|
||||||
|
*Improved processing of overhang walls
|
||||||
|
Overhang walls are detected and printed with different speeds. It will not start a perimeter on an overhanging wall. The quality of overhanging walls may be improved by printing those at a different speed. Contributed by smartavionics.
|
||||||
|
|
||||||
|
*Prime tower reliability
|
||||||
|
The prime tower has been improved for better reliability. This is especially useful when printing with two materials that do not adhere well.
|
||||||
|
|
||||||
|
*Support infill line direction
|
||||||
|
The support infill lines can now be rotated to increase the supporting capabilities and reduce artifacts on the model. This setting rotates existing patterns, like triangle support infill. Contributed by fieldOfView.
|
||||||
|
|
||||||
|
*Minimum polygon circumference
|
||||||
|
Polygons in sliced layers that have a circumference smaller than the setting value will be filtered out. Lower values lead to higher resolution meshes at the cost of increased slicing time. This setting is ideal for very tiny prints with a lot of detail, or for SLA printers. Contributed by cubiq.
|
||||||
|
|
||||||
|
*Initial layer support line distance
|
||||||
|
This setting enables the user to reduce or increase the density of the support initial layer in order to increase or reduce adhesion to the build plate and the overall strength.
|
||||||
|
|
||||||
|
*Extra infill wall line count
|
||||||
|
Adds extra walls around infill. Contributed by BagelOrb.
|
||||||
|
|
||||||
|
*Multiply infill
|
||||||
|
Creates multiple infill lines on the same pattern for sturdier infill. Contributed by BagelOrb.
|
||||||
|
|
||||||
|
*Connected infill polygons
|
||||||
|
Connecting infill lines now also works with concentric and cross infill patterns. The benefit would be stronger infill and more consistent material flow/saving retractions. Contributed by BagelOrb.
|
||||||
|
|
||||||
|
*Fan speed override
|
||||||
|
New setting to modify the fan speed of supported areas. This setting can be found in Support settings > Fan Speed Override when support is enabled. Contributed by smartavionics.
|
||||||
|
|
||||||
|
*Minimum wall flow
|
||||||
|
New setting to define a minimum flow for thin printed walls. Contributed by smartavionics.
|
||||||
|
|
||||||
|
*Custom support plugin
|
||||||
|
A tool downloadable from the toolbox, similar to the support blocker, that adds cubes of support to the model manually by clicking parts of it. Contributed by Lokster.
|
||||||
|
|
||||||
|
*Quickly toggle autoslicing
|
||||||
|
Adds a pause/play button to the progress bar to quickly toggle autoslicing. Contributed by fieldOfview.
|
||||||
|
|
||||||
|
*Cura-DuetRRFPlugin
|
||||||
|
Adds output devices for a Duet RepRapFirmware printer: "Print", "Simulate", and "Upload". Contributed by Kriechi.
|
||||||
|
|
||||||
|
*Dremel 3D20
|
||||||
|
This plugin adds the Dremel printer to Ultimaker Cura. Contributed by Kriechi.
|
||||||
|
|
||||||
|
*Bug fixes
|
||||||
|
- Removed extra M109 commands. Older versions would generate superfluous M109 commands. This has been fixed for better temperature stability when printing.
|
||||||
|
- Fixed minor mesh handling bugs. A few combinations of modifier meshes now lead to expected behavior.
|
||||||
|
- Removed unnecessary travels. Connected infill lines are now always printed completely connected, without unnecessary travel moves.
|
||||||
|
- Removed concentric 3D infill. This infill type has been removed due to lack of reliability.
|
||||||
|
- Extra skin wall count. Fixed an issue that caused extra print moves with this setting enabled.
|
||||||
|
- Concentric skin. Small gaps in concentric skin are now filled correctly.
|
||||||
|
- Order of printed models. The order of a large batch of printed models is now more consistent, instead of random.
|
||||||
|
|
||||||
|
*Third party printers
|
||||||
|
- TiZYX
|
||||||
|
- Winbo
|
||||||
|
- Tevo Tornado
|
||||||
|
- Creality CR-10S
|
||||||
|
- Wanhao Duplicator
|
||||||
|
- Deltacomb (update)
|
||||||
|
- Dacoma (update)
|
||||||
|
|
||||||
[3.4.1]
|
[3.4.1]
|
||||||
*Bug fixes
|
*Bug fixes
|
||||||
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.
|
- Fixed an issue that would occasionally cause an unnecessary extra skin wall to be printed, which increased print time.
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Shows changes since latest checked version.",
|
"description": "Shows changes since latest checked version.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -179,8 +179,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
# This is useful for debugging and used to actually start the engine.
|
# This is useful for debugging and used to actually start the engine.
|
||||||
# \return list of commands and args / parameters.
|
# \return list of commands and args / parameters.
|
||||||
def getEngineCommand(self) -> List[str]:
|
def getEngineCommand(self) -> List[str]:
|
||||||
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), ""]
|
||||||
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
|
||||||
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
|
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
|
||||||
|
@ -343,7 +342,7 @@ class CuraEngineBackend(QObject, Backend):
|
||||||
if not self._global_container_stack:
|
if not self._global_container_stack:
|
||||||
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
Logger.log("w", "Global container stack not assigned to CuraEngineBackend!")
|
||||||
return
|
return
|
||||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
|
||||||
error_keys = [] #type: List[str]
|
error_keys = [] #type: List[str]
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
error_keys.extend(extruder.getErrorKeys())
|
error_keys.extend(extruder.getErrorKeys())
|
||||||
|
|
|
@ -178,7 +178,7 @@ class ProcessSlicedLayersJob(Job):
|
||||||
# Find out colors per extruder
|
# Find out colors per extruder
|
||||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
manager = ExtruderManager.getInstance()
|
manager = ExtruderManager.getInstance()
|
||||||
extruders = list(manager.getMachineExtruders(global_container_stack.getId()))
|
extruders = manager.getActiveExtruderStacks()
|
||||||
if extruders:
|
if extruders:
|
||||||
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
material_color_map = numpy.zeros((len(extruders), 4), dtype=numpy.float32)
|
||||||
for extruder in extruders:
|
for extruder in extruders:
|
||||||
|
|
|
@ -220,8 +220,10 @@ class StartSliceJob(Job):
|
||||||
stack = global_stack
|
stack = global_stack
|
||||||
skip_group = False
|
skip_group = False
|
||||||
for node in group:
|
for node in group:
|
||||||
|
# Only check if the printing extruder is enabled for printing meshes
|
||||||
|
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
|
||||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||||
if not extruders_enabled[extruder_position]:
|
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
|
||||||
skip_group = True
|
skip_group = True
|
||||||
has_model_with_disabled_extruders = True
|
has_model_with_disabled_extruders = True
|
||||||
associated_disabled_extruders.add(extruder_position)
|
associated_disabled_extruders.add(extruder_position)
|
||||||
|
@ -331,7 +333,7 @@ class StartSliceJob(Job):
|
||||||
"-1": self._buildReplacementTokens(global_stack)
|
"-1": self._buildReplacementTokens(global_stack)
|
||||||
}
|
}
|
||||||
|
|
||||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(global_stack.getId()):
|
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
|
||||||
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
|
||||||
|
|
||||||
|
@ -438,8 +440,7 @@ class StartSliceJob(Job):
|
||||||
Job.yieldThread()
|
Job.yieldThread()
|
||||||
|
|
||||||
# Ensure that the engine is aware what the build extruder is.
|
# Ensure that the engine is aware what the build extruder is.
|
||||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
changed_setting_keys.add("extruder_nr")
|
||||||
changed_setting_keys.add("extruder_nr")
|
|
||||||
|
|
||||||
# Get values for all changed settings
|
# Get values for all changed settings
|
||||||
for key in changed_setting_keys:
|
for key in changed_setting_keys:
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "CuraEngine Backend",
|
"name": "CuraEngine Backend",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides the link to the CuraEngine slicing backend.",
|
"description": "Provides the link to the CuraEngine slicing backend.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing Cura profiles.",
|
"description": "Provides support for importing Cura profiles.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for exporting Cura profiles.",
|
"description": "Provides support for exporting Cura profiles.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog":"cura"
|
"i18n-catalog":"cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Checks for firmware updates.",
|
"description": "Checks for firmware updates.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,15 +4,22 @@
|
||||||
import gzip
|
import gzip
|
||||||
|
|
||||||
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
|
from UM.Mesh.MeshReader import MeshReader #The class we're extending/implementing.
|
||||||
|
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType #To add the .gcode.gz files to the MIME type database.
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
|
||||||
|
|
||||||
## A file reader that reads gzipped g-code.
|
## A file reader that reads gzipped g-code.
|
||||||
#
|
#
|
||||||
# If you're zipping g-code, you might as well use gzip!
|
# If you're zipping g-code, you might as well use gzip!
|
||||||
class GCodeGzReader(MeshReader):
|
class GCodeGzReader(MeshReader):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
MimeTypeDatabase.addMimeType(
|
||||||
|
MimeType(
|
||||||
|
name = "application/x-cura-compressed-gcode-file",
|
||||||
|
comment = "Cura Compressed GCode File",
|
||||||
|
suffixes = ["gcode.gz"]
|
||||||
|
)
|
||||||
|
)
|
||||||
self._supported_extensions = [".gcode.gz"]
|
self._supported_extensions = [".gcode.gz"]
|
||||||
|
|
||||||
def _read(self, file_name):
|
def _read(self, file_name):
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Reads g-code from a compressed archive.",
|
"description": "Reads g-code from a compressed archive.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,10 @@ catalog = i18nCatalog("cura")
|
||||||
#
|
#
|
||||||
# If you're zipping g-code, you might as well use gzip!
|
# If you're zipping g-code, you might as well use gzip!
|
||||||
class GCodeGzWriter(MeshWriter):
|
class GCodeGzWriter(MeshWriter):
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__(add_to_recent_files = False)
|
||||||
|
|
||||||
## Writes the gzipped g-code to a stream.
|
## Writes the gzipped g-code to a stream.
|
||||||
#
|
#
|
||||||
# Note that even though the function accepts a collection of nodes, the
|
# Note that even though the function accepts a collection of nodes, the
|
||||||
|
|
|
@ -16,7 +16,8 @@ def getMetaData():
|
||||||
"extension": file_extension,
|
"extension": file_extension,
|
||||||
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
|
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
|
||||||
"mime_type": "application/gzip",
|
"mime_type": "application/gzip",
|
||||||
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
|
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode,
|
||||||
|
"hide_in_file_dialog": True,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Writes g-code to a compressed archive.",
|
"description": "Writes g-code to a compressed archive.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing profiles from g-code files.",
|
"description": "Provides support for importing profiles from g-code files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -275,7 +275,7 @@ class FlavorParser:
|
||||||
## For showing correct x, y offsets for each extruder
|
## For showing correct x, y offsets for each extruder
|
||||||
def _extruderOffsets(self) -> Dict[int, List[float]]:
|
def _extruderOffsets(self) -> Dict[int, List[float]]:
|
||||||
result = {}
|
result = {}
|
||||||
for extruder in ExtruderManager.getInstance().getExtruderStacks():
|
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||||
result[int(extruder.getMetaData().get("position", "0"))] = [
|
result[int(extruder.getMetaData().get("position", "0"))] = [
|
||||||
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
extruder.getProperty("machine_nozzle_offset_x", "value"),
|
||||||
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
extruder.getProperty("machine_nozzle_offset_y", "value")]
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Copyright (c) 2017 Aleph Objects, Inc.
|
# Copyright (c) 2017 Aleph Objects, Inc.
|
||||||
|
# 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.FileHandler.FileReader import FileReader
|
from UM.FileHandler.FileReader import FileReader
|
||||||
|
@ -11,13 +12,7 @@ catalog = i18nCatalog("cura")
|
||||||
from . import MarlinFlavorParser, RepRapFlavorParser
|
from . import MarlinFlavorParser, RepRapFlavorParser
|
||||||
|
|
||||||
|
|
||||||
MimeTypeDatabase.addMimeType(
|
|
||||||
MimeType(
|
|
||||||
name = "application/x-cura-gcode-file",
|
|
||||||
comment = "Cura GCode File",
|
|
||||||
suffixes = ["gcode", "gcode.gz"]
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Class for loading and parsing G-code files
|
# Class for loading and parsing G-code files
|
||||||
|
@ -29,7 +24,15 @@ class GCodeReader(MeshReader):
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
MimeTypeDatabase.addMimeType(
|
||||||
|
MimeType(
|
||||||
|
name = "application/x-cura-gcode-file",
|
||||||
|
comment = "Cura GCode File",
|
||||||
|
suffixes = ["gcode"]
|
||||||
|
)
|
||||||
|
)
|
||||||
self._supported_extensions = [".gcode", ".g"]
|
self._supported_extensions = [".gcode", ".g"]
|
||||||
|
|
||||||
self._flavor_reader = None
|
self._flavor_reader = None
|
||||||
|
|
||||||
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Victor Larchenko",
|
"author": "Victor Larchenko",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Allows loading and displaying G-code files.",
|
"description": "Allows loading and displaying G-code files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -47,7 +47,7 @@ class GCodeWriter(MeshWriter):
|
||||||
_setting_keyword = ";SETTING_"
|
_setting_keyword = ";SETTING_"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__(add_to_recent_files = False)
|
||||||
|
|
||||||
self._application = Application.getInstance()
|
self._application = Application.getInstance()
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Writes g-code to a file.",
|
"description": "Writes g-code to a file.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,8 @@ Cura.MachineAction
|
||||||
property var extrudersModel: Cura.ExtrudersModel{}
|
property var extrudersModel: Cura.ExtrudersModel{}
|
||||||
property int extruderTabsCount: 0
|
property int extruderTabsCount: 0
|
||||||
|
|
||||||
|
property var activeMachineId: Cura.MachineManager.activeMachine != null ? Cura.MachineManager.activeMachine.id : ""
|
||||||
|
|
||||||
Connections
|
Connections
|
||||||
{
|
{
|
||||||
target: base.extrudersModel
|
target: base.extrudersModel
|
||||||
|
@ -511,7 +513,7 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return Cura.MachineManager.activeMachineId;
|
return base.activeMachineId
|
||||||
}
|
}
|
||||||
key: settingKey
|
key: settingKey
|
||||||
watchedProperties: [ "value", "description" ]
|
watchedProperties: [ "value", "description" ]
|
||||||
|
@ -564,7 +566,7 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return Cura.MachineManager.activeMachineId;
|
return base.activeMachineId
|
||||||
}
|
}
|
||||||
key: settingKey
|
key: settingKey
|
||||||
watchedProperties: [ "value", "description" ]
|
watchedProperties: [ "value", "description" ]
|
||||||
|
@ -655,7 +657,7 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return Cura.MachineManager.activeMachineId;
|
return base.activeMachineId
|
||||||
}
|
}
|
||||||
key: settingKey
|
key: settingKey
|
||||||
watchedProperties: [ "value", "options", "description" ]
|
watchedProperties: [ "value", "options", "description" ]
|
||||||
|
@ -754,7 +756,7 @@ Cura.MachineAction
|
||||||
}
|
}
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return Cura.MachineManager.activeMachineId;
|
return base.activeMachineId
|
||||||
}
|
}
|
||||||
key: settingKey
|
key: settingKey
|
||||||
watchedProperties: [ "value", "description" ]
|
watchedProperties: [ "value", "description" ]
|
||||||
|
@ -879,7 +881,7 @@ Cura.MachineAction
|
||||||
{
|
{
|
||||||
id: machineExtruderCountProvider
|
id: machineExtruderCountProvider
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: base.activeMachineId
|
||||||
key: "machine_extruder_count"
|
key: "machine_extruder_count"
|
||||||
watchedProperties: [ "value", "description" ]
|
watchedProperties: [ "value", "description" ]
|
||||||
storeIndex: manager.containerIndex
|
storeIndex: manager.containerIndex
|
||||||
|
@ -889,7 +891,7 @@ Cura.MachineAction
|
||||||
{
|
{
|
||||||
id: machineHeadPolygonProvider
|
id: machineHeadPolygonProvider
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStackId: base.activeMachineId
|
||||||
key: "machine_head_with_fans_polygon"
|
key: "machine_head_with_fans_polygon"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: manager.containerIndex
|
storeIndex: manager.containerIndex
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "fieldOfView",
|
"author": "fieldOfView",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Model Checker",
|
"name": "Model Checker",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "0.1",
|
"version": "0.1",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a monitor stage in Cura.",
|
"description": "Provides a monitor stage in Cura.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -17,7 +17,6 @@ Item {
|
||||||
|
|
||||||
width: childrenRect.width;
|
width: childrenRect.width;
|
||||||
height: childrenRect.height;
|
height: childrenRect.height;
|
||||||
|
|
||||||
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
|
property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
|
||||||
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
|
"travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
|
||||||
|
|
||||||
|
@ -45,7 +44,7 @@ Item {
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
{
|
{
|
||||||
id: meshTypePropertyProvider
|
id: meshTypePropertyProvider
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStack: Cura.MachineManager.activeMachine
|
||||||
watchedProperties: [ "enabled" ]
|
watchedProperties: [ "enabled" ]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,7 +517,7 @@ Item {
|
||||||
{
|
{
|
||||||
id: machineExtruderCount
|
id: machineExtruderCount
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStack: Cura.MachineManager.activeMachine
|
||||||
key: "machine_extruder_count"
|
key: "machine_extruder_count"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 0
|
storeIndex: 0
|
||||||
|
@ -528,7 +527,7 @@ Item {
|
||||||
{
|
{
|
||||||
id: printSequencePropertyProvider
|
id: printSequencePropertyProvider
|
||||||
|
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStack: Cura.MachineManager.activeMachine
|
||||||
key: "print_sequence"
|
key: "print_sequence"
|
||||||
watchedProperties: [ "value" ]
|
watchedProperties: [ "value" ]
|
||||||
storeIndex: 0
|
storeIndex: 0
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides the Per Model Settings.",
|
"description": "Provides the Per Model Settings.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
|
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
|
||||||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||||
|
|
||||||
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||||
|
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
|
@ -260,6 +261,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
# Create the plugin dialog component
|
# Create the plugin dialog component
|
||||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
|
||||||
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
|
||||||
|
return
|
||||||
Logger.log("d", "Post processing view created.")
|
Logger.log("d", "Post processing view created.")
|
||||||
|
|
||||||
# Create the save button component
|
# Create the save button component
|
||||||
|
@ -269,6 +273,9 @@ class PostProcessingPlugin(QObject, Extension):
|
||||||
def showPopup(self):
|
def showPopup(self):
|
||||||
if self._view is None:
|
if self._view is None:
|
||||||
self._createView()
|
self._createView()
|
||||||
|
if self._view is None:
|
||||||
|
Logger.log("e", "Not creating PostProcessing window since the QML component failed to be created.")
|
||||||
|
return
|
||||||
self._view.show()
|
self._view.show()
|
||||||
|
|
||||||
## Property changed: trigger re-slice
|
## Property changed: trigger re-slice
|
||||||
|
|
|
@ -384,7 +384,7 @@ UM.Dialog
|
||||||
UM.SettingPropertyProvider
|
UM.SettingPropertyProvider
|
||||||
{
|
{
|
||||||
id: inheritStackProvider
|
id: inheritStackProvider
|
||||||
containerStackId: Cura.MachineManager.activeMachineId
|
containerStack: Cura.MachineManager.activeMachine
|
||||||
key: model.key ? model.key : "None"
|
key: model.key ? model.key : "None"
|
||||||
watchedProperties: [ "limit_to_extruder" ]
|
watchedProperties: [ "limit_to_extruder" ]
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
"name": "Post Processing",
|
"name": "Post Processing",
|
||||||
"author": "Ultimaker",
|
"author": "Ultimaker",
|
||||||
"version": "2.2",
|
"version": "2.2",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"description": "Extension that allows for user created scripts for post processing",
|
"description": "Extension that allows for user created scripts for post processing",
|
||||||
"catalog": "cura"
|
"catalog": "cura"
|
||||||
}
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
from ..Script import Script
|
||||||
|
|
||||||
|
class PauseAtHeightRepRapFirmwareDuet(Script):
|
||||||
|
|
||||||
|
def getSettingDataString(self):
|
||||||
|
return """{
|
||||||
|
"name": "Pause at height for RepRapFirmware DuetWifi / Duet Ethernet / Duet Maestro",
|
||||||
|
"key": "PauseAtHeightRepRapFirmwareDuet",
|
||||||
|
"metadata": {},
|
||||||
|
"version": 2,
|
||||||
|
"settings":
|
||||||
|
{
|
||||||
|
"pause_height":
|
||||||
|
{
|
||||||
|
"label": "Pause height",
|
||||||
|
"description": "At what height should the pause occur",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 5.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}"""
|
||||||
|
|
||||||
|
def execute(self, data):
|
||||||
|
current_z = 0.
|
||||||
|
pause_z = self.getSettingValueByKey("pause_height")
|
||||||
|
|
||||||
|
layers_started = False
|
||||||
|
for layer_number, layer in enumerate(data):
|
||||||
|
lines = layer.split("\n")
|
||||||
|
for line in lines:
|
||||||
|
if ";LAYER:0" in line:
|
||||||
|
layers_started = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not layers_started:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
|
||||||
|
current_z = self.getValue(line, 'Z')
|
||||||
|
if current_z != None:
|
||||||
|
if current_z >= pause_z:
|
||||||
|
prepend_gcode = ";TYPE:CUSTOM\n"
|
||||||
|
prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z
|
||||||
|
prepend_gcode += self.putValue(M = 226) + "\n"
|
||||||
|
layer = prepend_gcode + layer
|
||||||
|
|
||||||
|
data[layer_number] = layer # Override the data of this layer with the modified data
|
||||||
|
return data
|
||||||
|
break
|
||||||
|
return data
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a prepare stage in Cura.",
|
"description": "Provides a prepare stage in Cura.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Provides removable drive hotplugging and writing support.",
|
"description": "Provides removable drive hotplugging and writing support.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
|
||||||
import UM 1.0 as UM
|
import UM 1.0 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: sliderRoot
|
id: sliderRoot
|
||||||
|
|
||||||
// handle properties
|
// handle properties
|
||||||
|
@ -39,40 +40,49 @@ Item {
|
||||||
property real lowerValue: minimumValue
|
property real lowerValue: minimumValue
|
||||||
|
|
||||||
property bool layersVisible: true
|
property bool layersVisible: true
|
||||||
|
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
|
||||||
|
|
||||||
function getUpperValueFromSliderHandle() {
|
function getUpperValueFromSliderHandle()
|
||||||
|
{
|
||||||
return upperHandle.getValue()
|
return upperHandle.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setUpperValue(value) {
|
function setUpperValue(value)
|
||||||
|
{
|
||||||
upperHandle.setValue(value)
|
upperHandle.setValue(value)
|
||||||
updateRangeHandle()
|
updateRangeHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLowerValueFromSliderHandle() {
|
function getLowerValueFromSliderHandle()
|
||||||
|
{
|
||||||
return lowerHandle.getValue()
|
return lowerHandle.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setLowerValue(value) {
|
function setLowerValue(value)
|
||||||
|
{
|
||||||
lowerHandle.setValue(value)
|
lowerHandle.setValue(value)
|
||||||
updateRangeHandle()
|
updateRangeHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRangeHandle() {
|
function updateRangeHandle()
|
||||||
|
{
|
||||||
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
|
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
|
||||||
}
|
}
|
||||||
|
|
||||||
// set the active handle to show only one label at a time
|
// set the active handle to show only one label at a time
|
||||||
function setActiveHandle(handle) {
|
function setActiveHandle(handle)
|
||||||
|
{
|
||||||
activeHandle = handle
|
activeHandle = handle
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeValue(value) {
|
function normalizeValue(value)
|
||||||
|
{
|
||||||
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// slider track
|
// slider track
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: track
|
id: track
|
||||||
|
|
||||||
width: sliderRoot.trackThickness
|
width: sliderRoot.trackThickness
|
||||||
|
@ -86,7 +96,8 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Range handle
|
// Range handle
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: rangeHandle
|
id: rangeHandle
|
||||||
|
|
||||||
y: upperHandle.y + upperHandle.height
|
y: upperHandle.y + upperHandle.height
|
||||||
|
@ -96,7 +107,9 @@ Item {
|
||||||
visible: sliderRoot.layersVisible
|
visible: sliderRoot.layersVisible
|
||||||
|
|
||||||
// set the new value when dragging
|
// set the new value when dragging
|
||||||
function onHandleDragged () {
|
function onHandleDragged()
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
|
||||||
upperHandle.y = y - upperHandle.height
|
upperHandle.y = y - upperHandle.height
|
||||||
lowerHandle.y = y + height
|
lowerHandle.y = y + height
|
||||||
|
@ -109,7 +122,14 @@ Item {
|
||||||
UM.SimulationView.setMinimumLayer(lowerValue)
|
UM.SimulationView.setMinimumLayer(lowerValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
function setValue (value) {
|
function setValueManually(value)
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
upperHandle.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function setValue(value)
|
||||||
|
{
|
||||||
var range = sliderRoot.upperValue - sliderRoot.lowerValue
|
var range = sliderRoot.upperValue - sliderRoot.lowerValue
|
||||||
value = Math.min(value, sliderRoot.maximumValue)
|
value = Math.min(value, sliderRoot.maximumValue)
|
||||||
value = Math.max(value, sliderRoot.minimumValue + range)
|
value = Math.max(value, sliderRoot.minimumValue + range)
|
||||||
|
@ -118,17 +138,20 @@ Item {
|
||||||
UM.SimulationView.setMinimumLayer(value - range)
|
UM.SimulationView.setMinimumLayer(value - range)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
width: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
||||||
height: parent.height + sliderRoot.handleSize
|
height: parent.height + sliderRoot.handleSize
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
color: sliderRoot.rangeHandleColor
|
color: sliderRoot.rangeHandleColor
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
drag {
|
drag
|
||||||
|
{
|
||||||
target: parent
|
target: parent
|
||||||
axis: Drag.YAxis
|
axis: Drag.YAxis
|
||||||
minimumY: upperHandle.height
|
minimumY: upperHandle.height
|
||||||
|
@ -139,7 +162,8 @@ Item {
|
||||||
onPressed: sliderRoot.setActiveHandle(rangeHandle)
|
onPressed: sliderRoot.setActiveHandle(rangeHandle)
|
||||||
}
|
}
|
||||||
|
|
||||||
SimulationSliderLabel {
|
SimulationSliderLabel
|
||||||
|
{
|
||||||
id: rangleHandleLabel
|
id: rangleHandleLabel
|
||||||
|
|
||||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||||
|
@ -152,12 +176,13 @@ Item {
|
||||||
maximumValue: sliderRoot.maximumValue
|
maximumValue: sliderRoot.maximumValue
|
||||||
value: sliderRoot.upperValue
|
value: sliderRoot.upperValue
|
||||||
busy: UM.SimulationView.busy
|
busy: UM.SimulationView.busy
|
||||||
setValue: rangeHandle.setValue // connect callback functions
|
setValue: rangeHandle.setValueManually // connect callback functions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Upper handle
|
// Upper handle
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: upperHandle
|
id: upperHandle
|
||||||
|
|
||||||
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
|
y: sliderRoot.height - (sliderRoot.minimumRangeHandleSize + 2 * sliderRoot.handleSize)
|
||||||
|
@ -168,10 +193,13 @@ Item {
|
||||||
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
|
color: upperHandleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.upperHandleColor
|
||||||
visible: sliderRoot.layersVisible
|
visible: sliderRoot.layersVisible
|
||||||
|
|
||||||
function onHandleDragged () {
|
function onHandleDragged()
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
|
||||||
// don't allow the lower handle to be heigher than the upper handle
|
// don't allow the lower handle to be heigher than the upper handle
|
||||||
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize) {
|
if (lowerHandle.y - (y + height) < sliderRoot.minimumRangeHandleSize)
|
||||||
|
{
|
||||||
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
|
lowerHandle.y = y + height + sliderRoot.minimumRangeHandleSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,15 +211,23 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the upper value based on the slider position
|
// get the upper value based on the slider position
|
||||||
function getValue () {
|
function getValue()
|
||||||
|
{
|
||||||
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
|
var result = y / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize))
|
||||||
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
|
result = sliderRoot.maximumValue + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumValue))
|
||||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setValueManually(value)
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
upperHandle.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
// set the slider position based on the upper value
|
// set the slider position based on the upper value
|
||||||
function setValue (value) {
|
function setValue(value)
|
||||||
|
{
|
||||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
value = sliderRoot.normalizeValue(value)
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
|
@ -209,10 +245,12 @@ Item {
|
||||||
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
Keys.onDownPressed: upperHandleLabel.setValue(upperHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||||
|
|
||||||
// dragging
|
// dragging
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
drag {
|
drag
|
||||||
|
{
|
||||||
target: parent
|
target: parent
|
||||||
axis: Drag.YAxis
|
axis: Drag.YAxis
|
||||||
minimumY: 0
|
minimumY: 0
|
||||||
|
@ -220,13 +258,15 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPositionChanged: parent.onHandleDragged()
|
onPositionChanged: parent.onHandleDragged()
|
||||||
onPressed: {
|
onPressed:
|
||||||
|
{
|
||||||
sliderRoot.setActiveHandle(upperHandle)
|
sliderRoot.setActiveHandle(upperHandle)
|
||||||
upperHandleLabel.forceActiveFocus()
|
upperHandleLabel.forceActiveFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SimulationSliderLabel {
|
SimulationSliderLabel
|
||||||
|
{
|
||||||
id: upperHandleLabel
|
id: upperHandleLabel
|
||||||
|
|
||||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||||
|
@ -239,12 +279,13 @@ Item {
|
||||||
maximumValue: sliderRoot.maximumValue
|
maximumValue: sliderRoot.maximumValue
|
||||||
value: sliderRoot.upperValue
|
value: sliderRoot.upperValue
|
||||||
busy: UM.SimulationView.busy
|
busy: UM.SimulationView.busy
|
||||||
setValue: upperHandle.setValue // connect callback functions
|
setValue: upperHandle.setValueManually // connect callback functions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Lower handle
|
// Lower handle
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: lowerHandle
|
id: lowerHandle
|
||||||
|
|
||||||
y: sliderRoot.height - sliderRoot.handleSize
|
y: sliderRoot.height - sliderRoot.handleSize
|
||||||
|
@ -256,10 +297,13 @@ Item {
|
||||||
|
|
||||||
visible: sliderRoot.layersVisible
|
visible: sliderRoot.layersVisible
|
||||||
|
|
||||||
function onHandleDragged () {
|
function onHandleDragged()
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
|
||||||
// don't allow the upper handle to be lower than the lower handle
|
// don't allow the upper handle to be lower than the lower handle
|
||||||
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize) {
|
if (y - (upperHandle.y + upperHandle.height) < sliderRoot.minimumRangeHandleSize)
|
||||||
|
{
|
||||||
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
|
upperHandle.y = y - (upperHandle.heigth + sliderRoot.minimumRangeHandleSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -271,15 +315,24 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the lower value from the current slider position
|
// get the lower value from the current slider position
|
||||||
function getValue () {
|
function getValue()
|
||||||
|
{
|
||||||
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
|
var result = (y - (sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize)) / (sliderRoot.height - (2 * sliderRoot.handleSize + sliderRoot.minimumRangeHandleSize));
|
||||||
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
|
result = sliderRoot.maximumValue - sliderRoot.minimumRange + result * (sliderRoot.minimumValue - (sliderRoot.maximumValue - sliderRoot.minimumRange))
|
||||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setValueManually(value)
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
lowerHandle.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
// set the slider position based on the lower value
|
// set the slider position based on the lower value
|
||||||
function setValue (value) {
|
function setValue(value)
|
||||||
|
{
|
||||||
|
|
||||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
value = sliderRoot.normalizeValue(value)
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
|
@ -297,10 +350,12 @@ Item {
|
||||||
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
Keys.onDownPressed: lowerHandleLabel.setValue(lowerHandleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||||
|
|
||||||
// dragging
|
// dragging
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
drag {
|
drag
|
||||||
|
{
|
||||||
target: parent
|
target: parent
|
||||||
axis: Drag.YAxis
|
axis: Drag.YAxis
|
||||||
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
|
minimumY: upperHandle.height + sliderRoot.minimumRangeHandleSize
|
||||||
|
@ -308,13 +363,15 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onPositionChanged: parent.onHandleDragged()
|
onPositionChanged: parent.onHandleDragged()
|
||||||
onPressed: {
|
onPressed:
|
||||||
|
{
|
||||||
sliderRoot.setActiveHandle(lowerHandle)
|
sliderRoot.setActiveHandle(lowerHandle)
|
||||||
lowerHandleLabel.forceActiveFocus()
|
lowerHandleLabel.forceActiveFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SimulationSliderLabel {
|
SimulationSliderLabel
|
||||||
|
{
|
||||||
id: lowerHandleLabel
|
id: lowerHandleLabel
|
||||||
|
|
||||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||||
|
@ -327,7 +384,7 @@ Item {
|
||||||
maximumValue: sliderRoot.maximumValue
|
maximumValue: sliderRoot.maximumValue
|
||||||
value: sliderRoot.lowerValue
|
value: sliderRoot.lowerValue
|
||||||
busy: UM.SimulationView.busy
|
busy: UM.SimulationView.busy
|
||||||
setValue: lowerHandle.setValue // connect callback functions
|
setValue: lowerHandle.setValueManually // connect callback functions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -9,7 +9,8 @@ import QtQuick.Controls.Styles 1.1
|
||||||
import UM 1.0 as UM
|
import UM 1.0 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: sliderRoot
|
id: sliderRoot
|
||||||
|
|
||||||
// handle properties
|
// handle properties
|
||||||
|
@ -34,26 +35,32 @@ Item {
|
||||||
property real handleValue: maximumValue
|
property real handleValue: maximumValue
|
||||||
|
|
||||||
property bool pathsVisible: true
|
property bool pathsVisible: true
|
||||||
|
property bool manuallyChanged: true // Indicates whether the value was changed manually or during simulation
|
||||||
|
|
||||||
function getHandleValueFromSliderHandle () {
|
function getHandleValueFromSliderHandle()
|
||||||
|
{
|
||||||
return handle.getValue()
|
return handle.getValue()
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHandleValue (value) {
|
function setHandleValue(value)
|
||||||
|
{
|
||||||
handle.setValue(value)
|
handle.setValue(value)
|
||||||
updateRangeHandle()
|
updateRangeHandle()
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRangeHandle () {
|
function updateRangeHandle()
|
||||||
|
{
|
||||||
rangeHandle.width = handle.x - sliderRoot.handleSize
|
rangeHandle.width = handle.x - sliderRoot.handleSize
|
||||||
}
|
}
|
||||||
|
|
||||||
function normalizeValue(value) {
|
function normalizeValue(value)
|
||||||
|
{
|
||||||
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
// slider track
|
// slider track
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: track
|
id: track
|
||||||
|
|
||||||
width: sliderRoot.width - sliderRoot.handleSize
|
width: sliderRoot.width - sliderRoot.handleSize
|
||||||
|
@ -67,7 +74,8 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress indicator
|
// Progress indicator
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: rangeHandle
|
id: rangeHandle
|
||||||
|
|
||||||
x: handle.width
|
x: handle.width
|
||||||
|
@ -76,7 +84,8 @@ Item {
|
||||||
anchors.verticalCenter: sliderRoot.verticalCenter
|
anchors.verticalCenter: sliderRoot.verticalCenter
|
||||||
visible: sliderRoot.pathsVisible
|
visible: sliderRoot.pathsVisible
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
height: sliderRoot.trackThickness - 2 * sliderRoot.trackBorderWidth
|
||||||
width: parent.width + sliderRoot.handleSize
|
width: parent.width + sliderRoot.handleSize
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
@ -85,7 +94,8 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle
|
// Handle
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: handle
|
id: handle
|
||||||
|
|
||||||
x: sliderRoot.handleSize
|
x: sliderRoot.handleSize
|
||||||
|
@ -96,7 +106,9 @@ Item {
|
||||||
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
|
color: handleLabel.activeFocus ? sliderRoot.handleActiveColor : sliderRoot.handleColor
|
||||||
visible: sliderRoot.pathsVisible
|
visible: sliderRoot.pathsVisible
|
||||||
|
|
||||||
function onHandleDragged () {
|
function onHandleDragged()
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
|
||||||
// update the range handle
|
// update the range handle
|
||||||
sliderRoot.updateRangeHandle()
|
sliderRoot.updateRangeHandle()
|
||||||
|
@ -106,15 +118,23 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get the value based on the slider position
|
// get the value based on the slider position
|
||||||
function getValue () {
|
function getValue()
|
||||||
|
{
|
||||||
var result = x / (sliderRoot.width - sliderRoot.handleSize)
|
var result = x / (sliderRoot.width - sliderRoot.handleSize)
|
||||||
result = result * sliderRoot.maximumValue
|
result = result * sliderRoot.maximumValue
|
||||||
result = sliderRoot.roundValues ? Math.round(result) : result
|
result = sliderRoot.roundValues ? Math.round(result) : result
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function setValueManually(value)
|
||||||
|
{
|
||||||
|
sliderRoot.manuallyChanged = true
|
||||||
|
handle.setValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
// set the slider position based on the value
|
// set the slider position based on the value
|
||||||
function setValue (value) {
|
function setValue(value)
|
||||||
|
{
|
||||||
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
// Normalize values between range, since using arrow keys will create out-of-the-range values
|
||||||
value = sliderRoot.normalizeValue(value)
|
value = sliderRoot.normalizeValue(value)
|
||||||
|
|
||||||
|
@ -132,23 +152,23 @@ Item {
|
||||||
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
Keys.onLeftPressed: handleLabel.setValue(handleLabel.value - ((event.modifiers & Qt.ShiftModifier) ? 10 : 1))
|
||||||
|
|
||||||
// dragging
|
// dragging
|
||||||
MouseArea {
|
MouseArea
|
||||||
|
{
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
||||||
drag {
|
drag
|
||||||
|
{
|
||||||
target: parent
|
target: parent
|
||||||
axis: Drag.XAxis
|
axis: Drag.XAxis
|
||||||
minimumX: 0
|
minimumX: 0
|
||||||
maximumX: sliderRoot.width - sliderRoot.handleSize
|
maximumX: sliderRoot.width - sliderRoot.handleSize
|
||||||
}
|
}
|
||||||
onPressed: {
|
onPressed: handleLabel.forceActiveFocus()
|
||||||
handleLabel.forceActiveFocus()
|
|
||||||
}
|
|
||||||
|
|
||||||
onPositionChanged: parent.onHandleDragged()
|
onPositionChanged: parent.onHandleDragged()
|
||||||
}
|
}
|
||||||
|
|
||||||
SimulationSliderLabel {
|
SimulationSliderLabel
|
||||||
|
{
|
||||||
id: handleLabel
|
id: handleLabel
|
||||||
|
|
||||||
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
height: sliderRoot.handleSize + UM.Theme.getSize("default_margin").height
|
||||||
|
@ -162,7 +182,7 @@ Item {
|
||||||
maximumValue: sliderRoot.maximumValue
|
maximumValue: sliderRoot.maximumValue
|
||||||
value: sliderRoot.handleValue
|
value: sliderRoot.handleValue
|
||||||
busy: UM.SimulationView.busy
|
busy: UM.SimulationView.busy
|
||||||
setValue: handle.setValue // connect callback functions
|
setValue: handle.setValueManually // connect callback functions
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,12 +44,11 @@ UM.PointingRectangle {
|
||||||
id: valueLabel
|
id: valueLabel
|
||||||
|
|
||||||
anchors {
|
anchors {
|
||||||
left: parent.left
|
|
||||||
leftMargin: Math.round(UM.Theme.getSize("default_margin").width / 2)
|
|
||||||
verticalCenter: parent.verticalCenter
|
verticalCenter: parent.verticalCenter
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
width: maximumValue.toString().length * 12 * screenScaleFactor
|
width: (maximumValue.toString().length + 1) * 10 * screenScaleFactor
|
||||||
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
text: sliderLabelRoot.value + startFrom // the current handle value, add 1 because layers is an array
|
||||||
horizontalAlignment: TextInput.AlignRight
|
horizontalAlignment: TextInput.AlignRight
|
||||||
|
|
||||||
|
|
|
@ -18,10 +18,13 @@ from UM.Platform import Platform
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||||
|
|
||||||
from UM.Scene.Selection import Selection
|
from UM.Scene.Selection import Selection
|
||||||
from UM.Signal import Signal
|
from UM.Signal import Signal
|
||||||
from UM.View.GL.OpenGL import OpenGL
|
from UM.View.GL.OpenGL import OpenGL
|
||||||
from UM.View.GL.OpenGLContext import OpenGLContext
|
from UM.View.GL.OpenGLContext import OpenGLContext
|
||||||
|
|
||||||
|
|
||||||
from UM.View.View import View
|
from UM.View.View import View
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
from cura.Scene.ConvexHullNode import ConvexHullNode
|
from cura.Scene.ConvexHullNode import ConvexHullNode
|
||||||
|
@ -30,11 +33,20 @@ from cura.CuraApplication import CuraApplication
|
||||||
from .NozzleNode import NozzleNode
|
from .NozzleNode import NozzleNode
|
||||||
from .SimulationPass import SimulationPass
|
from .SimulationPass import SimulationPass
|
||||||
from .SimulationViewProxy import SimulationViewProxy
|
from .SimulationViewProxy import SimulationViewProxy
|
||||||
|
import numpy
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from typing import Optional, TYPE_CHECKING, List
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from UM.Scene.SceneNode import SceneNode
|
||||||
|
from UM.Scene.Scene import Scene
|
||||||
|
from UM.View.GL.ShaderProgram import ShaderProgram
|
||||||
|
from UM.View.RenderPass import RenderPass
|
||||||
|
from UM.Settings.ContainerStack import ContainerStack
|
||||||
|
|
||||||
catalog = i18nCatalog("cura")
|
catalog = i18nCatalog("cura")
|
||||||
|
|
||||||
import numpy
|
|
||||||
import os.path
|
|
||||||
|
|
||||||
## View used to display g-code paths.
|
## View used to display g-code paths.
|
||||||
class SimulationView(View):
|
class SimulationView(View):
|
||||||
|
@ -44,7 +56,7 @@ class SimulationView(View):
|
||||||
LAYER_VIEW_TYPE_FEEDRATE = 2
|
LAYER_VIEW_TYPE_FEEDRATE = 2
|
||||||
LAYER_VIEW_TYPE_THICKNESS = 3
|
LAYER_VIEW_TYPE_THICKNESS = 3
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._max_layers = 0
|
self._max_layers = 0
|
||||||
|
@ -64,21 +76,21 @@ class SimulationView(View):
|
||||||
self._busy = False
|
self._busy = False
|
||||||
self._simulation_running = False
|
self._simulation_running = False
|
||||||
|
|
||||||
self._ghost_shader = None
|
self._ghost_shader = None # type: Optional["ShaderProgram"]
|
||||||
self._layer_pass = None
|
self._layer_pass = None # type: Optional[SimulationPass]
|
||||||
self._composite_pass = None
|
self._composite_pass = None # type: Optional[RenderPass]
|
||||||
self._old_layer_bindings = None
|
self._old_layer_bindings = None
|
||||||
self._simulationview_composite_shader = None
|
self._simulationview_composite_shader = None # type: Optional["ShaderProgram"]
|
||||||
self._old_composite_shader = None
|
self._old_composite_shader = None
|
||||||
|
|
||||||
self._global_container_stack = None
|
self._global_container_stack = None # type: Optional[ContainerStack]
|
||||||
self._proxy = SimulationViewProxy()
|
self._proxy = SimulationViewProxy()
|
||||||
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
self._controller.getScene().getRoot().childrenChanged.connect(self._onSceneChanged)
|
||||||
|
|
||||||
self._resetSettings()
|
self._resetSettings()
|
||||||
self._legend_items = None
|
self._legend_items = None
|
||||||
self._show_travel_moves = False
|
self._show_travel_moves = False
|
||||||
self._nozzle_node = None
|
self._nozzle_node = None # type: Optional[NozzleNode]
|
||||||
|
|
||||||
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
|
Application.getInstance().getPreferences().addPreference("view/top_layer_count", 5)
|
||||||
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
|
Application.getInstance().getPreferences().addPreference("view/only_show_top_layers", False)
|
||||||
|
@ -102,29 +114,29 @@ class SimulationView(View):
|
||||||
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
self._wireprint_warning_message = Message(catalog.i18nc("@info:status", "Cura does not accurately display layers when Wire Printing is enabled"),
|
||||||
title = catalog.i18nc("@info:title", "Simulation View"))
|
title = catalog.i18nc("@info:title", "Simulation View"))
|
||||||
|
|
||||||
def _evaluateCompatibilityMode(self):
|
def _evaluateCompatibilityMode(self) -> bool:
|
||||||
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
|
return OpenGLContext.isLegacyOpenGL() or bool(Application.getInstance().getPreferences().getValue("view/force_layer_view_compatibility_mode"))
|
||||||
|
|
||||||
def _resetSettings(self):
|
def _resetSettings(self) -> None:
|
||||||
self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
|
self._layer_view_type = 0 # type: int # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
|
||||||
self._extruder_count = 0
|
self._extruder_count = 0
|
||||||
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
||||||
self._show_travel_moves = 0
|
self._show_travel_moves = False
|
||||||
self._show_helpers = 1
|
self._show_helpers = True
|
||||||
self._show_skin = 1
|
self._show_skin = True
|
||||||
self._show_infill = 1
|
self._show_infill = True
|
||||||
self.resetLayerData()
|
self.resetLayerData()
|
||||||
|
|
||||||
def getActivity(self):
|
def getActivity(self) -> bool:
|
||||||
return self._activity
|
return self._activity
|
||||||
|
|
||||||
def setActivity(self, activity):
|
def setActivity(self, activity: bool) -> None:
|
||||||
if self._activity == activity:
|
if self._activity == activity:
|
||||||
return
|
return
|
||||||
self._activity = activity
|
self._activity = activity
|
||||||
self.activityChanged.emit()
|
self.activityChanged.emit()
|
||||||
|
|
||||||
def getSimulationPass(self):
|
def getSimulationPass(self) -> SimulationPass:
|
||||||
if not self._layer_pass:
|
if not self._layer_pass:
|
||||||
# Currently the RenderPass constructor requires a size > 0
|
# Currently the RenderPass constructor requires a size > 0
|
||||||
# This should be fixed in RenderPass's constructor.
|
# This should be fixed in RenderPass's constructor.
|
||||||
|
@ -133,30 +145,30 @@ class SimulationView(View):
|
||||||
self._layer_pass.setSimulationView(self)
|
self._layer_pass.setSimulationView(self)
|
||||||
return self._layer_pass
|
return self._layer_pass
|
||||||
|
|
||||||
def getCurrentLayer(self):
|
def getCurrentLayer(self) -> int:
|
||||||
return self._current_layer_num
|
return self._current_layer_num
|
||||||
|
|
||||||
def getMinimumLayer(self):
|
def getMinimumLayer(self) -> int:
|
||||||
return self._minimum_layer_num
|
return self._minimum_layer_num
|
||||||
|
|
||||||
def getMaxLayers(self):
|
def getMaxLayers(self) -> int:
|
||||||
return self._max_layers
|
return self._max_layers
|
||||||
|
|
||||||
def getCurrentPath(self):
|
def getCurrentPath(self) -> int:
|
||||||
return self._current_path_num
|
return self._current_path_num
|
||||||
|
|
||||||
def getMinimumPath(self):
|
def getMinimumPath(self) -> int:
|
||||||
return self._minimum_path_num
|
return self._minimum_path_num
|
||||||
|
|
||||||
def getMaxPaths(self):
|
def getMaxPaths(self) -> int:
|
||||||
return self._max_paths
|
return self._max_paths
|
||||||
|
|
||||||
def getNozzleNode(self):
|
def getNozzleNode(self) -> NozzleNode:
|
||||||
if not self._nozzle_node:
|
if not self._nozzle_node:
|
||||||
self._nozzle_node = NozzleNode()
|
self._nozzle_node = NozzleNode()
|
||||||
return self._nozzle_node
|
return self._nozzle_node
|
||||||
|
|
||||||
def _onSceneChanged(self, node):
|
def _onSceneChanged(self, node: "SceneNode") -> None:
|
||||||
if node.getMeshData() is None:
|
if node.getMeshData() is None:
|
||||||
self.resetLayerData()
|
self.resetLayerData()
|
||||||
|
|
||||||
|
@ -164,21 +176,21 @@ class SimulationView(View):
|
||||||
self.calculateMaxLayers()
|
self.calculateMaxLayers()
|
||||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||||
|
|
||||||
def isBusy(self):
|
def isBusy(self) -> bool:
|
||||||
return self._busy
|
return self._busy
|
||||||
|
|
||||||
def setBusy(self, busy):
|
def setBusy(self, busy: bool) -> None:
|
||||||
if busy != self._busy:
|
if busy != self._busy:
|
||||||
self._busy = busy
|
self._busy = busy
|
||||||
self.busyChanged.emit()
|
self.busyChanged.emit()
|
||||||
|
|
||||||
def isSimulationRunning(self):
|
def isSimulationRunning(self) -> bool:
|
||||||
return self._simulation_running
|
return self._simulation_running
|
||||||
|
|
||||||
def setSimulationRunning(self, running):
|
def setSimulationRunning(self, running: bool) -> None:
|
||||||
self._simulation_running = running
|
self._simulation_running = running
|
||||||
|
|
||||||
def resetLayerData(self):
|
def resetLayerData(self) -> None:
|
||||||
self._current_layer_mesh = None
|
self._current_layer_mesh = None
|
||||||
self._current_layer_jumps = None
|
self._current_layer_jumps = None
|
||||||
self._max_feedrate = sys.float_info.min
|
self._max_feedrate = sys.float_info.min
|
||||||
|
@ -186,7 +198,7 @@ class SimulationView(View):
|
||||||
self._max_thickness = sys.float_info.min
|
self._max_thickness = sys.float_info.min
|
||||||
self._min_thickness = sys.float_info.max
|
self._min_thickness = sys.float_info.max
|
||||||
|
|
||||||
def beginRendering(self):
|
def beginRendering(self) -> None:
|
||||||
scene = self.getController().getScene()
|
scene = self.getController().getScene()
|
||||||
renderer = self.getRenderer()
|
renderer = self.getRenderer()
|
||||||
|
|
||||||
|
@ -204,7 +216,7 @@ class SimulationView(View):
|
||||||
if (node.getMeshData()) and node.isVisible():
|
if (node.getMeshData()) and node.isVisible():
|
||||||
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
|
renderer.queueNode(node, transparent = True, shader = self._ghost_shader)
|
||||||
|
|
||||||
def setLayer(self, value):
|
def setLayer(self, value: int) -> None:
|
||||||
if self._current_layer_num != value:
|
if self._current_layer_num != value:
|
||||||
self._current_layer_num = value
|
self._current_layer_num = value
|
||||||
if self._current_layer_num < 0:
|
if self._current_layer_num < 0:
|
||||||
|
@ -218,7 +230,7 @@ class SimulationView(View):
|
||||||
|
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def setMinimumLayer(self, value):
|
def setMinimumLayer(self, value: int) -> None:
|
||||||
if self._minimum_layer_num != value:
|
if self._minimum_layer_num != value:
|
||||||
self._minimum_layer_num = value
|
self._minimum_layer_num = value
|
||||||
if self._minimum_layer_num < 0:
|
if self._minimum_layer_num < 0:
|
||||||
|
@ -232,7 +244,7 @@ class SimulationView(View):
|
||||||
|
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def setPath(self, value):
|
def setPath(self, value: int) -> None:
|
||||||
if self._current_path_num != value:
|
if self._current_path_num != value:
|
||||||
self._current_path_num = value
|
self._current_path_num = value
|
||||||
if self._current_path_num < 0:
|
if self._current_path_num < 0:
|
||||||
|
@ -246,7 +258,7 @@ class SimulationView(View):
|
||||||
|
|
||||||
self.currentPathNumChanged.emit()
|
self.currentPathNumChanged.emit()
|
||||||
|
|
||||||
def setMinimumPath(self, value):
|
def setMinimumPath(self, value: int) -> None:
|
||||||
if self._minimum_path_num != value:
|
if self._minimum_path_num != value:
|
||||||
self._minimum_path_num = value
|
self._minimum_path_num = value
|
||||||
if self._minimum_path_num < 0:
|
if self._minimum_path_num < 0:
|
||||||
|
@ -263,24 +275,24 @@ class SimulationView(View):
|
||||||
## Set the layer view type
|
## Set the layer view type
|
||||||
#
|
#
|
||||||
# \param layer_view_type integer as in SimulationView.qml and this class
|
# \param layer_view_type integer as in SimulationView.qml and this class
|
||||||
def setSimulationViewType(self, layer_view_type):
|
def setSimulationViewType(self, layer_view_type: int) -> None:
|
||||||
self._layer_view_type = layer_view_type
|
self._layer_view_type = layer_view_type
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
## Return the layer view type, integer as in SimulationView.qml and this class
|
## Return the layer view type, integer as in SimulationView.qml and this class
|
||||||
def getSimulationViewType(self):
|
def getSimulationViewType(self) -> int:
|
||||||
return self._layer_view_type
|
return self._layer_view_type
|
||||||
|
|
||||||
## Set the extruder opacity
|
## Set the extruder opacity
|
||||||
#
|
#
|
||||||
# \param extruder_nr 0..3
|
# \param extruder_nr 0..3
|
||||||
# \param opacity 0.0 .. 1.0
|
# \param opacity 0.0 .. 1.0
|
||||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
def setExtruderOpacity(self, extruder_nr: int, opacity: float) -> None:
|
||||||
if 0 <= extruder_nr <= 3:
|
if 0 <= extruder_nr <= 3:
|
||||||
self._extruder_opacity[extruder_nr] = opacity
|
self._extruder_opacity[extruder_nr] = opacity
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def getExtruderOpacities(self):
|
def getExtruderOpacities(self)-> List[float]:
|
||||||
return self._extruder_opacity
|
return self._extruder_opacity
|
||||||
|
|
||||||
def setShowTravelMoves(self, show):
|
def setShowTravelMoves(self, show):
|
||||||
|
@ -290,46 +302,46 @@ class SimulationView(View):
|
||||||
def getShowTravelMoves(self):
|
def getShowTravelMoves(self):
|
||||||
return self._show_travel_moves
|
return self._show_travel_moves
|
||||||
|
|
||||||
def setShowHelpers(self, show):
|
def setShowHelpers(self, show: bool) -> None:
|
||||||
self._show_helpers = show
|
self._show_helpers = show
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def getShowHelpers(self):
|
def getShowHelpers(self) -> bool:
|
||||||
return self._show_helpers
|
return self._show_helpers
|
||||||
|
|
||||||
def setShowSkin(self, show):
|
def setShowSkin(self, show: bool) -> None:
|
||||||
self._show_skin = show
|
self._show_skin = show
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def getShowSkin(self):
|
def getShowSkin(self) -> bool:
|
||||||
return self._show_skin
|
return self._show_skin
|
||||||
|
|
||||||
def setShowInfill(self, show):
|
def setShowInfill(self, show: bool) -> None:
|
||||||
self._show_infill = show
|
self._show_infill = show
|
||||||
self.currentLayerNumChanged.emit()
|
self.currentLayerNumChanged.emit()
|
||||||
|
|
||||||
def getShowInfill(self):
|
def getShowInfill(self) -> bool:
|
||||||
return self._show_infill
|
return self._show_infill
|
||||||
|
|
||||||
def getCompatibilityMode(self):
|
def getCompatibilityMode(self) -> bool:
|
||||||
return self._compatibility_mode
|
return self._compatibility_mode
|
||||||
|
|
||||||
def getExtruderCount(self):
|
def getExtruderCount(self) -> int:
|
||||||
return self._extruder_count
|
return self._extruder_count
|
||||||
|
|
||||||
def getMinFeedrate(self):
|
def getMinFeedrate(self) -> float:
|
||||||
return self._min_feedrate
|
return self._min_feedrate
|
||||||
|
|
||||||
def getMaxFeedrate(self):
|
def getMaxFeedrate(self) -> float:
|
||||||
return self._max_feedrate
|
return self._max_feedrate
|
||||||
|
|
||||||
def getMinThickness(self):
|
def getMinThickness(self) -> float:
|
||||||
return self._min_thickness
|
return self._min_thickness
|
||||||
|
|
||||||
def getMaxThickness(self):
|
def getMaxThickness(self) -> float:
|
||||||
return self._max_thickness
|
return self._max_thickness
|
||||||
|
|
||||||
def calculateMaxLayers(self):
|
def calculateMaxLayers(self) -> None:
|
||||||
scene = self.getController().getScene()
|
scene = self.getController().getScene()
|
||||||
|
|
||||||
self._old_max_layers = self._max_layers
|
self._old_max_layers = self._max_layers
|
||||||
|
@ -383,7 +395,7 @@ class SimulationView(View):
|
||||||
self.maxLayersChanged.emit()
|
self.maxLayersChanged.emit()
|
||||||
self._startUpdateTopLayers()
|
self._startUpdateTopLayers()
|
||||||
|
|
||||||
def calculateMaxPathsOnLayer(self, layer_num):
|
def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
|
||||||
# Update the currentPath
|
# Update the currentPath
|
||||||
scene = self.getController().getScene()
|
scene = self.getController().getScene()
|
||||||
for node in DepthFirstIterator(scene.getRoot()):
|
for node in DepthFirstIterator(scene.getRoot()):
|
||||||
|
@ -415,10 +427,10 @@ class SimulationView(View):
|
||||||
def getProxy(self, engine, script_engine):
|
def getProxy(self, engine, script_engine):
|
||||||
return self._proxy
|
return self._proxy
|
||||||
|
|
||||||
def endRendering(self):
|
def endRendering(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def event(self, event):
|
def event(self, event) -> bool:
|
||||||
modifiers = QApplication.keyboardModifiers()
|
modifiers = QApplication.keyboardModifiers()
|
||||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||||
shift_is_active = modifiers & Qt.ShiftModifier
|
shift_is_active = modifiers & Qt.ShiftModifier
|
||||||
|
@ -447,7 +459,7 @@ class SimulationView(View):
|
||||||
if QOpenGLContext.currentContext() is None:
|
if QOpenGLContext.currentContext() is None:
|
||||||
Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
|
Logger.log("d", "current context of OpenGL is empty on Mac OS X, will try to create shaders later")
|
||||||
CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
|
CuraApplication.getInstance().callLater(lambda e=event: self.event(e))
|
||||||
return
|
return False
|
||||||
|
|
||||||
# Make sure the SimulationPass is created
|
# Make sure the SimulationPass is created
|
||||||
layer_pass = self.getSimulationPass()
|
layer_pass = self.getSimulationPass()
|
||||||
|
@ -480,11 +492,14 @@ class SimulationView(View):
|
||||||
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
Application.getInstance().globalContainerStackChanged.disconnect(self._onGlobalStackChanged)
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
|
if self._nozzle_node:
|
||||||
self._nozzle_node.setParent(None)
|
self._nozzle_node.setParent(None)
|
||||||
self.getRenderer().removeRenderPass(self._layer_pass)
|
self.getRenderer().removeRenderPass(self._layer_pass)
|
||||||
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
if self._composite_pass:
|
||||||
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
self._composite_pass.setLayerBindings(self._old_layer_bindings)
|
||||||
|
self._composite_pass.setCompositeShader(self._old_composite_shader)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def getCurrentLayerMesh(self):
|
def getCurrentLayerMesh(self):
|
||||||
return self._current_layer_mesh
|
return self._current_layer_mesh
|
||||||
|
@ -492,7 +507,7 @@ class SimulationView(View):
|
||||||
def getCurrentLayerJumps(self):
|
def getCurrentLayerJumps(self):
|
||||||
return self._current_layer_jumps
|
return self._current_layer_jumps
|
||||||
|
|
||||||
def _onGlobalStackChanged(self):
|
def _onGlobalStackChanged(self) -> None:
|
||||||
if self._global_container_stack:
|
if self._global_container_stack:
|
||||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||||
|
@ -504,17 +519,17 @@ class SimulationView(View):
|
||||||
else:
|
else:
|
||||||
self._wireprint_warning_message.hide()
|
self._wireprint_warning_message.hide()
|
||||||
|
|
||||||
def _onPropertyChanged(self, key, property_name):
|
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||||
if key == "wireframe_enabled" and property_name == "value":
|
if key == "wireframe_enabled" and property_name == "value":
|
||||||
if self._global_container_stack.getProperty("wireframe_enabled", "value"):
|
if self._global_container_stack and self._global_container_stack.getProperty("wireframe_enabled", "value"):
|
||||||
self._wireprint_warning_message.show()
|
self._wireprint_warning_message.show()
|
||||||
else:
|
else:
|
||||||
self._wireprint_warning_message.hide()
|
self._wireprint_warning_message.hide()
|
||||||
|
|
||||||
def _onCurrentLayerNumChanged(self):
|
def _onCurrentLayerNumChanged(self) -> None:
|
||||||
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
self.calculateMaxPathsOnLayer(self._current_layer_num)
|
||||||
|
|
||||||
def _startUpdateTopLayers(self):
|
def _startUpdateTopLayers(self) -> None:
|
||||||
if not self._compatibility_mode:
|
if not self._compatibility_mode:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@ -525,10 +540,10 @@ class SimulationView(View):
|
||||||
self.setBusy(True)
|
self.setBusy(True)
|
||||||
|
|
||||||
self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
|
self._top_layers_job = _CreateTopLayersJob(self._controller.getScene(), self._current_layer_num, self._solid_layers)
|
||||||
self._top_layers_job.finished.connect(self._updateCurrentLayerMesh)
|
self._top_layers_job.finished.connect(self._updateCurrentLayerMesh) # type: ignore # mypy doesn't understand the whole private class thing that's going on here.
|
||||||
self._top_layers_job.start()
|
self._top_layers_job.start() # type: ignore
|
||||||
|
|
||||||
def _updateCurrentLayerMesh(self, job):
|
def _updateCurrentLayerMesh(self, job: "_CreateTopLayersJob") -> None:
|
||||||
self.setBusy(False)
|
self.setBusy(False)
|
||||||
|
|
||||||
if not job.getResult():
|
if not job.getResult():
|
||||||
|
@ -539,9 +554,9 @@ class SimulationView(View):
|
||||||
self._current_layer_jumps = job.getResult().get("jumps")
|
self._current_layer_jumps = job.getResult().get("jumps")
|
||||||
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
|
self._controller.getScene().sceneChanged.emit(self._controller.getScene().getRoot())
|
||||||
|
|
||||||
self._top_layers_job = None
|
self._top_layers_job = None # type: Optional["_CreateTopLayersJob"]
|
||||||
|
|
||||||
def _updateWithPreferences(self):
|
def _updateWithPreferences(self) -> None:
|
||||||
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
self._solid_layers = int(Application.getInstance().getPreferences().getValue("view/top_layer_count"))
|
||||||
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
self._only_show_top_layers = bool(Application.getInstance().getPreferences().getValue("view/only_show_top_layers"))
|
||||||
self._compatibility_mode = self._evaluateCompatibilityMode()
|
self._compatibility_mode = self._evaluateCompatibilityMode()
|
||||||
|
@ -563,7 +578,7 @@ class SimulationView(View):
|
||||||
self._startUpdateTopLayers()
|
self._startUpdateTopLayers()
|
||||||
self.preferencesChanged.emit()
|
self.preferencesChanged.emit()
|
||||||
|
|
||||||
def _onPreferencesChanged(self, preference):
|
def _onPreferencesChanged(self, preference: str) -> None:
|
||||||
if preference not in {
|
if preference not in {
|
||||||
"view/top_layer_count",
|
"view/top_layer_count",
|
||||||
"view/only_show_top_layers",
|
"view/only_show_top_layers",
|
||||||
|
@ -581,7 +596,7 @@ class SimulationView(View):
|
||||||
|
|
||||||
|
|
||||||
class _CreateTopLayersJob(Job):
|
class _CreateTopLayersJob(Job):
|
||||||
def __init__(self, scene, layer_number, solid_layers):
|
def __init__(self, scene: "Scene", layer_number: int, solid_layers: int) -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
|
|
||||||
self._scene = scene
|
self._scene = scene
|
||||||
|
@ -589,7 +604,7 @@ class _CreateTopLayersJob(Job):
|
||||||
self._solid_layers = solid_layers
|
self._solid_layers = solid_layers
|
||||||
self._cancel = False
|
self._cancel = False
|
||||||
|
|
||||||
def run(self):
|
def run(self) -> None:
|
||||||
layer_data = None
|
layer_data = None
|
||||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||||
layer_data = node.callDecoration("getLayerData")
|
layer_data = node.callDecoration("getLayerData")
|
||||||
|
@ -638,6 +653,6 @@ class _CreateTopLayersJob(Job):
|
||||||
|
|
||||||
self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
|
self.setResult({"layers": layer_mesh.build(), "jumps": jump_mesh})
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self) -> None:
|
||||||
self._cancel = True
|
self._cancel = True
|
||||||
super().cancel()
|
super().cancel()
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
// 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.
|
||||||
|
|
||||||
import QtQuick 2.4
|
import QtQuick 2.4
|
||||||
|
@ -12,30 +12,43 @@ import Cura 1.0 as Cura
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
id: base
|
id: base
|
||||||
width: {
|
width:
|
||||||
if (UM.SimulationView.compatibilityMode) {
|
{
|
||||||
|
if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size").width;
|
return UM.Theme.getSize("layerview_menu_size").width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
height: {
|
height: {
|
||||||
if (viewSettings.collapsed) {
|
if (viewSettings.collapsed)
|
||||||
if (UM.SimulationView.compatibilityMode) {
|
{
|
||||||
|
if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
|
||||||
}
|
}
|
||||||
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
|
||||||
} else if (UM.SimulationView.compatibilityMode) {
|
}
|
||||||
|
else if (UM.SimulationView.compatibilityMode)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
|
||||||
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
|
}
|
||||||
|
else if (UM.Preferences.getValue("layerview/layer_view_type") == 0)
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||||
} else {
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Behavior on height { NumberAnimation { duration: 100 } }
|
Behavior on height { NumberAnimation { duration: 100 } }
|
||||||
|
|
||||||
property var buttonTarget: {
|
property var buttonTarget:
|
||||||
|
{
|
||||||
if(parent != null)
|
if(parent != null)
|
||||||
{
|
{
|
||||||
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
|
||||||
|
@ -44,7 +57,8 @@ Item
|
||||||
return Qt.point(0,0)
|
return Qt.point(0,0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
id: layerViewMenu
|
id: layerViewMenu
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
|
@ -83,7 +97,8 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout
|
||||||
|
{
|
||||||
id: viewSettings
|
id: viewSettings
|
||||||
|
|
||||||
property bool collapsed: false
|
property bool collapsed: false
|
||||||
|
@ -195,7 +210,8 @@ Item
|
||||||
width: width
|
width: width
|
||||||
}
|
}
|
||||||
|
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.Preferences
|
target: UM.Preferences
|
||||||
onPreferenceChanged:
|
onPreferenceChanged:
|
||||||
{
|
{
|
||||||
|
@ -212,18 +228,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
|
{
|
||||||
model: Cura.ExtrudersModel{}
|
model: Cura.ExtrudersModel{}
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
id: extrudersModelCheckBox
|
id: extrudersModelCheckBox
|
||||||
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
|
||||||
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
|
||||||
}
|
}
|
||||||
visible: !UM.SimulationView.compatibilityMode
|
visible: !UM.SimulationView.compatibilityMode
|
||||||
enabled: index + 1 <= 4
|
enabled: index + 1 <= 4
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: extrudersModelCheckBox.right
|
anchors.right: extrudersModelCheckBox.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -253,8 +273,10 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
model: ListModel {
|
{
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
id: typesLegendModel
|
id: typesLegendModel
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
|
@ -285,13 +307,16 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
id: legendModelCheckBox
|
id: legendModelCheckBox
|
||||||
checked: model.initialValue
|
checked: model.initialValue
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue(model.preference, checked);
|
UM.Preferences.setValue(model.preference, checked);
|
||||||
}
|
}
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: legendModelCheckBox.right
|
anchors.right: legendModelCheckBox.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -320,18 +345,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
checked: viewSettings.only_show_top_layers
|
checked: viewSettings.only_show_top_layers
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Only Show Top Layers")
|
text: catalog.i18nc("@label", "Only Show Top Layers")
|
||||||
visible: UM.SimulationView.compatibilityMode
|
visible: UM.SimulationView.compatibilityMode
|
||||||
style: UM.Theme.styles.checkbox
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
CheckBox {
|
CheckBox
|
||||||
|
{
|
||||||
checked: viewSettings.top_layer_count == 5
|
checked: viewSettings.top_layer_count == 5
|
||||||
onClicked: {
|
onClicked:
|
||||||
|
{
|
||||||
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
|
||||||
}
|
}
|
||||||
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
|
||||||
|
@ -339,8 +368,10 @@ Item
|
||||||
style: UM.Theme.styles.checkbox
|
style: UM.Theme.styles.checkbox
|
||||||
}
|
}
|
||||||
|
|
||||||
Repeater {
|
Repeater
|
||||||
model: ListModel {
|
{
|
||||||
|
model: ListModel
|
||||||
|
{
|
||||||
id: typesLegendModelNoCheck
|
id: typesLegendModelNoCheck
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
{
|
{
|
||||||
|
@ -355,11 +386,13 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: label
|
text: label
|
||||||
visible: viewSettings.show_legend
|
visible: viewSettings.show_legend
|
||||||
id: typesLegendModelLabel
|
id: typesLegendModelLabel
|
||||||
Rectangle {
|
Rectangle
|
||||||
|
{
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
anchors.right: typesLegendModelLabel.right
|
anchors.right: typesLegendModelLabel.right
|
||||||
width: UM.Theme.getSize("layerview_legend_size").width
|
width: UM.Theme.getSize("layerview_legend_size").width
|
||||||
|
@ -378,30 +411,37 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
// Text for the minimum, maximum and units for the feedrates and layer thickness
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: gradientLegend
|
id: gradientLegend
|
||||||
visible: viewSettings.show_gradient
|
visible: viewSettings.show_gradient
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: UM.Theme.getSize("layerview_row").height
|
height: UM.Theme.getSize("layerview_row").height
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||||
horizontalCenter: parent.horizontalCenter
|
horizontalCenter: parent.horizontalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: minText()
|
text: minText()
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function minText() {
|
function minText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -409,20 +449,25 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: unitsText()
|
text: unitsText()
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function unitsText() {
|
function unitsText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return "mm/s"
|
return "mm/s"
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return "mm"
|
return "mm"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -430,20 +475,25 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Label {
|
Label
|
||||||
|
{
|
||||||
text: maxText()
|
text: maxText()
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
color: UM.Theme.getColor("setting_control_text")
|
color: UM.Theme.getColor("setting_control_text")
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
|
|
||||||
function maxText() {
|
function maxText()
|
||||||
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
|
{
|
||||||
|
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
|
||||||
|
{
|
||||||
// Feedrate selected
|
// Feedrate selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
|
||||||
}
|
}
|
||||||
// Layer thickness selected
|
// Layer thickness selected
|
||||||
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
|
||||||
|
{
|
||||||
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -453,7 +503,8 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient colors for feedrate
|
// Gradient colors for feedrate
|
||||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
Rectangle
|
||||||
|
{ // In QML 5.9 can be changed by LinearGradient
|
||||||
// Invert values because then the bar is rotated 90 degrees
|
// Invert values because then the bar is rotated 90 degrees
|
||||||
id: feedrateGradient
|
id: feedrateGradient
|
||||||
visible: viewSettings.show_feedrate_gradient
|
visible: viewSettings.show_feedrate_gradient
|
||||||
|
@ -463,20 +514,25 @@ Item
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||||
gradient: Gradient {
|
gradient: Gradient
|
||||||
GradientStop {
|
{
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.000
|
position: 0.000
|
||||||
color: Qt.rgba(1, 0.5, 0, 1)
|
color: Qt.rgba(1, 0.5, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.625
|
position: 0.625
|
||||||
color: Qt.rgba(0.375, 0.5, 0, 1)
|
color: Qt.rgba(0.375, 0.5, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.75
|
position: 0.75
|
||||||
color: Qt.rgba(0.25, 1, 0, 1)
|
color: Qt.rgba(0.25, 1, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 1.0
|
position: 1.0
|
||||||
color: Qt.rgba(0, 0, 1, 1)
|
color: Qt.rgba(0, 0, 1, 1)
|
||||||
}
|
}
|
||||||
|
@ -484,7 +540,8 @@ Item
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gradient colors for layer thickness (similar to parula colormap)
|
// Gradient colors for layer thickness (similar to parula colormap)
|
||||||
Rectangle { // In QML 5.9 can be changed by LinearGradient
|
Rectangle // In QML 5.9 can be changed by LinearGradient
|
||||||
|
{
|
||||||
// Invert values because then the bar is rotated 90 degrees
|
// Invert values because then the bar is rotated 90 degrees
|
||||||
id: thicknessGradient
|
id: thicknessGradient
|
||||||
visible: viewSettings.show_thickness_gradient
|
visible: viewSettings.show_thickness_gradient
|
||||||
|
@ -494,24 +551,30 @@ Item
|
||||||
border.width: UM.Theme.getSize("default_lining").width
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
border.color: UM.Theme.getColor("lining")
|
border.color: UM.Theme.getColor("lining")
|
||||||
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
|
||||||
gradient: Gradient {
|
gradient: Gradient
|
||||||
GradientStop {
|
{
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.000
|
position: 0.000
|
||||||
color: Qt.rgba(1, 1, 0, 1)
|
color: Qt.rgba(1, 1, 0, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.25
|
position: 0.25
|
||||||
color: Qt.rgba(1, 0.75, 0.25, 1)
|
color: Qt.rgba(1, 0.75, 0.25, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.5
|
position: 0.5
|
||||||
color: Qt.rgba(0, 0.75, 0.5, 1)
|
color: Qt.rgba(0, 0.75, 0.5, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 0.75
|
position: 0.75
|
||||||
color: Qt.rgba(0, 0.375, 0.75, 1)
|
color: Qt.rgba(0, 0.375, 0.75, 1)
|
||||||
}
|
}
|
||||||
GradientStop {
|
GradientStop
|
||||||
|
{
|
||||||
position: 1.0
|
position: 1.0
|
||||||
color: Qt.rgba(0, 0, 0.5, 1)
|
color: Qt.rgba(0, 0, 0.5, 1)
|
||||||
}
|
}
|
||||||
|
@ -520,19 +583,22 @@ Item
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Item {
|
Item
|
||||||
|
{
|
||||||
id: slidersBox
|
id: slidersBox
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
|
||||||
|
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
top: parent.bottom
|
top: parent.bottom
|
||||||
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
topMargin: UM.Theme.getSize("slider_layerview_margin").height
|
||||||
left: parent.left
|
left: parent.left
|
||||||
}
|
}
|
||||||
|
|
||||||
PathSlider {
|
PathSlider
|
||||||
|
{
|
||||||
id: pathSlider
|
id: pathSlider
|
||||||
|
|
||||||
height: UM.Theme.getSize("slider_handle").width
|
height: UM.Theme.getSize("slider_handle").width
|
||||||
|
@ -553,25 +619,37 @@ Item
|
||||||
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
rangeColor: UM.Theme.getColor("slider_groove_fill")
|
||||||
|
|
||||||
// update values when layer data changes
|
// update values when layer data changes
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.SimulationView
|
target: UM.SimulationView
|
||||||
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
onCurrentPathChanged:
|
||||||
|
{
|
||||||
|
// Only pause the simulation when the layer was changed manually, not when the simulation is running
|
||||||
|
if (pathSlider.manuallyChanged)
|
||||||
|
{
|
||||||
|
playButton.pauseSimulation()
|
||||||
|
}
|
||||||
|
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the slider handlers show the correct value after switching views
|
// make sure the slider handlers show the correct value after switching views
|
||||||
Component.onCompleted: {
|
Component.onCompleted:
|
||||||
|
{
|
||||||
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
pathSlider.setHandleValue(UM.SimulationView.currentPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LayerSlider {
|
LayerSlider
|
||||||
|
{
|
||||||
id: layerSlider
|
id: layerSlider
|
||||||
|
|
||||||
width: UM.Theme.getSize("slider_handle").width
|
width: UM.Theme.getSize("slider_handle").width
|
||||||
height: UM.Theme.getSize("layerview_menu_size").height
|
height: UM.Theme.getSize("layerview_menu_size").height
|
||||||
|
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
|
||||||
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
|
||||||
right: parent.right
|
right: parent.right
|
||||||
|
@ -593,56 +671,78 @@ Item
|
||||||
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
|
||||||
|
|
||||||
// update values when layer data changes
|
// update values when layer data changes
|
||||||
Connections {
|
Connections
|
||||||
|
{
|
||||||
target: UM.SimulationView
|
target: UM.SimulationView
|
||||||
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||||
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||||
onCurrentLayerChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
onCurrentLayerChanged:
|
||||||
|
{
|
||||||
|
// Only pause the simulation when the layer was changed manually, not when the simulation is running
|
||||||
|
if (layerSlider.manuallyChanged)
|
||||||
|
{
|
||||||
|
playButton.pauseSimulation()
|
||||||
|
}
|
||||||
|
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure the slider handlers show the correct value after switching views
|
// make sure the slider handlers show the correct value after switching views
|
||||||
Component.onCompleted: {
|
Component.onCompleted:
|
||||||
|
{
|
||||||
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
|
||||||
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Play simulation button
|
// Play simulation button
|
||||||
Button {
|
Button
|
||||||
|
{
|
||||||
id: playButton
|
id: playButton
|
||||||
iconSource: "./resources/simulation_resume.svg"
|
iconSource: "./resources/simulation_resume.svg"
|
||||||
style: UM.Theme.styles.small_tool_button
|
style: UM.Theme.styles.small_tool_button
|
||||||
visible: !UM.SimulationView.compatibilityMode
|
visible: !UM.SimulationView.compatibilityMode
|
||||||
anchors {
|
anchors
|
||||||
|
{
|
||||||
verticalCenter: pathSlider.verticalCenter
|
verticalCenter: pathSlider.verticalCenter
|
||||||
}
|
}
|
||||||
|
|
||||||
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
property var status: 0 // indicates if it's stopped (0) or playing (1)
|
||||||
|
|
||||||
onClicked: {
|
onClicked:
|
||||||
switch(status) {
|
{
|
||||||
case 0: {
|
switch(status)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
{
|
||||||
resumeSimulation()
|
resumeSimulation()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
case 1: {
|
case 1:
|
||||||
|
{
|
||||||
pauseSimulation()
|
pauseSimulation()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function pauseSimulation() {
|
function pauseSimulation()
|
||||||
|
{
|
||||||
UM.SimulationView.setSimulationRunning(false)
|
UM.SimulationView.setSimulationRunning(false)
|
||||||
iconSource = "./resources/simulation_resume.svg"
|
iconSource = "./resources/simulation_resume.svg"
|
||||||
simulationTimer.stop()
|
simulationTimer.stop()
|
||||||
status = 0
|
status = 0
|
||||||
|
layerSlider.manuallyChanged = true
|
||||||
|
pathSlider.manuallyChanged = true
|
||||||
}
|
}
|
||||||
|
|
||||||
function resumeSimulation() {
|
function resumeSimulation()
|
||||||
|
{
|
||||||
UM.SimulationView.setSimulationRunning(true)
|
UM.SimulationView.setSimulationRunning(true)
|
||||||
iconSource = "./resources/simulation_pause.svg"
|
iconSource = "./resources/simulation_pause.svg"
|
||||||
simulationTimer.start()
|
simulationTimer.start()
|
||||||
|
layerSlider.manuallyChanged = false
|
||||||
|
pathSlider.manuallyChanged = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -652,7 +752,8 @@ Item
|
||||||
interval: 100
|
interval: 100
|
||||||
running: false
|
running: false
|
||||||
repeat: true
|
repeat: true
|
||||||
onTriggered: {
|
onTriggered:
|
||||||
|
{
|
||||||
var currentPath = UM.SimulationView.currentPath
|
var currentPath = UM.SimulationView.currentPath
|
||||||
var numPaths = UM.SimulationView.numPaths
|
var numPaths = UM.SimulationView.numPaths
|
||||||
var currentLayer = UM.SimulationView.currentLayer
|
var currentLayer = UM.SimulationView.currentLayer
|
||||||
|
@ -692,12 +793,15 @@ Item
|
||||||
UM.SimulationView.setCurrentPath(currentPath+1)
|
UM.SimulationView.setCurrentPath(currentPath+1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// The status must be set here instead of in the resumeSimulation function otherwise it won't work
|
||||||
|
// correctly, because part of the logic is in this trigger function.
|
||||||
playButton.status = 1
|
playButton.status = 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FontMetrics {
|
FontMetrics
|
||||||
|
{
|
||||||
id: fontMetrics
|
id: fontMetrics
|
||||||
font: UM.Theme.getFont("default")
|
font: UM.Theme.getFont("default")
|
||||||
}
|
}
|
||||||
|
|
|
@ -256,6 +256,7 @@ fragment41core =
|
||||||
out vec4 frag_color;
|
out vec4 frag_color;
|
||||||
|
|
||||||
uniform mediump vec4 u_ambientColor;
|
uniform mediump vec4 u_ambientColor;
|
||||||
|
uniform mediump vec4 u_minimumAlbedo;
|
||||||
uniform highp vec3 u_lightPosition;
|
uniform highp vec3 u_lightPosition;
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
|
@ -263,7 +264,7 @@ fragment41core =
|
||||||
mediump vec4 finalColor = vec4(0.0);
|
mediump vec4 finalColor = vec4(0.0);
|
||||||
float alpha = f_color.a;
|
float alpha = f_color.a;
|
||||||
|
|
||||||
finalColor.rgb += f_color.rgb * 0.3;
|
finalColor.rgb += f_color.rgb * 0.2 + u_minimumAlbedo.rgb;
|
||||||
|
|
||||||
highp vec3 normal = normalize(f_normal);
|
highp vec3 normal = normalize(f_normal);
|
||||||
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
|
highp vec3 light_dir = normalize(u_lightPosition - f_vertex);
|
||||||
|
@ -285,6 +286,7 @@ u_extruder_opacity = [1.0, 1.0, 1.0, 1.0]
|
||||||
u_specularColor = [0.4, 0.4, 0.4, 1.0]
|
u_specularColor = [0.4, 0.4, 0.4, 1.0]
|
||||||
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
|
u_ambientColor = [0.3, 0.3, 0.3, 0.0]
|
||||||
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
|
u_diffuseColor = [1.0, 0.79, 0.14, 1.0]
|
||||||
|
u_minimumAlbedo = [0.1, 0.1, 0.1, 1.0]
|
||||||
u_shininess = 20.0
|
u_shininess = 20.0
|
||||||
|
|
||||||
u_show_travel_moves = 0
|
u_show_travel_moves = 0
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides the Simulation view.",
|
"description": "Provides the Simulation view.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import json
|
||||||
import os
|
import os
|
||||||
import platform
|
import platform
|
||||||
import time
|
import time
|
||||||
|
from typing import cast, Optional, Set
|
||||||
|
|
||||||
from PyQt5.QtCore import pyqtSlot, QObject
|
from PyQt5.QtCore import pyqtSlot, QObject
|
||||||
|
|
||||||
|
@ -16,7 +17,7 @@ from UM.i18n import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.PluginRegistry import PluginRegistry
|
from UM.PluginRegistry import PluginRegistry
|
||||||
from UM.Qt.Duration import DurationFormat
|
from UM.Qt.Duration import DurationFormat
|
||||||
from typing import cast, Optional
|
|
||||||
from .SliceInfoJob import SliceInfoJob
|
from .SliceInfoJob import SliceInfoJob
|
||||||
|
|
||||||
|
|
||||||
|
@ -95,13 +96,29 @@ class SliceInfo(QObject, Extension):
|
||||||
def setSendSliceInfo(self, enabled: bool):
|
def setSendSliceInfo(self, enabled: bool):
|
||||||
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
|
Application.getInstance().getPreferences().setValue("info/send_slice_info", enabled)
|
||||||
|
|
||||||
|
def _getUserModifiedSettingKeys(self) -> list:
|
||||||
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = cast(CuraApplication, Application.getInstance())
|
||||||
|
machine_manager = application.getMachineManager()
|
||||||
|
global_stack = machine_manager.activeMachine
|
||||||
|
|
||||||
|
user_modified_setting_keys = set() # type: Set[str]
|
||||||
|
|
||||||
|
for stack in [global_stack] + list(global_stack.extruders.values()):
|
||||||
|
# Get all settings in user_changes and quality_changes
|
||||||
|
all_keys = stack.userChanges.getAllKeys() | stack.qualityChanges.getAllKeys()
|
||||||
|
user_modified_setting_keys |= all_keys
|
||||||
|
|
||||||
|
return list(sorted(user_modified_setting_keys))
|
||||||
|
|
||||||
def _onWriteStarted(self, output_device):
|
def _onWriteStarted(self, output_device):
|
||||||
try:
|
try:
|
||||||
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
|
if not Application.getInstance().getPreferences().getValue("info/send_slice_info"):
|
||||||
Logger.log("d", "'info/send_slice_info' is turned off.")
|
Logger.log("d", "'info/send_slice_info' is turned off.")
|
||||||
return # Do nothing, user does not want to send data
|
return # Do nothing, user does not want to send data
|
||||||
|
|
||||||
application = Application.getInstance()
|
from cura.CuraApplication import CuraApplication
|
||||||
|
application = cast(CuraApplication, Application.getInstance())
|
||||||
machine_manager = application.getMachineManager()
|
machine_manager = application.getMachineManager()
|
||||||
print_information = application.getPrintInformation()
|
print_information = application.getPrintInformation()
|
||||||
|
|
||||||
|
@ -164,6 +181,8 @@ class SliceInfo(QObject, Extension):
|
||||||
|
|
||||||
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
|
data["quality_profile"] = global_stack.quality.getMetaData().get("quality_type")
|
||||||
|
|
||||||
|
data["user_modified_setting_keys"] = self._getUserModifiedSettingKeys()
|
||||||
|
|
||||||
data["models"] = []
|
data["models"] = []
|
||||||
# Listing all files placed on the build plate
|
# Listing all files placed on the build plate
|
||||||
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
for node in DepthFirstIterator(application.getController().getScene().getRoot()):
|
||||||
|
|
|
@ -56,6 +56,7 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"quality_profile": "fast",
|
"quality_profile": "fast",
|
||||||
|
"user_modified_setting_keys": ["layer_height", "wall_line_width", "infill_sparse_density"],
|
||||||
"models": [
|
"models": [
|
||||||
{
|
{
|
||||||
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
|
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,6 @@
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides a normal solid mesh view.",
|
"description": "Provides a normal solid mesh view.",
|
||||||
"api": 4,
|
"api": 5,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
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