Fix merge conflicts

This commit is contained in:
Lipu Fei 2019-01-24 14:55:29 +01:00
commit 173f125d3e
893 changed files with 48366 additions and 2016 deletions

View file

@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
from UM.i18n import i18nCatalog
from UM.Message import Message
from cura import UltimakerCloudAuthentication
from cura.OAuth2.AuthorizationService import AuthorizationService
from cura.OAuth2.Models import OAuth2Settings
@ -37,15 +38,16 @@ class Account(QObject):
self._logged_in = False
self._callback_port = 32118
self._oauth_root = "https://account.ultimaker.com"
self._cloud_api_root = "https://api.ultimaker.com"
self._oauth_root = UltimakerCloudAuthentication.CuraCloudAccountAPIRoot
self._oauth_settings = OAuth2Settings(
OAUTH_SERVER_URL= self._oauth_root,
CALLBACK_PORT=self._callback_port,
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
CLIENT_ID="um----------------------------ultimaker_cura",
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.write",
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download "
"packages.rating.read packages.rating.write connect.cluster.read connect.cluster.write "
"cura.printjob.read cura.printjob.write cura.mesh.read cura.mesh.write",
AUTH_DATA_PREFERENCE_KEY="general/ultimaker_auth_data",
AUTH_SUCCESS_REDIRECT="{}/app/auth-success".format(self._oauth_root),
AUTH_FAILED_REDIRECT="{}/app/auth-error".format(self._oauth_root)
@ -60,6 +62,11 @@ class Account(QObject):
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
self._authorization_service.loadAuthDataFromPreferences()
## Returns a boolean indicating whether the given authentication is applied against staging or not.
@property
def is_staging(self) -> bool:
return "staging" in self._oauth_root
@pyqtProperty(bool, notify=loginStateChanged)
def isLoggedIn(self) -> bool:
return self._logged_in

View file

@ -1,6 +1,6 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Tuple, Optional, TYPE_CHECKING
from typing import Tuple, Optional, TYPE_CHECKING, Dict, Any
from cura.Backups.BackupsManager import BackupsManager
@ -24,12 +24,12 @@ class Backups:
## Create a new back-up using the BackupsManager.
# \return Tuple containing a ZIP file with the back-up data and a dict
# with metadata about the back-up.
def createBackup(self) -> Tuple[Optional[bytes], Optional[dict]]:
def createBackup(self) -> Tuple[Optional[bytes], Optional[Dict[str, Any]]]:
return self.manager.createBackup()
## Restore a back-up using the BackupsManager.
# \param zip_file A ZIP file containing the actual back-up data.
# \param meta_data Some metadata needed for restoring a back-up, like the
# Cura version number.
def restoreBackup(self, zip_file: bytes, meta_data: dict) -> None:
def restoreBackup(self, zip_file: bytes, meta_data: Dict[str, Any]) -> None:
return self.manager.restoreBackup(zip_file, meta_data)

View file

@ -0,0 +1,42 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# ---------
# Genearl constants used in Cura
# ---------
DEFAULT_CURA_DISPLAY_NAME = "Ultimaker Cura"
DEFAULT_CURA_VERSION = "master"
DEFAULT_CURA_BUILD_TYPE = ""
DEFAULT_CURA_DEBUG_MODE = False
DEFAULT_CURA_SDK_VERSION = "6.0.0"
try:
from cura.CuraVersion import CuraAppDisplayName # type: ignore
if CuraAppDisplayName == "":
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
except ImportError:
CuraAppDisplayName = DEFAULT_CURA_DISPLAY_NAME
try:
from cura.CuraVersion import CuraVersion # type: ignore
if CuraVersion == "":
CuraVersion = DEFAULT_CURA_VERSION
except ImportError:
CuraVersion = DEFAULT_CURA_VERSION # [CodeStyle: Reflecting imported value]
try:
from cura.CuraVersion import CuraBuildType # type: ignore
except ImportError:
CuraBuildType = DEFAULT_CURA_BUILD_TYPE
try:
from cura.CuraVersion import CuraDebugMode # type: ignore
except ImportError:
CuraDebugMode = DEFAULT_CURA_DEBUG_MODE
try:
from cura.CuraVersion import CuraSDKVersion # type: ignore
if CuraSDKVersion == "":
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION
except ImportError:
CuraSDKVersion = DEFAULT_CURA_SDK_VERSION

View file

@ -1052,6 +1052,12 @@ class BuildVolume(SceneNode):
else:
raise Exception("Unknown bed adhesion type. Did you forget to update the build volume calculations for your new bed adhesion type?")
max_length_available = 0.5 * min(
self._global_container_stack.getProperty("machine_width", "value"),
self._global_container_stack.getProperty("machine_depth", "value")
)
bed_adhesion_size = min(bed_adhesion_size, max_length_available)
support_expansion = 0
support_enabled = self._global_container_stack.getProperty("support_enable", "value")
support_offset = self._global_container_stack.getProperty("support_offset", "value")

View file

@ -36,12 +36,12 @@ class CuraActions(QObject):
# Starting a web browser from a signal handler connected to a menu will crash on windows.
# So instead, defer the call to the next run of the event loop, since that does work.
# Note that weirdly enough, only signal handlers that open a web browser fail like that.
event = CallFunctionEvent(self._openUrl, [QUrl("http://ultimaker.com/en/support/software")], {})
event = CallFunctionEvent(self._openUrl, [QUrl("https://ultimaker.com/en/resources/manuals/software")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
@pyqtSlot()
def openBugReportPage(self) -> None:
event = CallFunctionEvent(self._openUrl, [QUrl("http://github.com/Ultimaker/Cura/issues")], {})
event = CallFunctionEvent(self._openUrl, [QUrl("https://github.com/Ultimaker/Cura/issues")], {})
cura.CuraApplication.CuraApplication.getInstance().functionEvent(event)
## Reset camera position and direction to default

View file

@ -4,7 +4,7 @@
import os
import sys
import time
from typing import cast, TYPE_CHECKING, Optional, Callable
from typing import cast, TYPE_CHECKING, Optional, Callable, List
import numpy
@ -51,7 +51,7 @@ from cura.Arranging.ArrangeObjectsJob import ArrangeObjectsJob
from cura.Arranging.ArrangeObjectsAllBuildPlatesJob import ArrangeObjectsAllBuildPlatesJob
from cura.Arranging.ShapeArray import ShapeArray
from cura.MultiplyObjectsJob import MultiplyObjectsJob
from cura.PrintersModel import PrintersModel
from cura.GlobalStacksModel import GlobalStacksModel
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from cura.Operations.SetParentOperation import SetParentOperation
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
@ -117,6 +117,8 @@ from cura.ObjectsModel import ObjectsModel
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
from cura import ApplicationMetadata, UltimakerCloudAuthentication
from UM.FlameProfiler import pyqtSlot
from UM.Decorators import override
@ -129,21 +131,12 @@ if TYPE_CHECKING:
numpy.seterr(all = "ignore")
try:
from cura.CuraVersion import CuraAppDisplayName, CuraVersion, CuraBuildType, CuraDebugMode, CuraSDKVersion # type: ignore
except ImportError:
CuraAppDisplayName = "Ultimaker Cura"
CuraVersion = "master" # [CodeStyle: Reflecting imported value]
CuraBuildType = ""
CuraDebugMode = False
CuraSDKVersion = "6.0.0"
class CuraApplication(QtApplication):
# SettingVersion represents the set of settings available in the machine/extruder definitions.
# You need to make sure that this version number needs to be increased if there is any non-backwards-compatible
# changes of the settings.
SettingVersion = 5
SettingVersion = 7
Created = False
@ -164,11 +157,11 @@ class CuraApplication(QtApplication):
def __init__(self, *args, **kwargs):
super().__init__(name = "cura",
app_display_name = CuraAppDisplayName,
version = CuraVersion,
api_version = CuraSDKVersion,
buildtype = CuraBuildType,
is_debug_mode = CuraDebugMode,
app_display_name = ApplicationMetadata.CuraAppDisplayName,
version = ApplicationMetadata.CuraVersion,
api_version = ApplicationMetadata.CuraSDKVersion,
buildtype = ApplicationMetadata.CuraBuildType,
is_debug_mode = ApplicationMetadata.CuraDebugMode,
tray_icon_name = "cura-icon-32.png",
**kwargs)
@ -263,6 +256,14 @@ class CuraApplication(QtApplication):
from cura.CuraPackageManager import CuraPackageManager
self._package_manager_class = CuraPackageManager
@pyqtProperty(str, constant=True)
def ultimakerCloudApiRootUrl(self) -> str:
return UltimakerCloudAuthentication.CuraCloudAPIRoot
@pyqtProperty(str, constant = True)
def ultimakerCloudAccountRootUrl(self) -> str:
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):
@ -434,7 +435,8 @@ class CuraApplication(QtApplication):
def startSplashWindowPhase(self) -> None:
super().startSplashWindowPhase()
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
if not self.getIsHeadLess():
self.setWindowIcon(QIcon(Resources.getPath(Resources.Images, "cura-icon.png")))
self.setRequiredPlugins([
# Misc.:
@ -499,7 +501,8 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("view/settings_list_height", 400)
preferences.addPreference("view/settings_visible", False)
preferences.addPreference("cura/currency", "")
preferences.addPreference("cura/material_settings", "{}")
@ -664,12 +667,12 @@ class CuraApplication(QtApplication):
## Handle loading of all plugin types (and the backend explicitly)
# \sa PluginRegistry
def _loadPlugins(self):
def _loadPlugins(self) -> None:
self._plugin_registry.addType("profile_reader", self._addProfileReader)
self._plugin_registry.addType("profile_writer", self._addProfileWriter)
if Platform.isLinux():
lib_suffixes = {"", "64", "32", "x32"} #A few common ones on different distributions.
lib_suffixes = {"", "64", "32", "x32"} # A few common ones on different distributions.
else:
lib_suffixes = {""}
for suffix in lib_suffixes:
@ -953,7 +956,7 @@ class CuraApplication(QtApplication):
engine.rootContext().setContextProperty("CuraApplication", self)
engine.rootContext().setContextProperty("PrintInformation", self._print_information)
engine.rootContext().setContextProperty("CuraActions", self._cura_actions)
engine.rootContext().setContextProperty("CuraSDKVersion", CuraSDKVersion)
engine.rootContext().setContextProperty("CuraSDKVersion", ApplicationMetadata.CuraSDKVersion)
qmlRegisterUncreatableType(CuraApplication, "Cura", 1, 0, "ResourceTypes", "Just an Enum type")
@ -971,7 +974,7 @@ class CuraApplication(QtApplication):
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")
qmlRegisterType(InstanceContainer, "Cura", 1, 0, "InstanceContainer")
qmlRegisterType(ExtrudersModel, "Cura", 1, 0, "ExtrudersModel")
qmlRegisterType(PrintersModel, "Cura", 1, 0, "PrintersModel")
qmlRegisterType(GlobalStacksModel, "Cura", 1, 0, "GlobalStacksModel")
qmlRegisterType(FavoriteMaterialsModel, "Cura", 1, 0, "FavoriteMaterialsModel")
qmlRegisterType(GenericMaterialsModel, "Cura", 1, 0, "GenericMaterialsModel")
@ -1105,88 +1108,6 @@ class CuraApplication(QtApplication):
self._platform_activity = True if count > 0 else False
self.activityChanged.emit()
# Remove all selected objects from the scene.
@pyqtSlot()
@deprecated("Moved to CuraActions", "2.6")
def deleteSelection(self):
if not self.getController().getToolsEnabled():
return
removed_group_nodes = []
op = GroupedOperation()
nodes = Selection.getAllSelectedObjects()
for node in nodes:
op.addOperation(RemoveSceneNodeOperation(node))
group_node = node.getParent()
if group_node and group_node.callDecoration("isGroup") and group_node not in removed_group_nodes:
remaining_nodes_in_group = list(set(group_node.getChildren()) - set(nodes))
if len(remaining_nodes_in_group) == 1:
removed_group_nodes.append(group_node)
op.addOperation(SetParentOperation(remaining_nodes_in_group[0], group_node.getParent()))
op.addOperation(RemoveSceneNodeOperation(group_node))
op.push()
## Remove an object from the scene.
# Note that this only removes an object if it is selected.
@pyqtSlot("quint64")
@deprecated("Use deleteSelection instead", "2.6")
def deleteObject(self, object_id):
if not self.getController().getToolsEnabled():
return
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if node:
op = GroupedOperation()
op.addOperation(RemoveSceneNodeOperation(node))
group_node = node.getParent()
if group_node:
# Note that at this point the node has not yet been deleted
if len(group_node.getChildren()) <= 2 and group_node.callDecoration("isGroup"):
op.addOperation(SetParentOperation(group_node.getChildren()[0], group_node.getParent()))
op.addOperation(RemoveSceneNodeOperation(group_node))
op.push()
## Create a number of copies of existing object.
# \param object_id
# \param count number of copies
# \param min_offset minimum offset to other objects.
@pyqtSlot("quint64", int)
@deprecated("Use CuraActions::multiplySelection", "2.6")
def multiplyObject(self, object_id, count, min_offset = 8):
node = self.getController().getScene().findObject(object_id)
if not node:
node = Selection.getSelectedObject(0)
while node.getParent() and node.getParent().callDecoration("isGroup"):
node = node.getParent()
job = MultiplyObjectsJob([node], count, min_offset)
job.start()
return
## Center object on platform.
@pyqtSlot("quint64")
@deprecated("Use CuraActions::centerSelection", "2.6")
def centerObject(self, object_id):
node = self.getController().getScene().findObject(object_id)
if not node and object_id != 0: # Workaround for tool handles overlapping the selected object
node = Selection.getSelectedObject(0)
if not node:
return
if node.getParent() and node.getParent().callDecoration("isGroup"):
node = node.getParent()
if node:
op = SetTransformOperation(node, Vector())
op.push()
## Select all nodes containing mesh data in the scene.
@pyqtSlot()
def selectAll(self):
@ -1266,62 +1187,75 @@ class CuraApplication(QtApplication):
## Arrange all objects.
@pyqtSlot()
def arrangeObjectsToAllBuildPlates(self):
nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
def arrangeObjectsToAllBuildPlates(self) -> None:
nodes_to_arrange = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is reset)
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
bounding_box = node.getBoundingBox()
# Skip nodes that are too big
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
nodes.append(node)
job = ArrangeObjectsAllBuildPlatesJob(nodes)
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
nodes_to_arrange.append(node)
job = ArrangeObjectsAllBuildPlatesJob(nodes_to_arrange)
job.start()
self.getCuraSceneController().setActiveBuildPlate(0) # Select first build plate
# Single build plate
@pyqtSlot()
def arrangeAll(self):
nodes = []
def arrangeAll(self) -> None:
nodes_to_arrange = []
active_build_plate = self.getMultiBuildPlateModel().activeBuildPlate
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
if not isinstance(node, SceneNode):
continue
if not node.getMeshData() and not node.callDecoration("isGroup"):
continue # Node that doesnt have a mesh and is not a group.
if node.getParent() and node.getParent().callDecoration("isGroup"):
parent_node = node.getParent()
if parent_node and parent_node.callDecoration("isGroup"):
continue # Grouped nodes don't need resetting as their parent (the group) is resetted)
if not node.isSelectable():
continue # i.e. node with layer data
if not node.callDecoration("isSliceable") and not node.callDecoration("isGroup"):
continue # i.e. node with layer data
if node.callDecoration("getBuildPlateNumber") == active_build_plate:
# Skip nodes that are too big
if node.getBoundingBox().width < self._volume.getBoundingBox().width or node.getBoundingBox().depth < self._volume.getBoundingBox().depth:
nodes.append(node)
self.arrange(nodes, fixed_nodes = [])
bounding_box = node.getBoundingBox()
if bounding_box is None or bounding_box.width < self._volume.getBoundingBox().width or bounding_box.depth < self._volume.getBoundingBox().depth:
nodes_to_arrange.append(node)
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, fixed_nodes):
def arrange(self, nodes: List[SceneNode], fixed_nodes: List[SceneNode]) -> None:
min_offset = self.getBuildVolume().getEdgeDisallowedSize() + 2 # Allow for some rounding errors
job = ArrangeObjectsJob(nodes, fixed_nodes, min_offset = max(min_offset, 8))
job.start()
## Reload all mesh data on the screen from file.
@pyqtSlot()
def reloadAll(self):
def reloadAll(self) -> None:
Logger.log("i", "Reloading all loaded mesh data.")
nodes = []
has_merged_nodes = False
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if not isinstance(node, CuraSceneNode) or not node.getMeshData() :
for node in DepthFirstIterator(self.getController().getScene().getRoot()): # type: ignore
if not isinstance(node, CuraSceneNode) or not node.getMeshData():
if node.getName() == "MergedMesh":
has_merged_nodes = True
continue
@ -1335,7 +1269,7 @@ class CuraApplication(QtApplication):
file_name = node.getMeshData().getFileName()
if file_name:
job = ReadMeshJob(file_name)
job._node = node
job._node = node # type: ignore
job.finished.connect(self._reloadMeshFinished)
if has_merged_nodes:
job.finished.connect(self.updateOriginOfMergedMeshes)
@ -1344,20 +1278,8 @@ class CuraApplication(QtApplication):
else:
Logger.log("w", "Unable to reload data because we don't have a filename.")
## Get logging data of the backend engine
# \returns \type{string} Logging data
@pyqtSlot(result = str)
def getEngineLog(self):
log = ""
for entry in self.getBackend().getLog():
log += entry.decode()
return log
@pyqtSlot("QStringList")
def setExpandedCategories(self, categories):
def setExpandedCategories(self, categories: List[str]) -> None:
categories = list(set(categories))
categories.sort()
joined = ";".join(categories)
@ -1368,7 +1290,7 @@ class CuraApplication(QtApplication):
expandedCategoriesChanged = pyqtSignal()
@pyqtProperty("QStringList", notify = expandedCategoriesChanged)
def expandedCategories(self):
def expandedCategories(self) -> List[str]:
return self.getPreferences().getValue("cura/categories_expanded").split(";")
@pyqtSlot()
@ -1418,13 +1340,12 @@ class CuraApplication(QtApplication):
## Updates origin position of all merged meshes
# \param jobNode \type{Job} empty object which passed which is required by JobQueue
def updateOriginOfMergedMeshes(self, jobNode):
def updateOriginOfMergedMeshes(self, _):
group_nodes = []
for node in DepthFirstIterator(self.getController().getScene().getRoot()):
if isinstance(node, CuraSceneNode) and node.getName() == "MergedMesh":
#checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
# Checking by name might be not enough, the merged mesh should has "GroupDecorator" decorator
for decorator in node.getDecorators():
if isinstance(decorator, GroupDecorator):
group_nodes.append(node)
@ -1468,7 +1389,7 @@ class CuraApplication(QtApplication):
@pyqtSlot()
def groupSelected(self):
def groupSelected(self) -> None:
# Create a group-node
group_node = CuraSceneNode()
group_decorator = GroupDecorator()
@ -1484,7 +1405,8 @@ class CuraApplication(QtApplication):
# Remove nodes that are directly parented to another selected node from the selection so they remain parented
selected_nodes = Selection.getAllSelectedObjects().copy()
for node in selected_nodes:
if node.getParent() in selected_nodes and not node.getParent().callDecoration("isGroup"):
parent = node.getParent()
if parent is not None and parent in selected_nodes and not parent.callDecoration("isGroup"):
Selection.remove(node)
# Move selected nodes into the group-node
@ -1496,7 +1418,7 @@ class CuraApplication(QtApplication):
Selection.add(group_node)
@pyqtSlot()
def ungroupSelected(self):
def ungroupSelected(self) -> None:
selected_objects = Selection.getAllSelectedObjects().copy()
for node in selected_objects:
if node.callDecoration("isGroup"):
@ -1519,7 +1441,7 @@ class CuraApplication(QtApplication):
# Note: The group removes itself from the scene once all its children have left it,
# see GroupDecorator._onChildrenChanged
def _createSplashScreen(self):
def _createSplashScreen(self) -> Optional[CuraSplashScreen.CuraSplashScreen]:
if self._is_headless:
return None
return CuraSplashScreen.CuraSplashScreen()

View file

@ -8,3 +8,4 @@ CuraDebugMode = True if "@_cura_debugmode@" == "ON" else False
CuraSDKVersion = "@CURA_SDK_VERSION@"
CuraCloudAPIRoot = "@CURA_CLOUD_API_ROOT@"
CuraCloudAPIVersion = "@CURA_CLOUD_API_VERSION@"
CuraCloudAccountAPIRoot = "@CURA_CLOUD_ACCOUNT_API_ROOT@"

View file

@ -1,18 +1,15 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtProperty, Qt
from UM.Qt.ListModel import ListModel
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.Settings.ContainerStack import ContainerStack
from cura.PrinterOutputDevice import ConnectionType
from cura.Settings.GlobalStack import GlobalStack
class PrintersModel(ListModel):
class GlobalStacksModel(ListModel):
NameRole = Qt.UserRole + 1
IdRole = Qt.UserRole + 2
HasRemoteConnectionRole = Qt.UserRole + 3
@ -37,25 +34,20 @@ class PrintersModel(ListModel):
## Handler for container added/removed events from registry
def _onContainerChanged(self, container):
from cura.Settings.GlobalStack import GlobalStack # otherwise circular imports
# We only need to update when the added / removed container GlobalStack
if isinstance(container, GlobalStack):
self._update()
## Handler for container name change events.
def _onContainerNameChanged(self):
self._update()
def _update(self) -> None:
items = []
for container in self._container_stacks:
container.nameChanged.disconnect(self._onContainerNameChanged)
container_stacks = ContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks:
connection_type = container_stack.getMetaDataEntry("connection_type")
connection_type = int(container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
has_remote_connection = connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]:
continue
@ -66,4 +58,4 @@ class PrintersModel(ListModel):
"connectionType": connection_type,
"metadata": container_stack.getMetaData().copy()})
items.sort(key=lambda i: not i["hasRemoteConnection"])
self.setItems(items)
self.setItems(items)

