Converted comments in dir Cura/cura to rst style

Converted doxygen style comments to reStructuredText style in the files found in Cura/cura directory using the script dox_2_rst.py (provided in the Uranium repo). Comments were manually checked and changed if needed.
This commit is contained in:
Jelle Spijker 2020-04-21 16:58:45 +02:00 committed by Jelle Spijker
parent fb4aec96a8
commit 6aedab78dc
No known key found for this signature in database
GPG Key ID: 6662DC033BE6B99A
15 changed files with 371 additions and 208 deletions

View File

@ -44,8 +44,9 @@ catalog = i18nCatalog("cura")
PRIME_CLEARANCE = 6.5 PRIME_CLEARANCE = 6.5
## Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas.
class BuildVolume(SceneNode): class BuildVolume(SceneNode):
"""Build volume is a special kind of node that is responsible for rendering the printable area & disallowed areas."""
raftThicknessChanged = Signal() raftThicknessChanged = Signal()
def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None: def __init__(self, application: "CuraApplication", parent: Optional[SceneNode] = None) -> None:
@ -163,10 +164,12 @@ class BuildVolume(SceneNode):
self._scene_objects = new_scene_objects self._scene_objects = new_scene_objects
self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered. self._onSettingPropertyChanged("print_sequence", "value") # Create fake event, so right settings are triggered.
## Updates the listeners that listen for changes in per-mesh stacks.
#
# \param node The node for which the decorators changed.
def _updateNodeListeners(self, node: SceneNode): def _updateNodeListeners(self, node: SceneNode):
"""Updates the listeners that listen for changes in per-mesh stacks.
:param node: The node for which the decorators changed.
"""
per_mesh_stack = node.callDecoration("getStack") per_mesh_stack = node.callDecoration("getStack")
if per_mesh_stack: if per_mesh_stack:
per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged) per_mesh_stack.propertyChanged.connect(self._onSettingPropertyChanged)
@ -187,10 +190,14 @@ class BuildVolume(SceneNode):
if shape: if shape:
self._shape = shape self._shape = shape
## Get the length of the 3D diagonal through the build volume.
#
# This gives a sense of the scale of the build volume in general.
def getDiagonalSize(self) -> float: def getDiagonalSize(self) -> float:
"""Get the length of the 3D diagonal through the build volume.
This gives a sense of the scale of the build volume in general.
:return: length of the 3D diagonal through the build volume
"""
return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth) return math.sqrt(self._width * self._width + self._height * self._height + self._depth * self._depth)
def getDisallowedAreas(self) -> List[Polygon]: def getDisallowedAreas(self) -> List[Polygon]:
@ -226,9 +233,9 @@ class BuildVolume(SceneNode):
return True return True
## For every sliceable node, update node._outside_buildarea
#
def updateNodeBoundaryCheck(self): def updateNodeBoundaryCheck(self):
"""For every sliceable node, update node._outside_buildarea"""
if not self._global_container_stack: if not self._global_container_stack:
return return
@ -295,8 +302,13 @@ class BuildVolume(SceneNode):
for child_node in children: for child_node in children:
child_node.setOutsideBuildArea(group_node.isOutsideBuildArea()) child_node.setOutsideBuildArea(group_node.isOutsideBuildArea())
## Update the outsideBuildArea of a single node, given bounds or current build volume
def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None) -> None: def checkBoundsAndUpdate(self, node: CuraSceneNode, bounds: Optional[AxisAlignedBox] = None) -> None:
"""Update the outsideBuildArea of a single node, given bounds or current build volume
:param node: single node
:param bounds: bounds or current build volume
"""
if not isinstance(node, CuraSceneNode) or self._global_container_stack is None: if not isinstance(node, CuraSceneNode) or self._global_container_stack is None:
return return
@ -484,8 +496,9 @@ class BuildVolume(SceneNode):
self._disallowed_area_size = max(size, self._disallowed_area_size) self._disallowed_area_size = max(size, self._disallowed_area_size)
return mb.build() return mb.build()
## Recalculates the build volume & disallowed areas.
def rebuild(self) -> None: def rebuild(self) -> None:
"""Recalculates the build volume & disallowed areas."""
if not self._width or not self._height or not self._depth: if not self._width or not self._height or not self._depth:
return return
@ -586,8 +599,9 @@ class BuildVolume(SceneNode):
def _onStackChanged(self): def _onStackChanged(self):
self._stack_change_timer.start() self._stack_change_timer.start()
## Update the build volume visualization
def _onStackChangeTimerFinished(self) -> None: def _onStackChangeTimerFinished(self) -> None:
"""Update the build volume visualization"""
if self._global_container_stack: if self._global_container_stack:
self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged) self._global_container_stack.propertyChanged.disconnect(self._onSettingPropertyChanged)
extruders = ExtruderManager.getInstance().getActiveExtruderStacks() extruders = ExtruderManager.getInstance().getActiveExtruderStacks()
@ -712,15 +726,15 @@ class BuildVolume(SceneNode):
self._depth = self._global_container_stack.getProperty("machine_depth", "value") self._depth = self._global_container_stack.getProperty("machine_depth", "value")
self._shape = self._global_container_stack.getProperty("machine_shape", "value") self._shape = self._global_container_stack.getProperty("machine_shape", "value")
## Calls _updateDisallowedAreas and makes sure the changes appear in the
# scene.
#
# This is required for a signal to trigger the update in one go. The
# ``_updateDisallowedAreas`` method itself shouldn't call ``rebuild``,
# since there may be other changes before it needs to be rebuilt, which
# would hit performance.
def _updateDisallowedAreasAndRebuild(self): def _updateDisallowedAreasAndRebuild(self):
"""Calls :py:meth:`cura.BuildVolume._updateDisallowedAreas` and makes sure the changes appear in the scene.
This is required for a signal to trigger the update in one go. The
:py:meth:`cura.BuildVolume._updateDisallowedAreas` method itself shouldn't call
:py:meth:`cura.BuildVolume.rebuild`, since there may be other changes before it needs to be rebuilt,
which would hit performance.
"""
self._updateDisallowedAreas() self._updateDisallowedAreas()
self._updateRaftThickness() self._updateRaftThickness()
self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks()) self._extra_z_clearance = self._calculateExtraZClearance(ExtruderManager.getInstance().getUsedExtruderStacks())
@ -782,15 +796,14 @@ class BuildVolume(SceneNode):
for extruder_id in result_areas_no_brim: for extruder_id in result_areas_no_brim:
self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id]) self._disallowed_areas_no_brim.extend(result_areas_no_brim[extruder_id])
## Computes the disallowed areas for objects that are printed with print
# features.
#
# This means that the brim, travel avoidance and such will be applied to
# these features.
#
# \return A dictionary with for each used extruder ID the disallowed areas
# where that extruder may not print.
def _computeDisallowedAreasPrinted(self, used_extruders): def _computeDisallowedAreasPrinted(self, used_extruders):
"""Computes the disallowed areas for objects that are printed with print features.
This means that the brim, travel avoidance and such will be applied to these features.
:return: A dictionary with for each used extruder ID the disallowed areas where that extruder may not print.
"""
result = {} result = {}
adhesion_extruder = None #type: ExtruderStack adhesion_extruder = None #type: ExtruderStack
for extruder in used_extruders: for extruder in used_extruders:
@ -828,18 +841,18 @@ class BuildVolume(SceneNode):
return result return result
## Computes the disallowed areas for the prime blobs.
#
# These are special because they are not subject to things like brim or
# travel avoidance. They do get a dilute with the border size though
# because they may not intersect with brims and such of other objects.
#
# \param border_size The size with which to offset the disallowed areas
# due to skirt, brim, travel avoid distance, etc.
# \param used_extruders The extruder stacks to generate disallowed areas
# for.
# \return A dictionary with for each used extruder ID the prime areas.
def _computeDisallowedAreasPrimeBlob(self, border_size: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]: def _computeDisallowedAreasPrimeBlob(self, border_size: float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
"""Computes the disallowed areas for the prime blobs.
These are special because they are not subject to things like brim or travel avoidance. They do get a dilute
with the border size though because they may not intersect with brims and such of other objects.
:param border_size: The size with which to offset the disallowed areas due to skirt, brim, travel avoid distance
, etc.
:param used_extruders: The extruder stacks to generate disallowed areas for.
:return: A dictionary with for each used extruder ID the prime areas.
"""
result = {} # type: Dict[str, List[Polygon]] result = {} # type: Dict[str, List[Polygon]]
if not self._global_container_stack: if not self._global_container_stack:
return result return result
@ -867,19 +880,18 @@ class BuildVolume(SceneNode):
return result return result
## Computes the disallowed areas that are statically placed in the machine.
#
# It computes different disallowed areas depending on the offset of the
# extruder. The resulting dictionary will therefore have an entry for each
# extruder that is used.
#
# \param border_size The size with which to offset the disallowed areas
# due to skirt, brim, travel avoid distance, etc.
# \param used_extruders The extruder stacks to generate disallowed areas
# for.
# \return A dictionary with for each used extruder ID the disallowed areas
# where that extruder may not print.
def _computeDisallowedAreasStatic(self, border_size:float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]: def _computeDisallowedAreasStatic(self, border_size:float, used_extruders: List["ExtruderStack"]) -> Dict[str, List[Polygon]]:
"""Computes the disallowed areas that are statically placed in the machine.
It computes different disallowed areas depending on the offset of the extruder. The resulting dictionary will
therefore have an entry for each extruder that is used.
:param border_size: The size with which to offset the disallowed areas due to skirt, brim, travel avoid distance
, etc.
:param used_extruders: The extruder stacks to generate disallowed areas for.
:return: A dictionary with for each used extruder ID the disallowed areas where that extruder may not print.
"""
# Convert disallowed areas to polygons and dilate them. # Convert disallowed areas to polygons and dilate them.
machine_disallowed_polygons = [] machine_disallowed_polygons = []
if self._global_container_stack is None: if self._global_container_stack is None:
@ -1010,13 +1022,14 @@ class BuildVolume(SceneNode):
return result return result
## Private convenience function to get a setting from every extruder.
#
# For single extrusion machines, this gets the setting from the global
# stack.
#
# \return A sequence of setting values, one for each extruder.
def _getSettingFromAllExtruders(self, setting_key: str) -> List[Any]: def _getSettingFromAllExtruders(self, setting_key: str) -> List[Any]:
"""Private convenience function to get a setting from every extruder.
For single extrusion machines, this gets the setting from the global stack.
:return: A sequence of setting values, one for each extruder.
"""
all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value") all_values = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "value")
all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type") all_types = ExtruderManager.getInstance().getAllExtruderSettings(setting_key, "type")
for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)): for i, (setting_value, setting_type) in enumerate(zip(all_values, all_types)):
@ -1101,12 +1114,13 @@ class BuildVolume(SceneNode):
return move_from_wall_radius return move_from_wall_radius
## Calculate the disallowed radius around the edge.
#
# This disallowed radius is to allow for space around the models that is
# not part of the collision radius, such as bed adhesion (skirt/brim/raft)
# and travel avoid distance.
def getEdgeDisallowedSize(self): def getEdgeDisallowedSize(self):
"""Calculate the disallowed radius around the edge.
This disallowed radius is to allow for space around the models that is not part of the collision radius, such as
bed adhesion (skirt/brim/raft) and travel avoid distance.
"""
if not self._global_container_stack or not self._global_container_stack.extruderList: if not self._global_container_stack or not self._global_container_stack.extruderList:
return 0 return 0

