Merge branch 'master' into master

This commit is contained in:
griehsler 2018-10-31 21:44:44 +01:00 committed by GitHub
commit 3a84a5868f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
220 changed files with 30385 additions and 25983 deletions

View file

@ -45,7 +45,7 @@ class Account(QObject):
CALLBACK_PORT=self._callback_port,
CALLBACK_URL="http://localhost:{}/callback".format(self._callback_port),
CLIENT_ID="um---------------ultimaker_cura_drive_plugin",
CLIENT_SCOPES="user.read drive.backups.read drive.backups.write",
CLIENT_SCOPES="account.user.read drive.backup.read drive.backup.write packages.download packages.rating.read packages.rating.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)

View file

@ -1,40 +0,0 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtGui import QImage
from PyQt5.QtQuick import QQuickImageProvider
from PyQt5.QtCore import QSize
from UM.Application import Application
## Creates screenshots of the current scene.
class CameraImageProvider(QQuickImageProvider):
def __init__(self):
super().__init__(QQuickImageProvider.Image)
## Request a new image.
#
# The image will be taken using the current camera position.
# Only the actual objects in the scene will get rendered. Not the build
# plate and such!
# \param id The ID for the image to create. This is the requested image
# source, with the "image:" scheme and provider identifier removed. It's
# a Qt thing, they'll provide this parameter.
# \param size The dimensions of the image to scale to.
def requestImage(self, id, size):
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
try:
image = output_device.activePrinter.camera.getImage()
if image.isNull():
image = QImage()
return image, QSize(15, 15)
except AttributeError:
try:
image = output_device.activeCamera.getImage()
return image, QSize(15, 15)
except AttributeError:
pass
return QImage(), QSize(15, 15)

View file

@ -96,7 +96,6 @@ from . import PrintInformation
from . import CuraActions
from cura.Scene import ZOffsetDecorator
from . import CuraSplashScreen
from . import CameraImageProvider
from . import PrintJobPreviewImageProvider
from . import MachineActionManager
@ -114,6 +113,8 @@ from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
from cura.ObjectsModel import ObjectsModel
from cura.PrinterOutput.NetworkMJPGImage import NetworkMJPGImage
from UM.FlameProfiler import pyqtSlot
from UM.Decorators import override
@ -168,6 +169,8 @@ class CuraApplication(QtApplication):
self.default_theme = "cura-light"
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
self._boot_loading_time = time.time()
self._on_exit_callback_manager = OnExitCallbackManager(self)
@ -304,8 +307,6 @@ class CuraApplication(QtApplication):
self._machine_action_manager = MachineActionManager.MachineActionManager(self)
self._machine_action_manager.initialize()
self.change_log_url = "https://ultimaker.com/ultimaker-cura-latest-features"
def __sendCommandToSingleInstance(self):
self._single_instance = SingleInstance(self, self._files_to_open)
@ -428,34 +429,30 @@ class CuraApplication(QtApplication):
self.setRequiredPlugins([
# Misc.:
"ConsoleLogger",
"CuraEngineBackend",
"UserAgreement",
"FileLogger",
"XmlMaterialProfile",
"Toolbox",
"PrepareStage",
"MonitorStage",
"LocalFileOutputDevice",
"LocalContainerProvider",
"ConsoleLogger", #You want to be able to read the log if something goes wrong.
"CuraEngineBackend", #Cura is useless without this one since you can't slice.
"UserAgreement", #Our lawyers want every user to see this at least once.
"FileLogger", #You want to be able to read the log if something goes wrong.
"XmlMaterialProfile", #Cura crashes without this one.
"Toolbox", #This contains the interface to enable/disable plug-ins, so if you disable it you can't enable it back.
"PrepareStage", #Cura is useless without this one since you can't load models.
"MonitorStage", #Major part of Cura's functionality.
"LocalFileOutputDevice", #Major part of Cura's functionality.
"LocalContainerProvider", #Cura is useless without any profiles or setting definitions.
# Views:
"SimpleView",
"SimulationView",
"SolidView",
"SimpleView", #Dependency of SolidView.
"SolidView", #Displays models. Cura is useless without it.
# Readers & Writers:
"GCodeWriter",
"STLReader",
"3MFWriter",
"GCodeWriter", #Cura is useless if it can't write its output.
"STLReader", #Most common model format, so disabling this makes Cura 90% useless.
"3MFWriter", #Required for writing project files.
# Tools:
"CameraTool",
"MirrorTool",
"RotateTool",
"ScaleTool",
"SelectionTool",
"TranslateTool",
"CameraTool", #Needed to see the scene. Cura is useless without it.
"SelectionTool", #Dependency of the rest of the tools.
"TranslateTool", #You'll need this for almost every print.
])
self._i18n_catalog = i18nCatalog("cura")
@ -523,7 +520,6 @@ class CuraApplication(QtApplication):
CuraApplication.Created = True
def _onEngineCreated(self):
self._qml_engine.addImageProvider("camera", CameraImageProvider.CameraImageProvider())
self._qml_engine.addImageProvider("print_job_preview", PrintJobPreviewImageProvider.PrintJobPreviewImageProvider())
@pyqtProperty(bool)
@ -701,7 +697,7 @@ class CuraApplication(QtApplication):
self._quality_manager.initialize()
Logger.log("i", "Initializing machine manager")
self._machine_manager = MachineManager(self)
self._machine_manager = MachineManager(self, parent = self)
Logger.log("i", "Initializing container manager")
self._container_manager = ContainerManager(self)
@ -947,6 +943,8 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterType(NetworkMJPGImage, "Cura", 1, 0, "NetworkMJPGImage")
qmlRegisterSingletonType(ObjectsModel, "Cura", 1, 0, "ObjectsModel", self.getObjectsModel)
qmlRegisterType(BuildPlateModel, "Cura", 1, 0, "BuildPlateModel")
qmlRegisterType(MultiBuildPlateModel, "Cura", 1, 0, "MultiBuildPlateModel")

View file

@ -1,13 +1,13 @@
# Copyright (c) 2016 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import os
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger
from UM.PluginObject import PluginObject
from UM.PluginRegistry import PluginRegistry
from UM.Application import Application
import os
## Machine actions are actions that are added to a specific machine type. Examples of such actions are
@ -19,7 +19,7 @@ class MachineAction(QObject, PluginObject):
## 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, label = ""):
def __init__(self, key: str, label: str = "") -> None:
super().__init__()
self._key = key
self._label = label
@ -30,14 +30,14 @@ class MachineAction(QObject, PluginObject):
labelChanged = pyqtSignal()
onFinished = pyqtSignal()
def getKey(self):
def getKey(self) -> str:
return self._key
@pyqtProperty(str, notify = labelChanged)
def label(self):
def label(self) -> str:
return self._label
def setLabel(self, label):
def setLabel(self, label: str) -> None:
if self._label != label:
self._label = label
self.labelChanged.emit()
@ -46,29 +46,35 @@ class MachineAction(QObject, PluginObject):
# This should not be re-implemented by child classes, instead re-implement _reset.
# /sa _reset
@pyqtSlot()
def reset(self):
def reset(self) -> None:
self._finished = False
self._reset()
## Protected implementation of reset.
# /sa reset()
def _reset(self):
def _reset(self) -> None:
pass
@pyqtSlot()
def setFinished(self):
def setFinished(self) -> None:
self._finished = True
self._reset()
self.onFinished.emit()
@pyqtProperty(bool, notify = onFinished)
def finished(self):
def finished(self) -> bool:
return self._finished
## Protected helper to create a view object based on provided QML.
def _createViewFromQML(self):
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), self._qml_url)
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
def _createViewFromQML(self) -> None:
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
return
path = os.path.join(plugin_path, self._qml_url)
from cura.CuraApplication import CuraApplication
self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
@pyqtProperty(QObject, constant = True)
def displayItem(self):

View file

@ -1,12 +1,18 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import TYPE_CHECKING, Optional, List, Set, Dict
from PyQt5.QtCore import QObject
from UM.FlameProfiler import pyqtSlot
from UM.Logger import Logger
from UM.PluginRegistry import PluginRegistry # So MachineAction can be added as plugin type
from UM.Settings.DefinitionContainer import DefinitionContainer
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.Settings.GlobalStack import GlobalStack
from .MachineAction import MachineAction
## Raised when trying to add an unknown machine action as a required action
@ -20,46 +26,54 @@ class NotUniqueMachineActionError(Exception):
class MachineActionManager(QObject):
def __init__(self, application, parent = None):
super().__init__(parent)
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent = parent)
self._application = application
self._container_registry = self._application.getContainerRegistry()
self._machine_actions = {} # Dict of all known machine actions
self._required_actions = {} # Dict of all required actions by definition ID
self._supported_actions = {} # Dict of all supported actions by definition ID
self._first_start_actions = {} # Dict of all actions that need to be done when first added by definition ID
# Keeps track of which machines have already been processed so we don't do that again.
self._definition_ids_with_default_actions_added = set() # type: Set[str]
# Dict of all known machine actions
self._machine_actions = {} # type: Dict[str, MachineAction]
# Dict of all required actions by definition ID
self._required_actions = {} # type: Dict[str, List[MachineAction]]
# Dict of all supported actions by definition ID
self._supported_actions = {} # type: Dict[str, List[MachineAction]]
# Dict of all actions that need to be done when first added by definition ID
self._first_start_actions = {} # type: Dict[str, List[MachineAction]]
def initialize(self):
container_registry = self._application.getContainerRegistry()
# Add machine_action as plugin type
PluginRegistry.addType("machine_action", self.addMachineAction)
# Ensure that all containers that were registered before creation of this registry are also handled.
# This should not have any effect, but it makes it safer if we ever refactor the order of things.
for container in container_registry.findDefinitionContainers():
self._onContainerAdded(container)
# Adds all default machine actions that are defined in the machine definition for the given machine.
def addDefaultMachineActions(self, global_stack: "GlobalStack") -> None:
definition_id = global_stack.definition.getId()
container_registry.containerAdded.connect(self._onContainerAdded)
if definition_id in self._definition_ids_with_default_actions_added:
Logger.log("i", "Default machine actions have been added for machine definition [%s], do nothing.",
definition_id)
return
def _onContainerAdded(self, container):
## Ensure that the actions are added to this manager
if isinstance(container, DefinitionContainer):
supported_actions = container.getMetaDataEntry("supported_actions", [])
for action in supported_actions:
self.addSupportedAction(container.getId(), action)
supported_actions = global_stack.getMetaDataEntry("supported_actions", [])
for action_key in supported_actions:
self.addSupportedAction(definition_id, action_key)
required_actions = container.getMetaDataEntry("required_actions", [])
for action in required_actions:
self.addRequiredAction(container.getId(), action)
required_actions = global_stack.getMetaDataEntry("required_actions", [])
for action_key in required_actions:
self.addRequiredAction(definition_id, action_key)
first_start_actions = container.getMetaDataEntry("first_start_actions", [])
for action in first_start_actions:
self.addFirstStartAction(container.getId(), action)
first_start_actions = global_stack.getMetaDataEntry("first_start_actions", [])
for action_key in first_start_actions:
self.addFirstStartAction(definition_id, action_key)
self._definition_ids_with_default_actions_added.add(definition_id)
Logger.log("i", "Default machine actions added for machine definition [%s]", definition_id)
## Add a required action to a machine
# Raises an exception when the action is not recognised.
def addRequiredAction(self, definition_id, action_key):
def addRequiredAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._required_actions:
if self._machine_actions[action_key] not in self._required_actions[definition_id]:
@ -70,7 +84,7 @@ class MachineActionManager(QObject):
raise UnknownMachineActionError("Action %s, which is required for %s is not known." % (action_key, definition_id))
## Add a supported action to a machine.
def addSupportedAction(self, definition_id, action_key):
def addSupportedAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._supported_actions:
if self._machine_actions[action_key] not in self._supported_actions[definition_id]:
@ -81,13 +95,10 @@ class MachineActionManager(QObject):
Logger.log("w", "Unable to add %s to %s, as the action is not recognised", action_key, definition_id)
## Add an action to the first start list of a machine.
def addFirstStartAction(self, definition_id, action_key, index = None):
def addFirstStartAction(self, definition_id: str, action_key: str) -> None:
if action_key in self._machine_actions:
if definition_id in self._first_start_actions:
if index is not None:
self._first_start_actions[definition_id].insert(index, self._machine_actions[action_key])
else:
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
self._first_start_actions[definition_id].append(self._machine_actions[action_key])
else:
self._first_start_actions[definition_id] = [self._machine_actions[action_key]]
else:
@ -95,7 +106,7 @@ class MachineActionManager(QObject):
## Add a (unique) MachineAction
# if the Key of the action is not unique, an exception is raised.
def addMachineAction(self, action):
def addMachineAction(self, action: "MachineAction") -> None:
if action.getKey() not in self._machine_actions:
self._machine_actions[action.getKey()] = action
else:
@ -105,7 +116,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition you want the supported actions of
# \returns set of supported actions.
@pyqtSlot(str, result = "QVariantList")
def getSupportedActions(self, definition_id):
def getSupportedActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._supported_actions:
return list(self._supported_actions[definition_id])
else:
@ -114,11 +125,11 @@ class MachineActionManager(QObject):
## Get all actions required by given machine
# \param definition_id The ID of the definition you want the required actions of
# \returns set of required actions.
def getRequiredActions(self, definition_id):
def getRequiredActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._required_actions:
return self._required_actions[definition_id]
else:
return set()
return list()
## Get all actions that need to be performed upon first start of a given machine.
# Note that contrary to required / supported actions a list is returned (as it could be required to run the same
@ -126,7 +137,7 @@ class MachineActionManager(QObject):
# \param definition_id The ID of the definition that you want to get the "on added" actions for.
# \returns List of actions.
@pyqtSlot(str, result="QVariantList")
def getFirstStartActions(self, definition_id):
def getFirstStartActions(self, definition_id: str) -> List["MachineAction"]:
if definition_id in self._first_start_actions:
return self._first_start_actions[definition_id]
else:
@ -134,7 +145,7 @@ class MachineActionManager(QObject):
## Remove Machine action from manager
# \param action to remove
def removeMachineAction(self, action):
def removeMachineAction(self, action: "MachineAction") -> None:
try:
del self._machine_actions[action.getKey()]
except KeyError:
@ -143,7 +154,7 @@ class MachineActionManager(QObject):
## Get MachineAction by key
# \param key String of key to select
# \return Machine action if found, None otherwise
def getMachineAction(self, key):
def getMachineAction(self, key: str) -> Optional["MachineAction"]:
if key in self._machine_actions:
return self._machine_actions[key]
else:

View file

@ -365,7 +365,7 @@ class MaterialManager(QObject):
nozzle_name = None
if extruder_stack.variant.getId() != "empty_variant":
nozzle_name = extruder_stack.variant.getName()
diameter = extruder_stack.approximateMaterialDiameter
diameter = extruder_stack.getApproximateMaterialDiameter()
# Fetch the available materials (ContainerNode) for the current active machine and extruder setup.
return self.getAvailableMaterials(machine.definition, nozzle_name, buildplate_name, diameter)
@ -478,12 +478,22 @@ class MaterialManager(QObject):
buildplate_name = global_stack.getBuildplateName()
machine_definition = global_stack.definition
if extruder_definition is None:
extruder_definition = global_stack.extruders[position].definition
if extruder_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
# At this point the extruder_definition is not None
material_diameter = extruder_definition.getProperty("material_diameter", "value")
# The extruder-compatible material diameter in the extruder definition may not be the correct value because
# the user can change it in the definition_changes container.
if extruder_definition is None:
extruder_stack_or_definition = global_stack.extruders[position]
is_extruder_stack = True
else:
extruder_stack_or_definition = extruder_definition
is_extruder_stack = False
if extruder_stack_or_definition and parseBool(global_stack.getMetaDataEntry("has_materials", False)):
if is_extruder_stack:
material_diameter = extruder_stack_or_definition.getCompatibleMaterialDiameter()
else:
material_diameter = extruder_stack_or_definition.getProperty("material_diameter", "value")
if isinstance(material_diameter, SettingFunction):
material_diameter = material_diameter(global_stack)
approximate_material_diameter = str(round(material_diameter))

View file

@ -64,9 +64,11 @@ class BaseMaterialsModel(ListModel):
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.disconnect(self._update)
self._extruder_stack.approximateMaterialDiameterChanged.disconnect(self._update)
self._extruder_stack = global_stack.extruders.get(str(self._extruder_position))
if self._extruder_stack is not None:
self._extruder_stack.pyqtContainersChanged.connect(self._update)
self._extruder_stack.approximateMaterialDiameterChanged.connect(self._update)
# Force update the model when the extruder stack changes
self._update()

View file

@ -1,119 +0,0 @@
from UM.Logger import Logger
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, QObject, pyqtSlot
from PyQt5.QtGui import QImage
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
class NetworkCamera(QObject):
newImage = pyqtSignal()
def __init__(self, target = None, parent = None):
super().__init__(parent)
self._stream_buffer = b""
self._stream_buffer_start_index = -1
self._manager = None
self._image_request = None
self._image_reply = None
self._image = QImage()
self._image_id = 0
self._target = target
self._started = False
@pyqtSlot(str)
def setTarget(self, target):
restart_required = False
if self._started:
self.stop()
restart_required = True
self._target = target
if restart_required:
self.start()
@pyqtProperty(QUrl, notify=newImage)
def latestImage(self):
self._image_id += 1
# There is an image provider that is called "camera". In order to ensure that the image qml object, that
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
# as new (instead of relying on cached version and thus forces an update.
temp = "image://camera/" + str(self._image_id)
return QUrl(temp, QUrl.TolerantMode)
@pyqtSlot()
def start(self):
# Ensure that previous requests (if any) are stopped.
self.stop()
if self._target is None:
Logger.log("w", "Unable to start camera stream without target!")
return
self._started = True
url = QUrl(self._target)
self._image_request = QNetworkRequest(url)
if self._manager is None:
self._manager = QNetworkAccessManager()
self._image_reply = self._manager.get(self._image_request)
self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
@pyqtSlot()
def stop(self):
self._stream_buffer = b""
self._stream_buffer_start_index = -1
if self._image_reply:
try:
# disconnect the signal
try:
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
except Exception:
pass
# abort the request if it's not finished
if not self._image_reply.isFinished():
self._image_reply.close()
except Exception as e: # RuntimeError
pass # It can happen that the wrapped c++ object is already deleted.
self._image_reply = None
self._image_request = None
self._manager = None
self._started = False
def getImage(self):
return self._image
## Ensure that close gets called when object is destroyed
def __del__(self):
self.stop()
def _onStreamDownloadProgress(self, bytes_received, bytes_total):
# An MJPG stream is (for our purpose) a stream of concatenated JPG images.
# JPG images start with the marker 0xFFD8, and end with 0xFFD9
if self._image_reply is None:
return
self._stream_buffer += self._image_reply.readAll()
if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
self.stop() # resets stream buffer and start index
self.start()
return
if self._stream_buffer_start_index == -1:
self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
# If this happens to be more than a single frame, then so be it; the JPG decoder will
# ignore the extra data. We do it like this in order not to get a buildup of frames
if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
self._stream_buffer_start_index = -1
self._image.loadFromData(jpg_data)
self.newImage.emit()

View file

