mirror of
https://git.mirrors.martin98.com/https://github.com/Ultimaker/Cura
synced 2025-06-04 03:04:23 +08:00
Convert remaining doxygen to rst
This commit is contained in:
parent
fe779d9501
commit
c2c96faf5f
@ -11,11 +11,13 @@ import os
|
||||
import sys
|
||||
|
||||
|
||||
## Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths.
|
||||
#
|
||||
# \param work_dir The directory to look for JSON files recursively.
|
||||
# \return A list of JSON files in absolute paths that are found in the given directory.
|
||||
def find_json_files(work_dir: str) -> list:
|
||||
"""Finds all JSON files in the given directory recursively and returns a list of those files in absolute paths.
|
||||
|
||||
:param work_dir: The directory to look for JSON files recursively.
|
||||
:return: A list of JSON files in absolute paths that are found in the given directory.
|
||||
"""
|
||||
|
||||
json_file_list = []
|
||||
for root, dir_names, file_names in os.walk(work_dir):
|
||||
for file_name in file_names:
|
||||
@ -24,12 +26,14 @@ def find_json_files(work_dir: str) -> list:
|
||||
return json_file_list
|
||||
|
||||
|
||||
## Removes the given entries from the given JSON file. The file will modified in-place.
|
||||
#
|
||||
# \param file_path The JSON file to modify.
|
||||
# \param entries A list of strings as entries to remove.
|
||||
# \return None
|
||||
def remove_entries_from_json_file(file_path: str, entries: list) -> None:
|
||||
"""Removes the given entries from the given JSON file. The file will modified in-place.
|
||||
|
||||
:param file_path: The JSON file to modify.
|
||||
:param entries: A list of strings as entries to remove.
|
||||
:return: None
|
||||
"""
|
||||
|
||||
try:
|
||||
with open(file_path, "r", encoding = "utf-8") as f:
|
||||
package_dict = json.load(f, object_hook = collections.OrderedDict)
|
||||
|
@ -6,8 +6,9 @@ from UM.Operations.GroupedOperation import GroupedOperation
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
|
||||
## A specialised operation designed specifically to modify the previous operation.
|
||||
class PlatformPhysicsOperation(Operation):
|
||||
"""A specialised operation designed specifically to modify the previous operation."""
|
||||
|
||||
def __init__(self, node: SceneNode, translation: Vector) -> None:
|
||||
super().__init__()
|
||||
self._node = node
|
||||
|
@ -7,8 +7,9 @@ from UM.Operations.Operation import Operation
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
|
||||
## Simple operation to set the buildplate number of a scenenode.
|
||||
class SetBuildPlateNumberOperation(Operation):
|
||||
"""Simple operation to set the buildplate number of a scenenode."""
|
||||
|
||||
def __init__(self, node: SceneNode, build_plate_nr: int) -> None:
|
||||
super().__init__()
|
||||
self._node = node
|
||||
|
@ -6,31 +6,37 @@ from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Operations import Operation
|
||||
|
||||
|
||||
|
||||
## An operation that parents a scene node to another scene node.
|
||||
class SetParentOperation(Operation.Operation):
|
||||
## Initialises this SetParentOperation.
|
||||
#
|
||||
# \param node The node which will be reparented.
|
||||
# \param parent_node The node which will be the parent.
|
||||
"""An operation that parents a scene node to another scene node."""
|
||||
|
||||
def __init__(self, node: SceneNode, parent_node: Optional[SceneNode]) -> None:
|
||||
"""Initialises this SetParentOperation.
|
||||
|
||||
:param node: The node which will be reparented.
|
||||
:param parent_node: The node which will be the parent.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
self._node = node
|
||||
self._parent = parent_node
|
||||
self._old_parent = node.getParent() # To restore the previous parent in case of an undo.
|
||||
|
||||
## Undoes the set-parent operation, restoring the old parent.
|
||||
def undo(self) -> None:
|
||||
"""Undoes the set-parent operation, restoring the old parent."""
|
||||
|
||||
self._set_parent(self._old_parent)
|
||||
|
||||
## Re-applies the set-parent operation.
|
||||
def redo(self) -> None:
|
||||
"""Re-applies the set-parent operation."""
|
||||
|
||||
self._set_parent(self._parent)
|
||||
|
||||
## Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
|
||||
#
|
||||
# \param new_parent The new parent. Note: this argument can be None, which would hide the node from the scene.
|
||||
def _set_parent(self, new_parent: Optional[SceneNode]) -> None:
|
||||
"""Sets the parent of the node while applying transformations to the world-transform of the node stays the same.
|
||||
|
||||
:param new_parent: The new parent. Note: this argument can be None, which would hide the node from the scene.
|
||||
"""
|
||||
|
||||
if new_parent:
|
||||
current_parent = self._node.getParent()
|
||||
if current_parent:
|
||||
@ -56,8 +62,10 @@ class SetParentOperation(Operation.Operation):
|
||||
|
||||
self._node.setParent(new_parent)
|
||||
|
||||
## Returns a programmer-readable representation of this operation.
|
||||
#
|
||||
# \return A programmer-readable representation of this operation.
|
||||
def __repr__(self) -> str:
|
||||
"""Returns a programmer-readable representation of this operation.
|
||||
|
||||
:return: A programmer-readable representation of this operation.
|
||||
"""
|
||||
|
||||
return "SetParentOperation(node = {0}, parent_node={1})".format(self._node, self._parent)
|
||||
|
@ -44,8 +44,9 @@ class FirmwareUpdater(QObject):
|
||||
def _updateFirmware(self) -> None:
|
||||
raise NotImplementedError("_updateFirmware needs to be implemented")
|
||||
|
||||
## Cleanup after a succesful update
|
||||
def _cleanupAfterUpdate(self) -> None:
|
||||
"""Cleanup after a succesful update"""
|
||||
|
||||
# Clean up for next attempt.
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True, name = "FirmwareUpdateThread")
|
||||
self._firmware_file = ""
|
||||
|
@ -47,10 +47,13 @@ class ExtruderConfigurationModel(QObject):
|
||||
def hotendID(self) -> Optional[str]:
|
||||
return self._hotend_id
|
||||
|
||||
## This method is intended to indicate whether the configuration is valid or not.
|
||||
# The method checks if the mandatory fields are or not set
|
||||
# At this moment is always valid since we allow to have empty material and variants.
|
||||
def isValid(self) -> bool:
|
||||
"""This method is intended to indicate whether the configuration is valid or not.
|
||||
|
||||
The method checks if the mandatory fields are or not set
|
||||
At this moment is always valid since we allow to have empty material and variants.
|
||||
"""
|
||||
|
||||
return True
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
@ -54,8 +54,9 @@ class ExtruderOutputModel(QObject):
|
||||
def updateActiveMaterial(self, material: Optional["MaterialOutputModel"]) -> None:
|
||||
self._extruder_configuration.setMaterial(material)
|
||||
|
||||
## Update the hotend temperature. This only changes it locally.
|
||||
def updateHotendTemperature(self, temperature: float) -> None:
|
||||
"""Update the hotend temperature. This only changes it locally."""
|
||||
|
||||
if self._hotend_temperature != temperature:
|
||||
self._hotend_temperature = temperature
|
||||
self.hotendTemperatureChanged.emit()
|
||||
@ -65,9 +66,10 @@ class ExtruderOutputModel(QObject):
|
||||
self._target_hotend_temperature = temperature
|
||||
self.targetHotendTemperatureChanged.emit()
|
||||
|
||||
## Set the target hotend temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(float)
|
||||
def setTargetHotendTemperature(self, temperature: float) -> None:
|
||||
"""Set the target hotend temperature. This ensures that it's actually sent to the remote."""
|
||||
|
||||
self._printer.getController().setTargetHotendTemperature(self._printer, self, temperature)
|
||||
self.updateTargetHotendTemperature(temperature)
|
||||
|
||||
@ -101,13 +103,15 @@ class ExtruderOutputModel(QObject):
|
||||
def isPreheating(self) -> bool:
|
||||
return self._is_preheating
|
||||
|
||||
## Pre-heats the extruder before printer.
|
||||
#
|
||||
# \param temperature The temperature to heat the extruder to, in degrees
|
||||
# Celsius.
|
||||
# \param duration How long the bed should stay warm, in seconds.
|
||||
@pyqtSlot(float, float)
|
||||
def preheatHotend(self, temperature: float, duration: float) -> None:
|
||||
"""Pre-heats the extruder before printer.
|
||||
|
||||
:param temperature: The temperature to heat the extruder to, in degrees
|
||||
Celsius.
|
||||
:param duration: How long the bed should stay warm, in seconds.
|
||||
"""
|
||||
|
||||
self._printer._controller.preheatHotend(self, temperature, duration)
|
||||
|
||||
@pyqtSlot()
|
||||
|
@ -48,9 +48,11 @@ class PrinterConfigurationModel(QObject):
|
||||
def buildplateConfiguration(self) -> str:
|
||||
return self._buildplate_configuration
|
||||
|
||||
## This method is intended to indicate whether the configuration is valid or not.
|
||||
# The method checks if the mandatory fields are or not set
|
||||
def isValid(self) -> bool:
|
||||
"""This method is intended to indicate whether the configuration is valid or not.
|
||||
|
||||
The method checks if the mandatory fields are or not set
|
||||
"""
|
||||
if not self._extruder_configurations:
|
||||
return False
|
||||
for configuration in self._extruder_configurations:
|
||||
@ -97,9 +99,11 @@ class PrinterConfigurationModel(QObject):
|
||||
|
||||
return True
|
||||
|
||||
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
|
||||
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
|
||||
def __hash__(self):
|
||||
"""The hash function is used to compare and create unique sets. The configuration is unique if the configuration
|
||||
|
||||
of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.
|
||||
"""
|
||||
extruder_hash = hash(0)
|
||||
first_extruder = None
|
||||
for configuration in self._extruder_configurations:
|
||||
|
@ -163,13 +163,15 @@ class PrinterOutputModel(QObject):
|
||||
def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
|
||||
self._controller.moveHead(self, x, y, z, speed)
|
||||
|
||||
## Pre-heats the heated bed of the printer.
|
||||
#
|
||||
# \param temperature The temperature to heat the bed to, in degrees
|
||||
# Celsius.
|
||||
# \param duration How long the bed should stay warm, in seconds.
|
||||
@pyqtSlot(float, float)
|
||||
def preheatBed(self, temperature: float, duration: float) -> None:
|
||||
"""Pre-heats the heated bed of the printer.
|
||||
|
||||
:param temperature: The temperature to heat the bed to, in degrees
|
||||
Celsius.
|
||||
:param duration: How long the bed should stay warm, in seconds.
|
||||
"""
|
||||
|
||||
self._controller.preheatBed(self, temperature, duration)
|
||||
|
||||
@pyqtSlot()
|
||||
@ -200,8 +202,9 @@ class PrinterOutputModel(QObject):
|
||||
self._unique_name = unique_name
|
||||
self.nameChanged.emit()
|
||||
|
||||
## Update the bed temperature. This only changes it locally.
|
||||
def updateBedTemperature(self, temperature: float) -> None:
|
||||
"""Update the bed temperature. This only changes it locally."""
|
||||
|
||||
if self._bed_temperature != temperature:
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
@ -211,9 +214,10 @@ class PrinterOutputModel(QObject):
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Set the target bed temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(float)
|
||||
def setTargetBedTemperature(self, temperature: float) -> None:
|
||||
"""Set the target bed temperature. This ensures that it's actually sent to the remote."""
|
||||
|
||||
self._controller.setTargetBedTemperature(self, temperature)
|
||||
self.updateTargetBedTemperature(temperature)
|
||||
|
||||
|
@ -32,8 +32,9 @@ class NetworkMJPGImage(QQuickPaintedItem):
|
||||
|
||||
self.setAntialiasing(True)
|
||||
|
||||
## Ensure that close gets called when object is destroyed
|
||||
def __del__(self) -> None:
|
||||
"""Ensure that close gets called when object is destroyed"""
|
||||
|
||||
self.stop()
|
||||
|
||||
|
||||
|
@ -84,8 +84,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
def _compressGCode(self) -> Optional[bytes]:
|
||||
self._compressing_gcode = True
|
||||
|
||||
## Mash the data into single string
|
||||
max_chars_per_line = int(1024 * 1024 / 4) # 1/4 MB per line.
|
||||
"""Mash the data into single string"""
|
||||
file_data_bytes_list = []
|
||||
batched_lines = []
|
||||
batched_lines_count = 0
|
||||
@ -145,9 +145,11 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
request.setHeader(QNetworkRequest.UserAgentHeader, self._user_agent)
|
||||
return request
|
||||
|
||||
## This method was only available privately before, but it was actually called from SendMaterialJob.py.
|
||||
# We now have a public equivalent as well. We did not remove the private one as plugins might be using that.
|
||||
def createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
|
||||
"""This method was only available privately before, but it was actually called from SendMaterialJob.py.
|
||||
|
||||
We now have a public equivalent as well. We did not remove the private one as plugins might be using that.
|
||||
"""
|
||||
return self._createFormPart(content_header, data, content_type)
|
||||
|
||||
def _createFormPart(self, content_header: str, data: bytes, content_type: Optional[str] = None) -> QHttpPart:
|
||||
@ -163,8 +165,9 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
part.setBody(data)
|
||||
return part
|
||||
|
||||
## Convenience function to get the username, either from the cloud or from the OS.
|
||||
def _getUserName(self) -> str:
|
||||
"""Convenience function to get the username, either from the cloud or from the OS."""
|
||||
|
||||
# check first if we are logged in with the Ultimaker Account
|
||||
account = CuraApplication.getInstance().getCuraAPI().account # type: Account
|
||||
if account and account.isLoggedIn:
|
||||
@ -187,15 +190,17 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
self._createNetworkManager()
|
||||
assert (self._manager is not None)
|
||||
|
||||
## Sends a put request to the given path.
|
||||
# \param url: The path after the API prefix.
|
||||
# \param data: The data to be sent in the body
|
||||
# \param content_type: The content type of the body data.
|
||||
# \param on_finished: The function to call when the response is received.
|
||||
# \param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
|
||||
def put(self, url: str, data: Union[str, bytes], content_type: Optional[str] = "application/json",
|
||||
on_finished: Optional[Callable[[QNetworkReply], None]] = None,
|
||||
on_progress: Optional[Callable[[int, int], None]] = None) -> None:
|
||||
"""Sends a put request to the given path.
|
||||
|
||||
:param url: The path after the API prefix.
|
||||
:param data: The data to be sent in the body
|
||||
:param content_type: The content type of the body data.
|
||||
:param on_finished: The function to call when the response is received.
|
||||
:param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
|
||||
"""
|
||||
self._validateManager()
|
||||
|
||||
request = self._createEmptyRequest(url, content_type = content_type)
|
||||
@ -212,10 +217,12 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
if on_progress is not None:
|
||||
reply.uploadProgress.connect(on_progress)
|
||||
|
||||
## Sends a delete request to the given path.
|
||||
# \param url: The path after the API prefix.
|
||||
# \param on_finished: The function to be call when the response is received.
|
||||
def delete(self, url: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
"""Sends a delete request to the given path.
|
||||
|
||||
:param url: The path after the API prefix.
|
||||
:param on_finished: The function to be call when the response is received.
|
||||
"""
|
||||
self._validateManager()
|
||||
|
||||
request = self._createEmptyRequest(url)
|
||||
@ -228,10 +235,12 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
reply = self._manager.deleteResource(request)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
|
||||
## Sends a get request to the given path.
|
||||
# \param url: The path after the API prefix.
|
||||
# \param on_finished: The function to be call when the response is received.
|
||||
def get(self, url: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
|
||||
"""Sends a get request to the given path.
|
||||
|
||||
:param url: The path after the API prefix.
|
||||
:param on_finished: The function to be call when the response is received.
|
||||
"""
|
||||
self._validateManager()
|
||||
|
||||
request = self._createEmptyRequest(url)
|
||||
@ -244,14 +253,18 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
reply = self._manager.get(request)
|
||||
self._registerOnFinishedCallback(reply, on_finished)
|
||||
|
||||
## Sends a post request to the given path.
|
||||
# \param url: The path after the API prefix.
|
||||
# \param data: The data to be sent in the body
|
||||
# \param on_finished: The function to call when the response is received.
|
||||
# \param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
|
||||
def post(self, url: str, data: Union[str, bytes],
|
||||
on_finished: Optional[Callable[[QNetworkReply], None]],
|
||||
on_progress: Optional[Callable[[int, int], None]] = None) -> None:
|
||||
|
||||
"""Sends a post request to the given path.
|
||||
|
||||
:param url: The path after the API prefix.
|
||||
:param data: The data to be sent in the body
|
||||
:param on_finished: The function to call when the response is received.
|
||||
:param on_progress: The function to call when the progress changes. Parameters are bytes_sent / bytes_total.
|
||||
"""
|
||||
|
||||
self._validateManager()
|
||||
|
||||
request = self._createEmptyRequest(url)
|
||||
@ -318,10 +331,13 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
if on_finished is not None:
|
||||
self._onFinishedCallbacks[reply.url().toString() + str(reply.operation())] = on_finished
|
||||
|
||||
## This method checks if the name of the group stored in the definition container is correct.
|
||||
# After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
|
||||
# then all the container stacks are updated, both the current and the hidden ones.
|
||||
def _checkCorrectGroupName(self, device_id: str, group_name: str) -> None:
|
||||
"""This method checks if the name of the group stored in the definition container is correct.
|
||||
|
||||
After updating from 3.2 to 3.3 some group names may be temporary. If there is a mismatch in the name of the group
|
||||
then all the container stacks are updated, both the current and the hidden ones.
|
||||
"""
|
||||
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
active_machine_network_name = CuraApplication.getInstance().getMachineManager().activeMachineNetworkKey()
|
||||
if global_container_stack and device_id == active_machine_network_name:
|
||||
@ -366,32 +382,38 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
||||
def getProperties(self):
|
||||
return self._properties
|
||||
|
||||
## Get the unique key of this machine
|
||||
# \return key String containing the key of the machine.
|
||||
@pyqtProperty(str, constant = True)
|
||||
def key(self) -> str:
|
||||
"""Get the unique key of this machine
|
||||
|
||||
:return: key String containing the key of the machine.
|
||||
"""
|
||||
return self._id
|
||||
|
||||
## The IP address of the printer.
|
||||
@pyqtProperty(str, constant = True)
|
||||
def address(self) -> str:
|
||||
"""The IP address of the printer."""
|
||||
|
||||
return self._properties.get(b"address", b"").decode("utf-8")
|
||||
|
||||
## Name of the printer (as returned from the ZeroConf properties)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def name(self) -> str:
|
||||
"""Name of the printer (as returned from the ZeroConf properties)"""
|
||||
|
||||
return self._properties.get(b"name", b"").decode("utf-8")
|
||||
|
||||
## Firmware version (as returned from the ZeroConf properties)
|
||||
@pyqtProperty(str, constant = True)
|
||||
def firmwareVersion(self) -> str:
|
||||
"""Firmware version (as returned from the ZeroConf properties)"""
|
||||
|
||||
return self._properties.get(b"firmware_version", b"").decode("utf-8")
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def printerType(self) -> str:
|
||||
return self._properties.get(b"printer_type", b"Unknown").decode("utf-8")
|
||||
|
||||
## IP adress of this printer
|
||||
@pyqtProperty(str, constant = True)
|
||||
def ipAddress(self) -> str:
|
||||
"""IP adress of this printer"""
|
||||
|
||||
return self._address
|
||||
|
@ -2,15 +2,19 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
## Data class that represents a peripheral for a printer.
|
||||
#
|
||||
# Output device plug-ins may specify that the printer has a certain set of
|
||||
# peripherals. This set is then possibly shown in the interface of the monitor
|
||||
# stage.
|
||||
class Peripheral:
|
||||
## Constructs the peripheral.
|
||||
# \param type A unique ID for the type of peripheral.
|
||||
# \param name A human-readable name for the peripheral.
|
||||
"""Data class that represents a peripheral for a printer.
|
||||
|
||||
Output device plug-ins may specify that the printer has a certain set of
|
||||
peripherals. This set is then possibly shown in the interface of the monitor
|
||||
stage.
|
||||
"""
|
||||
|
||||
def __init__(self, peripheral_type: str, name: str) -> None:
|
||||
"""Constructs the peripheral.
|
||||
|
||||
:param peripheral_type: A unique ID for the type of peripheral.
|
||||
:param name: A human-readable name for the peripheral.
|
||||
"""
|
||||
self.type = peripheral_type
|
||||
self.name = name
|
||||
|
@ -24,8 +24,9 @@ if MYPY:
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## The current processing state of the backend.
|
||||
class ConnectionState(IntEnum):
|
||||
"""The current processing state of the backend."""
|
||||
|
||||
Closed = 0
|
||||
Connecting = 1
|
||||
Connected = 2
|
||||
@ -40,17 +41,19 @@ class ConnectionType(IntEnum):
|
||||
CloudConnection = 3
|
||||
|
||||
|
||||
## Printer output device adds extra interface options on top of output device.
|
||||
#
|
||||
# The assumption is made the printer is a FDM printer.
|
||||
#
|
||||
# Note that a number of settings are marked as "final". This is because decorators
|
||||
# are not inherited by children. To fix this we use the private counter part of those
|
||||
# functions to actually have the implementation.
|
||||
#
|
||||
# For all other uses it should be used in the same way as a "regular" OutputDevice.
|
||||
@signalemitter
|
||||
class PrinterOutputDevice(QObject, OutputDevice):
|
||||
"""Printer output device adds extra interface options on top of output device.
|
||||
|
||||
The assumption is made the printer is a FDM printer.
|
||||
|
||||
Note that a number of settings are marked as "final". This is because decorators
|
||||
are not inherited by children. To fix this we use the private counter part of those
|
||||
functions to actually have the implementation.
|
||||
|
||||
For all other uses it should be used in the same way as a "regular" OutputDevice.
|
||||
"""
|
||||
|
||||
|
||||
printersChanged = pyqtSignal()
|
||||
connectionStateChanged = pyqtSignal(str)
|
||||
@ -184,26 +187,30 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
if self._monitor_item is None:
|
||||
self._monitor_item = QtApplication.getInstance().createQmlComponent(self._monitor_view_qml_path, {"OutputDevice": self})
|
||||
|
||||
## Attempt to establish connection
|
||||
def connect(self) -> None:
|
||||
"""Attempt to establish connection"""
|
||||
|
||||
self.setConnectionState(ConnectionState.Connecting)
|
||||
self._update_timer.start()
|
||||
|
||||
## Attempt to close the connection
|
||||
def close(self) -> None:
|
||||
"""Attempt to close the connection"""
|
||||
|
||||
self._update_timer.stop()
|
||||
self.setConnectionState(ConnectionState.Closed)
|
||||
|
||||
## Ensure that close gets called when object is destroyed
|
||||
def __del__(self) -> None:
|
||||
"""Ensure that close gets called when object is destroyed"""
|
||||
|
||||
self.close()
|
||||
|
||||
@pyqtProperty(bool, notify = acceptsCommandsChanged)
|
||||
def acceptsCommands(self) -> bool:
|
||||
return self._accepts_commands
|
||||
|
||||
## Set a flag to signal the UI that the printer is not (yet) ready to receive commands
|
||||
def _setAcceptsCommands(self, accepts_commands: bool) -> None:
|
||||
"""Set a flag to signal the UI that the printer is not (yet) ready to receive commands"""
|
||||
|
||||
if self._accepts_commands != accepts_commands:
|
||||
self._accepts_commands = accepts_commands
|
||||
|
||||
@ -241,16 +248,20 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
||||
# At this point there may be non-updated configurations
|
||||
self._updateUniqueConfigurations()
|
||||
|
||||
## Set the device firmware name
|
||||
#
|
||||
# \param name The name of the firmware.
|
||||
def _setFirmwareName(self, name: str) -> None:
|
||||
"""Set the device firmware name
|
||||
|
||||
:param name: The name of the firmware.
|
||||
"""
|
||||
|
||||
self._firmware_name = name
|
||||
|
||||
## Get the name of device firmware
|
||||
#
|
||||
# This name can be used to define device type
|
||||
def getFirmwareName(self) -> Optional[str]:
|
||||
"""Get the name of device firmware
|
||||
|
||||
This name can be used to define device type
|
||||
"""
|
||||
|
||||
return self._firmware_name
|
||||
|
||||
def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
|
@ -10,15 +10,19 @@ class NoProfileException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
## A type of plug-ins that reads profiles from a file.
|
||||
#
|
||||
# The profile is then stored as instance container of the type user profile.
|
||||
class ProfileReader(PluginObject):
|
||||
"""A type of plug-ins that reads profiles from a file.
|
||||
|
||||
The profile is then stored as instance container of the type user profile.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
## Read profile data from a file and return a filled profile.
|
||||
#
|
||||
# \return \type{Profile|Profile[]} The profile that was obtained from the file or a list of Profiles.
|
||||
def read(self, file_name):
|
||||
"""Read profile data from a file and return a filled profile.
|
||||
|
||||
:return: :type{Profile|Profile[]} The profile that was obtained from the file or a list of Profiles.
|
||||
"""
|
||||
|
||||
raise NotImplementedError("Profile reader plug-in was not correctly implemented. The read function was not implemented.")
|
||||
|
@ -3,23 +3,29 @@
|
||||
|
||||
from UM.PluginObject import PluginObject
|
||||
|
||||
## Base class for profile writer plugins.
|
||||
#
|
||||
# This class defines a write() function to write profiles to files with.
|
||||
|
||||
class ProfileWriter(PluginObject):
|
||||
## Initialises the profile writer.
|
||||
#
|
||||
# This currently doesn't do anything since the writer is basically static.
|
||||
"""Base class for profile writer plugins.
|
||||
|
||||
This class defines a write() function to write profiles to files with.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
"""Initialises the profile writer.
|
||||
|
||||
This currently doesn't do anything since the writer is basically static.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
|
||||
## Writes a profile to the specified file path.
|
||||
#
|
||||
# The profile writer may write its own file format to the specified file.
|
||||
#
|
||||
# \param path \type{string} The file to output to.
|
||||
# \param profiles \type{Profile} or \type{List} The profile(s) to write to the file.
|
||||
# \return \code True \endcode if the writing was successful, or \code
|
||||
# False \endcode if it wasn't.
|
||||
def write(self, path, profiles):
|
||||
"""Writes a profile to the specified file path.
|
||||
|
||||
The profile writer may write its own file format to the specified file.
|
||||
|
||||
:param path: :type{string} The file to output to.
|
||||
:param profiles: :type{Profile} or :type{List} The profile(s) to write to the file.
|
||||
:return: True if the writing was successful, or False if it wasn't.
|
||||
"""
|
||||
|
||||
raise NotImplementedError("Profile writer plugin was not correctly implemented. No write was specified.")
|
||||
|
@ -2,8 +2,9 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
|
||||
## Make a SceneNode build plate aware CuraSceneNode objects all have this decorator.
|
||||
class BuildPlateDecorator(SceneNodeDecorator):
|
||||
"""Make a SceneNode build plate aware CuraSceneNode objects all have this decorator."""
|
||||
|
||||
def __init__(self, build_plate_number: int = -1) -> None:
|
||||
super().__init__()
|
||||
self._build_plate_number = build_plate_number
|
||||
|
@ -23,9 +23,12 @@ if TYPE_CHECKING:
|
||||
from UM.Math.Matrix import Matrix
|
||||
|
||||
|
||||
## The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
|
||||
# If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
|
||||
class ConvexHullDecorator(SceneNodeDecorator):
|
||||
"""The convex hull decorator is a scene node decorator that adds the convex hull functionality to a scene node.
|
||||
|
||||
If a scene node has a convex hull decorator, it will have a shadow in which other objects can not be printed.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
@ -74,13 +77,16 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
|
||||
self._onChanged()
|
||||
|
||||
## Force that a new (empty) object is created upon copy.
|
||||
def __deepcopy__(self, memo):
|
||||
"""Force that a new (empty) object is created upon copy."""
|
||||
|
||||
return ConvexHullDecorator()
|
||||
|
||||
## The polygon representing the 2D adhesion area.
|
||||
# If no adhesion is used, the regular convex hull is returned
|
||||
def getAdhesionArea(self) -> Optional[Polygon]:
|
||||
"""The polygon representing the 2D adhesion area.
|
||||
|
||||
If no adhesion is used, the regular convex hull is returned
|
||||
"""
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
@ -90,9 +96,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
|
||||
return self._add2DAdhesionMargin(hull)
|
||||
|
||||
## Get the unmodified 2D projected convex hull of the node (if any)
|
||||
# In case of one-at-a-time, this includes adhesion and head+fans clearance
|
||||
def getConvexHull(self) -> Optional[Polygon]:
|
||||
"""Get the unmodified 2D projected convex hull of the node (if any)
|
||||
|
||||
In case of one-at-a-time, this includes adhesion and head+fans clearance
|
||||
"""
|
||||
if self._node is None:
|
||||
return None
|
||||
if self._node.callDecoration("isNonPrintingMesh"):
|
||||
@ -108,9 +116,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
|
||||
return self._compute2DConvexHull()
|
||||
|
||||
## For one at the time this is the convex hull of the node with the full head size
|
||||
# In case of printing all at once this is None.
|
||||
def getConvexHullHeadFull(self) -> Optional[Polygon]:
|
||||
"""For one at the time this is the convex hull of the node with the full head size
|
||||
|
||||
In case of printing all at once this is None.
|
||||
"""
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
@ -126,10 +136,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
return False
|
||||
return bool(parent.callDecoration("isGroup"))
|
||||
|
||||
## Get convex hull of the object + head size
|
||||
# In case of printing all at once this is None.
|
||||
# For one at the time this is area with intersection of mirrored head
|
||||
def getConvexHullHead(self) -> Optional[Polygon]:
|
||||
"""Get convex hull of the object + head size
|
||||
|
||||
In case of printing all at once this is None.
|
||||
For one at the time this is area with intersection of mirrored head
|
||||
"""
|
||||
if self._node is None:
|
||||
return None
|
||||
if self._node.callDecoration("isNonPrintingMesh"):
|
||||
@ -142,10 +154,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
return head_with_fans_with_adhesion_margin
|
||||
return None
|
||||
|
||||
## Get convex hull of the node
|
||||
# In case of printing all at once this None??
|
||||
# For one at the time this is the area without the head.
|
||||
def getConvexHullBoundary(self) -> Optional[Polygon]:
|
||||
"""Get convex hull of the node
|
||||
|
||||
In case of printing all at once this None??
|
||||
For one at the time this is the area without the head.
|
||||
"""
|
||||
if self._node is None:
|
||||
return None
|
||||
|
||||
@ -157,10 +171,12 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
return self._compute2DConvexHull()
|
||||
return None
|
||||
|
||||
## Get the buildplate polygon where will be printed
|
||||
# In case of printing all at once this is the same as convex hull (no individual adhesion)
|
||||
# For one at the time this includes the adhesion area
|
||||
def getPrintingArea(self) -> Optional[Polygon]:
|
||||
"""Get the buildplate polygon where will be printed
|
||||
|
||||
In case of printing all at once this is the same as convex hull (no individual adhesion)
|
||||
For one at the time this includes the adhesion area
|
||||
"""
|
||||
if self._isSingularOneAtATimeNode():
|
||||
# In one-at-a-time mode, every printed object gets it's own adhesion
|
||||
printing_area = self.getAdhesionArea()
|
||||
@ -168,8 +184,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
printing_area = self.getConvexHull()
|
||||
return printing_area
|
||||
|
||||
## The same as recomputeConvexHull, but using a timer if it was set.
|
||||
def recomputeConvexHullDelayed(self) -> None:
|
||||
"""The same as recomputeConvexHull, but using a timer if it was set."""
|
||||
|
||||
if self._recompute_convex_hull_timer is not None:
|
||||
self._recompute_convex_hull_timer.start()
|
||||
else:
|
||||
@ -325,9 +342,11 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
return convex_hull.getMinkowskiHull(head_and_fans)
|
||||
return None
|
||||
|
||||
## Compensate given 2D polygon with adhesion margin
|
||||
# \return 2D polygon with added margin
|
||||
def _add2DAdhesionMargin(self, poly: Polygon) -> Polygon:
|
||||
"""Compensate given 2D polygon with adhesion margin
|
||||
|
||||
:return: 2D polygon with added margin
|
||||
"""
|
||||
if not self._global_stack:
|
||||
return Polygon()
|
||||
# Compensate for raft/skirt/brim
|
||||
@ -358,12 +377,14 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
poly = poly.getMinkowskiHull(extra_margin_polygon)
|
||||
return poly
|
||||
|
||||
## Offset the convex hull with settings that influence the collision area.
|
||||
#
|
||||
# \param convex_hull Polygon of the original convex hull.
|
||||
# \return New Polygon instance that is offset with everything that
|
||||
# influences the collision area.
|
||||
def _offsetHull(self, convex_hull: Polygon) -> Polygon:
|
||||
"""Offset the convex hull with settings that influence the collision area.
|
||||
|
||||
:param convex_hull: Polygon of the original convex hull.
|
||||
:return: New Polygon instance that is offset with everything that
|
||||
influences the collision area.
|
||||
"""
|
||||
|
||||
horizontal_expansion = max(
|
||||
self._getSettingProperty("xy_offset", "value"),
|
||||
self._getSettingProperty("xy_offset_layer_0", "value")
|
||||
@ -409,8 +430,9 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
|
||||
self._onChanged()
|
||||
|
||||
## Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property).
|
||||
def _getSettingProperty(self, setting_key: str, prop: str = "value") -> Any:
|
||||
"""Private convenience function to get a setting from the correct extruder (as defined by limit_to_extruder property)."""
|
||||
|
||||
if self._global_stack is None or self._node is None:
|
||||
return None
|
||||
per_mesh_stack = self._node.callDecoration("getStack")
|
||||
@ -430,16 +452,18 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
# Limit_to_extruder is set. The global stack handles this then
|
||||
return self._global_stack.getProperty(setting_key, prop)
|
||||
|
||||
## Returns True if node is a descendant or the same as the root node.
|
||||
def __isDescendant(self, root: "SceneNode", node: Optional["SceneNode"]) -> bool:
|
||||
"""Returns True if node is a descendant or the same as the root node."""
|
||||
|
||||
if node is None:
|
||||
return False
|
||||
if root is node:
|
||||
return True
|
||||
return self.__isDescendant(root, node.getParent())
|
||||
|
||||
## True if print_sequence is one_at_a_time and _node is not part of a group
|
||||
def _isSingularOneAtATimeNode(self) -> bool:
|
||||
"""True if print_sequence is one_at_a_time and _node is not part of a group"""
|
||||
|
||||
if self._node is None:
|
||||
return False
|
||||
return self._global_stack is not None \
|
||||
@ -450,7 +474,8 @@ class ConvexHullDecorator(SceneNodeDecorator):
|
||||
"adhesion_type", "raft_margin", "print_sequence",
|
||||
"skirt_gap", "skirt_line_count", "skirt_brim_line_width", "skirt_distance", "brim_line_count"]
|
||||
|
||||
## Settings that change the convex hull.
|
||||
#
|
||||
# If these settings change, the convex hull should be recalculated.
|
||||
_influencing_settings = {"xy_offset", "xy_offset_layer_0", "mold_enabled", "mold_width", "anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
|
||||
"""Settings that change the convex hull.
|
||||
|
||||
If these settings change, the convex hull should be recalculated.
|
||||
"""
|
||||
|
@ -18,11 +18,13 @@ if TYPE_CHECKING:
|
||||
class ConvexHullNode(SceneNode):
|
||||
shader = None # To prevent the shader from being re-built over and over again, only load it once.
|
||||
|
||||
## Convex hull node is a special type of scene node that is used to display an area, to indicate the
|
||||
# location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
|
||||
# then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
|
||||
# to represent the raft as well.
|
||||
def __init__(self, node: SceneNode, hull: Optional[Polygon], thickness: float, parent: Optional[SceneNode] = None) -> None:
|
||||
"""Convex hull node is a special type of scene node that is used to display an area, to indicate the
|
||||
|
||||
location an object uses on the buildplate. This area (or area's in case of one at a time printing) is
|
||||
then displayed as a transparent shadow. If the adhesion type is set to raft, the area is extruded
|
||||
to represent the raft as well.
|
||||
"""
|
||||
super().__init__(parent)
|
||||
|
||||
self.setCalculateBoundingBox(False)
|
||||
|
@ -72,9 +72,10 @@ class CuraSceneController(QObject):
|
||||
max_build_plate = max(build_plate_number, max_build_plate)
|
||||
return max_build_plate
|
||||
|
||||
## Either select or deselect an item
|
||||
@pyqtSlot(int)
|
||||
def changeSelection(self, index):
|
||||
"""Either select or deselect an item"""
|
||||
|
||||
modifiers = QApplication.keyboardModifiers()
|
||||
ctrl_is_active = modifiers & Qt.ControlModifier
|
||||
shift_is_active = modifiers & Qt.ShiftModifier
|
||||
|
@ -15,9 +15,11 @@ from cura.Settings.ExtruderStack import ExtruderStack # For typing.
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator # For per-object settings.
|
||||
|
||||
|
||||
## Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||
# Note that many other nodes can just be UM SceneNode objects.
|
||||
class CuraSceneNode(SceneNode):
|
||||
"""Scene nodes that are models are only seen when selecting the corresponding build plate
|
||||
|
||||
Note that many other nodes can just be UM SceneNode objects.
|
||||
"""
|
||||
def __init__(self, parent: Optional["SceneNode"] = None, visible: bool = True, name: str = "", no_setting_override: bool = False) -> None:
|
||||
super().__init__(parent = parent, visible = visible, name = name)
|
||||
if not no_setting_override:
|
||||
@ -36,9 +38,11 @@ class CuraSceneNode(SceneNode):
|
||||
def isSelectable(self) -> bool:
|
||||
return super().isSelectable() and self.callDecoration("getBuildPlateNumber") == cura.CuraApplication.CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
|
||||
## Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
||||
# TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
||||
def getPrintingExtruder(self) -> Optional[ExtruderStack]:
|
||||
"""Get the extruder used to print this node. If there is no active node, then the extruder in position zero is returned
|
||||
|
||||
TODO The best way to do it is by adding the setActiveExtruder decorator to every node when is loaded
|
||||
"""
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack is None:
|
||||
return None
|
||||
@ -69,8 +73,9 @@ class CuraSceneNode(SceneNode):
|
||||
# This point should never be reached
|
||||
return None
|
||||
|
||||
## Return the color of the material used to print this model
|
||||
def getDiffuseColor(self) -> List[float]:
|
||||
"""Return the color of the material used to print this model"""
|
||||
|
||||
printing_extruder = self.getPrintingExtruder()
|
||||
|
||||
material_color = "#808080" # Fallback color
|
||||
@ -86,8 +91,9 @@ class CuraSceneNode(SceneNode):
|
||||
1.0
|
||||
]
|
||||
|
||||
## Return if any area collides with the convex hull of this scene node
|
||||
def collidesWithAreas(self, areas: List[Polygon]) -> bool:
|
||||
"""Return if any area collides with the convex hull of this scene node"""
|
||||
|
||||
convex_hull = self.callDecoration("getPrintingArea")
|
||||
if convex_hull:
|
||||
if not convex_hull.isValid():
|
||||
@ -101,8 +107,9 @@ class CuraSceneNode(SceneNode):
|
||||
return True
|
||||
return False
|
||||
|
||||
## Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box
|
||||
def _calculateAABB(self) -> None:
|
||||
"""Override of SceneNode._calculateAABB to exclude non-printing-meshes from bounding box"""
|
||||
|
||||
self._aabb = None
|
||||
if self._mesh_data:
|
||||
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
|
||||
@ -122,8 +129,9 @@ class CuraSceneNode(SceneNode):
|
||||
else:
|
||||
self._aabb = self._aabb + child.getBoundingBox()
|
||||
|
||||
## Taken from SceneNode, but replaced SceneNode with CuraSceneNode
|
||||
def __deepcopy__(self, memo: Dict[int, object]) -> "CuraSceneNode":
|
||||
"""Taken from SceneNode, but replaced SceneNode with CuraSceneNode"""
|
||||
|
||||
copy = CuraSceneNode(no_setting_override = True) # Setting override will be added later
|
||||
copy.setTransformation(self.getLocalTransformation())
|
||||
copy.setMeshData(self._mesh_data)
|
||||
|
@ -1,8 +1,9 @@
|
||||
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
|
||||
|
||||
|
||||
## A decorator that stores the amount an object has been moved below the platform.
|
||||
class ZOffsetDecorator(SceneNodeDecorator):
|
||||
"""A decorator that stores the amount an object has been moved below the platform."""
|
||||
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._z_offset = 0.
|
||||
|
@ -33,12 +33,14 @@ if TYPE_CHECKING:
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## Manager class that contains common actions to deal with containers in Cura.
|
||||
#
|
||||
# This is primarily intended as a class to be able to perform certain actions
|
||||
# from within QML. We want to be able to trigger things like removing a container
|
||||
# when a certain action happens. This can be done through this class.
|
||||
class ContainerManager(QObject):
|
||||
"""Manager class that contains common actions to deal with containers in Cura.
|
||||
|
||||
This is primarily intended as a class to be able to perform certain actions
|
||||
from within QML. We want to be able to trigger things like removing a container
|
||||
when a certain action happens. This can be done through this class.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self, application: "CuraApplication") -> None:
|
||||
if ContainerManager.__instance is not None:
|
||||
@ -67,21 +69,23 @@ class ContainerManager(QObject):
|
||||
return ""
|
||||
return str(result)
|
||||
|
||||
## Set a metadata entry of the specified container.
|
||||
#
|
||||
# This will set the specified entry of the container's metadata to the specified
|
||||
# value. Note that entries containing dictionaries can have their entries changed
|
||||
# by using "/" as a separator. For example, to change an entry "foo" in a
|
||||
# dictionary entry "bar", you can specify "bar/foo" as entry name.
|
||||
#
|
||||
# \param container_node \type{ContainerNode}
|
||||
# \param entry_name \type{str} The name of the metadata entry to change.
|
||||
# \param entry_value The new value of the entry.
|
||||
#
|
||||
# TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||
# Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
||||
@pyqtSlot("QVariant", str, str)
|
||||
def setContainerMetaDataEntry(self, container_node: "ContainerNode", entry_name: str, entry_value: str) -> bool:
|
||||
"""Set a metadata entry of the specified container.
|
||||
|
||||
This will set the specified entry of the container's metadata to the specified
|
||||
value. Note that entries containing dictionaries can have their entries changed
|
||||
by using "/" as a separator. For example, to change an entry "foo" in a
|
||||
dictionary entry "bar", you can specify "bar/foo" as entry name.
|
||||
|
||||
:param container_node: :type{ContainerNode}
|
||||
:param entry_name: :type{str} The name of the metadata entry to change.
|
||||
:param entry_value: The new value of the entry.
|
||||
|
||||
TODO: This is ONLY used by MaterialView for material containers. Maybe refactor this.
|
||||
Update: In order for QML to use objects and sub objects, those (sub) objects must all be QObject. Is that what we want?
|
||||
"""
|
||||
|
||||
if container_node.container is None:
|
||||
Logger.log("w", "Container node {0} doesn't have a container.".format(container_node.container_id))
|
||||
return False
|
||||
@ -124,18 +128,20 @@ class ContainerManager(QObject):
|
||||
def makeUniqueName(self, original_name: str) -> str:
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getContainerRegistry().uniqueName(original_name)
|
||||
|
||||
## Get a list of string that can be used as name filters for a Qt File Dialog
|
||||
#
|
||||
# This will go through the list of available container types and generate a list of strings
|
||||
# out of that. The strings are formatted as "description (*.extension)" and can be directly
|
||||
# passed to a nameFilters property of a Qt File Dialog.
|
||||
#
|
||||
# \param type_name Which types of containers to list. These types correspond to the "type"
|
||||
# key of the plugin metadata.
|
||||
#
|
||||
# \return A string list with name filters.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getContainerNameFilters(self, type_name: str) -> List[str]:
|
||||
"""Get a list of string that can be used as name filters for a Qt File Dialog
|
||||
|
||||
This will go through the list of available container types and generate a list of strings
|
||||
out of that. The strings are formatted as "description (*.extension)" and can be directly
|
||||
passed to a nameFilters property of a Qt File Dialog.
|
||||
|
||||
:param type_name: Which types of containers to list. These types correspond to the "type"
|
||||
key of the plugin metadata.
|
||||
|
||||
:return: A string list with name filters.
|
||||
"""
|
||||
|
||||
if not self._container_name_filters:
|
||||
self._updateContainerNameFilters()
|
||||
|
||||
@ -147,17 +153,18 @@ class ContainerManager(QObject):
|
||||
filters.append("All Files (*)")
|
||||
return filters
|
||||
|
||||
## Export a container to a file
|
||||
#
|
||||
# \param container_id The ID of the container to export
|
||||
# \param file_type The type of file to save as. Should be in the form of "description (*.extension, *.ext)"
|
||||
# \param file_url_or_string The URL where to save the file.
|
||||
#
|
||||
# \return A dictionary containing a key "status" with a status code and a key "message" with a message
|
||||
# explaining the status.
|
||||
# The status code can be one of "error", "cancelled", "success"
|
||||
@pyqtSlot(str, str, QUrl, result = "QVariantMap")
|
||||
def exportContainer(self, container_id: str, file_type: str, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||
"""Export a container to a file
|
||||
|
||||
:param container_id: The ID of the container to export
|
||||
:param file_type: The type of file to save as. Should be in the form of "description (*.extension, *.ext)"
|
||||
:param file_url_or_string: The URL where to save the file.
|
||||
|
||||
:return: A dictionary containing a key "status" with a status code and a key "message" with a message
|
||||
explaining the status. The status code can be one of "error", "cancelled", "success"
|
||||
"""
|
||||
|
||||
if not container_id or not file_type or not file_url_or_string:
|
||||
return {"status": "error", "message": "Invalid arguments"}
|
||||
|
||||
@ -214,14 +221,16 @@ class ContainerManager(QObject):
|
||||
|
||||
return {"status": "success", "message": "Successfully exported container", "path": file_url}
|
||||
|
||||
## Imports a profile from a file
|
||||
#
|
||||
# \param file_url A URL that points to the file to import.
|
||||
#
|
||||
# \return \type{Dict} dict with a 'status' key containing the string 'success' or 'error', and a 'message' key
|
||||
# containing a message for the user
|
||||
@pyqtSlot(QUrl, result = "QVariantMap")
|
||||
def importMaterialContainer(self, file_url_or_string: Union[QUrl, str]) -> Dict[str, str]:
|
||||
"""Imports a profile from a file
|
||||
|
||||
:param file_url: A URL that points to the file to import.
|
||||
|
||||
:return: :type{Dict} dict with a 'status' key containing the string 'success' or 'error', and a 'message' key
|
||||
containing a message for the user
|
||||
"""
|
||||
|
||||
if not file_url_or_string:
|
||||
return {"status": "error", "message": "Invalid path"}
|
||||
|
||||
@ -266,14 +275,16 @@ class ContainerManager(QObject):
|
||||
|
||||
return {"status": "success", "message": "Successfully imported container {0}".format(container.getName())}
|
||||
|
||||
## Update the current active quality changes container with the settings from the user container.
|
||||
#
|
||||
# This will go through the active global stack and all active extruder stacks and merge the changes from the user
|
||||
# container into the quality_changes container. After that, the user container is cleared.
|
||||
#
|
||||
# \return \type{bool} True if successful, False if not.
|
||||
@pyqtSlot(result = bool)
|
||||
def updateQualityChanges(self) -> bool:
|
||||
"""Update the current active quality changes container with the settings from the user container.
|
||||
|
||||
This will go through the active global stack and all active extruder stacks and merge the changes from the user
|
||||
container into the quality_changes container. After that, the user container is cleared.
|
||||
|
||||
:return: :type{bool} True if successful, False if not.
|
||||
"""
|
||||
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getMachineManager().activeMachine
|
||||
if not global_stack:
|
||||
@ -313,9 +324,10 @@ class ContainerManager(QObject):
|
||||
|
||||
return True
|
||||
|
||||
## Clear the top-most (user) containers of the active stacks.
|
||||
@pyqtSlot()
|
||||
def clearUserContainers(self) -> None:
|
||||
"""Clear the top-most (user) containers of the active stacks."""
|
||||
|
||||
machine_manager = cura.CuraApplication.CuraApplication.getInstance().getMachineManager()
|
||||
machine_manager.blurSettings.emit()
|
||||
|
||||
@ -335,25 +347,28 @@ class ContainerManager(QObject):
|
||||
for container in send_emits_containers:
|
||||
container.sendPostponedEmits()
|
||||
|
||||
## Get a list of materials that have the same GUID as the reference material
|
||||
#
|
||||
# \param material_node The node representing the material for which to get
|
||||
# the same GUID.
|
||||
# \param exclude_self Whether to include the name of the material you
|
||||
# provided.
|
||||
# \return A list of names of materials with the same GUID.
|
||||
@pyqtSlot("QVariant", bool, result = "QStringList")
|
||||
def getLinkedMaterials(self, material_node: "MaterialNode", exclude_self: bool = False) -> List[str]:
|
||||
"""Get a list of materials that have the same GUID as the reference material
|
||||
|
||||
:param material_node: The node representing the material for which to get
|
||||
the same GUID.
|
||||
:param exclude_self: Whether to include the name of the material you provided.
|
||||
:return: A list of names of materials with the same GUID.
|
||||
"""
|
||||
|
||||
same_guid = ContainerRegistry.getInstance().findInstanceContainersMetadata(GUID = material_node.guid)
|
||||
if exclude_self:
|
||||
return list({meta["name"] for meta in same_guid if meta["base_file"] != material_node.base_file})
|
||||
else:
|
||||
return list({meta["name"] for meta in same_guid})
|
||||
|
||||
## Unlink a material from all other materials by creating a new GUID
|
||||
# \param material_id \type{str} the id of the material to create a new GUID for.
|
||||
@pyqtSlot("QVariant")
|
||||
def unlinkMaterial(self, material_node: "MaterialNode") -> None:
|
||||
"""Unlink a material from all other materials by creating a new GUID
|
||||
|
||||
:param material_id: :type{str} the id of the material to create a new GUID for.
|
||||
"""
|
||||
# Get the material group
|
||||
if material_node.container is None: # Failed to lazy-load this container.
|
||||
return
|
||||
@ -428,9 +443,10 @@ class ContainerManager(QObject):
|
||||
name_filter = "{0} ({1})".format(mime_type.comment, suffix_list)
|
||||
self._container_name_filters[name_filter] = entry
|
||||
|
||||
## Import single profile, file_url does not have to end with curaprofile
|
||||
@pyqtSlot(QUrl, result = "QVariantMap")
|
||||
def importProfile(self, file_url: QUrl) -> Dict[str, str]:
|
||||
"""Import single profile, file_url does not have to end with curaprofile"""
|
||||
|
||||
if not file_url.isValid():
|
||||
return {"status": "error", "message": catalog.i18nc("@info:status", "Invalid file URL:") + " " + str(file_url)}
|
||||
path = file_url.toLocalFile()
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,25 +18,27 @@ from cura.Settings import cura_empty_instance_containers
|
||||
from . import Exceptions
|
||||
|
||||
|
||||
## Base class for Cura related stacks that want to enforce certain containers are available.
|
||||
#
|
||||
# This class makes sure that the stack has the following containers set: user changes, quality
|
||||
# changes, quality, material, variant, definition changes and finally definition. Initially,
|
||||
# these will be equal to the empty instance container.
|
||||
#
|
||||
# The container types are determined based on the following criteria:
|
||||
# - user: An InstanceContainer with the metadata entry "type" set to "user".
|
||||
# - quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes".
|
||||
# - quality: An InstanceContainer with the metadata entry "type" set to "quality".
|
||||
# - material: An InstanceContainer with the metadata entry "type" set to "material".
|
||||
# - variant: An InstanceContainer with the metadata entry "type" set to "variant".
|
||||
# - definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes".
|
||||
# - definition: A DefinitionContainer.
|
||||
#
|
||||
# Internally, this class ensures the mentioned containers are always there and kept in a specific order.
|
||||
# This also means that operations on the stack that modifies the container ordering is prohibited and
|
||||
# will raise an exception.
|
||||
class CuraContainerStack(ContainerStack):
|
||||
"""Base class for Cura related stacks that want to enforce certain containers are available.
|
||||
|
||||
This class makes sure that the stack has the following containers set: user changes, quality
|
||||
changes, quality, material, variant, definition changes and finally definition. Initially,
|
||||
these will be equal to the empty instance container.
|
||||
|
||||
The container types are determined based on the following criteria:
|
||||
- user: An InstanceContainer with the metadata entry "type" set to "user".
|
||||
- quality changes: An InstanceContainer with the metadata entry "type" set to "quality_changes".
|
||||
- quality: An InstanceContainer with the metadata entry "type" set to "quality".
|
||||
- material: An InstanceContainer with the metadata entry "type" set to "material".
|
||||
- variant: An InstanceContainer with the metadata entry "type" set to "variant".
|
||||
- definition changes: An InstanceContainer with the metadata entry "type" set to "definition_changes".
|
||||
- definition: A DefinitionContainer.
|
||||
|
||||
Internally, this class ensures the mentioned containers are always there and kept in a specific order.
|
||||
This also means that operations on the stack that modifies the container ordering is prohibited and
|
||||
will raise an exception.
|
||||
"""
|
||||
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
|
||||
@ -61,101 +63,131 @@ class CuraContainerStack(ContainerStack):
|
||||
# This is emitted whenever the containersChanged signal from the ContainerStack base class is emitted.
|
||||
pyqtContainersChanged = pyqtSignal()
|
||||
|
||||
## Set the user changes container.
|
||||
#
|
||||
# \param new_user_changes The new user changes container. It is expected to have a "type" metadata entry with the value "user".
|
||||
def setUserChanges(self, new_user_changes: InstanceContainer) -> None:
|
||||
"""Set the user changes container.
|
||||
|
||||
:param new_user_changes: The new user changes container. It is expected to have a "type" metadata entry with the value "user".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.UserChanges, new_user_changes)
|
||||
|
||||
## Get the user changes container.
|
||||
#
|
||||
# \return The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setUserChanges, notify = pyqtContainersChanged)
|
||||
def userChanges(self) -> InstanceContainer:
|
||||
"""Get the user changes container.
|
||||
|
||||
:return: The user changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.UserChanges])
|
||||
|
||||
## Set the quality changes container.
|
||||
#
|
||||
# \param new_quality_changes The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||
def setQualityChanges(self, new_quality_changes: InstanceContainer, postpone_emit = False) -> None:
|
||||
"""Set the quality changes container.
|
||||
|
||||
:param new_quality_changes: The new quality changes container. It is expected to have a "type" metadata entry with the value "quality_changes".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.QualityChanges, new_quality_changes, postpone_emit = postpone_emit)
|
||||
|
||||
## Get the quality changes container.
|
||||
#
|
||||
# \return The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setQualityChanges, notify = pyqtContainersChanged)
|
||||
def qualityChanges(self) -> InstanceContainer:
|
||||
"""Get the quality changes container.
|
||||
|
||||
:return: The quality changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.QualityChanges])
|
||||
|
||||
## Set the intent container.
|
||||
#
|
||||
# \param new_intent The new intent container. It is expected to have a "type" metadata entry with the value "intent".
|
||||
def setIntent(self, new_intent: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||
"""Set the intent container.
|
||||
|
||||
:param new_intent: The new intent container. It is expected to have a "type" metadata entry with the value "intent".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.Intent, new_intent, postpone_emit = postpone_emit)
|
||||
|
||||
## Get the quality container.
|
||||
#
|
||||
# \return The intent container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setIntent, notify = pyqtContainersChanged)
|
||||
def intent(self) -> InstanceContainer:
|
||||
"""Get the quality container.
|
||||
|
||||
:return: The intent container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Intent])
|
||||
|
||||
## Set the quality container.
|
||||
#
|
||||
# \param new_quality The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||
def setQuality(self, new_quality: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||
"""Set the quality container.
|
||||
|
||||
:param new_quality: The new quality container. It is expected to have a "type" metadata entry with the value "quality".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.Quality, new_quality, postpone_emit = postpone_emit)
|
||||
|
||||
## Get the quality container.
|
||||
#
|
||||
# \return The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setQuality, notify = pyqtContainersChanged)
|
||||
def quality(self) -> InstanceContainer:
|
||||
"""Get the quality container.
|
||||
|
||||
:return: The quality container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Quality])
|
||||
|
||||
## Set the material container.
|
||||
#
|
||||
# \param new_material The new material container. It is expected to have a "type" metadata entry with the value "material".
|
||||
def setMaterial(self, new_material: InstanceContainer, postpone_emit: bool = False) -> None:
|
||||
"""Set the material container.
|
||||
|
||||
:param new_material: The new material container. It is expected to have a "type" metadata entry with the value "material".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.Material, new_material, postpone_emit = postpone_emit)
|
||||
|
||||
## Get the material container.
|
||||
#
|
||||
# \return The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setMaterial, notify = pyqtContainersChanged)
|
||||
def material(self) -> InstanceContainer:
|
||||
"""Get the material container.
|
||||
|
||||
:return: The material container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Material])
|
||||
|
||||
## Set the variant container.
|
||||
#
|
||||
# \param new_variant The new variant container. It is expected to have a "type" metadata entry with the value "variant".
|
||||
def setVariant(self, new_variant: InstanceContainer) -> None:
|
||||
"""Set the variant container.
|
||||
|
||||
:param new_variant: The new variant container. It is expected to have a "type" metadata entry with the value "variant".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.Variant, new_variant)
|
||||
|
||||
## Get the variant container.
|
||||
#
|
||||
# \return The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setVariant, notify = pyqtContainersChanged)
|
||||
def variant(self) -> InstanceContainer:
|
||||
"""Get the variant container.
|
||||
|
||||
:return: The variant container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.Variant])
|
||||
|
||||
## Set the definition changes container.
|
||||
#
|
||||
# \param new_definition_changes The new definition changes container. It is expected to have a "type" metadata entry with the value "definition_changes".
|
||||
def setDefinitionChanges(self, new_definition_changes: InstanceContainer) -> None:
|
||||
"""Set the definition changes container.
|
||||
|
||||
:param new_definition_changes: The new definition changes container. It is expected to have a "type" metadata entry with the value "definition_changes".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.DefinitionChanges, new_definition_changes)
|
||||
|
||||
## Get the definition changes container.
|
||||
#
|
||||
# \return The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
@pyqtProperty(InstanceContainer, fset = setDefinitionChanges, notify = pyqtContainersChanged)
|
||||
def definitionChanges(self) -> InstanceContainer:
|
||||
"""Get the definition changes container.
|
||||
|
||||
:return: The definition changes container. Should always be a valid container, but can be equal to the empty InstanceContainer.
|
||||
"""
|
||||
|
||||
return cast(InstanceContainer, self._containers[_ContainerIndexes.DefinitionChanges])
|
||||
|
||||
## Set the definition container.
|
||||
#
|
||||
# \param new_definition The new definition container. It is expected to have a "type" metadata entry with the value "definition".
|
||||
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
|
||||
"""Set the definition container.
|
||||
|
||||
:param new_definition: The new definition container. It is expected to have a "type" metadata entry with the value "definition".
|
||||
"""
|
||||
|
||||
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
|
||||
|
||||
def getDefinition(self) -> "DefinitionContainer":
|
||||
@ -171,14 +203,16 @@ class CuraContainerStack(ContainerStack):
|
||||
def getTop(self) -> "InstanceContainer":
|
||||
return self.userChanges
|
||||
|
||||
## Check whether the specified setting has a 'user' value.
|
||||
#
|
||||
# A user value here is defined as the setting having a value in either
|
||||
# the UserChanges or QualityChanges container.
|
||||
#
|
||||
# \return True if the setting has a user value, False if not.
|
||||
@pyqtSlot(str, result = bool)
|
||||
def hasUserValue(self, key: str) -> bool:
|
||||
"""Check whether the specified setting has a 'user' value.
|
||||
|
||||
A user value here is defined as the setting having a value in either
|
||||
the UserChanges or QualityChanges container.
|
||||
|
||||
:return: True if the setting has a user value, False if not.
|
||||
"""
|
||||
|
||||
if self._containers[_ContainerIndexes.UserChanges].hasProperty(key, "value"):
|
||||
return True
|
||||
|
||||
@ -187,51 +221,61 @@ class CuraContainerStack(ContainerStack):
|
||||
|
||||
return False
|
||||
|
||||
## Set a property of a setting.
|
||||
#
|
||||
# This will set a property of a specified setting. Since the container stack does not contain
|
||||
# any settings itself, it is required to specify a container to set the property on. The target
|
||||
# container is matched by container type.
|
||||
#
|
||||
# \param key The key of the setting to set.
|
||||
# \param property_name The name of the property to set.
|
||||
# \param new_value The new value to set the property to.
|
||||
def setProperty(self, key: str, property_name: str, property_value: Any, container: "ContainerInterface" = None, set_from_cache: bool = False) -> None:
|
||||
"""Set a property of a setting.
|
||||
|
||||
This will set a property of a specified setting. Since the container stack does not contain
|
||||
any settings itself, it is required to specify a container to set the property on. The target
|
||||
container is matched by container type.
|
||||
|
||||
:param key: The key of the setting to set.
|
||||
:param property_name: The name of the property to set.
|
||||
:param new_value: The new value to set the property to.
|
||||
"""
|
||||
|
||||
container_index = _ContainerIndexes.UserChanges
|
||||
self._containers[container_index].setProperty(key, property_name, property_value, container, set_from_cache)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
# ordering, we disallow this operation.
|
||||
@override(ContainerStack)
|
||||
def addContainer(self, container: ContainerInterface) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
ordering, we disallow this operation.
|
||||
"""
|
||||
|
||||
raise Exceptions.InvalidOperationError("Cannot add a container to Global stack")
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
# ordering, we disallow this operation.
|
||||
@override(ContainerStack)
|
||||
def insertContainer(self, index: int, container: ContainerInterface) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
ordering, we disallow this operation.
|
||||
"""
|
||||
|
||||
raise Exceptions.InvalidOperationError("Cannot insert a container into Global stack")
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
# ordering, we disallow this operation.
|
||||
@override(ContainerStack)
|
||||
def removeContainer(self, index: int = 0) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
Since we have a fixed order of containers in the stack and this method would modify the container
|
||||
ordering, we disallow this operation.
|
||||
"""
|
||||
|
||||
raise Exceptions.InvalidOperationError("Cannot remove a container from Global stack")
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# Replaces the container at the specified index with another container.
|
||||
# This version performs checks to make sure the new container has the expected metadata and type.
|
||||
#
|
||||
# \throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type.
|
||||
@override(ContainerStack)
|
||||
def replaceContainer(self, index: int, container: ContainerInterface, postpone_emit: bool = False) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
Replaces the container at the specified index with another container.
|
||||
This version performs checks to make sure the new container has the expected metadata and type.
|
||||
|
||||
:throws Exception.InvalidContainerError Raised when trying to replace a container with a container that has an incorrect type.
|
||||
"""
|
||||
|
||||
expected_type = _ContainerIndexes.IndexTypeMap[index]
|
||||
if expected_type == "definition":
|
||||
if not isinstance(container, DefinitionContainer):
|
||||
@ -245,16 +289,18 @@ class CuraContainerStack(ContainerStack):
|
||||
|
||||
super().replaceContainer(index, container, postpone_emit)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# This deserialize will make sure the internal list of containers matches with what we expect.
|
||||
# It will first check to see if the container at a certain index already matches with what we
|
||||
# expect. If it does not, it will search for a matching container with the correct type. Should
|
||||
# no container with the correct type be found, it will use the empty container.
|
||||
#
|
||||
# \throws InvalidContainerStackError Raised when no definition can be found for the stack.
|
||||
@override(ContainerStack)
|
||||
def deserialize(self, serialized: str, file_name: Optional[str] = None) -> str:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
This deserialize will make sure the internal list of containers matches with what we expect.
|
||||
It will first check to see if the container at a certain index already matches with what we
|
||||
expect. If it does not, it will search for a matching container with the correct type. Should
|
||||
no container with the correct type be found, it will use the empty container.
|
||||
|
||||
:raise InvalidContainerStackError: Raised when no definition can be found for the stack.
|
||||
"""
|
||||
|
||||
# update the serialized data first
|
||||
serialized = super().deserialize(serialized, file_name)
|
||||
|
||||
@ -298,10 +344,9 @@ class CuraContainerStack(ContainerStack):
|
||||
## TODO; Deserialize the containers.
|
||||
return serialized
|
||||
|
||||
## protected:
|
||||
|
||||
# Helper to make sure we emit a PyQt signal on container changes.
|
||||
def _onContainersChanged(self, container: Any) -> None:
|
||||
"""Helper to make sure we emit a PyQt signal on container changes."""
|
||||
|
||||
Application.getInstance().callLater(self.pyqtContainersChanged.emit)
|
||||
|
||||
# Helper that can be overridden to get the "machine" definition, that is, the definition that defines the machine
|
||||
@ -309,16 +354,18 @@ class CuraContainerStack(ContainerStack):
|
||||
def _getMachineDefinition(self) -> DefinitionContainer:
|
||||
return self.definition
|
||||
|
||||
## Find the ID that should be used when searching for instance containers for a specified definition.
|
||||
#
|
||||
# This handles the situation where the definition specifies we should use a different definition when
|
||||
# searching for instance containers.
|
||||
#
|
||||
# \param machine_definition The definition to find the "quality definition" for.
|
||||
#
|
||||
# \return The ID of the definition container to use when searching for instance containers.
|
||||
@classmethod
|
||||
def _findInstanceContainerDefinitionId(cls, machine_definition: DefinitionContainerInterface) -> str:
|
||||
"""Find the ID that should be used when searching for instance containers for a specified definition.
|
||||
|
||||
This handles the situation where the definition specifies we should use a different definition when
|
||||
searching for instance containers.
|
||||
|
||||
:param machine_definition: The definition to find the "quality definition" for.
|
||||
|
||||
:return: The ID of the definition container to use when searching for instance containers.
|
||||
"""
|
||||
|
||||
quality_definition = machine_definition.getMetaDataEntry("quality_definition")
|
||||
if not quality_definition:
|
||||
return machine_definition.id #type: ignore
|
||||
@ -330,17 +377,18 @@ class CuraContainerStack(ContainerStack):
|
||||
|
||||
return cls._findInstanceContainerDefinitionId(definitions[0])
|
||||
|
||||
## getProperty for extruder positions, with translation from -1 to default extruder number
|
||||
def getExtruderPositionValueWithDefault(self, key):
|
||||
"""getProperty for extruder positions, with translation from -1 to default extruder number"""
|
||||
|
||||
value = self.getProperty(key, "value")
|
||||
if value == -1:
|
||||
value = int(Application.getInstance().getMachineManager().defaultExtruderPosition)
|
||||
return value
|
||||
|
||||
## private:
|
||||
|
||||
# Private helper class to keep track of container positions and their types.
|
||||
class _ContainerIndexes:
|
||||
"""Private helper class to keep track of container positions and their types."""
|
||||
|
||||
UserChanges = 0
|
||||
QualityChanges = 1
|
||||
Intent = 2
|
||||
|
@ -13,17 +13,20 @@ from .GlobalStack import GlobalStack
|
||||
from .ExtruderStack import ExtruderStack
|
||||
|
||||
|
||||
## Contains helper functions to create new machines.
|
||||
class CuraStackBuilder:
|
||||
"""Contains helper functions to create new machines."""
|
||||
|
||||
|
||||
## Create a new instance of a machine.
|
||||
#
|
||||
# \param name The name of the new machine.
|
||||
# \param definition_id The ID of the machine definition to use.
|
||||
#
|
||||
# \return The new global stack or None if an error occurred.
|
||||
@classmethod
|
||||
def createMachine(cls, name: str, definition_id: str) -> Optional[GlobalStack]:
|
||||
"""Create a new instance of a machine.
|
||||
|
||||
:param name: The name of the new machine.
|
||||
:param definition_id: The ID of the machine definition to use.
|
||||
|
||||
:return: The new global stack or None if an error occurred.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
@ -71,12 +74,14 @@ class CuraStackBuilder:
|
||||
|
||||
return new_global_stack
|
||||
|
||||
## Create a default Extruder Stack
|
||||
#
|
||||
# \param global_stack The global stack this extruder refers to.
|
||||
# \param extruder_position The position of the current extruder.
|
||||
@classmethod
|
||||
def createExtruderStackWithDefaultSetup(cls, global_stack: "GlobalStack", extruder_position: int) -> None:
|
||||
"""Create a default Extruder Stack
|
||||
|
||||
:param global_stack: The global stack this extruder refers to.
|
||||
:param extruder_position: The position of the current extruder.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
@ -120,17 +125,6 @@ class CuraStackBuilder:
|
||||
|
||||
registry.addContainer(new_extruder)
|
||||
|
||||
## Create a new Extruder stack
|
||||
#
|
||||
# \param new_stack_id The ID of the new stack.
|
||||
# \param extruder_definition The definition to base the new stack on.
|
||||
# \param machine_definition_id The ID of the machine definition to use for the user container.
|
||||
# \param position The position the extruder occupies in the machine.
|
||||
# \param variant_container The variant selected for the current extruder.
|
||||
# \param material_container The material selected for the current extruder.
|
||||
# \param quality_container The quality selected for the current extruder.
|
||||
#
|
||||
# \return A new Extruder stack instance with the specified parameters.
|
||||
@classmethod
|
||||
def createExtruderStack(cls, new_stack_id: str, extruder_definition: DefinitionContainerInterface,
|
||||
machine_definition_id: str,
|
||||
@ -139,6 +133,19 @@ class CuraStackBuilder:
|
||||
material_container: "InstanceContainer",
|
||||
quality_container: "InstanceContainer") -> ExtruderStack:
|
||||
|
||||
"""Create a new Extruder stack
|
||||
|
||||
:param new_stack_id: The ID of the new stack.
|
||||
:param extruder_definition: The definition to base the new stack on.
|
||||
:param machine_definition_id: The ID of the machine definition to use for the user container.
|
||||
:param position: The position the extruder occupies in the machine.
|
||||
:param variant_container: The variant selected for the current extruder.
|
||||
:param material_container: The material selected for the current extruder.
|
||||
:param quality_container: The quality selected for the current extruder.
|
||||
|
||||
:return: A new Extruder stack instance with the specified parameters.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
@ -167,29 +174,23 @@ class CuraStackBuilder:
|
||||
|
||||
return stack
|
||||
|
||||
## Create a new Global stack
|
||||
#
|
||||
# \param new_stack_id The ID of the new stack.
|
||||
# \param definition The definition to base the new stack on.
|
||||
# \param kwargs You can add keyword arguments to specify IDs of containers to use for a specific type, for example "variant": "0.4mm"
|
||||
#
|
||||
# \return A new Global stack instance with the specified parameters.
|
||||
|
||||
## Create a new Global stack
|
||||
#
|
||||
# \param new_stack_id The ID of the new stack.
|
||||
# \param definition The definition to base the new stack on.
|
||||
# \param variant_container The variant selected for the current stack.
|
||||
# \param material_container The material selected for the current stack.
|
||||
# \param quality_container The quality selected for the current stack.
|
||||
#
|
||||
# \return A new Global stack instance with the specified parameters.
|
||||
@classmethod
|
||||
def createGlobalStack(cls, new_stack_id: str, definition: DefinitionContainerInterface,
|
||||
variant_container: "InstanceContainer",
|
||||
material_container: "InstanceContainer",
|
||||
quality_container: "InstanceContainer") -> GlobalStack:
|
||||
|
||||
"""Create a new Global stack
|
||||
|
||||
:param new_stack_id: The ID of the new stack.
|
||||
:param definition: The definition to base the new stack on.
|
||||
:param variant_container: The variant selected for the current stack.
|
||||
:param material_container: The material selected for the current stack.
|
||||
:param quality_container: The quality selected for the current stack.
|
||||
|
||||
:return: A new Global stack instance with the specified parameters.
|
||||
"""
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
application = CuraApplication.getInstance()
|
||||
registry = application.getContainerRegistry()
|
||||
|
@ -2,21 +2,25 @@
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
|
||||
## Raised when trying to perform an operation like add on a stack that does not allow that.
|
||||
class InvalidOperationError(Exception):
|
||||
"""Raised when trying to perform an operation like add on a stack that does not allow that."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## Raised when trying to replace a container with a container that does not have the expected type.
|
||||
class InvalidContainerError(Exception):
|
||||
"""Raised when trying to replace a container with a container that does not have the expected type."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders.
|
||||
class TooManyExtrudersError(Exception):
|
||||
"""Raised when trying to add an extruder to a Global stack that already has the maximum number of extruders."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## Raised when an extruder has no next stack set.
|
||||
class NoGlobalStackError(Exception):
|
||||
"""Raised when an extruder has no next stack set."""
|
||||
|
||||
pass
|
||||
|
@ -19,13 +19,15 @@ if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
|
||||
## Manages all existing extruder stacks.
|
||||
#
|
||||
# This keeps a list of extruder stacks for each machine.
|
||||
class ExtruderManager(QObject):
|
||||
"""Manages all existing extruder stacks.
|
||||
|
||||
This keeps a list of extruder stacks for each machine.
|
||||
"""
|
||||
|
||||
## Registers listeners and such to listen to changes to the extruders.
|
||||
def __init__(self, parent = None):
|
||||
"""Registers listeners and such to listen to changes to the extruders."""
|
||||
|
||||
if ExtruderManager.__instance is not None:
|
||||
raise RuntimeError("Try to create singleton '%s' more than once" % self.__class__.__name__)
|
||||
ExtruderManager.__instance = self
|
||||
@ -43,20 +45,22 @@ class ExtruderManager(QObject):
|
||||
|
||||
Selection.selectionChanged.connect(self.resetSelectedObjectExtruders)
|
||||
|
||||
## Signal to notify other components when the list of extruders for a machine definition changes.
|
||||
extrudersChanged = pyqtSignal(QVariant)
|
||||
"""Signal to notify other components when the list of extruders for a machine definition changes."""
|
||||
|
||||
## Notify when the user switches the currently active extruder.
|
||||
activeExtruderChanged = pyqtSignal()
|
||||
"""Notify when the user switches the currently active extruder."""
|
||||
|
||||
## Gets the unique identifier of the currently active extruder stack.
|
||||
#
|
||||
# The currently active extruder stack is the stack that is currently being
|
||||
# edited.
|
||||
#
|
||||
# \return The unique ID of the currently active extruder stack.
|
||||
@pyqtProperty(str, notify = activeExtruderChanged)
|
||||
def activeExtruderStackId(self) -> Optional[str]:
|
||||
"""Gets the unique identifier of the currently active extruder stack.
|
||||
|
||||
The currently active extruder stack is the stack that is currently being
|
||||
edited.
|
||||
|
||||
:return: The unique ID of the currently active extruder stack.
|
||||
"""
|
||||
|
||||
if not self._application.getGlobalContainerStack():
|
||||
return None # No active machine, so no active extruder.
|
||||
try:
|
||||
@ -64,9 +68,10 @@ class ExtruderManager(QObject):
|
||||
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
|
||||
return None
|
||||
|
||||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
def extruderIds(self) -> Dict[str, str]:
|
||||
"""Gets a dict with the extruder stack ids with the extruder number as the key."""
|
||||
|
||||
extruder_stack_ids = {} # type: Dict[str, str]
|
||||
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
@ -75,11 +80,13 @@ class ExtruderManager(QObject):
|
||||
|
||||
return extruder_stack_ids
|
||||
|
||||
## Changes the active extruder by index.
|
||||
#
|
||||
# \param index The index of the new active extruder.
|
||||
@pyqtSlot(int)
|
||||
def setActiveExtruderIndex(self, index: int) -> None:
|
||||
"""Changes the active extruder by index.
|
||||
|
||||
:param index: The index of the new active extruder.
|
||||
"""
|
||||
|
||||
if self._active_extruder_index != index:
|
||||
self._active_extruder_index = index
|
||||
self.activeExtruderChanged.emit()
|
||||
@ -88,12 +95,13 @@ class ExtruderManager(QObject):
|
||||
def activeExtruderIndex(self) -> int:
|
||||
return self._active_extruder_index
|
||||
|
||||
## Emitted whenever the selectedObjectExtruders property changes.
|
||||
selectedObjectExtrudersChanged = pyqtSignal()
|
||||
"""Emitted whenever the selectedObjectExtruders property changes."""
|
||||
|
||||
## Provides a list of extruder IDs used by the current selected objects.
|
||||
@pyqtProperty("QVariantList", notify = selectedObjectExtrudersChanged)
|
||||
def selectedObjectExtruders(self) -> List[Union[str, "ExtruderStack"]]:
|
||||
"""Provides a list of extruder IDs used by the current selected objects."""
|
||||
|
||||
if not self._selected_object_extruders:
|
||||
object_extruders = set()
|
||||
|
||||
@ -122,11 +130,13 @@ class ExtruderManager(QObject):
|
||||
|
||||
return self._selected_object_extruders
|
||||
|
||||
## Reset the internal list used for the selectedObjectExtruders property
|
||||
#
|
||||
# This will trigger a recalculation of the extruders used for the
|
||||
# selection.
|
||||
def resetSelectedObjectExtruders(self) -> None:
|
||||
"""Reset the internal list used for the selectedObjectExtruders property
|
||||
|
||||
This will trigger a recalculation of the extruders used for the
|
||||
selection.
|
||||
"""
|
||||
|
||||
self._selected_object_extruders = []
|
||||
self.selectedObjectExtrudersChanged.emit()
|
||||
|
||||
@ -134,8 +144,9 @@ class ExtruderManager(QObject):
|
||||
def getActiveExtruderStack(self) -> Optional["ExtruderStack"]:
|
||||
return self.getExtruderStack(self.activeExtruderIndex)
|
||||
|
||||
## Get an extruder stack by index
|
||||
def getExtruderStack(self, index) -> Optional["ExtruderStack"]:
|
||||
"""Get an extruder stack by index"""
|
||||
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if global_container_stack.getId() in self._extruder_trains:
|
||||
@ -162,12 +173,14 @@ class ExtruderManager(QObject):
|
||||
if changed:
|
||||
self.extrudersChanged.emit(machine_id)
|
||||
|
||||
## Gets a property of a setting for all extruders.
|
||||
#
|
||||
# \param setting_key \type{str} The setting to get the property of.
|
||||
# \param property \type{str} The property to get.
|
||||
# \return \type{List} the list of results
|
||||
def getAllExtruderSettings(self, setting_key: str, prop: str) -> List[Any]:
|
||||
"""Gets a property of a setting for all extruders.
|
||||
|
||||
:param setting_key: :type{str} The setting to get the property of.
|
||||
:param prop: :type{str} The property to get.
|
||||
:return: :type{List} the list of results
|
||||
"""
|
||||
|
||||
result = []
|
||||
|
||||
for extruder_stack in self.getActiveExtruderStacks():
|
||||
@ -182,17 +195,19 @@ class ExtruderManager(QObject):
|
||||
else:
|
||||
return value
|
||||
|
||||
## Gets the extruder stacks that are actually being used at the moment.
|
||||
#
|
||||
# An extruder stack is being used if it is the extruder to print any mesh
|
||||
# with, or if it is the support infill extruder, the support interface
|
||||
# extruder, or the bed adhesion extruder.
|
||||
#
|
||||
# If there are no extruders, this returns the global stack as a singleton
|
||||
# list.
|
||||
#
|
||||
# \return A list of extruder stacks.
|
||||
def getUsedExtruderStacks(self) -> List["ExtruderStack"]:
|
||||
"""Gets the extruder stacks that are actually being used at the moment.
|
||||
|
||||
An extruder stack is being used if it is the extruder to print any mesh
|
||||
with, or if it is the support infill extruder, the support interface
|
||||
extruder, or the bed adhesion extruder.
|
||||
|
||||
If there are no extruders, this returns the global stack as a singleton
|
||||
list.
|
||||
|
||||
:return: A list of extruder stacks.
|
||||
"""
|
||||
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
|
||||
@ -277,11 +292,13 @@ class ExtruderManager(QObject):
|
||||
Logger.log("e", "Unable to find one or more of the extruders in %s", used_extruder_stack_ids)
|
||||
return []
|
||||
|
||||
## Get the extruder that the print will start with.
|
||||
#
|
||||
# This should mirror the implementation in CuraEngine of
|
||||
# ``FffGcodeWriter::getStartExtruder()``.
|
||||
def getInitialExtruderNr(self) -> int:
|
||||
"""Get the extruder that the print will start with.
|
||||
|
||||
This should mirror the implementation in CuraEngine of
|
||||
``FffGcodeWriter::getStartExtruder()``.
|
||||
"""
|
||||
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getGlobalContainerStack()
|
||||
|
||||
@ -296,28 +313,34 @@ class ExtruderManager(QObject):
|
||||
# REALLY no adhesion? Use the first used extruder.
|
||||
return self.getUsedExtruderStacks()[0].getProperty("extruder_nr", "value")
|
||||
|
||||
## Removes the container stack and user profile for the extruders for a specific machine.
|
||||
#
|
||||
# \param machine_id The machine to remove the extruders for.
|
||||
def removeMachineExtruders(self, machine_id: str) -> None:
|
||||
"""Removes the container stack and user profile for the extruders for a specific machine.
|
||||
|
||||
:param machine_id: The machine to remove the extruders for.
|
||||
"""
|
||||
|
||||
for extruder in self.getMachineExtruders(machine_id):
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.userChanges.getId())
|
||||
ContainerRegistry.getInstance().removeContainer(extruder.getId())
|
||||
if machine_id in self._extruder_trains:
|
||||
del self._extruder_trains[machine_id]
|
||||
|
||||
## Returns extruders for a specific machine.
|
||||
#
|
||||
# \param machine_id The machine to get the extruders of.
|
||||
def getMachineExtruders(self, machine_id: str) -> List["ExtruderStack"]:
|
||||
"""Returns extruders for a specific machine.
|
||||
|
||||
:param machine_id: The machine to get the extruders of.
|
||||
"""
|
||||
|
||||
if machine_id not in self._extruder_trains:
|
||||
return []
|
||||
return [self._extruder_trains[machine_id][name] for name in self._extruder_trains[machine_id]]
|
||||
|
||||
## Returns the list of active extruder stacks, taking into account the machine extruder count.
|
||||
#
|
||||
# \return \type{List[ContainerStack]} a list of
|
||||
def getActiveExtruderStacks(self) -> List["ExtruderStack"]:
|
||||
"""Returns the list of active extruder stacks, taking into account the machine extruder count.
|
||||
|
||||
:return: :type{List[ContainerStack]} a list of
|
||||
"""
|
||||
|
||||
global_stack = self._application.getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return []
|
||||
@ -329,8 +352,9 @@ class ExtruderManager(QObject):
|
||||
|
||||
self.resetSelectedObjectExtruders()
|
||||
|
||||
## Adds the extruders to the selected machine.
|
||||
def addMachineExtruders(self, global_stack: GlobalStack) -> None:
|
||||
"""Adds the extruders to the selected machine."""
|
||||
|
||||
extruders_changed = False
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
global_stack_id = global_stack.getId()
|
||||
@ -396,26 +420,30 @@ class ExtruderManager(QObject):
|
||||
raise IndexError(msg)
|
||||
extruder_stack_0.definition = extruder_definition
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to qml for display purposes
|
||||
#
|
||||
# \param key The key of the setting to retrieve values for.
|
||||
#
|
||||
# \return String representing the extruder values
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def getInstanceExtruderValues(self, key: str) -> List:
|
||||
"""Get all extruder values for a certain setting.
|
||||
|
||||
This is exposed to qml for display purposes
|
||||
|
||||
:param key: The key of the setting to retrieve values for.
|
||||
|
||||
:return: String representing the extruder values
|
||||
"""
|
||||
|
||||
return self._application.getCuraFormulaFunctions().getValuesInAllExtruders(key)
|
||||
|
||||
## Get the resolve value or value for a given key
|
||||
#
|
||||
# This is the effective value for a given key, it is used for values in the global stack.
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
# \param key The key of the setting to get the value of.
|
||||
#
|
||||
# \return The effective value
|
||||
@staticmethod
|
||||
def getResolveOrValue(key: str) -> Any:
|
||||
"""Get the resolve value or value for a given key
|
||||
|
||||
This is the effective value for a given key, it is used for values in the global stack.
|
||||
This is exposed to SettingFunction to use in value functions.
|
||||
:param key: The key of the setting to get the value of.
|
||||
|
||||
:return: The effective value
|
||||
"""
|
||||
|
||||
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack())
|
||||
resolved_value = global_stack.getProperty(key, "value")
|
||||
|
||||
|
@ -22,10 +22,9 @@ if TYPE_CHECKING:
|
||||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
|
||||
## Represents an Extruder and its related containers.
|
||||
#
|
||||
#
|
||||
class ExtruderStack(CuraContainerStack):
|
||||
"""Represents an Extruder and its related containers."""
|
||||
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
|
||||
@ -35,11 +34,13 @@ class ExtruderStack(CuraContainerStack):
|
||||
|
||||
enabledChanged = pyqtSignal()
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# This will set the next stack and ensure that we register this stack as an extruder.
|
||||
@override(ContainerStack)
|
||||
def setNextStack(self, stack: CuraContainerStack, connect_signals: bool = True) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
This will set the next stack and ensure that we register this stack as an extruder.
|
||||
"""
|
||||
|
||||
super().setNextStack(stack)
|
||||
stack.addExtruder(self)
|
||||
self.setMetaDataEntry("machine", stack.id)
|
||||
@ -71,11 +72,13 @@ class ExtruderStack(CuraContainerStack):
|
||||
|
||||
compatibleMaterialDiameterChanged = pyqtSignal()
|
||||
|
||||
## Return the filament diameter that the machine requires.
|
||||
#
|
||||
# If the machine has no requirement for the diameter, -1 is returned.
|
||||
# \return The filament diameter for the printer
|
||||
def getCompatibleMaterialDiameter(self) -> float:
|
||||
"""Return the filament diameter that the machine requires.
|
||||
|
||||
If the machine has no requirement for the diameter, -1 is returned.
|
||||
:return: The filament diameter for the printer
|
||||
"""
|
||||
|
||||
context = PropertyEvaluationContext(self)
|
||||
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
|
||||
|
||||
@ -97,31 +100,35 @@ class ExtruderStack(CuraContainerStack):
|
||||
|
||||
approximateMaterialDiameterChanged = pyqtSignal()
|
||||
|
||||
## Return the approximate filament diameter that the machine requires.
|
||||
#
|
||||
# The approximate material diameter is the material diameter rounded to
|
||||
# the nearest millimetre.
|
||||
#
|
||||
# If the machine has no requirement for the diameter, -1 is returned.
|
||||
#
|
||||
# \return The approximate filament diameter for the printer
|
||||
def getApproximateMaterialDiameter(self) -> float:
|
||||
"""Return the approximate filament diameter that the machine requires.
|
||||
|
||||
The approximate material diameter is the material diameter rounded to
|
||||
the nearest millimetre.
|
||||
|
||||
If the machine has no requirement for the diameter, -1 is returned.
|
||||
|
||||
:return: The approximate filament diameter for the printer
|
||||
"""
|
||||
|
||||
return round(self.getCompatibleMaterialDiameter())
|
||||
|
||||
approximateMaterialDiameter = pyqtProperty(float, fget = getApproximateMaterialDiameter,
|
||||
notify = approximateMaterialDiameterChanged)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# It will perform a few extra checks when trying to get properties.
|
||||
#
|
||||
# The two extra checks it currently does is to ensure a next stack is set and to bypass
|
||||
# the extruder when the property is not settable per extruder.
|
||||
#
|
||||
# \throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without
|
||||
# having a next stack set.
|
||||
@override(ContainerStack)
|
||||
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
It will perform a few extra checks when trying to get properties.
|
||||
|
||||
The two extra checks it currently does is to ensure a next stack is set and to bypass
|
||||
the extruder when the property is not settable per extruder.
|
||||
|
||||
:throws Exceptions.NoGlobalStackError Raised when trying to get a property from an extruder without
|
||||
having a next stack set.
|
||||
"""
|
||||
|
||||
if not self._next_stack:
|
||||
raise Exceptions.NoGlobalStackError("Extruder {id} is missing the next stack!".format(id = self.id))
|
||||
|
||||
|
@ -29,9 +29,9 @@ if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
|
||||
|
||||
## Represents the Global or Machine stack and its related containers.
|
||||
#
|
||||
class GlobalStack(CuraContainerStack):
|
||||
"""Represents the Global or Machine stack and its related containers."""
|
||||
|
||||
def __init__(self, container_id: str) -> None:
|
||||
super().__init__(container_id)
|
||||
|
||||
@ -58,12 +58,14 @@ class GlobalStack(CuraContainerStack):
|
||||
extrudersChanged = pyqtSignal()
|
||||
configuredConnectionTypesChanged = pyqtSignal()
|
||||
|
||||
## Get the list of extruders of this stack.
|
||||
#
|
||||
# \return The extruders registered with this stack.
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
@deprecated("Please use extruderList instead.", "4.4")
|
||||
def extruders(self) -> Dict[str, "ExtruderStack"]:
|
||||
"""Get the list of extruders of this stack.
|
||||
|
||||
:return: The extruders registered with this stack.
|
||||
"""
|
||||
|
||||
return self._extruders
|
||||
|
||||
@pyqtProperty("QVariantList", notify = extrudersChanged)
|
||||
@ -86,16 +88,18 @@ class GlobalStack(CuraContainerStack):
|
||||
def getLoadingPriority(cls) -> int:
|
||||
return 2
|
||||
|
||||
## The configured connection types can be used to find out if the global
|
||||
# stack is configured to be connected with a printer, without having to
|
||||
# know all the details as to how this is exactly done (and without
|
||||
# actually setting the stack to be active).
|
||||
#
|
||||
# This data can then in turn also be used when the global stack is active;
|
||||
# If we can't get a network connection, but it is configured to have one,
|
||||
# we can display a different icon to indicate the difference.
|
||||
@pyqtProperty("QVariantList", notify=configuredConnectionTypesChanged)
|
||||
def configuredConnectionTypes(self) -> List[int]:
|
||||
"""The configured connection types can be used to find out if the global
|
||||
|
||||
stack is configured to be connected with a printer, without having to
|
||||
know all the details as to how this is exactly done (and without
|
||||
actually setting the stack to be active).
|
||||
|
||||
This data can then in turn also be used when the global stack is active;
|
||||
If we can't get a network connection, but it is configured to have one,
|
||||
we can display a different icon to indicate the difference.
|
||||
"""
|
||||
# Requesting it from the metadata actually gets them as strings (as that's what you get from serializing).
|
||||
# But we do want them returned as a list of ints (so the rest of the code can directly compare)
|
||||
connection_types = self.getMetaDataEntry("connection_type", "").split(",")
|
||||
@ -122,16 +126,18 @@ class GlobalStack(CuraContainerStack):
|
||||
ConnectionType.CloudConnection.value]
|
||||
return has_remote_connection
|
||||
|
||||
## \sa configuredConnectionTypes
|
||||
def addConfiguredConnectionType(self, connection_type: int) -> None:
|
||||
""":sa configuredConnectionTypes"""
|
||||
|
||||
configured_connection_types = self.configuredConnectionTypes
|
||||
if connection_type not in configured_connection_types:
|
||||
# Store the values as a string.
|
||||
configured_connection_types.append(connection_type)
|
||||
self.setMetaDataEntry("connection_type", ",".join([str(c_type) for c_type in configured_connection_types]))
|
||||
|
||||
## \sa configuredConnectionTypes
|
||||
def removeConfiguredConnectionType(self, connection_type: int) -> None:
|
||||
""":sa configuredConnectionTypes"""
|
||||
|
||||
configured_connection_types = self.configuredConnectionTypes
|
||||
if connection_type in configured_connection_types:
|
||||
# Store the values as a string.
|
||||
@ -163,13 +169,15 @@ class GlobalStack(CuraContainerStack):
|
||||
def preferred_output_file_formats(self) -> str:
|
||||
return self.getMetaDataEntry("file_formats")
|
||||
|
||||
## Add an extruder to the list of extruders of this stack.
|
||||
#
|
||||
# \param extruder The extruder to add.
|
||||
#
|
||||
# \throws Exceptions.TooManyExtrudersError Raised when trying to add an extruder while we
|
||||
# already have the maximum number of extruders.
|
||||
def addExtruder(self, extruder: ContainerStack) -> None:
|
||||
"""Add an extruder to the list of extruders of this stack.
|
||||
|
||||
:param extruder: The extruder to add.
|
||||
|
||||
:raise Exceptions.TooManyExtrudersError: Raised when trying to add an extruder while we
|
||||
already have the maximum number of extruders.
|
||||
"""
|
||||
|
||||
position = extruder.getMetaDataEntry("position")
|
||||
if position is None:
|
||||
Logger.log("w", "No position defined for extruder {extruder}, cannot add it to stack {stack}", extruder = extruder.id, stack = self.id)
|
||||
@ -183,19 +191,21 @@ class GlobalStack(CuraContainerStack):
|
||||
self.extrudersChanged.emit()
|
||||
Logger.log("i", "Extruder[%s] added to [%s] at position [%s]", extruder.id, self.id, position)
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# This will return the value of the specified property for the specified setting,
|
||||
# unless the property is "value" and that setting has a "resolve" function set.
|
||||
# When a resolve is set, it will instead try and execute the resolve first and
|
||||
# then fall back to the normal "value" property.
|
||||
#
|
||||
# \param key The setting key to get the property of.
|
||||
# \param property_name The property to get the value of.
|
||||
#
|
||||
# \return The value of the property for the specified setting, or None if not found.
|
||||
@override(ContainerStack)
|
||||
def getProperty(self, key: str, property_name: str, context: Optional[PropertyEvaluationContext] = None) -> Any:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
This will return the value of the specified property for the specified setting,
|
||||
unless the property is "value" and that setting has a "resolve" function set.
|
||||
When a resolve is set, it will instead try and execute the resolve first and
|
||||
then fall back to the normal "value" property.
|
||||
|
||||
:param key: The setting key to get the property of.
|
||||
:param property_name: The property to get the value of.
|
||||
|
||||
:return: The value of the property for the specified setting, or None if not found.
|
||||
"""
|
||||
|
||||
if not self.definition.findDefinitions(key = key):
|
||||
return None
|
||||
|
||||
@ -235,11 +245,13 @@ class GlobalStack(CuraContainerStack):
|
||||
context.popContainer()
|
||||
return result
|
||||
|
||||
## Overridden from ContainerStack
|
||||
#
|
||||
# This will simply raise an exception since the Global stack cannot have a next stack.
|
||||
@override(ContainerStack)
|
||||
def setNextStack(self, stack: CuraContainerStack, connect_signals: bool = True) -> None:
|
||||
"""Overridden from ContainerStack
|
||||
|
||||
This will simply raise an exception since the Global stack cannot have a next stack.
|
||||
"""
|
||||
|
||||
raise Exceptions.InvalidOperationError("Global stack cannot have a next stack!")
|
||||
|
||||
# protected:
|
||||
@ -267,9 +279,11 @@ class GlobalStack(CuraContainerStack):
|
||||
|
||||
return True
|
||||
|
||||
## Perform some sanity checks on the global stack
|
||||
# Sanity check for extruders; they must have positions 0 and up to machine_extruder_count - 1
|
||||
def isValid(self) -> bool:
|
||||
"""Perform some sanity checks on the global stack
|
||||
|
||||
Sanity check for extruders; they must have positions 0 and up to machine_extruder_count - 1
|
||||
"""
|
||||
container_registry = ContainerRegistry.getInstance()
|
||||
extruder_trains = container_registry.findContainerStacks(type = "extruder_train", machine = self.getId())
|
||||
|
||||
@ -299,9 +313,10 @@ class GlobalStack(CuraContainerStack):
|
||||
def hasVariantBuildplates(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_variant_buildplates", False))
|
||||
|
||||
## Get default firmware file name if one is specified in the firmware
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self) -> str:
|
||||
"""Get default firmware file name if one is specified in the firmware"""
|
||||
|
||||
machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
|
||||
|
||||
baudrate = 250000
|
||||
|
@ -15,29 +15,32 @@ if TYPE_CHECKING:
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
|
||||
## Front-end for querying which intents are available for a certain
|
||||
# configuration.
|
||||
class IntentManager(QObject):
|
||||
"""Front-end for querying which intents are available for a certain configuration.
|
||||
"""
|
||||
__instance = None
|
||||
|
||||
## This class is a singleton.
|
||||
@classmethod
|
||||
def getInstance(cls):
|
||||
"""This class is a singleton."""
|
||||
|
||||
if not cls.__instance:
|
||||
cls.__instance = IntentManager()
|
||||
return cls.__instance
|
||||
|
||||
intentCategoryChanged = pyqtSignal() #Triggered when we switch categories.
|
||||
|
||||
## Gets the metadata dictionaries of all intent profiles for a given
|
||||
# configuration.
|
||||
#
|
||||
# \param definition_id ID of the printer.
|
||||
# \param nozzle_name Name of the nozzle.
|
||||
# \param material_base_file The base_file of the material.
|
||||
# \return A list of metadata dictionaries matching the search criteria, or
|
||||
# an empty list if nothing was found.
|
||||
def intentMetadatas(self, definition_id: str, nozzle_name: str, material_base_file: str) -> List[Dict[str, Any]]:
|
||||
"""Gets the metadata dictionaries of all intent profiles for a given
|
||||
|
||||
configuration.
|
||||
|
||||
:param definition_id: ID of the printer.
|
||||
:param nozzle_name: Name of the nozzle.
|
||||
:param material_base_file: The base_file of the material.
|
||||
:return: A list of metadata dictionaries matching the search criteria, or
|
||||
an empty list if nothing was found.
|
||||
"""
|
||||
intent_metadatas = [] # type: List[Dict[str, Any]]
|
||||
try:
|
||||
materials = ContainerTree.getInstance().machines[definition_id].variants[nozzle_name].materials
|
||||
@ -53,28 +56,32 @@ class IntentManager(QObject):
|
||||
intent_metadatas.append(intent_node.getMetadata())
|
||||
return intent_metadatas
|
||||
|
||||
## Collects and returns all intent categories available for the given
|
||||
# parameters. Note that the 'default' category is always available.
|
||||
#
|
||||
# \param definition_id ID of the printer.
|
||||
# \param nozzle_name Name of the nozzle.
|
||||
# \param material_id ID of the material.
|
||||
# \return A set of intent category names.
|
||||
def intentCategories(self, definition_id: str, nozzle_id: str, material_id: str) -> List[str]:
|
||||
"""Collects and returns all intent categories available for the given
|
||||
|
||||
parameters. Note that the 'default' category is always available.
|
||||
|
||||
:param definition_id: ID of the printer.
|
||||
:param nozzle_name: Name of the nozzle.
|
||||
:param material_id: ID of the material.
|
||||
:return: A set of intent category names.
|
||||
"""
|
||||
categories = set()
|
||||
for intent in self.intentMetadatas(definition_id, nozzle_id, material_id):
|
||||
categories.add(intent["intent_category"])
|
||||
categories.add("default") #The "empty" intent is not an actual profile specific to the configuration but we do want it to appear in the categories list.
|
||||
return list(categories)
|
||||
|
||||
## List of intents to be displayed in the interface.
|
||||
#
|
||||
# For the interface this will have to be broken up into the different
|
||||
# intent categories. That is up to the model there.
|
||||
#
|
||||
# \return A list of tuples of intent_category and quality_type. The actual
|
||||
# instance may vary per extruder.
|
||||
def getCurrentAvailableIntents(self) -> List[Tuple[str, str]]:
|
||||
"""List of intents to be displayed in the interface.
|
||||
|
||||
For the interface this will have to be broken up into the different
|
||||
intent categories. That is up to the model there.
|
||||
|
||||
:return: A list of tuples of intent_category and quality_type. The actual
|
||||
instance may vary per extruder.
|
||||
"""
|
||||
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
global_stack = application.getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
@ -100,16 +107,18 @@ class IntentManager(QObject):
|
||||
result.add((intent_metadata["intent_category"], intent_metadata["quality_type"]))
|
||||
return list(result)
|
||||
|
||||
## List of intent categories available in either of the extruders.
|
||||
#
|
||||
# This is purposefully inconsistent with the way that the quality types
|
||||
# are listed. The quality types will show all quality types available in
|
||||
# the printer using any configuration. This will only list the intent
|
||||
# categories that are available using the current configuration (but the
|
||||
# union over the extruders).
|
||||
# \return List of all categories in the current configurations of all
|
||||
# extruders.
|
||||
def currentAvailableIntentCategories(self) -> List[str]:
|
||||
"""List of intent categories available in either of the extruders.
|
||||
|
||||
This is purposefully inconsistent with the way that the quality types
|
||||
are listed. The quality types will show all quality types available in
|
||||
the printer using any configuration. This will only list the intent
|
||||
categories that are available using the current configuration (but the
|
||||
union over the extruders).
|
||||
:return: List of all categories in the current configurations of all
|
||||
extruders.
|
||||
"""
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return ["default"]
|
||||
@ -123,10 +132,12 @@ class IntentManager(QObject):
|
||||
final_intent_categories.update(self.intentCategories(current_definition_id, nozzle_name, material_id))
|
||||
return list(final_intent_categories)
|
||||
|
||||
## The intent that gets selected by default when no intent is available for
|
||||
# the configuration, an extruder can't match the intent that the user
|
||||
# selects, or just when creating a new printer.
|
||||
def getDefaultIntent(self) -> "InstanceContainer":
|
||||
"""The intent that gets selected by default when no intent is available for
|
||||
|
||||
the configuration, an extruder can't match the intent that the user
|
||||
selects, or just when creating a new printer.
|
||||
"""
|
||||
return empty_intent_container
|
||||
|
||||
@pyqtProperty(str, notify = intentCategoryChanged)
|
||||
@ -137,9 +148,10 @@ class IntentManager(QObject):
|
||||
return ""
|
||||
return active_extruder_stack.intent.getMetaDataEntry("intent_category", "")
|
||||
|
||||
## Apply intent on the stacks.
|
||||
@pyqtSlot(str, str)
|
||||
def selectIntent(self, intent_category: str, quality_type: str) -> None:
|
||||
"""Apply intent on the stacks."""
|
||||
|
||||
Logger.log("i", "Attempting to set intent_category to [%s] and quality type to [%s]", intent_category, quality_type)
|
||||
old_intent_category = self.currentIntentCategory
|
||||
application = cura.CuraApplication.CuraApplication.getInstance()
|
||||
|
@ -215,8 +215,9 @@ class MachineManager(QObject):
|
||||
return set()
|
||||
return general_definition_containers[0].getAllKeys()
|
||||
|
||||
## Triggered when the global container stack is changed in CuraApplication.
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
"""Triggered when the global container stack is changed in CuraApplication."""
|
||||
|
||||
if self._global_container_stack:
|
||||
try:
|
||||
self._global_container_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||
@ -338,12 +339,15 @@ class MachineManager(QObject):
|
||||
Logger.log("w", "An extruder has an unknown material, switching it to the preferred material")
|
||||
self.setMaterialById(extruder.getMetaDataEntry("position"), machine_node.preferred_material)
|
||||
|
||||
## Given a definition id, return the machine with this id.
|
||||
# Optional: add a list of keys and values to filter the list of machines with the given definition id
|
||||
# \param definition_id \type{str} definition id that needs to look for
|
||||
# \param metadata_filter \type{dict} list of metadata keys and values used for filtering
|
||||
@staticmethod
|
||||
def getMachine(definition_id: str, metadata_filter: Optional[Dict[str, str]] = None) -> Optional["GlobalStack"]:
|
||||
"""Given a definition id, return the machine with this id.
|
||||
|
||||
Optional: add a list of keys and values to filter the list of machines with the given definition id
|
||||
:param definition_id: :type{str} definition id that needs to look for
|
||||
:param metadata_filter: :type{dict} list of metadata keys and values used for filtering
|
||||
"""
|
||||
|
||||
if metadata_filter is None:
|
||||
metadata_filter = {}
|
||||
machines = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
|
||||
@ -397,9 +401,10 @@ class MachineManager(QObject):
|
||||
Logger.log("d", "Checking %s stacks for errors took %.2f s" % (count, time.time() - time_start))
|
||||
return False
|
||||
|
||||
## Check if the global_container has instances in the user container
|
||||
@pyqtProperty(bool, notify = activeStackValueChanged)
|
||||
def hasUserSettings(self) -> bool:
|
||||
"""Check if the global_container has instances in the user container"""
|
||||
|
||||
if not self._global_container_stack:
|
||||
return False
|
||||
|
||||
@ -422,10 +427,12 @@ class MachineManager(QObject):
|
||||
num_user_settings += stack.getTop().getNumInstances()
|
||||
return num_user_settings
|
||||
|
||||
## Delete a user setting from the global stack and all extruder stacks.
|
||||
# \param key \type{str} the name of the key to delete
|
||||
@pyqtSlot(str)
|
||||
def clearUserSettingAllCurrentStacks(self, key: str) -> None:
|
||||
"""Delete a user setting from the global stack and all extruder stacks.
|
||||
|
||||
:param key: :type{str} the name of the key to delete
|
||||
"""
|
||||
Logger.log("i", "Clearing the setting [%s] from all stacks", key)
|
||||
if not self._global_container_stack:
|
||||
return
|
||||
@ -454,11 +461,13 @@ class MachineManager(QObject):
|
||||
for container in send_emits_containers:
|
||||
container.sendPostponedEmits()
|
||||
|
||||
## Check if none of the stacks contain error states
|
||||
# Note that the _stacks_have_errors is cached due to performance issues
|
||||
# Calling _checkStack(s)ForErrors on every change is simply too expensive
|
||||
@pyqtProperty(bool, notify = stacksValidationChanged)
|
||||
def stacksHaveErrors(self) -> bool:
|
||||
"""Check if none of the stacks contain error states
|
||||
|
||||
Note that the _stacks_have_errors is cached due to performance issues
|
||||
Calling _checkStack(s)ForErrors on every change is simply too expensive
|
||||
"""
|
||||
return bool(self._stacks_have_errors)
|
||||
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
@ -528,14 +537,16 @@ class MachineManager(QObject):
|
||||
return material.getId()
|
||||
return ""
|
||||
|
||||
## Gets the layer height of the currently active quality profile.
|
||||
#
|
||||
# This is indicated together with the name of the active quality profile.
|
||||
#
|
||||
# \return The layer height of the currently active quality profile. If
|
||||
# there is no quality profile, this returns the default layer height.
|
||||
@pyqtProperty(float, notify = activeQualityGroupChanged)
|
||||
def activeQualityLayerHeight(self) -> float:
|
||||
"""Gets the layer height of the currently active quality profile.
|
||||
|
||||
This is indicated together with the name of the active quality profile.
|
||||
|
||||
:return: The layer height of the currently active quality profile. If
|
||||
there is no quality profile, this returns the default layer height.
|
||||
"""
|
||||
|
||||
if not self._global_container_stack:
|
||||
return 0
|
||||
value = self._global_container_stack.getRawProperty("layer_height", "value", skip_until_container = self._global_container_stack.qualityChanges.getId())
|
||||
@ -605,13 +616,15 @@ class MachineManager(QObject):
|
||||
|
||||
return result
|
||||
|
||||
## Returns whether there is anything unsupported in the current set-up.
|
||||
#
|
||||
# The current set-up signifies the global stack and all extruder stacks,
|
||||
# so this indicates whether there is any container in any of the container
|
||||
# stacks that is not marked as supported.
|
||||
@pyqtProperty(bool, notify = activeQualityChanged)
|
||||
def isCurrentSetupSupported(self) -> bool:
|
||||
"""Returns whether there is anything unsupported in the current set-up.
|
||||
|
||||
The current set-up signifies the global stack and all extruder stacks,
|
||||
so this indicates whether there is any container in any of the container
|
||||
stacks that is not marked as supported.
|
||||
"""
|
||||
|
||||
if not self._global_container_stack:
|
||||
return False
|
||||
for stack in [self._global_container_stack] + self._global_container_stack.extruderList:
|
||||
@ -622,9 +635,10 @@ class MachineManager(QObject):
|
||||
return False
|
||||
return True
|
||||
|
||||
## Copy the value of the setting of the current extruder to all other extruders as well as the global container.
|
||||
@pyqtSlot(str)
|
||||
def copyValueToExtruders(self, key: str) -> None:
|
||||
"""Copy the value of the setting of the current extruder to all other extruders as well as the global container."""
|
||||
|
||||
if self._active_container_stack is None or self._global_container_stack is None:
|
||||
return
|
||||
new_value = self._active_container_stack.getProperty(key, "value")
|
||||
@ -634,9 +648,10 @@ class MachineManager(QObject):
|
||||
if extruder_stack != self._active_container_stack and extruder_stack.getProperty(key, "value") != new_value:
|
||||
extruder_stack.userChanges.setProperty(key, "value", new_value) # TODO: nested property access, should be improved
|
||||
|
||||
## Copy the value of all manually changed settings of the current extruder to all other extruders.
|
||||
@pyqtSlot()
|
||||
def copyAllValuesToExtruders(self) -> None:
|
||||
"""Copy the value of all manually changed settings of the current extruder to all other extruders."""
|
||||
|
||||
if self._active_container_stack is None or self._global_container_stack is None:
|
||||
return
|
||||
|
||||
@ -648,19 +663,23 @@ class MachineManager(QObject):
|
||||
# Check if the value has to be replaced
|
||||
extruder_stack.userChanges.setProperty(key, "value", new_value)
|
||||
|
||||
## Get the Definition ID to use to select quality profiles for the currently active machine
|
||||
# \returns DefinitionID (string) if found, empty string otherwise
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeQualityDefinitionId(self) -> str:
|
||||
"""Get the Definition ID to use to select quality profiles for the currently active machine
|
||||
|
||||
:returns: DefinitionID (string) if found, empty string otherwise
|
||||
"""
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack:
|
||||
return ""
|
||||
return ContainerTree.getInstance().machines[global_stack.definition.getId()].quality_definition
|
||||
|
||||
## Gets how the active definition calls variants
|
||||
# Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
||||
@pyqtProperty(str, notify = globalContainerChanged)
|
||||
def activeDefinitionVariantsName(self) -> str:
|
||||
"""Gets how the active definition calls variants
|
||||
|
||||
Caveat: per-definition-variant-title is currently not translated (though the fallback is)
|
||||
"""
|
||||
fallback_title = catalog.i18nc("@label", "Nozzle")
|
||||
if self._global_container_stack:
|
||||
return self._global_container_stack.definition.getMetaDataEntry("variants_name", fallback_title)
|
||||
@ -708,9 +727,10 @@ class MachineManager(QObject):
|
||||
# This reuses the method and remove all printers recursively
|
||||
self.removeMachine(hidden_containers[0].getId())
|
||||
|
||||
## The selected buildplate is compatible if it is compatible with all the materials in all the extruders
|
||||
@pyqtProperty(bool, notify = activeMaterialChanged)
|
||||
def variantBuildplateCompatible(self) -> bool:
|
||||
"""The selected buildplate is compatible if it is compatible with all the materials in all the extruders"""
|
||||
|
||||
if not self._global_container_stack:
|
||||
return True
|
||||
|
||||
@ -727,10 +747,12 @@ class MachineManager(QObject):
|
||||
|
||||
return buildplate_compatible
|
||||
|
||||
## The selected buildplate is usable if it is usable for all materials OR it is compatible for one but not compatible
|
||||
# for the other material but the buildplate is still usable
|
||||
@pyqtProperty(bool, notify = activeMaterialChanged)
|
||||
def variantBuildplateUsable(self) -> bool:
|
||||
"""The selected buildplate is usable if it is usable for all materials OR it is compatible for one but not compatible
|
||||
|
||||
for the other material but the buildplate is still usable
|
||||
"""
|
||||
if not self._global_container_stack:
|
||||
return True
|
||||
|
||||
@ -751,11 +773,13 @@ class MachineManager(QObject):
|
||||
|
||||
return result
|
||||
|
||||
## Get the Definition ID of a machine (specified by ID)
|
||||
# \param machine_id string machine id to get the definition ID of
|
||||
# \returns DefinitionID if found, None otherwise
|
||||
@pyqtSlot(str, result = str)
|
||||
def getDefinitionByMachineId(self, machine_id: str) -> Optional[str]:
|
||||
"""Get the Definition ID of a machine (specified by ID)
|
||||
|
||||
:param machine_id: string machine id to get the definition ID of
|
||||
:returns: DefinitionID if found, None otherwise
|
||||
"""
|
||||
containers = CuraContainerRegistry.getInstance().findContainerStacks(id = machine_id)
|
||||
if containers:
|
||||
return containers[0].definition.getId()
|
||||
@ -786,8 +810,9 @@ class MachineManager(QObject):
|
||||
Logger.log("d", "Reset setting [%s] in [%s] because its old value [%s] is no longer valid", setting_key, container, old_value)
|
||||
return result
|
||||
|
||||
## Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed
|
||||
def correctExtruderSettings(self) -> None:
|
||||
"""Update extruder number to a valid value when the number of extruders are changed, or when an extruder is changed"""
|
||||
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
for setting_key in self.getIncompatibleSettingsOnEnabledExtruders(self._global_container_stack.userChanges):
|
||||
@ -803,9 +828,11 @@ class MachineManager(QObject):
|
||||
title = catalog.i18nc("@info:title", "Settings updated"))
|
||||
caution_message.show()
|
||||
|
||||
## Set the amount of extruders on the active machine (global stack)
|
||||
# \param extruder_count int the number of extruders to set
|
||||
def setActiveMachineExtruderCount(self, extruder_count: int) -> None:
|
||||
"""Set the amount of extruders on the active machine (global stack)
|
||||
|
||||
:param extruder_count: int the number of extruders to set
|
||||
"""
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
extruder_manager = self._application.getExtruderManager()
|
||||
@ -902,9 +929,10 @@ class MachineManager(QObject):
|
||||
def defaultExtruderPosition(self) -> str:
|
||||
return self._default_extruder_position
|
||||
|
||||
## This will fire the propertiesChanged for all settings so they will be updated in the front-end
|
||||
@pyqtSlot()
|
||||
def forceUpdateAllSettings(self) -> None:
|
||||
"""This will fire the propertiesChanged for all settings so they will be updated in the front-end"""
|
||||
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
with postponeSignals(*self._getContainerChangedSignals(), compress = CompressTechnique.CompressPerParameterValue):
|
||||
@ -945,11 +973,13 @@ class MachineManager(QObject):
|
||||
def _onMaterialNameChanged(self) -> None:
|
||||
self.activeMaterialChanged.emit()
|
||||
|
||||
## Get the signals that signal that the containers changed for all stacks.
|
||||
#
|
||||
# This includes the global stack and all extruder stacks. So if any
|
||||
# container changed anywhere.
|
||||
def _getContainerChangedSignals(self) -> List[Signal]:
|
||||
"""Get the signals that signal that the containers changed for all stacks.
|
||||
|
||||
This includes the global stack and all extruder stacks. So if any
|
||||
container changed anywhere.
|
||||
"""
|
||||
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
return [s.containersChanged for s in self._global_container_stack.extruderList + [self._global_container_stack]]
|
||||
@ -962,18 +992,21 @@ class MachineManager(QObject):
|
||||
container = extruder.userChanges
|
||||
container.setProperty(setting_name, property_name, property_value)
|
||||
|
||||
## Reset all setting properties of a setting for all extruders.
|
||||
# \param setting_name The ID of the setting to reset.
|
||||
@pyqtSlot(str)
|
||||
def resetSettingForAllExtruders(self, setting_name: str) -> None:
|
||||
"""Reset all setting properties of a setting for all extruders.
|
||||
|
||||
:param setting_name: The ID of the setting to reset.
|
||||
"""
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
for extruder in self._global_container_stack.extruderList:
|
||||
container = extruder.userChanges
|
||||
container.removeInstance(setting_name)
|
||||
|
||||
## Update _current_root_material_id when the current root material was changed.
|
||||
def _onRootMaterialChanged(self) -> None:
|
||||
"""Update _current_root_material_id when the current root material was changed."""
|
||||
|
||||
self._current_root_material_id = {}
|
||||
|
||||
changed = False
|
||||
@ -1135,8 +1168,9 @@ class MachineManager(QObject):
|
||||
return False
|
||||
return True
|
||||
|
||||
## Update current quality type and machine after setting material
|
||||
def _updateQualityWithMaterial(self, *args: Any) -> None:
|
||||
"""Update current quality type and machine after setting material"""
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
@ -1177,8 +1211,9 @@ class MachineManager(QObject):
|
||||
current_quality_type, quality_type)
|
||||
self._setQualityGroup(candidate_quality_groups[quality_type], empty_quality_changes = True)
|
||||
|
||||
## Update the current intent after the quality changed
|
||||
def _updateIntentWithQuality(self):
|
||||
"""Update the current intent after the quality changed"""
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
@ -1205,12 +1240,14 @@ class MachineManager(QObject):
|
||||
category = current_category
|
||||
self.setIntentByCategory(category)
|
||||
|
||||
## Update the material profile in the current stacks when the variant is
|
||||
# changed.
|
||||
# \param position The extruder stack to update. If provided with None, all
|
||||
# extruder stacks will be updated.
|
||||
@pyqtSlot()
|
||||
def updateMaterialWithVariant(self, position: Optional[str] = None) -> None:
|
||||
"""Update the material profile in the current stacks when the variant is
|
||||
|
||||
changed.
|
||||
:param position: The extruder stack to update. If provided with None, all
|
||||
extruder stacks will be updated.
|
||||
"""
|
||||
if self._global_container_stack is None:
|
||||
return
|
||||
if position is None:
|
||||
@ -1245,10 +1282,12 @@ class MachineManager(QObject):
|
||||
material_node = nozzle_node.preferredMaterial(approximate_material_diameter)
|
||||
self._setMaterial(position_item, material_node)
|
||||
|
||||
## Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
|
||||
# instance with the same network key.
|
||||
@pyqtSlot(str)
|
||||
def switchPrinterType(self, machine_name: str) -> None:
|
||||
"""Given a printer definition name, select the right machine instance. In case it doesn't exist, create a new
|
||||
|
||||
instance with the same network key.
|
||||
"""
|
||||
# Don't switch if the user tries to change to the same type of printer
|
||||
if self._global_container_stack is None or self._global_container_stack.definition.name == machine_name:
|
||||
return
|
||||
@ -1400,10 +1439,12 @@ class MachineManager(QObject):
|
||||
material_node = ContainerTree.getInstance().machines[machine_definition_id].variants[nozzle_name].materials[root_material_id]
|
||||
self.setMaterial(position, material_node)
|
||||
|
||||
## Global_stack: if you want to provide your own global_stack instead of the current active one
|
||||
# if you update an active machine, special measures have to be taken.
|
||||
@pyqtSlot(str, "QVariant")
|
||||
def setMaterial(self, position: str, container_node, global_stack: Optional["GlobalStack"] = None) -> None:
|
||||
"""Global_stack: if you want to provide your own global_stack instead of the current active one
|
||||
|
||||
if you update an active machine, special measures have to be taken.
|
||||
"""
|
||||
if global_stack is not None and global_stack != self._global_container_stack:
|
||||
global_stack.extruders[position].material = container_node.container
|
||||
return
|
||||
@ -1449,10 +1490,12 @@ class MachineManager(QObject):
|
||||
# Get all the quality groups for this global stack and filter out by quality_type
|
||||
self.setQualityGroup(ContainerTree.getInstance().getCurrentQualityGroups()[quality_type])
|
||||
|
||||
## Optionally provide global_stack if you want to use your own
|
||||
# The active global_stack is treated differently.
|
||||
@pyqtSlot(QObject)
|
||||
def setQualityGroup(self, quality_group: "QualityGroup", no_dialog: bool = False, global_stack: Optional["GlobalStack"] = None) -> None:
|
||||
"""Optionally provide global_stack if you want to use your own
|
||||
|
||||
The active global_stack is treated differently.
|
||||
"""
|
||||
if global_stack is not None and global_stack != self._global_container_stack:
|
||||
if quality_group is None:
|
||||
Logger.log("e", "Could not set quality group because quality group is None")
|
||||
@ -1514,15 +1557,17 @@ class MachineManager(QObject):
|
||||
return {"main": main_part,
|
||||
"suffix": suffix_part}
|
||||
|
||||
## Change the intent category of the current printer.
|
||||
#
|
||||
# All extruders can change their profiles. If an intent profile is
|
||||
# available with the desired intent category, that one will get chosen.
|
||||
# Otherwise the intent profile will be left to the empty profile, which
|
||||
# represents the "default" intent category.
|
||||
# \param intent_category The intent category to change to.
|
||||
@pyqtSlot(str)
|
||||
def setIntentByCategory(self, intent_category: str) -> None:
|
||||
"""Change the intent category of the current printer.
|
||||
|
||||
All extruders can change their profiles. If an intent profile is
|
||||
available with the desired intent category, that one will get chosen.
|
||||
Otherwise the intent profile will be left to the empty profile, which
|
||||
represents the "default" intent category.
|
||||
:param intent_category: The intent category to change to.
|
||||
"""
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_stack is None:
|
||||
return
|
||||
@ -1554,21 +1599,25 @@ class MachineManager(QObject):
|
||||
else: # No intent had the correct category.
|
||||
extruder.intent = empty_intent_container
|
||||
|
||||
## Get the currently activated quality group.
|
||||
#
|
||||
# If no printer is added yet or the printer doesn't have quality profiles,
|
||||
# this returns ``None``.
|
||||
# \return The currently active quality group.
|
||||
def activeQualityGroup(self) -> Optional["QualityGroup"]:
|
||||
"""Get the currently activated quality group.
|
||||
|
||||
If no printer is added yet or the printer doesn't have quality profiles,
|
||||
this returns ``None``.
|
||||
:return: The currently active quality group.
|
||||
"""
|
||||
|
||||
global_stack = cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not global_stack or global_stack.quality == empty_quality_container:
|
||||
return None
|
||||
return ContainerTree.getInstance().getCurrentQualityGroups().get(self.activeQualityType)
|
||||
|
||||
## Get the name of the active quality group.
|
||||
# \return The name of the active quality group.
|
||||
@pyqtProperty(str, notify = activeQualityGroupChanged)
|
||||
def activeQualityGroupName(self) -> str:
|
||||
"""Get the name of the active quality group.
|
||||
|
||||
:return: The name of the active quality group.
|
||||
"""
|
||||
quality_group = self.activeQualityGroup()
|
||||
if quality_group is None:
|
||||
return ""
|
||||
@ -1641,9 +1690,10 @@ class MachineManager(QObject):
|
||||
self.updateMaterialWithVariant(None)
|
||||
self._updateQualityWithMaterial()
|
||||
|
||||
## This function will translate any printer type name to an abbreviated printer type name
|
||||
@pyqtSlot(str, result = str)
|
||||
def getAbbreviatedMachineName(self, machine_type_name: str) -> str:
|
||||
"""This function will translate any printer type name to an abbreviated printer type name"""
|
||||
|
||||
abbr_machine = ""
|
||||
for word in re.findall(r"[\w']+", machine_type_name):
|
||||
if word.lower() == "ultimaker":
|
||||
|
@ -10,10 +10,13 @@ from UM.Resources import Resources
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.InstanceContainer import InstanceContainer
|
||||
|
||||
## Are machine names valid?
|
||||
#
|
||||
# Performs checks based on the length of the name.
|
||||
|
||||
class MachineNameValidator(QObject):
|
||||
"""Are machine names valid?
|
||||
|
||||
Performs checks based on the length of the name.
|
||||
"""
|
||||
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent)
|
||||
|
||||
@ -32,12 +35,13 @@ class MachineNameValidator(QObject):
|
||||
|
||||
validationChanged = pyqtSignal()
|
||||
|
||||
## Check if a specified machine name is allowed.
|
||||
#
|
||||
# \param name The machine name to check.
|
||||
# \return ``QValidator.Invalid`` if it's disallowed, or
|
||||
# ``QValidator.Acceptable`` if it's allowed.
|
||||
def validate(self, name):
|
||||
"""Check if a specified machine name is allowed.
|
||||
|
||||
:param name: The machine name to check.
|
||||
:return: ``QValidator.Invalid`` if it's disallowed, or ``QValidator.Acceptable`` if it's allowed.
|
||||
"""
|
||||
|
||||
#Check for file name length of the current settings container (which is the longest file we're saving with the name).
|
||||
try:
|
||||
filename_max_length = os.statvfs(Resources.getDataStoragePath()).f_namemax
|
||||
@ -50,9 +54,10 @@ class MachineNameValidator(QObject):
|
||||
|
||||
return QValidator.Acceptable #All checks succeeded.
|
||||
|
||||
## Updates the validation state of a machine name text field.
|
||||
@pyqtSlot(str)
|
||||
def updateValidation(self, new_name):
|
||||
"""Updates the validation state of a machine name text field."""
|
||||
|
||||
is_valid = self.validate(new_name)
|
||||
if is_valid == QValidator.Acceptable:
|
||||
self.validation_regex = "^.*$" #Matches anything.
|
||||
|
@ -6,8 +6,10 @@ from UM.Operations.Operation import Operation
|
||||
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
## Simple operation to set the extruder a certain object should be printed with.
|
||||
|
||||
class SetObjectExtruderOperation(Operation):
|
||||
"""Simple operation to set the extruder a certain object should be printed with."""
|
||||
|
||||
def __init__(self, node: SceneNode, extruder_id: str) -> None:
|
||||
self._node = node
|
||||
self._extruder_id = extruder_id
|
||||
|
@ -45,9 +45,10 @@ class SettingInheritanceManager(QObject):
|
||||
|
||||
settingsWithIntheritanceChanged = pyqtSignal()
|
||||
|
||||
## Get the keys of all children settings with an override.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getChildrenKeysWithOverride(self, key: str) -> List[str]:
|
||||
"""Get the keys of all children settings with an override."""
|
||||
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||
@ -163,8 +164,9 @@ class SettingInheritanceManager(QObject):
|
||||
def settingsWithInheritanceWarning(self) -> List[str]:
|
||||
return self._settings_with_inheritance_warning
|
||||
|
||||
## Check if a setting has an inheritance function that is overwritten
|
||||
def _settingIsOverwritingInheritance(self, key: str, stack: ContainerStack = None) -> bool:
|
||||
"""Check if a setting has an inheritance function that is overwritten"""
|
||||
|
||||
has_setting_function = False
|
||||
if not stack:
|
||||
stack = self._active_container_stack
|
||||
@ -177,17 +179,19 @@ class SettingInheritanceManager(QObject):
|
||||
|
||||
containers = [] # type: List[ContainerInterface]
|
||||
|
||||
## Check if the setting has a user state. If not, it is never overwritten.
|
||||
has_user_state = stack.getProperty(key, "state") == InstanceState.User
|
||||
"""Check if the setting has a user state. If not, it is never overwritten."""
|
||||
|
||||
if not has_user_state:
|
||||
return False
|
||||
|
||||
## If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
|
||||
# If a setting is not enabled, don't label it as overwritten (It's never visible anyway).
|
||||
if not stack.getProperty(key, "enabled"):
|
||||
return False
|
||||
|
||||
## Also check if the top container is not a setting function (this happens if the inheritance is restored).
|
||||
user_container = stack.getTop()
|
||||
"""Also check if the top container is not a setting function (this happens if the inheritance is restored)."""
|
||||
|
||||
if user_container and isinstance(user_container.getProperty(key, "value"), SettingFunction):
|
||||
return False
|
||||
|
||||
|
@ -15,21 +15,24 @@ from UM.Application import Application
|
||||
from cura.Settings.PerObjectContainerStack import PerObjectContainerStack
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
## A decorator that adds a container stack to a Node. This stack should be queried for all settings regarding
|
||||
# the linked node. The Stack in question will refer to the global stack (so that settings that are not defined by
|
||||
# this stack still resolve.
|
||||
@signalemitter
|
||||
class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
## Event indicating that the user selected a different extruder.
|
||||
"""A decorator that adds a container stack to a Node. This stack should be queried for all settings regarding
|
||||
|
||||
the linked node. The Stack in question will refer to the global stack (so that settings that are not defined by
|
||||
this stack still resolve.
|
||||
"""
|
||||
activeExtruderChanged = Signal()
|
||||
"""Event indicating that the user selected a different extruder."""
|
||||
|
||||
## Non-printing meshes
|
||||
#
|
||||
# If these settings are True for any mesh, the mesh does not need a convex hull,
|
||||
# and is sent to the slicer regardless of whether it fits inside the build volume.
|
||||
# Note that Support Mesh is not in here because it actually generates
|
||||
# g-code in the volume of the mesh.
|
||||
_non_printing_mesh_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh"}
|
||||
"""Non-printing meshes
|
||||
|
||||
If these settings are True for any mesh, the mesh does not need a convex hull,
|
||||
and is sent to the slicer regardless of whether it fits inside the build volume.
|
||||
Note that Support Mesh is not in here because it actually generates
|
||||
g-code in the volume of the mesh.
|
||||
"""
|
||||
_non_thumbnail_visible_settings = {"anti_overhang_mesh", "infill_mesh", "cutting_mesh", "support_mesh"}
|
||||
|
||||
def __init__(self):
|
||||
@ -56,11 +59,11 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
return "SettingOverrideInstanceContainer-%s" % uuid.uuid1()
|
||||
|
||||
def __deepcopy__(self, memo):
|
||||
## Create a fresh decorator object
|
||||
deep_copy = SettingOverrideDecorator()
|
||||
"""Create a fresh decorator object"""
|
||||
|
||||
## Copy the instance
|
||||
instance_container = copy.deepcopy(self._stack.getContainer(0), memo)
|
||||
"""Copy the instance"""
|
||||
|
||||
# A unique name must be added, or replaceContainer will not replace it
|
||||
instance_container.setMetaDataEntry("id", self._generateUniqueName())
|
||||
@ -78,22 +81,28 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
|
||||
return deep_copy
|
||||
|
||||
## Gets the currently active extruder to print this object with.
|
||||
#
|
||||
# \return An extruder's container stack.
|
||||
def getActiveExtruder(self):
|
||||
"""Gets the currently active extruder to print this object with.
|
||||
|
||||
:return: An extruder's container stack.
|
||||
"""
|
||||
|
||||
return self._extruder_stack
|
||||
|
||||
## Gets the signal that emits if the active extruder changed.
|
||||
#
|
||||
# This can then be accessed via a decorator.
|
||||
def getActiveExtruderChangedSignal(self):
|
||||
"""Gets the signal that emits if the active extruder changed.
|
||||
|
||||
This can then be accessed via a decorator.
|
||||
"""
|
||||
|
||||
return self.activeExtruderChanged
|
||||
|
||||
## Gets the currently active extruders position
|
||||
#
|
||||
# \return An extruder's position, or None if no position info is available.
|
||||
def getActiveExtruderPosition(self):
|
||||
"""Gets the currently active extruders position
|
||||
|
||||
:return: An extruder's position, or None if no position info is available.
|
||||
"""
|
||||
|
||||
# for support_meshes, always use the support_extruder
|
||||
if self.getStack().getProperty("support_mesh", "value"):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
@ -126,9 +135,11 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
Application.getInstance().getBackend().needsSlicing()
|
||||
Application.getInstance().getBackend().tickle()
|
||||
|
||||
## Makes sure that the stack upon which the container stack is placed is
|
||||
# kept up to date.
|
||||
def _updateNextStack(self):
|
||||
"""Makes sure that the stack upon which the container stack is placed is
|
||||
|
||||
kept up to date.
|
||||
"""
|
||||
if self._extruder_stack:
|
||||
extruder_stack = ContainerRegistry.getInstance().findContainerStacks(id = self._extruder_stack)
|
||||
if extruder_stack:
|
||||
@ -147,10 +158,12 @@ class SettingOverrideDecorator(SceneNodeDecorator):
|
||||
else:
|
||||
self._stack.setNextStack(Application.getInstance().getGlobalContainerStack())
|
||||
|
||||
## Changes the extruder with which to print this node.
|
||||
#
|
||||
# \param extruder_stack_id The new extruder stack to print with.
|
||||
def setActiveExtruder(self, extruder_stack_id):
|
||||
"""Changes the extruder with which to print this node.
|
||||
|
||||
:param extruder_stack_id: The new extruder stack to print with.
|
||||
"""
|
||||
|
||||
self._extruder_stack = extruder_stack_id
|
||||
self._updateNextStack()
|
||||
ExtruderManager.getInstance().resetSelectedObjectExtruders()
|
||||
|
@ -15,13 +15,15 @@ if TYPE_CHECKING:
|
||||
from cura.MachineAction import MachineAction
|
||||
|
||||
|
||||
## Raised when trying to add an unknown machine action as a required action
|
||||
class UnknownMachineActionError(Exception):
|
||||
"""Raised when trying to add an unknown machine action as a required action"""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
## Raised when trying to add a machine action that does not have an unique key.
|
||||
class NotUniqueMachineActionError(Exception):
|
||||
"""Raised when trying to add a machine action that does not have an unique key."""
|
||||
|
||||
pass
|
||||
|
||||
|
||||
@ -71,9 +73,11 @@ class MachineActionManager(QObject):
|
||||
self._definition_ids_with_default_actions_added.add(definition_id)
|
||||
Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id)
|
||||
|
||||
## Add a required action to a machine
|
||||
# Raises an exception when the action is not recognised.
|
||||
def addRequiredAction(self, definition_id: str, action_key: str) -> None:
|
||||
"""Add a required action to a machine
|
||||
|
||||
Raises an exception when the action is not recognised.
|
||||
"""
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._required_actions:
|
||||
if self._machine_actions[action_key] not in self._required_actions[definition_id]:
|
||||
@ -83,8 +87,9 @@ class MachineActionManager(QObject):
|
||||
else:
|
||||
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
|
||||
|
||||
## Add a supported action to a machine.
|
||||
def addSupportedAction(self, definition_id: str, action_key: str) -> None:
|
||||
"""Add a supported action to a machine."""
|
||||
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._supported_actions:
|
||||
if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
|
||||
@ -94,8 +99,9 @@ class MachineActionManager(QObject):
|
||||
else:
|
||||
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
|
||||
|
||||
## Add an action to the first start list of a machine.
|
||||
def addFirstStartAction(self, definition_id: str, action_key: str) -> None:
|
||||
"""Add an action to the first start list of a machine."""
|
||||
|
||||
if action_key in self._machine_actions:
|
||||
if definition_id in self._first_start_actions:
|
||||
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
|
||||
@ -104,57 +110,69 @@ class MachineActionManager(QObject):
|
||||
else:
|
||||
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
|
||||
|
||||
## Add a (unique) MachineAction
|
||||
# if the Key of the action is not unique, an exception is raised.
|
||||
def addMachineAction(self, action: "MachineAction") -> None:
|
||||
"""Add a (unique) MachineAction
|
||||
|
||||
if the Key of the action is not unique, an exception is raised.
|
||||
"""
|
||||
if action.getKey() not in self._machine_actions:
|
||||
self._machine_actions[action.getKey()] = action
|
||||
else:
|
||||
raise NotUniqueMachineActionError("MachineAction with key %s was already added. Actions must have unique keys.", action.getKey())
|
||||
|
||||
## Get all actions supported by given machine
|
||||
# \param definition_id The ID of the definition you want the supported actions of
|
||||
# \returns set of supported actions.
|
||||
@pyqtSlot(str, result = "QVariantList")
|
||||
def getSupportedActions(self, definition_id: str) -> List["MachineAction"]:
|
||||
"""Get all actions supported by given machine
|
||||
|
||||
:param definition_id: The ID of the definition you want the supported actions of
|
||||
:returns: set of supported actions.
|
||||
"""
|
||||
if definition_id in self._supported_actions:
|
||||
return list(self._supported_actions[definition_id])
|
||||
else:
|
||||
return list()
|
||||
|
||||
## Get all actions required by given machine
|
||||
# \param definition_id The ID of the definition you want the required actions of
|
||||
# \returns set of required actions.
|
||||
def getRequiredActions(self, definition_id: str) -> List["MachineAction"]:
|
||||
"""Get all actions required by given machine
|
||||
|
||||
:param definition_id: The ID of the definition you want the required actions of
|
||||
:returns: set of required actions.
|
||||
"""
|
||||
if definition_id in self._required_actions:
|
||||
return self._required_actions[definition_id]
|
||||
else:
|
||||
return list()
|
||||
|
||||
## Get all actions that need to be performed upon first start of a given machine.
|
||||
# Note that contrary to required / supported actions a list is returned (as it could be required to run the same
|
||||
# action multiple times).
|
||||
# \param definition_id The ID of the definition that you want to get the "on added" actions for.
|
||||
# \returns List of actions.
|
||||
@pyqtSlot(str, result = "QVariantList")
|
||||
def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]:
|
||||
"""Get all actions that need to be performed upon first start of a given machine.
|
||||
|
||||
Note that contrary to required / supported actions a list is returned (as it could be required to run the same
|
||||
action multiple times).
|
||||
:param definition_id: The ID of the definition that you want to get the "on added" actions for.
|
||||
:returns: List of actions.
|
||||
"""
|
||||
if definition_id in self._first_start_actions:
|
||||
return self._first_start_actions[definition_id]
|
||||
else:
|
||||
return []
|
||||
|
||||
## Remove Machine action from manager
|
||||
# \param action to remove
|
||||
def removeMachineAction(self, action: "MachineAction") -> None:
|
||||
"""Remove Machine action from manager
|
||||
|
||||
:param action: to remove
|
||||
"""
|
||||
try:
|
||||
del self._machine_actions[action.getKey()]
|
||||
except KeyError:
|
||||
Logger.log("w", "Trying to remove MachineAction (%s) that was already removed", action.getKey())
|
||||
|
||||
## Get MachineAction by key
|
||||
# \param key String of key to select
|
||||
# \return Machine action if found, None otherwise
|
||||
def getMachineAction(self, key: str) -> Optional["MachineAction"]:
|
||||
"""Get MachineAction by key
|
||||
|
||||
:param key: String of key to select
|
||||
:return: Machine action if found, None otherwise
|
||||
"""
|
||||
if key in self._machine_actions:
|
||||
return self._machine_actions[key]
|
||||
else:
|
||||
|
@ -31,8 +31,9 @@ class _NodeInfo:
|
||||
self.is_group = is_group # type: bool
|
||||
|
||||
|
||||
## Keep track of all objects in the project
|
||||
class ObjectsModel(ListModel):
|
||||
"""Keep track of all objects in the project"""
|
||||
|
||||
NameRole = Qt.UserRole + 1
|
||||
SelectedRole = Qt.UserRole + 2
|
||||
OutsideAreaRole = Qt.UserRole + 3
|
||||
|
@ -21,11 +21,13 @@ if TYPE_CHECKING:
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## A class for processing and the print times per build plate as well as managing the job name
|
||||
#
|
||||
# This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
|
||||
# This job name is requested by the JobSpecs qml file.
|
||||
class PrintInformation(QObject):
|
||||
"""A class for processing and the print times per build plate as well as managing the job name
|
||||
|
||||
This class also mangles the current machine name and the filename of the first loaded mesh into a job name.
|
||||
This job name is requested by the JobSpecs qml file.
|
||||
"""
|
||||
|
||||
|
||||
UNTITLED_JOB_NAME = "Untitled"
|
||||
|
||||
@ -380,10 +382,12 @@ class PrintInformation(QObject):
|
||||
def baseName(self):
|
||||
return self._base_name
|
||||
|
||||
## Created an acronym-like abbreviated machine name from the currently
|
||||
# active machine name.
|
||||
# Called each time the global stack is switched.
|
||||
def _defineAbbreviatedMachineName(self) -> None:
|
||||
"""Created an acronym-like abbreviated machine name from the currently active machine name.
|
||||
|
||||
Called each time the global stack is switched.
|
||||
"""
|
||||
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
self._abbr_machine = ""
|
||||
@ -392,8 +396,9 @@ class PrintInformation(QObject):
|
||||
|
||||
self._abbr_machine = self._application.getMachineManager().getAbbreviatedMachineName(active_machine_type_name)
|
||||
|
||||
## Utility method that strips accents from characters (eg: â -> a)
|
||||
def _stripAccents(self, to_strip: str) -> str:
|
||||
"""Utility method that strips accents from characters (eg: â -> a)"""
|
||||
|
||||
return ''.join(char for char in unicodedata.normalize('NFD', to_strip) if unicodedata.category(char) != 'Mn')
|
||||
|
||||
@pyqtSlot(result = "QVariantMap")
|
||||
@ -431,6 +436,7 @@ class PrintInformation(QObject):
|
||||
return
|
||||
self._change_timer.start()
|
||||
|
||||
## Listen to scene changes to check if we need to reset the print information
|
||||
def _onSceneChanged(self) -> None:
|
||||
"""Listen to scene changes to check if we need to reset the print information"""
|
||||
|
||||
self.setToZeroPrintInformation(self._active_build_plate)
|
||||
|
@ -11,13 +11,15 @@ from typing import Callable
|
||||
SEMANTIC_VERSION_REGEX = re.compile(r"^[0-9]+\.[0-9]+(\.[0-9]+)?$")
|
||||
|
||||
|
||||
## Decorator for functions that belong to a set of APIs. For now, this should only be used for officially supported
|
||||
# APIs, meaning that those APIs should be versioned and maintained.
|
||||
#
|
||||
# \param since_version The earliest version since when this API becomes supported. This means that since this version,
|
||||
# this API function is supposed to behave the same. This parameter is not used. It's just a
|
||||
# documentation.
|
||||
def api(since_version: str) -> Callable:
|
||||
"""Decorator for functions that belong to a set of APIs. For now, this should only be used for officially supported
|
||||
|
||||
APIs, meaning that those APIs should be versioned and maintained.
|
||||
|
||||
:param since_version: The earliest version since when this API becomes supported. This means that since this version,
|
||||
this API function is supposed to behave the same. This parameter is not used. It's just a
|
||||
documentation.
|
||||
"""
|
||||
# Make sure that APi versions are semantic versions
|
||||
if not SEMANTIC_VERSION_REGEX.fullmatch(since_version):
|
||||
raise ValueError("API since_version [%s] is not a semantic version." % since_version)
|
||||
|
@ -26,12 +26,14 @@ def container_registry():
|
||||
result.findContainersMetadata = MagicMock(return_value = [metadata_dict])
|
||||
return result
|
||||
|
||||
## Creates a machine node without anything underneath it. No sub-nodes.
|
||||
#
|
||||
# For testing stuff with machine nodes without testing _loadAll(). You'll need
|
||||
# to add subnodes manually in your test.
|
||||
@pytest.fixture
|
||||
def empty_machine_node():
|
||||
"""Creates a machine node without anything underneath it. No sub-nodes.
|
||||
|
||||
For testing stuff with machine nodes without testing _loadAll(). You'll need
|
||||
to add subnodes manually in your test.
|
||||
"""
|
||||
|
||||
empty_container_registry = MagicMock()
|
||||
empty_container_registry.findContainersMetadata = MagicMock(return_value = [metadata_dict]) # Still contain the MachineNode's own metadata for the constructor.
|
||||
empty_container_registry.findInstanceContainersMetadata = MagicMock(return_value = [])
|
||||
@ -77,9 +79,13 @@ def test_metadataProperties(container_registry):
|
||||
assert node.preferred_material == metadata_dict["preferred_material"]
|
||||
assert node.preferred_quality_type == metadata_dict["preferred_quality_type"]
|
||||
|
||||
## Test getting quality groups when there are quality profiles available for
|
||||
# the requested configurations on two extruders.
|
||||
|
||||
def test_getQualityGroupsBothExtrudersAvailable(empty_machine_node):
|
||||
"""Test getting quality groups when there are quality profiles available for
|
||||
|
||||
the requested configurations on two extruders.
|
||||
"""
|
||||
|
||||
# Prepare a tree inside the machine node.
|
||||
extruder_0_node = MagicMock(quality_type = "quality_type_1")
|
||||
extruder_1_node = MagicMock(quality_type = "quality_type_1") # Same quality type, so this is the one that can be returned.
|
||||
@ -121,12 +127,15 @@ def test_getQualityGroupsBothExtrudersAvailable(empty_machine_node):
|
||||
assert result["quality_type_1"].name == global_node.getMetaDataEntry("name", "Unnamed Profile")
|
||||
assert result["quality_type_1"].quality_type == "quality_type_1"
|
||||
|
||||
## Test the "is_available" flag on quality groups.
|
||||
#
|
||||
# If a profile is available for a quality type on an extruder but not on all
|
||||
# extruders, there should be a quality group for it but it should not be made
|
||||
# available.
|
||||
|
||||
def test_getQualityGroupsAvailability(empty_machine_node):
|
||||
"""Test the "is_available" flag on quality groups.
|
||||
|
||||
If a profile is available for a quality type on an extruder but not on all
|
||||
extruders, there should be a quality group for it but it should not be made
|
||||
available.
|
||||
"""
|
||||
|
||||
# Prepare a tree inside the machine node.
|
||||
extruder_0_both = MagicMock(quality_type = "quality_type_both") # This quality type is available for both extruders.
|
||||
extruder_1_both = MagicMock(quality_type = "quality_type_both")
|
||||
|
@ -6,7 +6,7 @@ import pytest
|
||||
|
||||
from cura.Machines.QualityNode import QualityNode
|
||||
|
||||
## Metadata for hypothetical containers that get put in the registry.
|
||||
# Metadata for hypothetical containers that get put in the registry.
|
||||
metadatas = [
|
||||
{
|
||||
"id": "matching_intent", # Matches our query.
|
||||
|
@ -49,12 +49,14 @@ def machine_node():
|
||||
mocked_machine_node.preferred_material = "preferred_material"
|
||||
return mocked_machine_node
|
||||
|
||||
## Constructs a variant node without any subnodes.
|
||||
#
|
||||
# This is useful for performing tests on VariantNode without being dependent
|
||||
# on how _loadAll works.
|
||||
@pytest.fixture
|
||||
def empty_variant_node(machine_node):
|
||||
"""Constructs a variant node without any subnodes.
|
||||
|
||||
This is useful for performing tests on VariantNode without being dependent
|
||||
on how _loadAll works.
|
||||
"""
|
||||
|
||||
container_registry = MagicMock(
|
||||
findContainersMetadata = MagicMock(return_value = [{"name": "test variant name"}])
|
||||
)
|
||||
@ -132,9 +134,12 @@ def test_materialAdded_update(container_registry, machine_node, metadata, change
|
||||
for key in changed_material_list:
|
||||
assert original_material_nodes[key] != variant_node.materials[key]
|
||||
|
||||
## Tests the preferred material when the exact base file is available in the
|
||||
# materials list for this node.
|
||||
|
||||
def test_preferredMaterialExactMatch(empty_variant_node):
|
||||
"""Tests the preferred material when the exact base file is available in the
|
||||
|
||||
materials list for this node.
|
||||
"""
|
||||
empty_variant_node.materials = {
|
||||
"some_different_material": MagicMock(getMetaDataEntry = lambda x: 3),
|
||||
"preferred_material": MagicMock(getMetaDataEntry = lambda x: 3) # Exact match.
|
||||
@ -143,9 +148,12 @@ def test_preferredMaterialExactMatch(empty_variant_node):
|
||||
|
||||
assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material"], "It should match exactly on this one since it's the preferred material."
|
||||
|
||||
## Tests the preferred material when a submaterial is available in the
|
||||
# materials list for this node.
|
||||
|
||||
def test_preferredMaterialSubmaterial(empty_variant_node):
|
||||
"""Tests the preferred material when a submaterial is available in the
|
||||
|
||||
materials list for this node.
|
||||
"""
|
||||
empty_variant_node.materials = {
|
||||
"some_different_material": MagicMock(getMetaDataEntry = lambda x: 3),
|
||||
"preferred_material_base_file_aa04": MagicMock(getMetaDataEntry = lambda x: 3) # This is a submaterial of the preferred material.
|
||||
@ -154,9 +162,10 @@ def test_preferredMaterialSubmaterial(empty_variant_node):
|
||||
|
||||
assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_base_file_aa04"], "It should match on the submaterial just as well."
|
||||
|
||||
## Tests the preferred material matching on the approximate diameter of the
|
||||
# filament.
|
||||
|
||||
def test_preferredMaterialDiameter(empty_variant_node):
|
||||
"""Tests the preferred material matching on the approximate diameter of the filament.
|
||||
"""
|
||||
empty_variant_node.materials = {
|
||||
"some_different_material": MagicMock(getMetaDataEntry = lambda x: 3),
|
||||
"preferred_material_wrong_diameter": MagicMock(getMetaDataEntry = lambda x: 2), # Approximate diameter is 2 instead of 3.
|
||||
@ -166,18 +175,22 @@ def test_preferredMaterialDiameter(empty_variant_node):
|
||||
|
||||
assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["preferred_material_correct_diameter"], "It should match only on the material with correct diameter."
|
||||
|
||||
## Tests the preferred material matching on a different material if the
|
||||
# diameter is wrong.
|
||||
|
||||
def test_preferredMaterialDiameterNoMatch(empty_variant_node):
|
||||
"""Tests the preferred material matching on a different material if the diameter is wrong."""
|
||||
|
||||
empty_variant_node.materials = collections.OrderedDict()
|
||||
empty_variant_node.materials["some_different_material"] = MagicMock(getMetaDataEntry = lambda x: 3) # This one first so that it gets iterated over first.
|
||||
empty_variant_node.materials["preferred_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # Wrong diameter.
|
||||
|
||||
assert empty_variant_node.preferredMaterial(approximate_diameter = 3) == empty_variant_node.materials["some_different_material"], "It should match on another material with the correct diameter if the preferred one is unavailable."
|
||||
|
||||
## Tests that the material diameter is considered more important to match than
|
||||
# the preferred diameter.
|
||||
|
||||
def test_preferredMaterialDiameterPreference(empty_variant_node):
|
||||
"""Tests that the material diameter is considered more important to match than
|
||||
the preferred diameter.
|
||||
"""
|
||||
|
||||
empty_variant_node.materials = collections.OrderedDict()
|
||||
empty_variant_node.materials["some_different_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # This one first so that it gets iterated over first.
|
||||
empty_variant_node.materials["preferred_material"] = MagicMock(getMetaDataEntry = lambda x: 2) # Matches on ID but not diameter.
|
||||
|
@ -5,18 +5,21 @@ import UM.PluginObject
|
||||
from UM.Signal import Signal
|
||||
|
||||
|
||||
## Fake container class to add to the container registry.
|
||||
#
|
||||
# This allows us to test the container registry without testing the container
|
||||
# class. If something is wrong in the container class it won't influence this
|
||||
# test.
|
||||
|
||||
class MockContainer(ContainerInterface, UM.PluginObject.PluginObject):
|
||||
## Initialise a new definition container.
|
||||
#
|
||||
# The container will have the specified ID and all metadata in the
|
||||
# provided dictionary.
|
||||
"""Fake container class to add to the container registry.
|
||||
|
||||
This allows us to test the container registry without testing the container
|
||||
class. If something is wrong in the container class it won't influence this
|
||||
test.
|
||||
"""
|
||||
|
||||
def __init__(self, metadata = None):
|
||||
"""Initialise a new definition container.
|
||||
|
||||
The container will have the specified ID and all metadata in the
|
||||
provided dictionary.
|
||||
"""
|
||||
|
||||
super().__init__()
|
||||
if metadata is None:
|
||||
self._metadata = {}
|
||||
@ -24,55 +27,69 @@ class MockContainer(ContainerInterface, UM.PluginObject.PluginObject):
|
||||
self._metadata = metadata
|
||||
self._plugin_id = "MockContainerPlugin"
|
||||
|
||||
## Gets the ID that was provided at initialisation.
|
||||
#
|
||||
# \return The ID of the container.
|
||||
def getId(self):
|
||||
"""Gets the ID that was provided at initialisation.
|
||||
|
||||
:return: The ID of the container.
|
||||
"""
|
||||
|
||||
return self._metadata["id"]
|
||||
|
||||
## Gets all metadata of this container.
|
||||
#
|
||||
# This returns the metadata dictionary that was provided in the
|
||||
# constructor of this mock container.
|
||||
#
|
||||
# \return The metadata for this container.
|
||||
def getMetaData(self):
|
||||
"""Gets all metadata of this container.
|
||||
|
||||
This returns the metadata dictionary that was provided in the
|
||||
constructor of this mock container.
|
||||
|
||||
:return: The metadata for this container.
|
||||
"""
|
||||
|
||||
return self._metadata
|
||||
|
||||
## Gets a metadata entry from the metadata dictionary.
|
||||
#
|
||||
# \param key The key of the metadata entry.
|
||||
# \return The value of the metadata entry, or None if there is no such
|
||||
# entry.
|
||||
def getMetaDataEntry(self, entry, default = None):
|
||||
"""Gets a metadata entry from the metadata dictionary.
|
||||
|
||||
:param key: The key of the metadata entry.
|
||||
:return: The value of the metadata entry, or None if there is no such
|
||||
entry.
|
||||
"""
|
||||
|
||||
if entry in self._metadata:
|
||||
return self._metadata[entry]
|
||||
return default
|
||||
|
||||
## Gets a human-readable name for this container.
|
||||
# \return The name from the metadata, or "MockContainer" if there was no
|
||||
# name provided.
|
||||
def getName(self):
|
||||
"""Gets a human-readable name for this container.
|
||||
|
||||
:return: The name from the metadata, or "MockContainer" if there was no
|
||||
name provided.
|
||||
"""
|
||||
return self._metadata.get("name", "MockContainer")
|
||||
|
||||
## Get whether a container stack is enabled or not.
|
||||
# \return Always returns True.
|
||||
@property
|
||||
def isEnabled(self):
|
||||
"""Get whether a container stack is enabled or not.
|
||||
|
||||
:return: Always returns True.
|
||||
"""
|
||||
return True
|
||||
|
||||
## Get whether the container item is stored on a read only location in the filesystem.
|
||||
#
|
||||
# \return Always returns False
|
||||
def isReadOnly(self):
|
||||
"""Get whether the container item is stored on a read only location in the filesystem.
|
||||
|
||||
:return: Always returns False
|
||||
"""
|
||||
|
||||
return False
|
||||
|
||||
## Mock get path
|
||||
def getPath(self):
|
||||
"""Mock get path"""
|
||||
|
||||
return "/path/to/the/light/side"
|
||||
|
||||
## Mock set path
|
||||
def setPath(self, path):
|
||||
"""Mock set path"""
|
||||
|
||||
pass
|
||||
|
||||
def getAllKeys(self):
|
||||
@ -91,31 +108,38 @@ class MockContainer(ContainerInterface, UM.PluginObject.PluginObject):
|
||||
|
||||
return None
|
||||
|
||||
## Get the value of a container item.
|
||||
#
|
||||
# Since this mock container cannot contain any items, it always returns
|
||||
# None.
|
||||
#
|
||||
# \return Always returns None.
|
||||
def getValue(self, key):
|
||||
"""Get the value of a container item.
|
||||
|
||||
Since this mock container cannot contain any items, it always returns None.
|
||||
|
||||
:return: Always returns None.
|
||||
"""
|
||||
|
||||
pass
|
||||
|
||||
## Get whether the container item has a specific property.
|
||||
#
|
||||
# This method is not implemented in the mock container.
|
||||
def hasProperty(self, key, property_name):
|
||||
"""Get whether the container item has a specific property.
|
||||
|
||||
This method is not implemented in the mock container.
|
||||
"""
|
||||
|
||||
return key in self.items
|
||||
|
||||
## Serializes the container to a string representation.
|
||||
#
|
||||
# This method is not implemented in the mock container.
|
||||
def serialize(self, ignored_metadata_keys = None):
|
||||
"""Serializes the container to a string representation.
|
||||
|
||||
This method is not implemented in the mock container.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
## Deserializes the container from a string representation.
|
||||
#
|
||||
# This method is not implemented in the mock container.
|
||||
def deserialize(self, serialized, file_name: Optional[str] = None):
|
||||
"""Deserializes the container from a string representation.
|
||||
|
||||
This method is not implemented in the mock container.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
|
@ -42,8 +42,9 @@ def test_createUniqueName(container_registry):
|
||||
assert container_registry.createUniqueName("user", "test", "", "nope") == "nope"
|
||||
|
||||
|
||||
## Tests whether addContainer properly converts to ExtruderStack.
|
||||
def test_addContainerExtruderStack(container_registry, definition_container, definition_changes_container):
|
||||
"""Tests whether addContainer properly converts to ExtruderStack."""
|
||||
|
||||
container_registry.addContainer(definition_container)
|
||||
container_registry.addContainer(definition_changes_container)
|
||||
|
||||
@ -61,8 +62,9 @@ def test_addContainerExtruderStack(container_registry, definition_container, def
|
||||
assert type(mock_super_add_container.call_args_list[0][0][0]) == ExtruderStack
|
||||
|
||||
|
||||
## Tests whether addContainer properly converts to GlobalStack.
|
||||
def test_addContainerGlobalStack(container_registry, definition_container, definition_changes_container):
|
||||
"""Tests whether addContainer properly converts to GlobalStack."""
|
||||
|
||||
container_registry.addContainer(definition_container)
|
||||
container_registry.addContainer(definition_changes_container)
|
||||
|
||||
|
@ -57,9 +57,10 @@ def test_noCategory(file_path):
|
||||
metadata = DefinitionContainer.deserializeMetadata(json, "test_container_id")
|
||||
assert "category" not in metadata[0]
|
||||
|
||||
## Tests all definition containers
|
||||
@pytest.mark.parametrize("file_path", machine_filepaths)
|
||||
def test_validateMachineDefinitionContainer(file_path, definition_container):
|
||||
"""Tests all definition containers"""
|
||||
|
||||
file_name = os.path.basename(file_path)
|
||||
if file_name == "fdmprinter.def.json" or file_name == "fdmextruder.def.json":
|
||||
return # Stop checking, these are root files.
|
||||
@ -85,13 +86,15 @@ def assertIsDefinitionValid(definition_container, file_path):
|
||||
if "platform_texture" in metadata[0]:
|
||||
assert metadata[0]["platform_texture"] in all_images
|
||||
|
||||
## Tests whether setting values are not being hidden by parent containers.
|
||||
#
|
||||
# When a definition container defines a "default_value" but inherits from a
|
||||
# definition that defines a "value", the "default_value" is ineffective. This
|
||||
# test fails on those things.
|
||||
@pytest.mark.parametrize("file_path", definition_filepaths)
|
||||
def test_validateOverridingDefaultValue(file_path: str):
|
||||
"""Tests whether setting values are not being hidden by parent containers.
|
||||
|
||||
When a definition container defines a "default_value" but inherits from a
|
||||
definition that defines a "value", the "default_value" is ineffective. This
|
||||
test fails on those things.
|
||||
"""
|
||||
|
||||
with open(file_path, encoding = "utf-8") as f:
|
||||
doc = json.load(f)
|
||||
|
||||
@ -107,12 +110,14 @@ def test_validateOverridingDefaultValue(file_path: str):
|
||||
faulty_keys.add(key)
|
||||
assert not faulty_keys, "Unnecessary default_values for {faulty_keys} in {file_name}".format(faulty_keys = sorted(faulty_keys), file_name = file_path) # If there is a value in the parent settings, then the default_value is not effective.
|
||||
|
||||
## Get all settings and their properties from a definition we're inheriting
|
||||
# from.
|
||||
# \param definition_id The definition we're inheriting from.
|
||||
# \return A dictionary of settings by key. Each setting is a dictionary of
|
||||
# properties.
|
||||
|
||||
def getInheritedSettings(definition_id: str) -> Dict[str, Any]:
|
||||
"""Get all settings and their properties from a definition we're inheriting from.
|
||||
|
||||
:param definition_id: The definition we're inheriting from.
|
||||
:return: A dictionary of settings by key. Each setting is a dictionary of properties.
|
||||
"""
|
||||
|
||||
definition_path = os.path.join(os.path.dirname(__file__), "..", "..", "resources", "definitions", definition_id + ".def.json")
|
||||
with open(definition_path, encoding = "utf-8") as f:
|
||||
doc = json.load(f)
|
||||
@ -127,13 +132,15 @@ def getInheritedSettings(definition_id: str) -> Dict[str, Any]:
|
||||
|
||||
return result
|
||||
|
||||
## Put all settings in the main dictionary rather than in children dicts.
|
||||
# \param settings Nested settings. The keys are the setting IDs. The values
|
||||
# are dictionaries of properties per setting, including the "children"
|
||||
# property.
|
||||
# \return A dictionary of settings by key. Each setting is a dictionary of
|
||||
# properties.
|
||||
|
||||
def flattenSettings(settings: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Put all settings in the main dictionary rather than in children dicts.
|
||||
|
||||
:param settings: Nested settings. The keys are the setting IDs. The values
|
||||
are dictionaries of properties per setting, including the "children" property.
|
||||
:return: A dictionary of settings by key. Each setting is a dictionary of properties.
|
||||
"""
|
||||
|
||||
result = {}
|
||||
for entry, contents in settings.items():
|
||||
if "children" in contents:
|
||||
@ -142,12 +149,16 @@ def flattenSettings(settings: Dict[str, Any]) -> Dict[str, Any]:
|
||||
result[entry] = contents
|
||||
return result
|
||||
|
||||
## Make one dictionary override the other. Nested dictionaries override each
|
||||
# other in the same way.
|
||||
# \param base A dictionary of settings that will get overridden by the other.
|
||||
# \param overrides A dictionary of settings that will override the other.
|
||||
# \return Combined setting data.
|
||||
|
||||
def merge_dicts(base: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Make one dictionary override the other. Nested dictionaries override each
|
||||
|
||||
other in the same way.
|
||||
:param base: A dictionary of settings that will get overridden by the other.
|
||||
:param overrides: A dictionary of settings that will override the other.
|
||||
:return: Combined setting data.
|
||||
"""
|
||||
|
||||
result = {}
|
||||
result.update(base)
|
||||
for key, val in overrides.items():
|
||||
@ -161,21 +172,25 @@ def merge_dicts(base: Dict[str, Any], overrides: Dict[str, Any]) -> Dict[str, An
|
||||
result[key] = val
|
||||
return result
|
||||
|
||||
## Verifies that definition contains don't have an ID field.
|
||||
#
|
||||
# ID fields are legacy. They should not be used any more. This is legacy that
|
||||
# people don't seem to be able to get used to.
|
||||
|
||||
@pytest.mark.parametrize("file_path", definition_filepaths)
|
||||
def test_noId(file_path: str):
|
||||
"""Verifies that definition contains don't have an ID field.
|
||||
|
||||
ID fields are legacy. They should not be used any more. This is legacy that
|
||||
people don't seem to be able to get used to.
|
||||
"""
|
||||
|
||||
with open(file_path, encoding = "utf-8") as f:
|
||||
doc = json.load(f)
|
||||
|
||||
assert "id" not in doc, "Definitions should not have an ID field."
|
||||
|
||||
## Verifies that extruders say that they work on the same extruder_nr as what
|
||||
# is listed in their machine definition.
|
||||
|
||||
@pytest.mark.parametrize("file_path", extruder_filepaths)
|
||||
def test_extruderMatch(file_path: str):
|
||||
"""Verifies that extruders say that they work on the same extruder_nr as what is listed in their machine definition."""
|
||||
|
||||
extruder_id = os.path.basename(file_path).split(".")[0]
|
||||
with open(file_path, encoding = "utf-8") as f:
|
||||
doc = json.load(f)
|
||||
|
@ -14,11 +14,13 @@ from cura.Settings.Exceptions import InvalidContainerError, InvalidOperationErro
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from cura.Settings.cura_empty_instance_containers import empty_container
|
||||
|
||||
## Gets an instance container with a specified container type.
|
||||
#
|
||||
# \param container_type The type metadata for the instance container.
|
||||
# \return An instance container instance.
|
||||
def getInstanceContainer(container_type) -> InstanceContainer:
|
||||
"""Gets an instance container with a specified container type.
|
||||
|
||||
:param container_type: The type metadata for the instance container.
|
||||
:return: An instance container instance.
|
||||
"""
|
||||
|
||||
container = InstanceContainer(container_id = "InstanceContainer")
|
||||
container.setMetaDataEntry("type", container_type)
|
||||
return container
|
||||
@ -32,10 +34,12 @@ class InstanceContainerSubClass(InstanceContainer):
|
||||
super().__init__(container_id = "SubInstanceContainer")
|
||||
self.setMetaDataEntry("type", container_type)
|
||||
|
||||
#############################START OF TEST CASES################################
|
||||
############################START OF TEST CASES################################
|
||||
|
||||
|
||||
## Tests whether adding a container is properly forbidden.
|
||||
def test_addContainer(extruder_stack):
|
||||
"""Tests whether adding a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
extruder_stack.addContainer(unittest.mock.MagicMock())
|
||||
|
||||
@ -164,8 +168,10 @@ def test_constrainDefinitionInvalid(container, extruder_stack):
|
||||
def test_constrainDefinitionValid(container, extruder_stack):
|
||||
extruder_stack.definition = container #Should not give an error.
|
||||
|
||||
## Tests whether deserialising completes the missing containers with empty ones.
|
||||
|
||||
def test_deserializeCompletesEmptyContainers(extruder_stack):
|
||||
"""Tests whether deserialising completes the missing containers with empty ones."""
|
||||
|
||||
extruder_stack._containers = [DefinitionContainer(container_id = "definition"), extruder_stack.definitionChanges] #Set the internal state of this stack manually.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
@ -179,8 +185,10 @@ def test_deserializeCompletesEmptyContainers(extruder_stack):
|
||||
continue
|
||||
assert extruder_stack.getContainer(container_type_index) == empty_container #All others need to be empty.
|
||||
|
||||
## Tests whether an instance container with the wrong type gets removed when deserialising.
|
||||
|
||||
def test_deserializeRemovesWrongInstanceContainer(extruder_stack):
|
||||
"""Tests whether an instance container with the wrong type gets removed when deserialising."""
|
||||
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type")
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -189,8 +197,10 @@ def test_deserializeRemovesWrongInstanceContainer(extruder_stack):
|
||||
|
||||
assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty.
|
||||
|
||||
## Tests whether a container with the wrong class gets removed when deserialising.
|
||||
|
||||
def test_deserializeRemovesWrongContainerClass(extruder_stack):
|
||||
"""Tests whether a container with the wrong class gets removed when deserialising."""
|
||||
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class")
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -199,16 +209,20 @@ def test_deserializeRemovesWrongContainerClass(extruder_stack):
|
||||
|
||||
assert extruder_stack.quality == extruder_stack._empty_instance_container #Replaced with empty.
|
||||
|
||||
## Tests whether an instance container in the definition spot results in an error.
|
||||
|
||||
def test_deserializeWrongDefinitionClass(extruder_stack):
|
||||
"""Tests whether an instance container in the definition spot results in an error."""
|
||||
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
with pytest.raises(UM.Settings.ContainerStack.InvalidContainerStackError): #Must raise an error that there is no definition container.
|
||||
extruder_stack.deserialize("")
|
||||
|
||||
## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising.
|
||||
|
||||
def test_deserializeMoveInstanceContainer(extruder_stack):
|
||||
"""Tests whether an instance container with the wrong type is moved into the correct slot by deserialising."""
|
||||
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot.
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -218,8 +232,10 @@ def test_deserializeMoveInstanceContainer(extruder_stack):
|
||||
assert extruder_stack.quality == empty_container
|
||||
assert extruder_stack.material != empty_container
|
||||
|
||||
## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising.
|
||||
|
||||
def test_deserializeMoveDefinitionContainer(extruder_stack):
|
||||
"""Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising."""
|
||||
|
||||
extruder_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
@ -228,8 +244,10 @@ def test_deserializeMoveDefinitionContainer(extruder_stack):
|
||||
assert extruder_stack.material == empty_container
|
||||
assert extruder_stack.definition != empty_container
|
||||
|
||||
## Tests whether getProperty properly applies the stack-like behaviour on its containers.
|
||||
|
||||
def test_getPropertyFallThrough(global_stack, extruder_stack):
|
||||
"""Tests whether getProperty properly applies the stack-like behaviour on its containers."""
|
||||
|
||||
# ExtruderStack.setNextStack calls registerExtruder for backward compatibility, but we do not need a complete extruder manager
|
||||
ExtruderManager._ExtruderManager__instance = unittest.mock.MagicMock()
|
||||
|
||||
@ -273,13 +291,17 @@ def test_getPropertyFallThrough(global_stack, extruder_stack):
|
||||
extruder_stack.userChanges = mock_layer_heights[container_indices.UserChanges]
|
||||
assert extruder_stack.getProperty("layer_height", "value") == container_indices.UserChanges
|
||||
|
||||
## Tests whether inserting a container is properly forbidden.
|
||||
|
||||
def test_insertContainer(extruder_stack):
|
||||
"""Tests whether inserting a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
extruder_stack.insertContainer(0, unittest.mock.MagicMock())
|
||||
|
||||
## Tests whether removing a container is properly forbidden.
|
||||
|
||||
def test_removeContainer(extruder_stack):
|
||||
"""Tests whether removing a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
extruder_stack.removeContainer(unittest.mock.MagicMock())
|
||||
|
||||
|
@ -16,11 +16,13 @@ import UM.Settings.SettingDefinition #To add settings to the definition.
|
||||
from cura.Settings.cura_empty_instance_containers import empty_container
|
||||
|
||||
|
||||
## Gets an instance container with a specified container type.
|
||||
#
|
||||
# \param container_type The type metadata for the instance container.
|
||||
# \return An instance container instance.
|
||||
def getInstanceContainer(container_type) -> InstanceContainer:
|
||||
"""Gets an instance container with a specified container type.
|
||||
|
||||
:param container_type: The type metadata for the instance container.
|
||||
:return: An instance container instance.
|
||||
"""
|
||||
|
||||
container = InstanceContainer(container_id = "InstanceContainer")
|
||||
container.setMetaDataEntry("type", container_type)
|
||||
return container
|
||||
@ -37,17 +39,19 @@ class InstanceContainerSubClass(InstanceContainer):
|
||||
self.setMetaDataEntry("type", container_type)
|
||||
|
||||
|
||||
#############################START OF TEST CASES################################
|
||||
############################START OF TEST CASES################################
|
||||
|
||||
|
||||
## Tests whether adding a container is properly forbidden.
|
||||
def test_addContainer(global_stack):
|
||||
"""Tests whether adding a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
global_stack.addContainer(unittest.mock.MagicMock())
|
||||
|
||||
|
||||
## Tests adding extruders to the global stack.
|
||||
def test_addExtruder(global_stack):
|
||||
"""Tests adding extruders to the global stack."""
|
||||
|
||||
mock_definition = unittest.mock.MagicMock()
|
||||
mock_definition.getProperty = lambda key, property, context = None: 2 if key == "machine_extruder_count" and property == "value" else None
|
||||
|
||||
@ -213,9 +217,12 @@ def test_constrainDefinitionValid(container, global_stack):
|
||||
global_stack.definition = container #Should not give an error.
|
||||
|
||||
|
||||
## Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the
|
||||
# definition and the definition_changes (that cannot be empty after CURA-5281)
|
||||
def test_deserializeCompletesEmptyContainers(global_stack):
|
||||
"""Tests whether deserialising completes the missing containers with empty ones. The initial containers are just the
|
||||
|
||||
definition and the definition_changes (that cannot be empty after CURA-5281)
|
||||
"""
|
||||
|
||||
global_stack._containers = [DefinitionContainer(container_id = "definition"), global_stack.definitionChanges] #Set the internal state of this stack manually.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
@ -229,8 +236,9 @@ def test_deserializeCompletesEmptyContainers(global_stack):
|
||||
assert global_stack.getContainer(container_type_index) == empty_container #All others need to be empty.
|
||||
|
||||
|
||||
## Tests whether an instance container with the wrong type gets removed when deserialising.
|
||||
def test_deserializeRemovesWrongInstanceContainer(global_stack):
|
||||
"""Tests whether an instance container with the wrong type gets removed when deserialising."""
|
||||
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "wrong type")
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -240,8 +248,9 @@ def test_deserializeRemovesWrongInstanceContainer(global_stack):
|
||||
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
|
||||
|
||||
|
||||
## Tests whether a container with the wrong class gets removed when deserialising.
|
||||
def test_deserializeRemovesWrongContainerClass(global_stack):
|
||||
"""Tests whether a container with the wrong class gets removed when deserialising."""
|
||||
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = DefinitionContainer(container_id = "wrong class")
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -251,8 +260,9 @@ def test_deserializeRemovesWrongContainerClass(global_stack):
|
||||
assert global_stack.quality == global_stack._empty_instance_container #Replaced with empty.
|
||||
|
||||
|
||||
## Tests whether an instance container in the definition spot results in an error.
|
||||
def test_deserializeWrongDefinitionClass(global_stack):
|
||||
"""Tests whether an instance container in the definition spot results in an error."""
|
||||
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = getInstanceContainer(container_type = "definition") #Correct type but wrong class.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
@ -260,8 +270,9 @@ def test_deserializeWrongDefinitionClass(global_stack):
|
||||
global_stack.deserialize("")
|
||||
|
||||
|
||||
## Tests whether an instance container with the wrong type is moved into the correct slot by deserialising.
|
||||
def test_deserializeMoveInstanceContainer(global_stack):
|
||||
"""Tests whether an instance container with the wrong type is moved into the correct slot by deserialising."""
|
||||
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Quality] = getInstanceContainer(container_type = "material") #Not in the correct spot.
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Definition] = DefinitionContainer(container_id = "some definition")
|
||||
|
||||
@ -272,8 +283,9 @@ def test_deserializeMoveInstanceContainer(global_stack):
|
||||
assert global_stack.material != empty_container
|
||||
|
||||
|
||||
## Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising.
|
||||
def test_deserializeMoveDefinitionContainer(global_stack):
|
||||
"""Tests whether a definition container in the wrong spot is moved into the correct spot by deserialising."""
|
||||
|
||||
global_stack._containers[cura.Settings.CuraContainerStack._ContainerIndexes.Material] = DefinitionContainer(container_id = "some definition") #Not in the correct spot.
|
||||
|
||||
with unittest.mock.patch("UM.Settings.ContainerStack.ContainerStack.deserialize", unittest.mock.MagicMock()): #Prevent calling super().deserialize.
|
||||
@ -283,8 +295,9 @@ def test_deserializeMoveDefinitionContainer(global_stack):
|
||||
assert global_stack.definition != empty_container
|
||||
|
||||
|
||||
## Tests whether getProperty properly applies the stack-like behaviour on its containers.
|
||||
def test_getPropertyFallThrough(global_stack):
|
||||
"""Tests whether getProperty properly applies the stack-like behaviour on its containers."""
|
||||
|
||||
#A few instance container mocks to put in the stack.
|
||||
mock_layer_heights = {} #For each container type, a mock container that defines layer height to something unique.
|
||||
mock_no_settings = {} #For each container type, a mock container that has no settings at all.
|
||||
@ -326,8 +339,9 @@ def test_getPropertyFallThrough(global_stack):
|
||||
assert global_stack.getProperty("layer_height", "value") == container_indexes.UserChanges
|
||||
|
||||
|
||||
## In definitions, test whether having no resolve allows us to find the value.
|
||||
def test_getPropertyNoResolveInDefinition(global_stack):
|
||||
"""In definitions, test whether having no resolve allows us to find the value."""
|
||||
|
||||
value = unittest.mock.MagicMock() #Just sets the value for bed temperature.
|
||||
value.getProperty = lambda key, property, context = None: 10 if (key == "material_bed_temperature" and property == "value") else None
|
||||
|
||||
@ -336,8 +350,9 @@ def test_getPropertyNoResolveInDefinition(global_stack):
|
||||
assert global_stack.getProperty("material_bed_temperature", "value") == 10 #No resolve, so fall through to value.
|
||||
|
||||
|
||||
## In definitions, when the value is asked and there is a resolve function, it must get the resolve first.
|
||||
def test_getPropertyResolveInDefinition(global_stack):
|
||||
"""In definitions, when the value is asked and there is a resolve function, it must get the resolve first."""
|
||||
|
||||
resolve_and_value = unittest.mock.MagicMock() #Sets the resolve and value for bed temperature.
|
||||
resolve_and_value.getProperty = lambda key, property, context = None: (7.5 if property == "resolve" else 5) if (key == "material_bed_temperature" and property in ("resolve", "value")) else None #7.5 resolve, 5 value.
|
||||
|
||||
@ -346,8 +361,9 @@ def test_getPropertyResolveInDefinition(global_stack):
|
||||
assert global_stack.getProperty("material_bed_temperature", "value") == 7.5 #Resolve wins in the definition.
|
||||
|
||||
|
||||
## In instance containers, when the value is asked and there is a resolve function, it must get the value first.
|
||||
def test_getPropertyResolveInInstance(global_stack):
|
||||
"""In instance containers, when the value is asked and there is a resolve function, it must get the value first."""
|
||||
|
||||
container_indices = cura.Settings.CuraContainerStack._ContainerIndexes
|
||||
instance_containers = {}
|
||||
for container_type in container_indices.IndexTypeMap:
|
||||
@ -373,8 +389,9 @@ def test_getPropertyResolveInInstance(global_stack):
|
||||
assert global_stack.getProperty("material_bed_temperature", "value") == 5
|
||||
|
||||
|
||||
## Tests whether the value in instances gets evaluated before the resolve in definitions.
|
||||
def test_getPropertyInstancesBeforeResolve(global_stack):
|
||||
"""Tests whether the value in instances gets evaluated before the resolve in definitions."""
|
||||
|
||||
def getValueProperty(key, property, context = None):
|
||||
if key != "material_bed_temperature":
|
||||
return None
|
||||
@ -404,8 +421,9 @@ def test_getPropertyInstancesBeforeResolve(global_stack):
|
||||
assert global_stack.getProperty("material_bed_temperature", "value") == 10
|
||||
|
||||
|
||||
## Tests whether the hasUserValue returns true for settings that are changed in the user-changes container.
|
||||
def test_hasUserValueUserChanges(global_stack):
|
||||
"""Tests whether the hasUserValue returns true for settings that are changed in the user-changes container."""
|
||||
|
||||
container = unittest.mock.MagicMock()
|
||||
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "user")
|
||||
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||
@ -416,8 +434,9 @@ def test_hasUserValueUserChanges(global_stack):
|
||||
assert not global_stack.hasUserValue("")
|
||||
|
||||
|
||||
## Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container.
|
||||
def test_hasUserValueQualityChanges(global_stack):
|
||||
"""Tests whether the hasUserValue returns true for settings that are changed in the quality-changes container."""
|
||||
|
||||
container = unittest.mock.MagicMock()
|
||||
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality_changes")
|
||||
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||
@ -428,8 +447,9 @@ def test_hasUserValueQualityChanges(global_stack):
|
||||
assert not global_stack.hasUserValue("")
|
||||
|
||||
|
||||
## Tests whether a container in some other place on the stack is correctly not recognised as user value.
|
||||
def test_hasNoUserValue(global_stack):
|
||||
"""Tests whether a container in some other place on the stack is correctly not recognised as user value."""
|
||||
|
||||
container = unittest.mock.MagicMock()
|
||||
container.getMetaDataEntry = unittest.mock.MagicMock(return_value = "quality")
|
||||
container.hasProperty = lambda key, property: key == "layer_height" #Only have the layer_height property set.
|
||||
@ -438,20 +458,23 @@ def test_hasNoUserValue(global_stack):
|
||||
assert not global_stack.hasUserValue("layer_height") #However this container is quality, so it's not a user value.
|
||||
|
||||
|
||||
## Tests whether inserting a container is properly forbidden.
|
||||
def test_insertContainer(global_stack):
|
||||
"""Tests whether inserting a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
global_stack.insertContainer(0, unittest.mock.MagicMock())
|
||||
|
||||
|
||||
## Tests whether removing a container is properly forbidden.
|
||||
def test_removeContainer(global_stack):
|
||||
"""Tests whether removing a container is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
global_stack.removeContainer(unittest.mock.MagicMock())
|
||||
|
||||
|
||||
## Tests whether changing the next stack is properly forbidden.
|
||||
def test_setNextStack(global_stack):
|
||||
"""Tests whether changing the next stack is properly forbidden."""
|
||||
|
||||
with pytest.raises(InvalidOperationError):
|
||||
global_stack.setNextStack(unittest.mock.MagicMock())
|
||||
|
||||
|
@ -61,9 +61,10 @@ variant_filepaths = collectAllVariants()
|
||||
intent_filepaths = collectAllIntents()
|
||||
|
||||
|
||||
## Attempt to load all the quality profiles.
|
||||
@pytest.mark.parametrize("file_name", quality_filepaths)
|
||||
def test_validateQualityProfiles(file_name):
|
||||
"""Attempt to load all the quality profiles."""
|
||||
|
||||
try:
|
||||
with open(file_name, encoding = "utf-8") as data:
|
||||
serialized = data.read()
|
||||
@ -114,9 +115,10 @@ def test_validateIntentProfiles(file_name):
|
||||
# File can't be read, header sections missing, whatever the case, this shouldn't happen!
|
||||
assert False, "Got an exception while reading the file {file_name}: {err}".format(file_name = file_name, err = str(e))
|
||||
|
||||
## Attempt to load all the variant profiles.
|
||||
@pytest.mark.parametrize("file_name", variant_filepaths)
|
||||
def test_validateVariantProfiles(file_name):
|
||||
"""Attempt to load all the variant profiles."""
|
||||
|
||||
try:
|
||||
with open(file_name, encoding = "utf-8") as data:
|
||||
serialized = data.read()
|
||||
|
@ -6,36 +6,43 @@ import numpy
|
||||
from cura.Arranging.Arrange import Arrange
|
||||
from cura.Arranging.ShapeArray import ShapeArray
|
||||
|
||||
## Triangle of area 12
|
||||
def gimmeTriangle():
|
||||
"""Triangle of area 12"""
|
||||
|
||||
return numpy.array([[-3, 1], [3, 1], [0, -3]], dtype=numpy.int32)
|
||||
|
||||
## Boring square
|
||||
def gimmeSquare():
|
||||
"""Boring square"""
|
||||
|
||||
return numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32)
|
||||
|
||||
## Triangle of area 12
|
||||
def gimmeShapeArray(scale = 1.0):
|
||||
"""Triangle of area 12"""
|
||||
|
||||
vertices = gimmeTriangle()
|
||||
shape_arr = ShapeArray.fromPolygon(vertices, scale = scale)
|
||||
return shape_arr
|
||||
|
||||
## Boring square
|
||||
def gimmeShapeArraySquare(scale = 1.0):
|
||||
"""Boring square"""
|
||||
|
||||
vertices = gimmeSquare()
|
||||
shape_arr = ShapeArray.fromPolygon(vertices, scale = scale)
|
||||
return shape_arr
|
||||
|
||||
## Smoke test for Arrange
|
||||
def test_smoke_arrange():
|
||||
"""Smoke test for Arrange"""
|
||||
|
||||
Arrange.create(fixed_nodes = [])
|
||||
|
||||
## Smoke test for ShapeArray
|
||||
def test_smoke_ShapeArray():
|
||||
"""Smoke test for ShapeArray"""
|
||||
|
||||
gimmeShapeArray()
|
||||
|
||||
## Test ShapeArray
|
||||
def test_ShapeArray():
|
||||
"""Test ShapeArray"""
|
||||
|
||||
scale = 1
|
||||
ar = Arrange(16, 16, 8, 8, scale = scale)
|
||||
ar.centerFirst()
|
||||
@ -44,8 +51,9 @@ def test_ShapeArray():
|
||||
count = len(numpy.where(shape_arr.arr == 1)[0])
|
||||
assert count >= 10 # should approach 12
|
||||
|
||||
## Test ShapeArray with scaling
|
||||
def test_ShapeArray_scaling():
|
||||
"""Test ShapeArray with scaling"""
|
||||
|
||||
scale = 2
|
||||
ar = Arrange(16, 16, 8, 8, scale = scale)
|
||||
ar.centerFirst()
|
||||
@ -54,8 +62,9 @@ def test_ShapeArray_scaling():
|
||||
count = len(numpy.where(shape_arr.arr == 1)[0])
|
||||
assert count >= 40 # should approach 2*2*12 = 48
|
||||
|
||||
## Test ShapeArray with scaling
|
||||
def test_ShapeArray_scaling2():
|
||||
"""Test ShapeArray with scaling"""
|
||||
|
||||
scale = 0.5
|
||||
ar = Arrange(16, 16, 8, 8, scale = scale)
|
||||
ar.centerFirst()
|
||||
@ -64,8 +73,9 @@ def test_ShapeArray_scaling2():
|
||||
count = len(numpy.where(shape_arr.arr == 1)[0])
|
||||
assert count >= 1 # should approach 3, but it can be inaccurate due to pixel rounding
|
||||
|
||||
## Test centerFirst
|
||||
def test_centerFirst():
|
||||
"""Test centerFirst"""
|
||||
|
||||
ar = Arrange(300, 300, 150, 150, scale = 1)
|
||||
ar.centerFirst()
|
||||
assert ar._priority[150][150] < ar._priority[170][150]
|
||||
@ -75,8 +85,9 @@ def test_centerFirst():
|
||||
assert ar._priority[150][150] < ar._priority[150][130]
|
||||
assert ar._priority[150][150] < ar._priority[130][130]
|
||||
|
||||
## Test centerFirst
|
||||
def test_centerFirst_rectangular():
|
||||
"""Test centerFirst"""
|
||||
|
||||
ar = Arrange(400, 300, 200, 150, scale = 1)
|
||||
ar.centerFirst()
|
||||
assert ar._priority[150][200] < ar._priority[150][220]
|
||||
@ -86,15 +97,17 @@ def test_centerFirst_rectangular():
|
||||
assert ar._priority[150][200] < ar._priority[130][200]
|
||||
assert ar._priority[150][200] < ar._priority[130][180]
|
||||
|
||||
## Test centerFirst
|
||||
def test_centerFirst_rectangular2():
|
||||
"""Test centerFirst"""
|
||||
|
||||
ar = Arrange(10, 20, 5, 10, scale = 1)
|
||||
ar.centerFirst()
|
||||
assert ar._priority[10][5] < ar._priority[10][7]
|
||||
|
||||
|
||||
## Test backFirst
|
||||
def test_backFirst():
|
||||
"""Test backFirst"""
|
||||
|
||||
ar = Arrange(300, 300, 150, 150, scale = 1)
|
||||
ar.backFirst()
|
||||
assert ar._priority[150][150] < ar._priority[170][150]
|
||||
@ -102,8 +115,9 @@ def test_backFirst():
|
||||
assert ar._priority[150][150] > ar._priority[130][150]
|
||||
assert ar._priority[150][150] > ar._priority[130][130]
|
||||
|
||||
## See if the result of bestSpot has the correct form
|
||||
def test_smoke_bestSpot():
|
||||
"""See if the result of bestSpot has the correct form"""
|
||||
|
||||
ar = Arrange(30, 30, 15, 15, scale = 1)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -114,8 +128,9 @@ def test_smoke_bestSpot():
|
||||
assert hasattr(best_spot, "penalty_points")
|
||||
assert hasattr(best_spot, "priority")
|
||||
|
||||
## Real life test
|
||||
def test_bestSpot():
|
||||
"""Real life test"""
|
||||
|
||||
ar = Arrange(16, 16, 8, 8, scale = 1)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -131,8 +146,9 @@ def test_bestSpot():
|
||||
assert best_spot.x != 0 or best_spot.y != 0 # it can't be on the same location
|
||||
ar.place(best_spot.x, best_spot.y, shape_arr)
|
||||
|
||||
## Real life test rectangular build plate
|
||||
def test_bestSpot_rectangular_build_plate():
|
||||
"""Real life test rectangular build plate"""
|
||||
|
||||
ar = Arrange(16, 40, 8, 20, scale = 1)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -164,8 +180,9 @@ def test_bestSpot_rectangular_build_plate():
|
||||
best_spot_x = ar.bestSpot(shape_arr)
|
||||
ar.place(best_spot_x.x, best_spot_x.y, shape_arr)
|
||||
|
||||
## Real life test
|
||||
def test_bestSpot_scale():
|
||||
"""Real life test"""
|
||||
|
||||
scale = 0.5
|
||||
ar = Arrange(16, 16, 8, 8, scale = scale)
|
||||
ar.centerFirst()
|
||||
@ -182,8 +199,9 @@ def test_bestSpot_scale():
|
||||
assert best_spot.x != 0 or best_spot.y != 0 # it can't be on the same location
|
||||
ar.place(best_spot.x, best_spot.y, shape_arr)
|
||||
|
||||
## Real life test
|
||||
def test_bestSpot_scale_rectangular():
|
||||
"""Real life test"""
|
||||
|
||||
scale = 0.5
|
||||
ar = Arrange(16, 40, 8, 20, scale = scale)
|
||||
ar.centerFirst()
|
||||
@ -205,8 +223,9 @@ def test_bestSpot_scale_rectangular():
|
||||
best_spot = ar.bestSpot(shape_arr_square)
|
||||
ar.place(best_spot.x, best_spot.y, shape_arr_square)
|
||||
|
||||
## Try to place an object and see if something explodes
|
||||
def test_smoke_place():
|
||||
"""Try to place an object and see if something explodes"""
|
||||
|
||||
ar = Arrange(30, 30, 15, 15)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -216,8 +235,9 @@ def test_smoke_place():
|
||||
ar.place(0, 0, shape_arr)
|
||||
assert numpy.any(ar._occupied)
|
||||
|
||||
## See of our center has less penalty points than out of the center
|
||||
def test_checkShape():
|
||||
"""See of our center has less penalty points than out of the center"""
|
||||
|
||||
ar = Arrange(30, 30, 15, 15)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -228,8 +248,9 @@ def test_checkShape():
|
||||
assert points2 > points
|
||||
assert points3 > points
|
||||
|
||||
## See of our center has less penalty points than out of the center
|
||||
def test_checkShape_rectangular():
|
||||
"""See of our center has less penalty points than out of the center"""
|
||||
|
||||
ar = Arrange(20, 30, 10, 15)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -240,8 +261,9 @@ def test_checkShape_rectangular():
|
||||
assert points2 > points
|
||||
assert points3 > points
|
||||
|
||||
## Check that placing an object on occupied place returns None.
|
||||
def test_checkShape_place():
|
||||
"""Check that placing an object on occupied place returns None."""
|
||||
|
||||
ar = Arrange(30, 30, 15, 15)
|
||||
ar.centerFirst()
|
||||
|
||||
@ -252,8 +274,9 @@ def test_checkShape_place():
|
||||
|
||||
assert points2 is None
|
||||
|
||||
## Test the whole sequence
|
||||
def test_smoke_place_objects():
|
||||
"""Test the whole sequence"""
|
||||
|
||||
ar = Arrange(20, 20, 10, 10, scale = 1)
|
||||
ar.centerFirst()
|
||||
shape_arr = gimmeShapeArray()
|
||||
@ -268,26 +291,30 @@ def test_compare_occupied_and_priority_tables():
|
||||
ar.centerFirst()
|
||||
assert ar._priority.shape == ar._occupied.shape
|
||||
|
||||
## Polygon -> array
|
||||
def test_arrayFromPolygon():
|
||||
"""Polygon -> array"""
|
||||
|
||||
vertices = numpy.array([[-3, 1], [3, 1], [0, -3]])
|
||||
array = ShapeArray.arrayFromPolygon([5, 5], vertices)
|
||||
assert numpy.any(array)
|
||||
|
||||
## Polygon -> array
|
||||
def test_arrayFromPolygon2():
|
||||
"""Polygon -> array"""
|
||||
|
||||
vertices = numpy.array([[-3, 1], [3, 1], [2, -3]])
|
||||
array = ShapeArray.arrayFromPolygon([5, 5], vertices)
|
||||
assert numpy.any(array)
|
||||
|
||||
## Polygon -> array
|
||||
def test_fromPolygon():
|
||||
"""Polygon -> array"""
|
||||
|
||||
vertices = numpy.array([[0, 0.5], [0, 0], [0.5, 0]])
|
||||
array = ShapeArray.fromPolygon(vertices, scale=0.5)
|
||||
assert numpy.any(array.arr)
|
||||
|
||||
## Line definition -> array with true/false
|
||||
def test_check():
|
||||
"""Line definition -> array with true/false"""
|
||||
|
||||
base_array = numpy.zeros([5, 5], dtype=float)
|
||||
p1 = numpy.array([0, 0])
|
||||
p2 = numpy.array([4, 4])
|
||||
@ -296,8 +323,9 @@ def test_check():
|
||||
assert check_array[3][0]
|
||||
assert not check_array[0][3]
|
||||
|
||||
## Line definition -> array with true/false
|
||||
def test_check2():
|
||||
"""Line definition -> array with true/false"""
|
||||
|
||||
base_array = numpy.zeros([5, 5], dtype=float)
|
||||
p1 = numpy.array([0, 3])
|
||||
p2 = numpy.array([4, 3])
|
||||
@ -306,8 +334,9 @@ def test_check2():
|
||||
assert not check_array[3][0]
|
||||
assert check_array[3][4]
|
||||
|
||||
## Just adding some stuff to ensure fromNode works as expected. Some parts should actually be in UM
|
||||
def test_parts_of_fromNode():
|
||||
"""Just adding some stuff to ensure fromNode works as expected. Some parts should actually be in UM"""
|
||||
|
||||
from UM.Math.Polygon import Polygon
|
||||
p = Polygon(numpy.array([[-2, -2], [2, -2], [2, 2], [-2, 2]], dtype=numpy.int32))
|
||||
offset = 1
|
||||
|
Loading…
x
Reference in New Issue
Block a user