View File

@ -150,8 +150,9 @@ class CrashHandler:
self._sendCrashReport() self._sendCrashReport()
os._exit(1) os._exit(1)
## Backup the current resource directories and create clean ones.
def _backupAndStartClean(self): def _backupAndStartClean(self):
"""Backup the current resource directories and create clean ones."""
Resources.factoryReset() Resources.factoryReset()
self.early_crash_dialog.close() self.early_crash_dialog.close()
@ -162,8 +163,9 @@ class CrashHandler:
def _showDetailedReport(self): def _showDetailedReport(self):
self.dialog.exec_() self.dialog.exec_()
## Creates a modal dialog.
def _createDialog(self): def _createDialog(self):
"""Creates a modal dialog."""
self.dialog.setMinimumWidth(640) self.dialog.setMinimumWidth(640)
self.dialog.setMinimumHeight(640) self.dialog.setMinimumHeight(640)
self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report")) self.dialog.setWindowTitle(catalog.i18nc("@title:window", "Crash Report"))

View File

@ -43,9 +43,10 @@ class CuraActions(QObject):
event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {}) event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event) cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
## Reset camera position and direction to default
@pyqtSlot() @pyqtSlot()
def homeCamera(self) -> None: def homeCamera(self) -> None:
"""Reset camera position and direction to default"""
scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene() scene = cura.CuraApplication.CuraApplication.getInstance().getController().getScene()
camera = scene.getActiveCamera() camera = scene.getActiveCamera()
if camera: if camera:
@ -54,9 +55,10 @@ class CuraActions(QObject):
camera.setPerspective(True) camera.setPerspective(True)
camera.lookAt(Vector(0, 0, 0)) camera.lookAt(Vector(0, 0, 0))
## Center all objects in the selection
@pyqtSlot() @pyqtSlot()
def centerSelection(self) -> None: def centerSelection(self) -> None:
"""Center all objects in the selection"""
operation = GroupedOperation() operation = GroupedOperation()
for node in Selection.getAllSelectedObjects(): for node in Selection.getAllSelectedObjects():
current_node = node current_node = node
@ -73,18 +75,21 @@ class CuraActions(QObject):
operation.addOperation(center_operation) operation.addOperation(center_operation)
operation.push() operation.push()
## Multiply all objects in the selection
#
# \param count The number of times to multiply the selection.
@pyqtSlot(int) @pyqtSlot(int)
def multiplySelection(self, count: int) -> None: def multiplySelection(self, count: int) -> None:
"""Multiply all objects in the selection
:param count: The number of times to multiply the selection.
"""
min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors min_offset = cura.CuraApplication.CuraApplication.getInstance().getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8)) job = MultiplyObjectsJob(Selection.getAllSelectedObjects(), count, min_offset = max(min_offset, 8))
job.start() job.start()
## Delete all selected objects.
@pyqtSlot() @pyqtSlot()
def deleteSelection(self) -> None: def deleteSelection(self) -> None:
"""Delete all selected objects."""
if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled(): if not cura.CuraApplication.CuraApplication.getInstance().getController().getToolsEnabled():
return return
@ -106,11 +111,13 @@ class CuraActions(QObject):
op.push() op.push()
## Set the extruder that should be used to print the selection.
#
# \param extruder_id The ID of the extruder stack to use for the selected objects.
@pyqtSlot(str) @pyqtSlot(str)
def setExtruderForSelection(self, extruder_id: str) -> None: def setExtruderForSelection(self, extruder_id: str) -> None:
"""Set the extruder that should be used to print the selection.
:param extruder_id: The ID of the extruder stack to use for the selected objects.
"""
operation = GroupedOperation() operation = GroupedOperation()
nodes_to_change = [] nodes_to_change = []