@ -0,0 +1,153 @@
# Copyright (c) 2018 Aldo Hoeben / fieldOfView
# NetworkMJPGImage is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QUrl, pyqtProperty, pyqtSignal, pyqtSlot, QRect, QByteArray
from PyQt5.QtGui import QImage, QPainter
from PyQt5.QtQuick import QQuickPaintedItem
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from UM.Logger import Logger
#
# A QQuickPaintedItem that progressively downloads a network mjpeg stream,
# picks it apart in individual jpeg frames, and paints it.
#
class NetworkMJPGImage(QQuickPaintedItem):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
self._stream_buffer = QByteArray()
self._stream_buffer_start_index = -1
self._network_manager = None # type: QNetworkAccessManager
self._image_request = None # type: QNetworkRequest
self._image_reply = None # type: QNetworkReply
self._image = QImage()
self._image_rect = QRect()
self._source_url = QUrl()
self._started = False
self._mirror = False
self.setAntialiasing(True)
## Ensure that close gets called when object is destroyed
def __del__(self) -> None:
self.stop()
def paint(self, painter: "QPainter") -> None:
if self._mirror:
painter.drawImage(self.contentsBoundingRect(), self._image.mirrored())
return
painter.drawImage(self.contentsBoundingRect(), self._image)
def setSourceURL(self, source_url: "QUrl") -> None:
self._source_url = source_url
self.sourceURLChanged.emit()
if self._started:
self.start()
def getSourceURL(self) -> "QUrl":
return self._source_url
sourceURLChanged = pyqtSignal()
source = pyqtProperty(QUrl, fget = getSourceURL, fset = setSourceURL, notify = sourceURLChanged)
def setMirror(self, mirror: bool) -> None:
if mirror == self._mirror:
return
self._mirror = mirror
self.mirrorChanged.emit()
self.update()
def getMirror(self) -> bool:
return self._mirror
mirrorChanged = pyqtSignal()
mirror = pyqtProperty(bool, fget = getMirror, fset = setMirror, notify = mirrorChanged)
imageSizeChanged = pyqtSignal()
@pyqtProperty(int, notify = imageSizeChanged)
def imageWidth(self) -> int:
return self._image.width()
@pyqtProperty(int, notify = imageSizeChanged)
def imageHeight(self) -> int:
return self._image.height()
@pyqtSlot()
def start(self) -> None:
self.stop() # Ensure that previous requests (if any) are stopped.
if not self._source_url:
Logger.log("w", "Unable to start camera stream without target!")
return
self._started = True
self._image_request = QNetworkRequest(self._source_url)
if self._network_manager is None:
self._network_manager = QNetworkAccessManager()
self._image_reply = self._network_manager.get(self._image_request)
self._image_reply.downloadProgress.connect(self._onStreamDownloadProgress)
@pyqtSlot()
def stop(self) -> None:
self._stream_buffer = QByteArray()
self._stream_buffer_start_index = -1
if self._image_reply:
try:
try:
self._image_reply.downloadProgress.disconnect(self._onStreamDownloadProgress)
except Exception:
pass
if not self._image_reply.isFinished():
self._image_reply.close()
except Exception as e: # RuntimeError
pass # It can happen that the wrapped c++ object is already deleted.
self._image_reply = None
self._image_request = None
self._network_manager = None
self._started = False
def _onStreamDownloadProgress(self, bytes_received: int, bytes_total: int) -> None:
# An MJPG stream is (for our purpose) a stream of concatenated JPG images.
# JPG images start with the marker 0xFFD8, and end with 0xFFD9
if self._image_reply is None:
return
self._stream_buffer += self._image_reply.readAll()
if len(self._stream_buffer) > 2000000: # No single camera frame should be 2 Mb or larger
Logger.log("w", "MJPEG buffer exceeds reasonable size. Restarting stream...")
self.stop() # resets stream buffer and start index
self.start()
return
if self._stream_buffer_start_index == -1:
self._stream_buffer_start_index = self._stream_buffer.indexOf(b'\xff\xd8')
stream_buffer_end_index = self._stream_buffer.lastIndexOf(b'\xff\xd9')
# If this happens to be more than a single frame, then so be it; the JPG decoder will
# ignore the extra data. We do it like this in order not to get a buildup of frames
if self._stream_buffer_start_index != -1 and stream_buffer_end_index != -1:
jpg_data = self._stream_buffer[self._stream_buffer_start_index:stream_buffer_end_index + 2]
self._stream_buffer = self._stream_buffer[stream_buffer_end_index + 2:]
self._stream_buffer_start_index = -1
self._image.loadFromData(jpg_data)
if self._image.rect() != self._image_rect:
self.imageSizeChanged.emit()
self.update()

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
@ -12,7 +12,6 @@ if TYPE_CHECKING:
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
class PrintJobOutputModel(QObject):
stateChanged = pyqtSignal()
timeTotalChanged = pyqtSignal()
@ -55,7 +54,7 @@ class PrintJobOutputModel(QObject):
@pyqtProperty(QUrl, notify=previewImageChanged)
def previewImageUrl(self):
self._preview_image_id += 1
# There is an image provider that is called "camera". In order to ensure that the image qml object, that
# There is an image provider that is called "print_job_preview". In order to ensure that the image qml object, that
# requires a QUrl to function, updates correctly we add an increasing number. This causes to see the QUrl
# as new (instead of relying on cached version and thus forces an update.
temp = "image://print_job_preview/" + str(self._preview_image_id) + "/" + self._key
@ -147,4 +146,4 @@ class PrintJobOutputModel(QObject):
@pyqtSlot(str)
def setState(self, state):
self._output_controller.setJobState(self, state)
self._output_controller.setJobState(self, state)

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 pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot, QUrl
from typing import List, Dict, Optional
from UM.Math.Vector import Vector
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
@ -11,7 +11,6 @@ MYPY = False
if MYPY:
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
from cura.PrinterOutput.NetworkCamera import NetworkCamera
class PrinterOutputModel(QObject):
@ -25,7 +24,7 @@ class PrinterOutputModel(QObject):
keyChanged = pyqtSignal()
printerTypeChanged = pyqtSignal()
buildplateChanged = pyqtSignal()
cameraChanged = pyqtSignal()
cameraUrlChanged = pyqtSignal()
configurationChanged = pyqtSignal()
canUpdateFirmwareChanged = pyqtSignal()
@ -50,16 +49,20 @@ class PrinterOutputModel(QObject):
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
self._extruders]
self._camera = None
self._camera_url = QUrl() # type: QUrl
@pyqtProperty(str, constant = True)
def firmwareVersion(self) -> str:
return self._firmware_version
def setCamera(self, camera: Optional["NetworkCamera"]) -> None:
if self._camera is not camera:
self._camera = camera
self.cameraChanged.emit()
def setCameraUrl(self, camera_url: "QUrl") -> None:
if self._camera_url != camera_url:
self._camera_url = camera_url
self.cameraUrlChanged.emit()
@pyqtProperty(QUrl, fset = setCameraUrl, notify = cameraUrlChanged)
def cameraUrl(self) -> "QUrl":
return self._camera_url
def updateIsPreheating(self, pre_heating: bool) -> None:
if self._is_preheating != pre_heating:
@ -70,10 +73,6 @@ class PrinterOutputModel(QObject):
def isPreheating(self) -> bool:
return self._is_preheating
@pyqtProperty(QObject, notify=cameraChanged)
def camera(self) -> Optional["NetworkCamera"]:
return self._camera
@pyqtProperty(str, notify = printerTypeChanged)
def type(self) -> str:
return self._printer_type

View file

@ -1,9 +1,12 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Scene.SceneNodeDecorator import SceneNodeDecorator
class BlockSlicingDecorator(SceneNodeDecorator):
def __init__(self):
def __init__(self) -> None:
super().__init__()
def isBlockSlicing(self):
def isBlockSlicing(self) -> bool:
return True

View file

@ -145,13 +145,11 @@ class CuraContainerStack(ContainerStack):
def setDefinition(self, new_definition: DefinitionContainerInterface) -> None:
self.replaceContainer(_ContainerIndexes.Definition, new_definition)
## Get the definition container.
#
# \return The definition container. Should always be a valid container, but can be equal to the empty InstanceContainer.
@pyqtProperty(QObject, fset = setDefinition, notify = pyqtContainersChanged)
def definition(self) -> DefinitionContainer:
def getDefinition(self) -> "DefinitionContainer":
return cast(DefinitionContainer, self._containers[_ContainerIndexes.Definition])
definition = pyqtProperty(QObject, fget = getDefinition, fset = setDefinition, notify = pyqtContainersChanged)
@override(ContainerStack)
def getBottom(self) -> "DefinitionContainer":
return self.definition

View file

@ -129,7 +129,7 @@ class CuraStackBuilder:
# get material container for extruders
material_container = application.empty_material_container
material_node = material_manager.getDefaultMaterial(global_stack, extruder_position, extruder_variant_name,
material_node = material_manager.getDefaultMaterial(global_stack, str(extruder_position), extruder_variant_name,
extruder_definition = extruder_definition)
if material_node and material_node.getContainer():
material_container = material_node.getContainer()
@ -145,7 +145,6 @@ class CuraStackBuilder:
quality_container = application.empty_quality_container
)
new_extruder.setNextStack(global_stack)
global_stack.addExtruder(new_extruder)
registry.addContainer(new_extruder)

View file

@ -374,8 +374,6 @@ class ExtruderManager(QObject):
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
extruder_stack_0.definition = extruder_definition
extruder_stack_0.setNextStack(global_stack)
## Get all extruder values for a certain setting.
#
# This is exposed to qml for display purposes

View file

@ -65,16 +65,33 @@ class ExtruderStack(CuraContainerStack):
def getLoadingPriority(cls) -> int:
return 3
compatibleMaterialDiameterChanged = pyqtSignal()
## Return the filament diameter that the machine requires.
#
# If the machine has no requirement for the diameter, -1 is returned.
# \return The filament diameter for the printer
@property
def materialDiameter(self) -> float:
def getCompatibleMaterialDiameter(self) -> float:
context = PropertyEvaluationContext(self)
context.context["evaluate_from_container_index"] = _ContainerIndexes.Variant
return self.getProperty("material_diameter", "value", context = context)
return float(self.getProperty("material_diameter", "value", context = context))
def setCompatibleMaterialDiameter(self, value: float) -> None:
old_approximate_diameter = self.getApproximateMaterialDiameter()
if self.getCompatibleMaterialDiameter() != value:
self.definitionChanges.setProperty("material_diameter", "value", value)
self.compatibleMaterialDiameterChanged.emit()
# Emit approximate diameter changed signal if needed
if old_approximate_diameter != self.getApproximateMaterialDiameter():
self.approximateMaterialDiameterChanged.emit()
compatibleMaterialDiameter = pyqtProperty(float, fset = setCompatibleMaterialDiameter,
fget = getCompatibleMaterialDiameter,
notify = compatibleMaterialDiameterChanged)
approximateMaterialDiameterChanged = pyqtSignal()
## Return the approximate filament diameter that the machine requires.
#
@ -84,9 +101,11 @@ class ExtruderStack(CuraContainerStack):
# If the machine has no requirement for the diameter, -1 is returned.
#
# \return The approximate filament diameter for the printer
@pyqtProperty(float)
def approximateMaterialDiameter(self) -> float:
return round(float(self.materialDiameter))
def getApproximateMaterialDiameter(self) -> float:
return round(self.getCompatibleMaterialDiameter())
approximateMaterialDiameter = pyqtProperty(float, fget = getApproximateMaterialDiameter,
notify = approximateMaterialDiameterChanged)
## Overridden from ContainerStack
#

View file