View file

@ -5,6 +5,8 @@ from UM.Application import Application
from typing import Any
import numpy
from UM.Logger import Logger
class LayerPolygon:
NoneType = 0
@ -18,7 +20,8 @@ class LayerPolygon:
MoveCombingType = 8
MoveRetractionType = 9
SupportInterfaceType = 10
__number_of_types = 11
PrimeTower = 11
__number_of_types = 12
__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)
@ -33,7 +36,8 @@ class LayerPolygon:
self._extruder = extruder
self._types = line_types
for i in range(len(self._types)):
if self._types[i] >= self.__number_of_types: #Got faulty line data from the engine.
if self._types[i] >= self.__number_of_types: # Got faulty line data from the engine.
Logger.log("w", "Found an unknown line type: %s", i)
self._types[i] = self.NoneType
self._data = data
self._line_widths = line_widths
@ -236,7 +240,8 @@ class LayerPolygon:
theme.getColor("layerview_support_infill").getRgbF(), # SupportInfillType
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
theme.getColor("layerview_support_interface").getRgbF() # SupportInterfaceType
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
theme.getColor("layerview_prime_tower").getRgbF()
])
return cls.__color_map

View file

@ -302,6 +302,10 @@ class MaterialManager(QObject):
def getMaterialGroupListByGUID(self, guid: str) -> Optional[List[MaterialGroup]]:
return self._guid_material_groups_map.get(guid)
# Returns a dict of all material groups organized by root_material_id.
def getAllMaterialGroups(self) -> Dict[str, "MaterialGroup"]:
return self._material_group_map
#
# Return a dict with all root material IDs (k) and ContainerNodes (v) that's suitable for the given setup.
#
@ -679,7 +683,11 @@ class MaterialManager(QObject):
@pyqtSlot(str)
def removeFavorite(self, root_material_id: str) -> None:
self._favorites.remove(root_material_id)
try:
self._favorites.remove(root_material_id)
except KeyError:
Logger.log("w", "Could not delete material %s from favorites as it was already deleted", root_material_id)
return
self.materialsUpdated.emit()
# Ensure all settings are saved.
@ -688,4 +696,4 @@ class MaterialManager(QObject):
@pyqtSlot()
def getFavorites(self):
return self._favorites
return self._favorites

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QTimer
from UM.Application import Application
from UM.Logger import Logger
@ -39,15 +39,23 @@ class QualityProfilesDropDownMenuModel(ListModel):
self._machine_manager = self._application.getMachineManager()
self._quality_manager = Application.getInstance().getQualityManager()
self._application.globalContainerStackChanged.connect(self._update)
self._machine_manager.activeQualityGroupChanged.connect(self._update)
self._machine_manager.extruderChanged.connect(self._update)
self._quality_manager.qualitiesUpdated.connect(self._update)
self._application.globalContainerStackChanged.connect(self._onChange)
self._machine_manager.activeQualityGroupChanged.connect(self._onChange)
self._machine_manager.extruderChanged.connect(self._onChange)
self._quality_manager.qualitiesUpdated.connect(self._onChange)
self._layer_height_unit = "" # This is cached
self._update_timer = QTimer() # type: QTimer
self._update_timer.setInterval(100)
self._update_timer.setSingleShot(True)
self._update_timer.timeout.connect(self._update)
self._update()
def _onChange(self) -> None:
self._update_timer.start()
def _update(self):
Logger.log("d", "Updating {model_class_name}.".format(model_class_name = self.__class__.__name__))

