Merge branch 'Ultimaker:master' into master
@ -16,7 +16,7 @@ For crashes and similar issues, please attach the following information:
|
|||||||
|
|
||||||
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
If the Cura user interface still starts, you can also reach this directory from the application menu in Help -> Show settings folder
|
||||||
|
|
||||||
For additional support, you could also ask in the #cura channel on FreeNode IRC. For help with development, there is also the #cura-dev channel.
|
For additional support, you could also ask in the [#cura channel](https://web.libera.chat/#cura) on [libera.chat](https://libera.chat/). For help with development, there is also the [#cura-dev channel](https://web.libera.chat/#cura-dev).
|
||||||
|
|
||||||
Dependencies
|
Dependencies
|
||||||
------------
|
------------
|
||||||
@ -26,10 +26,16 @@ Dependencies
|
|||||||
* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support.
|
* [PySerial](https://github.com/pyserial/pyserial) Only required for USB printing support.
|
||||||
* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers.
|
* [python-zeroconf](https://github.com/jstasiak/python-zeroconf) Only required to detect mDNS-enabled printers.
|
||||||
|
|
||||||
|
For a list of required Python packages, with their recommended version, see `requirements.txt`.
|
||||||
|
|
||||||
|
This list is not exhaustive at the moment, please check the links in the next section for more details.
|
||||||
|
|
||||||
Build scripts
|
Build scripts
|
||||||
-------------
|
-------------
|
||||||
Please check out [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
|
Please check out [cura-build](https://github.com/Ultimaker/cura-build) for detailed building instructions.
|
||||||
|
|
||||||
|
If you want to build the entire environment from scratch before building Cura as well, [cura-build-environment](https://github.com/Ultimaker/cura-build) might be a starting point before cura-build. (Again, see cura-build for more details.)
|
||||||
|
|
||||||
Running from Source
|
Running from Source
|
||||||
-------------
|
-------------
|
||||||
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source.
|
Please check our [Wiki page](https://github.com/Ultimaker/Cura/wiki/Running-Cura-from-Source) for details about running Cura from source.
|
||||||
|
@ -13,7 +13,7 @@ DEFAULT_CURA_DEBUG_MODE = False
|
|||||||
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
# Each release has a fixed SDK version coupled with it. It doesn't make sense to make it configurable because, for
|
||||||
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
# example Cura 3.2 with SDK version 6.1 will not work. So the SDK version is hard-coded here and left out of the
|
||||||
# CuraVersion.py.in template.
|
# CuraVersion.py.in template.
|
||||||
CuraSDKVersion = "7.5.0"
|
CuraSDKVersion = "7.6.0"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from cura.CuraVersion import CuraAppName # type: ignore
|
from cura.CuraVersion import CuraAppName # type: ignore
|
||||||
|
@ -7,13 +7,14 @@ import re
|
|||||||
import shutil
|
import shutil
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
|
from zipfile import ZipFile, ZIP_DEFLATED, BadZipfile
|
||||||
from typing import Dict, Optional, TYPE_CHECKING
|
from typing import Dict, Optional, TYPE_CHECKING, List
|
||||||
|
|
||||||
from UM import i18nCatalog
|
from UM import i18nCatalog
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from UM.Message import Message
|
from UM.Message import Message
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
from UM.Resources import Resources
|
from UM.Resources import Resources
|
||||||
|
from UM.Version import Version
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
@ -28,6 +29,8 @@ class Backup:
|
|||||||
IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
|
IGNORED_FILES = [r"cura\.log", r"plugins\.json", r"cache", r"__pycache__", r"\.qmlc", r"\.pyc"]
|
||||||
"""These files should be ignored when making a backup."""
|
"""These files should be ignored when making a backup."""
|
||||||
|
|
||||||
|
IGNORED_FOLDERS = [] # type: List[str]
|
||||||
|
|
||||||
SECRETS_SETTINGS = ["general/ultimaker_auth_data"]
|
SECRETS_SETTINGS = ["general/ultimaker_auth_data"]
|
||||||
"""Secret preferences that need to obfuscated when making a backup of Cura"""
|
"""Secret preferences that need to obfuscated when making a backup of Cura"""
|
||||||
|
|
||||||
@ -74,8 +77,9 @@ class Backup:
|
|||||||
machine_count = max(len([s for s in files if "machine_instances/" in s]) - 1, 0) # If people delete their profiles but not their preferences, it can still make a backup, and report -1 profiles. Server crashes on this.
|
machine_count = max(len([s for s in files if "machine_instances/" in s]) - 1, 0) # If people delete their profiles but not their preferences, it can still make a backup, and report -1 profiles. Server crashes on this.
|
||||||
material_count = max(len([s for s in files if "materials/" in s]) - 1, 0)
|
material_count = max(len([s for s in files if "materials/" in s]) - 1, 0)
|
||||||
profile_count = max(len([s for s in files if "quality_changes/" in s]) - 1, 0)
|
profile_count = max(len([s for s in files if "quality_changes/" in s]) - 1, 0)
|
||||||
plugin_count = len([s for s in files if "plugin.json" in s])
|
# We don't store plugins anymore, since if you can make backups, you have an account (and the plugins are
|
||||||
|
# on the marketplace anyway)
|
||||||
|
plugin_count = 0
|
||||||
# Store the archive and metadata so the BackupManager can fetch them when needed.
|
# Store the archive and metadata so the BackupManager can fetch them when needed.
|
||||||
self.zip_file = buffer.getvalue()
|
self.zip_file = buffer.getvalue()
|
||||||
self.meta_data = {
|
self.meta_data = {
|
||||||
@ -94,8 +98,7 @@ class Backup:
|
|||||||
:param root_path: The root directory to archive recursively.
|
:param root_path: The root directory to archive recursively.
|
||||||
:return: The archive as bytes.
|
:return: The archive as bytes.
|
||||||
"""
|
"""
|
||||||
|
ignore_string = re.compile("|".join(self.IGNORED_FILES + self.IGNORED_FOLDERS))
|
||||||
ignore_string = re.compile("|".join(self.IGNORED_FILES))
|
|
||||||
try:
|
try:
|
||||||
archive = ZipFile(buffer, "w", ZIP_DEFLATED)
|
archive = ZipFile(buffer, "w", ZIP_DEFLATED)
|
||||||
for root, folders, files in os.walk(root_path):
|
for root, folders, files in os.walk(root_path):
|
||||||
@ -132,8 +135,8 @@ class Backup:
|
|||||||
"Tried to restore a Cura backup without having proper data or meta data."))
|
"Tried to restore a Cura backup without having proper data or meta data."))
|
||||||
return False
|
return False
|
||||||
|
|
||||||
current_version = self._application.getVersion()
|
current_version = Version(self._application.getVersion())
|
||||||
version_to_restore = self.meta_data.get("cura_release", "master")
|
version_to_restore = Version(self.meta_data.get("cura_release", "master"))
|
||||||
|
|
||||||
if current_version < version_to_restore:
|
if current_version < version_to_restore:
|
||||||
# Cannot restore version newer than current because settings might have changed.
|
# Cannot restore version newer than current because settings might have changed.
|
||||||
@ -163,6 +166,9 @@ class Backup:
|
|||||||
Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
|
Logger.log("d", "Moving preferences file from %s to %s", backup_preferences_file, preferences_file)
|
||||||
shutil.move(backup_preferences_file, preferences_file)
|
shutil.move(backup_preferences_file, preferences_file)
|
||||||
|
|
||||||
|
# Read the preferences from the newly restored configuration (or else the cached Preferences will override the restored ones)
|
||||||
|
self._application.readPreferencesFromConfiguration()
|
||||||
|
|
||||||
# Restore the obfuscated settings
|
# Restore the obfuscated settings
|
||||||
self._illuminate(**secrets)
|
self._illuminate(**secrets)
|
||||||
|
|
||||||
@ -187,11 +193,13 @@ class Backup:
|
|||||||
Logger.log("d", "Removing current data in location: %s", target_path)
|
Logger.log("d", "Removing current data in location: %s", target_path)
|
||||||
Resources.factoryReset()
|
Resources.factoryReset()
|
||||||
Logger.log("d", "Extracting backup to location: %s", target_path)
|
Logger.log("d", "Extracting backup to location: %s", target_path)
|
||||||
try:
|
name_list = archive.namelist()
|
||||||
archive.extractall(target_path)
|
for archive_filename in name_list:
|
||||||
except (PermissionError, EnvironmentError):
|
try:
|
||||||
Logger.logException("e", "Unable to extract the backup due to permission or file system errors.")
|
archive.extract(archive_filename, target_path)
|
||||||
return False
|
except (PermissionError, EnvironmentError):
|
||||||
|
Logger.logException("e", f"Unable to extract the file {archive_filename} from the backup due to permission or file system errors.")
|
||||||
|
CuraApplication.getInstance().processEvents()
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _obfuscate(self) -> Dict[str, str]:
|
def _obfuscate(self) -> Dict[str, str]:
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
from typing import Dict, Optional, Tuple, TYPE_CHECKING
|
from typing import Dict, Optional, Tuple, TYPE_CHECKING
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
from UM.Version import Version
|
||||||
from cura.Backups.Backup import Backup
|
from cura.Backups.Backup import Backup
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@ -52,6 +53,7 @@ class BackupsManager:
|
|||||||
|
|
||||||
backup = Backup(self._application, zip_file = zip_file, meta_data = meta_data)
|
backup = Backup(self._application, zip_file = zip_file, meta_data = meta_data)
|
||||||
restored = backup.restore()
|
restored = backup.restore()
|
||||||
|
|
||||||
if restored:
|
if restored:
|
||||||
# At this point, Cura will need to restart for the changes to take effect.
|
# At this point, Cura will need to restart for the changes to take effect.
|
||||||
# We don't want to store the data at this point as that would override the just-restored backup.
|
# We don't want to store the data at this point as that would override the just-restored backup.
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 numpy
|
import numpy
|
||||||
@ -916,6 +916,8 @@ class BuildVolume(SceneNode):
|
|||||||
return {}
|
return {}
|
||||||
|
|
||||||
for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"):
|
for area in self._global_container_stack.getProperty("machine_disallowed_areas", "value"):
|
||||||
|
if len(area) == 0:
|
||||||
|
continue # Numpy doesn't deal well with 0-length arrays, since it can't determine the dimensionality of them.
|
||||||
polygon = Polygon(numpy.array(area, numpy.float32))
|
polygon = Polygon(numpy.array(area, numpy.float32))
|
||||||
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
polygon = polygon.getMinkowskiHull(Polygon.approximatedCircle(border_size))
|
||||||
machine_disallowed_polygons.append(polygon)
|
machine_disallowed_polygons.append(polygon)
|
||||||
|
@ -67,11 +67,15 @@ class CuraActions(QObject):
|
|||||||
current_node = parent_node
|
current_node = parent_node
|
||||||
parent_node = current_node.getParent()
|
parent_node = current_node.getParent()
|
||||||
|
|
||||||
# This was formerly done with SetTransformOperation but because of
|
# Find out where the bottom of the object is
|
||||||
# unpredictable matrix deconstruction it was possible that mirrors
|
bbox = current_node.getBoundingBox()
|
||||||
# could manifest as rotations. Centering is therefore done by
|
if bbox:
|
||||||
# moving the node to negative whatever its position is:
|
center_y = current_node.getWorldPosition().y - bbox.bottom
|
||||||
center_operation = TranslateOperation(current_node, -current_node._position)
|
else:
|
||||||
|
center_y = 0
|
||||||
|
|
||||||
|
# Move the object so that it's bottom is on to of the buildplate
|
||||||
|
center_operation = TranslateOperation(current_node, Vector(0, center_y, 0), set_position = True)
|
||||||
operation.addOperation(center_operation)
|
operation.addOperation(center_operation)
|
||||||
operation.push()
|
operation.push()
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ class CuraApplication(QtApplication):
|
|||||||
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
# SettingVersion represents the set of settings available in the machine/extruder definitions.
|
||||||
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
|
||||||
# changes of the settings.
|
# changes of the settings.
|
||||||
SettingVersion = 16
|
SettingVersion = 17
|
||||||
|
|
||||||
Created = False
|
Created = False
|
||||||
|
|
||||||
@ -257,6 +257,9 @@ class CuraApplication(QtApplication):
|
|||||||
from cura.CuraPackageManager import CuraPackageManager
|
from cura.CuraPackageManager import CuraPackageManager
|
||||||
self._package_manager_class = CuraPackageManager
|
self._package_manager_class = CuraPackageManager
|
||||||
|
|
||||||
|
from UM.CentralFileStorage import CentralFileStorage
|
||||||
|
CentralFileStorage.setIsEnterprise(ApplicationMetadata.IsEnterpriseVersion)
|
||||||
|
|
||||||
@pyqtProperty(str, constant=True)
|
@pyqtProperty(str, constant=True)
|
||||||
def ultimakerCloudApiRootUrl(self) -> str:
|
def ultimakerCloudApiRootUrl(self) -> str:
|
||||||
return UltimakerCloudConstants.CuraCloudAPIRoot
|
return UltimakerCloudConstants.CuraCloudAPIRoot
|
||||||
@ -705,6 +708,8 @@ class CuraApplication(QtApplication):
|
|||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def discardOrKeepProfileChangesClosed(self, option: str) -> None:
|
def discardOrKeepProfileChangesClosed(self, option: str) -> None:
|
||||||
global_stack = self.getGlobalContainerStack()
|
global_stack = self.getGlobalContainerStack()
|
||||||
|
if global_stack is None:
|
||||||
|
return
|
||||||
if option == "discard":
|
if option == "discard":
|
||||||
for extruder in global_stack.extruderList:
|
for extruder in global_stack.extruderList:
|
||||||
extruder.userChanges.clear()
|
extruder.userChanges.clear()
|
||||||
@ -1526,12 +1531,8 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
# Compute the center of the objects
|
# Compute the center of the objects
|
||||||
object_centers = []
|
object_centers = []
|
||||||
# Forget about the translation that the original objects have
|
|
||||||
zero_translation = Matrix(data=numpy.zeros(3))
|
|
||||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||||
transformation = node.getLocalTransformation()
|
transformed_mesh = mesh.getTransformed(Matrix()) # Forget about the transformations that the original object had.
|
||||||
transformation.setTranslation(zero_translation)
|
|
||||||
transformed_mesh = mesh.getTransformed(transformation)
|
|
||||||
center = transformed_mesh.getCenterPosition()
|
center = transformed_mesh.getCenterPosition()
|
||||||
if center is not None:
|
if center is not None:
|
||||||
object_centers.append(center)
|
object_centers.append(center)
|
||||||
@ -1546,7 +1547,7 @@ class CuraApplication(QtApplication):
|
|||||||
|
|
||||||
# Move each node to the same position.
|
# Move each node to the same position.
|
||||||
for mesh, node in zip(meshes, group_node.getChildren()):
|
for mesh, node in zip(meshes, group_node.getChildren()):
|
||||||
node.setTransformation(Matrix())
|
node.setTransformation(Matrix()) # Removes any changes in position and rotation.
|
||||||
# Align the object around its zero position
|
# Align the object around its zero position
|
||||||
# and also apply the offset to center it inside the group.
|
# and also apply the offset to center it inside the group.
|
||||||
node.setPosition(-mesh.getZeroPosition() - offset)
|
node.setPosition(-mesh.getZeroPosition() - offset)
|
||||||
@ -1867,6 +1868,7 @@ class CuraApplication(QtApplication):
|
|||||||
else:
|
else:
|
||||||
node = CuraSceneNode()
|
node = CuraSceneNode()
|
||||||
node.setMeshData(original_node.getMeshData())
|
node.setMeshData(original_node.getMeshData())
|
||||||
|
node.source_mime_type = original_node.source_mime_type
|
||||||
|
|
||||||
# Setting meshdata does not apply scaling.
|
# Setting meshdata does not apply scaling.
|
||||||
if original_node.getScale() != Vector(1.0, 1.0, 1.0):
|
if original_node.getScale() != Vector(1.0, 1.0, 1.0):
|
||||||
|
@ -53,6 +53,9 @@ class ExtrudersModel(ListModel):
|
|||||||
EnabledRole = Qt.UserRole + 11
|
EnabledRole = Qt.UserRole + 11
|
||||||
"""Is the extruder enabled?"""
|
"""Is the extruder enabled?"""
|
||||||
|
|
||||||
|
MaterialTypeRole = Qt.UserRole + 12
|
||||||
|
"""The type of the material (e.g. PLA, ABS, PETG, etc.)."""
|
||||||
|
|
||||||
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
defaultColors = ["#ffc924", "#86ec21", "#22eeee", "#245bff", "#9124ff", "#ff24c8"]
|
||||||
"""List of colours to display if there is no material or the material has no known colour. """
|
"""List of colours to display if there is no material or the material has no known colour. """
|
||||||
|
|
||||||
@ -75,6 +78,7 @@ class ExtrudersModel(ListModel):
|
|||||||
self.addRoleName(self.StackRole, "stack")
|
self.addRoleName(self.StackRole, "stack")
|
||||||
self.addRoleName(self.MaterialBrandRole, "material_brand")
|
self.addRoleName(self.MaterialBrandRole, "material_brand")
|
||||||
self.addRoleName(self.ColorNameRole, "color_name")
|
self.addRoleName(self.ColorNameRole, "color_name")
|
||||||
|
self.addRoleName(self.MaterialTypeRole, "material_type")
|
||||||
self._update_extruder_timer = QTimer()
|
self._update_extruder_timer = QTimer()
|
||||||
self._update_extruder_timer.setInterval(100)
|
self._update_extruder_timer.setInterval(100)
|
||||||
self._update_extruder_timer.setSingleShot(True)
|
self._update_extruder_timer.setSingleShot(True)
|
||||||
@ -193,7 +197,8 @@ class ExtrudersModel(ListModel):
|
|||||||
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
|
"variant": extruder.variant.getName() if extruder.variant else "", # e.g. print core
|
||||||
"stack": extruder,
|
"stack": extruder,
|
||||||
"material_brand": material_brand,
|
"material_brand": material_brand,
|
||||||
"color_name": color_name
|
"color_name": color_name,
|
||||||
|
"material_type": extruder.material.getMetaDataEntry("material") if extruder.material else "",
|
||||||
}
|
}
|
||||||
|
|
||||||
items.append(item)
|
items.append(item)
|
||||||
@ -218,6 +223,7 @@ class ExtrudersModel(ListModel):
|
|||||||
"stack": None,
|
"stack": None,
|
||||||
"material_brand": "",
|
"material_brand": "",
|
||||||
"color_name": "",
|
"color_name": "",
|
||||||
|
"material_type": "",
|
||||||
}
|
}
|
||||||
items.append(item)
|
items.append(item)
|
||||||
if self._items != items:
|
if self._items != items:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2021 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
|
from typing import TYPE_CHECKING
|
||||||
@ -34,4 +34,4 @@ def fetchLayerHeight(quality_group: "QualityGroup") -> float:
|
|||||||
if isinstance(layer_height, SettingFunction):
|
if isinstance(layer_height, SettingFunction):
|
||||||
layer_height = layer_height(global_stack)
|
layer_height = layer_height(global_stack)
|
||||||
|
|
||||||
return float(layer_height)
|
return round(float(layer_height), 3)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2021 Ultimaker B.V.
|
||||||
# Cura is released under the terms of the LGPLv3 or higher.
|
# Cura is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import copy # To duplicate materials.
|
import copy # To duplicate materials.
|
||||||
@ -79,6 +79,7 @@ class MaterialManagementModel(QObject):
|
|||||||
|
|
||||||
:param material_node: The material to remove.
|
:param material_node: The material to remove.
|
||||||
"""
|
"""
|
||||||
|
Logger.info(f"Removing material {material_node.container_id}")
|
||||||
|
|
||||||
container_registry = CuraContainerRegistry.getInstance()
|
container_registry = CuraContainerRegistry.getInstance()
|
||||||
materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
|
materials_this_base_file = container_registry.findContainersMetadata(base_file = material_node.base_file)
|
||||||
@ -194,6 +195,7 @@ class MaterialManagementModel(QObject):
|
|||||||
|
|
||||||
:return: The root material ID of the duplicate material.
|
:return: The root material ID of the duplicate material.
|
||||||
"""
|
"""
|
||||||
|
Logger.info(f"Duplicating material {material_node.base_file} to {new_base_id}")
|
||||||
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
|
return self.duplicateMaterialByBaseFile(material_node.base_file, new_base_id, new_metadata)
|
||||||
|
|
||||||
@pyqtSlot(result = str)
|
@pyqtSlot(result = str)
|
||||||
|
@ -99,7 +99,7 @@ class QualitySettingsModel(ListModel):
|
|||||||
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
if self._selected_position == self.GLOBAL_STACK_POSITION:
|
||||||
quality_node = quality_group.node_for_global
|
quality_node = quality_group.node_for_global
|
||||||
else:
|
else:
|
||||||
quality_node = quality_group.nodes_for_extruders.get(str(self._selected_position))
|
quality_node = quality_group.nodes_for_extruders.get(self._selected_position)
|
||||||
settings_keys = quality_group.getAllKeys()
|
settings_keys = quality_group.getAllKeys()
|
||||||
quality_containers = []
|
quality_containers = []
|
||||||
if quality_node is not None and quality_node.container is not None:
|
if quality_node is not None and quality_node.container is not None:
|
||||||
@ -114,10 +114,13 @@ class QualitySettingsModel(ListModel):
|
|||||||
global_container = None if len(global_containers) == 0 else global_containers[0]
|
global_container = None if len(global_containers) == 0 else global_containers[0]
|
||||||
extruders_containers = {pos: container_registry.findContainers(id = quality_changes_group.metadata_per_extruder[pos]["id"]) for pos in quality_changes_group.metadata_per_extruder}
|
extruders_containers = {pos: container_registry.findContainers(id = quality_changes_group.metadata_per_extruder[pos]["id"]) for pos in quality_changes_group.metadata_per_extruder}
|
||||||
extruders_container = {pos: None if not containers else containers[0] for pos, containers in extruders_containers.items()}
|
extruders_container = {pos: None if not containers else containers[0] for pos, containers in extruders_containers.items()}
|
||||||
|
quality_changes_metadata = None
|
||||||
if self._selected_position == self.GLOBAL_STACK_POSITION and global_container:
|
if self._selected_position == self.GLOBAL_STACK_POSITION and global_container:
|
||||||
quality_changes_metadata = global_container.getMetaData()
|
quality_changes_metadata = global_container.getMetaData()
|
||||||
else:
|
else:
|
||||||
quality_changes_metadata = extruders_container.get(str(self._selected_position))
|
extruder = extruders_container.get(self._selected_position)
|
||||||
|
if extruder:
|
||||||
|
quality_changes_metadata = extruder.getMetaData()
|
||||||
if quality_changes_metadata is not None: # It can be None if number of extruders are changed during runtime.
|
if quality_changes_metadata is not None: # It can be None if number of extruders are changed during runtime.
|
||||||
container = container_registry.findContainers(id = quality_changes_metadata["id"])
|
container = container_registry.findContainers(id = quality_changes_metadata["id"])
|
||||||
if container:
|
if container:
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
from hashlib import sha512
|
from hashlib import sha512
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from typing import Optional, Any, Dict, Tuple
|
from typing import Optional
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from UM.i18n import i18nCatalog
|
from UM.i18n import i18nCatalog
|
||||||
@ -115,7 +115,7 @@ class AuthorizationHelpers:
|
|||||||
token_request = requests.get(check_token_url, headers = {
|
token_request = requests.get(check_token_url, headers = {
|
||||||
"Authorization": "Bearer {}".format(access_token)
|
"Authorization": "Bearer {}".format(access_token)
|
||||||
})
|
})
|
||||||
except requests.exceptions.ConnectionError:
|
except (requests.exceptions.ConnectionError, requests.exceptions.Timeout):
|
||||||
# Connection was suddenly dropped. Nothing we can do about that.
|
# Connection was suddenly dropped. Nothing we can do about that.
|
||||||
Logger.logException("w", "Something failed while attempting to parse the JWT token")
|
Logger.logException("w", "Something failed while attempting to parse the JWT token")
|
||||||
return None
|
return None
|
||||||
|
@ -113,8 +113,10 @@ class AuthorizationService:
|
|||||||
# The token could not be refreshed using the refresh token. We should login again.
|
# The token could not be refreshed using the refresh token. We should login again.
|
||||||
return None
|
return None
|
||||||
# Ensure it gets stored as otherwise we only have it in memory. The stored refresh token has been deleted
|
# Ensure it gets stored as otherwise we only have it in memory. The stored refresh token has been deleted
|
||||||
# from the server already.
|
# from the server already. Do not store the auth_data if we could not get new auth_data (eg due to a
|
||||||
self._storeAuthData(self._auth_data)
|
# network error), since this would cause an infinite loop trying to get new auth-data
|
||||||
|
if self._auth_data.success:
|
||||||
|
self._storeAuthData(self._auth_data)
|
||||||
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
return self._auth_helpers.parseJWT(self._auth_data.access_token)
|
||||||
|
|
||||||
def getAccessToken(self) -> Optional[str]:
|
def getAccessToken(self) -> Optional[str]:
|
||||||
|
@ -4,7 +4,7 @@ from typing import Type, TYPE_CHECKING, Optional, List
|
|||||||
|
|
||||||
import keyring
|
import keyring
|
||||||
from keyring.backend import KeyringBackend
|
from keyring.backend import KeyringBackend
|
||||||
from keyring.errors import NoKeyringError, PasswordSetError
|
from keyring.errors import NoKeyringError, PasswordSetError, KeyringLocked
|
||||||
|
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
|
|
||||||
@ -39,6 +39,10 @@ class KeyringAttribute:
|
|||||||
self._store_secure = False
|
self._store_secure = False
|
||||||
Logger.logException("w", "No keyring backend present")
|
Logger.logException("w", "No keyring backend present")
|
||||||
return getattr(instance, self._name)
|
return getattr(instance, self._name)
|
||||||
|
except KeyringLocked:
|
||||||
|
self._store_secure = False
|
||||||
|
Logger.log("i", "Access to the keyring was denied.")
|
||||||
|
return getattr(instance, self._name)
|
||||||
else:
|
else:
|
||||||
return getattr(instance, self._name)
|
return getattr(instance, self._name)
|
||||||
|
|
||||||
@ -48,7 +52,7 @@ class KeyringAttribute:
|
|||||||
if value is not None:
|
if value is not None:
|
||||||
try:
|
try:
|
||||||
keyring.set_password("cura", self._keyring_name, value)
|
keyring.set_password("cura", self._keyring_name, value)
|
||||||
except PasswordSetError:
|
except (PasswordSetError, KeyringLocked):
|
||||||
self._store_secure = False
|
self._store_secure = False
|
||||||
if self._name not in DONT_EVER_STORE_LOCALLY:
|
if self._name not in DONT_EVER_STORE_LOCALLY:
|
||||||
setattr(instance, self._name, value)
|
setattr(instance, self._name, value)
|
||||||
|
@ -119,21 +119,23 @@ class CuraSceneNode(SceneNode):
|
|||||||
self._aabb = None
|
self._aabb = None
|
||||||
if self._mesh_data:
|
if self._mesh_data:
|
||||||
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation(copy = False))
|
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation(copy = False))
|
||||||
else: # If there is no mesh_data, use a bounding box that encompasses the local (0,0,0)
|
|
||||||
position = self.getWorldPosition()
|
|
||||||
self._aabb = AxisAlignedBox(minimum = position, maximum = position)
|
|
||||||
|
|
||||||
for child in self.getAllChildren():
|
for child in self.getAllChildren():
|
||||||
if child.callDecoration("isNonPrintingMesh"):
|
if child.callDecoration("isNonPrintingMesh"):
|
||||||
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
|
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
|
||||||
continue
|
continue
|
||||||
if not child.getMeshData():
|
child_bb = child.getBoundingBox()
|
||||||
# Nodes without mesh data should not affect bounding boxes of their parents.
|
if child_bb is None or child_bb.minimum == child_bb.maximum:
|
||||||
|
# Child had a degenerate bounding box, such as an empty group. Don't count it along.
|
||||||
continue
|
continue
|
||||||
if self._aabb is None:
|
if self._aabb is None:
|
||||||
self._aabb = child.getBoundingBox()
|
self._aabb = child_bb
|
||||||
else:
|
else:
|
||||||
self._aabb = self._aabb + child.getBoundingBox()
|
self._aabb = self._aabb + child_bb
|
||||||
|
|
||||||
|
if self._aabb is None: # No children that should be included? Just use your own position then, but it's an invalid AABB.
|
||||||
|
position = self.getWorldPosition()
|
||||||
|
self._aabb = AxisAlignedBox(minimum = position, maximum = position)
|
||||||
|
|
||||||
def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
|
def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
|
||||||
"""Taken from SceneNode, but replaced SceneNode with CuraSceneNode"""
|
"""Taken from SceneNode, but replaced SceneNode with CuraSceneNode"""
|
||||||
@ -142,6 +144,7 @@ class CuraSceneNode(SceneNode):
|
|||||||
copy.setTransformation(self.getLocalTransformation(copy= False))
|
copy.setTransformation(self.getLocalTransformation(copy= False))
|
||||||
copy.setMeshData(self._mesh_data)
|
copy.setMeshData(self._mesh_data)
|
||||||
copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
|
copy.setVisible(cast(bool, deepcopy(self._visible, memo)))
|
||||||
|
copy.source_mime_type = cast(str, deepcopy(self.source_mime_type, memo))
|
||||||
copy._selectable = cast(bool, deepcopy(self._selectable, memo))
|
copy._selectable = cast(bool, deepcopy(self._selectable, memo))
|
||||||
copy._name = cast(str, deepcopy(self._name, memo))
|
copy._name = cast(str, deepcopy(self._name, memo))
|
||||||
for decorator in self._decorators:
|
for decorator in self._decorators:
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 os
|
import os
|
||||||
@ -241,6 +241,7 @@ class ContainerManager(QObject):
|
|||||||
file_url = file_url_or_string.toLocalFile()
|
file_url = file_url_or_string.toLocalFile()
|
||||||
else:
|
else:
|
||||||
file_url = file_url_or_string
|
file_url = file_url_or_string
|
||||||
|
Logger.info(f"Importing material from {file_url}")
|
||||||
|
|
||||||
if not file_url or not os.path.exists(file_url):
|
if not file_url or not os.path.exists(file_url):
|
||||||
return {"status": "error", "message": "Invalid path"}
|
return {"status": "error", "message": "Invalid path"}
|
||||||
|
@ -29,7 +29,7 @@ class WhatsNewPagesModel(WelcomePagesModel):
|
|||||||
for filename in files:
|
for filename in files:
|
||||||
basename = os.path.basename(filename)
|
basename = os.path.basename(filename)
|
||||||
base, ext = os.path.splitext(basename)
|
base, ext = os.path.splitext(basename)
|
||||||
if ext not in include or not base.isdigit():
|
if ext.lower() not in include or not base.isdigit():
|
||||||
continue
|
continue
|
||||||
page_no = int(base)
|
page_no = int(base)
|
||||||
highest = max(highest, page_no)
|
highest = max(highest, page_no)
|
||||||
|
@ -16,14 +16,6 @@ import argparse
|
|||||||
import faulthandler
|
import faulthandler
|
||||||
import os
|
import os
|
||||||
|
|
||||||
# Workaround for a race condition on certain systems where there
|
|
||||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
|
||||||
# first seems to prevent Sip from going into a state where it
|
|
||||||
# tries to create PyQt objects on a non-main thread.
|
|
||||||
import Arcus # @UnusedImport
|
|
||||||
import Savitar # @UnusedImport
|
|
||||||
import pynest2d # @UnusedImport
|
|
||||||
|
|
||||||
from PyQt5.QtNetwork import QSslConfiguration, QSslSocket
|
from PyQt5.QtNetwork import QSslConfiguration, QSslSocket
|
||||||
|
|
||||||
from UM.Platform import Platform
|
from UM.Platform import Platform
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
@ -412,7 +412,12 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||||||
quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)]
|
quality_container_id = parser["containers"][str(_ContainerIndexes.Quality)]
|
||||||
quality_type = "empty_quality"
|
quality_type = "empty_quality"
|
||||||
if quality_container_id not in ("empty", "empty_quality"):
|
if quality_container_id not in ("empty", "empty_quality"):
|
||||||
quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
|
if quality_container_id in instance_container_info_dict:
|
||||||
|
quality_type = instance_container_info_dict[quality_container_id].parser["metadata"]["quality_type"]
|
||||||
|
else: # If a version upgrade changed the quality profile in the stack, we'll need to look for it in the built-in profiles instead of the workspace.
|
||||||
|
quality_matches = ContainerRegistry.getInstance().findContainersMetadata(id = quality_container_id)
|
||||||
|
if quality_matches: # If there's no profile with this ID, leave it empty_quality.
|
||||||
|
quality_type = quality_matches[0]["quality_type"]
|
||||||
|
|
||||||
# Get machine info
|
# Get machine info
|
||||||
serialized = archive.open(global_stack_file).read().decode("utf-8")
|
serialized = archive.open(global_stack_file).read().decode("utf-8")
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for reading 3MF files.",
|
"description": "Provides support for reading 3MF files.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for writing 3MF files.",
|
"description": "Provides support for writing 3MF files.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
"author": "fieldOfView",
|
"author": "fieldOfView",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for reading AMF files.",
|
"description": "Provides support for reading AMF files.",
|
||||||
"api": "7.5.0"
|
"api": 7
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Backup and restore your configuration.",
|
"description": "Backup and restore your configuration.",
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import threading
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Dict, Optional
|
from typing import Any, Dict, Optional
|
||||||
|
|
||||||
import sentry_sdk
|
|
||||||
from PyQt5.QtNetwork import QNetworkReply
|
from PyQt5.QtNetwork import QNetworkReply
|
||||||
|
|
||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
@ -99,13 +98,7 @@ class CreateBackupJob(Job):
|
|||||||
if HttpRequestManager.safeHttpStatus(reply) == 400:
|
if HttpRequestManager.safeHttpStatus(reply) == 400:
|
||||||
errors = json.loads(replyText)["errors"]
|
errors = json.loads(replyText)["errors"]
|
||||||
if "moreThanMaximum" in [error["code"] for error in errors if error["meta"] and error["meta"]["field_name"] == "backup_size"]:
|
if "moreThanMaximum" in [error["code"] for error in errors if error["meta"] and error["meta"]["field_name"] == "backup_size"]:
|
||||||
if self._backup_zip is None: # will never happen; keep mypy happy
|
|
||||||
zip_error = "backup is None."
|
|
||||||
else:
|
|
||||||
zip_error = "{} exceeds max size.".format(str(len(self._backup_zip)))
|
|
||||||
sentry_sdk.capture_message("backup failed: {}".format(zip_error), level ="warning")
|
|
||||||
self.backup_upload_error_message = catalog.i18nc("@error:file_size", "The backup exceeds the maximum file size.")
|
self.backup_upload_error_message = catalog.i18nc("@error:file_size", "The backup exceeds the maximum file size.")
|
||||||
from sentry_sdk import capture_message
|
|
||||||
|
|
||||||
self._job_done.set()
|
self._job_done.set()
|
||||||
return
|
return
|
||||||
|
@ -93,7 +93,7 @@ class DriveApiService:
|
|||||||
def _onRestoreFinished(self, job: "RestoreBackupJob") -> None:
|
def _onRestoreFinished(self, job: "RestoreBackupJob") -> None:
|
||||||
if job.restore_backup_error_message != "":
|
if job.restore_backup_error_message != "":
|
||||||
# If the job contains an error message we pass it along so the UI can display it.
|
# If the job contains an error message we pass it along so the UI can display it.
|
||||||
self.restoringStateChanged.emit(is_restoring=False)
|
self.restoringStateChanged.emit(is_restoring = False)
|
||||||
else:
|
else:
|
||||||
self.restoringStateChanged.emit(is_restoring = False, error_message = job.restore_backup_error_message)
|
self.restoringStateChanged.emit(is_restoring = False, error_message = job.restore_backup_error_message)
|
||||||
|
|
||||||
|
@ -34,6 +34,9 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
# Signal emitted when preferences changed (like auto-backup).
|
# Signal emitted when preferences changed (like auto-backup).
|
||||||
preferencesChanged = pyqtSignal()
|
preferencesChanged = pyqtSignal()
|
||||||
|
|
||||||
|
# Signal emitted when the id of the backup-to-be-restored is changed
|
||||||
|
backupIdBeingRestoredChanged = pyqtSignal(arguments = ["backup_id_being_restored"])
|
||||||
|
|
||||||
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
|
DATE_FORMAT = "%d/%m/%Y %H:%M:%S"
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
@ -45,6 +48,7 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
self._backups = [] # type: List[Dict[str, Any]]
|
self._backups = [] # type: List[Dict[str, Any]]
|
||||||
self._is_restoring_backup = False
|
self._is_restoring_backup = False
|
||||||
self._is_creating_backup = False
|
self._is_creating_backup = False
|
||||||
|
self._backup_id_being_restored = ""
|
||||||
|
|
||||||
# Initialize services.
|
# Initialize services.
|
||||||
preferences = CuraApplication.getInstance().getPreferences()
|
preferences = CuraApplication.getInstance().getPreferences()
|
||||||
@ -52,6 +56,7 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
|
|
||||||
# Attach signals.
|
# Attach signals.
|
||||||
CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
|
CuraApplication.getInstance().getCuraAPI().account.loginStateChanged.connect(self._onLoginStateChanged)
|
||||||
|
CuraApplication.getInstance().applicationShuttingDown.connect(self._onApplicationShuttingDown)
|
||||||
self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
|
self._drive_api_service.restoringStateChanged.connect(self._onRestoringStateChanged)
|
||||||
self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
|
self._drive_api_service.creatingStateChanged.connect(self._onCreatingStateChanged)
|
||||||
|
|
||||||
@ -75,6 +80,10 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
if self._drive_window:
|
if self._drive_window:
|
||||||
self._drive_window.show()
|
self._drive_window.show()
|
||||||
|
|
||||||
|
def _onApplicationShuttingDown(self):
|
||||||
|
if self._drive_window:
|
||||||
|
self._drive_window.hide()
|
||||||
|
|
||||||
def _autoBackup(self) -> None:
|
def _autoBackup(self) -> None:
|
||||||
preferences = CuraApplication.getInstance().getPreferences()
|
preferences = CuraApplication.getInstance().getPreferences()
|
||||||
if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
|
if preferences.getValue(Settings.AUTO_BACKUP_ENABLED_PREFERENCE_KEY) and self._isLastBackupTooLongAgo():
|
||||||
@ -100,10 +109,11 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
if logged_in:
|
if logged_in:
|
||||||
self.refreshBackups()
|
self.refreshBackups()
|
||||||
|
|
||||||
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: str = None) -> None:
|
def _onRestoringStateChanged(self, is_restoring: bool = False, error_message: Optional[str] = None) -> None:
|
||||||
self._is_restoring_backup = is_restoring
|
self._is_restoring_backup = is_restoring
|
||||||
self.restoringStateChanged.emit()
|
self.restoringStateChanged.emit()
|
||||||
if error_message:
|
if error_message:
|
||||||
|
self.backupIdBeingRestored = ""
|
||||||
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
Message(error_message, title = catalog.i18nc("@info:title", "Backup")).show()
|
||||||
|
|
||||||
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
|
def _onCreatingStateChanged(self, is_creating: bool = False, error_message: str = None) -> None:
|
||||||
@ -152,6 +162,7 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
for backup in self._backups:
|
for backup in self._backups:
|
||||||
if backup.get("backup_id") == backup_id:
|
if backup.get("backup_id") == backup_id:
|
||||||
self._drive_api_service.restoreBackup(backup)
|
self._drive_api_service.restoreBackup(backup)
|
||||||
|
self.setBackupIdBeingRestored(backup_id)
|
||||||
return
|
return
|
||||||
Logger.log("w", "Unable to find backup with the ID %s", backup_id)
|
Logger.log("w", "Unable to find backup with the ID %s", backup_id)
|
||||||
|
|
||||||
@ -166,3 +177,12 @@ class DrivePluginExtension(QObject, Extension):
|
|||||||
def _backupDeletedCallback(self, success: bool):
|
def _backupDeletedCallback(self, success: bool):
|
||||||
if success:
|
if success:
|
||||||
self.refreshBackups()
|
self.refreshBackups()
|
||||||
|
|
||||||
|
def setBackupIdBeingRestored(self, backup_id_being_restored: str) -> None:
|
||||||
|
if backup_id_being_restored != self._backup_id_being_restored:
|
||||||
|
self._backup_id_being_restored = backup_id_being_restored
|
||||||
|
self.backupIdBeingRestoredChanged.emit()
|
||||||
|
|
||||||
|
@pyqtProperty(str, fset = setBackupIdBeingRestored, notify = backupIdBeingRestoredChanged)
|
||||||
|
def backupIdBeingRestored(self) -> str:
|
||||||
|
return self._backup_id_being_restored
|
||||||
|
@ -71,6 +71,7 @@ Item
|
|||||||
text: catalog.i18nc("@button", "Restore")
|
text: catalog.i18nc("@button", "Restore")
|
||||||
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
enabled: !CuraDrive.isCreatingBackup && !CuraDrive.isRestoringBackup
|
||||||
onClicked: confirmRestoreDialog.visible = true
|
onClicked: confirmRestoreDialog.visible = true
|
||||||
|
busy: CuraDrive.backupIdBeingRestored == modelData.backup_id && CuraDrive.isRestoringBackup
|
||||||
}
|
}
|
||||||
|
|
||||||
UM.SimpleButton
|
UM.SimpleButton
|
||||||
|
@ -17,7 +17,7 @@ ColumnLayout
|
|||||||
// Cura version
|
// Cura version
|
||||||
BackupListItemDetailsRow
|
BackupListItemDetailsRow
|
||||||
{
|
{
|
||||||
iconSource: UM.Theme.getIcon("UltimakeCura")
|
iconSource: UM.Theme.getIcon("UltimakerCura")
|
||||||
label: catalog.i18nc("@backuplist:label", "Cura Version")
|
label: catalog.i18nc("@backuplist:label", "Cura Version")
|
||||||
value: backupDetailsData.metadata.cura_release
|
value: backupDetailsData.metadata.cura_release
|
||||||
}
|
}
|
||||||
|
@ -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": "7.5.0",
|
"api": 7,
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing Cura profiles.",
|
"description": "Provides support for importing Cura profiles.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for exporting Cura profiles.",
|
"description": "Provides support for exporting Cura profiles.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog":"cura"
|
"i18n-catalog":"cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
|
"description": "Connects to the Digital Library, allowing Cura to open files from and save files to the Digital Library.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,11 @@ Item
|
|||||||
model: manager.digitalFactoryFileModel
|
model: manager.digitalFactoryFileModel
|
||||||
visible: model.count != 0 && manager.retrievingFileStatus != DF.RetrievalStatus.InProgress
|
visible: model.count != 0 && manager.retrievingFileStatus != DF.RetrievalStatus.InProgress
|
||||||
selectionMode: OldControls.SelectionMode.SingleSelection
|
selectionMode: OldControls.SelectionMode.SingleSelection
|
||||||
|
onDoubleClicked:
|
||||||
|
{
|
||||||
|
manager.setSelectedFileIndices([row]);
|
||||||
|
openFilesButton.clicked();
|
||||||
|
}
|
||||||
|
|
||||||
OldControls.TableViewColumn
|
OldControls.TableViewColumn
|
||||||
{
|
{
|
||||||
|
@ -385,6 +385,11 @@ class DigitalFactoryController(QObject):
|
|||||||
def _applicationInitializationFinished(self) -> None:
|
def _applicationInitializationFinished(self) -> None:
|
||||||
self._supported_file_types = self._application.getInstance().getMeshFileHandler().getSupportedFileTypesRead()
|
self._supported_file_types = self._application.getInstance().getMeshFileHandler().getSupportedFileTypesRead()
|
||||||
|
|
||||||
|
# Although Cura supports these, it's super confusing in this context to show them.
|
||||||
|
for extension in ["jpg", "jpeg", "png", "bmp", "gif"]:
|
||||||
|
if extension in self._supported_file_types:
|
||||||
|
del self._supported_file_types[extension]
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def openSelectedFiles(self) -> None:
|
def openSelectedFiles(self) -> None:
|
||||||
""" Downloads, then opens all files selected in the Qt frontend open dialog.
|
""" Downloads, then opens all files selected in the Qt frontend open dialog.
|
||||||
|
@ -1,8 +1,3 @@
|
|||||||
# Prevents error: "PyCapsule_GetPointer called with incorrect name" with conflicting SIP configurations between Arcus and PyQt: Import custom Sip bindings first!
|
|
||||||
import Savitar # Dont remove this line
|
|
||||||
import Arcus # No really. Don't. It needs to be there!
|
|
||||||
import pynest2d # Really!
|
|
||||||
|
|
||||||
|
|
||||||
# Ensure that the importing for all tests work
|
# Ensure that the importing for all tests work
|
||||||
import sys
|
import sys
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Checks for firmware updates.",
|
"description": "Checks for firmware updates.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a machine actions for updating firmware.",
|
"description": "Provides a machine actions for updating firmware.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Reads g-code from a compressed archive.",
|
"description": "Reads g-code from a compressed archive.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Writes g-code to a compressed archive.",
|
"description": "Writes g-code to a compressed archive.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing profiles from g-code files.",
|
"description": "Provides support for importing profiles from g-code files.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Victor Larchenko, Ultimaker B.V.",
|
"author": "Victor Larchenko, Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Allows loading and displaying G-code files.",
|
"description": "Allows loading and displaying G-code files.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Writes g-code to a file.",
|
"description": "Writes g-code to a file.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Enables ability to generate printable geometry from 2D image files.",
|
"description": "Enables ability to generate printable geometry from 2D image files.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for importing profiles from legacy Cura versions.",
|
"description": "Provides support for importing profiles from legacy Cura versions.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "fieldOfView, Ultimaker B.V.",
|
"author": "fieldOfView, Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"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": "7.5.0",
|
"api": 7,
|
||||||
"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": "1.0.1",
|
"version": "1.0.1",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"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.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a monitor stage in Cura.",
|
"description": "Provides a monitor stage in Cura.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
@ -73,38 +73,40 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
|
|||||||
|
|
||||||
# Add all instances that are not added, but are in visibility list
|
# Add all instances that are not added, but are in visibility list
|
||||||
for item in visible:
|
for item in visible:
|
||||||
if settings.getInstance(item) is None: # Setting was not added already.
|
if settings.getInstance(item) is not None: # Setting was added already.
|
||||||
definition = self._stack.getSettingDefinition(item)
|
continue
|
||||||
if definition:
|
definition = self._stack.getSettingDefinition(item)
|
||||||
new_instance = SettingInstance(definition, settings)
|
if not definition:
|
||||||
|
Logger.log("w", f"Unable to add instance ({item}) to per-object visibility because we couldn't find the matching definition.")
|
||||||
|
continue
|
||||||
|
|
||||||
|
new_instance = SettingInstance(definition, settings)
|
||||||
|
stack_nr = -1
|
||||||
|
stack = None
|
||||||
|
# Check from what stack we should copy the raw property of the setting from.
|
||||||
|
if self._stack.getProperty("machine_extruder_count", "value") > 1:
|
||||||
|
if definition.limit_to_extruder != "-1":
|
||||||
|
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
||||||
|
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
||||||
|
|
||||||
|
# Check if the found stack_number is in the extruder list of extruders.
|
||||||
|
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
|
||||||
stack_nr = -1
|
stack_nr = -1
|
||||||
stack = None
|
|
||||||
# Check from what stack we should copy the raw property of the setting from.
|
|
||||||
if self._stack.getProperty("machine_extruder_count", "value") > 1:
|
|
||||||
if definition.limit_to_extruder != "-1":
|
|
||||||
# A limit to extruder function was set and it's a multi extrusion machine. Check what stack we do need to use.
|
|
||||||
stack_nr = str(int(round(float(self._stack.getProperty(item, "limit_to_extruder")))))
|
|
||||||
|
|
||||||
# Check if the found stack_number is in the extruder list of extruders.
|
# Use the found stack number to get the right stack to copy the value from.
|
||||||
if stack_nr not in ExtruderManager.getInstance().extruderIds and self._stack.getProperty("extruder_nr", "value") is not None:
|
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
||||||
stack_nr = -1
|
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
||||||
|
else:
|
||||||
|
stack = self._stack
|
||||||
|
|
||||||
# Use the found stack number to get the right stack to copy the value from.
|
# Use the raw property to set the value (so the inheritance doesn't break)
|
||||||
if stack_nr in ExtruderManager.getInstance().extruderIds:
|
if stack is not None:
|
||||||
stack = ContainerRegistry.getInstance().findContainerStacks(id = ExtruderManager.getInstance().extruderIds[stack_nr])[0]
|
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
||||||
else:
|
else:
|
||||||
stack = self._stack
|
new_instance.setProperty("value", None)
|
||||||
|
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
||||||
# Use the raw property to set the value (so the inheritance doesn't break)
|
settings.addInstance(new_instance)
|
||||||
if stack is not None:
|
visibility_changed = True
|
||||||
new_instance.setProperty("value", stack.getRawProperty(item, "value"))
|
|
||||||
else:
|
|
||||||
new_instance.setProperty("value", None)
|
|
||||||
new_instance.resetState() # Ensure that the state is not seen as a user state.
|
|
||||||
settings.addInstance(new_instance)
|
|
||||||
visibility_changed = True
|
|
||||||
else:
|
|
||||||
Logger.log("w", "Unable to add instance (%s) to per-object visibility because we couldn't find the matching definition", item)
|
|
||||||
|
|
||||||
if visibility_changed:
|
if visibility_changed:
|
||||||
self.visibilityChanged.emit()
|
self.visibilityChanged.emit()
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
// Copyright (c) 2017 Ultimaker B.V.
|
// Copyright (c) 2021 Ultimaker B.V.
|
||||||
// Uranium is released under the terms of the LGPLv3 or higher.
|
// Uranium is released under the terms of the LGPLv3 or higher.
|
||||||
|
|
||||||
import QtQuick 2.2
|
import QtQuick 2.2
|
||||||
@ -116,7 +116,7 @@ Item
|
|||||||
{
|
{
|
||||||
id: antiOverhangMeshButton
|
id: antiOverhangMeshButton
|
||||||
text: catalog.i18nc("@label", "Don't support overlaps")
|
text: catalog.i18nc("@label", "Don't support overlaps")
|
||||||
iconSource: UM.Theme.getIcon("MeshTypeExclude");
|
iconSource: UM.Theme.getIcon("BlockSupportOverlaps");
|
||||||
property bool needBorder: true
|
property bool needBorder: true
|
||||||
checkable: true
|
checkable: true
|
||||||
onClicked: setMeshType(antiOverhangMeshType)
|
onClicked: setMeshType(antiOverhangMeshType)
|
||||||
@ -136,10 +136,12 @@ Item
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
ComboBox
|
Cura.ComboBox
|
||||||
{
|
{
|
||||||
id: infillOnlyComboBox
|
id: infillOnlyComboBox
|
||||||
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
width: parent.width / 2 - UM.Theme.getSize("default_margin").width
|
||||||
|
height: UM.Theme.getSize("setting_control").height
|
||||||
|
textRole: "text"
|
||||||
|
|
||||||
model: ListModel
|
model: ListModel
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides the Per Model Settings.",
|
"description": "Provides the Per Model Settings.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<polygon points="4.4,12 8.2,15.8 6.8,17.2 1.6,12 6.8,6.8 8.2,8.2" />
|
||||||
<g>
|
<polygon points="22.4,12 17.2,17.2 15.8,15.8 19.6,12 15.8,8.2 17.2,6.8" />
|
||||||
<polygon style="fill:#1D1C1A;" points="4.4,12 8.2,15.8 6.8,17.2 1.6,12 6.8,6.8 8.2,8.2 "/>
|
<rect x="3.9" y="11" transform="matrix(0.1236 -0.9923 0.9923 0.1236 -1.429 22.4317)" width="16.1" height="2" />
|
||||||
<polygon style="fill:#1D1C1A;" points="22.4,12 17.2,17.2 15.8,15.8 19.6,12 15.8,8.2 17.2,6.8 "/>
|
|
||||||
|
|
||||||
<rect x="3.9" y="11" transform="matrix(0.1236 -0.9923 0.9923 0.1236 -1.429 22.4317)" style="fill:#1D1C1A;" width="16.1" height="2"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 380 B |
@ -1,14 +1,6 @@
|
|||||||
# Copyright (c) 2020 Jaime van Kessel, Ultimaker B.V.
|
# Copyright (c) 2020 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.
|
||||||
|
|
||||||
# Workaround for a race condition on certain systems where there
|
|
||||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
|
||||||
# first seems to prevent Sip from going into a state where it
|
|
||||||
# tries to create PyQt objects on a non-main thread.
|
|
||||||
import Arcus # @UnusedImport
|
|
||||||
import Savitar # @UnusedImport
|
|
||||||
import pynest2d # @UnusedImport
|
|
||||||
|
|
||||||
from . import PostProcessingPlugin
|
from . import PostProcessingPlugin
|
||||||
|
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "Post Processing",
|
"name": "Post Processing",
|
||||||
"author": "Ultimaker",
|
"author": "Ultimaker",
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"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"
|
||||||
}
|
}
|
@ -72,6 +72,15 @@ class FilamentChange(Script):
|
|||||||
"type": "float",
|
"type": "float",
|
||||||
"default_value": 0,
|
"default_value": 0,
|
||||||
"enabled": "not firmware_config"
|
"enabled": "not firmware_config"
|
||||||
|
},
|
||||||
|
"z_position":
|
||||||
|
{
|
||||||
|
"label": "Z Position (relative)",
|
||||||
|
"description": "Extruder relative Z position. Move the print head up for filament change.",
|
||||||
|
"unit": "mm",
|
||||||
|
"type": "float",
|
||||||
|
"default_value": 0,
|
||||||
|
"minimum_value": 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}"""
|
}"""
|
||||||
@ -87,6 +96,7 @@ class FilamentChange(Script):
|
|||||||
later_retract = self.getSettingValueByKey("later_retract")
|
later_retract = self.getSettingValueByKey("later_retract")
|
||||||
x_pos = self.getSettingValueByKey("x_position")
|
x_pos = self.getSettingValueByKey("x_position")
|
||||||
y_pos = self.getSettingValueByKey("y_position")
|
y_pos = self.getSettingValueByKey("y_position")
|
||||||
|
z_pos = self.getSettingValueByKey("z_position")
|
||||||
firmware_config = self.getSettingValueByKey("firmware_config")
|
firmware_config = self.getSettingValueByKey("firmware_config")
|
||||||
|
|
||||||
color_change = "M600"
|
color_change = "M600"
|
||||||
@ -100,10 +110,13 @@ class FilamentChange(Script):
|
|||||||
|
|
||||||
if x_pos is not None:
|
if x_pos is not None:
|
||||||
color_change = color_change + (" X%.2f" % x_pos)
|
color_change = color_change + (" X%.2f" % x_pos)
|
||||||
|
|
||||||
if y_pos is not None:
|
if y_pos is not None:
|
||||||
color_change = color_change + (" Y%.2f" % y_pos)
|
color_change = color_change + (" Y%.2f" % y_pos)
|
||||||
|
|
||||||
|
if z_pos is not None and z_pos > 0.:
|
||||||
|
color_change = color_change + (" Z%.2f" % z_pos)
|
||||||
|
|
||||||
color_change = color_change + " ; Generated by FilamentChange plugin\n"
|
color_change = color_change + " ; Generated by FilamentChange plugin\n"
|
||||||
|
|
||||||
layer_targets = layer_nums.split(",")
|
layer_targets = layer_nums.split(",")
|
||||||
@ -116,4 +129,4 @@ class FilamentChange(Script):
|
|||||||
if 0 < layer_num < len(data):
|
if 0 < layer_num < len(data):
|
||||||
data[layer_num] = color_change + data[layer_num]
|
data[layer_num] = color_change + data[layer_num]
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 ..Script import Script
|
from ..Script import Script
|
||||||
@ -387,7 +387,7 @@ class PauseAtHeight(Script):
|
|||||||
#Retraction
|
#Retraction
|
||||||
prepend_gcode += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n"
|
prepend_gcode += self.putValue(M = 83) + " ; switch to relative E values for any needed retraction\n"
|
||||||
if retraction_amount != 0:
|
if retraction_amount != 0:
|
||||||
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = 6000) + "\n"
|
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = 6000) + "\n"
|
||||||
|
|
||||||
#Move the head away
|
#Move the head away
|
||||||
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + " ; move up a millimeter to get out of the way\n"
|
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + " ; move up a millimeter to get out of the way\n"
|
||||||
@ -507,10 +507,23 @@ class PauseAtHeight(Script):
|
|||||||
else:
|
else:
|
||||||
Logger.log("w", "No previous feedrate found in gcode, feedrate for next layer(s) might be incorrect")
|
Logger.log("w", "No previous feedrate found in gcode, feedrate for next layer(s) might be incorrect")
|
||||||
|
|
||||||
prepend_gcode += self.putValue(M = 82) + " ; switch back to absolute E values\n"
|
extrusion_mode_string = "absolute"
|
||||||
|
extrusion_mode_numeric = 82
|
||||||
|
|
||||||
# reset extrude value to pre pause value
|
relative_extrusion = Application.getInstance().getGlobalContainerStack().getProperty("relative_extrusion", "value")
|
||||||
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
|
if relative_extrusion:
|
||||||
|
extrusion_mode_string = "relative"
|
||||||
|
extrusion_mode_numeric = 83
|
||||||
|
|
||||||
|
prepend_gcode += self.putValue(M = extrusion_mode_numeric) + " ; switch back to " + extrusion_mode_string + " E values\n"
|
||||||
|
|
||||||
|
# reset extrude value to pre pause value
|
||||||
|
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
|
||||||
|
|
||||||
|
elif redo_layer:
|
||||||
|
# All other options reset the E value to what it was before the pause because E things were added.
|
||||||
|
# If it's not yet reset, it still needs to be reset if there were any redo layers.
|
||||||
|
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
|
||||||
|
|
||||||
layer = prepend_gcode + layer
|
layer = prepend_gcode + layer
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@ import QtQuick.Controls 2.3
|
|||||||
import UM 1.3 as UM
|
import UM 1.3 as UM
|
||||||
import Cura 1.1 as Cura
|
import Cura 1.1 as Cura
|
||||||
|
|
||||||
import QtGraphicalEffects 1.0 // For the dropshadow
|
|
||||||
|
|
||||||
Item
|
Item
|
||||||
{
|
{
|
||||||
@ -42,42 +41,34 @@ Item
|
|||||||
anchors.left: openFileButton.right
|
anchors.left: openFileButton.right
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
anchors.leftMargin: UM.Theme.getSize("default_margin").width
|
||||||
|
property int machineSelectorWidth: Math.round((width - printSetupSelectorItem.width) / 3)
|
||||||
|
|
||||||
height: parent.height
|
height: parent.height
|
||||||
spacing: 0
|
// This is a trick to make sure that the borders of the two adjacent buttons' borders overlap. Otherwise
|
||||||
|
// there will be double border (one from each button)
|
||||||
|
spacing: -UM.Theme.getSize("default_lining").width
|
||||||
|
|
||||||
Cura.MachineSelector
|
Cura.MachineSelector
|
||||||
{
|
{
|
||||||
id: machineSelection
|
id: machineSelection
|
||||||
headerCornerSide: Cura.RoundedRectangle.Direction.Left
|
headerCornerSide: Cura.RoundedRectangle.Direction.Left
|
||||||
Layout.minimumWidth: UM.Theme.getSize("machine_selector_widget").width
|
headerBackgroundBorder.width: UM.Theme.getSize("default_lining").width
|
||||||
Layout.maximumWidth: UM.Theme.getSize("machine_selector_widget").width
|
headerBackgroundBorder.color: UM.Theme.getColor("lining")
|
||||||
|
enableHeaderShadow: false
|
||||||
|
Layout.preferredWidth: parent.machineSelectorWidth
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Separator line
|
|
||||||
Rectangle
|
|
||||||
{
|
|
||||||
height: parent.height
|
|
||||||
width: UM.Theme.getSize("default_lining").width
|
|
||||||
color: UM.Theme.getColor("lining")
|
|
||||||
}
|
|
||||||
|
|
||||||
Cura.ConfigurationMenu
|
Cura.ConfigurationMenu
|
||||||
{
|
{
|
||||||
id: printerSetup
|
id: printerSetup
|
||||||
|
enableHeaderShadow: false
|
||||||
|
headerBackgroundBorder.width: UM.Theme.getSize("default_lining").width
|
||||||
|
headerBackgroundBorder.color: UM.Theme.getColor("lining")
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredWidth: itemRow.width - machineSelection.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width
|
Layout.preferredWidth: parent.machineSelectorWidth * 2
|
||||||
}
|
|
||||||
|
|
||||||
// Separator line
|
|
||||||
Rectangle
|
|
||||||
{
|
|
||||||
height: parent.height
|
|
||||||
width: UM.Theme.getSize("default_lining").width
|
|
||||||
color: UM.Theme.getColor("lining")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Item
|
Item
|
||||||
@ -120,24 +111,12 @@ Item
|
|||||||
id: background
|
id: background
|
||||||
height: UM.Theme.getSize("stage_menu").height
|
height: UM.Theme.getSize("stage_menu").height
|
||||||
width: UM.Theme.getSize("stage_menu").height
|
width: UM.Theme.getSize("stage_menu").height
|
||||||
|
border.color: UM.Theme.getColor("lining")
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
|
||||||
radius: UM.Theme.getSize("default_radius").width
|
radius: UM.Theme.getSize("default_radius").width
|
||||||
color: openFileButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
|
color: openFileButton.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
|
||||||
}
|
}
|
||||||
|
|
||||||
DropShadow
|
|
||||||
{
|
|
||||||
id: shadow
|
|
||||||
// Don't blur the shadow
|
|
||||||
radius: 0
|
|
||||||
anchors.fill: background
|
|
||||||
source: background
|
|
||||||
verticalOffset: 2
|
|
||||||
visible: true
|
|
||||||
color: UM.Theme.getColor("action_button_shadow")
|
|
||||||
// Should always be drawn behind the background.
|
|
||||||
z: background.z - 1
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a prepare stage in Cura.",
|
"description": "Provides a prepare stage in Cura.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a preview stage in Cura.",
|
"description": "Provides a preview stage in Cura.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"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.1",
|
"version": "1.0.1",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"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": "Logs certain events so that they can be used by the crash reporter",
|
"description": "Logs certain events so that they can be used by the crash reporter",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class SimulationPass(RenderPass):
|
|||||||
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
self._layer_shader.setUniformValue("u_active_extruder", float(max(0, self._extruder_manager.activeExtruderIndex)))
|
||||||
if not self._compatibility_mode:
|
if not self._compatibility_mode:
|
||||||
self._layer_shader.setUniformValue("u_starts_color", Color(*Application.getInstance().getTheme().getColor("layerview_starts").getRgb()))
|
self._layer_shader.setUniformValue("u_starts_color", Color(*Application.getInstance().getTheme().getColor("layerview_starts").getRgb()))
|
||||||
|
|
||||||
if self._layer_view:
|
if self._layer_view:
|
||||||
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
|
self._layer_shader.setUniformValue("u_max_feedrate", self._layer_view.getMaxFeedrate())
|
||||||
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
|
self._layer_shader.setUniformValue("u_min_feedrate", self._layer_view.getMinFeedrate())
|
||||||
@ -73,6 +73,8 @@ class SimulationPass(RenderPass):
|
|||||||
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
|
self._layer_shader.setUniformValue("u_min_thickness", self._layer_view.getMinThickness())
|
||||||
self._layer_shader.setUniformValue("u_max_line_width", self._layer_view.getMaxLineWidth())
|
self._layer_shader.setUniformValue("u_max_line_width", self._layer_view.getMaxLineWidth())
|
||||||
self._layer_shader.setUniformValue("u_min_line_width", self._layer_view.getMinLineWidth())
|
self._layer_shader.setUniformValue("u_min_line_width", self._layer_view.getMinLineWidth())
|
||||||
|
self._layer_shader.setUniformValue("u_max_flow_rate", self._layer_view.getMaxFlowRate())
|
||||||
|
self._layer_shader.setUniformValue("u_min_flow_rate", self._layer_view.getMinFlowRate())
|
||||||
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
|
self._layer_shader.setUniformValue("u_layer_view_type", self._layer_view.getSimulationViewType())
|
||||||
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
|
self._layer_shader.setUniformValue("u_extruder_opacity", self._layer_view.getExtruderOpacities())
|
||||||
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
|
self._layer_shader.setUniformValue("u_show_travel_moves", self._layer_view.getShowTravelMoves())
|
||||||
@ -86,6 +88,8 @@ class SimulationPass(RenderPass):
|
|||||||
self._layer_shader.setUniformValue("u_min_feedrate", 0)
|
self._layer_shader.setUniformValue("u_min_feedrate", 0)
|
||||||
self._layer_shader.setUniformValue("u_max_thickness", 1)
|
self._layer_shader.setUniformValue("u_max_thickness", 1)
|
||||||
self._layer_shader.setUniformValue("u_min_thickness", 0)
|
self._layer_shader.setUniformValue("u_min_thickness", 0)
|
||||||
|
self._layer_shader.setUniformValue("u_max_flow_rate", 1)
|
||||||
|
self._layer_shader.setUniformValue("u_min_flow_rate", 0)
|
||||||
self._layer_shader.setUniformValue("u_max_line_width", 1)
|
self._layer_shader.setUniformValue("u_max_line_width", 1)
|
||||||
self._layer_shader.setUniformValue("u_min_line_width", 0)
|
self._layer_shader.setUniformValue("u_min_line_width", 0)
|
||||||
self._layer_shader.setUniformValue("u_layer_view_type", 1)
|
self._layer_shader.setUniformValue("u_layer_view_type", 1)
|
||||||
@ -174,9 +178,9 @@ class SimulationPass(RenderPass):
|
|||||||
self._switching_layers = True
|
self._switching_layers = True
|
||||||
|
|
||||||
# The first line does not have a previous line: add a MoveCombingType in front for start detection
|
# The first line does not have a previous line: add a MoveCombingType in front for start detection
|
||||||
# this way the first start of the layer can also be drawn
|
# this way the first start of the layer can also be drawn
|
||||||
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
prev_line_types = numpy.concatenate([numpy.asarray([LayerPolygon.MoveCombingType], dtype = numpy.float32), layer_data._attributes["line_types"]["value"]])
|
||||||
# Remove the last element
|
# Remove the last element
|
||||||
prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
|
prev_line_types = prev_line_types[0:layer_data._attributes["line_types"]["value"].size]
|
||||||
layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
|
layer_data._attributes["prev_line_types"] = {'opengl_type': 'float', 'value': prev_line_types, 'opengl_name': 'a_prev_line_type'}
|
||||||
|
|
||||||
|
@ -94,6 +94,8 @@ class SimulationView(CuraView):
|
|||||||
self._min_thickness = sys.float_info.max
|
self._min_thickness = sys.float_info.max
|
||||||
self._max_line_width = sys.float_info.min
|
self._max_line_width = sys.float_info.min
|
||||||
self._min_line_width = sys.float_info.max
|
self._min_line_width = sys.float_info.max
|
||||||
|
self._min_flow_rate = sys.float_info.max
|
||||||
|
self._max_flow_rate = sys.float_info.min
|
||||||
|
|
||||||
self._global_container_stack = None # type: Optional[ContainerStack]
|
self._global_container_stack = None # type: Optional[ContainerStack]
|
||||||
self._proxy = None
|
self._proxy = None
|
||||||
@ -411,6 +413,14 @@ class SimulationView(CuraView):
|
|||||||
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||||
return self._min_line_width
|
return self._min_line_width
|
||||||
|
|
||||||
|
def getMaxFlowRate(self) -> float:
|
||||||
|
return self._max_flow_rate
|
||||||
|
|
||||||
|
def getMinFlowRate(self) -> float:
|
||||||
|
if abs(self._min_flow_rate - sys.float_info.max) < 10: # Some lenience due to floating point rounding.
|
||||||
|
return 0.0 # If it's still max-float, there are no measurements. Use 0 then.
|
||||||
|
return self._min_flow_rate
|
||||||
|
|
||||||
def calculateMaxLayers(self) -> None:
|
def calculateMaxLayers(self) -> None:
|
||||||
"""
|
"""
|
||||||
Calculates number of layers, triggers signals if the number of layers changed and makes sure the top layers are
|
Calculates number of layers, triggers signals if the number of layers changed and makes sure the top layers are
|
||||||
@ -468,6 +478,8 @@ class SimulationView(CuraView):
|
|||||||
old_max_linewidth = self._max_line_width
|
old_max_linewidth = self._max_line_width
|
||||||
old_min_thickness = self._min_thickness
|
old_min_thickness = self._min_thickness
|
||||||
old_max_thickness = self._max_thickness
|
old_max_thickness = self._max_thickness
|
||||||
|
old_min_flow_rate = self._min_flow_rate
|
||||||
|
old_max_flow_rate = self._max_flow_rate
|
||||||
|
|
||||||
self._min_feedrate = sys.float_info.max
|
self._min_feedrate = sys.float_info.max
|
||||||
self._max_feedrate = sys.float_info.min
|
self._max_feedrate = sys.float_info.min
|
||||||
@ -475,6 +487,8 @@ class SimulationView(CuraView):
|
|||||||
self._max_line_width = sys.float_info.min
|
self._max_line_width = sys.float_info.min
|
||||||
self._min_thickness = sys.float_info.max
|
self._min_thickness = sys.float_info.max
|
||||||
self._max_thickness = sys.float_info.min
|
self._max_thickness = sys.float_info.min
|
||||||
|
self._min_flow_rate = sys.float_info.max
|
||||||
|
self._max_flow_rate = sys.float_info.min
|
||||||
|
|
||||||
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
|
# The colour scheme is only influenced by the visible lines, so filter the lines by if they should be visible.
|
||||||
visible_line_types = []
|
visible_line_types = []
|
||||||
@ -490,6 +504,7 @@ class SimulationView(CuraView):
|
|||||||
visible_line_types.append(LayerPolygon.SupportType)
|
visible_line_types.append(LayerPolygon.SupportType)
|
||||||
visible_line_types.append(LayerPolygon.SupportInfillType)
|
visible_line_types.append(LayerPolygon.SupportInfillType)
|
||||||
visible_line_types.append(LayerPolygon.SupportInterfaceType)
|
visible_line_types.append(LayerPolygon.SupportInterfaceType)
|
||||||
|
visible_line_types_with_extrusion = visible_line_types.copy() # Copy before travel moves are added
|
||||||
if self.getShowTravelMoves():
|
if self.getShowTravelMoves():
|
||||||
visible_line_types.append(LayerPolygon.MoveCombingType)
|
visible_line_types.append(LayerPolygon.MoveCombingType)
|
||||||
visible_line_types.append(LayerPolygon.MoveRetractionType)
|
visible_line_types.append(LayerPolygon.MoveRetractionType)
|
||||||
@ -503,12 +518,20 @@ class SimulationView(CuraView):
|
|||||||
for polyline in layer_data.getLayer(layer_index).polygons:
|
for polyline in layer_data.getLayer(layer_index).polygons:
|
||||||
is_visible = numpy.isin(polyline.types, visible_line_types)
|
is_visible = numpy.isin(polyline.types, visible_line_types)
|
||||||
visible_indices = numpy.where(is_visible)[0]
|
visible_indices = numpy.where(is_visible)[0]
|
||||||
|
visible_indicies_with_extrusion = numpy.where(numpy.isin(polyline.types, visible_line_types_with_extrusion))[0]
|
||||||
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
if visible_indices.size == 0: # No items to take maximum or minimum of.
|
||||||
continue
|
continue
|
||||||
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
visible_feedrates = numpy.take(polyline.lineFeedrates, visible_indices)
|
||||||
|
visible_feedrates_with_extrusion = numpy.take(polyline.lineFeedrates, visible_indicies_with_extrusion)
|
||||||
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
|
visible_linewidths = numpy.take(polyline.lineWidths, visible_indices)
|
||||||
|
visible_linewidths_with_extrusion = numpy.take(polyline.lineWidths, visible_indicies_with_extrusion)
|
||||||
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
visible_thicknesses = numpy.take(polyline.lineThicknesses, visible_indices)
|
||||||
|
visible_thicknesses_with_extrusion = numpy.take(polyline.lineThicknesses, visible_indicies_with_extrusion)
|
||||||
self._max_feedrate = max(float(visible_feedrates.max()), self._max_feedrate)
|
self._max_feedrate = max(float(visible_feedrates.max()), self._max_feedrate)
|
||||||
|
if visible_feedrates_with_extrusion.size != 0:
|
||||||
|
flow_rates = visible_feedrates_with_extrusion * visible_linewidths_with_extrusion * visible_thicknesses_with_extrusion
|
||||||
|
self._min_flow_rate = min(float(flow_rates.min()), self._min_flow_rate)
|
||||||
|
self._max_flow_rate = max(float(flow_rates.max()), self._max_flow_rate)
|
||||||
self._min_feedrate = min(float(visible_feedrates.min()), self._min_feedrate)
|
self._min_feedrate = min(float(visible_feedrates.min()), self._min_feedrate)
|
||||||
self._max_line_width = max(float(visible_linewidths.max()), self._max_line_width)
|
self._max_line_width = max(float(visible_linewidths.max()), self._max_line_width)
|
||||||
self._min_line_width = min(float(visible_linewidths.min()), self._min_line_width)
|
self._min_line_width = min(float(visible_linewidths.min()), self._min_line_width)
|
||||||
@ -517,11 +540,12 @@ class SimulationView(CuraView):
|
|||||||
self._min_thickness = min(float(visible_thicknesses[numpy.nonzero(visible_thicknesses)].min()), self._min_thickness)
|
self._min_thickness = min(float(visible_thicknesses[numpy.nonzero(visible_thicknesses)].min()), self._min_thickness)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding the zero) can't be calculated.
|
# Sometimes, when importing a GCode the line thicknesses are zero and so the minimum (avoiding the zero) can't be calculated.
|
||||||
Logger.log("i", "Min thickness can't be calculated because all the values are zero")
|
Logger.log("w", "Min thickness can't be calculated because all the values are zero")
|
||||||
|
|
||||||
if old_min_feedrate != self._min_feedrate or old_max_feedrate != self._max_feedrate \
|
if old_min_feedrate != self._min_feedrate or old_max_feedrate != self._max_feedrate \
|
||||||
or old_min_linewidth != self._min_line_width or old_max_linewidth != self._max_line_width \
|
or old_min_linewidth != self._min_line_width or old_max_linewidth != self._max_line_width \
|
||||||
or old_min_thickness != self._min_thickness or old_max_thickness != self._max_thickness:
|
or old_min_thickness != self._min_thickness or old_max_thickness != self._max_thickness \
|
||||||
|
or old_min_flow_rate != self._min_flow_rate or old_max_flow_rate != self._max_flow_rate:
|
||||||
self.colorSchemeLimitsChanged.emit()
|
self.colorSchemeLimitsChanged.emit()
|
||||||
|
|
||||||
def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
|
def calculateMaxPathsOnLayer(self, layer_num: int) -> None:
|
||||||
|
@ -90,6 +90,7 @@ Cura.ExpandableComponent
|
|||||||
property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
|
property bool show_feedrate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 2
|
||||||
property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
|
property bool show_thickness_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 3
|
||||||
property bool show_line_width_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 4
|
property bool show_line_width_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 4
|
||||||
|
property bool show_flow_rate_gradient: show_gradient && UM.Preferences.getValue("layerview/layer_view_type") == 5
|
||||||
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
property bool only_show_top_layers: UM.Preferences.getValue("view/only_show_top_layers")
|
||||||
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
property int top_layer_count: UM.Preferences.getValue("view/top_layer_count")
|
||||||
|
|
||||||
@ -125,6 +126,10 @@ Cura.ExpandableComponent
|
|||||||
text: catalog.i18nc("@label:listbox", "Line Width"),
|
text: catalog.i18nc("@label:listbox", "Line Width"),
|
||||||
type_id: 4
|
type_id: 4
|
||||||
})
|
})
|
||||||
|
layerViewTypes.append({
|
||||||
|
text: catalog.i18nc("@label:listbox", "Flow"),
|
||||||
|
type_id: 5
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
ComboBox
|
ComboBox
|
||||||
@ -150,10 +155,13 @@ Cura.ExpandableComponent
|
|||||||
{
|
{
|
||||||
// Update the visibility of the legends.
|
// Update the visibility of the legends.
|
||||||
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
|
viewSettings.show_legend = UM.SimulationView.compatibilityMode || (type_id == 1);
|
||||||
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode && (type_id == 2 || type_id == 3 || type_id == 4);
|
viewSettings.show_gradient = !UM.SimulationView.compatibilityMode &&
|
||||||
|
(type_id == 2 || type_id == 3 || type_id == 4 || type_id == 5) ;
|
||||||
|
|
||||||
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
|
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
|
||||||
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
|
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
|
||||||
viewSettings.show_line_width_gradient = viewSettings.show_gradient && (type_id == 4);
|
viewSettings.show_line_width_gradient = viewSettings.show_gradient && (type_id == 4);
|
||||||
|
viewSettings.show_flow_rate_gradient = viewSettings.show_gradient && (type_id == 5);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -396,11 +404,17 @@ Cura.ExpandableComponent
|
|||||||
{
|
{
|
||||||
return parseFloat(UM.SimulationView.minThickness).toFixed(2)
|
return parseFloat(UM.SimulationView.minThickness).toFixed(2)
|
||||||
}
|
}
|
||||||
//Line width selected
|
// Line width selected
|
||||||
if(UM.Preferences.getValue("layerview/layer_view_type") == 4)
|
if(UM.Preferences.getValue("layerview/layer_view_type") == 4)
|
||||||
{
|
{
|
||||||
return parseFloat(UM.SimulationView.minLineWidth).toFixed(2);
|
return parseFloat(UM.SimulationView.minLineWidth).toFixed(2);
|
||||||
}
|
}
|
||||||
|
// Flow Rate selected
|
||||||
|
if(UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||||
|
{
|
||||||
|
return parseFloat(UM.SimulationView.minFlowRate).toFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return catalog.i18nc("@label","min")
|
return catalog.i18nc("@label","min")
|
||||||
}
|
}
|
||||||
@ -431,6 +445,11 @@ Cura.ExpandableComponent
|
|||||||
{
|
{
|
||||||
return "mm"
|
return "mm"
|
||||||
}
|
}
|
||||||
|
// Flow Rate selected
|
||||||
|
if (UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||||
|
{
|
||||||
|
return "mm³/s"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
@ -460,6 +479,11 @@ Cura.ExpandableComponent
|
|||||||
{
|
{
|
||||||
return parseFloat(UM.SimulationView.maxLineWidth).toFixed(2);
|
return parseFloat(UM.SimulationView.maxLineWidth).toFixed(2);
|
||||||
}
|
}
|
||||||
|
// Flow rate selected
|
||||||
|
if(UM.Preferences.getValue("layerview/layer_view_type") == 5)
|
||||||
|
{
|
||||||
|
return parseFloat(UM.SimulationView.maxFlowRate).toFixed(2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return catalog.i18nc("@label","max")
|
return catalog.i18nc("@label","max")
|
||||||
}
|
}
|
||||||
@ -474,7 +498,10 @@ Cura.ExpandableComponent
|
|||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: feedrateGradient
|
id: feedrateGradient
|
||||||
visible: viewSettings.show_feedrate_gradient || viewSettings.show_line_width_gradient
|
visible: (
|
||||||
|
viewSettings.show_feedrate_gradient ||
|
||||||
|
viewSettings.show_line_width_gradient
|
||||||
|
)
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||||
@ -526,7 +553,9 @@ Cura.ExpandableComponent
|
|||||||
Rectangle
|
Rectangle
|
||||||
{
|
{
|
||||||
id: thicknessGradient
|
id: thicknessGradient
|
||||||
visible: viewSettings.show_thickness_gradient
|
visible: (
|
||||||
|
viewSettings.show_thickness_gradient
|
||||||
|
)
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||||
@ -578,6 +607,85 @@ Cura.ExpandableComponent
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Gradient colors for flow (similar to jet colormap)
|
||||||
|
Rectangle
|
||||||
|
{
|
||||||
|
id: jetGradient
|
||||||
|
visible: (
|
||||||
|
viewSettings.show_flow_rate_gradient
|
||||||
|
)
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
height: Math.round(UM.Theme.getSize("layerview_row").height * 1.5)
|
||||||
|
border.width: UM.Theme.getSize("default_lining").width
|
||||||
|
border.color: UM.Theme.getColor("lining")
|
||||||
|
|
||||||
|
LinearGradient
|
||||||
|
{
|
||||||
|
anchors
|
||||||
|
{
|
||||||
|
left: parent.left
|
||||||
|
leftMargin: UM.Theme.getSize("default_lining").width
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: UM.Theme.getSize("default_lining").width
|
||||||
|
top: parent.top
|
||||||
|
topMargin: UM.Theme.getSize("default_lining").width
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: UM.Theme.getSize("default_lining").width
|
||||||
|
}
|
||||||
|
start: Qt.point(0, 0)
|
||||||
|
end: Qt.point(parent.width, 0)
|
||||||
|
gradient: Gradient
|
||||||
|
{
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.0
|
||||||
|
color: Qt.rgba(0, 0, 0.5, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.125
|
||||||
|
color: Qt.rgba(0, 0.0, 1.0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.25
|
||||||
|
color: Qt.rgba(0, 0.5, 1.0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.375
|
||||||
|
color: Qt.rgba(0.0, 1.0, 1.0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.5
|
||||||
|
color: Qt.rgba(0.5, 1.0, 0.5, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.625
|
||||||
|
color: Qt.rgba(1.0, 1.0, 0.0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.75
|
||||||
|
color: Qt.rgba(1.0, 0.5, 0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 0.875
|
||||||
|
color: Qt.rgba(1.0, 0.0, 0, 1)
|
||||||
|
}
|
||||||
|
GradientStop
|
||||||
|
{
|
||||||
|
position: 1.0
|
||||||
|
color: Qt.rgba(0.5, 0, 0, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FontMetrics
|
FontMetrics
|
||||||
|
@ -126,6 +126,14 @@ class SimulationViewProxy(QObject):
|
|||||||
def minLineWidth(self):
|
def minLineWidth(self):
|
||||||
return self._simulation_view.getMinLineWidth()
|
return self._simulation_view.getMinLineWidth()
|
||||||
|
|
||||||
|
@pyqtProperty(float, notify=colorSchemeLimitsChanged)
|
||||||
|
def maxFlowRate(self):
|
||||||
|
return self._simulation_view.getMaxFlowRate()
|
||||||
|
|
||||||
|
@pyqtProperty(float, notify=colorSchemeLimitsChanged)
|
||||||
|
def minFlowRate(self):
|
||||||
|
return self._simulation_view.getMinFlowRate()
|
||||||
|
|
||||||
# Opacity 0..1
|
# Opacity 0..1
|
||||||
@pyqtSlot(int, float)
|
@pyqtSlot(int, float)
|
||||||
def setExtruderOpacity(self, extruder_nr, opacity):
|
def setExtruderOpacity(self, extruder_nr, opacity):
|
||||||
|
@ -12,6 +12,8 @@ vertex41core =
|
|||||||
uniform lowp float u_min_thickness;
|
uniform lowp float u_min_thickness;
|
||||||
uniform lowp float u_max_line_width;
|
uniform lowp float u_max_line_width;
|
||||||
uniform lowp float u_min_line_width;
|
uniform lowp float u_min_line_width;
|
||||||
|
uniform lowp float u_max_flow_rate;
|
||||||
|
uniform lowp float u_min_flow_rate;
|
||||||
uniform lowp int u_layer_view_type;
|
uniform lowp int u_layer_view_type;
|
||||||
uniform lowp mat4 u_extruder_opacity; // currently only for max 16 extruders, others always visible
|
uniform lowp mat4 u_extruder_opacity; // currently only for max 16 extruders, others always visible
|
||||||
|
|
||||||
@ -105,6 +107,30 @@ vertex41core =
|
|||||||
return vec4(red, green, blue, 1.0);
|
return vec4(red, green, blue, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
float clamp(float v)
|
||||||
|
{
|
||||||
|
float t = v < 0 ? 0 : v;
|
||||||
|
return t > 1.0 ? 1.0 : t;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired by https://stackoverflow.com/a/46628410
|
||||||
|
vec4 flowRateGradientColor(float abs_value, float min_value, float max_value)
|
||||||
|
{
|
||||||
|
float t;
|
||||||
|
if(abs(min_value - max_value) < 0.0001)
|
||||||
|
{
|
||||||
|
t = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
t = 2.0 * ((abs_value - min_value) / (max_value - min_value)) - 1;
|
||||||
|
}
|
||||||
|
float red = clamp(1.5 - abs(2.0 * t - 1.0));
|
||||||
|
float green = clamp(1.5 - abs(2.0 * t));
|
||||||
|
float blue = clamp(1.5 - abs(2.0 * t + 1.0));
|
||||||
|
return vec4(red, green, blue, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 v1_vertex = a_vertex;
|
vec4 v1_vertex = a_vertex;
|
||||||
@ -130,6 +156,10 @@ vertex41core =
|
|||||||
case 4: // "Line width"
|
case 4: // "Line width"
|
||||||
v_color = lineWidthGradientColor(a_line_dim.x, u_min_line_width, u_max_line_width);
|
v_color = lineWidthGradientColor(a_line_dim.x, u_min_line_width, u_max_line_width);
|
||||||
break;
|
break;
|
||||||
|
case 5: // "Flow"
|
||||||
|
float flow_rate = a_line_dim.x * a_line_dim.y * a_feedrate;
|
||||||
|
v_color = flowRateGradientColor(flow_rate, u_min_flow_rate, u_max_flow_rate);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
v_vertex = world_space_vert.xyz;
|
v_vertex = world_space_vert.xyz;
|
||||||
@ -318,7 +348,6 @@ geometry41core =
|
|||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if ((u_show_starts == 1) && (v_prev_line_type[0] != 1) && (v_line_type[0] == 1)) {
|
if ((u_show_starts == 1) && (v_prev_line_type[0] != 1) && (v_line_type[0] == 1)) {
|
||||||
float w = size_x;
|
float w = size_x;
|
||||||
float h = size_y;
|
float h = size_y;
|
||||||
@ -337,7 +366,7 @@ geometry41core =
|
|||||||
myEmitVertex(v_vertex[0] + vec3(-w, -h, -w), u_starts_color, normalize(vec3(-1.0, -1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, -h, -w, 0.0))); // Back-bottom-right
|
myEmitVertex(v_vertex[0] + vec3(-w, -h, -w), u_starts_color, normalize(vec3(-1.0, -1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, -h, -w, 0.0))); // Back-bottom-right
|
||||||
myEmitVertex(v_vertex[0] + vec3( w, h, -w), u_starts_color, normalize(vec3( 1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4( w, h, -w, 0.0))); // Back-top-left
|
myEmitVertex(v_vertex[0] + vec3( w, h, -w), u_starts_color, normalize(vec3( 1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4( w, h, -w, 0.0))); // Back-top-left
|
||||||
myEmitVertex(v_vertex[0] + vec3(-w, h, -w), u_starts_color, normalize(vec3(-1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, h, -w, 0.0))); // Back-top-right
|
myEmitVertex(v_vertex[0] + vec3(-w, h, -w), u_starts_color, normalize(vec3(-1.0, 1.0, -1.0)), viewProjectionMatrix * (gl_in[0].gl_Position + vec4(-w, h, -w, 0.0))); // Back-top-right
|
||||||
|
|
||||||
EndPrimitive();
|
EndPrimitive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides the Simulation view.",
|
"description": "Provides the Simulation view.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M8,21H6V3h2V21z M18,3h-2v18h2V3z" />
|
||||||
<path style="fill:#000E1A;" d="M8,21H6V3h2V21z M18,3h-2v18h2V3z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 329 B After Width: | Height: | Size: 167 B |
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M5,20V4c0-0.8,0.9-1.3,1.5-0.9l13,8c0.6,0.4,0.6,1.3,0,1.7l-13,8C5.9,21.3,5,20.8,5,20z M7,5.8v12.4
|
||||||
<path style="fill:#231F20;" d="M5,20V4c0-0.8,0.9-1.3,1.5-0.9l13,8c0.6,0.4,0.6,1.3,0,1.7l-13,8C5.9,21.3,5,20.8,5,20z M7,5.8v12.4
|
|
||||||
L17.1,12L7,5.8z"/>
|
L17.1,12L7,5.8z"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 410 B After Width: | Height: | Size: 247 B |
@ -1,4 +1,4 @@
|
|||||||
# Copyright (c) 2020 Ultimaker B.V.
|
# Copyright (c) 2021 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 json
|
import json
|
||||||
@ -87,8 +87,12 @@ class SliceInfo(QObject, Extension):
|
|||||||
return None
|
return None
|
||||||
file_path = os.path.join(plugin_path, "example_data.html")
|
file_path = os.path.join(plugin_path, "example_data.html")
|
||||||
if file_path:
|
if file_path:
|
||||||
with open(file_path, "r", encoding = "utf-8") as f:
|
try:
|
||||||
self._example_data_content = f.read()
|
with open(file_path, "r", encoding = "utf-8") as f:
|
||||||
|
self._example_data_content = f.read()
|
||||||
|
except EnvironmentError as e:
|
||||||
|
Logger.error(f"Unable to read example slice info data to show to the user: {e}")
|
||||||
|
self._example_data_content = "<i>" + catalog.i18nc("@text", "Unable to read example data file.") + "</i>"
|
||||||
return self._example_data_content
|
return self._example_data_content
|
||||||
|
|
||||||
@pyqtSlot(bool)
|
@pyqtSlot(bool)
|
||||||
@ -229,6 +233,11 @@ class SliceInfo(QObject, Extension):
|
|||||||
|
|
||||||
model["model_settings"] = model_settings
|
model["model_settings"] = model_settings
|
||||||
|
|
||||||
|
if node.source_mime_type is None:
|
||||||
|
model["mime_type"] = ""
|
||||||
|
else:
|
||||||
|
model["mime_type"] = node.source_mime_type.name
|
||||||
|
|
||||||
data["models"].append(model)
|
data["models"].append(model)
|
||||||
|
|
||||||
print_times = print_information.printTimes()
|
print_times = print_information.printTimes()
|
||||||
|
@ -54,6 +54,7 @@
|
|||||||
<li><b>Bounding Box:</b> [minimum x, y, z; maximum x, y, z]</li>
|
<li><b>Bounding Box:</b> [minimum x, y, z; maximum x, y, z]</li>
|
||||||
<li><b>Is Helper Mesh:</b> no</li>
|
<li><b>Is Helper Mesh:</b> no</li>
|
||||||
<li><b>Helper Mesh Type:</b> support mesh</li>
|
<li><b>Helper Mesh Type:</b> support mesh</li>
|
||||||
|
<li><b>File type:</b> STL</li>
|
||||||
</ul>
|
</ul>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
"description": "Submits anonymous slice info. Can be disabled through preferences.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides a normal solid mesh view.",
|
"description": "Provides a normal solid mesh view.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
"description": "Creates an eraser mesh to block the printing of support in certain places",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,6 @@
|
|||||||
"name": "Toolbox",
|
"name": "Toolbox",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"description": "Find, manage and install new Cura packages."
|
"description": "Find, manage and install new Cura packages."
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M19,3H5C3.3,3,2,4.3,2,6v3c0,1.5,0.8,2.7,2,3.4V22h16v-9.6c1.2-0.7,2-2,2-3.4V6C22,4.3,20.7,3,19,3z
|
||||||
<path style="fill:#000E1A;" d="M19,3H5C3.3,3,2,4.3,2,6v3c0,1.5,0.8,2.7,2,3.4V22h16v-9.6c1.2-0.7,2-2,2-3.4V6C22,4.3,20.7,3,19,3z
|
M10,5h4v4c0,1.1-0.9,2-2,2s-2-0.9-2-2V5z M4,9V5h4v4c0,1.1-0.9,2-2,2S4,10.1,4,9z M18,20h-4v-5h-4v5H6v-7c1.2,0,2.3-0.5,3-1.4
|
||||||
M10,5h4v4c0,1.1-0.9,2-2,2s-2-0.9-2-2V5z M4,9V5h4v4c0,1.1-0.9,2-2,2S4,10.1,4,9z M18,20h-4v-5h-4v5H6v-7c1.2,0,2.3-0.5,3-1.4
|
c0.7,0.8,1.8,1.4,3,1.4s2.3-0.5,3-1.4c0.7,0.8,1.8,1.4,3,1.4V20z M20,9c0,1.1-0.9,2-2,2s-2-0.9-2-2V5h4V9z" />
|
||||||
c0.7,0.8,1.8,1.4,3,1.4s2.3-0.5,3-1.4c0.7,0.8,1.8,1.4,3,1.4V20z M20,9c0,1.1-0.9,2-2,2s-2-0.9-2-2V5h4V9z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 621 B After Width: | Height: | Size: 458 B |
@ -4,7 +4,7 @@
|
|||||||
import QtQuick 2.10
|
import QtQuick 2.10
|
||||||
import QtQuick.Controls 1.4
|
import QtQuick.Controls 1.4
|
||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
import UM 1.1 as UM
|
import UM 1.1 as UM
|
||||||
|
|
||||||
Rectangle
|
Rectangle
|
||||||
|
@ -3,5 +3,5 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Provides support for reading model files.",
|
"description": "Provides support for reading model files.",
|
||||||
"api": "7.5.0"
|
"api": 7
|
||||||
}
|
}
|
||||||
|
@ -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 Ultimaker Format Packages.",
|
"description": "Provides support for reading Ultimaker Format Packages.",
|
||||||
"supported_sdk_versions": ["7.5.0"],
|
"supported_sdk_versions": ["7.6.0"],
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ from typing import cast, List, Dict
|
|||||||
|
|
||||||
from Charon.VirtualFile import VirtualFile # To open UFP files.
|
from Charon.VirtualFile import VirtualFile # To open UFP files.
|
||||||
from Charon.OpenMode import OpenMode # To indicate that we want to write to UFP files.
|
from Charon.OpenMode import OpenMode # To indicate that we want to write to UFP files.
|
||||||
|
from Charon.filetypes.OpenPackagingConvention import OPCError
|
||||||
from io import StringIO # For converting g-code to bytes.
|
from io import StringIO # For converting g-code to bytes.
|
||||||
|
|
||||||
from PyQt5.QtCore import QBuffer
|
from PyQt5.QtCore import QBuffer
|
||||||
@ -47,35 +48,53 @@ class UFPWriter(MeshWriter):
|
|||||||
archive = VirtualFile()
|
archive = VirtualFile()
|
||||||
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
|
||||||
|
|
||||||
self._writeObjectList(archive)
|
try:
|
||||||
|
self._writeObjectList(archive)
|
||||||
|
|
||||||
# Store the g-code from the scene.
|
# Store the g-code from the scene.
|
||||||
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
|
||||||
|
except EnvironmentError as e:
|
||||||
|
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||||
|
self.setInformation(error_msg)
|
||||||
|
Logger.error(error_msg)
|
||||||
|
return False
|
||||||
gcode_textio = StringIO() # We have to convert the g-code into bytes.
|
gcode_textio = StringIO() # We have to convert the g-code into bytes.
|
||||||
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
|
||||||
success = gcode_writer.write(gcode_textio, None)
|
success = gcode_writer.write(gcode_textio, None)
|
||||||
if not success: # Writing the g-code failed. Then I can also not write the gzipped g-code.
|
if not success: # Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||||
self.setInformation(gcode_writer.getInformation())
|
self.setInformation(gcode_writer.getInformation())
|
||||||
return False
|
return False
|
||||||
gcode = archive.getStream("/3D/model.gcode")
|
try:
|
||||||
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
gcode = archive.getStream("/3D/model.gcode")
|
||||||
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
|
||||||
|
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
|
||||||
|
except EnvironmentError as e:
|
||||||
|
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||||
|
self.setInformation(error_msg)
|
||||||
|
Logger.error(error_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
# Attempt to store the thumbnail, if any:
|
# Attempt to store the thumbnail, if any:
|
||||||
backend = CuraApplication.getInstance().getBackend()
|
backend = CuraApplication.getInstance().getBackend()
|
||||||
snapshot = None if getattr(backend, "getLatestSnapshot", None) is None else backend.getLatestSnapshot()
|
snapshot = None if getattr(backend, "getLatestSnapshot", None) is None else backend.getLatestSnapshot()
|
||||||
if snapshot:
|
if snapshot:
|
||||||
archive.addContentType(extension = "png", mime_type = "image/png")
|
try:
|
||||||
thumbnail = archive.getStream("/Metadata/thumbnail.png")
|
archive.addContentType(extension = "png", mime_type = "image/png")
|
||||||
|
thumbnail = archive.getStream("/Metadata/thumbnail.png")
|
||||||
|
|
||||||
thumbnail_buffer = QBuffer()
|
thumbnail_buffer = QBuffer()
|
||||||
thumbnail_buffer.open(QBuffer.ReadWrite)
|
thumbnail_buffer.open(QBuffer.ReadWrite)
|
||||||
snapshot.save(thumbnail_buffer, "PNG")
|
snapshot.save(thumbnail_buffer, "PNG")
|
||||||
|
|
||||||
thumbnail.write(thumbnail_buffer.data())
|
thumbnail.write(thumbnail_buffer.data())
|
||||||
archive.addRelation(virtual_path = "/Metadata/thumbnail.png",
|
archive.addRelation(virtual_path = "/Metadata/thumbnail.png",
|
||||||
relation_type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
|
relation_type = "http://schemas.openxmlformats.org/package/2006/relationships/metadata/thumbnail",
|
||||||
origin = "/3D/model.gcode")
|
origin = "/3D/model.gcode")
|
||||||
|
except EnvironmentError as e:
|
||||||
|
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||||
|
self.setInformation(error_msg)
|
||||||
|
Logger.error(error_msg)
|
||||||
|
return False
|
||||||
else:
|
else:
|
||||||
Logger.log("w", "Thumbnail not created, cannot save it")
|
Logger.log("w", "Thumbnail not created, cannot save it")
|
||||||
|
|
||||||
@ -90,7 +109,7 @@ class UFPWriter(MeshWriter):
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
|
||||||
except:
|
except OPCError:
|
||||||
Logger.log("w", "The material extension: %s was already added", material_extension)
|
Logger.log("w", "The material extension: %s was already added", material_extension)
|
||||||
|
|
||||||
added_materials = []
|
added_materials = []
|
||||||
@ -120,17 +139,23 @@ class UFPWriter(MeshWriter):
|
|||||||
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
material_file = archive.getStream(material_file_name)
|
try:
|
||||||
material_file.write(serialized_material.encode("UTF-8"))
|
material_file = archive.getStream(material_file_name)
|
||||||
archive.addRelation(virtual_path = material_file_name,
|
material_file.write(serialized_material.encode("UTF-8"))
|
||||||
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
archive.addRelation(virtual_path = material_file_name,
|
||||||
origin = "/3D/model.gcode")
|
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
|
||||||
|
origin = "/3D/model.gcode")
|
||||||
|
except EnvironmentError as e:
|
||||||
|
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||||
|
self.setInformation(error_msg)
|
||||||
|
Logger.error(error_msg)
|
||||||
|
return False
|
||||||
|
|
||||||
added_materials.append(material_file_name)
|
added_materials.append(material_file_name)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
archive.close()
|
archive.close()
|
||||||
except OSError as e:
|
except EnvironmentError as e:
|
||||||
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
error_msg = catalog.i18nc("@info:error", "Can't write to UFP file:") + " " + str(e)
|
||||||
self.setInformation(error_msg)
|
self.setInformation(error_msg)
|
||||||
Logger.error(error_msg)
|
Logger.error(error_msg)
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides support for writing Ultimaker Format Packages.",
|
"description": "Provides support for writing Ultimaker Format Packages.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"description": "Manages network connections to Ultimaker networked printers.",
|
"description": "Manages network connections to Ultimaker networked printers.",
|
||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import QtQuick.Controls 1.4
|
|||||||
import QtQuick.Controls.Styles 1.4
|
import QtQuick.Controls.Styles 1.4
|
||||||
import UM 1.3 as UM
|
import UM 1.3 as UM
|
||||||
import Cura 1.0 as Cura
|
import Cura 1.0 as Cura
|
||||||
import QtGraphicalEffects 1.0
|
|
||||||
|
|
||||||
// This is the root component for the monitor stage.
|
// This is the root component for the monitor stage.
|
||||||
Component
|
Component
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M22,6.4l-4,2V8c0-1.7-1.3-3-3-3H5C3.3,5,2,6.3,2,8v8c0,1.7,1.3,3,3,3h10c1.7,0,3-1.3,3-3v-0.4l4,2
|
||||||
<path style="fill:#000E1A;" d="M22,6.4l-4,2V8c0-1.7-1.3-3-3-3H5C3.3,5,2,6.3,2,8v8c0,1.7,1.3,3,3,3h10c1.7,0,3-1.3,3-3v-0.4l4,2
|
V6.4z M16,17H4V7h12V17z M20,14.4l-2-1c0-0.9,0-1.8,0-2.8l2-1V14.4z" />
|
||||||
V6.4z M16,17H4V7h12V17z M20,14.4l-2-1c0-0.9,0-1.8,0-2.8l2-1V14.4z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 458 B After Width: | Height: | Size: 296 B |
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8
|
||||||
<path style="fill:#000E1A;" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M12,20c-4.4,0-8-3.6-8-8s3.6-8,8-8
|
|
||||||
s8,3.6,8,8S16.4,20,12,20z M16.7,8.7L13.4,12l3.3,3.3l-1.4,1.4L12,13.4l-3.3,3.3l-1.4-1.4l3.3-3.3L7.3,8.7l1.4-1.4l3.3,3.3l3.3-3.3
|
s8,3.6,8,8S16.4,20,12,20z M16.7,8.7L13.4,12l3.3,3.3l-1.4,1.4L12,13.4l-3.3,3.3l-1.4-1.4l3.3-3.3L7.3,8.7l1.4-1.4l3.3,3.3l3.3-3.3
|
||||||
L16.7,8.7z"/>
|
L16.7,8.7z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 532 B After Width: | Height: | Size: 370 B |
@ -1,5 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<polygon points="9.1,19.4 1.9,12.2 3.3,10.8 9.1,16.6 19.4,6.3 20.8,7.7" />
|
||||||
<polygon style="fill:#000E1A;" points="9.1,19.4 1.9,12.2 3.3,10.8 9.1,16.6 19.4,6.3 20.8,7.7 "/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 359 B After Width: | Height: | Size: 196 B |
@ -1,9 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10s10-4.5,10-10C22,6.5,17.5,2,12,2z M12,20
|
||||||
<g>
|
c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8s8,3.6,8,8C20,16.4,16.4,20,12,20z" />
|
||||||
<path style="fill:#000E1A;" d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10s10-4.5,10-10C22,6.5,17.5,2,12,2z M12,20
|
<polygon points="16.3,8.3 11,13.6 7.7,10.3 6.3,11.7 11,16.4 17.7,9.7" />
|
||||||
c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8s8,3.6,8,8C20,16.4,16.4,20,12,20z"/>
|
|
||||||
<polygon style="fill:#000E1A;" points="16.3,8.3 11,13.6 7.7,10.3 6.3,11.7 11,16.4 17.7,9.7 "/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 554 B After Width: | Height: | Size: 357 B |
@ -1,10 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10s10-4.5,10-10C22,6.5,17.5,2,12,2z M12,20
|
||||||
<g>
|
c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8s8,3.6,8,8C20,16.4,16.4,20,12,20z" />
|
||||||
<path style="fill:#000E1A;" d="M12,2C6.5,2,2,6.5,2,12c0,5.5,4.5,10,10,10s10-4.5,10-10C22,6.5,17.5,2,12,2z M12,20
|
<rect x="13" y="8" width="2" height="8"/>
|
||||||
c-4.4,0-8-3.6-8-8c0-4.4,3.6-8,8-8s8,3.6,8,8C20,16.4,16.4,20,12,20z"/>
|
<rect x="9" y="8" width="2" height="8"/>
|
||||||
<rect x="13" y="8" style="fill:#000E1A;" width="2" height="8"/>
|
|
||||||
<rect x="9" y="8" style="fill:#000E1A;" width="2" height="8"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 586 B After Width: | Height: | Size: 367 B |
@ -1,7 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M4,12c0-1.8,0.6-3.5,1.7-4.9
|
||||||
<path style="fill:#000E1A;" d="M12,2C6.5,2,2,6.5,2,12s4.5,10,10,10s10-4.5,10-10S17.5,2,12,2z M4,12c0-1.8,0.6-3.5,1.7-4.9
|
|
||||||
l11.2,11.2C15.5,19.4,13.8,20,12,20C7.6,20,4,16.4,4,12z M18.3,16.9L7.1,5.7C8.5,4.6,10.2,4,12,4c4.4,0,8,3.6,8,8
|
l11.2,11.2C15.5,19.4,13.8,20,12,20C7.6,20,4,16.4,4,12z M18.3,16.9L7.1,5.7C8.5,4.6,10.2,4,12,4c4.4,0,8,3.6,8,8
|
||||||
C20,13.8,19.4,15.5,18.3,16.9z"/>
|
C20,13.8,19.4,15.5,18.3,16.9z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 528 B After Width: | Height: | Size: 366 B |
@ -1,6 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
<path d="M22,18L13.7,3.1c-0.4-0.6-1-1-1.7-1s-1.4,0.4-1.7,1L2,18c-0.3,0.6-0.3,1.4,0,2c0.4,0.6,1,1,1.7,1
|
||||||
<path style="fill:#000E1A;" d="M22,18L13.7,3.1c-0.4-0.6-1-1-1.7-1s-1.4,0.4-1.7,1L2,18c-0.3,0.6-0.3,1.4,0,2c0.4,0.6,1,1,1.7,1
|
h16.6c0.7,0,1.4-0.4,1.7-1C22.4,19.4,22.4,18.7,22,18z M3.7,19L12,4.1L20.3,19H3.7z M11,8h2v6h-2V8z M13,18h-2v-2h2V18z" />
|
||||||
h16.6c0.7,0,1.4-0.4,1.7-1C22.4,19.4,22.4,18.7,22,18z M3.7,19L12,4.1L20.3,19H3.7z M11,8h2v6h-2V8z M13,18h-2v-2h2V18z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 507 B After Width: | Height: | Size: 345 B |
@ -1,7 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<path d="M18,21H6c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h12c1.7,0,3,1.3,3,3v12C21,19.7,19.7,21,18,21z M5,19h14
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
V5H5V19z M14.7,7.7l-1.4-1.4l-7,7l1.4,1.4L14.7,7.7z M9.7,7.7L8.3,6.3l-2,2l1.4,1.4L9.7,7.7z" />
|
||||||
<path style="fill:#000E1A;" d="M18,21H6c-1.7,0-3-1.3-3-3V6c0-1.7,1.3-3,3-3h12c1.7,0,3,1.3,3,3v12C21,19.7,19.7,21,18,21z M5,19h14
|
|
||||||
V5H5V19z M14.7,7.7l-1.4-1.4l-7,7l1.4,1.4L14.7,7.7z M9.7,7.7L8.3,6.3l-2,2l1.4,1.4L9.7,7.7z"/>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 580 B After Width: | Height: | Size: 323 B |
@ -1,8 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 25.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<svg version="1.1" id="Artwork" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<path d="M19,6h-1.6l-2-2H8.6l-2,2H5C3.3,6,2,7.3,2,9v8c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V9
|
||||||
viewBox="0 0 24 24" style="enable-background:new 0 0 24 24;" xml:space="preserve">
|
|
||||||
<path style="fill:#000E1A;" d="M19,6h-1.6l-2-2H8.6l-2,2H5C3.3,6,2,7.3,2,9v8c0,1.7,1.3,3,3,3h14c1.7,0,3-1.3,3-3V9
|
|
||||||
C22,7.3,20.7,6,19,6z M20,18H4V8h3.4l2-2h5.2l2,2H20V18z M12,8c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4S14.2,8,12,8z M12,14
|
C22,7.3,20.7,6,19,6z M20,18H4V8h3.4l2-2h5.2l2,2H20V18z M12,8c-2.2,0-4,1.8-4,4s1.8,4,4,4s4-1.8,4-4S14.2,8,12,8z M12,14
|
||||||
c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S13.1,14,12,14z"/>
|
c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S13.1,14,12,14z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 389 B |
@ -1,15 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30">
|
||||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
<path d="M15,3C8.37,3,3,8.37,3,15v11h2v-4.36C7.15,24.87,10.83,27,15,27c6.63,0,12-5.37,12-12S21.63,3,15,3z M15,25
|
||||||
viewBox="0 0 30 30" style="enable-background:new 0 0 30 30;" xml:space="preserve">
|
C9.49,25,5,20.51,5,15S9.49,5,15,5s10,4.49,10,10S20.51,25,15,25z"/>
|
||||||
<g id="Layer_1">
|
|
||||||
<g>
|
|
||||||
<g>
|
|
||||||
<path d="M15,3C8.37,3,3,8.37,3,15v11h2v-4.36C7.15,24.87,10.83,27,15,27c6.63,0,12-5.37,12-12S21.63,3,15,3z M15,25
|
|
||||||
C9.49,25,5,20.51,5,15S9.49,5,15,5s10,4.49,10,10S20.51,25,15,25z"/>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
</g>
|
|
||||||
<g id="Comments">
|
|
||||||
</g>
|
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 600 B After Width: | Height: | Size: 302 B |
@ -399,7 +399,7 @@ class CloudOutputDeviceManager:
|
|||||||
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
|
output_device_manager = CuraApplication.getInstance().getOutputDeviceManager()
|
||||||
stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
|
stored_cluster_id = active_machine.getMetaDataEntry(self.META_CLUSTER_ID)
|
||||||
local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY)
|
local_network_key = active_machine.getMetaDataEntry(self.META_NETWORK_KEY)
|
||||||
for device in self._remote_clusters.values():
|
for device in list(self._remote_clusters.values()): # Make a copy of the remote devices list, to prevent modifying the list while iterating, if a device gets added asynchronously.
|
||||||
if device.key == stored_cluster_id:
|
if device.key == stored_cluster_id:
|
||||||
# Connect to it if the stored ID matches.
|
# Connect to it if the stored ID matches.
|
||||||
self._connectToOutputDevice(device, active_machine)
|
self._connectToOutputDevice(device, active_machine)
|
||||||
|
@ -7,26 +7,34 @@ from PyQt5.QtNetwork import QNetworkReply, QNetworkRequest
|
|||||||
from UM.Job import Job
|
from UM.Job import Job
|
||||||
from UM.Logger import Logger
|
from UM.Logger import Logger
|
||||||
from cura.CuraApplication import CuraApplication
|
from cura.CuraApplication import CuraApplication
|
||||||
|
from cura.Utils.Threading import call_on_qt_thread
|
||||||
|
|
||||||
from ..Models.Http.ClusterMaterial import ClusterMaterial
|
from ..Models.Http.ClusterMaterial import ClusterMaterial
|
||||||
from ..Models.LocalMaterial import LocalMaterial
|
from ..Models.LocalMaterial import LocalMaterial
|
||||||
from ..Messages.MaterialSyncMessage import MaterialSyncMessage
|
from ..Messages.MaterialSyncMessage import MaterialSyncMessage
|
||||||
|
|
||||||
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .LocalClusterOutputDevice import LocalClusterOutputDevice
|
from .LocalClusterOutputDevice import LocalClusterOutputDevice
|
||||||
|
|
||||||
|
|
||||||
class SendMaterialJob(Job):
|
class SendMaterialJob(Job):
|
||||||
|
|
||||||
"""Asynchronous job to send material profiles to the printer.
|
"""Asynchronous job to send material profiles to the printer.
|
||||||
|
|
||||||
This way it won't freeze up the interface while sending those materials.
|
This way it won't freeze up the interface while sending those materials.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, device: "LocalClusterOutputDevice") -> None:
|
def __init__(self, device: "LocalClusterOutputDevice") -> None:
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.device = device # type: LocalClusterOutputDevice
|
self.device = device # type: LocalClusterOutputDevice
|
||||||
|
|
||||||
|
self._send_material_thread = threading.Thread(target = self._sendMissingMaterials)
|
||||||
|
self._send_material_thread.setDaemon(True)
|
||||||
|
|
||||||
|
self._remote_materials = {} # type: Dict[str, ClusterMaterial]
|
||||||
|
|
||||||
def run(self) -> None:
|
def run(self) -> None:
|
||||||
"""Send the request to the printer and register a callback"""
|
"""Send the request to the printer and register a callback"""
|
||||||
|
|
||||||
@ -36,9 +44,15 @@ class SendMaterialJob(Job):
|
|||||||
"""Callback for when the remote materials were returned."""
|
"""Callback for when the remote materials were returned."""
|
||||||
|
|
||||||
remote_materials_by_guid = {material.guid: material for material in materials}
|
remote_materials_by_guid = {material.guid: material for material in materials}
|
||||||
self._sendMissingMaterials(remote_materials_by_guid)
|
self._remote_materials = remote_materials_by_guid
|
||||||
|
# It's not the nicest way to do it, but if we don't handle this in a thread
|
||||||
|
# we are blocking the main interface (even though the original call was done in a job)
|
||||||
|
# This should really be refactored so that calculating the list of materials that need to be sent
|
||||||
|
# to the printer is done outside of the job (and running the job actually sends the materials)
|
||||||
|
# TODO: Fix this hack that was introduced for 4.9.1
|
||||||
|
self._send_material_thread.start()
|
||||||
|
|
||||||
def _sendMissingMaterials(self, remote_materials_by_guid: Dict[str, ClusterMaterial]) -> None:
|
def _sendMissingMaterials(self) -> None:
|
||||||
"""Determine which materials should be updated and send them to the printer.
|
"""Determine which materials should be updated and send them to the printer.
|
||||||
|
|
||||||
:param remote_materials_by_guid: The remote materials by GUID.
|
:param remote_materials_by_guid: The remote materials by GUID.
|
||||||
@ -47,7 +61,7 @@ class SendMaterialJob(Job):
|
|||||||
if len(local_materials_by_guid) == 0:
|
if len(local_materials_by_guid) == 0:
|
||||||
Logger.log("d", "There are no local materials to synchronize with the printer.")
|
Logger.log("d", "There are no local materials to synchronize with the printer.")
|
||||||
return
|
return
|
||||||
material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, remote_materials_by_guid)
|
material_ids_to_send = self._determineMaterialsToSend(local_materials_by_guid, self._remote_materials)
|
||||||
if len(material_ids_to_send) == 0:
|
if len(material_ids_to_send) == 0:
|
||||||
Logger.log("d", "There are no remote materials to update.")
|
Logger.log("d", "There are no remote materials to update.")
|
||||||
return
|
return
|
||||||
@ -96,7 +110,11 @@ class SendMaterialJob(Job):
|
|||||||
|
|
||||||
file_name = os.path.basename(file_path)
|
file_name = os.path.basename(file_path)
|
||||||
self._sendMaterialFile(file_path, file_name, root_material_id)
|
self._sendMaterialFile(file_path, file_name, root_material_id)
|
||||||
|
time.sleep(1) # Throttle the sending a bit.
|
||||||
|
|
||||||
|
# This needs to be called on the QT thread since the onFinished needs to happen
|
||||||
|
# in the same thread as where the network manager is located (aka; main thread)
|
||||||
|
@call_on_qt_thread
|
||||||
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
|
def _sendMaterialFile(self, file_path: str, file_name: str, material_id: str) -> None:
|
||||||
"""Send a single material file to the printer.
|
"""Send a single material file to the printer.
|
||||||
|
|
||||||
|
@ -1,9 +1,2 @@
|
|||||||
# Copyright (c) 2019 Ultimaker B.V.
|
# Copyright (c) 2019 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.
|
||||||
|
|
||||||
# Workaround for a race condition on certain systems where there
|
|
||||||
# is a race condition between Arcus and PyQt. Importing Arcus
|
|
||||||
# first seems to prevent Sip from going into a state where it
|
|
||||||
# tries to create PyQt objects on a non-main thread.
|
|
||||||
import Arcus #@UnusedImport
|
|
||||||
import Savitar #@UnusedImport
|
|
@ -70,7 +70,10 @@ class AutoDetectBaudJob(Job):
|
|||||||
timeout_time = time() + wait_response_timeout
|
timeout_time = time() + wait_response_timeout
|
||||||
|
|
||||||
while timeout_time > time():
|
while timeout_time > time():
|
||||||
line = serial.readline()
|
# If baudrate is wrong, then readline() might never
|
||||||
|
# return, even with timeouts set. Using read_until
|
||||||
|
# with size limit seems to fix this.
|
||||||
|
line = serial.read_until(size = 100)
|
||||||
if b"ok" in line and b"T:" in line:
|
if b"ok" in line and b"T:" in line:
|
||||||
self.setResult(baud_rate)
|
self.setResult(baud_rate)
|
||||||
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
|
Logger.log("d", "Detected baud rate {baud_rate} on serial {serial} on retry {retry} with after {time_elapsed:0.2f} seconds.".format(
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
"name": "USB printing",
|
"name": "USB printing",
|
||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,6 @@
|
|||||||
"author": "Ultimaker B.V.",
|
"author": "Ultimaker B.V.",
|
||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
|
||||||
"api": "7.5.0",
|
"api": 7,
|
||||||
"i18n-catalog": "cura"
|
"i18n-catalog": "cura"
|
||||||
}
|
}
|
||||||
|