@ -20,7 +20,6 @@ from UM.Message import Message
from UM.Settings.SettingFunction import SettingFunction
from UM.Signal import postponeSignals, CompressTechnique
import cura.CuraApplication
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from cura.PrinterOutputDevice import PrinterOutputDevice
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
@ -29,6 +28,9 @@ from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.cura_empty_instance_containers import (empty_definition_changes_container, empty_variant_container,
empty_material_container, empty_quality_container,
empty_quality_changes_container)
from .CuraStackBuilder import CuraStackBuilder
@ -36,6 +38,7 @@ from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.Settings.CuraContainerStack import CuraContainerStack
from cura.Settings.GlobalStack import GlobalStack
from cura.Machines.MaterialManager import MaterialManager
@ -47,7 +50,7 @@ if TYPE_CHECKING:
class MachineManager(QObject):
def __init__(self, parent: QObject = None) -> None:
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent)
self._active_container_stack = None # type: Optional[ExtruderStack]
@ -66,9 +69,10 @@ class MachineManager(QObject):
self._instance_container_timer.setSingleShot(True)
self._instance_container_timer.timeout.connect(self.__emitChangedSignals)
self._application = cura.CuraApplication.CuraApplication.getInstance() #type: cura.CuraApplication.CuraApplication
self._application = application
self._container_registry = self._application.getContainerRegistry()
self._application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
self._application.getContainerRegistry().containerLoadComplete.connect(self._onContainersChanged)
self._container_registry.containerLoadComplete.connect(self._onContainersChanged)
## When the global container is changed, active material probably needs to be updated.
self.globalContainerChanged.connect(self.activeMaterialChanged)
@ -80,13 +84,6 @@ class MachineManager(QObject):
self._stacks_have_errors = None # type: Optional[bool]
self._empty_container = CuraContainerRegistry.getInstance().getEmptyInstanceContainer() #type: InstanceContainer
self._empty_definition_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_definition_changes")[0] #type: InstanceContainer
self._empty_variant_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_variant")[0] #type: InstanceContainer
self._empty_material_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_material")[0] #type: InstanceContainer
self._empty_quality_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality")[0] #type: InstanceContainer
self._empty_quality_changes_container = CuraContainerRegistry.getInstance().findContainers(id = "empty_quality_changes")[0] #type: InstanceContainer
self._onGlobalContainerChanged()
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderStackChanged)
@ -192,19 +189,21 @@ class MachineManager(QObject):
for extruder in self._global_container_stack.extruders.values():
extruder_configuration = ExtruderConfigurationModel()
# For compare just the GUID is needed at this moment
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != self._empty_material_container else None
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != self._empty_material_container else None
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != self._empty_material_container else None
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != self._empty_material_container else None
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != self._empty_material_container else None
mat_type = extruder.material.getMetaDataEntry("material") if extruder.material != empty_material_container else None
mat_guid = extruder.material.getMetaDataEntry("GUID") if extruder.material != empty_material_container else None
mat_color = extruder.material.getMetaDataEntry("color_name") if extruder.material != empty_material_container else None
mat_brand = extruder.material.getMetaDataEntry("brand") if extruder.material != empty_material_container else None
mat_name = extruder.material.getMetaDataEntry("name") if extruder.material != empty_material_container else None
material_model = MaterialOutputModel(mat_guid, mat_type, mat_color, mat_brand, mat_name)
extruder_configuration.position = int(extruder.getMetaDataEntry("position"))
extruder_configuration.material = material_model
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != self._empty_variant_container else None
extruder_configuration.hotendID = extruder.variant.getName() if extruder.variant != empty_variant_container else None
self._current_printer_configuration.extruderConfigurations.append(extruder_configuration)
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != self._empty_variant_container else None
# an empty build plate configuration from the network printer is presented as an empty string, so use "" for an
# empty build plate.
self._current_printer_configuration.buildplateConfiguration = self._global_container_stack.getProperty("machine_buildplate_type", "value") if self._global_container_stack.variant != empty_variant_container else ""
self.currentConfigurationChanged.emit()
@pyqtSlot(QObject, result = bool)
@ -258,14 +257,14 @@ class MachineManager(QObject):
# Global stack can have only a variant if it is a buildplate
global_variant = self._global_container_stack.variant
if global_variant != self._empty_variant_container:
if global_variant != empty_variant_container:
if global_variant.getMetaDataEntry("hardware_type") != "buildplate":
self._global_container_stack.setVariant(self._empty_variant_container)
self._global_container_stack.setVariant(empty_variant_container)
# set the global material to empty as we now use the extruder stack at all times - CURA-4482
global_material = self._global_container_stack.material
if global_material != self._empty_material_container:
self._global_container_stack.setMaterial(self._empty_material_container)
if global_material != empty_material_container:
self._global_container_stack.setMaterial(empty_material_container)
# Listen for changes on all extruder stacks
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
@ -367,6 +366,10 @@ class MachineManager(QObject):
return
global_stack = containers[0]
# Make sure that the default machine actions for this machine have been added
self._application.getMachineActionManager().addDefaultMachineActions(global_stack)
ExtruderManager.getInstance()._fixSingleExtrusionMachineExtruderDefinition(global_stack)
if not global_stack.isValid():
# Mark global stack as invalid
@ -593,7 +596,7 @@ class MachineManager(QObject):
def globalVariantName(self) -> str:
if self._global_container_stack:
variant = self._global_container_stack.variant
if variant and not isinstance(variant, type(self._empty_variant_container)):
if variant and not isinstance(variant, type(empty_variant_container)):
return variant.getName()
return ""
@ -781,7 +784,7 @@ class MachineManager(QObject):
if not stack.isEnabled:
continue
material_container = stack.material
if material_container == self._empty_material_container:
if material_container == empty_material_container:
continue
if material_container.getMetaDataEntry("buildplate_compatible"):
buildplate_compatible = buildplate_compatible and material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName]
@ -803,7 +806,7 @@ class MachineManager(QObject):
extruder_stacks = self._global_container_stack.extruders.values()
for stack in extruder_stacks:
material_container = stack.material
if material_container == self._empty_material_container:
if material_container == empty_material_container:
continue
buildplate_compatible = material_container.getMetaDataEntry("buildplate_compatible")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_compatible") else True
buildplate_usable = material_container.getMetaDataEntry("buildplate_recommended")[self.activeVariantBuildplateName] if material_container.getMetaDataEntry("buildplate_recommended") else True
@ -873,7 +876,7 @@ class MachineManager(QObject):
extruder_manager = self._application.getExtruderManager()
definition_changes_container = self._global_container_stack.definitionChanges
if not self._global_container_stack or definition_changes_container == self._empty_definition_changes_container:
if not self._global_container_stack or definition_changes_container == empty_definition_changes_container:
return
previous_extruder_count = self._global_container_stack.getProperty("machine_extruder_count", "value")
@ -1072,7 +1075,7 @@ class MachineManager(QObject):
for stack in active_stacks:
variant_container = stack.variant
position = stack.getMetaDataEntry("position")
if variant_container and variant_container != self._empty_variant_container:
if variant_container and variant_container != empty_variant_container:
result[position] = variant_container.getName()
return result
@ -1086,11 +1089,11 @@ class MachineManager(QObject):
return
self._current_quality_group = None
self._current_quality_changes_group = None
self._global_container_stack.quality = self._empty_quality_container
self._global_container_stack.qualityChanges = self._empty_quality_changes_container
self._global_container_stack.quality = empty_quality_container
self._global_container_stack.qualityChanges = empty_quality_changes_container
for extruder in self._global_container_stack.extruders.values():
extruder.quality = self._empty_quality_container
extruder.qualityChanges = self._empty_quality_changes_container
extruder.quality = empty_quality_container
extruder.qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@ -1115,13 +1118,13 @@ class MachineManager(QObject):
# Set quality and quality_changes for the GlobalStack
self._global_container_stack.quality = quality_group.node_for_global.getContainer()
if empty_quality_changes:
self._global_container_stack.qualityChanges = self._empty_quality_changes_container
self._global_container_stack.qualityChanges = empty_quality_changes_container
# Set quality and quality_changes for each ExtruderStack
for position, node in quality_group.nodes_for_extruders.items():
self._global_container_stack.extruders[str(position)].quality = node.getContainer()
if empty_quality_changes:
self._global_container_stack.extruders[str(position)].qualityChanges = self._empty_quality_changes_container
self._global_container_stack.extruders[str(position)].qualityChanges = empty_quality_changes_container
self.activeQualityGroupChanged.emit()
self.activeQualityChangesGroupChanged.emit()
@ -1147,8 +1150,8 @@ class MachineManager(QObject):
if quality_group is None:
self._fixQualityChangesGroupToNotSupported(quality_changes_group)
quality_changes_container = self._empty_quality_changes_container
quality_container = self._empty_quality_container # type: Optional[InstanceContainer]
quality_changes_container = empty_quality_changes_container
quality_container = empty_quality_container # type: Optional[InstanceContainer]
if quality_changes_group.node_for_global and quality_changes_group.node_for_global.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_group.node_for_global.getContainer())
if quality_group is not None and quality_group.node_for_global and quality_group.node_for_global.getContainer():
@ -1163,8 +1166,8 @@ class MachineManager(QObject):
if quality_group is not None:
quality_node = quality_group.nodes_for_extruders.get(position)
quality_changes_container = self._empty_quality_changes_container
quality_container = self._empty_quality_container
quality_changes_container = empty_quality_changes_container
quality_container = empty_quality_container
if quality_changes_node and quality_changes_node.getContainer():
quality_changes_container = cast(InstanceContainer, quality_changes_node.getContainer())
if quality_node and quality_node.getContainer():
@ -1198,7 +1201,7 @@ class MachineManager(QObject):
self._global_container_stack.extruders[position].material = container_node.getContainer()
root_material_id = container_node.getMetaDataEntry("base_file", None)
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
self._global_container_stack.extruders[position].material = empty_material_container
root_material_id = None
# The _current_root_material_id is used in the MaterialMenu to see which material is selected
if root_material_id != self._current_root_material_id[position]:
@ -1273,14 +1276,10 @@ class MachineManager(QObject):
current_material_base_name = extruder.material.getMetaDataEntry("base_file")
current_nozzle_name = None
if extruder.variant.getId() != self._empty_variant_container.getId():
if extruder.variant.getId() != empty_variant_container.getId():
current_nozzle_name = extruder.variant.getMetaDataEntry("name")
from UM.Settings.Interfaces import PropertyEvaluationContext
from cura.Settings.CuraContainerStack import _ContainerIndexes
context = PropertyEvaluationContext(extruder)
context.context["evaluate_from_container_index"] = _ContainerIndexes.DefinitionChanges
material_diameter = extruder.getProperty("material_diameter", "value", context)
material_diameter = extruder.getCompatibleMaterialDiameter()
candidate_materials = self._material_manager.getAvailableMaterials(
self._global_container_stack.definition,
current_nozzle_name,
@ -1348,12 +1347,12 @@ class MachineManager(QObject):
if variant_container_node:
self._setVariantNode(position, variant_container_node)
else:
self._global_container_stack.extruders[position].variant = self._empty_variant_container
self._global_container_stack.extruders[position].variant = empty_variant_container
if material_container_node:
self._setMaterial(position, material_container_node)
else:
self._global_container_stack.extruders[position].material = self._empty_material_container
self._global_container_stack.extruders[position].material = empty_material_container
self.updateMaterialWithVariant(position)
if configuration.buildplateConfiguration is not None:
@ -1361,9 +1360,9 @@ class MachineManager(QObject):
if global_variant_container_node:
self._setGlobalVariant(global_variant_container_node)
else:
self._global_container_stack.variant = self._empty_variant_container
self._global_container_stack.variant = empty_variant_container
else:
self._global_container_stack.variant = self._empty_variant_container
self._global_container_stack.variant = empty_variant_container
self._updateQualityWithMaterial()
# See if we need to show the Discard or Keep changes screen
@ -1415,7 +1414,7 @@ class MachineManager(QObject):
position = str(position)
extruder_stack = self._global_container_stack.extruders[position]
nozzle_name = extruder_stack.variant.getName()
material_diameter = extruder_stack.approximateMaterialDiameter
material_diameter = extruder_stack.getApproximateMaterialDiameter()
material_node = self._material_manager.getMaterialNode(machine_definition_id, nozzle_name, buildplate_name,
material_diameter, root_material_id)
self.setMaterial(position, material_node)
@ -1481,7 +1480,7 @@ class MachineManager(QObject):
# This is not changing the quality for the active machine !!!!!!!!
global_stack.quality = quality_group.node_for_global.getContainer()
for extruder_nr, extruder_stack in global_stack.extruders.items():
quality_container = self._empty_quality_container
quality_container = empty_quality_container
if extruder_nr in quality_group.nodes_for_extruders:
container = quality_group.nodes_for_extruders[extruder_nr].getContainer()
quality_container = container if container is not None else quality_container
@ -1525,7 +1524,7 @@ class MachineManager(QObject):
@pyqtProperty(str, notify = activeQualityGroupChanged)
def activeQualityOrQualityChangesName(self) -> str:
name = self._empty_quality_container.getName()
name = empty_quality_container.getName()
if self._current_quality_changes_group:
name = self._current_quality_changes_group.name
elif self._current_quality_group:

View file

@ -926,7 +926,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
build_plate_id = global_stack.variant.getId()
# get material diameter of this extruder
machine_material_diameter = extruder_stack.materialDiameter
machine_material_diameter = extruder_stack.getCompatibleMaterialDiameter()
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
extruder_stack.variant.getName(),
build_plate_id,

View file

@ -1,3 +1,65 @@
[3.6.0]
*Gyroid infill
New infill pattern with enhanced strength properties. Gyroid infill is one of the strongest infill types for a given weight, has isotropic properties, and prints relatively fast with reduced material use and a fully connected part interior. Note: Slicing time can increase up to 40 seconds or more, depending on the model. Contributed by smartavionics.
*Support brim
New setting that integrates the first layer of support material with the brims geometry. This significantly improves adhesion when printing with support material. Contributed by BagelOrb.
*Cooling fan number
It is now possible to specify the cooling fan to use if your printer has multiple fans. This is implemented under Machine settings in the Extruder tab. Contributed by smartavionics.
*Settings refactor
The CuraEngine has been refactored to create a more testable, future-proof way of storing and representing settings. This makes slicing faster, and future development easier.
*Print core CC Red 0.6
The new print core CC Red 0.6 is selectable when the Ultimaker S5 profile is active. This print core is optimized for use with abrasive materials and composites.
*File name and layer display
Added M117 commands to GCODE to give real-time information about the print job file name and layer number shown on the printers display when printing via USB. Contributed by adecastilho.
*Firmware checker/Ultimaker S5
The update checker code has been improved and tested for more reliable firmware update notifications in Ultimaker Cura. The Ultimaker S5 is now included.
*Fullscreen mode shortcuts
Fullscreen mode can be toggled using the View menu or with the keyboard shortcuts: Command + Control + F (macOS), or F11 (Windows and Linux). Contributed by KangDroid.
*Configuration error message
In previous versions, Ultimaker Cura would display an error dialog explaining when something happened to user configuration files, including the option to reset to factory defaults. This would not warn about losing the current printer and print profile settings, so this information has been added.
*Rename Toolbox to Marketplace
The entry points to the Toolbox are now renamed to Marketplace.
*Materials in the Marketplace
A new tab has been added to the Marketplace that includes downloadable material profiles, to quickly and easily prepare models for a range of third-party materials.
*New third-party definitions
New profiles added for Anycube 4MAx and Tizyx K25. Contributed by jscurtu and ValentinPitre respectively.
*Improved definitions for Ender-3
The Ender-3 build plate size has been adjusted to the correct size of 235 x 235 mm, corrected the start-up sequence, and the printhead position has been adjusted when prints are purged or completed. Contributed by stelgenhof.
*Add mesh names to slicing message
Added comment generation to indicate which mesh the GCODE after this comment is constructing. Contributed by paukstelis.
*Bug fixes
- The active material is highlighted in Ultimaker Curas material manager list. This behavior is now consistent with the profile and machine manager.
- The option to use 1.75 mm diameter filament with third-party 3D printers is now fixed and does not revert back to 2.85 mm. This fix also applies the appropriate a Z-axis speed change for 1.75 mm filament printers. Contributed by kaleidoscopeit.
- A fix was created to handle OSX version 10.10, but due to the QT upgrade, users with older versions wont be able to run Ultimaker Cura on their system without a system update. This applies to OSX version 10.09 and 10.08.
- Fixed a memory leak when leaving the “Monitor” page open.
- Added performance improvements to the PolygonConnector to efficiently connect polygons that are close to each other. This also reduces the chances of the print head collide with previously printed things. Contributed by BagelOrb.
- Fixed a bug where the GCODE reader didnt show retractions.
- Changes the USBPrinting update thread to prevent flooding the printer with M105 temperature update requests. Contributed by fieldOfView.
- Fix the behavior of the "manage visible settings" button, when pressing the "cog" icon of a particular category. Contributed by fieldOfView.
- Add a new post processing script that pauses the print at a certain height that works with RepRap printers. Contributed by Kriechi.
- Fix updates to the print monitor temperatures while preheating. Contributed by fieldOfView.
- Fixed a bug where material cost is not shown unless weight is changed.
- Fixed bugs crashing the CuraEngine when TreeSupport is enabled.
- Fixed a bug where Ultimaker Cura would upload the wrong firmware after switching printers in the UI.
- Fixed a bug where the layer view was missing if the first layer was empty.
- Fixed a bug where erroneous combing movements were taking place.
- Fixed a bug where the initial layer temperature is set correctly for the first object but then never again.
- Fixed a bug where clicking the fx icon didnt respond.
[3.5.1]
*Bug fixes
- Fixed M104 temperature commands giving inaccurate results.

View file

@ -195,10 +195,6 @@ class FlavorParser:
self._previous_z = z
elif self._previous_extrusion_value > e[self._extruder_number]:
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
# This case only for initial start, for the first coordinate in GCode
elif e[self._extruder_number] == 0 and self._previous_extrusion_value == 0:
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
else:
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
return self._position(x, y, z, f, e)
@ -235,6 +231,9 @@ class FlavorParser:
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
position.e[self._extruder_number] = params.e
self._previous_extrusion_value = params.e
else:
self._previous_extrusion_value = 0.0
return self._position(
params.x if params.x is not None else position.x,
params.y if params.y is not None else position.y,
@ -243,7 +242,6 @@ class FlavorParser:
position.e)
def processGCode(self, G: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
self._previous_extrusion_value = 0.0
func = getattr(self, "_gCode%s" % G, None)
line = line.split(";", 1)[0] # Remove comments (if any)
if func is not None:
@ -295,7 +293,7 @@ class FlavorParser:
self._cancelled = False
# We obtain the filament diameter from the selected extruder to calculate line widths
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_stack:
return None
@ -338,6 +336,7 @@ class FlavorParser:
min_layer_number = 0
negative_layers = 0
previous_layer = 0
self._previous_extrusion_value = 0.0
for line in stream.split("\n"):
if self._cancelled:

View file

@ -1,9 +1,9 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from . import FlavorParser
# This parser is intented for interpret the RepRap Firmware flavor
## This parser is intended to interpret the RepRap Firmware g-code flavor.
class RepRapFlavorParser(FlavorParser.FlavorParser):
def __init__(self):

View file

@ -405,7 +405,15 @@ Cura.MachineAction
{
if (settingsTabs.currentIndex > 0)
{
manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1);
manager.updateMaterialForDiameter(settingsTabs.currentIndex - 1)
}
}
function setValueFunction(value)
{
if (settingsTabs.currentIndex > 0)
{
const extruderIndex = index.toString()
Cura.MachineManager.activeMachine.extruders[extruderIndex].compatibleMaterialDiameter = value
}
}
property bool isExtruderSetting: true
@ -564,6 +572,7 @@ Cura.MachineAction
property bool _forceUpdateOnChange: (typeof(forceUpdateOnChange) === 'undefined') ? false : forceUpdateOnChange
property string _label: (typeof(label) === 'undefined') ? "" : label
property string _tooltip: (typeof(tooltip) === 'undefined') ? propertyProvider.properties.description : tooltip
property var _setValueFunction: (typeof(setValueFunction) === 'undefined') ? undefined : setValueFunction
UM.SettingPropertyProvider
{
@ -616,14 +625,32 @@ Cura.MachineAction
{
if (propertyProvider && text != propertyProvider.properties.value)
{
propertyProvider.setPropertyValue("value", text);
// For some properties like the extruder-compatible material diameter, they need to
// trigger many updates, such as the available materials, the current material may
// need to be switched, etc. Although setting the diameter can be done directly via
// the provider, all the updates that need to be triggered then need to depend on
// the metadata update, a signal that can be fired way too often. The update functions
// can have if-checks to filter out the irrelevant updates, but still it incurs unnecessary
// overhead.
// The ExtruderStack class has a dedicated function for this call "setCompatibleMaterialDiameter()",
// and it triggers the diameter update signals only when it is needed. Here it is optionally
// choose to use setCompatibleMaterialDiameter() or other more specific functions that
// are available.
if (_setValueFunction !== undefined)
{
_setValueFunction(text)
}
else
{
propertyProvider.setPropertyValue("value", text)
}
if(_forceUpdateOnChange)
{
manager.forceUpdate();
manager.forceUpdate()
}
if(_afterOnEditingFinished)
{
_afterOnEditingFinished();
_afterOnEditingFinished()
}
}
}

View file

@ -407,14 +407,9 @@ Item {
function updateFilter()
{
var new_filter = {};
if (printSequencePropertyProvider.properties.value == "one_at_a_time")
{
new_filter["settable_per_meshgroup"] = true;
}
else
{
new_filter["settable_per_mesh"] = true;
}
new_filter["settable_per_mesh"] = true;
// Don't filter on "settable_per_meshgroup" any more when `printSequencePropertyProvider.properties.value`
// is set to "one_at_a_time", because the current backend architecture isn't ready for that.
if(filterInput.text != "")
{

View file

@ -1,5 +1,6 @@
# This PostProcessing Plugin script is released
# under the terms of the AGPLv3 or higher
# Copyright (c) 2018 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from typing import Optional, Tuple
from UM.Logger import Logger
@ -72,12 +73,12 @@ class FilamentChange(Script):
later_retract = self.getSettingValueByKey("later_retract")
x_pos = self.getSettingValueByKey("x_position")
y_pos = self.getSettingValueByKey("y_position")
color_change = "M600"
if initial_retract is not None and initial_retract > 0.:
color_change = color_change + (" E-%.2f" % initial_retract)
color_change = color_change + (" E%.2f" % initial_retract)
if later_retract is not None and later_retract > 0.:
color_change = color_change + (" L-%.2f" % later_retract)
@ -88,7 +89,7 @@ class FilamentChange(Script):
color_change = color_change + (" Y%.2f" % y_pos)
color_change = color_change + " ; Generated by FilamentChange plugin"
layer_targets = layer_nums.split(",")
if len(layer_targets) > 0:
for layer_num in layer_targets:

View file

@ -10,7 +10,7 @@ Window
{
id: base
property var selection: null
title: catalog.i18nc("@title", "Toolbox")
title: catalog.i18nc("@title", "Marketplace")
modality: Qt.ApplicationModal
flags: Qt.Dialog | Qt.CustomizeWindowHint | Qt.WindowTitleHint | Qt.WindowCloseButtonHint

View file

@ -25,14 +25,11 @@ Item
rightMargin: UM.Theme.getSize("wide_margin").width
}
height: UM.Theme.getSize("toolbox_detail_header").height
Image
Rectangle
{
id: thumbnail
width: UM.Theme.getSize("toolbox_thumbnail_medium").width
height: UM.Theme.getSize("toolbox_thumbnail_medium").height
fillMode: Image.PreserveAspectFit
source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
mipmap: true
anchors
{
top: parent.top
@ -40,6 +37,14 @@ Item
leftMargin: UM.Theme.getSize("wide_margin").width
topMargin: UM.Theme.getSize("wide_margin").height
}
color: white //Always a white background for image (regardless of theme).
Image
{
anchors.fill: parent
fillMode: Image.PreserveAspectFit
source: details === null ? "" : (details.icon_url || "../images/logobot.svg")
mipmap: true
}
}
Label

View file

@ -1,5 +1,5 @@
// Copyright (c) 2018 Ultimaker B.V.
// Toolbox is released under the terms of the LGPLv3 or higher.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.7
import QtQuick.Controls 1.4

View file

@ -52,7 +52,7 @@ class PackagesModel(ListModel):
items = []
if self._metadata is None:
Logger.logException("w", "Failed to load packages for Toolbox")
Logger.logException("w", "Failed to load packages for Marketplace")
self.setItems(items)
return

View file

@ -245,7 +245,7 @@ class Toolbox(QObject, Extension):
self._dialog = self._createDialog("Toolbox.qml")
if not self._dialog:
Logger.log("e", "Unexpected error trying to create the 'Toolbox' dialog.")
Logger.log("e", "Unexpected error trying to create the 'Marketplace' dialog.")
return
self._dialog.show()
@ -254,7 +254,7 @@ class Toolbox(QObject, Extension):
self.enabledChanged.emit()
def _createDialog(self, qml_name: str) -> Optional[QObject]:
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
Logger.log("d", "Marketplace: Creating dialog [%s].", qml_name)
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if not plugin_path:
return None
@ -262,7 +262,7 @@ class Toolbox(QObject, Extension):
dialog = self._application.createQmlComponent(path, {"toolbox": self})
if not dialog:
raise Exception("Failed to create toolbox dialog")
raise Exception("Failed to create Marketplace dialog")
return dialog
def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
@ -578,7 +578,7 @@ class Toolbox(QObject, Extension):
# Make API Calls
# --------------------------------------------------------------------------
def _makeRequestByType(self, type: str) -> None:
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
Logger.log("i", "Marketplace: Requesting %s metadata from server.", type)
request = QNetworkRequest(self._request_urls[type])
request.setRawHeader(*self._request_header)
if self._network_manager:
@ -586,7 +586,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def startDownload(self, url: str) -> None:
Logger.log("i", "Toolbox: Attempting to download & install package from %s.", url)
Logger.log("i", "Marketplace: Attempting to download & install package from %s.", url)
url = QUrl(url)
self._download_request = QNetworkRequest(url)
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
@ -603,7 +603,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot()
def cancelDownload(self) -> None:
Logger.log("i", "Toolbox: User cancelled the download of a package.")
Logger.log("i", "Marketplace: User cancelled the download of a package.")
self.resetDownload()
def resetDownload(self) -> None:
@ -690,7 +690,7 @@ class Toolbox(QObject, Extension):
return
except json.decoder.JSONDecodeError:
Logger.log("w", "Toolbox: Received invalid JSON for %s.", type)
Logger.log("w", "Marketplace: Received invalid JSON for %s.", type)
break
else:
self.setViewPage("errored")
@ -717,10 +717,10 @@ class Toolbox(QObject, Extension):
self._onDownloadComplete(file_path)
def _onDownloadComplete(self, file_path: str) -> None:
Logger.log("i", "Toolbox: Download complete.")
Logger.log("i", "Marketplace: Download complete.")
package_info = self._package_manager.getPackageInfo(file_path)
if not package_info:
Logger.log("w", "Toolbox: Package file [%s] was not a valid CuraPackage.", file_path)
Logger.log("w", "Marketplace: Package file [%s] was not a valid CuraPackage.", file_path)
return
license_content = self._package_manager.getPackageLicense(file_path)
@ -819,7 +819,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, str, str)
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({filter_type: parameter})
self.filterChanged.emit()
@ -827,7 +827,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str, "QVariantMap")
def setFilters(self, model_type: str, filter_dict: dict) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
Logger.log("w", "Marketplace: Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter(filter_dict)
self.filterChanged.emit()
@ -835,7 +835,7 @@ class Toolbox(QObject, Extension):
@pyqtSlot(str)
def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", model_type)
Logger.log("w", "Marketplace: Couldn't remove filters on %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({})
self.filterChanged.emit()
@ -845,6 +845,7 @@ class Toolbox(QObject, Extension):
def buildMaterialsModels(self) -> None:
self._metadata["materials_showcase"] = []
self._metadata["materials_available"] = []
self._metadata["materials_generic"] = []
processed_authors = [] # type: List[str]

View file

@ -1,46 +1,40 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtQuick.Controls 2.0 as Controls2
import QtGraphicalEffects 1.0
import UM 1.3 as UM
import Cura 1.0 as Cura
Rectangle
{
property var iconSource: null
Rectangle {
property var iconSource: null;
color: clickArea.containsMouse ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary"); // "Cura Blue"
height: width;
radius: Math.round(0.5 * width);
width: 36 * screenScaleFactor;
width: 36 * screenScaleFactor
height: width
radius: 0.5 * width
color: clickArea.containsMouse ? UM.Theme.getColor("primary_hover") : UM.Theme.getColor("primary")
UM.RecolorImage
{
id: icon
width: parent.width / 2
height: width
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("primary_text")
source: iconSource
UM.RecolorImage {
id: icon;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor("primary_text");
height: width;
source: iconSource;
width: Math.round(parent.width / 2);
}
MouseArea
{
id: clickArea
anchors.fill:parent
hoverEnabled: true
onClicked:
{
if (OutputDevice.activeCamera !== null)
{
OutputDevice.setActiveCamera(null)
}
else
{
OutputDevice.setActiveCamera(modelData.camera)
MouseArea {
id: clickArea;
anchors.fill: parent;
hoverEnabled: true;
onClicked: {
if (OutputDevice.activeCameraUrl != "") {
OutputDevice.setActiveCameraUrl("");
} else {
OutputDevice.setActiveCameraUrl(modelData.cameraUrl);
}
}
}

View file

@ -1,803 +1,109 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls 2.0 as Controls2
import UM 1.3 as UM
import Cura 1.0 as Cura
Component {
Rectangle {
id: base;
property var shadowRadius: UM.Theme.getSize("monitor_shadow_radius").width;
property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width;
anchors.fill: parent;
color: UM.Theme.getColor("sidebar");
visible: OutputDevice != null;
Component
{
Rectangle
{
id: base
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var shadowRadius: 5 * screenScaleFactor
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
visible: OutputDevice != null
anchors.fill: parent
color: "white"
UM.I18nCatalog
{
id: catalog
name: "cura"
UM.I18nCatalog {
id: catalog;
name: "cura";
}
Label
{
id: printingLabel
font: UM.Theme.getFont("large")
anchors
{
margins: 2 * UM.Theme.getSize("default_margin").width
leftMargin: 4 * UM.Theme.getSize("default_margin").width
top: parent.top
left: parent.left
right: parent.right
Label {
id: printingLabel;
anchors {
left: parent.left;
leftMargin: 4 * UM.Theme.getSize("default_margin").width;
margins: 2 * UM.Theme.getSize("default_margin").width;
right: parent.right;
top: parent.top;
}
text: catalog.i18nc("@label", "Printing")
elide: Text.ElideRight
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("large");
text: catalog.i18nc("@label", "Printing");
}
Label
{
id: managePrintersLabel
anchors.rightMargin: 4 * UM.Theme.getSize("default_margin").width
anchors.right: printerScrollView.right
anchors.bottom: printingLabel.bottom
text: catalog.i18nc("@label link to connect manager", "Manage printers")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("primary")
linkColor: UM.Theme.getColor("primary")
}
MouseArea
{
anchors.fill: managePrintersLabel
hoverEnabled: true
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel()
onEntered: managePrintersLabel.font.underline = true
onExited: managePrintersLabel.font.underline = false
}
ScrollView
{
id: printerScrollView
anchors
{
top: printingLabel.bottom
left: parent.left
right: parent.right
topMargin: UM.Theme.getSize("default_margin").height
bottom: parent.bottom
bottomMargin: UM.Theme.getSize("default_margin").height
Label {
id: managePrintersLabel;
anchors {
bottom: printingLabel.bottom;
right: printerScrollView.right;
rightMargin: 4 * UM.Theme.getSize("default_margin").width;
}
color: UM.Theme.getColor("primary"); // "Cura Blue"
font: UM.Theme.getFont("default");
linkColor: UM.Theme.getColor("primary"); // "Cura Blue"
text: catalog.i18nc("@label link to connect manager", "Manage printers");
}
style: UM.Theme.styles.scrollview
MouseArea {
anchors.fill: managePrintersLabel;
hoverEnabled: true;
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrinterControlPanel();
onEntered: managePrintersLabel.font.underline = true;
onExited: managePrintersLabel.font.underline = false;
}
ListView
{
id: printer_list
property var current_index: -1
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.right
leftMargin: 2 * UM.Theme.getSize("default_margin").width
rightMargin: 2 * UM.Theme.getSize("default_margin").width
// Skeleton loading
Column {
id: skeletonLoader;
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("wide_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("wide_margin").width;
top: printingLabel.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
spacing: UM.Theme.getSize("default_margin").height - 10;
visible: printerList.count === 0;
PrinterCard {
printer: null;
}
PrinterCard {
printer: null;
}
}
// Actual content
ScrollView {
id: printerScrollView;
anchors {
bottom: parent.bottom;
left: parent.left;
right: parent.right;
top: printingLabel.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
style: UM.Theme.styles.scrollview;
ListView {
id: printerList;
property var currentIndex: -1;
anchors {
fill: parent;
leftMargin: UM.Theme.getSize("wide_margin").width;
rightMargin: UM.Theme.getSize("wide_margin").width;
}
spacing: UM.Theme.getSize("default_margin").height -10
model: OutputDevice.printers
delegate: Item
{
width: parent.width
height: base.height + 2 * base.shadowRadius // To ensure that the shadow doesn't get cut off.
Rectangle
{
width: parent.width - 2 * shadowRadius
height: childrenRect.height + UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
color:
{
if(modelData.state == "disabled")
{
return UM.Theme.getColor("monitor_background_inactive")
}
else
{
return UM.Theme.getColor("monitor_background_active")
}
}
id: base
property var shadowRadius: 5 * screenScaleFactor
property var collapsed: true
layer.enabled: true
layer.effect: DropShadow
{
radius: 5 * screenScaleFactor
verticalOffset: 2
color: "#3F000000" // 25% shadow
}
Connections
{
target: printer_list
onCurrent_indexChanged: { base.collapsed = printer_list.current_index != model.index }
}
Item
{
id: printerInfo
height: machineIcon.height
anchors
{
top: parent.top
left: parent.left
right: parent.right
margins: UM.Theme.getSize("default_margin").width
}
MouseArea
{
anchors.fill: parent
onClicked:
{
if (base.collapsed) {
printer_list.current_index = model.index
}
else
{
printer_list.current_index = -1
}
}
}
Item
{
id: machineIcon
// Yeah, this is hardcoded now, but I can't think of a good way to fix this.
// The UI is going to get another update soon, so it's probably not worth the effort...
width: 58
height: 58
anchors.top: parent.top
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.left: parent.left
UM.RecolorImage
{
anchors.centerIn: parent
source:
{
switch(modelData.type)
{
case "Ultimaker 3":
return "../svg/UM3-icon.svg"
case "Ultimaker 3 Extended":
return "../svg/UM3x-icon.svg"
case "Ultimaker S5":
return "../svg/UMs5-icon.svg"
}
}
width: sourceSize.width
height: sourceSize.height
color:
{
if(modelData.state == "disabled")
{
return UM.Theme.getColor("monitor_text_inactive")
}
if(modelData.activePrintJob != undefined)
{
return UM.Theme.getColor("primary")
}
return UM.Theme.getColor("monitor_text_inactive")
}
}
}
Item
{
height: childrenRect.height
anchors
{
right: collapseIcon.left
rightMargin: UM.Theme.getSize("default_margin").width
left: machineIcon.right
leftMargin: UM.Theme.getSize("default_margin").width
verticalCenter: machineIcon.verticalCenter
}
Label
{
id: machineNameLabel
text: modelData.name
width: parent.width
elide: Text.ElideRight
font: UM.Theme.getFont("default_bold")
}
Label
{
id: activeJobLabel
text:
{
if (modelData.state == "disabled")
{
return catalog.i18nc("@label", "Not available")
} else if (modelData.state == "unreachable")
{
return catalog.i18nc("@label", "Unreachable")
}
if (modelData.activePrintJob != null)
{
return modelData.activePrintJob.name
}
return catalog.i18nc("@label", "Available")
}
anchors.top: machineNameLabel.bottom
width: parent.width
elide: Text.ElideRight
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("monitor_text_inactive")
}
}
UM.RecolorImage
{
id: collapseIcon
width: 15
height: 15
sourceSize.width: width
sourceSize.height: height
source: base.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom")
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
color: "black"
}
}
Item
{
id: detailedInfo
property var printJob: modelData.activePrintJob
visible: height == childrenRect.height
anchors.top: printerInfo.bottom
width: parent.width
height: !base.collapsed ? childrenRect.height : 0
opacity: visible ? 1 : 0
Behavior on height { NumberAnimation { duration: 100 } }
Behavior on opacity { NumberAnimation { duration: 100 } }
Rectangle
{
id: topSpacer
color:
{
if(modelData.state == "disabled")
{
return UM.Theme.getColor("monitor_lining_inactive")
}
return UM.Theme.getColor("viewport_background")
}
// UM.Theme.getColor("viewport_background")
height: 1
anchors
{
left: parent.left
right: parent.right
margins: UM.Theme.getSize("default_margin").width
top: parent.top
topMargin: UM.Theme.getSize("default_margin").width
}
}
PrinterFamilyPill
{
id: printerFamilyPill
color:
{
if(modelData.state == "disabled")
{
return "transparent"
}
return UM.Theme.getColor("viewport_background")
}
anchors.top: topSpacer.bottom
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
text: modelData.type
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
padding: 3
}
Row
{
id: extrudersInfo
anchors.top: printerFamilyPill.bottom
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
anchors.right: parent.right
anchors.rightMargin: 2 * UM.Theme.getSize("default_margin").width
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
PrintCoreConfiguration
{
id: leftExtruderInfo
width: Math.round(parent.width / 2)
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[0]
}
PrintCoreConfiguration
{
id: rightExtruderInfo
width: Math.round(parent.width / 2)
printCoreConfiguration: modelData.printerConfiguration.extruderConfigurations[1]
}
}
Rectangle
{
id: jobSpacer
color: UM.Theme.getColor("viewport_background")
height: 2
anchors
{
left: parent.left
right: parent.right
margins: UM.Theme.getSize("default_margin").width
top: extrudersInfo.bottom
topMargin: 2 * UM.Theme.getSize("default_margin").height
}
}
Item
{
id: jobInfo
property var showJobInfo: modelData.activePrintJob != null && modelData.activePrintJob.state != "queued"
anchors.top: jobSpacer.bottom
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
anchors.left: parent.left
anchors.right: parent.right
anchors.margins: UM.Theme.getSize("default_margin").width
anchors.leftMargin: 2 * UM.Theme.getSize("default_margin").width
height: showJobInfo ? childrenRect.height + 2 * UM.Theme.getSize("default_margin").height: 0
visible: showJobInfo
Label
{
id: printJobName
text: modelData.activePrintJob != null ? modelData.activePrintJob.name : ""
font: UM.Theme.getFont("default_bold")
anchors.left: parent.left
anchors.right: contextButton.left
anchors.rightMargin: UM.Theme.getSize("default_margin").width
elide: Text.ElideRight
}
Label
{
id: ownerName
anchors.top: printJobName.bottom
text: modelData.activePrintJob != null ? modelData.activePrintJob.owner : ""
font: UM.Theme.getFont("default")
opacity: 0.6
width: parent.width
elide: Text.ElideRight
}
function switchPopupState()
{
popup.visible ? popup.close() : popup.open()
}
Controls2.Button
{
id: contextButton
text: "\u22EE" //Unicode; Three stacked points.
width: 35
height: width
anchors
{
right: parent.right
top: parent.top
}
hoverEnabled: true
background: Rectangle
{
opacity: contextButton.down || contextButton.hovered ? 1 : 0
width: contextButton.width
height: contextButton.height
radius: 0.5 * width
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: contextButton.text
color: UM.Theme.getColor("monitor_text_inactive")
font.pixelSize: 25
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
onClicked: parent.switchPopupState()
}
Controls2.Popup
{
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
id: popup
clip: true
closePolicy: Popup.CloseOnPressOutside
x: (parent.width - width) + 26 * screenScaleFactor
y: contextButton.height - 5 * screenScaleFactor // Because shadow
width: 182 * screenScaleFactor
height: contentItem.height + 2 * padding
visible: false
padding: 5 * screenScaleFactor // Because shadow
transformOrigin: Popup.Top
contentItem: Item
{
width: popup.width
height: childrenRect.height + 36 * screenScaleFactor
anchors.topMargin: 10 * screenScaleFactor
anchors.bottomMargin: 10 * screenScaleFactor
Controls2.Button
{
id: pauseButton
text: modelData.activePrintJob != null && modelData.activePrintJob.state == "paused" ? catalog.i18nc("@label", "Resume") : catalog.i18nc("@label", "Pause")
onClicked:
{
if(modelData.activePrintJob.state == "paused")
{
modelData.activePrintJob.setState("print")
}
else if(modelData.activePrintJob.state == "printing")
{
modelData.activePrintJob.setState("pause")
}
popup.close()
}
width: parent.width
enabled: modelData.activePrintJob != null && ["paused", "printing"].indexOf(modelData.activePrintJob.state) >= 0
visible: enabled
anchors.top: parent.top
anchors.topMargin: 18 * screenScaleFactor
height: visible ? 39 * screenScaleFactor : 0 * screenScaleFactor
hoverEnabled: true
background: Rectangle
{
opacity: pauseButton.down || pauseButton.hovered ? 1 : 0
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: pauseButton.text
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
}
Controls2.Button
{
id: abortButton
text: catalog.i18nc("@label", "Abort")
onClicked:
{
abortConfirmationDialog.visible = true;
popup.close();
}
width: parent.width
height: 39 * screenScaleFactor
anchors.top: pauseButton.bottom
hoverEnabled: true
enabled: modelData.activePrintJob != null && ["paused", "printing", "pre_print"].indexOf(modelData.activePrintJob.state) >= 0
background: Rectangle
{
opacity: abortButton.down || abortButton.hovered ? 1 : 0
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: abortButton.text
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
}
}
MessageDialog
{
id: abortConfirmationDialog
title: catalog.i18nc("@window:title", "Abort print")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(modelData.activePrintJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: modelData.activePrintJob.setState("abort")
}
}
background: Item
{
width: popup.width
height: popup.height
DropShadow
{
anchors.fill: pointedRectangle
radius: 5
color: "#3F000000" // 25% shadow
source: pointedRectangle
transparentBorder: true
verticalOffset: 2
}
Item
{
id: pointedRectangle
width: parent.width - 10 * screenScaleFactor // Because of the shadow
height: parent.height - 10 * screenScaleFactor // Because of the shadow
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Rectangle
{
id: point
height: 14 * screenScaleFactor
width: 14 * screenScaleFactor
color: UM.Theme.getColor("setting_control")
transform: Rotation { angle: 45}
anchors.right: bloop.right
anchors.rightMargin: 24
y: 1
}
Rectangle
{
id: bloop
color: UM.Theme.getColor("setting_control")
width: parent.width
anchors.top: parent.top
anchors.topMargin: 8 * screenScaleFactor // Because of the shadow + point
anchors.bottom: parent.bottom
anchors.bottomMargin: 8 * screenScaleFactor // Because of the shadow
}
}
}
exit: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
enter: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
onClosed: visible = false
onOpened: visible = true
}
Image
{
id: printJobPreview
source: modelData.activePrintJob != null ? modelData.activePrintJob.previewImageUrl : ""
anchors.top: ownerName.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width / 2
height: width
opacity:
{
if(modelData.activePrintJob == null)
{
return 1.0
}
switch(modelData.activePrintJob.state)
{
case "wait_cleanup":
case "wait_user_action":
case "paused":
return 0.5
default:
return 1.0
}
}
}
UM.RecolorImage
{
id: statusImage
anchors.centerIn: printJobPreview
source:
{
if(modelData.activePrintJob == null)
{
return ""
}
switch(modelData.activePrintJob.state)
{
case "paused":
return "../svg/paused-icon.svg"
case "wait_cleanup":
if(modelData.activePrintJob.timeElapsed < modelData.activePrintJob.timeTotal)
{
return "../svg/aborted-icon.svg"
}
return "../svg/approved-icon.svg"
case "wait_user_action":
return "../svg/aborted-icon.svg"
default:
return ""
}
}
visible: source != ""
width: 0.5 * printJobPreview.width
height: 0.5 * printJobPreview.height
sourceSize.width: width
sourceSize.height: height
color: "black"
}
CameraButton
{
id: showCameraButton
iconSource: "../svg/camera-icon.svg"
anchors
{
left: parent.left
bottom: printJobPreview.bottom
}
}
}
}
ProgressBar
{
property var progress:
{
if(modelData.activePrintJob == null)
{
return 0
}
var result = modelData.activePrintJob.timeElapsed / modelData.activePrintJob.timeTotal
if(result > 1.0)
{
result = 1.0
}
return result
}
id: jobProgressBar
width: parent.width
value: progress
anchors.top: detailedInfo.bottom
anchors.topMargin: UM.Theme.getSize("default_margin").height
visible: modelData.activePrintJob != null && modelData.activePrintJob != undefined
style: ProgressBarStyle
{
property var remainingTime:
{
if(modelData.activePrintJob == null)
{
return 0
}
/* Sometimes total minus elapsed is less than 0. Use Math.max() to prevent remaining
time from ever being less than 0. Negative durations cause strange behavior such
as displaying "-1h -1m". */
var activeJob = modelData.activePrintJob
return Math.max(activeJob.timeTotal - activeJob.timeElapsed, 0);
}
property var progressText:
{
if(modelData.activePrintJob == null)
{
return ""
}
switch(modelData.activePrintJob.state)
{
case "wait_cleanup":
if(modelData.activePrintJob.timeTotal > modelData.activePrintJob.timeElapsed)
{
return catalog.i18nc("@label:status", "Aborted")
}
return catalog.i18nc("@label:status", "Finished")
case "pre_print":
case "sent_to_printer":
return catalog.i18nc("@label:status", "Preparing")
case "aborted":
return catalog.i18nc("@label:status", "Aborted")
case "wait_user_action":
return catalog.i18nc("@label:status", "Aborted")
case "pausing":
return catalog.i18nc("@label:status", "Pausing")
case "paused":
return OutputDevice.formatDuration( remainingTime )
case "resuming":
return catalog.i18nc("@label:status", "Resuming")
case "queued":
return catalog.i18nc("@label:status", "Action required")
default:
return OutputDevice.formatDuration( remainingTime )
}
}
background: Rectangle
{
implicitWidth: 100
implicitHeight: visible ? 24 : 0
color: UM.Theme.getColor("viewport_background")
}
progress: Rectangle
{
color:
{
var state = modelData.activePrintJob.state
var inactiveStates = [
"pausing",
"paused",
"resuming",
"wait_cleanup"
]
if(inactiveStates.indexOf(state) > -1 && remainingTime > 0)
{
return UM.Theme.getColor("monitor_text_inactive")
}
else
{
return UM.Theme.getColor("primary")
}
}
id: progressItem
function getTextOffset()
{
if(progressItem.width + progressLabel.width + 16 < control.width)
{
return progressItem.width + UM.Theme.getSize("default_margin").width
}
else
{
return progressItem.width - progressLabel.width - UM.Theme.getSize("default_margin").width
}
}
Label
{
id: progressLabel
anchors.left: parent.left
anchors.leftMargin: getTextOffset()
text: progressText
anchors.verticalCenter: parent.verticalCenter
color: progressItem.width + progressLabel.width < control.width ? "black" : "white"
width: contentWidth
font: UM.Theme.getFont("default")
}
}
}
}
}
delegate: PrinterCard {
printer: modelData;
}
model: OutputDevice.printers;
spacing: UM.Theme.getSize("default_margin").height - 10;
}
}
}

View file

@ -1,108 +1,132 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Component
{
Rectangle
{
id: monitorFrame
width: maximumWidth
height: maximumHeight
color: UM.Theme.getColor("viewport_background")
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight")
property var lineColor: "#DCDCDC" // TODO: Should be linked to theme.
property var cornerRadius: 4 * screenScaleFactor // TODO: Should be linked to theme.
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Label
{
id: manageQueueLabel
anchors.rightMargin: 3 * UM.Theme.getSize("default_margin").width
anchors.right: queuedPrintJobs.right
anchors.bottom: queuedLabel.bottom
text: catalog.i18nc("@label link to connect manager", "Manage queue")
font: UM.Theme.getFont("default")
color: UM.Theme.getColor("primary")
linkColor: UM.Theme.getColor("primary")
}
MouseArea
{
anchors.fill: manageQueueLabel
hoverEnabled: true
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel()
onEntered: manageQueueLabel.font.underline = true
onExited: manageQueueLabel.font.underline = false
}
Label
{
id: queuedLabel
anchors.left: queuedPrintJobs.left
anchors.top: parent.top
anchors.topMargin: 2 * UM.Theme.getSize("default_margin").height
anchors.leftMargin: 3 * UM.Theme.getSize("default_margin").width + 5
text: catalog.i18nc("@label", "Queued")
font: UM.Theme.getFont("large")
color: UM.Theme.getColor("text")
}
ScrollView
{
id: queuedPrintJobs
anchors
{
top: queuedLabel.bottom
topMargin: UM.Theme.getSize("default_margin").height
horizontalCenter: parent.horizontalCenter
bottomMargin: 0
bottom: parent.bottom
Component {
Rectangle {
id: monitorFrame;
property var emphasisColor: UM.Theme.getColor("setting_control_border_highlight");
property var cornerRadius: UM.Theme.getSize("monitor_corner_radius").width;
color: UM.Theme.getColor("viewport_background");
height: maximumHeight;
onVisibleChanged: {
if (monitorFrame != null && !monitorFrame.visible) {
OutputDevice.setActiveCameraUrl("");
}
style: UM.Theme.styles.scrollview
width: Math.min(800 * screenScaleFactor, maximumWidth)
ListView
{
anchors.fill: parent
//anchors.margins: UM.Theme.getSize("default_margin").height
spacing: UM.Theme.getSize("default_margin").height - 10 // 2x the shadow radius
}
width: maximumWidth;
model: OutputDevice.queuedPrintJobs
UM.I18nCatalog {
id: catalog;
name: "cura";
}
delegate: PrintJobInfoBlock
{
printJob: modelData
anchors.left: parent.left
anchors.right: parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").height
height: 175 * screenScaleFactor
Label {
id: manageQueueLabel;
anchors {
bottom: queuedLabel.bottom;
right: queuedPrintJobs.right;
rightMargin: 3 * UM.Theme.getSize("default_margin").width;
}
color: UM.Theme.getColor("primary");
font: UM.Theme.getFont("default");
linkColor: UM.Theme.getColor("primary");
text: catalog.i18nc("@label link to connect manager", "Manage queue");
}
MouseArea {
anchors.fill: manageQueueLabel;
hoverEnabled: true;
onClicked: Cura.MachineManager.printerOutputDevices[0].openPrintJobControlPanel();
onEntered: manageQueueLabel.font.underline = true;
onExited: manageQueueLabel.font.underline = false;
}
Label {
id: queuedLabel;
anchors {
left: queuedPrintJobs.left;
leftMargin: 3 * UM.Theme.getSize("default_margin").width + 5 * screenScaleFactor;
top: parent.top;
topMargin: 2 * UM.Theme.getSize("default_margin").height;
}
color: UM.Theme.getColor("text");
font: UM.Theme.getFont("large");
text: catalog.i18nc("@label", "Queued");
}
Column {
id: skeletonLoader;
anchors {
bottom: parent.bottom;
bottomMargin: UM.Theme.getSize("default_margin").height;
horizontalCenter: parent.horizontalCenter;
top: queuedLabel.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
visible: !queuedPrintJobs.visible;
width: Math.min(800 * screenScaleFactor, maximumWidth);
PrintJobInfoBlock {
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
}
printJob: null; // Use as skeleton
}
PrintJobInfoBlock {
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
}
printJob: null; // Use as skeleton
}
}
PrinterVideoStream
{
visible: OutputDevice.activeCamera != null
anchors.fill: parent
camera: OutputDevice.activeCamera
ScrollView {
id: queuedPrintJobs;
anchors {
top: queuedLabel.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
horizontalCenter: parent.horizontalCenter;
bottomMargin: UM.Theme.getSize("default_margin").height;
bottom: parent.bottom;
}
style: UM.Theme.styles.scrollview;
visible: OutputDevice.receivedPrintJobs;
width: Math.min(800 * screenScaleFactor, maximumWidth);
ListView {
id: printJobList;
anchors.fill: parent;
delegate: PrintJobInfoBlock {
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
}
printJob: modelData;
}
model: OutputDevice.queuedPrintJobs;
spacing: UM.Theme.getSize("default_margin").height - 2 * UM.Theme.getSize("monitor_shadow_radius").width;
}
}
onVisibleChanged:
{
if (monitorFrame != null && !monitorFrame.visible)
{
OutputDevice.setActiveCamera(null)
}
PrinterVideoStream {
anchors.fill: parent;
cameraUrl: OutputDevice.activeCameraUrl;
visible: OutputDevice.activeCameraUrl != "";
}
}
}

View file

@ -1,3 +1,6 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import UM 1.2 as UM
import Cura 1.0 as Cura

View file

@ -0,0 +1,12 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls 2.0
import UM 1.3 as UM
Rectangle {
color: UM.Theme.getColor("monitor_lining_light"); // TODO: Maybe theme separately? Maybe not.
height: UM.Theme.getSize("default_lining").height;
width: parent.width;
}

View file

@ -1,54 +1,45 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import UM 1.3 as UM
import Cura 1.0 as Cura
Component
{
Item
{
width: maximumWidth
height: maximumHeight
Image
{
id: cameraImage
width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
height: Math.floor((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
z: 1
Component.onCompleted:
{
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
{
OutputDevice.activePrinter.camera.start()
Component {
Item {
height: maximumHeight;
width: maximumWidth;
Cura.NetworkMJPGImage {
id: cameraImage;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
Component.onCompleted: {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.start();
}
}
onVisibleChanged:
{
if(visible)
{
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
{
OutputDevice.activePrinter.camera.start()
height: Math.floor((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
onVisibleChanged: {
if (visible) {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.start();
}
} else
{
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null)
{
OutputDevice.activePrinter.camera.stop()
} else {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
cameraImage.stop();
}
}
}
source:
{
if(OutputDevice.activePrinter != null && OutputDevice.activePrinter.camera != null && OutputDevice.activePrinter.camera.latestImage)
{
return OutputDevice.activePrinter.camera.latestImage;
source: {
if (OutputDevice.activePrinter != null && OutputDevice.activePrinter.cameraUrl != null) {
return OutputDevice.activePrinter.cameraUrl;
}
return "";
}
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
z: 1;
}
}
}

View file

@ -1,93 +1,121 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.2 as UM
Item {
id: extruderInfo;
property var printCoreConfiguration: null;
height: childrenRect.height;
width: Math.round(parent.width / 2);
Item
{
id: extruderInfo
property var printCoreConfiguration
// Extruder circle
Item {
id: extruderCircle;
height: UM.Theme.getSize("monitor_extruder_circle").height;
width: UM.Theme.getSize("monitor_extruder_circle").width;
width: Math.round(parent.width / 2)
height: childrenRect.height
// Loading skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill");
radius: Math.round(width / 2);
visible: !printCoreConfiguration;
}
Item
{
id: extruderCircle
width: 30
height: 30
anchors.verticalCenter: printAndMaterialLabel.verticalCenter
opacity:
{
if(printCoreConfiguration == null || printCoreConfiguration.activeMaterial == null || printCoreConfiguration.hotendID == null)
{
return 0.5
// Actual content
Rectangle {
anchors.fill: parent;
border.width: UM.Theme.getSize("monitor_thick_lining").width;
border.color: UM.Theme.getColor("monitor_lining_heavy");
color: "transparent";
opacity: {
if (printCoreConfiguration == null || printCoreConfiguration.activeMaterial == null || printCoreConfiguration.hotendID == null) {
return 0.5;
}
return 1;
}
return 1
}
radius: Math.round(width / 2);
visible: printCoreConfiguration;
Rectangle
{
anchors.fill: parent
radius: Math.round(width / 2)
border.width: 2
border.color: "black"
}
Label
{
anchors.centerIn: parent
font: UM.Theme.getFont("default_bold")
text: printCoreConfiguration.position + 1
Label {
anchors.centerIn: parent;
color: UM.Theme.getColor("text");
font: UM.Theme.getFont("default_bold");
text: printCoreConfiguration ? printCoreConfiguration.position + 1 : 0;
}
}
}
Item
{
id: printAndMaterialLabel
anchors
{
right: parent.right
left: extruderCircle.right
margins: UM.Theme.getSize("default_margin").width
// Print core and material labels
Item {
id: materialLabel
anchors {
left: extruderCircle.right;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
top: parent.top;
}
height: childrenRect.height
height: UM.Theme.getSize("monitor_text_line").height;
Label
{
id: materialLabel
text:
{
if(printCoreConfiguration != undefined && printCoreConfiguration.activeMaterial != undefined)
{
return printCoreConfiguration.activeMaterial.name
}
return ""
}
font: UM.Theme.getFont("default")
elide: Text.ElideRight
width: parent.width
// Loading skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill");
visible: !extruderInfo.printCoreConfiguration;
}
Label
{
id: printCoreLabel
text:
{
if(printCoreConfiguration != undefined && printCoreConfiguration.hotendID != undefined)
{
return printCoreConfiguration.hotendID
// Actual content
Label {
anchors.fill: parent;
elide: Text.ElideRight;
color: UM.Theme.getColor("text");
font: UM.Theme.getFont("default");
text: {
if (printCoreConfiguration && printCoreConfiguration.activeMaterial != undefined) {
return printCoreConfiguration.activeMaterial.name;
}
return ""
return "";
}
anchors.top: materialLabel.bottom
elide: Text.ElideRight
width: parent.width
opacity: 0.6
font: UM.Theme.getFont("default")
visible: extruderInfo.printCoreConfiguration;
}
}
Item {
id: printCoreLabel;
anchors {
left: extruderCircle.right;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
top: materialLabel.bottom;
topMargin: Math.floor(UM.Theme.getSize("default_margin").height/4);
}
height: UM.Theme.getSize("monitor_text_line").height;
// Loading skeleton
Rectangle {
color: UM.Theme.getColor("monitor_skeleton_fill");
height: parent.height;
visible: !extruderInfo.printCoreConfiguration;
width: Math.round(parent.width / 3);
}
// Actual content
Label {
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default");
opacity: 0.6;
text: {
if (printCoreConfiguration != undefined && printCoreConfiguration.hotendID != undefined) {
return printCoreConfiguration.hotendID;
}
return "";
}
visible: extruderInfo.printCoreConfiguration;
}
}
}

View file

@ -0,0 +1,220 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtQuick.Dialogs 1.1
import QtGraphicalEffects 1.0
import UM 1.3 as UM
Item {
id: root;
property var printJob: null;
property var running: isRunning(printJob);
property var assigned: isAssigned(printJob);
Button {
id: button;
background: Rectangle {
color: UM.Theme.getColor("viewport_background"); // TODO: Theme!
height: button.height;
opacity: button.down || button.hovered ? 1 : 0;
radius: Math.round(0.5 * width);
width: button.width;
}
contentItem: Label {
color: UM.Theme.getColor("monitor_context_menu_dots");
font.pixelSize: 25 * screenScaleFactor;
horizontalAlignment: Text.AlignHCenter;
text: button.text;
verticalAlignment: Text.AlignVCenter;
}
height: width;
hoverEnabled: true;
onClicked: parent.switchPopupState();
text: "\u22EE"; //Unicode; Three stacked points.
visible: printJob.state == "queued" || running ? true : false;
width: 35 * screenScaleFactor; // TODO: Theme!
}
Popup {
id: popup;
background: Item {
anchors.fill: parent;
DropShadow {
anchors.fill: pointedRectangle;
color: UM.Theme.getColor("monitor_shadow");
radius: UM.Theme.getSize("monitor_shadow_radius").width;
source: pointedRectangle;
transparentBorder: true;
verticalOffset: 2 * screenScaleFactor;
}
Item {
id: pointedRectangle;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
height: parent.height - 10 * screenScaleFactor; // Because of the shadow
width: parent.width - 10 * screenScaleFactor; // Because of the shadow
Rectangle {
id: point;
anchors {
right: bloop.right;
rightMargin: 24 * screenScaleFactor;
}
color: UM.Theme.getColor("monitor_context_menu_background");
height: 14 * screenScaleFactor;
transform: Rotation {
angle: 45;
}
width: 14 * screenScaleFactor;
y: 1 * screenScaleFactor;
}
Rectangle {
id: bloop;
anchors {
bottom: parent.bottom;
bottomMargin: 8 * screenScaleFactor; // Because of the shadow
top: parent.top;
topMargin: 8 * screenScaleFactor; // Because of the shadow + point
}
color: UM.Theme.getColor("monitor_context_menu_background");
width: parent.width;
}
}
}
clip: true;
closePolicy: Popup.CloseOnPressOutside;
contentItem: Column {
id: popupOptions;
anchors {
top: parent.top;
topMargin: UM.Theme.getSize("default_margin").height + 10 * screenScaleFactor; // Account for the point of the box
}
height: childrenRect.height + spacing * popupOptions.children.length + UM.Theme.getSize("default_margin").height;
spacing: Math.floor(UM.Theme.getSize("default_margin").height / 2);
width: parent.width;
PrintJobContextMenuItem {
enabled: {
if (printJob && printJob.state == "queued" && !assigned) {
if (OutputDevice && OutputDevice.queuedPrintJobs[0]) {
return OutputDevice.queuedPrintJobs[0].key != printJob.key;
}
}
return false;
}
onClicked: {
sendToTopConfirmationDialog.visible = true;
popup.close();
}
text: catalog.i18nc("@label", "Move to top");
}
PrintJobContextMenuItem {
enabled: printJob && !running;
onClicked: {
deleteConfirmationDialog.visible = true;
popup.close();
}
text: catalog.i18nc("@label", "Delete");
}
PrintJobContextMenuItem {
enabled: printJob && running;
onClicked: {
if (printJob.state == "paused") {
printJob.setState("print");
} else if(printJob.state == "printing") {
printJob.setState("pause");
}
popup.close();
}
text: printJob && printJob.state == "paused" ? catalog.i18nc("@label", "Resume") : catalog.i18nc("@label", "Pause");
}
PrintJobContextMenuItem {
enabled: printJob && running;
onClicked: {
abortConfirmationDialog.visible = true;
popup.close();
}
text: catalog.i18nc("@label", "Abort");
}
}
enter: Transition {
NumberAnimation {
duration: 75;
property: "visible";
}
}
exit: Transition {
NumberAnimation {
duration: 75;
property: "visible";
}
}
height: contentItem.height + 2 * padding;
onClosed: visible = false;
onOpened: visible = true;
padding: UM.Theme.getSize("monitor_shadow_radius").width;
transformOrigin: Popup.Top;
visible: false;
width: 182 * screenScaleFactor;
x: (button.width - width) + 26 * screenScaleFactor;
y: button.height + 5 * screenScaleFactor; // Because shadow
}
MessageDialog {
id: sendToTopConfirmationDialog;
Component.onCompleted: visible = false;
icon: StandardIcon.Warning;
onYes: OutputDevice.sendJobToTop(printJob.key);
standardButtons: StandardButton.Yes | StandardButton.No;
text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name) : "";
title: catalog.i18nc("@window:title", "Move print job to top");
}
MessageDialog {
id: deleteConfirmationDialog;
Component.onCompleted: visible = false;
icon: StandardIcon.Warning;
onYes: OutputDevice.deleteJobFromQueue(printJob.key);
standardButtons: StandardButton.Yes | StandardButton.No;
text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name) : "";
title: catalog.i18nc("@window:title", "Delete print job");
}
MessageDialog {
id: abortConfirmationDialog;
Component.onCompleted: visible = false;
icon: StandardIcon.Warning;
onYes: printJob.setState("abort");
standardButtons: StandardButton.Yes | StandardButton.No;
text: printJob && printJob.name ? catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to abort %1?").arg(printJob.name) : "";
title: catalog.i18nc("@window:title", "Abort print");
}
// Utils
function switchPopupState() {
popup.visible ? popup.close() : popup.open();
}
function isRunning(job) {
if (!job) {
return false;
}
return ["paused", "printing", "pre_print"].indexOf(job.state) !== -1;
}
function isAssigned(job) {
if (!job) {
return false;
}
return job.assignedPrinter ? true : false;
}
}

View file

@ -0,0 +1,24 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
Button {
background: Rectangle {
opacity: parent.down || parent.hovered ? 1 : 0;
color: UM.Theme.getColor("monitor_context_menu_highlight");
}
contentItem: Label {
color: UM.Theme.getColor("text");
text: parent.text
horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter;
}
height: 39 * screenScaleFactor; // TODO: Theme!
hoverEnabled: true;
visible: enabled;
width: parent.width;
}

View file

@ -1,429 +1,476 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.4
import QtGraphicalEffects 1.0
import QtQuick.Layouts 1.1
import QtQuick.Dialogs 1.1
import UM 1.3 as UM
Item {
id: root;
property var shadowRadius: UM.Theme.getSize("monitor_shadow_radius").width;
property var shadowOffset: 2 * screenScaleFactor;
property var debug: false;
property var printJob: null;
width: parent.width; // Bubbles downward
height: childrenRect.height + shadowRadius * 2; // Bubbles upward
Item
{
id: base
property var printJob: null
property var shadowRadius: 5 * screenScaleFactor
function getPrettyTime(time)
{
return OutputDevice.formatDuration(time)
UM.I18nCatalog {
id: catalog;
name: "cura";
}
width: parent.width
UM.I18nCatalog
{
id: catalog
name: "cura"
}
Rectangle
{
id: background
anchors
{
top: parent.top
topMargin: 3 * screenScaleFactor
left: parent.left
leftMargin: base.shadowRadius
rightMargin: base.shadowRadius
right: parent.right
bottom: parent.bottom
bottomMargin: base.shadowRadius
// The actual card (white block)
Rectangle {
// 5px margin, but shifted 2px vertically because of the shadow
anchors {
bottomMargin: root.shadowRadius + root.shadowOffset;
leftMargin: root.shadowRadius;
rightMargin: root.shadowRadius;
topMargin: root.shadowRadius - root.shadowOffset;
}
color: UM.Theme.getColor("monitor_card_background");
height: childrenRect.height;
layer.enabled: true
layer.effect: DropShadow
{
radius: base.shadowRadius
layer.effect: DropShadow {
radius: root.shadowRadius
verticalOffset: 2 * screenScaleFactor
color: "#3F000000" // 25% shadow
color: "#3F000000" // 25% shadow
}
width: parent.width - shadowRadius * 2;
Item
{
// Content on the left of the infobox
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.left
right: parent.horizontalCenter
margins: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("default_margin").width
}
Column {
height: childrenRect.height;
width: parent.width;
Label
{
id: printJobName
text: printJob.name
font: UM.Theme.getFont("default_bold")
width: parent.width
elide: Text.ElideRight
}
// Main content
Item {
id: mainContent;
height: 200 * screenScaleFactor; // TODO: Theme!
width: parent.width;
Label
{
id: ownerName
anchors.top: printJobName.bottom
text: printJob.owner
font: UM.Theme.getFont("default")
opacity: 0.6
width: parent.width
elide: Text.ElideRight
}
Image
{
id: printJobPreview
source: printJob.previewImageUrl
anchors.top: ownerName.bottom
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: totalTimeLabel.bottom
width: height
opacity: printJob.state == "error" ? 0.5 : 1.0
}
UM.RecolorImage
{
id: statusImage
anchors.centerIn: printJobPreview
source: printJob.state == "error" ? "../svg/aborted-icon.svg" : ""
visible: source != ""
width: 0.5 * printJobPreview.width
height: 0.5 * printJobPreview.height
sourceSize.width: width
sourceSize.height: height
color: "black"
}
Label
{
id: totalTimeLabel
anchors.bottom: parent.bottom
anchors.right: parent.right
font: UM.Theme.getFont("default")
text: printJob != null ? getPrettyTime(printJob.timeTotal) : ""
elide: Text.ElideRight
}
}
Item
{
// Content on the right side of the infobox.
anchors
{
top: parent.top
bottom: parent.bottom
left: parent.horizontalCenter
right: parent.right
margins: 2 * UM.Theme.getSize("default_margin").width
leftMargin: UM.Theme.getSize("default_margin").width
rightMargin: UM.Theme.getSize("default_margin").width / 2
}
Label
{
id: targetPrinterLabel
elide: Text.ElideRight
font: UM.Theme.getFont("default_bold")
text:
{
if(printJob.assignedPrinter == null)
{
if(printJob.state == "error")
{
return catalog.i18nc("@label", "Waiting for: Unavailable printer")
}
return catalog.i18nc("@label", "Waiting for: First available")
}
else
{
return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name
// Left content
Item {
anchors {
bottom: parent.bottom;
left: parent.left;
margins: UM.Theme.getSize("wide_margin").width;
right: parent.horizontalCenter;
top: parent.top;
}
}
Item {
id: printJobName;
width: parent.width;
height: UM.Theme.getSize("monitor_text_line").height;
anchors
{
left: parent.left
right: contextButton.left
rightMargin: UM.Theme.getSize("default_margin").width
}
}
function switchPopupState()
{
popup.visible ? popup.close() : popup.open()
}
Button
{
id: contextButton
text: "\u22EE" //Unicode; Three stacked points.
width: 35
height: width
anchors
{
right: parent.right
top: parent.top
}
hoverEnabled: true
background: Rectangle
{
opacity: contextButton.down || contextButton.hovered ? 1 : 0
width: contextButton.width
height: contextButton.height
radius: 0.5 * width
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: contextButton.text
color: UM.Theme.getColor("monitor_text_inactive")
font.pixelSize: 25
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
onClicked: parent.switchPopupState()
}
Popup
{
// TODO Change once updating to Qt5.10 - The 'opened' property is in 5.10 but the behavior is now implemented with the visible property
id: popup
clip: true
closePolicy: Popup.CloseOnPressOutside
x: (parent.width - width) + 26 * screenScaleFactor
y: contextButton.height - 5 * screenScaleFactor // Because shadow
width: 182 * screenScaleFactor
height: contentItem.height + 2 * padding
visible: false
padding: 5 * screenScaleFactor // Because shadow
transformOrigin: Popup.Top
contentItem: Item
{
width: popup.width
height: childrenRect.height + 36 * screenScaleFactor
anchors.topMargin: 10 * screenScaleFactor
anchors.bottomMargin: 10 * screenScaleFactor
Button
{
id: sendToTopButton
text: catalog.i18nc("@label", "Move to top")
onClicked:
{
sendToTopConfirmationDialog.visible = true;
popup.close();
Rectangle {
color: UM.Theme.getColor("monitor_skeleton_fill");
height: parent.height;
visible: !printJob;
width: Math.round(parent.width / 3);
}
width: parent.width
enabled: OutputDevice.queuedPrintJobs[0].key != printJob.key
visible: enabled
anchors.top: parent.top
anchors.topMargin: 18 * screenScaleFactor
height: visible ? 39 * screenScaleFactor : 0 * screenScaleFactor
hoverEnabled: true
background: Rectangle
{
opacity: sendToTopButton.down || sendToTopButton.hovered ? 1 : 0
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: sendToTopButton.text
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Label {
anchors.fill: parent;
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default_bold");
text: printJob && printJob.name ? printJob.name : ""; // Supress QML warnings
visible: printJob;
}
}
MessageDialog
{
id: sendToTopConfirmationDialog
title: catalog.i18nc("@window:title", "Move print job to top")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to move %1 to the top of the queue?").arg(printJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: OutputDevice.sendJobToTop(printJob.key)
}
Item {
id: printJobOwnerName;
anchors {
top: printJobName.bottom;
topMargin: Math.floor(UM.Theme.getSize("default_margin").height / 2);
}
height: UM.Theme.getSize("monitor_text_line").height;
width: parent.width;
Button
{
id: deleteButton
text: catalog.i18nc("@label", "Delete")
onClicked:
{
deleteConfirmationDialog.visible = true;
popup.close();
Rectangle {
color: UM.Theme.getColor("monitor_skeleton_fill");
height: parent.height;
visible: !printJob;
width: Math.round(parent.width / 2);
}
width: parent.width
height: 39 * screenScaleFactor
anchors.top: sendToTopButton.bottom
hoverEnabled: true
background: Rectangle
{
opacity: deleteButton.down || deleteButton.hovered ? 1 : 0
color: UM.Theme.getColor("viewport_background")
}
contentItem: Label
{
text: deleteButton.text
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
Label {
anchors.fill: parent;
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default");
text: printJob ? printJob.owner : ""; // Supress QML warnings
visible: printJob;
}
}
MessageDialog
{
id: deleteConfirmationDialog
title: catalog.i18nc("@window:title", "Delete print job")
icon: StandardIcon.Warning
text: catalog.i18nc("@label %1 is the name of a print job.", "Are you sure you want to delete %1?").arg(printJob.name)
standardButtons: StandardButton.Yes | StandardButton.No
Component.onCompleted: visible = false
onYes: OutputDevice.deleteJobFromQueue(printJob.key)
}
}
Item {
id: printJobPreview;
property var useUltibot: false;
anchors {
bottom: parent.bottom;
horizontalCenter: parent.horizontalCenter;
top: printJobOwnerName.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
width: height;
background: Item
{
width: popup.width
height: popup.height
DropShadow
{
anchors.fill: pointedRectangle
radius: 5
color: "#3F000000" // 25% shadow
source: pointedRectangle
transparentBorder: true
verticalOffset: 2
}
Item
{
id: pointedRectangle
width: parent.width - 10 * screenScaleFactor // Because of the shadow
height: parent.height - 10 * screenScaleFactor // Because of the shadow
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
Rectangle
{
id: point
height: 14 * screenScaleFactor
width: 14 * screenScaleFactor
color: UM.Theme.getColor("setting_control")
transform: Rotation { angle: 45}
anchors.right: bloop.right
anchors.rightMargin: 24
y: 1
// Skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill");
radius: UM.Theme.getSize("default_margin").width;
visible: !printJob;
}
Rectangle
{
id: bloop
color: UM.Theme.getColor("setting_control")
width: parent.width
anchors.top: parent.top
anchors.topMargin: 8 * screenScaleFactor // Because of the shadow + point
anchors.bottom: parent.bottom
anchors.bottomMargin: 8 * screenScaleFactor // Because of the shadow
// Actual content
Image {
id: previewImage;
anchors.fill: parent;
opacity: printJob && printJob.state == "error" ? 0.5 : 1.0;
source: printJob ? printJob.previewImageUrl : "";
visible: printJob;
}
UM.RecolorImage {
id: ultiBotImage;
anchors.centerIn: printJobPreview;
color: UM.Theme.getColor("monitor_placeholder_image");
height: printJobPreview.height;
source: "../svg/ultibot.svg";
sourceSize {
height: height;
width: width;
}
/* Since print jobs ALWAYS have an image url, we have to check if that image URL errors or
not in order to determine if we show the placeholder (ultibot) image instead. */
visible: printJob && previewImage.status == Image.Error;
width: printJobPreview.width;
}
UM.RecolorImage {
id: statusImage;
anchors.centerIn: printJobPreview;
color: UM.Theme.getColor("monitor_image_overlay");
height: 0.5 * printJobPreview.height;
source: printJob && printJob.state == "error" ? "../svg/aborted-icon.svg" : "";
sourceSize {
height: height;
width: width;
}
visible: source != "";
width: 0.5 * printJobPreview.width;
}
}
}
exit: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
}
enter: Transition
{
// This applies a default NumberAnimation to any changes a state change makes to x or y properties
NumberAnimation { property: "visible"; duration: 75; }
// Divider
Rectangle {
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: !printJob ? UM.Theme.getColor("monitor_skeleton_fill") : UM.Theme.getColor("monitor_lining_light");
height: parent.height - 2 * UM.Theme.getSize("default_margin").height;
width: UM.Theme.getSize("default_lining").width;
}
onClosed: visible = false
onOpened: visible = true
}
// Right content
Item {
anchors {
bottom: parent.bottom;
left: parent.horizontalCenter;
margins: UM.Theme.getSize("wide_margin").width;
right: parent.right;
top: parent.top;
}
Row
{
id: printerFamilyPills
spacing: 0.5 * UM.Theme.getSize("default_margin").width
anchors
{
left: parent.left
right: parent.right
bottom: extrudersInfo.top
bottomMargin: UM.Theme.getSize("default_margin").height
}
height: childrenRect.height
Repeater
{
model: printJob.compatibleMachineFamilies
Item {
id: targetPrinterLabel;
height: UM.Theme.getSize("monitor_text_line").height;
width: parent.width;
delegate: PrinterFamilyPill
{
text: modelData
color: UM.Theme.getColor("viewport_background")
padding: 3 * screenScaleFactor
Rectangle {
visible: !printJob;
color: UM.Theme.getColor("monitor_skeleton_fill");
anchors.fill: parent;
}
Label {
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default_bold");
text: {
if (printJob !== null) {
if (printJob.assignedPrinter == null) {
if (printJob.state == "error") {
return catalog.i18nc("@label", "Waiting for: Unavailable printer");
}
return catalog.i18nc("@label", "Waiting for: First available");
} else {
return catalog.i18nc("@label", "Waiting for: ") + printJob.assignedPrinter.name;
}
}
return "";
}
visible: printJob;
}
}
PrinterInfoBlock {
anchors.bottom: parent.bottom;
printer: root.printJon && root.printJob.assignedPrinter;
printJob: root.printJob;
}
}
}
// PrintCore && Material config
Row
{
id: extrudersInfo
anchors.bottom: parent.bottom
anchors
{
left: parent.left
right: parent.right
}
height: childrenRect.height
spacing: UM.Theme.getSize("default_margin").width
PrintCoreConfiguration
{
id: leftExtruderInfo
width: Math.round(parent.width / 2) * screenScaleFactor
printCoreConfiguration: printJob.configuration.extruderConfigurations[0]
}
PrintCoreConfiguration
{
id: rightExtruderInfo
width: Math.round(parent.width / 2) * screenScaleFactor
printCoreConfiguration: printJob.configuration.extruderConfigurations[1]
PrintJobContextMenu {
id: contextButton;
anchors {
right: mainContent.right;
rightMargin: UM.Theme.getSize("default_margin").width * 3 + root.shadowRadius;
top: mainContent.top;
topMargin: UM.Theme.getSize("default_margin").height;
}
printJob: root.printJob;
visible: root.printJob;
}
}
}
Item {
id: configChangesBox;
height: childrenRect.height;
visible: printJob && printJob.configurationChanges.length !== 0;
width: parent.width;
Rectangle
{
color: UM.Theme.getColor("viewport_background")
width: 2 * screenScaleFactor
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.margins: UM.Theme.getSize("default_margin").height
anchors.horizontalCenter: parent.horizontalCenter
// Config change toggle
Rectangle {
id: configChangeToggle;
color: {
if (configChangeToggleArea.containsMouse) {
return UM.Theme.getColor("viewport_background"); // TODO: Theme!
} else {
return "transparent";
}
}
width: parent.width;
height: UM.Theme.getSize("default_margin").height * 4; // TODO: Theme!
anchors {
left: parent.left;
right: parent.right;
top: parent.top;
}
Rectangle {
color: !printJob ? UM.Theme.getColor("monitor_skeleton_fill") : UM.Theme.getColor("monitor_lining_light");
height: UM.Theme.getSize("default_lining").height;
width: parent.width;
}
UM.RecolorImage {
anchors {
right: configChangeToggleLabel.left;
rightMargin: UM.Theme.getSize("default_margin").width;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor("text");
height: 23 * screenScaleFactor; // TODO: Theme!
source: "../svg/warning-icon.svg";
sourceSize {
height: height;
width: width;
}
width: 23 * screenScaleFactor; // TODO: Theme!
}
Label {
id: configChangeToggleLabel;
anchors {
horizontalCenter: parent.horizontalCenter;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor("text");
text: catalog.i18nc("@label", "Configuration change");
}
UM.RecolorImage {
anchors {
left: configChangeToggleLabel.right;
leftMargin: UM.Theme.getSize("default_margin").width;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor("text");
height: 15 * screenScaleFactor; // TODO: Theme!
source: {
if (configChangeDetails.visible) {
return UM.Theme.getIcon("arrow_top");
} else {
return UM.Theme.getIcon("arrow_bottom");
}
}
sourceSize {
width: width;
height: height;
}
width: 15 * screenScaleFactor; // TODO: Theme!
}
MouseArea {
id: configChangeToggleArea;
anchors.fill: parent;
hoverEnabled: true;
onClicked: {
configChangeDetails.visible = !configChangeDetails.visible;
}
}
}
// Config change details
Item {
id: configChangeDetails;
anchors.top: configChangeToggle.bottom;
Behavior on height { NumberAnimation { duration: 100 } }
// In case of really massive multi-line configuration changes
height: visible ? Math.max(UM.Theme.getSize("monitor_config_override_box").height, childrenRect.height) : 0;
visible: false;
width: parent.width;
Item {
anchors {
bottomMargin: UM.Theme.getSize("wide_margin").height;
fill: parent;
leftMargin: UM.Theme.getSize("wide_margin").height * 4;
rightMargin: UM.Theme.getSize("wide_margin").height * 4;
topMargin: UM.Theme.getSize("wide_margin").height;
}
clip: true;
Label {
anchors.fill: parent;
elide: Text.ElideRight;
color: UM.Theme.getColor("text");
font: UM.Theme.getFont("large_nonbold");
text: {
if (!printJob || printJob.configurationChanges.length === 0) {
return "";
}
var topLine;
if (materialsAreKnown(printJob)) {
topLine = catalog.i18nc("@label", "The assigned printer, %1, requires the following configuration change(s):").arg(printJob.assignedPrinter.name);
} else {
topLine = catalog.i18nc("@label", "The printer %1 is assigned, but the job contains an unknown material configuration.").arg(printJob.assignedPrinter.name);
}
var result = "<p>" + topLine +"</p>";
for (var i = 0; i < printJob.configurationChanges.length; i++) {
var change = printJob.configurationChanges[i];
var text;
switch (change.typeOfChange) {
case "material_change":
text = catalog.i18nc("@label", "Change material %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName);
break;
case "material_insert":
text = catalog.i18nc("@label", "Load %3 as material %1 (This cannot be overridden).").arg(change.index + 1).arg(change.targetName);
break;
case "print_core_change":
text = catalog.i18nc("@label", "Change print core %1 from %2 to %3.").arg(change.index + 1).arg(change.originName).arg(change.targetName);
break;
case "buildplate_change":
text = catalog.i18nc("@label", "Change build plate to %1 (This cannot be overridden).").arg(formatBuildPlateType(change.target_name));
break;
default:
text = "";
}
result += "<p><b>" + text + "</b></p>";
}
return result;
}
wrapMode: Text.WordWrap;
}
Button {
anchors {
bottom: parent.bottom;
left: parent.left;
}
onClicked: {
overrideConfirmationDialog.visible = true;
}
text: catalog.i18nc("@label", "Override");
visible: {
if (printJob && printJob.configurationChanges) {
var length = printJob.configurationChanges.length;
for (var i = 0; i < length; i++) {
var typeOfChange = printJob.configurationChanges[i].typeOfChange;
if (typeOfChange === "material_insert" || typeOfChange === "buildplate_change") {
return false;
}
}
}
return true;
}
}
}
}
MessageDialog {
id: overrideConfirmationDialog;
Component.onCompleted: visible = false;
icon: StandardIcon.Warning;
onYes: OutputDevice.forceSendJob(printJob.key);
standardButtons: StandardButton.Yes | StandardButton.No;
text: {
if (!printJob) {
return "";
}
var printJobName = formatPrintJobName(printJob.name);
var confirmText = catalog.i18nc("@label", "Starting a print job with an incompatible configuration could damage your 3D printer. Are you sure you want to override the configuration and print %1?").arg(printJobName);
return confirmText;
}
title: catalog.i18nc("@window:title", "Override configuration configuration and start print");
}
}
}
}
}
// Utils
function formatPrintJobName(name) {
var extensions = [ ".gz", ".gcode", ".ufp" ];
for (var i = 0; i < extensions.length; i++) {
var extension = extensions[i];
if (name.slice(-extension.length) === extension) {
name = name.substring(0, name.length - extension.length);
}
}
return name;
}
function materialsAreKnown(job) {
var conf0 = job.configuration[0];
if (conf0 && !conf0.material.material) {
return false;
}
var conf1 = job.configuration[1];
if (conf1 && !conf1.material.material) {
return false;
}
return true;
}
function formatBuildPlateType(buildPlateType) {
var translationText = "";
switch (buildPlateType) {
case "glass":
translationText = catalog.i18nc("@label", "Glass");
break;
case "aluminum":
translationText = catalog.i18nc("@label", "Aluminum");
break;
default:
translationText = null;
}
return translationText;
}
}

View file

@ -0,0 +1,75 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4 as LegacyControls
import UM 1.3 as UM
// Includes print job name, owner, and preview
Item {
property var job: null;
property var useUltibot: false;
height: 100 * screenScaleFactor;
width: height;
// Skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill");
radius: UM.Theme.getSize("default_margin").width;
visible: !job;
}
// Actual content
Image {
id: previewImage;
visible: job;
source: job ? job.previewImageUrl : "";
opacity: {
if (job == null) {
return 1.0;
}
var states = ["wait_cleanup", "wait_user_action", "error", "paused"];
if (states.indexOf(job.state) !== -1) {
return 0.5;
}
return 1.0;
}
anchors.fill: parent;
}
UM.RecolorImage {
id: ultibotImage;
anchors.centerIn: parent;
color: UM.Theme.getColor("monitor_placeholder_image"); // TODO: Theme!
height: parent.height;
source: "../svg/ultibot.svg";
sourceSize {
height: height;
width: width;
}
/* Since print jobs ALWAYS have an image url, we have to check if that image URL errors or
not in order to determine if we show the placeholder (ultibot) image instead. */
visible: job && previewImage.status == Image.Error;
width: parent.width;
}
UM.RecolorImage {
id: statusImage;
anchors.centerIn: parent;
color: "black"; // TODO: Theme!
height: Math.round(0.5 * parent.height);
source: job && job.state == "error" ? "../svg/aborted-icon.svg" : "";
sourceSize {
height: height;
width: width;
}
visible: source != "";
width: Math.round(0.5 * parent.width);
}
}

View file

@ -0,0 +1,59 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls 2.0
import UM 1.3 as UM
Column {
property var job: null;
height: childrenRect.height;
spacing: Math.floor( UM.Theme.getSize("default_margin").height / 2); // TODO: Use explicit theme size
width: parent.width;
Item {
id: jobName;
height: UM.Theme.getSize("monitor_text_line").height;
width: parent.width;
// Skeleton loading
Rectangle {
color: UM.Theme.getColor("monitor_skeleton_fill");
height: parent.height;
visible: !job;
width: Math.round(parent.width / 3);
}
Label {
anchors.fill: parent;
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default_bold");
text: job && job.name ? job.name : "";
visible: job;
}
}
Item {
id: ownerName;
height: UM.Theme.getSize("monitor_text_line").height;
width: parent.width;
// Skeleton loading
Rectangle {
color: UM.Theme.getColor("monitor_skeleton_fill");
height: parent.height;
visible: !job;
width: Math.round(parent.width / 2);
}
Label {
anchors.fill: parent;
color: UM.Theme.getColor("text")
elide: Text.ElideRight;
font: UM.Theme.getFont("default");
text: job ? job.owner : "";
visible: job;
}
}
}

View file

@ -4,112 +4,100 @@
import QtQuick 2.2
import QtQuick.Window 2.2
import QtQuick.Controls 1.2
import UM 1.1 as UM
UM.Dialog
{
UM.Dialog {
id: base;
minimumWidth: 500 * screenScaleFactor
minimumHeight: 140 * screenScaleFactor
maximumWidth: minimumWidth
maximumHeight: minimumHeight
width: minimumWidth
height: minimumHeight
visible: true
modality: Qt.ApplicationModal
onVisibleChanged:
{
if(visible)
{
resetPrintersModel()
}
else
{
OutputDevice.cancelPrintSelection()
}
}
title: catalog.i18nc("@title:window", "Print over network")
property var printersModel: ListModel{}
function resetPrintersModel() {
printersModel.clear()
printersModel.append({ name: "Automatic", key: ""})
for (var index in OutputDevice.printers)
{
printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key})
}
}
Column
{
id: printerSelection
anchors.fill: parent
anchors.top: parent.top
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.rightMargin: UM.Theme.getSize("default_margin").width
height: 50 * screenScaleFactor
Label
{
id: manualPrinterSelectionLabel
anchors
{
left: parent.left
topMargin: UM.Theme.getSize("default_margin").height
right: parent.right
}
text: catalog.i18nc("@label", "Printer selection")
wrapMode: Text.Wrap
height: 20 * screenScaleFactor
}
ComboBox
{
id: printerSelectionCombobox
model: base.printersModel
textRole: "name"
width: parent.width
height: 40 * screenScaleFactor
Behavior on height { NumberAnimation { duration: 100 } }
}
SystemPalette
{
id: palette
}
UM.I18nCatalog { id: catalog; name: "cura"; }
}
height: minimumHeight;
leftButtons: [
Button
{
text: catalog.i18nc("@action:button","Cancel")
enabled: true
Button {
enabled: true;
onClicked: {
base.visible = false;
printerSelectionCombobox.currentIndex = 0
OutputDevice.cancelPrintSelection()
printerSelectionCombobox.currentIndex = 0;
OutputDevice.cancelPrintSelection();
}
text: catalog.i18nc("@action:button","Cancel");
}
]
maximumHeight: minimumHeight;
maximumWidth: minimumWidth;
minimumHeight: 140 * screenScaleFactor;
minimumWidth: 500 * screenScaleFactor;
modality: Qt.ApplicationModal;
onVisibleChanged: {
if (visible) {
resetPrintersModel();
} else {
OutputDevice.cancelPrintSelection();
}
}
rightButtons: [
Button
{
text: catalog.i18nc("@action:button","Print")
enabled: true
Button {
enabled: true;
onClicked: {
base.visible = false;
OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key)
OutputDevice.selectPrinter(printerSelectionCombobox.model.get(printerSelectionCombobox.currentIndex).key);
// reset to defaults
printerSelectionCombobox.currentIndex = 0
printerSelectionCombobox.currentIndex = 0;
}
text: catalog.i18nc("@action:button","Print");
}
]
title: catalog.i18nc("@title:window", "Print over network");
visible: true;
width: minimumWidth;
Column {
id: printerSelection;
anchors {
fill: parent;
leftMargin: UM.Theme.getSize("default_margin").width;
rightMargin: UM.Theme.getSize("default_margin").width;
top: parent.top;
topMargin: UM.Theme.getSize("default_margin").height;
}
height: 50 * screenScaleFactor;
SystemPalette {
id: palette;
}
UM.I18nCatalog {
id: catalog;
name: "cura";
}
Label {
id: manualPrinterSelectionLabel;
anchors {
left: parent.left;
right: parent.right;
topMargin: UM.Theme.getSize("default_margin").height;
}
height: 20 * screenScaleFactor;
text: catalog.i18nc("@label", "Printer selection");
wrapMode: Text.Wrap;
}
ComboBox {
id: printerSelectionCombobox;
Behavior on height { NumberAnimation { duration: 100 } }
height: 40 * screenScaleFactor;
model: ListModel {
id: printersModel;
}
textRole: "name";
width: parent.width;
}
}
// Utils
function resetPrintersModel() {
printersModel.clear();
printersModel.append({ name: "Automatic", key: ""});
for (var index in OutputDevice.printers) {
printersModel.append({name: OutputDevice.printers[index].name, key: OutputDevice.printers[index].key});
}
}
}

View file

@ -0,0 +1,270 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import UM 1.3 as UM
Item {
id: root;
property var shadowRadius: UM.Theme.getSize("monitor_shadow_radius").width;
property var shadowOffset: UM.Theme.getSize("monitor_shadow_offset").width;
property var printer: null;
property var collapsed: true;
height: childrenRect.height + shadowRadius * 2; // Bubbles upward
width: parent.width; // Bubbles downward
// The actual card (white block)
Rectangle {
// 5px margin, but shifted 2px vertically because of the shadow
anchors {
bottomMargin: root.shadowRadius + root.shadowOffset;
leftMargin: root.shadowRadius;
rightMargin: root.shadowRadius;
topMargin: root.shadowRadius - root.shadowOffset;
}
color: {
if (!printer) {
return UM.Theme.getColor("monitor_card_background_inactive");
}
if (printer.state == "disabled") {
return UM.Theme.getColor("monitor_card_background_inactive");
} else {
return UM.Theme.getColor("monitor_card_background");
}
}
height: childrenRect.height + UM.Theme.getSize("default_margin").height;
layer.effect: DropShadow {
radius: root.shadowRadius;
verticalOffset: root.shadowOffset;
color: "#3F000000"; // 25% shadow
}
layer.enabled: true
width: parent.width - 2 * shadowRadius;
Column {
id: cardContents;
anchors {
top: parent.top;
topMargin: UM.Theme.getSize("default_margin").height;
}
height: childrenRect.height;
width: parent.width;
spacing: UM.Theme.getSize("default_margin").height;
// Main card
Item {
id: mainCard;
height: 60 * screenScaleFactor;
width: parent.width;
// Machine icon
Item {
id: machineIcon;
anchors {
leftMargin: UM.Theme.getSize("wide_margin").width;
top: parent.top;
left: parent.left;
margins: UM.Theme.getSize("default_margin").width;
}
height: parent.height - 2 * UM.Theme.getSize("default_margin").width;
width: height;
// Skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill_dark");
radius: UM.Theme.getSize("default_margin").width;
visible: !printer;
}
// Content
UM.RecolorImage {
anchors.centerIn: parent;
color: {
if (printer && printer.activePrintJob != undefined) {
return UM.Theme.getColor("monitor_printer_icon");
}
return UM.Theme.getColor("monitor_printer_icon_inactive");
}
height: sourceSize.height;
source: {
if (!printer) {
return "";
}
switch(printer.type) {
case "Ultimaker 3":
return "../svg/UM3-icon.svg";
case "Ultimaker 3 Extended":
return "../svg/UM3x-icon.svg";
case "Ultimaker S5":
return "../svg/UMs5-icon.svg";
}
}
visible: printer;
width: sourceSize.width;
}
}
// Printer info
Item {
id: printerInfo;
anchors {
left: machineIcon.right;
leftMargin: UM.Theme.getSize("wide_margin").width;
right: collapseIcon.left;
verticalCenter: machineIcon.verticalCenter;
}
height: childrenRect.height;
// Machine name
Item {
id: machineNameLabel;
height: UM.Theme.getSize("monitor_text_line").height;
width: {
var percent = printer ? 0.75 : 0.3;
return Math.round(parent.width * percent);
}
// Skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill_dark");
visible: !printer;
}
// Actual content
Label {
anchors.fill: parent;
color: UM.Theme.getColor("text");
elide: Text.ElideRight;
font: UM.Theme.getFont("default_bold");
text: printer ? printer.name : "";
visible: printer;
width: parent.width;
}
}
// Job name
Item {
id: activeJobLabel;
anchors {
top: machineNameLabel.bottom;
topMargin: Math.round(UM.Theme.getSize("default_margin").height / 2);
}
height: UM.Theme.getSize("monitor_text_line").height;
width: Math.round(parent.width * 0.75);
// Skeleton
Rectangle {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_skeleton_fill_dark");
visible: !printer;
}
// Actual content
Label {
anchors.fill: parent;
color: UM.Theme.getColor("monitor_text_inactive");
elide: Text.ElideRight;
font: UM.Theme.getFont("default");
text: {
if (!printer) {
return "";
}
if (printer.state == "disabled") {
return catalog.i18nc("@label", "Not available");
} else if (printer.state == "unreachable") {
return catalog.i18nc("@label", "Unreachable");
}
if (printer.activePrintJob != null && printer.activePrintJob.name) {
return printer.activePrintJob.name;
}
return catalog.i18nc("@label", "Available");
}
visible: printer;
}
}
}
// Collapse icon
UM.RecolorImage {
id: collapseIcon;
anchors {
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
verticalCenter: parent.verticalCenter;
}
color: UM.Theme.getColor("text");
height: 15 * screenScaleFactor; // TODO: Theme!
source: root.collapsed ? UM.Theme.getIcon("arrow_left") : UM.Theme.getIcon("arrow_bottom");
sourceSize {
height: height;
width: width;
}
visible: printer;
width: 15 * screenScaleFactor; // TODO: Theme!
}
MouseArea {
anchors.fill: parent;
enabled: printer;
onClicked: {
if (model && root.collapsed) {
printerList.currentIndex = model.index;
} else {
printerList.currentIndex = -1;
}
}
}
Connections {
target: printerList;
onCurrentIndexChanged: {
root.collapsed = printerList.currentIndex != model.index;
}
}
}
HorizontalLine {
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
}
visible: root.printer;
}
// Detailed card
PrinterCardDetails {
collapsed: root.collapsed;
printer: root.printer;
visible: root.printer;
}
CameraButton {
id: showCameraButton;
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
}
iconSource: "../svg/camera-icon.svg";
visible: root.printer && root.printJob;
}
}
// Progress bar
PrinterCardProgressBar {
anchors {
top: cardContents.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
visible: printer && printer.activePrintJob != null;
width: parent.width;
}
}
}

View file

@ -0,0 +1,69 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4 as LegacyControls
import UM 1.3 as UM
Item {
id: root;
property var printer: null;
property var printJob: printer ? printer.activePrintJob : null;
property var collapsed: true;
Behavior on height { NumberAnimation { duration: 100 } }
Behavior on opacity { NumberAnimation { duration: 100 } }
height: collapsed ? 0 : childrenRect.height;
opacity: collapsed ? 0 : 1;
width: parent.width;
Column {
id: contentColumn;
anchors {
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").width;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
}
height: childrenRect.height + UM.Theme.getSize("wide_margin").height;
spacing: UM.Theme.getSize("default_margin").height;
width: parent.width;
PrinterInfoBlock {
printer: root.printer;
printJob: root.printer ? root.printer.activePrintJob : null;
}
HorizontalLine {
visible: root.printJob;
}
Row {
height: childrenRect.height;
visible: root.printJob;
width: parent.width;
PrintJobTitle {
job: root.printer ? root.printer.activePrintJob : null;
}
PrintJobContextMenu {
id: contextButton;
anchors {
right: parent.right;
rightMargin: UM.Theme.getSize("wide_margin").width;
}
printJob: root.printer ? root.printer.activePrintJob : null;
visible: printJob;
}
}
PrintJobPreview {
anchors.horizontalCenter: parent.horizontalCenter;
job: root.printer && root.printer.activePrintJob ? root.printer.activePrintJob : null;
visible: root.printJob;
}
}
}

View file

@ -0,0 +1,108 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Controls.Styles 1.3
import QtQuick.Controls 1.4
import UM 1.3 as UM
ProgressBar {
property var progress: {
if (!printer || printer.activePrintJob == null) {
return 0;
}
var result = printer.activePrintJob.timeElapsed / printer.activePrintJob.timeTotal;
if (result > 1.0) {
result = 1.0;
}
return result;
}
style: ProgressBarStyle {
property var remainingTime: {
if (!printer || printer.activePrintJob == null) {
return 0;
}
/* Sometimes total minus elapsed is less than 0. Use Math.max() to prevent remaining
time from ever being less than 0. Negative durations cause strange behavior such
as displaying "-1h -1m". */
return Math.max(printer.activePrintJob.timeTotal - printer.activePrintJob.timeElapsed, 0);
}
property var progressText: {
if (printer === null ) {
return "";
}
switch (printer.activePrintJob.state) {
case "wait_cleanup":
if (printer.activePrintJob.timeTotal > printer.activePrintJob.timeElapsed) {
return catalog.i18nc("@label:status", "Aborted");
}
return catalog.i18nc("@label:status", "Finished");
case "pre_print":
case "sent_to_printer":
return catalog.i18nc("@label:status", "Preparing");
case "aborted":
return catalog.i18nc("@label:status", "Aborted");
case "wait_user_action":
return catalog.i18nc("@label:status", "Aborted");
case "pausing":
return catalog.i18nc("@label:status", "Pausing");
case "paused":
return OutputDevice.formatDuration( remainingTime );
case "resuming":
return catalog.i18nc("@label:status", "Resuming");
case "queued":
return catalog.i18nc("@label:status", "Action required");
default:
return OutputDevice.formatDuration( remainingTime );
}
}
background: Rectangle {
color: UM.Theme.getColor("monitor_progress_background");
implicitHeight: visible ? 24 : 0;
implicitWidth: 100;
}
progress: Rectangle {
id: progressItem;
color: {
if (! printer || !printer.activePrintJob) {
return "black";
}
var state = printer.activePrintJob.state
var inactiveStates = [
"pausing",
"paused",
"resuming",
"wait_cleanup"
];
if (inactiveStates.indexOf(state) > -1 && remainingTime > 0) {
return UM.Theme.getColor("monitor_progress_fill_inactive");
} else {
return UM.Theme.getColor("monitor_progress_fill");
}
}
Label {
id: progressLabel;
anchors {
left: parent.left;
leftMargin: getTextOffset();
}
text: progressText;
anchors.verticalCenter: parent.verticalCenter;
color: progressItem.width + progressLabel.width < control.width ? UM.Theme.getColor("text") : UM.Theme.getColor("monitor_progress_fill_text");
width: contentWidth;
font: UM.Theme.getFont("default");
}
function getTextOffset() {
if (progressItem.width + progressLabel.width + 16 < control.width) {
return progressItem.width + UM.Theme.getSize("default_margin").width;
} else {
return progressItem.width - progressLabel.width - UM.Theme.getSize("default_margin").width;
}
}
}
}
value: progress;
width: parent.width;
}

View file

@ -1,28 +1,32 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import UM 1.2 as UM
Item
{
property alias color: background.color
property alias text: familyNameLabel.text
property var padding: 0
implicitHeight: familyNameLabel.contentHeight + 2 * padding // Apply the padding to top and bottom.
implicitWidth: familyNameLabel.contentWidth + implicitHeight // The extra height is added to ensure the radius doesn't cut something off.
Rectangle
{
id: background
height: parent.height
width: parent.width
color: parent.color
anchors.right: parent.right
anchors.horizontalCenter: parent.horizontalCenter
radius: 0.5 * height
Item {
property alias text: familyNameLabel.text;
property var padding: 3 * screenScaleFactor; // TODO: Theme!
implicitHeight: familyNameLabel.contentHeight + 2 * padding; // Apply the padding to top and bottom.
implicitWidth: Math.max(48 * screenScaleFactor, familyNameLabel.contentWidth + implicitHeight); // The extra height is added to ensure the radius doesn't cut something off.
Rectangle {
id: background;
anchors {
horizontalCenter: parent.horizontalCenter;
right: parent.right;
}
color: familyNameLabel.text.length < 1 ? UM.Theme.getColor("monitor_skeleton_fill") : UM.Theme.getColor("monitor_pill_background");
height: parent.height;
radius: 0.5 * height;
width: parent.width;
}
Label
{
id: familyNameLabel
anchors.centerIn: parent
text: ""
Label {
id: familyNameLabel;
anchors.centerIn: parent;
color: UM.Theme.getColor("text");
text: "";
}
}

View file

@ -0,0 +1,83 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.3
import QtQuick.Dialogs 1.1
import QtQuick.Controls 2.0
import QtQuick.Controls.Styles 1.3
import QtGraphicalEffects 1.0
import QtQuick.Controls 1.4 as LegacyControls
import UM 1.3 as UM
// Includes printer type pill and extuder configurations
Item {
id: root;
property var printer: null;
property var printJob: null;
width: parent.width;
height: childrenRect.height;
// Printer family pills
Row {
id: printerFamilyPills;
anchors {
left: parent.left;
right: parent.right;
}
height: childrenRect.height;
spacing: Math.round(0.5 * UM.Theme.getSize("default_margin").width);
width: parent.width;
Repeater {
id: compatiblePills;
delegate: PrinterFamilyPill {
text: modelData;
}
model: printJob ? printJob.compatibleMachineFamilies : [];
visible: printJob;
}
PrinterFamilyPill {
text: printer ? printer.type : "";
visible: !compatiblePills.visible && printer;
}
}
// Extruder info
Row {
id: extrudersInfo;
anchors {
left: parent.left;
right: parent.right;
rightMargin: UM.Theme.getSize("default_margin").width;
top: printerFamilyPills.bottom;
topMargin: UM.Theme.getSize("default_margin").height;
}
height: childrenRect.height;
spacing: UM.Theme.getSize("default_margin").width;
width: parent.width;
PrintCoreConfiguration {
width: Math.round(parent.width / 2) * screenScaleFactor;
printCoreConfiguration: getExtruderConfig(0);
}
PrintCoreConfiguration {
width: Math.round(parent.width / 2) * screenScaleFactor;
printCoreConfiguration: getExtruderConfig(1);
}
}
function getExtruderConfig( i ) {
if (root.printJob) {
// Use more-specific print job if possible
return root.printJob.configuration.extruderConfigurations[i];
}
if (root.printer) {
return root.printer.printerConfiguration.extruderConfigurations[i];
}
return null;
}
}

View file

@ -1,84 +1,65 @@
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.4
import QtQuick.Controls.Styles 1.4
import UM 1.3 as UM
import Cura 1.0 as Cura
Item {
property var cameraUrl: "";
Item
{
property var camera: null
Rectangle
{
anchors.fill:parent
color: UM.Theme.getColor("viewport_overlay")
opacity: 0.5
Rectangle {
anchors.fill:parent;
color: UM.Theme.getColor("viewport_overlay");
opacity: 0.5;
}
MouseArea
{
anchors.fill: parent
onClicked: OutputDevice.setActiveCamera(null)
z: 0
MouseArea {
anchors.fill: parent;
onClicked: OutputDevice.setActiveCameraUrl("");
z: 0;
}
CameraButton
{
id: closeCameraButton
iconSource: UM.Theme.getIcon("cross1")
anchors
{
top: cameraImage.top
topMargin: UM.Theme.getSize("default_margin").height
CameraButton {
id: closeCameraButton;
anchors {
right: cameraImage.right
rightMargin: UM.Theme.getSize("default_margin").width
top: cameraImage.top
topMargin: UM.Theme.getSize("default_margin").height
}
z: 999
iconSource: UM.Theme.getIcon("cross1");
z: 999;
}
Image
{
Cura.NetworkMJPGImage {
id: cameraImage
width: Math.min(sourceSize.width === 0 ? 800 * screenScaleFactor : sourceSize.width, maximumWidth)
height: Math.round((sourceSize.height === 0 ? 600 * screenScaleFactor : sourceSize.height) * width / sourceSize.width)
anchors.horizontalCenter: parent.horizontalCenter
anchors.verticalCenter: parent.verticalCenter
anchors.horizontalCenter: parent.horizontalCenter;
anchors.verticalCenter: parent.verticalCenter;
height: Math.round((imageHeight === 0 ? 600 * screenScaleFactor : imageHeight) * width / imageWidth);
onVisibleChanged: {
if (visible) {
if (cameraUrl != "") {
start();
}
} else {
if (cameraUrl != "") {
stop();
}
}
}
source: cameraUrl
width: Math.min(imageWidth === 0 ? 800 * screenScaleFactor : imageWidth, maximumWidth);
z: 1
onVisibleChanged:
{
if(visible)
{
if(camera != null)
{
camera.start()
}
} else
{
if(camera != null)
{
camera.stop()
}
}
}
source:
{
if(camera != null && camera.latestImage != null)
{
return camera.latestImage;
}
return "";
}
}
MouseArea
{
anchors.fill: cameraImage
onClicked:
{
OutputDevice.setActiveCamera(null)
MouseArea {
anchors.fill: cameraImage;
onClicked: {
OutputDevice.setActiveCameraUrl("");
}
z: 1
z: 1;
}
}

View file

@ -1,125 +1,126 @@
import UM 1.2 as UM
import Cura 1.0 as Cura
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.1
import UM 1.2 as UM
import Cura 1.0 as Cura
Item
{
id: base
Item {
id: base;
property string activeQualityDefinitionId: Cura.MachineManager.activeQualityDefinitionId;
property bool isUM3: activeQualityDefinitionId == "ultimaker3" || activeQualityDefinitionId.match("ultimaker_") != null;
property bool printerConnected: Cura.MachineManager.printerConnected;
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands;
property bool authenticationRequested: printerConnected && (Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 || Cura.MachineManager.printerOutputDevices[0].authenticationState == 5); // AuthState.AuthenticationRequested or AuthenticationReceived.
property string activeQualityDefinitionId: Cura.MachineManager.activeQualityDefinitionId
property bool isUM3: activeQualityDefinitionId == "ultimaker3" || activeQualityDefinitionId.match("ultimaker_") != null
property bool printerConnected: Cura.MachineManager.printerConnected
property bool printerAcceptsCommands: printerConnected && Cura.MachineManager.printerOutputDevices[0].acceptsCommands
property bool authenticationRequested: printerConnected && (Cura.MachineManager.printerOutputDevices[0].authenticationState == 2 || Cura.MachineManager.printerOutputDevices[0].authenticationState == 5) // AuthState.AuthenticationRequested or AuthenticationReceived.
UM.I18nCatalog {
id: catalog;
name: "cura";
}
Row
{
objectName: "networkPrinterConnectButton"
visible: isUM3
spacing: UM.Theme.getSize("default_margin").width
Row {
objectName: "networkPrinterConnectButton";
spacing: UM.Theme.getSize("default_margin").width;
visible: isUM3;
Button
{
height: UM.Theme.getSize("save_button_save_to_button").height
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
text: catalog.i18nc("@action:button", "Request Access")
style: UM.Theme.styles.sidebar_action_button
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
Button {
height: UM.Theme.getSize("save_button_save_to_button").height;
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication();
style: UM.Theme.styles.sidebar_action_button;
text: catalog.i18nc("@action:button", "Request Access");
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer");
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested;
}
Button
{
height: UM.Theme.getSize("save_button_save_to_button").height
tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer")
text: catalog.i18nc("@action:button", "Connect")
style: UM.Theme.styles.sidebar_action_button
onClicked: connectActionDialog.show()
visible: !printerConnected
Button {
height: UM.Theme.getSize("save_button_save_to_button").height;
onClicked: connectActionDialog.show();
style: UM.Theme.styles.sidebar_action_button;
text: catalog.i18nc("@action:button", "Connect");
tooltip: catalog.i18nc("@info:tooltip", "Connect to a printer");
visible: !printerConnected;
}
}
UM.Dialog
{
id: connectActionDialog
Loader
{
anchors.fill: parent
source: "DiscoverUM3Action.qml"
UM.Dialog {
id: connectActionDialog;
rightButtons: Button {
iconName: "dialog-close";
onClicked: connectActionDialog.reject();
text: catalog.i18nc("@action:button", "Close");
}
rightButtons: Button
{
text: catalog.i18nc("@action:button", "Close")
iconName: "dialog-close"
onClicked: connectActionDialog.reject()
Loader {
anchors.fill: parent;
source: "DiscoverUM3Action.qml";
}
}
Column {
anchors.fill: parent;
objectName: "networkPrinterConnectionInfo";
spacing: UM.Theme.getSize("default_margin").width;
visible: isUM3;
Column
{
objectName: "networkPrinterConnectionInfo"
visible: isUM3
spacing: UM.Theme.getSize("default_margin").width
anchors.fill: parent
Button
{
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer")
text: catalog.i18nc("@action:button", "Request Access")
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication()
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested
Button {
onClicked: Cura.MachineManager.printerOutputDevices[0].requestAuthentication();
text: catalog.i18nc("@action:button", "Request Access");
tooltip: catalog.i18nc("@info:tooltip", "Send access request to the printer");
visible: printerConnected && !printerAcceptsCommands && !authenticationRequested;
}
Row
{
visible: printerConnected
spacing: UM.Theme.getSize("default_margin").width
Row {
anchors {
left: parent.left;
right: parent.right;
}
height: childrenRect.height;
spacing: UM.Theme.getSize("default_margin").width;
visible: printerConnected;
anchors.left: parent.left
anchors.right: parent.right
height: childrenRect.height
Column {
Repeater {
model: Cura.ExtrudersModel {
simpleNames: true;
}
Column
{
Repeater
{
model: Cura.ExtrudersModel { simpleNames: true }
Label { text: model.name }
Label {
text: model.name;
}
}
}
Column
{
Repeater
{
id: nozzleColumn
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].hotendIds : null
Label { text: nozzleColumn.model[index] }
Column {
Repeater {
id: nozzleColumn;
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].hotendIds : null;
Label {
text: nozzleColumn.model[index];
}
}
}
Column
{
Repeater
{
id: materialColumn
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].materialNames : null
Label { text: materialColumn.model[index] }
Column {
Repeater {
id: materialColumn;
model: printerConnected ? Cura.MachineManager.printerOutputDevices[0].materialNames : null;
Label {
text: materialColumn.model[index];
}
}
}
}
Button
{
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura")
text: catalog.i18nc("@action:button", "Activate Configuration")
visible: false // printerConnected && !isClusterPrinter()
onClicked: manager.loadConfigurationFromPrinter()
Button {
onClicked: manager.loadConfigurationFromPrinter();
text: catalog.i18nc("@action:button", "Activate Configuration");
tooltip: catalog.i18nc("@info:tooltip", "Load the configuration of the printer into Cura");
visible: false; // printerConnected && !isClusterPrinter()
}
}
UM.I18nCatalog{id: catalog; name:"cura"}
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1 +1,4 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><title>warning-icon</title><path d="M18.09,1.31A2.35,2.35,0,0,0,16,0a2.31,2.31,0,0,0-2.09,1.31L.27,28.44A2.49,2.49,0,0,0,.11,30.3a2.38,2.38,0,0,0,1.16,1.42A2.33,2.33,0,0,0,2.36,32H29.64A2.4,2.4,0,0,0,32,29.57a2.55,2.55,0,0,0-.27-1.14ZM3.34,29,16,3.83,28.66,29Z"/><polygon points="13.94 25.19 13.94 25.19 13.94 25.19 13.94 25.19"/><polygon points="14.39 21.68 17.61 21.68 18.11 11.85 13.89 11.85 14.39 21.68"/><path d="M16.06,23.08a2.19,2.19,0,0,0-1.56,3.66,2.14,2.14,0,0,0,1.56.55,2.06,2.06,0,0,0,1.54-.55,2.1,2.1,0,0,0,.55-1.55,2.17,2.17,0,0,0-.53-1.55A2.05,2.05,0,0,0,16.06,23.08Z"/></svg>
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<title>warning-icon</title>
<path d="M18.09,1.31A2.35,2.35,0,0,0,16,0a2.31,2.31,0,0,0-2.09,1.31L.27,28.44A2.49,2.49,0,0,0,.11,30.3a2.38,2.38,0,0,0,1.16,1.42A2.33,2.33,0,0,0,2.36,32H29.64A2.4,2.4,0,0,0,32,29.57a2.55,2.55,0,0,0-.27-1.14ZM3.34,29,16,3.83,28.66,29Z"/><polygon points="13.94 25.19 13.94 25.19 13.94 25.19 13.94 25.19"/><polygon points="14.39 21.68 17.61 21.68 18.11 11.85 13.89 11.85 14.39 21.68"/><path d="M16.06,23.08a2.19,2.19,0,0,0-1.56,3.66,2.14,2.14,0,0,0,1.56.55,2.06,2.06,0,0,0,1.54-.55,2.1,2.1,0,0,0,.55-1.55,2.17,2.17,0,0,0-.53-1.55A2.05,2.05,0,0,0,16.06,23.08Z"/>
</svg>

Before

Width:  |  Height:  |  Size: 684 B

After

Width:  |  Height:  |  Size: 695 B

Before After
Before After

View file

@ -21,12 +21,12 @@ from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
from cura.PrinterOutput.ExtruderConfigurationModel import ExtruderConfigurationModel
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.PrinterOutput.NetworkCamera import NetworkCamera
from .ClusterUM3PrinterOutputController import ClusterUM3PrinterOutputController
from .SendMaterialJob import SendMaterialJob
from .ConfigurationChangeModel import ConfigurationChangeModel
from .UM3PrintJobOutputModel import UM3PrintJobOutputModel
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply
from PyQt5.QtGui import QDesktopServices, QImage
@ -46,7 +46,8 @@ i18n_catalog = i18nCatalog("cura")
class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
printJobsChanged = pyqtSignal()
activePrinterChanged = pyqtSignal()
activeCameraChanged = pyqtSignal()
activeCameraUrlChanged = pyqtSignal()
receivedPrintJobsChanged = pyqtSignal()
# This is a bit of a hack, as the notify can only use signals that are defined by the class that they are in.
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
@ -60,7 +61,8 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._dummy_lambdas = ("", {}, io.BytesIO()) #type: Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]
self._print_jobs = [] # type: List[PrintJobOutputModel]
self._print_jobs = [] # type: List[UM3PrintJobOutputModel]
self._received_print_jobs = False # type: bool
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterMonitorItem.qml")
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "../resources/qml/ClusterControlItem.qml")
@ -90,14 +92,14 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
self._finished_jobs = [] # type: List[PrintJobOutputModel]
self._finished_jobs = [] # type: List[UM3PrintJobOutputModel]
self._cluster_size = int(properties.get(b"cluster_size", 0)) # type: int
self._latest_reply_handler = None # type: Optional[QNetworkReply]
self._sending_job = None
self._active_camera = None # type: Optional[NetworkCamera]
self._active_camera_url = QUrl() # type: QUrl
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
self.writeStarted.emit(self)
@ -261,30 +263,21 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def activePrinter(self) -> Optional[PrinterOutputModel]:
return self._active_printer
@pyqtProperty(QObject, notify=activeCameraChanged)
def activeCamera(self) -> Optional[NetworkCamera]:
return self._active_camera
@pyqtSlot(QObject)
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
if self._active_printer != printer:
if self._active_printer and self._active_printer.camera:
self._active_printer.camera.stop()
self._active_printer = printer
self.activePrinterChanged.emit()
@pyqtSlot(QObject)
def setActiveCamera(self, camera: Optional[NetworkCamera]) -> None:
if self._active_camera != camera:
if self._active_camera:
self._active_camera.stop()
@pyqtProperty(QUrl, notify = activeCameraUrlChanged)
def activeCameraUrl(self) -> "QUrl":
return self._active_camera_url
self._active_camera = camera
if self._active_camera:
self._active_camera.start()
self.activeCameraChanged.emit()
@pyqtSlot(QUrl)
def setActiveCameraUrl(self, camera_url: "QUrl") -> None:
if self._active_camera_url != camera_url:
self._active_camera_url = camera_url
self.activeCameraUrlChanged.emit()
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
if self._progress_message:
@ -349,15 +342,19 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
@pyqtProperty("QVariantList", notify = printJobsChanged)
def printJobs(self)-> List[PrintJobOutputModel]:
def printJobs(self)-> List[UM3PrintJobOutputModel]:
return self._print_jobs
@pyqtProperty(bool, notify = receivedPrintJobsChanged)
def receivedPrintJobs(self) -> bool:
return self._received_print_jobs
@pyqtProperty("QVariantList", notify = printJobsChanged)
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
def queuedPrintJobs(self) -> List[UM3PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.state == "queued" or print_job.state == "error"]
@pyqtProperty("QVariantList", notify = printJobsChanged)
def activePrintJobs(self) -> List[PrintJobOutputModel]:
def activePrintJobs(self) -> List[UM3PrintJobOutputModel]:
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
@pyqtProperty("QVariantList", notify = clusterPrintersChanged)
@ -406,6 +403,11 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
# is a modification of the cluster queue and not of the actual job.
self.delete("print_jobs/{uuid}".format(uuid = print_job_uuid), on_finished=None)
@pyqtSlot(str)
def forceSendJob(self, print_job_uuid: str) -> None:
data = "{\"force\": true}"
self.put("print_jobs/{uuid}".format(uuid=print_job_uuid), data, on_finished=None)
def _printJobStateChanged(self) -> None:
username = self._getUserName()
@ -455,6 +457,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
self.get("print_jobs/{uuid}/preview_image".format(uuid=print_job.key), on_finished=self._onGetPreviewImageFinished)
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
self._received_print_jobs = True
self.receivedPrintJobsChanged.emit()
if not checkValidGetReply(reply):
return
@ -533,12 +538,12 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
number_of_extruders = self._number_of_extruders)
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
printer.setCameraUrl(QUrl("http://" + data["ip_address"] + ":8080/?action=stream"))
self._printers.append(printer)
return printer
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
def _createPrintJobModel(self, data: Dict[str, Any]) -> UM3PrintJobOutputModel:
print_job = UM3PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
key=data["uuid"], name= data["name"])
configuration = ConfigurationModel()
@ -558,7 +563,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
print_job.stateChanged.connect(self._printJobStateChanged)
return print_job
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
def _updatePrintJob(self, print_job: UM3PrintJobOutputModel, data: Dict[str, Any]) -> None:
print_job.updateTimeTotal(data["time_total"])
print_job.updateTimeElapsed(data["time_elapsed"])
impediments_to_printing = data.get("impediments_to_printing", [])
@ -574,6 +579,16 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
if not status_set_by_impediment:
print_job.updateState(data["status"])
print_job.updateConfigurationChanges(self._createConfigurationChanges(data["configuration_changes_required"]))
def _createConfigurationChanges(self, data: List[Dict[str, Any]]) -> List[ConfigurationChangeModel]:
result = []
for change in data:
result.append(ConfigurationChangeModel(type_of_change=change["type_of_change"],
index=change["index"],
target_name=change["target_name"],
origin_name=change["origin_name"]))
return result
def _createMaterialOutputModel(self, material_data) -> MaterialOutputModel:
containers = ContainerRegistry.getInstance().findInstanceContainers(type="material", GUID=material_data["guid"])
@ -631,7 +646,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
material = self._createMaterialOutputModel(material_data)
extruder.updateActiveMaterial(material)
def _removeJob(self, job: PrintJobOutputModel) -> bool:
def _removeJob(self, job: UM3PrintJobOutputModel) -> bool:
if job not in self._print_jobs:
return False
@ -675,7 +690,7 @@ def checkValidGetReply(reply: QNetworkReply) -> bool:
return True
def findByKey(lst: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
def findByKey(lst: List[Union[UM3PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[UM3PrintJobOutputModel]:
for item in lst:
if item.key == key:
return item

View file

@ -0,0 +1,29 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
class ConfigurationChangeModel(QObject):
def __init__(self, type_of_change: str, index: int, target_name: str, origin_name: str) -> None:
super().__init__()
self._type_of_change = type_of_change
# enum = ["material", "print_core_change"]
self._index = index
self._target_name = target_name
self._origin_name = origin_name
@pyqtProperty(int, constant = True)
def index(self) -> int:
return self._index
@pyqtProperty(str, constant = True)
def typeOfChange(self) -> str:
return self._type_of_change
@pyqtProperty(str, constant = True)
def targetName(self) -> str:
return self._target_name
@pyqtProperty(str, constant = True)
def originName(self) -> str:
return self._origin_name

View file

@ -7,7 +7,6 @@ from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutp
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from cura.PrinterOutput.MaterialOutputModel import MaterialOutputModel
from cura.PrinterOutput.NetworkCamera import NetworkCamera
from cura.Settings.ContainerManager import ContainerManager
from cura.Settings.ExtruderManager import ExtruderManager
@ -18,7 +17,7 @@ from UM.i18n import i18nCatalog
from UM.Message import Message
from PyQt5.QtNetwork import QNetworkRequest
from PyQt5.QtCore import QTimer
from PyQt5.QtCore import QTimer, QUrl
from PyQt5.QtWidgets import QMessageBox
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
@ -568,7 +567,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
# Quickest way to get the firmware version is to grab it from the zeroconf.
firmware_version = self._properties.get(b"firmware_version", b"").decode("utf-8")
self._printers = [PrinterOutputModel(output_controller=self._output_controller, number_of_extruders=self._number_of_extruders, firmware_version=firmware_version)]
self._printers[0].setCamera(NetworkCamera("http://" + self._address + ":8080/?action=stream"))
self._printers[0].setCameraUrl(QUrl("http://" + self._address + ":8080/?action=stream"))
for extruder in self._printers[0].extruders:
extruder.activeMaterialChanged.connect(self.materialIdChanged)
extruder.hotendIDChanged.connect(self.hotendIdChanged)

View file

@ -0,0 +1,29 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, pyqtSlot
from typing import Optional, TYPE_CHECKING, List
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QImage
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
from .ConfigurationChangeModel import ConfigurationChangeModel
class UM3PrintJobOutputModel(PrintJobOutputModel):
configurationChangesChanged = pyqtSignal()
def __init__(self, output_controller: "PrinterOutputController", key: str = "", name: str = "", parent=None) -> None:
super().__init__(output_controller, key, name, parent)
self._configuration_changes = [] # type: List[ConfigurationChangeModel]
@pyqtProperty("QVariantList", notify=configurationChangesChanged)
def configurationChanges(self) -> List[ConfigurationChangeModel]:
return self._configuration_changes
def updateConfigurationChanges(self, changes: List[ConfigurationChangeModel]) -> None:
if len(self._configuration_changes) == 0 and len(changes) == 0:
return
self._configuration_changes = changes
self.configurationChangesChanged.emit()

View file

@ -64,6 +64,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._accepts_commands = True
self._paused = False
self._printer_busy = False # when printer is preheating and waiting (M190/M109), or when waiting for action on the printer
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
@ -73,6 +74,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
self._command_received = Event()
self._command_received.set()
self._firmware_name_requested = False
self._firmware_updater = AvrFirmwareUpdater(self)
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
@ -223,15 +225,18 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
except:
continue
if not self._firmware_name_requested:
self._firmware_name_requested = True
self.sendCommand("M115")
if b"FIRMWARE_NAME:" in line:
self._setFirmwareName(line)
if self._last_temperature_request is None or time() > self._last_temperature_request + self._timeout:
# Timeout, or no request has been sent at all.
self._command_received.set() # We haven't really received the ok, but we need to send a new command
self.sendCommand("M105")
self._last_temperature_request = time()
if self._firmware_name is None:
self.sendCommand("M115")
if not self._printer_busy: # Don't flood the printer with temperature requests while it is busy
self.sendCommand("M105")
self._last_temperature_request = time()
if re.search(b"[B|T\d*]: ?\d+\.?\d*", line): # Temperature message. 'T:' for extruder and 'B:' for bed
extruder_temperature_matches = re.findall(b"T(\d*): ?(\d+\.?\d*) ?\/?(\d+\.?\d*)?", line)
@ -264,29 +269,39 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if match[1]:
self._printers[0].updateTargetBedTemperature(float(match[1]))
if b"FIRMWARE_NAME:" in line:
self._setFirmwareName(line)
if line == b"":
# An empty line means that the firmware is idle
# Multiple empty lines probably means that the firmware and Cura are waiting
# for eachother due to a missed "ok", so we keep track of empty lines
self._firmware_idle_count += 1
else:
self._firmware_idle_count = 0
if line.startswith(b"ok") or self._firmware_idle_count > 1:
self._printer_busy = False
if b"ok" in line:
self._command_received.set()
if not self._command_queue.empty():
self._sendCommand(self._command_queue.get())
if self._is_printing:
elif self._is_printing:
if self._paused:
pass # Nothing to do!
else:
self._sendNextGcodeLine()
if line.startswith(b"echo:busy:"):
self._printer_busy = True
if self._is_printing:
if line.startswith(b'!!'):
Logger.log('e', "Printer signals fatal error. Cancelling print. {}".format(line))
self.cancelPrint()
elif b"resend" in line.lower() or b"rs" in line:
elif line.lower().startswith(b"resend") or line.startswith(b"rs"):
# A resend can be requested either by Resend, resend or rs.
try:
self._gcode_position = int(line.replace(b"N:", b" ").replace(b"N", b" ").replace(b":", b" ").split()[-1])
except:
if b"rs" in line:
if line.startswith(b"rs"):
# In some cases of the RS command it needs to be handled differently.
self._gcode_position = int(line.split()[1])

View file

@ -753,8 +753,8 @@
"package_type": "material",
"display_name": "Generic ABS",
"description": "The generic ABS profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -771,8 +771,44 @@
"package_type": "material",
"display_name": "Generic BAM",
"description": "The generic BAM profile which other profiles can be based upon.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericCFFCPE": {
"package_info": {
"package_id": "GenericCFFCPE",
"package_type": "material",
"display_name": "Generic CFF CPE",
"description": "The generic CFF CPE profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericCFFPA": {
"package_info": {
"package_id": "GenericCFFPA",
"package_type": "material",
"display_name": "Generic CFF PA",
"description": "The generic CFF PA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -789,8 +825,8 @@
"package_type": "material",
"display_name": "Generic CPE",
"description": "The generic CPE profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -807,8 +843,44 @@
"package_type": "material",
"display_name": "Generic CPE+",
"description": "The generic CPE+ profile which other profiles can be based upon.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericGFFCPE": {
"package_info": {
"package_id": "GenericGFFCPE",
"package_type": "material",
"display_name": "Generic GFF CPE",
"description": "The generic GFF CPE profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
"display_name": "Generic",
"email": "materials@ultimaker.com",
"website": "https://github.com/Ultimaker/fdm_materials",
"description": "Professional 3D printing made accessible."
}
}
},
"GenericGFFPA": {
"package_info": {
"package_id": "GenericGFFPA",
"package_type": "material",
"display_name": "Generic GFF PA",
"description": "The generic GFF PA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -826,7 +898,7 @@
"display_name": "Generic HIPS",
"description": "The generic HIPS profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -843,8 +915,8 @@
"package_type": "material",
"display_name": "Generic Nylon",
"description": "The generic Nylon profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -861,8 +933,8 @@
"package_type": "material",
"display_name": "Generic PC",
"description": "The generic PC profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -880,7 +952,7 @@
"display_name": "Generic PETG",
"description": "The generic PETG profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -897,8 +969,8 @@
"package_type": "material",
"display_name": "Generic PLA",
"description": "The generic PLA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -915,8 +987,8 @@
"package_type": "material",
"display_name": "Generic PP",
"description": "The generic PP profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -933,8 +1005,8 @@
"package_type": "material",
"display_name": "Generic PVA",
"description": "The generic PVA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -951,8 +1023,8 @@
"package_type": "material",
"display_name": "Generic Tough PLA",
"description": "The generic Tough PLA profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.0.1",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -969,8 +1041,8 @@
"package_type": "material",
"display_name": "Generic TPU",
"description": "The generic TPU profile which other profiles can be based upon.",
"package_version": "1.0.0",
"sdk_version": 6,
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://github.com/Ultimaker/fdm_materials",
"author": {
"author_id": "Generic",
@ -1077,7 +1149,7 @@
"website": "http://fiberlogy.com/en/fiberlogy-filaments/filament-hd-pla/",
"author": {
"author_id": "Fiberlogy",
"diplay_name": "Fiberlogy S.A.",
"display_name": "Fiberlogy S.A.",
"email": "grzegorz.h@fiberlogy.com",
"website": "http://fiberlogy.com"
}
@ -1225,7 +1297,7 @@
"package_type": "material",
"display_name": "Ultimaker ABS",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
@ -1238,13 +1310,32 @@
}
}
},
"UltimakerBAM": {
"package_info": {
"package_id": "UltimakerBAM",
"package_type": "material",
"display_name": "Ultimaker Breakaway",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/breakaway",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
}
}
},
"UltimakerCPE": {
"package_info": {
"package_id": "UltimakerCPE",
"package_type": "material",
"display_name": "Ultimaker CPE",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
@ -1257,13 +1348,32 @@
}
}
},
"UltimakerCPEP": {
"package_info": {
"package_id": "UltimakerCPEP",
"package_type": "material",
"display_name": "Ultimaker CPE+",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/cpe",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
}
}
},
"UltimakerNylon": {
"package_info": {
"package_id": "UltimakerNylon",
"package_type": "material",
"display_name": "Ultimaker Nylon",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
@ -1282,7 +1392,7 @@
"package_type": "material",
"display_name": "Ultimaker PC",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/pc",
"author": {
@ -1301,7 +1411,7 @@
"package_type": "material",
"display_name": "Ultimaker PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
@ -1314,13 +1424,32 @@
}
}
},
"UltimakerPP": {
"package_info": {
"package_id": "UltimakerPP",
"package_type": "material",
"display_name": "Ultimaker PP",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/pp",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
}
}
},
"UltimakerPVA": {
"package_info": {
"package_id": "UltimakerPVA",
"package_type": "material",
"display_name": "Ultimaker PVA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/abs",
"author": {
@ -1333,6 +1462,44 @@
}
}
},
"UltimakerTPU": {
"package_info": {
"package_id": "UltimakerTPU",
"package_type": "material",
"display_name": "Ultimaker TPU 95A",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.1.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/tpu-95a",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
}
}
},
"UltimakerTPLA": {
"package_info": {
"package_id": "UltimakerTPLA",
"package_type": "material",
"display_name": "Ultimaker Tough PLA",
"description": "Example package for material and quality profiles for Ultimaker materials.",
"package_version": "1.0.0",
"sdk_version": 5,
"website": "https://ultimaker.com/products/materials/tough-pla",
"author": {
"author_id": "Ultimaker",
"display_name": "Ultimaker B.V.",
"email": "materials@ultimaker.com",
"website": "https://ultimaker.com",
"description": "Professional 3D printing made accessible.",
"support_website": "https://ultimaker.com/en/resources/troubleshooting/materials"
}
}
},
"VertexDeltaABS": {
"package_info": {
"package_id": "VertexDeltaABS",

View file

@ -7,7 +7,6 @@
"author": "3Dator GmbH",
"manufacturer": "3Dator GmbH",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"supports_usb_connection": true,
"platform": "3dator_platform.stl",
"machine_extruder_trains":

View file

@ -0,0 +1,88 @@
{
"version": 2,
"name": "Anycubic 4Max",
"inherits": "fdmprinter",
"metadata":
{
"visible": true,
"author": "Jason Scurtu",
"manufacturer": "Anycubic",
"category": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "anycubic_4max_platform.stl",
"has_materials": true,
"quality_definition": "anycubic_4max",
"has_machine_quality": true,
"preferred_quality_type": "normal",
"machine_extruder_trains":
{
"0": "anycubic_4max_extruder_0"
}
},
"overrides":
{
"machine_name": { "default_value": "Anycubic 4Max" },
"machine_heated_bed": { "default_value": true },
"machine_width": { "default_value": 220 },
"machine_height": {"default_value": 300 },
"machine_depth": { "default_value": 220 },
"machine_center_is_zero": { "default_value": false },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 10 },
"machine_acceleration": { "default_value": 1500 },
"machine_max_acceleration_x": { "default_value": 1500 },
"machine_max_acceleration_y": { "default_value": 1500 },
"machine_max_acceleration_z": { "default_value": 100 },
"machine_max_jerk_xy": { "default_value": 11.0 },
"machine_max_jerk_z": { "default_value": 0.4 },
"machine_max_jerk_e": { "default_value": 11.0 },
"jerk_enabled": { "value": "True" },
"jerk_layer_0": { "value": "jerk_topbottom" },
"jerk_prime_tower": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_print": { "value": "11" },
"jerk_support": { "value": "math.ceil(jerk_print * 15 / 25)" },
"jerk_support_interface": { "value": "jerk_topbottom" },
"jerk_topbottom": { "value": "math.ceil(jerk_print * 5 / 25)" },
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"gantry_height": { "default_value": 25.0 },
"skin_overlap": { "value": "10" },
"acceleration_enabled": { "value": "True" },
"acceleration_layer_0": { "value": "acceleration_topbottom" },
"acceleration_prime_tower": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_print": { "value": "900" },
"acceleration_support": { "value": "math.ceil(acceleration_print * 2000 / 4000)" },
"acceleration_support_interface": { "value": "acceleration_topbottom" },
"acceleration_topbottom": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_travel": { "value": "acceleration_print" },
"acceleration_wall": { "value": "math.ceil(acceleration_print * 1000 / 3000)" },
"acceleration_wall_0": { "value": "math.ceil(acceleration_wall * 1000 / 1000)" },
"speed_layer_0": { "value": "20" },
"speed_print": { "value": "40" },
"speed_support": { "value": "speed_wall_0" },
"speed_support_interface": { "value": "speed_topbottom" },
"speed_topbottom": { "value": "math.ceil(speed_print * 20 / 35)" },
"speed_travel": { "value": "60" },
"speed_wall": { "value": "math.ceil(speed_print * 30 / 35)" },
"speed_wall_0": { "value": "math.ceil(speed_wall * 20 / 30)" },
"speed_wall_x": { "value": "speed_wall" },
"infill_pattern": {"value": "'zigzag'" },
"infill_before_walls": {"value": false },
"adhesion_type": { "default_value": "skirt" },
"material_bed_temperature": { "maximum_value": "150" },
"material_bed_temperature_layer_0": { "maximum_value": "150" },
"machine_gcode_flavor":{"default_value": "RepRap (Marlin/Sprinter)"},
"machine_start_gcode":{"default_value": "G21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 X0 Y0 ;move X/Y to min endstops\nG28 Z0 ;move Z to min endstops\nG1 Z15.0 F{speed_travel} ;move the platform down 15mm\nG92 E0 ;zero the extruded length\nG1 F200 E3 ;extrude 3mm of feed stock\nG92 E0 ;zero the extruded length again\nG1 F{speed_travel}\nM117 Printing...\nG5"},
"machine_end_gcode":{"default_value": "M104 S0 ; turn off extruder\nM140 S0 ; turn off bed\nM84 ; disable motors\nM107\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle\nto release some of the pressure\nG1 Z+0.5 E-5 ;X-20 Y-20 F{speed_travel} ;move Z up a bit and retract filament even more\nG28 X0 ;Y0 ;move X/Y to min endstops\nso the head is out of the way\nG1 Y180 F2000\nM84 ;steppers off\nG90\nM300 P300 S4000"}
}
}

View file

@ -8,7 +8,6 @@
"author": "TheTobby",
"manufacturer": "Anycubic",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "anycubic_i3_mega_platform.stl",
"has_materials": false,
"has_machine_quality": true,

View file

@ -19,13 +19,13 @@
"default_value": "Creality Ender-3"
},
"machine_width": {
"default_value": 220
"default_value": 235
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 220
"default_value": 235
},
"machine_heated_bed": {
"default_value": true
@ -87,10 +87,10 @@
"default_value": 5
},
"machine_start_gcode": {
"default_value": "; Ender 3 Custom Start G-code\nM104 S{material_print_temperature_layer_0} ; Set Extruder temperature\nM140 S{material_bed_temperature_layer_0} ; Set Heat Bed temperature\nM190 S{material_bed_temperature_layer_0} ; Wait for Heat Bed temperature\nM109 S{material_print_temperature_layer_0} ; Wait for Extruder temperature\nG28 ; Home all axes\nG92 E0 ; Reset Extruder\nG1 Z5.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.3 F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z0.3 F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3 F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z5.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed"
"default_value": "; Ender 3 Custom Start G-code\nG28 ; Home all axes\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\nG1 X0.1 Y20 Z0.3 F5000.0 ; Move to start position\nG1 X0.1 Y200.0 Z0.3 F1500.0 E15 ; Draw the first line\nG1 X0.4 Y200.0 Z0.3 F5000.0 ; Move to side a little\nG1 X0.4 Y20 Z0.3 F1500.0 E30 ; Draw the second line\nG92 E0 ; Reset Extruder\nG1 Z2.0 F3000 ; Move Z Axis up little to prevent scratching of Heat Bed\n; End of custom start GCode"
},
"machine_end_gcode": {
"default_value": "; Ender 3 Custom End G-code\nG4 ; Wait\nM220 S100 ; Reset Speed factor override percentage to default (100%)\nM221 S100 ; Reset Extrude factor override percentage to default (100%)\nG91 ; Set coordinates to relative\nG1 F1800 E-3 ; Retract filament 3 mm to prevent oozing\nG1 F3000 Z10 ; Move Z Axis up 10 mm to allow filament ooze freely\nG90 ; Set coordinates to absolute\nG1 X0 Y{machine_depth} F1000 ; Move Heat Bed to the front for easy print removal\nM104 S0 ; Turn off Extruder temperature\nM140 S0 ; Turn off Heat Bed\nM106 S0 ; Turn off Cooling Fan\nM107 ; Turn off Fan\nM84 ; Disable stepper motors"
"default_value": "; Ender 3 Custom End G-code\nG4 ; Wait\nM220 S100 ; Reset Speed factor override percentage to default (100%)\nM221 S100 ; Reset Extrude factor override percentage to default (100%)\nG91 ; Set coordinates to relative\nG1 F1800 E-3 ; Retract filament 3 mm to prevent oozing\nG1 F3000 Z20 ; Move Z Axis up 20 mm to allow filament ooze freely\nG90 ; Set coordinates to absolute\nG1 X0 Y{machine_depth} F1000 ; Move Heat Bed to the front for easy print removal\nM84 ; Disable stepper motors\n; End of custom end GCode"
}
}
}

5
resources/definitions/deltacomb.def.json Normal file → Executable file
View file

@ -8,7 +8,6 @@
"manufacturer": "Deltacomb 3D",
"category": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "deltacomb.stl",
"has_machine_quality": true,
"machine_extruder_trains":
@ -33,6 +32,7 @@
"material_final_print_temperature": { "value": "material_print_temperature - 5" },
"material_initial_print_temperature": { "value": "material_print_temperature" },
"material_print_temperature_layer_0": { "value": "material_print_temperature + 5" },
"material_diameter": { "default_value": 1.75 },
"travel_avoid_distance": { "default_value": 1, "value": "1" },
"speed_print" : { "default_value": 70 },
"speed_travel": { "value": "150.0" },
@ -55,6 +55,7 @@
"support_use_towers" : { "default_value": false },
"jerk_wall_0" : { "value": "30" },
"jerk_travel" : { "default_value": 20 },
"acceleration_travel" : { "value": 10000 }
"acceleration_travel" : { "value": 10000 },
"machine_max_feedrate_z" : { "default_value": 150 }
}
}

View file

@ -9,7 +9,6 @@
"category": "Other",
"file_formats": "text/x-gcode",
"platform": "fabtotum_platform.stl",
"icon": "fabtotum_platform.png",
"has_machine_quality": true,
"has_variants": true,
"variants_name": "Head",

View file

@ -4619,7 +4619,7 @@
"enabled": "resolveOrValue('adhesion_type') == 'brim' and support_enable",
"settable_per_mesh": false,
"settable_per_extruder": true,
"limit_to_extruder": "adhesion_extruder_nr"
"limit_to_extruder": "support_infill_extruder_nr"
},
"brim_outside_only":
{
@ -6598,9 +6598,8 @@
"unit": "%",
"type": "float",
"default_value": 100,
"minimum_value": "10",
"minimum_value": "0.001",
"minimum_value_warning": "25",
"maximum_value": "100",
"settable_per_mesh": true
},
"bridge_settings_enabled":

View file

@ -7,7 +7,6 @@
"author": "Simon Cor",
"manufacturer": "German RepRap",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png",
"platform": "grr_neo_platform.stl",
"machine_extruder_trains":
{

View file

@ -7,7 +7,6 @@
"author": "Claudio Sampaio (Patola)",
"manufacturer": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "kossel_platform.stl",
"platform_offset": [0, -0.25, 0],
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "Chris Petersen",
"manufacturer": "OpenBeam",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "kossel_pro_build_platform.stl",
"platform_offset": [0, -0.25, 0],
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "makeR",
"manufacturer": "makeR",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "makeR_pegasus_platform.stl",
"platform_offset": [-200, -10, 200],
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "makeR",
"manufacturer": "makeR",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "makeR_prusa_tairona_i3_platform.stl",
"platform_offset": [-2, 0, 0],
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "tvlgiao",
"manufacturer": "3DMaker",
"file_formats": "text/x-gcode;application/x-stl-ascii;application/x-stl-binary;application/x-wavefront-obj",
"icon": "icon_ultimaker2.png",
"platform": "makerstarter_platform.stl",
"preferred_quality_type": "draft",
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "Quillford",
"manufacturer": "Prusajr",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"machine_extruder_trains":
{

View file

@ -7,7 +7,6 @@
"author": "Apsu, Nounours2099",
"manufacturer": "Prusa Research",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"has_materials": true,
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "guigashm",
"manufacturer": "Prusajr",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"platform": "prusai3_xl_platform.stl",
"machine_extruder_trains":
{

View file

@ -7,7 +7,6 @@
"author": "PouncingIguana, JJ",
"manufacturer": "SeeMeCNC",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "artemis_platform.stl",
"has_materials": true,
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "PouncingIguana, JJ",
"manufacturer": "SeeMeCNC",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "rostock_platform.stl",
"has_materials": true,
"machine_extruder_trains":

View file

@ -7,7 +7,6 @@
"author": "TheTobby",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"has_materials": false,
"has_machine_quality": true,
"platform": "tevo_blackwidow.stl",

View file

@ -8,7 +8,6 @@
"author": "TheAssassin",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "prusai3_platform.stl",
"machine_extruder_trains":
{

View file

@ -7,7 +7,6 @@
"author": "nean",
"manufacturer": "Tevo",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"has_materials": true,
"machine_extruder_trains": {
"0": "tevo_tornado_extruder_0"

View file

@ -14,6 +14,8 @@
"preferred_material": "tizyx_pla",
"has_machine_quality": true,
"has_materials": true,
"has_variants": true,
"preferred_variant_name": "0.4 mm",
"machine_extruder_trains":
{
"0": "tizyx_k25_extruder_0"

View file

@ -9,7 +9,6 @@
"manufacturer": "uBuild-3D",
"category": "Other",
"file_formats": "text/x-gcode",
"icon": "icon_uBuild-3D",
"platform": "mr_bot_280_platform.stl",
"has_materials": true,
"preferred_quality_type": "draft",

View file

@ -8,7 +8,6 @@
"manufacturer": "Ultimaker B.V.",
"weight": 3,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2backplate.png",
"platform_offset": [9, 0, 0],

View file

@ -8,7 +8,6 @@
"quality_definition": "ultimaker2",
"weight": 3,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "Ultimaker2Extendedbackplate.png",
"machine_extruder_trains":

View file

@ -8,7 +8,6 @@
"quality_definition": "ultimaker2",
"weight": 3,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"platform": "ultimaker2go_platform.obj",
"platform_texture": "Ultimaker2Gobackplate.png",
"platform_offset": [0, 0, 0],

View file

@ -8,7 +8,6 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png",
"platform": "ultimaker_platform.stl",
"has_materials": true,
"has_machine_quality": true,

View file

@ -8,7 +8,6 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png",
"platform": "ultimaker_platform.stl",
"has_materials": true,
"has_machine_quality": true,

View file

@ -7,7 +7,6 @@
"manufacturer": "Ultimaker B.V.",
"weight": 4,
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png",
"platform": "ultimaker2_platform.obj",
"platform_texture": "UltimakerPlusbackplate.png",
"quality_definition": "ultimaker_original",

View file

@ -6,7 +6,6 @@
"author": "Unimatech",
"manufacturer": "Unimatech",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2.png",
"machine_extruder_trains":
{
"0": "uniqbot_one_extruder_0"

View file

@ -6,7 +6,6 @@
"visible": true,
"manufacturer": "Velleman",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "Vertex_build_panel.stl",
"platform_offset": [0, -3, 0],
"supports_usb_connection": true,

View file

@ -6,7 +6,6 @@
"visible": true,
"manufacturer": "Velleman",
"file_formats": "text/x-gcode",
"icon": "icon_ultimaker2",
"platform": "Vertex_build_panel.stl",
"platform_offset": [0, -3, 0],
"machine_extruder_trains": {

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_225_145_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_300_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_300_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_110_110_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -7,7 +7,6 @@
"author": "Ricardo Snoek",
"manufacturer": "Wanhao",
"file_formats": "text/x-gcode",
"icon": "wanhao-icon.png",
"has_materials": true,
"platform": "wanhao_200_200_platform.obj",
"platform_texture": "Wanhaobackplate.png",

View file

@ -0,0 +1,16 @@
{
"id": "anycubic_4max_extruder_0",
"version": 2,
"name": "Extruder 1",
"inherits": "fdmextruder",
"metadata": {
"machine": "anycubic_4max",
"position": "0"
},
"overrides": {
"extruder_nr": { "default_value": 0 },
"machine_nozzle_size": { "default_value": 0.4 },
"material_diameter": { "default_value": 1.75 }
}
}

0
resources/extruders/deltacomb_extruder_0.def.json Normal file → Executable file
View file

File diff suppressed because it is too large Load diff

Some files were not shown because too many files have changed in this diff Show more