View file

@ -6,6 +6,7 @@ from typing import Optional, List
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
from UM.Logger import Logger
from UM.Preferences import Preferences
from UM.Resources import Resources
from UM.i18n import i18nCatalog
@ -18,14 +19,20 @@ class SettingVisibilityPresetsModel(QObject):
onItemsChanged = pyqtSignal()
activePresetChanged = pyqtSignal()
def __init__(self, preferences, parent = None):
def __init__(self, preferences: Preferences, parent = None) -> None:
super().__init__(parent)
self._items = [] # type: List[SettingVisibilityPreset]
self._custom_preset = SettingVisibilityPreset(preset_id = "custom", name = "Custom selection", weight = -100)
self._populate()
basic_item = self.getVisibilityPresetById("basic")
basic_visibile_settings = ";".join(basic_item.settings)
if basic_item is not None:
basic_visibile_settings = ";".join(basic_item.settings)
else:
Logger.log("w", "Unable to find the basic visiblity preset.")
basic_visibile_settings = ""
self._preferences = preferences
@ -42,7 +49,8 @@ class SettingVisibilityPresetsModel(QObject):
visible_settings = self._preferences.getValue("general/visible_settings")
if not visible_settings:
self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item.settings))
new_visible_settings = self._active_preset_item.settings if self._active_preset_item is not None else []
self._preferences.setValue("general/visible_settings", ";".join(new_visible_settings))
else:
self._onPreferencesChanged("general/visible_settings")
@ -59,9 +67,7 @@ class SettingVisibilityPresetsModel(QObject):
def _populate(self) -> None:
from cura.CuraApplication import CuraApplication
items = [] # type: List[SettingVisibilityPreset]
custom_preset = SettingVisibilityPreset(preset_id="custom", name ="Custom selection", weight = -100)
items.append(custom_preset)
items.append(self._custom_preset)
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
setting_visibility_preset = SettingVisibilityPreset()
try:
@ -77,7 +83,7 @@ class SettingVisibilityPresetsModel(QObject):
self.setItems(items)
@pyqtProperty("QVariantList", notify = onItemsChanged)
def items(self):
def items(self) -> List[SettingVisibilityPreset]:
return self._items
def setItems(self, items: List[SettingVisibilityPreset]) -> None:
@ -87,7 +93,7 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtSlot(str)
def setActivePreset(self, preset_id: str) -> None:
if preset_id == self._active_preset_item.presetId:
if self._active_preset_item is not None and preset_id == self._active_preset_item.presetId:
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
return
@ -96,7 +102,7 @@ class SettingVisibilityPresetsModel(QObject):
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
return
need_to_save_to_custom = self._active_preset_item.presetId == "custom" and preset_id != "custom"
need_to_save_to_custom = self._active_preset_item is None or (self._active_preset_item.presetId == "custom" and preset_id != "custom")
if need_to_save_to_custom:
# Save the current visibility settings to custom
current_visibility_string = self._preferences.getValue("general/visible_settings")
@ -117,7 +123,9 @@ class SettingVisibilityPresetsModel(QObject):
@pyqtProperty(str, notify = activePresetChanged)
def activePreset(self) -> str:
return self._active_preset_item.presetId
if self._active_preset_item is not None:
return self._active_preset_item.presetId
return ""
def _onPreferencesChanged(self, name: str) -> None:
if name != "general/visible_settings":
@ -149,7 +157,12 @@ class SettingVisibilityPresetsModel(QObject):
else:
item_to_set = matching_preset_item
# If we didn't find a matching preset, fallback to custom.
if item_to_set is None:
item_to_set = self._custom_preset
if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId:
self._active_preset_item = item_to_set
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
if self._active_preset_item is not None:
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
self.activePresetChanged.emit()