View File

@ -258,9 +258,12 @@ class CuraApplication(QtApplication):
def ultimakerCloudAccountRootUrl(self) -> str: def ultimakerCloudAccountRootUrl(self) -> str:
return UltimakerCloudAuthentication.CuraCloudAccountAPIRoot return UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
# Adds command line options to the command line parser. This should be called after the application is created and
# before the pre-start.
def addCommandLineOptions(self): def addCommandLineOptions(self):
"""Adds command line options to the command line parser.
This should be called after the application is created and before the pre-start.
"""
super().addCommandLineOptions() super().addCommandLineOptions()
self._cli_parser.add_argument("--help", "-h", self._cli_parser.add_argument("--help", "-h",
action = "store_true", action = "store_true",
@ -322,8 +325,9 @@ class CuraApplication(QtApplication):
Logger.log("i", "Single instance commands were sent, exiting") Logger.log("i", "Single instance commands were sent, exiting")
sys.exit(0) sys.exit(0)
# Adds expected directory names and search paths for Resources.
def __addExpectedResourceDirsAndSearchPaths(self): def __addExpectedResourceDirsAndSearchPaths(self):
"""Adds expected directory names and search paths for Resources."""
# this list of dir names will be used by UM to detect an old cura directory # this list of dir names will be used by UM to detect an old cura directory
for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]: for dir_name in ["extruders", "machine_instances", "materials", "plugins", "quality", "quality_changes", "user", "variants", "intent"]:
Resources.addExpectedDirNameInData(dir_name) Resources.addExpectedDirNameInData(dir_name)
@ -365,9 +369,12 @@ class CuraApplication(QtApplication):
SettingDefinition.addSettingType("[int]", None, str, None) SettingDefinition.addSettingType("[int]", None, str, None)
# Adds custom property types, settings types, and extra operators (functions) that need to be registered in
# SettingDefinition and SettingFunction.
def _initializeSettingFunctions(self): def _initializeSettingFunctions(self):
"""Adds custom property types, settings types, and extra operators (functions).
Whom need to be registered in SettingDefinition and SettingFunction.
"""
self._cura_formula_functions = CuraFormulaFunctions(self) self._cura_formula_functions = CuraFormulaFunctions(self)
SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder) SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder)
@ -377,8 +384,9 @@ class CuraApplication(QtApplication):
SettingFunction.registerOperator("valueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndex) SettingFunction.registerOperator("valueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndex)
SettingFunction.registerOperator("extruderValueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndexInExtruder) SettingFunction.registerOperator("extruderValueFromContainer", self._cura_formula_functions.getValueFromContainerAtIndexInExtruder)
# Adds all resources and container related resources.
def __addAllResourcesAndContainerResources(self) -> None: def __addAllResourcesAndContainerResources(self) -> None:
"""Adds all resources and container related resources."""
Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality") Resources.addStorageType(self.ResourceTypes.QualityInstanceContainer, "quality")
Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes") Resources.addStorageType(self.ResourceTypes.QualityChangesInstanceContainer, "quality_changes")
Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants") Resources.addStorageType(self.ResourceTypes.VariantInstanceContainer, "variants")
@ -403,8 +411,9 @@ class CuraApplication(QtApplication):
Resources.addType(self.ResourceTypes.QmlFiles, "qml") Resources.addType(self.ResourceTypes.QmlFiles, "qml")
Resources.addType(self.ResourceTypes.Firmware, "firmware") Resources.addType(self.ResourceTypes.Firmware, "firmware")
# Adds all empty containers.
def __addAllEmptyContainers(self) -> None: def __addAllEmptyContainers(self) -> None:
"""Adds all empty containers."""
# Add empty variant, material and quality containers. # Add empty variant, material and quality containers.
# Since they are empty, they should never be serialized and instead just programmatically created. # Since they are empty, they should never be serialized and instead just programmatically created.
# We need them to simplify the switching between materials. # We need them to simplify the switching between materials.
@ -429,9 +438,11 @@ class CuraApplication(QtApplication):
self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_changes_container) self._container_registry.addContainer(cura.Settings.cura_empty_instance_containers.empty_quality_changes_container)
self.empty_quality_changes_container = cura.Settings.cura_empty_instance_containers.empty_quality_changes_container self.empty_quality_changes_container = cura.Settings.cura_empty_instance_containers.empty_quality_changes_container
# Initializes the version upgrade manager with by providing the paths for each resource type and the latest
# versions.
def __setLatestResouceVersionsForVersionUpgrade(self): def __setLatestResouceVersionsForVersionUpgrade(self):
"""Initializes the version upgrade manager with by providing the paths for each resource type and the latest
versions.
"""
self._version_upgrade_manager.setCurrentVersions( self._version_upgrade_manager.setCurrentVersions(
{ {
("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"), ("quality", InstanceContainer.Version * 1000000 + self.SettingVersion): (self.ResourceTypes.QualityInstanceContainer, "application/x-uranium-instancecontainer"),
@ -446,8 +457,9 @@ class CuraApplication(QtApplication):
} }
) )
# Runs preparations that needs to be done before the starting process.
def startSplashWindowPhase(self) -> None: def startSplashWindowPhase(self) -> None:
"""Runs preparations that needs to be done before the starting process."""
super().startSplashWindowPhase() super().startSplashWindowPhase()
if not self.getIsHeadLess(): if not self.getIsHeadLess():
@ -607,12 +619,13 @@ class CuraApplication(QtApplication):
def callConfirmExitDialogCallback(self, yes_or_no: bool) -> None: def callConfirmExitDialogCallback(self, yes_or_no: bool) -> None:
self._confirm_exit_dialog_callback(yes_or_no) self._confirm_exit_dialog_callback(yes_or_no)
## Signal to connect preferences action in QML
showPreferencesWindow = pyqtSignal() showPreferencesWindow = pyqtSignal()
"""Signal to connect preferences action in QML"""
## Show the preferences window
@pyqtSlot() @pyqtSlot()
def showPreferences(self) -> None: def showPreferences(self) -> None:
"""Show the preferences window"""
self.showPreferencesWindow.emit() self.showPreferencesWindow.emit()
# This is called by drag-and-dropping curapackage files. # This is called by drag-and-dropping curapackage files.
@ -630,10 +643,9 @@ class CuraApplication(QtApplication):
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine...")) self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Initializing Active Machine..."))
super().setGlobalContainerStack(stack) super().setGlobalContainerStack(stack)
## A reusable dialogbox
#
showMessageBox = pyqtSignal(str,str, str, str, int, int, showMessageBox = pyqtSignal(str,str, str, str, int, int,
arguments = ["title", "text", "informativeText", "detailedText","buttons", "icon"]) arguments = ["title", "text", "informativeText", "detailedText","buttons", "icon"])
""" A reusable dialogbox"""
def messageBox(self, title, text, def messageBox(self, title, text,
informativeText = "", informativeText = "",
@ -711,9 +723,12 @@ class CuraApplication(QtApplication):
def setDefaultPath(self, key, default_path): def setDefaultPath(self, key, default_path):
self.getPreferences().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile()) self.getPreferences().setValue("local_file/%s" % key, QUrl(default_path).toLocalFile())
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry
def _loadPlugins(self) -> None: def _loadPlugins(self) -> None:
"""Handle loading of all plugin types (and the backend explicitly)
:py:class:`Uranium.UM.PluginRegistry`
"""
self._plugin_registry.setCheckIfTrusted(ApplicationMetadata.IsEnterpriseVersion) self._plugin_registry.setCheckIfTrusted(ApplicationMetadata.IsEnterpriseVersion)
self._plugin_registry.addType("profile_reader", self._addProfileReader) self._plugin_registry.addType("profile_reader", self._addProfileReader)
@ -737,9 +752,12 @@ class CuraApplication(QtApplication):
self._plugins_loaded = True self._plugins_loaded = True
## Set a short, user-friendly hint about current loading status.
# The way this message is displayed depends on application state
def _setLoadingHint(self, hint: str): def _setLoadingHint(self, hint: str):
"""Set a short, user-friendly hint about current loading status.
The way this message is displayed depends on application state
"""
if self.started: if self.started:
Logger.info(hint) Logger.info(hint)
else: else:
@ -824,12 +842,14 @@ class CuraApplication(QtApplication):
initializationFinished = pyqtSignal() initializationFinished = pyqtSignal()
## Run Cura without GUI elements and interaction (server mode).
def runWithoutGUI(self): def runWithoutGUI(self):
"""Run Cura without GUI elements and interaction (server mode)."""
self.closeSplash() self.closeSplash()
## Run Cura with GUI (desktop mode).
def runWithGUI(self): def runWithGUI(self):
"""Run Cura with GUI (desktop mode)."""
self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Setting up scene...")) self._setLoadingHint(self._i18n_catalog.i18nc("@info:progress", "Setting up scene..."))
controller = self.getController() controller = self.getController()
@ -979,10 +999,13 @@ class CuraApplication(QtApplication):
self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager() self._setting_inheritance_manager = SettingInheritanceManager.createSettingInheritanceManager()
return self._setting_inheritance_manager return self._setting_inheritance_manager
## Get the machine action manager
# We ignore any *args given to this, as we also register the machine manager as qml singleton.
# It wants to give this function an engine and script engine, but we don't care about that.
def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager: def getMachineActionManager(self, *args: Any) -> MachineActionManager.MachineActionManager:
"""Get the machine action manager
We ignore any *args given to this, as we also register the machine manager as qml singleton.
It wants to give this function an engine and script engine, but we don't care about that.
"""
return cast(MachineActionManager.MachineActionManager, self._machine_action_manager) return cast(MachineActionManager.MachineActionManager, self._machine_action_manager)
@pyqtSlot(result = QObject) @pyqtSlot(result = QObject)
@ -1002,8 +1025,9 @@ class CuraApplication(QtApplication):
self._simple_mode_settings_manager = SimpleModeSettingsManager() self._simple_mode_settings_manager = SimpleModeSettingsManager()
return self._simple_mode_settings_manager return self._simple_mode_settings_manager
## Handle Qt events
def event(self, event): def event(self, event):
"""Handle Qt events"""
if event.type() == QEvent.FileOpen: if event.type() == QEvent.FileOpen:
if self._plugins_loaded: if self._plugins_loaded:
self._openFile(event.file()) self._openFile(event.file())
@ -1015,8 +1039,9 @@ class CuraApplication(QtApplication):
def getAutoSave(self) -> Optional[AutoSave]: def getAutoSave(self) -> Optional[AutoSave]:
return self._auto_save return self._auto_save
## Get print information (duration / material used)
def getPrintInformation(self): def getPrintInformation(self):
"""Get print information (duration / material used)"""
return self._print_information return self._print_information
def getQualityProfilesDropDownMenuModel(self, *args, **kwargs): def getQualityProfilesDropDownMenuModel(self, *args, **kwargs):
@ -1032,10 +1057,12 @@ class CuraApplication(QtApplication):
def getCuraAPI(self, *args, **kwargs) -> "CuraAPI": def getCuraAPI(self, *args, **kwargs) -> "CuraAPI":
return self._cura_API return self._cura_API
## Registers objects for the QML engine to use.
#
# \param engine The QML engine.
def registerObjects(self, engine): def registerObjects(self, engine):
"""Registers objects for the QML engine to use.
:param engine: The QML engine.
"""
super().registerObjects(engine) super().registerObjects(engine)
# global contexts # global contexts
@ -1169,8 +1196,9 @@ class CuraApplication(QtApplication):
if node is not None and (node.getMeshData() is not None or node.callDecoration("getLayerData")): if node is not None and (node.getMeshData() is not None or node.callDecoration("getLayerData")):
self._update_platform_activity_timer.start() self._update_platform_activity_timer.start()
## Update scene bounding box for current build plate
def updatePlatformActivity(self, node = None): def updatePlatformActivity(self, node = None):
"""Update scene bounding box for current build plate"""
count = 0 count = 0
scene_bounding_box = None scene_bounding_box = None
is_block_slicing_node = False is_block_slicing_node = False
@ -1214,9 +1242,10 @@ class CuraApplication(QtApplication):
self._platform_activity = True if count > 0 else False self._platform_activity = True if count > 0 else False
self.activityChanged.emit() self.activityChanged.emit()
## Select all nodes containing mesh data in the scene.
@pyqtSlot() @pyqtSlot()
def selectAll(self): def selectAll(self):
"""Select all nodes containing mesh data in the scene."""
if not self.getController().getToolsEnabled(): if not self.getController().getToolsEnabled():
return return
@ -1235,9 +1264,10 @@ class CuraApplication(QtApplication):
Selection.add(node) Selection.add(node)
## Reset all translation on nodes with mesh data.
@pyqtSlot() @pyqtSlot()
def resetAllTranslation(self): def resetAllTranslation(self):
"""Reset all translation on nodes with mesh data."""
Logger.log("i", "Resetting all scene translations") Logger.log("i", "Resetting all scene translations")
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
@ -1263,9 +1293,10 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0))) op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0)))
op.push() op.push()
## Reset all transformations on nodes with mesh data.
@pyqtSlot() @pyqtSlot()
def resetAll(self): def resetAll(self):
"""Reset all transformations on nodes with mesh data."""
Logger.log("i", "Resetting all scene transformations") Logger.log("i", "Resetting all scene transformations")
nodes = [] nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
@ -1291,9 +1322,10 @@ class CuraApplication(QtApplication):
op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1))) op.addOperation(SetTransformOperation(node, Vector(0, center_y, 0), Quaternion(), Vector(1, 1, 1)))
op.push() op.push()
## Arrange all objects.
@pyqtSlot() @pyqtSlot()
def arrangeObjectsToAllBuildPlates(self) -> None: def arrangeObjectsToAllBuildPlates(self) -> None:
"""Arrange all objects."""
nodes_to_arrange = [] nodes_to_arrange = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, SceneNode): if not isinstance(node, SceneNode):
@ -1346,17 +1378,21 @@ class CuraApplication(QtApplication):
nodes_to_arrange.append(node) nodes_to_arrange.append(node)
self.arrange(nodes_to_arrange, fixed_nodes = []) self.arrange(nodes_to_arrange, fixed_nodes = [])
## Arrange a set of nodes given a set of fixed nodes
# \param nodes nodes that we have to place
# \param fixed_nodes nodes that are placed in the arranger before finding spots for nodes
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None: def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
"""Arrange a set of nodes given a set of fixed nodes
:param nodes: nodes that we have to place
:param fixed_nodes: nodes that are placed in the arranger before finding spots for nodes
"""
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8)) job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
job.start() job.start()
## Reload all mesh data on the screen from file.
@pyqtSlot() @pyqtSlot()
def reloadAll(self) -> None: def reloadAll(self) -> None:
"""Reload all mesh data on the screen from file."""
Logger.log("i", "Reloading all loaded mesh data.") Logger.log("i", "Reloading all loaded mesh data.")
nodes = [] nodes = []
has_merged_nodes = False has_merged_nodes = False
@ -1466,8 +1502,9 @@ class CuraApplication(QtApplication):
group_node.setName("MergedMesh") # add a specific name to distinguish this node group_node.setName("MergedMesh") # add a specific name to distinguish this node
## Updates origin position of all merged meshes
def updateOriginOfMergedMeshes(self, _): def updateOriginOfMergedMeshes(self, _):
"""Updates origin position of all merged meshes"""
group_nodes = [] group_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh": if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
@ -1585,9 +1622,10 @@ class CuraApplication(QtApplication):
scene from its source file. The function gets all the nodes that exist in the file through the job result, and scene from its source file. The function gets all the nodes that exist in the file through the job result, and
then finds the scene node that it wants to refresh by its object id. Each job refreshes only one node. then finds the scene node that it wants to refresh by its object id. Each job refreshes only one node.
:param job: The ReadMeshJob running in the background that reads all the meshes in a file :param job: The :py:class:`Uranium.UM.ReadMeshJob.ReadMeshJob` running in the background that reads all the
:return: None meshes in a file
""" """
job_result = job.getResult() # nodes that exist inside the file read by this job job_result = job.getResult() # nodes that exist inside the file read by this job
if len(job_result) == 0: if len(job_result) == 0:
Logger.log("e", "Reloading the mesh failed.") Logger.log("e", "Reloading the mesh failed.")
@ -1633,12 +1671,15 @@ class CuraApplication(QtApplication):
def additionalComponents(self): def additionalComponents(self):
return self._additional_components return self._additional_components
## Add a component to a list of components to be reparented to another area in the GUI.
# The actual reparenting is done by the area itself.
# \param area_id \type{str} Identifying name of the area to which the component should be reparented
# \param component \type{QQuickComponent} The component that should be reparented
@pyqtSlot(str, "QVariant") @pyqtSlot(str, "QVariant")
def addAdditionalComponent(self, area_id, component): def addAdditionalComponent(self, area_id: str, component):
"""Add a component to a list of components to be reparented to another area in the GUI.
The actual reparenting is done by the area itself.
:param area_id: dentifying name of the area to which the component should be reparented
:param (QQuickComponent) component: The component that should be reparented
"""
if area_id not in self._additional_components: if area_id not in self._additional_components:
self._additional_components[area_id] = [] self._additional_components[area_id] = []
self._additional_components[area_id].append(component) self._additional_components[area_id].append(component)
@ -1653,10 +1694,13 @@ class CuraApplication(QtApplication):
@pyqtSlot(QUrl, str) @pyqtSlot(QUrl, str)
@pyqtSlot(QUrl) @pyqtSlot(QUrl)
## Open a local file
# \param project_mode How to handle project files. Either None(default): Follow user preference, "open_as_model" or
# "open_as_project". This parameter is only considered if the file is a project file.
def readLocalFile(self, file: QUrl, project_mode: Optional[str] = None): def readLocalFile(self, file: QUrl, project_mode: Optional[str] = None):
"""Open a local file
:param project_mode: How to handle project files. Either None(default): Follow user preference, "open_as_model"
or "open_as_project". This parameter is only considered if the file is a project file.
"""
if not file.isValid(): if not file.isValid():
return return
@ -1829,9 +1873,8 @@ class CuraApplication(QtApplication):
@pyqtSlot(str, result=bool) @pyqtSlot(str, result=bool)
def checkIsValidProjectFile(self, file_url): def checkIsValidProjectFile(self, file_url):
""" """ Checks if the given file URL is a valid project file. """
Checks if the given file URL is a valid project file.
"""
file_path = QUrl(file_url).toLocalFile() file_path = QUrl(file_url).toLocalFile()
workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path) workspace_reader = self.getWorkspaceFileHandler().getReaderForFile(file_path)
if workspace_reader is None: if workspace_reader is None:

View File

@ -24,11 +24,15 @@ class CuraPackageManager(PackageManager):
super().initialize() super().initialize()
## Returns a list of where the package is used
# empty if it is never used.
# It loops through all the package contents and see if some of the ids are used.
# The list consists of 3-tuples: (global_stack, extruder_nr, container_id)
def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]: def getMachinesUsingPackage(self, package_id: str) -> Tuple[List[Tuple[GlobalStack, str, str]], List[Tuple[GlobalStack, str, str]]]:
"""Returns a list of where the package is used
It loops through all the package contents and see if some of the ids are used.
:param package_id: package id to search for
:return: empty if it is never used, otherwise a list consisting of 3-tuples
"""
ids = self.getPackageContainerIds(package_id) ids = self.getPackageContainerIds(package_id)
container_stacks = self._application.getContainerRegistry().findContainerStacks() container_stacks = self._application.getContainerRegistry().findContainerStacks()
global_stacks = [container_stack for container_stack in container_stacks if isinstance(container_stack, GlobalStack)] global_stacks = [container_stack for container_stack in container_stacks if isinstance(container_stack, GlobalStack)]

View File

@ -3,9 +3,12 @@
from UM.Mesh.MeshData import MeshData from UM.Mesh.MeshData import MeshData
## Class to holds the layer mesh and information about the layers.
# Immutable, use LayerDataBuilder to create one of these.
class LayerData(MeshData): class LayerData(MeshData):
"""Class to holds the layer mesh and information about the layers.
Immutable, use :py:class:`cura.LayerDataBuilder.LayerDataBuilder` to create one of these.
"""
def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None, def __init__(self, vertices = None, normals = None, indices = None, colors = None, uvs = None, file_name = None,
center_position = None, layers=None, element_counts=None, attributes=None): center_position = None, layers=None, element_counts=None, attributes=None):
super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs, super().__init__(vertices=vertices, normals=normals, indices=indices, colors=colors, uvs=uvs,

View File

@ -10,8 +10,9 @@ import numpy
from typing import Dict, Optional from typing import Dict, Optional
## Builder class for constructing a LayerData object
class LayerDataBuilder(MeshBuilder): class LayerDataBuilder(MeshBuilder):
"""Builder class for constructing a :py:class:`cura.LayerData.LayerData` object"""
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._layers = {} # type: Dict[int, Layer] self._layers = {} # type: Dict[int, Layer]
@ -42,11 +43,13 @@ class LayerDataBuilder(MeshBuilder):
self._layers[layer].setThickness(thickness) self._layers[layer].setThickness(thickness)
## Return the layer data as LayerData.
#
# \param material_color_map: [r, g, b, a] for each extruder row.
# \param line_type_brightness: compatibility layer view uses line type brightness of 0.5
def build(self, material_color_map, line_type_brightness = 1.0): def build(self, material_color_map, line_type_brightness = 1.0):
"""Return the layer data as :py:class:`cura.LayerData.LayerData`.
:param material_color_map:: [r, g, b, a] for each extruder row.
:param line_type_brightness:: compatibility layer view uses line type brightness of 0.5
"""
vertex_count = 0 vertex_count = 0
index_count = 0 index_count = 0
for layer, data in self._layers.items(): for layer, data in self._layers.items():

View File

@ -7,8 +7,9 @@ from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
from cura.LayerData import LayerData from cura.LayerData import LayerData
## Simple decorator to indicate a scene node holds layer data.
class LayerDataDecorator(SceneNodeDecorator): class LayerDataDecorator(SceneNodeDecorator):
"""Simple decorator to indicate a scene node holds layer data."""
def __init__(self) -> None: def __init__(self) -> None:
super().__init__() super().__init__()
self._layer_data = None # type: Optional[LayerData] self._layer_data = None # type: Optional[LayerData]

View File

@ -26,14 +26,17 @@ class LayerPolygon:
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType) __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
## LayerPolygon, used in ProcessSlicedLayersJob
# \param extruder The position of the extruder
# \param line_types array with line_types
# \param data new_points
# \param line_widths array with line widths
# \param line_thicknesses: array with type as index and thickness as value
# \param line_feedrates array with line feedrates
def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None: def __init__(self, extruder: int, line_types: numpy.ndarray, data: numpy.ndarray, line_widths: numpy.ndarray, line_thicknesses: numpy.ndarray, line_feedrates: numpy.ndarray) -> None:
"""LayerPolygon, used in ProcessSlicedLayersJob
:param extruder: The position of the extruder
:param line_types: array with line_types
:param data: new_points
:param line_widths: array with line widths
:param line_thicknesses: array with type as index and thickness as value
:param line_feedrates: array with line feedrates
"""
self._extruder = extruder self._extruder = extruder
self._types = line_types self._types = line_types
for i in range(len(self._types)): for i in range(len(self._types)):
@ -83,19 +86,22 @@ class LayerPolygon:
self._vertex_begin = 0 self._vertex_begin = 0
self._vertex_end = numpy.sum( self._build_cache_needed_points ) self._vertex_end = numpy.sum( self._build_cache_needed_points )
## Set all the arrays provided by the function caller, representing the LayerPolygon
# The arrays are either by vertex or by indices.
#
# \param vertex_offset : determines where to start and end filling the arrays
# \param index_offset : determines where to start and end filling the arrays
# \param vertices : vertex numpy array to be filled
# \param colors : vertex numpy array to be filled
# \param line_dimensions : vertex numpy array to be filled
# \param feedrates : vertex numpy array to be filled
# \param extruders : vertex numpy array to be filled
# \param line_types : vertex numpy array to be filled
# \param indices : index numpy array to be filled
def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None: def build(self, vertex_offset: int, index_offset: int, vertices: numpy.ndarray, colors: numpy.ndarray, line_dimensions: numpy.ndarray, feedrates: numpy.ndarray, extruders: numpy.ndarray, line_types: numpy.ndarray, indices: numpy.ndarray) -> None:
"""Set all the arrays provided by the function caller, representing the LayerPolygon
The arrays are either by vertex or by indices.
:param vertex_offset: : determines where to start and end filling the arrays
:param index_offset: : determines where to start and end filling the arrays
:param vertices: : vertex numpy array to be filled
:param colors: : vertex numpy array to be filled
:param line_dimensions: : vertex numpy array to be filled
:param feedrates: : vertex numpy array to be filled
:param extruders: : vertex numpy array to be filled
:param line_types: : vertex numpy array to be filled
:param indices: : index numpy array to be filled
"""
if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None: if self._build_cache_line_mesh_mask is None or self._build_cache_needed_points is None:
self.buildCache() self.buildCache()
@ -202,8 +208,12 @@ class LayerPolygon:
def jumpCount(self): def jumpCount(self):
return self._jump_count return self._jump_count
# Calculate normals for the entire polygon using numpy.
def getNormals(self) -> numpy.ndarray: def getNormals(self) -> numpy.ndarray:
"""Calculate normals for the entire polygon using numpy.
:return: normals for the entire polygon
"""
normals = numpy.copy(self._data) normals = numpy.copy(self._data)
normals[:, 1] = 0.0 # We are only interested in 2D normals normals[:, 1] = 0.0 # We are only interested in 2D normals
@ -229,9 +239,10 @@ class LayerPolygon:
__color_map = None # type: numpy.ndarray __color_map = None # type: numpy.ndarray
## Gets the instance of the VersionUpgradeManager, or creates one.
@classmethod @classmethod
def getColorMap(cls) -> numpy.ndarray: def getColorMap(cls) -> numpy.ndarray:
"""Gets the instance of the VersionUpgradeManager, or creates one."""
if cls.__color_map is None: if cls.__color_map is None:
theme = cast(Theme, QtApplication.getInstance().getTheme()) theme = cast(Theme, QtApplication.getInstance().getTheme())
cls.__color_map = numpy.array([ cls.__color_map = numpy.array([

View File

@ -11,16 +11,22 @@ from UM.PluginObject import PluginObject
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
# updating the firmware, connecting with remote devices or doing bed leveling. A machine action can also have a
# qml, which should contain a "Cura.MachineAction" item. When activated, the item will be displayed in a dialog
# and this object will be added as "manager" (so all pyqtSlot() functions can be called by calling manager.func())
class MachineAction(QObject, PluginObject): class MachineAction(QObject, PluginObject):
"""Machine actions are actions that are added to a specific machine type.
Examples of such actions are updating the firmware, connecting with remote devices or doing bed leveling. A
machine action can also have a qml, which should contain a :py:class:`cura.MachineAction.MachineAction` item.
When activated, the item will be displayed in a dialog and this object will be added as "manager" (so all
pyqtSlot() functions can be called by calling manager.func())
"""
## Create a new Machine action.
# \param key unique key of the machine action
# \param label Human readable label used to identify the machine action.
def __init__(self, key: str, label: str = "") -> None: def __init__(self, key: str, label: str = "") -> None:
"""Create a new Machine action.
:param key: unique key of the machine action
:param label: Human readable label used to identify the machine action.
"""
super().__init__() super().__init__()
self._key = key self._key = key
self._label = label self._label = label
@ -34,10 +40,14 @@ class MachineAction(QObject, PluginObject):
def getKey(self) -> str: def getKey(self) -> str:
return self._key return self._key
## Whether this action needs to ask the user anything.
# If not, we shouldn't present the user with certain screens which otherwise show up.
# Defaults to true to be in line with the old behaviour.
def needsUserInteraction(self) -> bool: def needsUserInteraction(self) -> bool:
"""Whether this action needs to ask the user anything.
If not, we shouldn't present the user with certain screens which otherwise show up.
:return: Defaults to true to be in line with the old behaviour.
"""
return True return True
@pyqtProperty(str, notify = labelChanged) @pyqtProperty(str, notify = labelChanged)
@ -49,17 +59,24 @@ class MachineAction(QObject, PluginObject):
self._label = label self._label = label
self.labelChanged.emit() self.labelChanged.emit()
## Reset the action to it's default state.
# This should not be re-implemented by child classes, instead re-implement _reset.
# /sa _reset
@pyqtSlot() @pyqtSlot()
def reset(self) -> None: def reset(self) -> None:
"""Reset the action to it's default state.
This should not be re-implemented by child classes, instead re-implement _reset.
:py:meth:`cura.MachineAction.MachineAction._reset`
"""
self._finished = False self._finished = False
self._reset() self._reset()
## Protected implementation of reset.
# /sa reset()
def _reset(self) -> None: def _reset(self) -> None:
"""Protected implementation of reset.
:py:meth:`cura.MachineAction.MachineAction.reset`
"""
pass pass
@pyqtSlot() @pyqtSlot()
@ -72,8 +89,9 @@ class MachineAction(QObject, PluginObject):
def finished(self) -> bool: def finished(self) -> bool:
return self._finished return self._finished
## Protected helper to create a view object based on provided QML.
def _createViewFromQML(self) -> Optional["QObject"]: def _createViewFromQML(self) -> Optional["QObject"]:
"""Protected helper to create a view object based on provided QML."""
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None: if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId()) Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())

View File

@ -7,18 +7,21 @@ from UM.Scene.Iterator import Iterator
from UM.Scene.SceneNode import SceneNode from UM.Scene.SceneNode import SceneNode
from functools import cmp_to_key from functools import cmp_to_key
## Iterator that returns a list of nodes in the order that they need to be printed
# If there is no solution an empty list is returned.
# Take note that the list of nodes can have children (that may or may not contain mesh data)
class OneAtATimeIterator(Iterator.Iterator): class OneAtATimeIterator(Iterator.Iterator):
"""Iterator that returns a list of nodes in the order that they need to be printed
If there is no solution an empty list is returned.
Take note that the list of nodes can have children (that may or may not contain mesh data)
"""
def __init__(self, scene_node) -> None: def __init__(self, scene_node) -> None:
super().__init__(scene_node) # Call super to make multiple inheritance work. super().__init__(scene_node) # Call super to make multiple inheritance work.
self._hit_map = [[]] # type: List[List[bool]] # For each node, which other nodes this hits. A grid of booleans on which nodes hit which. self._hit_map = [[]] # type: List[List[bool]] # For each node, which other nodes this hits. A grid of booleans on which nodes hit which.
self._original_node_list = [] # type: List[SceneNode] # The nodes that need to be checked for collisions. self._original_node_list = [] # type: List[SceneNode] # The nodes that need to be checked for collisions.
## Fills the ``_node_stack`` with a list of scene nodes that need to be
# printed in order.
def _fillStack(self) -> None: def _fillStack(self) -> None:
"""Fills the ``_node_stack`` with a list of scene nodes that need to be printed in order. """
node_list = [] node_list = []
for node in self._scene_node.getChildren(): for node in self._scene_node.getChildren():
if not issubclass(type(node), SceneNode): if not issubclass(type(node), SceneNode):
@ -75,10 +78,14 @@ class OneAtATimeIterator(Iterator.Iterator):
return True return True
return False return False
## Check for a node whether it hits any of the other nodes.
# \param node The node to check whether it collides with the other nodes.
# \param other_nodes The nodes to check for collisions.
def _checkBlockMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool: def _checkBlockMultiple(self, node: SceneNode, other_nodes: List[SceneNode]) -> bool:
"""Check for a node whether it hits any of the other nodes.
:param node: The node to check whether it collides with the other nodes.
:param other_nodes: The nodes to check for collisions.
:return: returns collision between nodes
"""
node_index = self._original_node_list.index(node) node_index = self._original_node_list.index(node)
for other_node in other_nodes: for other_node in other_nodes:
other_node_index = self._original_node_list.index(other_node) other_node_index = self._original_node_list.index(other_node)
@ -86,14 +93,26 @@ class OneAtATimeIterator(Iterator.Iterator):
return True return True
return False return False
## Calculate score simply sums the number of other objects it 'blocks'
def _calculateScore(self, a: SceneNode, b: SceneNode) -> int: def _calculateScore(self, a: SceneNode, b: SceneNode) -> int:
"""Calculate score simply sums the number of other objects it 'blocks'
:param a: node
:param b: node
:return: sum of the number of other objects
"""
score_a = sum(self._hit_map[self._original_node_list.index(a)]) score_a = sum(self._hit_map[self._original_node_list.index(a)])
score_b = sum(self._hit_map[self._original_node_list.index(b)]) score_b = sum(self._hit_map[self._original_node_list.index(b)])
return score_a - score_b return score_a - score_b
## Checks if A can be printed before B
def _checkHit(self, a: SceneNode, b: SceneNode) -> bool: def _checkHit(self, a: SceneNode, b: SceneNode) -> bool:
"""Checks if a can be printed before b
:param a: node
:param b: node
:return: true if a can be printed before b
"""
if a == b: if a == b:
return False return False
@ -116,12 +135,14 @@ class OneAtATimeIterator(Iterator.Iterator):
return False return False
## Internal object used to keep track of a possible order in which to print objects.
class _ObjectOrder: class _ObjectOrder:
## Creates the _ObjectOrder instance. """Internal object used to keep track of a possible order in which to print objects."""
# \param order List of indices in which to print objects, ordered by printing
# order.
# \param todo: List of indices which are not yet inserted into the order list.
def __init__(self, order: List[SceneNode], todo: List[SceneNode]) -> None: def __init__(self, order: List[SceneNode], todo: List[SceneNode]) -> None:
"""Creates the _ObjectOrder instance.
:param order: List of indices in which to print objects, ordered by printing order.
:param todo: List of indices which are not yet inserted into the order list.
"""
self.order = order self.order = order
self.todo = todo self.todo = todo

View File

@ -16,11 +16,15 @@ from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
if TYPE_CHECKING: if TYPE_CHECKING:
from UM.View.GL.ShaderProgram import ShaderProgram from UM.View.GL.ShaderProgram import ShaderProgram
## A RenderPass subclass that renders a the distance of selectable objects from the active camera to a texture.
# The texture is used to map a 2d location (eg the mouse location) to a world space position
#
# Note that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
class PickingPass(RenderPass): class PickingPass(RenderPass):
"""A :py:class:`Uranium.UM.View.RenderPass` subclass that renders a the distance of selectable objects from the
active camera to a texture.
The texture is used to map a 2d location (eg the mouse location) to a world space position
.. note:: that in order to increase precision, the 24 bit depth value is encoded into all three of the R,G & B channels
"""
def __init__(self, width: int, height: int) -> None: def __init__(self, width: int, height: int) -> None:
super().__init__("picking", width, height) super().__init__("picking", width, height)
@ -50,8 +54,14 @@ class PickingPass(RenderPass):
batch.render(self._scene.getActiveCamera()) batch.render(self._scene.getActiveCamera())
self.release() self.release()
## Get the distance in mm from the camera to at a certain pixel coordinate.
def getPickedDepth(self, x: int, y: int) -> float: def getPickedDepth(self, x: int, y: int) -> float:
"""Get the distance in mm from the camera to at a certain pixel coordinate.
:param x: x component of coordinate vector in pixels
:param y: y component of coordinate vector in pixels
:return: distance in mm from the camera to pixel coordinate
"""
output = self.getOutput() output = self.getOutput()
window_size = self._renderer.getWindowSize() window_size = self._renderer.getWindowSize()
@ -66,8 +76,14 @@ class PickingPass(RenderPass):
distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and covert to mm distance = (distance & 0x00ffffff) / 1000. # drop the alpha channel and covert to mm
return distance return distance
## Get the world coordinates of a picked point
def getPickedPosition(self, x: int, y: int) -> Vector: def getPickedPosition(self, x: int, y: int) -> Vector:
"""Get the world coordinates of a picked point
:param x: x component of coordinate vector in pixels
:param y: y component of coordinate vector in pixels
:return: vector of the world coordinate
"""
distance = self.getPickedDepth(x, y) distance = self.getPickedDepth(x, y)
camera = self._scene.getActiveCamera() camera = self._scene.getActiveCamera()
if camera: if camera:

View File

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, TYPE_CHECKING, cast from typing import Optional, TYPE_CHECKING, cast, List
from UM.Application import Application from UM.Application import Application
@ -20,9 +20,14 @@ if TYPE_CHECKING:
from UM.Scene.Camera import Camera from UM.Scene.Camera import Camera
# Make color brighter by normalizing it (maximum factor 2.5 brighter) def prettier_color(color_list: List[float]) -> List[float]:
# color_list is a list of 4 elements: [r, g, b, a], each element is a float 0..1 """Make color brighter by normalizing
def prettier_color(color_list):
maximum factor 2.5 brighter
:param color_list: a list of 4 elements: [r, g, b, a], each element is a float 0..1
:return: a normalized list of 4 elements: [r, g, b, a], each element is a float 0..1
"""
maximum = max(color_list[:3]) maximum = max(color_list[:3])
if maximum > 0: if maximum > 0:
factor = min(1 / maximum, 2.5) factor = min(1 / maximum, 2.5)
@ -31,11 +36,14 @@ def prettier_color(color_list):
return [min(i * factor, 1.0) for i in color_list] return [min(i * factor, 1.0) for i in color_list]
## A render pass subclass that renders slicable objects with default parameters.
# It uses the active camera by default, but it can be overridden to use a different camera.
#
# This is useful to get a preview image of a scene taken from a different location as the active camera.
class PreviewPass(RenderPass): class PreviewPass(RenderPass):
"""A :py:class:`Uranium.UM.View.RenderPass` subclass that renders slicable objects with default parameters.
It uses the active camera by default, but it can be overridden to use a different camera.
This is useful to get a preview image of a scene taken from a different location as the active camera.
"""
def __init__(self, width: int, height: int) -> None: def __init__(self, width: int, height: int) -> None:
super().__init__("preview", width, height, 0) super().__init__("preview", width, height, 0)

View File

@ -10,8 +10,14 @@ class PrintJobPreviewImageProvider(QQuickImageProvider):
def __init__(self): def __init__(self):
super().__init__(QQuickImageProvider.Image) super().__init__(QQuickImageProvider.Image)
## Request a new image.
def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]: def requestImage(self, id: str, size: QSize) -> Tuple[QImage, QSize]:
"""Request a new image.
:param id: id of the requested image
:param size: is not used defaults to QSize(15, 15)
:return: an tuple containing the image and size
"""
# The id will have an uuid and an increment separated by a slash. As we don't care about the value of the # The id will have an uuid and an increment separated by a slash. As we don't care about the value of the
# increment, we need to strip that first. # increment, we need to strip that first.
uuid = id[id.find("/") + 1:] uuid = id[id.find("/") + 1:]

View File

@ -30,11 +30,17 @@ class Snapshot:
return min_x, max_x, min_y, max_y return min_x, max_x, min_y, max_y
## Return a QImage of the scene
# Uses PreviewPass that leaves out some elements
# Aspect ratio assumes a square
@staticmethod @staticmethod
def snapshot(width = 300, height = 300): def snapshot(width = 300, height = 300):
"""Return a QImage of the scene
Uses PreviewPass that leaves out some elements Aspect ratio assumes a square
:param width: width of the aspect ratio default 300
:param height: height of the aspect ratio default 300
:return: None when there is no model on the build plate otherwise it will return an image
"""
scene = Application.getInstance().getController().getScene() scene = Application.getInstance().getController().getScene()
active_camera = scene.getActiveCamera() active_camera = scene.getActiveCamera()
render_width, render_height = active_camera.getWindowSize() render_width, render_height = active_camera.getWindowSize()