View file

@ -25,7 +25,7 @@ class MultiplyObjectsJob(Job):
def run(self):
status_message = Message(i18n_catalog.i18nc("@info:status", "Multiplying and placing objects"), lifetime=0,
dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Object"))
dismissable=False, progress=0, title = i18n_catalog.i18nc("@info:title", "Placing Objects"))
status_message.show()
scene = Application.getInstance().getController().getScene()

View file

@ -52,8 +52,11 @@ class AuthorizationService:
if not self._user_profile:
# If no user profile was stored locally, we try to get it from JWT.
self._user_profile = self._parseJWT()
if not self._user_profile:
if not self._user_profile and self._auth_data:
# If there is still no user profile from the JWT, we have to log in again.
Logger.log("w", "The user profile could not be loaded. The user must log in again!")
self.deleteAuthData()
return None
return self._user_profile
@ -83,9 +86,11 @@ class AuthorizationService:
if not self.getUserProfile():
# We check if we can get the user profile.
# If we can't get it, that means the access token (JWT) was invalid or expired.
Logger.log("w", "Unable to get the user profile.")
return None
if self._auth_data is None:
Logger.log("d", "No auth data to retrieve the access_token from")
return None
return self._auth_data.access_token

View file

@ -54,7 +54,7 @@ class ConfigurationModel(QObject):
for configuration in self._extruder_configurations:
if configuration is None:
return False
return self._printer_type is not None
return self._printer_type != ""
def __str__(self):
message_chunks = []

View file

@ -4,6 +4,7 @@
from UM.FileHandler.FileHandler import FileHandler #For typing.
from UM.Logger import Logger
from UM.Scene.SceneNode import SceneNode #For typing.
from cura.API import Account
from cura.CuraApplication import CuraApplication
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, ConnectionType
@ -11,12 +12,13 @@ from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState, Conne
from PyQt5.QtNetwork import QHttpMultiPart, QHttpPart, QNetworkRequest, QNetworkAccessManager, QNetworkReply, QAuthenticator
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject, QUrl, QCoreApplication
from time import time
from typing import Any, Callable, Dict, List, Optional
from typing import Callable, Dict, List, Optional, Union
from enum import IntEnum
import os # To get the username
import gzip
class AuthState(IntEnum):
NotAuthenticated = 1
AuthenticationRequested = 2
@ -41,7 +43,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._api_prefix = ""
self._address = address
self._properties = properties
self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(), CuraApplication.getInstance().getVersion())
self._user_agent = "%s/%s " % (CuraApplication.getInstance().getApplicationName(),
CuraApplication.getInstance().getVersion())
self._onFinishedCallbacks = {} # type: Dict[str, Callable[[QNetworkReply], None]]
self._authentication_state = AuthState.NotAuthenticated
@ -55,7 +58,8 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._gcode = [] # type: List[str]
self._connection_state_before_timeout = None # type: Optional[ConnectionState]
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False,
file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
def setAuthenticationState(self, authentication_state: AuthState) -> None:
@ -143,10 +147,12 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
url = QUrl("http://" + self._address + self._api_prefix + target)
request = QNetworkRequest(url)
if content_type is not None:
request.setHeader(QNetworkRequest.ContentTypeHeader, "application/json")
request.setHeader(QNetworkRequest.ContentTypeHeader, content_type)
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:
return self._createFormPart(content_header, data, content_type)
@ -163,9 +169,15 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
part.setBody(data)
return part
## Convenience function to get the username from the OS.
# The code was copied from the getpass module, as we try to use as little dependencies as possible.
## Convenience function to get the username, either from the cloud or from the OS.
def _getUserName(self) -> str:
# check first if we are logged in with the Ultimaker Account
account = CuraApplication.getInstance().getCuraAPI().account # type: Account
if account and account.isLoggedIn:
return account.userName
# Otherwise get the username from the US
# The code below was copied from the getpass module, as we try to use as little dependencies as possible.
for name in ("LOGNAME", "USER", "LNAME", "USERNAME"):
user = os.environ.get(name)
if user:
@ -181,49 +193,89 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
self._createNetworkManager()
assert (self._manager is not None)
def put(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], 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.
def put(self, url: str, data: Union[str, bytes], content_type: Optional[str] = None,
on_finished: Optional[Callable[[QNetworkReply], None]] = None,
on_progress: Optional[Callable[[int, int], None]] = None) -> None:
self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
if self._manager is not None:
reply = self._manager.put(request, data.encode())
self._registerOnFinishedCallback(reply, on_finished)
else:
Logger.log("e", "Could not find manager.")
def delete(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
request = self._createEmptyRequest(url, content_type = content_type)
self._last_request_time = time()
if not self._manager:
Logger.log("e", "No network manager was created to execute the PUT call with.")
return
body = data if isinstance(data, bytes) else data.encode() # type: bytes
reply = self._manager.put(request, body)
self._registerOnFinishedCallback(reply, on_finished)
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:
self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
if self._manager is not None:
reply = self._manager.deleteResource(request)
self._registerOnFinishedCallback(reply, on_finished)
else:
Logger.log("e", "Could not find manager.")
def get(self, target: str, on_finished: Optional[Callable[[QNetworkReply], None]]) -> None:
request = self._createEmptyRequest(url)
self._last_request_time = time()
if not self._manager:
Logger.log("e", "No network manager was created to execute the DELETE call with.")
return
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:
self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
if self._manager is not None:
reply = self._manager.get(request)
self._registerOnFinishedCallback(reply, on_finished)
else:
Logger.log("e", "Could not find manager.")
def post(self, target: str, data: str, on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> None:
request = self._createEmptyRequest(url)
self._last_request_time = time()
if not self._manager:
Logger.log("e", "No network manager was created to execute the GET call with.")
return
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:
self._validateManager()
request = self._createEmptyRequest(target)
self._last_request_time = time()
if self._manager is not None:
reply = self._manager.post(request, data.encode())
if on_progress is not None:
reply.uploadProgress.connect(on_progress)
self._registerOnFinishedCallback(reply, on_finished)
else:
Logger.log("e", "Could not find manager.")
def postFormWithParts(self, target: str, parts: List[QHttpPart], on_finished: Optional[Callable[[QNetworkReply], None]], on_progress: Callable = None) -> QNetworkReply:
request = self._createEmptyRequest(url)
self._last_request_time = time()
if not self._manager:
Logger.log("e", "Could not find manager.")
return
body = data if isinstance(data, bytes) else data.encode() # type: bytes
reply = self._manager.post(request, body)
if on_progress is not None:
reply.uploadProgress.connect(on_progress)
self._registerOnFinishedCallback(reply, on_finished)
def postFormWithParts(self, target: str, parts: List[QHttpPart],
on_finished: Optional[Callable[[QNetworkReply], None]],
on_progress: Optional[Callable[[int, int], None]] = None) -> QNetworkReply:
self._validateManager()
request = self._createEmptyRequest(target, content_type=None)
multi_post_part = QHttpMultiPart(QHttpMultiPart.FormDataType)

View file

@ -132,9 +132,9 @@ class PrintJobOutputModel(QObject):
@pyqtProperty(float, notify = timeElapsedChanged)
def progress(self) -> float:
result = self.timeElapsed / self.timeTotal
# Never get a progress past 1.0
return min(result, 1.0)
time_elapsed = max(float(self.timeElapsed), 1.0) # Prevent a division by zero exception
result = time_elapsed / self.timeTotal
return min(result, 1.0) # Never get a progress past 1.0
@pyqtProperty(str, notify=stateChanged)
def state(self) -> str:

View file

@ -1,10 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from enum import IntEnum
from typing import Callable, List, Optional, Union
from UM.Decorators import deprecated
from UM.i18n import i18nCatalog
from UM.OutputDevice.OutputDevice import OutputDevice
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl, Q_ENUMS
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from UM.Logger import Logger
@ -12,9 +14,6 @@ from UM.Signal import signalemitter
from UM.Qt.QtApplication import QtApplication
from UM.FlameProfiler import pyqtSlot
from enum import IntEnum # For the connection state tracking.
from typing import Callable, List, Optional, Union
MYPY = False
if MYPY:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
@ -36,7 +35,7 @@ class ConnectionState(IntEnum):
class ConnectionType(IntEnum):
Unknown = 0
NotConnected = 0
UsbConnection = 1
NetworkConnection = 2
CloudConnection = 3
@ -54,10 +53,6 @@ class ConnectionType(IntEnum):
@signalemitter
class PrinterOutputDevice(QObject, OutputDevice):
# Put ConnectionType here with Q_ENUMS() so it can be registered as a QML type and accessible via QML, and there is
# no need to remember what those Enum integer values mean.
Q_ENUMS(ConnectionType)
printersChanged = pyqtSignal()
connectionStateChanged = pyqtSignal(str)
acceptsCommandsChanged = pyqtSignal()
@ -74,34 +69,34 @@ class PrinterOutputDevice(QObject, OutputDevice):
# Signal to indicate that the configuration of one of the printers has changed.
uniqueConfigurationsChanged = pyqtSignal()
def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.Unknown, parent: QObject = None) -> None:
def __init__(self, device_id: str, connection_type: "ConnectionType" = ConnectionType.NotConnected, parent: QObject = None) -> None:
super().__init__(device_id = device_id, parent = parent) # type: ignore # MyPy complains with the multiple inheritance
self._printers = [] # type: List[PrinterOutputModel]
self._unique_configurations = [] # type: List[ConfigurationModel]
self._monitor_view_qml_path = "" #type: str
self._monitor_component = None #type: Optional[QObject]
self._monitor_item = None #type: Optional[QObject]
self._monitor_view_qml_path = "" # type: str
self._monitor_component = None # type: Optional[QObject]
self._monitor_item = None # type: Optional[QObject]
self._control_view_qml_path = "" #type: str
self._control_component = None #type: Optional[QObject]
self._control_item = None #type: Optional[QObject]
self._control_view_qml_path = "" # type: str
self._control_component = None # type: Optional[QObject]
self._control_item = None # type: Optional[QObject]
self._accepts_commands = False #type: bool
self._accepts_commands = False # type: bool
self._update_timer = QTimer() #type: QTimer
self._update_timer = QTimer() # type: QTimer
self._update_timer.setInterval(2000) # TODO; Add preference for update interval
self._update_timer.setSingleShot(False)
self._update_timer.timeout.connect(self._update)
self._connection_state = ConnectionState.Closed #type: ConnectionState
self._connection_type = connection_type
self._connection_state = ConnectionState.Closed # type: ConnectionState
self._connection_type = connection_type # type: ConnectionType
self._firmware_updater = None #type: Optional[FirmwareUpdater]
self._firmware_name = None #type: Optional[str]
self._address = "" #type: str
self._connection_text = "" #type: str
self._firmware_updater = None # type: Optional[FirmwareUpdater]
self._firmware_name = None # type: Optional[str]
self._address = "" # type: str
self._connection_text = "" # type: str
self.printersChanged.connect(self._onPrintersChanged)
QtApplication.getInstance().getOutputDeviceManager().outputDevicesChanged.connect(self._updateUniqueConfigurations)
@ -130,10 +125,11 @@ class PrinterOutputDevice(QObject, OutputDevice):
self._connection_state = connection_state
self.connectionStateChanged.emit(self._id)
def getConnectionType(self) -> "ConnectionType":
@pyqtProperty(int, constant = True)
def connectionType(self) -> "ConnectionType":
return self._connection_type
@pyqtProperty(str, notify = connectionStateChanged)
@pyqtProperty(int, notify = connectionStateChanged)
def connectionState(self) -> "ConnectionState":
return self._connection_state
@ -147,7 +143,8 @@ class PrinterOutputDevice(QObject, OutputDevice):
return None
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False,
file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
raise NotImplementedError("requestWrite needs to be implemented")
@pyqtProperty(QObject, notify = printersChanged)
@ -223,8 +220,10 @@ class PrinterOutputDevice(QObject, OutputDevice):
return self._unique_configurations
def _updateUniqueConfigurations(self) -> None:
self._unique_configurations = list(set([printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None]))
self._unique_configurations.sort(key = lambda k: k.printerType)
self._unique_configurations = sorted(
{printer.printerConfiguration for printer in self._printers if printer.printerConfiguration is not None},
key=lambda config: config.printerType,
)
self.uniqueConfigurationsChanged.emit()
# Returns the unique configurations of the printers within this output device

View file

@ -242,7 +242,7 @@ class ConvexHullDecorator(SceneNodeDecorator):
# See http://stackoverflow.com/questions/16970982/find-unique-rows-in-numpy-array
vertex_byte_view = numpy.ascontiguousarray(vertex_data).view(
numpy.dtype((numpy.void, vertex_data.dtype.itemsize * vertex_data.shape[1])))
_, idx = numpy.unique(vertex_byte_view, return_index=True)
_, idx = numpy.unique(vertex_byte_view, return_index = True)
vertex_data = vertex_data[idx] # Select the unique rows by index.
hull = Polygon(vertex_data)
@ -289,16 +289,21 @@ class ConvexHullDecorator(SceneNodeDecorator):
# Add extra margin depending on adhesion type
adhesion_type = self._global_stack.getProperty("adhesion_type", "value")
max_length_available = 0.5 * min(
self._getSettingProperty("machine_width", "value"),
self._getSettingProperty("machine_depth", "value")
)
if adhesion_type == "raft":
extra_margin = max(0, self._getSettingProperty("raft_margin", "value"))
extra_margin = min(max_length_available, max(0, self._getSettingProperty("raft_margin", "value")))
elif adhesion_type == "brim":
extra_margin = max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
extra_margin = min(max_length_available, max(0, self._getSettingProperty("brim_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")))
elif adhesion_type == "none":
extra_margin = 0
elif adhesion_type == "skirt":
extra_margin = max(
extra_margin = min(max_length_available, max(
0, self._getSettingProperty("skirt_gap", "value") +
self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value"))
self._getSettingProperty("skirt_line_count", "value") * self._getSettingProperty("skirt_brim_line_width", "value")))
else:
raise Exception("Unknown bed adhesion type. Did you forget to update the convex hull calculations for your new bed adhesion type?")

View file

@ -54,7 +54,7 @@ class ConvexHullNode(SceneNode):
if hull_mesh_builder.addConvexPolygonExtrusion(
self._hull.getPoints()[::-1], # bottom layer is reversed
self._mesh_height-thickness, self._mesh_height, color=self._color):
self._mesh_height - thickness, self._mesh_height, color = self._color):
hull_mesh = hull_mesh_builder.build()
self.setMeshData(hull_mesh)

View file

@ -302,12 +302,7 @@ class ExtruderManager(QObject):
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
return []
result_tuple_list = sorted(list(global_stack.extruders.items()), key = lambda x: int(x[0]))
result_list = [item[1] for item in result_tuple_list]
machine_extruder_count = global_stack.getProperty("machine_extruder_count", "value")
return result_list[:machine_extruder_count]
return global_stack.extruderList
def _globalContainerStackChanged(self) -> None:
# If the global container changed, the machine changed and might have extruders that were not registered yet

View file

@ -97,6 +97,7 @@ class GlobalStack(CuraContainerStack):
return
self._extruders[position] = extruder
self.extrudersChanged.emit()
Logger.log("i", "Extruder[%s] added to [%s] at position [%s]", extruder.id, self.id, position)
## Overridden from ContainerStack

View file

@ -64,8 +64,6 @@ class MachineManager(QObject):
self._default_extruder_position = "0" # to be updated when extruders are switched on and off
self.machine_extruder_material_update_dict = collections.defaultdict(list) #type: Dict[str, List[Callable[[], None]]]
self._instance_container_timer = QTimer() # type: QTimer
self._instance_container_timer.setInterval(250)
self._instance_container_timer.setSingleShot(True)
@ -178,6 +176,7 @@ class MachineManager(QObject):
self._printer_output_devices.append(printer_output_device)
self.outputDevicesChanged.emit()
self.printerConnectedStatusChanged.emit()
@pyqtProperty(QObject, notify = currentConfigurationChanged)
def currentConfiguration(self) -> ConfigurationModel:
@ -275,11 +274,6 @@ class MachineManager(QObject):
extruder_stack.propertyChanged.connect(self._onPropertyChanged)
extruder_stack.containersChanged.connect(self._onContainersChanged)
if self._global_container_stack.getId() in self.machine_extruder_material_update_dict:
for func in self.machine_extruder_material_update_dict[self._global_container_stack.getId()]:
self._application.callLater(func)
del self.machine_extruder_material_update_dict[self._global_container_stack.getId()]
self.activeQualityGroupChanged.emit()
def _onActiveExtruderStackChanged(self) -> None:
@ -443,12 +437,12 @@ class MachineManager(QObject):
if not self._global_container_stack:
return False
if self._global_container_stack.getTop().findInstances():
if self._global_container_stack.getTop().getNumInstances() != 0:
return True
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
for stack in stacks:
if stack.getTop().findInstances():
if stack.getTop().getNumInstances() != 0:
return True
return False
@ -458,10 +452,10 @@ class MachineManager(QObject):
if not self._global_container_stack:
return 0
num_user_settings = 0
num_user_settings += len(self._global_container_stack.getTop().findInstances())
stacks = ExtruderManager.getInstance().getActiveExtruderStacks()
num_user_settings += self._global_container_stack.getTop().getNumInstances()
stacks = self._global_container_stack.extruderList
for stack in stacks:
num_user_settings += len(stack.getTop().findInstances())
num_user_settings += stack.getTop().getNumInstances()
return num_user_settings
## Delete a user setting from the global stack and all extruder stacks.
@ -521,16 +515,30 @@ class MachineManager(QObject):
return ""
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def printerConnected(self):
def printerConnected(self) -> bool:
return bool(self._printer_output_devices)
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineHasRemoteConnection(self) -> bool:
if self._global_container_stack:
connection_type = self._global_container_stack.getMetaDataEntry("connection_type")
connection_type = int(self._global_container_stack.getMetaDataEntry("connection_type", ConnectionType.NotConnected.value))
return connection_type in [ConnectionType.NetworkConnection.value, ConnectionType.CloudConnection.value]
return False
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineIsGroup(self) -> bool:
return bool(self._printer_output_devices) and len(self._printer_output_devices[0].printers) > 1
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineHasActiveNetworkConnection(self) -> bool:
# A network connection is only available if any output device is actually a network connected device.
return any(d.connectionType == ConnectionType.NetworkConnection for d in self._printer_output_devices)
@pyqtProperty(bool, notify = printerConnectedStatusChanged)
def activeMachineHasActiveCloudConnection(self) -> bool:
# A cloud connection is only available if any output device actually is a cloud connected device.
return any(d.connectionType == ConnectionType.CloudConnection for d in self._printer_output_devices)
def activeMachineNetworkKey(self) -> str:
if self._global_container_stack:
return self._global_container_stack.getMetaDataEntry("um_network_key", "")

View file

@ -0,0 +1,30 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
# ---------
# Constants used for the Cloud API
# ---------
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" # type: str
DEFAULT_CLOUD_API_VERSION = "1" # type: str
DEFAULT_CLOUD_ACCOUNT_API_ROOT = "https://account.ultimaker.com" # type: str
try:
from cura.CuraVersion import CuraCloudAPIRoot # type: ignore
if CuraCloudAPIRoot == "":
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
except ImportError:
CuraCloudAPIRoot = DEFAULT_CLOUD_API_ROOT
try:
from cura.CuraVersion import CuraCloudAPIVersion # type: ignore
if CuraCloudAPIVersion == "":
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
except ImportError:
CuraCloudAPIVersion = DEFAULT_CLOUD_API_VERSION
try:
from cura.CuraVersion import CuraCloudAccountAPIRoot # type: ignore
if CuraCloudAccountAPIRoot == "":
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT
except ImportError:
CuraCloudAccountAPIRoot = DEFAULT_CLOUD_ACCOUNT_API_ROOT