Merge remote-tracking branch 'origin/master' into shader_optimization

This commit is contained in:
Lipu Fei 2019-05-16 14:22:05 +02:00
commit dab0a43214
304 changed files with 53275 additions and 18828 deletions

View file

@ -29,6 +29,7 @@ i18n_catalog = i18nCatalog("cura")
class Account(QObject): class Account(QObject):
# Signal emitted when user logged in or out. # Signal emitted when user logged in or out.
loginStateChanged = pyqtSignal(bool) loginStateChanged = pyqtSignal(bool)
accessTokenChanged = pyqtSignal()
def __init__(self, application: "CuraApplication", parent = None) -> None: def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -59,8 +60,12 @@ class Account(QObject):
self._authorization_service.initialize(self._application.getPreferences()) self._authorization_service.initialize(self._application.getPreferences())
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged) self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged) self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
self._authorization_service.loadAuthDataFromPreferences() self._authorization_service.loadAuthDataFromPreferences()
def _onAccessTokenChanged(self):
self.accessTokenChanged.emit()
## Returns a boolean indicating whether the given authentication is applied against staging or not. ## Returns a boolean indicating whether the given authentication is applied against staging or not.
@property @property
def is_staging(self) -> bool: def is_staging(self) -> bool:
@ -105,7 +110,7 @@ class Account(QObject):
return None return None
return user_profile.profile_image_url return user_profile.profile_image_url
@pyqtProperty(str, notify=loginStateChanged) @pyqtProperty(str, notify=accessTokenChanged)
def accessToken(self) -> Optional[str]: def accessToken(self) -> Optional[str]:
return self._authorization_service.getAccessToken() return self._authorization_service.getAccessToken()

139
cura/API/Machines.py Normal file
View file

@ -0,0 +1,139 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional, Dict, List, TYPE_CHECKING, Any
from PyQt5.QtCore import QObject, pyqtSignal, pyqtSlot, pyqtProperty
from UM.i18n import i18nCatalog
from UM.Logger import Logger
if TYPE_CHECKING:
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
i18n_catalog = i18nCatalog("cura")
## The account API provides a version-proof bridge to use Ultimaker Accounts
#
# Usage:
# ```
# from cura.API import CuraAPI
# api = CuraAPI()
# api.machines.addOutputDeviceToCurrentMachine()
# ```
## Since Cura doesn't have a machine class, we're going to make a fake one to make our lives a
# little bit easier.
class Machine():
def __init__(self) -> None:
self.hostname = "" # type: str
self.group_id = "" # type: str
self.group_name = "" # type: str
self.um_network_key = "" # type: str
self.configuration = {} # type: Dict[str, Any]
self.connection_types = [] # type: List[int]
class Machines(QObject):
def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
self._application = application
@pyqtSlot(result="QVariantMap")
def getCurrentMachine(self) -> Machine:
fake_machine = Machine() # type: Machine
global_stack = self._application.getGlobalContainerStack()
if global_stack:
metadata = global_stack.getMetaData()
if "group_id" in metadata:
fake_machine.group_id = global_stack.getMetaDataEntry("group_id")
if "group_name" in metadata:
fake_machine.group_name = global_stack.getMetaDataEntry("group_name")
if "um_network_key" in metadata:
fake_machine.um_network_key = global_stack.getMetaDataEntry("um_network_key")
fake_machine.connection_types = global_stack.configuredConnectionTypes
return fake_machine
## Set the current machine's friendy name.
# This is the same as "group name" since we use "group" and "current machine" interchangeably.
# TODO: Maybe make this "friendly name" to distinguish from "hostname"?
@pyqtSlot(str)
def setCurrentMachineGroupName(self, group_name: str) -> None:
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
global_stack = self._application.getGlobalContainerStack()
if global_stack:
# Update a GlobalStacks in the same group with the new group name.
group_id = global_stack.getMetaDataEntry("group_id")
machine_manager = self._application.getMachineManager()
for machine in machine_manager.getMachinesInGroup(group_id):
machine.setMetaDataEntry("group_name", group_name)
# Set the default value for "hidden", which is used when you have a group with multiple types of printers
global_stack.setMetaDataEntry("hidden", False)
## Set the current machine's configuration from an (optional) output device.
# If no output device is given, the first one available on the machine will be used.
# NOTE: Group and machine are used interchangeably.
# NOTE: This doesn't seem to be used anywhere. Maybe delete?
@pyqtSlot(QObject)
def updateCurrentMachineConfiguration(self, output_device: Optional["PrinterOutputDevice"]) -> None:
if output_device is None:
machine_manager = self._application.getMachineManager()
output_device = machine_manager.printerOutputDevices[0]
hotend_ids = output_device.hotendIds
for index in range(len(hotend_ids)):
output_device.hotendIdChanged.emit(index, hotend_ids[index])
material_ids = output_device.materialIds
for index in range(len(material_ids)):
output_device.materialIdChanged.emit(index, material_ids[index])
## Add an output device to the current machine.
# In practice, this means:
# - Setting the output device's network key in the current machine's metadata
# - Adding the output device's connection type to the current machine's configured connection
# types.
# TODO: CHANGE TO HOSTNAME
@pyqtSlot(QObject)
def addOutputDeviceToCurrentMachine(self, output_device: "PrinterOutputDevice") -> None:
if not output_device:
return
Logger.log("d",
"Attempting to set the network key of the active machine to %s",
output_device.key)
global_stack = self._application.getGlobalContainerStack()
if not global_stack:
return
metadata = global_stack.getMetaData()
# Global stack already had a connection, but it's changed.
if "um_network_key" in metadata:
old_network_key = metadata["um_network_key"]
# Since we might have a bunch of hidden stacks, we also need to change it there.
metadata_filter = {"um_network_key": old_network_key}
containers = self._application.getContainerRegistry().findContainerStacks(
type = "machine", **metadata_filter)
for container in containers:
container.setMetaDataEntry("um_network_key", output_device.key)
# Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s",
global_stack.getMetaDataEntry("network_authentication_id", None),
output_device.key)
container.removeMetaDataEntry("network_authentication_id")
container.removeMetaDataEntry("network_authentication_key")
# Ensure that these containers do know that they are configured for the given
# connection type (can be more than one type; e.g. LAN & Cloud)
container.addConfiguredConnectionType(output_device.connectionType.value)
else: # Global stack didn't have a connection yet, configure it.
global_stack.setMetaDataEntry("um_network_key", output_device.key)
global_stack.addConfiguredConnectionType(output_device.connectionType.value)
return None

View file

@ -6,6 +6,7 @@ from PyQt5.QtCore import QObject, pyqtProperty
from cura.API.Backups import Backups from cura.API.Backups import Backups
from cura.API.Interface import Interface from cura.API.Interface import Interface
from cura.API.Machines import Machines
from cura.API.Account import Account from cura.API.Account import Account
if TYPE_CHECKING: if TYPE_CHECKING:
@ -44,6 +45,9 @@ class CuraAPI(QObject):
# Backups API # Backups API
self._backups = Backups(self._application) self._backups = Backups(self._application)
# Machines API
self._machines = Machines(self._application)
# Interface API # Interface API
self._interface = Interface(self._application) self._interface = Interface(self._application)
@ -58,6 +62,10 @@ class CuraAPI(QObject):
def backups(self) -> "Backups": def backups(self) -> "Backups":
return self._backups return self._backups
@pyqtProperty(QObject)
def machines(self) -> "Machines":
return self._machines
@property @property
def interface(self) -> "Interface": def interface(self) -> "Interface":
return self._interface return self._interface

View file

@ -19,6 +19,7 @@ class AutoSave:
self._change_timer.setInterval(self._application.getPreferences().getValue("cura/autosave_delay")) self._change_timer.setInterval(self._application.getPreferences().getValue("cura/autosave_delay"))
self._change_timer.setSingleShot(True) self._change_timer.setSingleShot(True)
self._enabled = True
self._saving = False self._saving = False
def initialize(self): def initialize(self):
@ -32,6 +33,13 @@ class AutoSave:
if not self._saving: if not self._saving:
self._change_timer.start() self._change_timer.start()
def setEnabled(self, enabled: bool) -> None:
self._enabled = enabled
if self._enabled:
self._change_timer.start()
else:
self._change_timer.stop()
def _onGlobalStackChanged(self): def _onGlobalStackChanged(self):
if self._global_stack: if self._global_stack:
self._global_stack.propertyChanged.disconnect(self._triggerTimer) self._global_stack.propertyChanged.disconnect(self._triggerTimer)

View file

@ -51,8 +51,8 @@ class BackupsManager:
## Here we try to disable the auto-save plug-in as it might interfere with ## Here we try to disable the auto-save plug-in as it might interfere with
# restoring a back-up. # restoring a back-up.
def _disableAutoSave(self) -> None: def _disableAutoSave(self) -> None:
self._application.setSaveDataEnabled(False) self._application.getAutoSave().setEnabled(False)
## Re-enable auto-save after we're done. ## Re-enable auto-save after we're done.
def _enableAutoSave(self) -> None: def _enableAutoSave(self) -> None:
self._application.setSaveDataEnabled(True) self._application.getAutoSave().setEnabled(True)

View file

@ -23,6 +23,7 @@ from UM.Platform import Platform
from UM.PluginError import PluginNotFoundError from UM.PluginError import PluginNotFoundError
from UM.Resources import Resources from UM.Resources import Resources
from UM.Preferences import Preferences from UM.Preferences import Preferences
from UM.Qt.Bindings import MainWindow
from UM.Qt.QtApplication import QtApplication # The class we're inheriting from. from UM.Qt.QtApplication import QtApplication # The class we're inheriting from.
import UM.Util import UM.Util
from UM.View.SelectionPass import SelectionPass # For typing. from UM.View.SelectionPass import SelectionPass # For typing.
@ -116,6 +117,8 @@ from cura.UI.AddPrinterPagesModel import AddPrinterPagesModel
from cura.UI.WelcomePagesModel import WelcomePagesModel from cura.UI.WelcomePagesModel import WelcomePagesModel
from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel from cura.UI.WhatsNewPagesModel import WhatsNewPagesModel
from cura.Utils.NetworkingUtil import NetworkingUtil
from .SingleInstance import SingleInstance from .SingleInstance import SingleInstance
from .AutoSave import AutoSave from .AutoSave import AutoSave
from . import PlatformPhysics from . import PlatformPhysics
@ -216,7 +219,7 @@ class CuraApplication(QtApplication):
self._machine_settings_manager = MachineSettingsManager(self, parent = self) self._machine_settings_manager = MachineSettingsManager(self, parent = self)
self._discovered_printer_model = DiscoveredPrintersModel(parent = self) self._discovered_printer_model = DiscoveredPrintersModel(self, parent = self)
self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self) self._first_start_machine_actions_model = FirstStartMachineActionsModel(self, parent = self)
self._welcome_pages_model = WelcomePagesModel(self, parent = self) self._welcome_pages_model = WelcomePagesModel(self, parent = self)
self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self) self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self)
@ -258,7 +261,6 @@ class CuraApplication(QtApplication):
# Backups # Backups
self._auto_save = None self._auto_save = None
self._save_data_enabled = True
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
self._container_registry_class = CuraContainerRegistry self._container_registry_class = CuraContainerRegistry
@ -523,6 +525,10 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/use_multi_build_plate", False) preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("view/settings_list_height", 400) preferences.addPreference("view/settings_list_height", 400)
preferences.addPreference("view/settings_visible", False) preferences.addPreference("view/settings_visible", False)
preferences.addPreference("view/settings_xpos", 0)
preferences.addPreference("view/settings_ypos", 56)
preferences.addPreference("view/colorscheme_xpos", 0)
preferences.addPreference("view/colorscheme_ypos", 56)
preferences.addPreference("cura/currency", "") preferences.addPreference("cura/currency", "")
preferences.addPreference("cura/material_settings", "{}") preferences.addPreference("cura/material_settings", "{}")
@ -669,12 +675,9 @@ class CuraApplication(QtApplication):
self._message_box_callback = None self._message_box_callback = None
self._message_box_callback_arguments = [] self._message_box_callback_arguments = []
def setSaveDataEnabled(self, enabled: bool) -> None:
self._save_data_enabled = enabled
# Cura has multiple locations where instance containers need to be saved, so we need to handle this differently. # Cura has multiple locations where instance containers need to be saved, so we need to handle this differently.
def saveSettings(self): def saveSettings(self):
if not self.started or not self._save_data_enabled: if not self.started:
# Do not do saving during application start or when data should not be saved on quit. # Do not do saving during application start or when data should not be saved on quit.
return return
ContainerRegistry.getInstance().saveDirtyContainers() ContainerRegistry.getInstance().saveDirtyContainers()
@ -1027,6 +1030,8 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager) qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager) qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel") qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel") qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel") qmlRegisterType(AddPrinterPagesModel, "Cura", 1, 0, "AddPrinterPagesModel")
@ -1786,3 +1791,19 @@ class CuraApplication(QtApplication):
# Only show the what's new dialog if there's no machine and we have just upgraded # Only show the what's new dialog if there's no machine and we have just upgraded
show_whatsnew_only = has_active_machine and has_app_just_upgraded show_whatsnew_only = has_active_machine and has_app_just_upgraded
return show_whatsnew_only return show_whatsnew_only
@pyqtSlot(result = int)
def appWidth(self) -> int:
main_window = QtApplication.getInstance().getMainWindow()
if main_window:
return main_window.width()
else:
return 0
@pyqtSlot(result = int)
def appHeight(self) -> int:
main_window = QtApplication.getInstance().getMainWindow()
if main_window:
return main_window.height()
else:
return 0

View file

@ -3,8 +3,11 @@
from PyQt5.QtCore import pyqtProperty, QUrl from PyQt5.QtCore import pyqtProperty, QUrl
from UM.Resources import Resources
from UM.View.View import View from UM.View.View import View
from cura.CuraApplication import CuraApplication
# Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure # Since Cura has a few pre-defined "space claims" for the locations of certain components, we've provided some structure
# to indicate this. # to indicate this.
@ -12,13 +15,20 @@ from UM.View.View import View
# the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage # the stageMenuComponent returns an item that should be used somehwere in the stage menu. It's up to the active stage
# to actually do something with this. # to actually do something with this.
class CuraView(View): class CuraView(View):
def __init__(self, parent = None) -> None: def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None:
super().__init__(parent) super().__init__(parent)
self._empty_menu_placeholder_url = QUrl(Resources.getPath(CuraApplication.ResourceTypes.QmlFiles,
"EmptyViewMenuComponent.qml"))
self._use_empty_menu_placeholder = use_empty_menu_placeholder
@pyqtProperty(QUrl, constant = True) @pyqtProperty(QUrl, constant = True)
def mainComponent(self) -> QUrl: def mainComponent(self) -> QUrl:
return self.getDisplayComponent("main") return self.getDisplayComponent("main")
@pyqtProperty(QUrl, constant = True) @pyqtProperty(QUrl, constant = True)
def stageMenuComponent(self) -> QUrl: def stageMenuComponent(self) -> QUrl:
return self.getDisplayComponent("menu") url = self.getDisplayComponent("menu")
if not url.toString() and self._use_empty_menu_placeholder:
url = self._empty_menu_placeholder_url
return url

View file

@ -20,7 +20,7 @@ class LayerPolygon:
MoveCombingType = 8 MoveCombingType = 8
MoveRetractionType = 9 MoveRetractionType = 9
SupportInterfaceType = 10 SupportInterfaceType = 10
PrimeTower = 11 PrimeTowerType = 11
__number_of_types = 12 __number_of_types = 12
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType) __jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
@ -245,7 +245,7 @@ class LayerPolygon:
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
theme.getColor("layerview_prime_tower").getRgbF() theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
]) ])
return cls.__color_map return cls.__color_map

View file

@ -2,8 +2,9 @@
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import os import os
from typing import Optional
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal from PyQt5.QtCore import QObject, QUrl, pyqtSlot, pyqtProperty, pyqtSignal
from UM.Logger import Logger from UM.Logger import Logger
from UM.PluginObject import PluginObject from UM.PluginObject import PluginObject
@ -72,18 +73,26 @@ class MachineAction(QObject, PluginObject):
return self._finished return self._finished
## Protected helper to create a view object based on provided QML. ## Protected helper to create a view object based on provided QML.
def _createViewFromQML(self) -> None: def _createViewFromQML(self) -> Optional["QObject"]:
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId()) plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None: if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId()) Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
return return None
path = os.path.join(plugin_path, self._qml_url) path = os.path.join(plugin_path, self._qml_url)
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
self._view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
return view
@pyqtProperty(QObject, constant = True) @pyqtProperty(QUrl, constant = True)
def displayItem(self): def qmlPath(self) -> "QUrl":
if not self._view: plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
self._createViewFromQML() if plugin_path is None:
return self._view Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
return QUrl("")
path = os.path.join(plugin_path, self._qml_url)
return QUrl.fromLocalFile(path)
@pyqtSlot(result = QObject)
def getDisplayItem(self) -> Optional["QObject"]:
return self._createViewFromQML()

View file

@ -103,6 +103,8 @@ class MaterialManager(QObject):
continue continue
root_material_id = material_metadata.get("base_file", "") root_material_id = material_metadata.get("base_file", "")
if root_material_id not in material_metadatas: #Not a registered material profile. Don't store this in the look-up tables.
continue
if root_material_id not in self._material_group_map: if root_material_id not in self._material_group_map:
self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id])) self._material_group_map[root_material_id] = MaterialGroup(root_material_id, MaterialNode(material_metadatas[root_material_id]))
self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id) self._material_group_map[root_material_id].is_read_only = self._container_registry.isReadOnly(root_material_id)

View file

@ -3,15 +3,17 @@
from typing import Callable, Dict, List, Optional, TYPE_CHECKING from typing import Callable, Dict, List, Optional, TYPE_CHECKING
from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject from PyQt5.QtCore import pyqtSlot, pyqtProperty, pyqtSignal, QObject, QTimer
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Logger import Logger from UM.Logger import Logger
from UM.Util import parseBool from UM.Util import parseBool
from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
if TYPE_CHECKING: if TYPE_CHECKING:
from PyQt5.QtCore import QObject from PyQt5.QtCore import QObject
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
@ -45,6 +47,10 @@ class DiscoveredPrinter(QObject):
self._name = name self._name = name
self.nameChanged.emit() self.nameChanged.emit()
@pyqtProperty(str, constant = True)
def address(self) -> str:
return self._ip_address
machineTypeChanged = pyqtSignal() machineTypeChanged = pyqtSignal()
@pyqtProperty(str, notify = machineTypeChanged) @pyqtProperty(str, notify = machineTypeChanged)
@ -60,7 +66,14 @@ class DiscoveredPrinter(QObject):
@pyqtProperty(str, notify = machineTypeChanged) @pyqtProperty(str, notify = machineTypeChanged)
def readableMachineType(self) -> str: def readableMachineType(self) -> str:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
readable_type = CuraApplication.getInstance().getMachineManager().getMachineTypeNameFromId(self._machine_type) machine_manager = CuraApplication.getInstance().getMachineManager()
# In ClusterUM3OutputDevice, when it updates a printer information, it updates the machine type using the field
# "machine_variant", and for some reason, it's not the machine type ID/codename/... but a human-readable string
# like "Ultimaker 3". The code below handles this case.
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
readable_type = self._machine_type
else:
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
if not readable_type: if not readable_type:
readable_type = catalog.i18nc("@label", "Unknown") readable_type = catalog.i18nc("@label", "Unknown")
return readable_type return readable_type
@ -68,7 +81,11 @@ class DiscoveredPrinter(QObject):
@pyqtProperty(bool, notify = machineTypeChanged) @pyqtProperty(bool, notify = machineTypeChanged)
def isUnknownMachineType(self) -> bool: def isUnknownMachineType(self) -> bool:
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
readable_type = CuraApplication.getInstance().getMachineManager().getMachineTypeNameFromId(self._machine_type) machine_manager = CuraApplication.getInstance().getMachineManager()
if machine_manager.hasHumanReadableMachineTypeName(self._machine_type):
readable_type = self._machine_type
else:
readable_type = machine_manager.getMachineTypeNameFromId(self._machine_type)
return not readable_type return not readable_type
@pyqtProperty(QObject, constant = True) @pyqtProperty(QObject, constant = True)
@ -94,14 +111,88 @@ class DiscoveredPrinter(QObject):
# #
class DiscoveredPrintersModel(QObject): class DiscoveredPrintersModel(QObject):
def __init__(self, parent: Optional["QObject"] = None) -> None: def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent) super().__init__(parent)
self._application = application
self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter] self._discovered_printer_by_ip_dict = dict() # type: Dict[str, DiscoveredPrinter]
self._plugin_for_manual_device = None # type: Optional[OutputDevicePlugin]
self._manual_device_address = ""
self._manual_device_request_timeout_in_seconds = 5 # timeout for adding a manual device in seconds
self._manual_device_request_timer = QTimer()
self._manual_device_request_timer.setInterval(self._manual_device_request_timeout_in_seconds * 1000)
self._manual_device_request_timer.setSingleShot(True)
self._manual_device_request_timer.timeout.connect(self._onManualRequestTimeout)
discoveredPrintersChanged = pyqtSignal() discoveredPrintersChanged = pyqtSignal()
@pyqtProperty(list, notify = discoveredPrintersChanged) @pyqtSlot(str)
def checkManualDevice(self, address: str) -> None:
if self.hasManualDeviceRequestInProgress:
Logger.log("i", "A manual device request for address [%s] is still in progress, do nothing",
self._manual_device_address)
return
priority_order = [
ManualDeviceAdditionAttempt.PRIORITY,
ManualDeviceAdditionAttempt.POSSIBLE,
] # type: List[ManualDeviceAdditionAttempt]
all_plugins_dict = self._application.getOutputDeviceManager().getAllOutputDevicePlugins()
can_add_manual_plugins = [item for item in filter(
lambda plugin_item: plugin_item.canAddManualDevice(address) in priority_order,
all_plugins_dict.values())]
if not can_add_manual_plugins:
Logger.log("d", "Could not find a plugin to accept adding %s manually via address.", address)
return
plugin = max(can_add_manual_plugins, key = lambda p: priority_order.index(p.canAddManualDevice(address)))
self._plugin_for_manual_device = plugin
self._plugin_for_manual_device.addManualDevice(address, callback = self._onManualDeviceRequestFinished)
self._manual_device_address = address
self._manual_device_request_timer.start()
self.hasManualDeviceRequestInProgressChanged.emit()
@pyqtSlot()
def cancelCurrentManualDeviceRequest(self) -> None:
self._manual_device_request_timer.stop()
if self._manual_device_address:
if self._plugin_for_manual_device is not None:
self._plugin_for_manual_device.removeManualDevice(self._manual_device_address, address = self._manual_device_address)
self._manual_device_address = ""
self._plugin_for_manual_device = None
self.hasManualDeviceRequestInProgressChanged.emit()
self.manualDeviceRequestFinished.emit(False)
def _onManualRequestTimeout(self) -> None:
Logger.log("w", "Manual printer [%s] request timed out. Cancel the current request.", self._manual_device_address)
self.cancelCurrentManualDeviceRequest()
hasManualDeviceRequestInProgressChanged = pyqtSignal()
@pyqtProperty(bool, notify = hasManualDeviceRequestInProgressChanged)
def hasManualDeviceRequestInProgress(self) -> bool:
return self._manual_device_address != ""
manualDeviceRequestFinished = pyqtSignal(bool, arguments = ["success"])
def _onManualDeviceRequestFinished(self, success: bool, address: str) -> None:
self._manual_device_request_timer.stop()
if address == self._manual_device_address:
self._manual_device_address = ""
self.hasManualDeviceRequestInProgressChanged.emit()
self.manualDeviceRequestFinished.emit(success)
@pyqtProperty("QVariantMap", notify = discoveredPrintersChanged)
def discoveredPrintersByAddress(self) -> Dict[str, DiscoveredPrinter]:
return self._discovered_printer_by_ip_dict
@pyqtProperty("QVariantList", notify = discoveredPrintersChanged)
def discoveredPrinters(self) -> List["DiscoveredPrinter"]: def discoveredPrinters(self) -> List["DiscoveredPrinter"]:
item_list = list( item_list = list(
x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary"))) x for x in self._discovered_printer_by_ip_dict.values() if not parseBool(x.device.getProperty("temporary")))
@ -157,11 +248,3 @@ class DiscoveredPrintersModel(QObject):
@pyqtSlot("QVariant") @pyqtSlot("QVariant")
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None: def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
discovered_printer.create_callback(discovered_printer.getKey()) discovered_printer.create_callback(discovered_printer.getKey())
@pyqtSlot(str)
def createMachineFromDiscoveredPrinterAddress(self, ip_address: str) -> None:
if ip_address not in self._discovered_printer_by_ip_dict:
Logger.log("i", "Key [%s] does not exist in the discovered printers list.", ip_address)
return
self.createMachineFromDiscoveredPrinter(self._discovered_printer_by_ip_dict[ip_address])

View file

@ -35,6 +35,8 @@ class FirstStartMachineActionsModel(ListModel):
self._application = application self._application = application
self._application.initializationFinished.connect(self._initialize) self._application.initializationFinished.connect(self._initialize)
self._previous_global_stack = None
def _initialize(self) -> None: def _initialize(self) -> None:
self._application.getMachineManager().globalContainerChanged.connect(self._update) self._application.getMachineManager().globalContainerChanged.connect(self._update)
self._update() self._update()
@ -86,13 +88,19 @@ class FirstStartMachineActionsModel(ListModel):
self.setItems([]) self.setItems([])
return return
# Do not update if the machine has not been switched. This can cause the SettingProviders on the Machine
# Setting page to do a force update, but they can use potential outdated cached values.
if self._previous_global_stack is not None and global_stack.getId() == self._previous_global_stack.getId():
return
self._previous_global_stack = global_stack
definition_id = global_stack.definition.getId() definition_id = global_stack.definition.getId()
first_start_actions = self._application.getMachineActionManager().getFirstStartActions(definition_id) first_start_actions = self._application.getMachineActionManager().getFirstStartActions(definition_id)
item_list = [] item_list = []
for item in first_start_actions: for item in first_start_actions:
item_list.append({"title": item.label, item_list.append({"title": item.label,
"content": item.displayItem, "content": item.getDisplayItem(),
"action": item, "action": item,
}) })
item.reset() item.reset()

View file

@ -5,6 +5,7 @@ from PyQt5.QtCore import Qt, QTimer
from UM.Qt.ListModel import ListModel from UM.Qt.ListModel import ListModel
from UM.i18n import i18nCatalog from UM.i18n import i18nCatalog
from UM.Util import parseBool
from cura.PrinterOutput.PrinterOutputDevice import ConnectionType from cura.PrinterOutput.PrinterOutputDevice import ConnectionType
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
@ -54,7 +55,6 @@ class GlobalStacksModel(ListModel):
items = [] items = []
container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine") container_stacks = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine")
for container_stack in container_stacks: for container_stack in container_stacks:
has_remote_connection = False has_remote_connection = False
@ -62,7 +62,7 @@ class GlobalStacksModel(ListModel):
has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value, has_remote_connection |= connection_type in [ConnectionType.NetworkConnection.value,
ConnectionType.CloudConnection.value] ConnectionType.CloudConnection.value]
if container_stack.getMetaDataEntry("hidden", False) in ["True", True]: if parseBool(container_stack.getMetaDataEntry("hidden", False)):
continue continue
section_name = "Network enabled printers" if has_remote_connection else "Local printers" section_name = "Network enabled printers" if has_remote_connection else "Local printers"
@ -73,5 +73,5 @@ class GlobalStacksModel(ListModel):
"hasRemoteConnection": has_remote_connection, "hasRemoteConnection": has_remote_connection,
"metadata": container_stack.getMetaData().copy(), "metadata": container_stack.getMetaData().copy(),
"discoverySource": section_name}) "discoverySource": section_name})
items.sort(key = lambda i: not i["hasRemoteConnection"]) items.sort(key = lambda i: (not i["hasRemoteConnection"], i["name"]))
self.setItems(items) self.setItems(items)

View file

@ -34,6 +34,8 @@ class AuthorizationService:
# Emit signal when authentication failed. # Emit signal when authentication failed.
onAuthenticationError = Signal() onAuthenticationError = Signal()
accessTokenChanged = Signal()
def __init__(self, settings: "OAuth2Settings", preferences: Optional["Preferences"] = None) -> None: def __init__(self, settings: "OAuth2Settings", preferences: Optional["Preferences"] = None) -> None:
self._settings = settings self._settings = settings
self._auth_helpers = AuthorizationHelpers(settings) self._auth_helpers = AuthorizationHelpers(settings)
@ -99,7 +101,9 @@ class AuthorizationService:
Logger.log("w", "Unable to use the refresh token to get a new access token.") Logger.log("w", "Unable to use the refresh token to get a new access token.")
# The token could not be refreshed using the refresh token. We should login again. # The token could not be refreshed using the refresh token. We should login again.
return None return None
# Ensure it gets stored as otherwise we only have it in memory. The stored refresh token has been deleted
# from the server already.
self._storeAuthData(self._auth_data)
return self._auth_helpers.parseJWT(self._auth_data.access_token) return self._auth_helpers.parseJWT(self._auth_data.access_token)
## Get the access token as provided by the repsonse data. ## Get the access token as provided by the repsonse data.
@ -128,6 +132,7 @@ class AuthorizationService:
self._storeAuthData(response) self._storeAuthData(response)
self.onAuthStateChanged.emit(logged_in = True) self.onAuthStateChanged.emit(logged_in = True)
else: else:
Logger.log("w", "Failed to get a new access token from the server.")
self.onAuthStateChanged.emit(logged_in = False) self.onAuthStateChanged.emit(logged_in = False)
## Delete the authentication data that we have stored locally (eg; logout) ## Delete the authentication data that we have stored locally (eg; logout)
@ -198,6 +203,7 @@ class AuthorizationService:
## Store authentication data in preferences. ## Store authentication data in preferences.
def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None: def _storeAuthData(self, auth_data: Optional[AuthenticationResponse] = None) -> None:
Logger.log("d", "Attempting to store the auth data")
if self._preferences is None: if self._preferences is None:
Logger.log("e", "Unable to save authentication data, since no preference has been set!") Logger.log("e", "Unable to save authentication data, since no preference has been set!")
return return
@ -210,6 +216,8 @@ class AuthorizationService:
self._user_profile = None self._user_profile = None
self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY) self._preferences.resetPreference(self._settings.AUTH_DATA_PREFERENCE_KEY)
self.accessTokenChanged.emit()
def _onMessageActionTriggered(self, _, action): def _onMessageActionTriggered(self, _, action):
if action == "retry": if action == "retry":
self.loadAuthDataFromPreferences() self.loadAuthDataFromPreferences()

View file

@ -62,7 +62,24 @@ class ExtruderConfigurationModel(QObject):
return " ".join(message_chunks) return " ".join(message_chunks)
def __eq__(self, other) -> bool: def __eq__(self, other) -> bool:
return hash(self) == hash(other) if not isinstance(other, ExtruderConfigurationModel):
return False
if self._position != other.position:
return False
# Empty materials should be ignored for comparison
if self.activeMaterial is not None and other.activeMaterial is not None:
if self.activeMaterial.guid != other.activeMaterial.guid:
if self.activeMaterial.guid != "" and other.activeMaterial.guid != "":
return False
else:
# At this point there is no material, so it doesn't matter what the hotend is.
return True
if self.hotendID != other.hotendID:
return False
return True
# Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is # Calculating a hash function using the position of the extruder, the material GUID and the hotend id to check if is
# unique within a set # unique within a set

View file

@ -71,7 +71,23 @@ class PrinterConfigurationModel(QObject):
return "\n".join(message_chunks) return "\n".join(message_chunks)
def __eq__(self, other): def __eq__(self, other):
return hash(self) == hash(other) if not isinstance(other, PrinterConfigurationModel):
return False
if self.printerType != other.printerType:
return False
if self.buildplateConfiguration != other.buildplateConfiguration:
return False
if len(self.extruderConfigurations) != len(other.extruderConfigurations):
return False
for self_extruder, other_extruder in zip(sorted(self._extruder_configurations, key=lambda x: x.position), sorted(other.extruderConfigurations, key=lambda x: x.position)):
if self_extruder != other_extruder:
return False
return True
## The hash function is used to compare and create unique sets. The configuration is unique if the configuration ## The hash function is used to compare and create unique sets. The configuration is unique if the configuration
# of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same. # of the extruders is unique (the order of the extruders matters), and the type and buildplate is the same.

View file

@ -0,0 +1,4 @@
import warnings
warnings.warn("Importing cura.PrinterOutput.PrintJobOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrintJobOutputModel inststad", DeprecationWarning, stacklevel=2)
# We moved the the models to one submodule deeper
from cura.PrinterOutput.Models.PrintJobOutputModel import PrintJobOutputModel

View file

@ -0,0 +1,4 @@
import warnings
warnings.warn("Importing cura.PrinterOutput.PrinterOutputModel has been deprecated since 4.1, use cura.PrinterOutput.Models.PrinterOutputModel inststad", DeprecationWarning, stacklevel=2)
# We moved the the models to one submodule deeper
from cura.PrinterOutput.Models.PrinterOutputModel import PrinterOutputModel

View file

@ -0,0 +1,4 @@
import warnings
warnings.warn("Importing cura.PrinterOutputDevice has been deprecated since 4.1, use cura.PrinterOutput.PrinterOutputDevice inststad", DeprecationWarning, stacklevel=2)
# We moved the PrinterOutput device to it's own submodule.
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice, ConnectionState

View file

@ -116,11 +116,11 @@ class CuraSceneNode(SceneNode):
if self._mesh_data: if self._mesh_data:
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation()) self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
for child in self._children: for child in self.getAllChildren():
if child.callDecoration("isNonPrintingMesh"): if child.callDecoration("isNonPrintingMesh"):
# Non-printing-meshes inside a group should not affect push apart or drop to build plate # Non-printing-meshes inside a group should not affect push apart or drop to build plate
continue continue
if not child._mesh_data: if not child.getMeshData():
# Nodes without mesh data should not affect bounding boxes of their parents. # Nodes without mesh data should not affect bounding boxes of their parents.
continue continue
if self._aabb is None: if self._aabb is None:

View file

@ -4,6 +4,8 @@
from collections import defaultdict from collections import defaultdict
import threading import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
import uuid
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override from UM.Decorators import override
@ -34,6 +36,12 @@ class GlobalStack(CuraContainerStack):
self.setMetaDataEntry("type", "machine") # For backward compatibility self.setMetaDataEntry("type", "machine") # For backward compatibility
# TL;DR: If Cura is looking for printers that belong to the same group, it should use "group_id".
# Each GlobalStack by default belongs to a group which is identified via "group_id". This group_id is used to
# figure out which GlobalStacks are in the printer cluster for example without knowing the implementation
# details such as the um_network_key or some other identifier that's used by the underlying device plugin.
self.setMetaDataEntry("group_id", str(uuid.uuid4())) # Assign a new GlobalStack to a unique group by default
self._extruders = {} # type: Dict[str, "ExtruderStack"] self._extruders = {} # type: Dict[str, "ExtruderStack"]
# This property is used to track which settings we are calculating the "resolve" for # This property is used to track which settings we are calculating the "resolve" for
@ -68,6 +76,10 @@ class GlobalStack(CuraContainerStack):
def maxExtruderCount(self): def maxExtruderCount(self):
return len(self.getMetaDataEntry("machine_extruder_trains")) return len(self.getMetaDataEntry("machine_extruder_trains"))
@pyqtProperty(bool, notify=configuredConnectionTypesChanged)
def supportsNetworkConnection(self):
return self.getMetaDataEntry("supports_network_connection", False)
@classmethod @classmethod
def getLoadingPriority(cls) -> int: def getLoadingPriority(cls) -> int:
return 2 return 2

View file

@ -720,7 +720,7 @@ class MachineManager(QObject):
extruder_stack.userChanges.setProperty(key, "value", new_value) extruder_stack.userChanges.setProperty(key, "value", new_value)
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
@deprecated("use Cura.activeStack.variant.name instead", "4.1") @deprecated("use Cura.MachineManager.activeStack.variant.name instead", "4.1")
def activeVariantName(self) -> str: def activeVariantName(self) -> str:
if self._active_container_stack: if self._active_container_stack:
variant = self._active_container_stack.variant variant = self._active_container_stack.variant
@ -730,7 +730,7 @@ class MachineManager(QObject):
return "" return ""
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
@deprecated("use Cura.activeStack.variant.id instead", "4.1") @deprecated("use Cura.MachineManager.activeStack.variant.id instead", "4.1")
def activeVariantId(self) -> str: def activeVariantId(self) -> str:
if self._active_container_stack: if self._active_container_stack:
variant = self._active_container_stack.variant variant = self._active_container_stack.variant
@ -740,7 +740,7 @@ class MachineManager(QObject):
return "" return ""
@pyqtProperty(str, notify = activeVariantChanged) @pyqtProperty(str, notify = activeVariantChanged)
@deprecated("use Cura.activeMachine.variant.name instead", "4.1") @deprecated("use Cura.MachineManager.activeMachine.variant.name instead", "4.1")
def activeVariantBuildplateName(self) -> str: def activeVariantBuildplateName(self) -> str:
if self._global_container_stack: if self._global_container_stack:
variant = self._global_container_stack.variant variant = self._global_container_stack.variant
@ -750,7 +750,7 @@ class MachineManager(QObject):
return "" return ""
@pyqtProperty(str, notify = globalContainerChanged) @pyqtProperty(str, notify = globalContainerChanged)
@deprecated("use Cura.activeMachine.definition.id instead", "4.1") @deprecated("use Cura.MachineManager.activeMachine.definition.id instead", "4.1")
def activeDefinitionId(self) -> str: def activeDefinitionId(self) -> str:
if self._global_container_stack: if self._global_container_stack:
return self._global_container_stack.definition.id return self._global_container_stack.definition.id
@ -797,7 +797,6 @@ class MachineManager(QObject):
self.setActiveMachine(other_machine_stacks[0]["id"]) self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0] metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
network_key = metadata.get("um_network_key", None)
ExtruderManager.getInstance().removeMachineExtruders(machine_id) ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id) containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers: for container in containers:
@ -805,8 +804,9 @@ class MachineManager(QObject):
CuraContainerRegistry.getInstance().removeContainer(machine_id) CuraContainerRegistry.getInstance().removeContainer(machine_id)
# If the printer that is being removed is a network printer, the hidden printers have to be also removed # If the printer that is being removed is a network printer, the hidden printers have to be also removed
if network_key: group_id = metadata.get("group_id", None)
metadata_filter = {"um_network_key": network_key} if group_id:
metadata_filter = {"group_id": group_id}
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter) hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
if hidden_containers: if hidden_containers:
# This reuses the method and remove all printers recursively # This reuses the method and remove all printers recursively
@ -1263,8 +1263,8 @@ class MachineManager(QObject):
if self._global_container_stack is not None: if self._global_container_stack is not None:
if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)): if Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items(): for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"): if not extruder.isEnabled:
return False continue
if not extruder.material.getMetaDataEntry("compatible"): if not extruder.material.getMetaDataEntry("compatible"):
return False return False
return True return True
@ -1360,21 +1360,24 @@ class MachineManager(QObject):
# Get the definition id corresponding to this machine name # Get the definition id corresponding to this machine name
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId() machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# Try to find a machine with the same network key # Try to find a machine with the same network key
new_machine = self.getMachine(machine_definition_id, metadata_filter = {"um_network_key": self.activeMachineNetworkKey()}) metadata_filter = {"group_id": self._global_container_stack.getMetaDataEntry("group_id"),
"um_network_key": self.activeMachineNetworkKey(),
}
new_machine = self.getMachine(machine_definition_id, metadata_filter = metadata_filter)
# If there is no machine, then create a new one and set it to the non-hidden instance # If there is no machine, then create a new one and set it to the non-hidden instance
if not new_machine: if not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id) new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine: if not new_machine:
return return
new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id"))
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey()) new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName) new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False)
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type")) new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
else: else:
Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey()) Logger.log("i", "Found a %s with the key %s. Let's use it!", machine_name, self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("hidden", False)
# Set the current printer instance to hidden (the metadata entry must exist) # Set the current printer instance to hidden (the metadata entry must exist)
new_machine.setMetaDataEntry("hidden", False)
self._global_container_stack.setMetaDataEntry("hidden", True) self._global_container_stack.setMetaDataEntry("hidden", True)
self.setActiveMachine(new_machine.getId()) self.setActiveMachine(new_machine.getId())
@ -1640,6 +1643,13 @@ class MachineManager(QObject):
return abbr_machine return abbr_machine
# Checks if the given machine type name in the available machine list.
# The machine type is a code name such as "ultimaker_3", while the machine type name is the human-readable name of
# the machine type, which is "Ultimaker 3" for "ultimaker_3".
def hasHumanReadableMachineTypeName(self, machine_type_name: str) -> bool:
results = self._container_registry.findDefinitionContainersMetadata(name = machine_type_name)
return len(results) > 0
@pyqtSlot(str, result = str) @pyqtSlot(str, result = str)
def getMachineTypeNameFromId(self, machine_type_id: str) -> str: def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
machine_type_name = "" machine_type_name = ""
@ -1647,3 +1657,7 @@ class MachineManager(QObject):
if results: if results:
machine_type_name = results[0]["name"] machine_type_name = results[0]["name"]
return machine_type_name return machine_type_name
# Gets all machines that belong to the given group_id.
def getMachinesInGroup(self, group_id: str) -> List["GlobalStack"]:
return self._container_registry.findContainerStacks(type = "machine", group_id = group_id)

View file

@ -113,19 +113,15 @@ class SettingOverrideDecorator(SceneNodeDecorator):
def _onSettingChanged(self, setting_key, property_name): # Reminder: 'property' is a built-in function def _onSettingChanged(self, setting_key, property_name): # Reminder: 'property' is a built-in function
# We're only interested in a few settings and only if it's value changed. # We're only interested in a few settings and only if it's value changed.
if property_name == "value" and (setting_key in self._non_printing_mesh_settings or setting_key in self._non_thumbnail_visible_settings): if property_name == "value":
if setting_key in self._non_printing_mesh_settings or setting_key in self._non_thumbnail_visible_settings:
# Trigger slice/need slicing if the value has changed. # Trigger slice/need slicing if the value has changed.
new_is_non_printing_mesh = self._evaluateIsNonPrintingMesh() new_is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
new_is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh() self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
changed = False
if self._is_non_printing_mesh != new_is_non_printing_mesh: if self._is_non_printing_mesh != new_is_non_printing_mesh:
self._is_non_printing_mesh = new_is_non_printing_mesh self._is_non_printing_mesh = new_is_non_printing_mesh
self._node.setCalculateBoundingBox(not self._is_non_printing_mesh)
changed = True
if self._is_non_thumbnail_visible_mesh != new_is_non_thumbnail_visible_mesh:
changed = True
if changed:
Application.getInstance().getBackend().needsSlicing() Application.getInstance().getBackend().needsSlicing()
Application.getInstance().getBackend().tickle() Application.getInstance().getBackend().tickle()

View file

@ -1,9 +1,11 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
import copy import copy
from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# Empty definition changes # Empty definition changes
@ -28,7 +30,7 @@ empty_material_container.setMetaDataEntry("type", "material")
EMPTY_QUALITY_CONTAINER_ID = "empty_quality" EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
empty_quality_container = copy.deepcopy(empty_container) empty_quality_container = copy.deepcopy(empty_container)
empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID) empty_quality_container.setMetaDataEntry("id", EMPTY_QUALITY_CONTAINER_ID)
empty_quality_container.setName("Not Supported") empty_quality_container.setName(catalog.i18nc("@info:not supported profile", "Not supported"))
empty_quality_container.setMetaDataEntry("quality_type", "not_supported") empty_quality_container.setMetaDataEntry("quality_type", "not_supported")
empty_quality_container.setMetaDataEntry("type", "quality") empty_quality_container.setMetaDataEntry("type", "quality")
empty_quality_container.setMetaDataEntry("supported", False) empty_quality_container.setMetaDataEntry("supported", False)

View file

@ -15,6 +15,7 @@ class AddPrinterPagesModel(WelcomePagesModel):
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
"next_page_id": "machine_actions", "next_page_id": "machine_actions",
"next_page_button_text": self._catalog.i18nc("@action:button", "Add"), "next_page_button_text": self._catalog.i18nc("@action:button", "Add"),
"previous_page_button_text": self._catalog.i18nc("@action:button", "Cancel"),
}) })
self._pages.append({"id": "add_printer_by_ip", self._pages.append({"id": "add_printer_by_ip",
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"), "page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),

View file

@ -81,6 +81,7 @@ class PrintInformation(QObject):
"support_interface": catalog.i18nc("@tooltip", "Support Interface"), "support_interface": catalog.i18nc("@tooltip", "Support Interface"),
"support": catalog.i18nc("@tooltip", "Support"), "support": catalog.i18nc("@tooltip", "Support"),
"skirt": catalog.i18nc("@tooltip", "Skirt"), "skirt": catalog.i18nc("@tooltip", "Skirt"),
"prime_tower": catalog.i18nc("@tooltip", "Prime Tower"),
"travel": catalog.i18nc("@tooltip", "Travel"), "travel": catalog.i18nc("@tooltip", "Travel"),
"retract": catalog.i18nc("@tooltip", "Retractions"), "retract": catalog.i18nc("@tooltip", "Retractions"),
"none": catalog.i18nc("@tooltip", "Other") "none": catalog.i18nc("@tooltip", "Other")

View file

@ -39,6 +39,7 @@ class WelcomePagesModel(ListModel):
PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file PageUrlRole = Qt.UserRole + 2 # URL to the page's QML file
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button NextPageButtonTextRole = Qt.UserRole + 4 # The text for the next page button
PreviousPageButtonTextRole = Qt.UserRole + 5 # The text for the previous page button
def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None: def __init__(self, application: "CuraApplication", parent: Optional["QObject"] = None) -> None:
super().__init__(parent) super().__init__(parent)
@ -47,6 +48,7 @@ class WelcomePagesModel(ListModel):
self.addRoleName(self.PageUrlRole, "page_url") self.addRoleName(self.PageUrlRole, "page_url")
self.addRoleName(self.NextPageIdRole, "next_page_id") self.addRoleName(self.NextPageIdRole, "next_page_id")
self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text") self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text")
self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text")
self._application = application self._application = application
self._catalog = i18nCatalog("cura") self._catalog = i18nCatalog("cura")

View file

@ -0,0 +1,44 @@
# Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import socket
from typing import Optional
from PyQt5.QtCore import QObject, pyqtSlot
#
# This is a QObject because some of the functions can be used (and are useful) in QML.
#
class NetworkingUtil(QObject):
def __init__(self, parent: Optional["QObject"] = None) -> None:
super().__init__(parent = parent)
# Checks if the given string is a valid IPv4 address.
@pyqtSlot(str, result = bool)
def isIPv4(self, address: str) -> bool:
try:
socket.inet_pton(socket.AF_INET, address)
result = True
except:
result = False
return result
# Checks if the given string is a valid IPv6 address.
@pyqtSlot(str, result = bool)
def isIPv6(self, address: str) -> bool:
try:
socket.inet_pton(socket.AF_INET6, address)
result = True
except:
result = False
return result
# Checks if the given string is a valid IPv4 or IPv6 address.
@pyqtSlot(str, result = bool)
def isValidIP(self, address: str) -> bool:
return self.isIPv4(address) or self.isIPv6(address)
__all__ = ["NetworkingUtil"]

View file

@ -1,7 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher. # Cura is released under the terms of the LGPLv3 or higher.
from typing import Optional from typing import List, Optional, Union, TYPE_CHECKING
import os.path import os.path
import zipfile import zipfile
@ -9,15 +9,16 @@ import numpy
import Savitar import Savitar
from UM.Application import Application
from UM.Logger import Logger from UM.Logger import Logger
from UM.Math.Matrix import Matrix from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator from UM.Scene.GroupDecorator import GroupDecorator
from UM.Scene.SceneNode import SceneNode #For typing.
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
@ -25,11 +26,9 @@ from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
try: try:
if not MYPY: if not TYPE_CHECKING:
import xml.etree.cElementTree as ET import xml.etree.cElementTree as ET
except ImportError: except ImportError:
Logger.log("w", "Unable to load cElementTree, switching to slower version") Logger.log("w", "Unable to load cElementTree, switching to slower version")
@ -55,7 +54,7 @@ class ThreeMFReader(MeshReader):
self._unit = None self._unit = None
self._object_count = 0 # Used to name objects as there is no node name yet. self._object_count = 0 # Used to name objects as there is no node name yet.
def _createMatrixFromTransformationString(self, transformation): def _createMatrixFromTransformationString(self, transformation: str) -> Matrix:
if transformation == "": if transformation == "":
return Matrix() return Matrix()
@ -85,13 +84,13 @@ class ThreeMFReader(MeshReader):
return temp_mat return temp_mat
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node. ## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
# \returns Uranium scene node. # \returns Scene node.
def _convertSavitarNodeToUMNode(self, savitar_node): def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode) -> Optional[SceneNode]:
self._object_count += 1 self._object_count += 1
node_name = "Object %s" % self._object_count node_name = "Object %s" % self._object_count
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
um_node = CuraSceneNode() # This adds a SettingOverrideDecorator um_node = CuraSceneNode() # This adds a SettingOverrideDecorator
um_node.addDecorator(BuildPlateDecorator(active_build_plate)) um_node.addDecorator(BuildPlateDecorator(active_build_plate))
@ -122,7 +121,7 @@ class ThreeMFReader(MeshReader):
# Add the setting override decorator, so we can add settings to this node. # Add the setting override decorator, so we can add settings to this node.
if settings: if settings:
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
# Ensure the correct next container for the SettingOverride decorator is set. # Ensure the correct next container for the SettingOverride decorator is set.
if global_container_stack: if global_container_stack:
@ -161,7 +160,7 @@ class ThreeMFReader(MeshReader):
um_node.addDecorator(sliceable_decorator) um_node.addDecorator(sliceable_decorator)
return um_node return um_node
def _read(self, file_name): def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
result = [] result = []
self._object_count = 0 # Used to name objects as there is no node name yet. self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive. # The base object of 3mf is a zipped archive.
@ -181,12 +180,13 @@ class ThreeMFReader(MeshReader):
mesh_data = um_node.getMeshData() mesh_data = um_node.getMeshData()
if mesh_data is not None: if mesh_data is not None:
extents = mesh_data.getExtents() extents = mesh_data.getExtents()
if extents is not None:
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z) center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
transform_matrix.setByTranslation(center_vector) transform_matrix.setByTranslation(center_vector)
transform_matrix.multiply(um_node.getLocalTransformation()) transform_matrix.multiply(um_node.getLocalTransformation())
um_node.setTransformation(transform_matrix) um_node.setTransformation(transform_matrix)
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
# Create a transformation Matrix to convert from 3mf worldspace into ours. # Create a transformation Matrix to convert from 3mf worldspace into ours.
# First step: flip the y and z axis. # First step: flip the y and z axis.
@ -215,8 +215,11 @@ class ThreeMFReader(MeshReader):
um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix)) um_node.setTransformation(um_node.getLocalTransformation().preMultiply(transformation_matrix))
# Check if the model is positioned below the build plate and honor that when loading project files. # Check if the model is positioned below the build plate and honor that when loading project files.
if um_node.getMeshData() is not None: node_meshdata = um_node.getMeshData()
minimum_z_value = um_node.getMeshData().getExtents(um_node.getWorldTransformation()).minimum.y # y is z in transformation coordinates if node_meshdata is not None:
aabb = node_meshdata.getExtents(um_node.getWorldTransformation())
if aabb is not None:
minimum_z_value = aabb.minimum.y # y is z in transformation coordinates
if minimum_z_value < 0: if minimum_z_value < 0:
um_node.addDecorator(ZOffsetDecorator()) um_node.addDecorator(ZOffsetDecorator())
um_node.callDecoration("setZOffset", minimum_z_value) um_node.callDecoration("setZOffset", minimum_z_value)
@ -225,7 +228,7 @@ class ThreeMFReader(MeshReader):
except Exception: except Exception:
Logger.logException("e", "An exception occurred in 3mf reader.") Logger.logException("e", "An exception occurred in 3mf reader.")
return None return []
return result return result

View file

@ -0,0 +1,173 @@
# Copyright (c) 2019 fieldOfView
# Cura is released under the terms of the LGPLv3 or higher.
# This AMF parser is based on the AMF parser in legacy cura:
# https://github.com/daid/LegacyCura/blob/ad7641e059048c7dcb25da1f47c0a7e95e7f4f7c/Cura/util/meshLoaders/amf.py
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.CuraApplication import CuraApplication
from UM.Logger import Logger
from UM.Mesh.MeshData import MeshData, calculateNormalsFromIndexedVertices
from UM.Mesh.MeshReader import MeshReader
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from cura.Scene.ConvexHullDecorator import ConvexHullDecorator
from UM.Scene.GroupDecorator import GroupDecorator
import numpy
import trimesh
import os.path
import zipfile
MYPY = False
try:
if not MYPY:
import xml.etree.cElementTree as ET
except ImportError:
import xml.etree.ElementTree as ET
from typing import Dict
class AMFReader(MeshReader):
def __init__(self) -> None:
super().__init__()
self._supported_extensions = [".amf"]
self._namespaces = {} # type: Dict[str, str]
MimeTypeDatabase.addMimeType(
MimeType(
name="application/x-amf",
comment="AMF",
suffixes=["amf"]
)
)
# Main entry point
# Reads the file, returns a SceneNode (possibly with nested ones), or None
def _read(self, file_name):
base_name = os.path.basename(file_name)
try:
zipped_file = zipfile.ZipFile(file_name)
xml_document = zipped_file.read(zipped_file.namelist()[0])
zipped_file.close()
except zipfile.BadZipfile:
raw_file = open(file_name, "r")
xml_document = raw_file.read()
raw_file.close()
try:
amf_document = ET.fromstring(xml_document)
except ET.ParseError:
Logger.log("e", "Could not parse XML in file %s" % base_name)
return None
if "unit" in amf_document.attrib:
unit = amf_document.attrib["unit"].lower()
else:
unit = "millimeter"
if unit == "millimeter":
scale = 1.0
elif unit == "meter":
scale = 1000.0
elif unit == "inch":
scale = 25.4
elif unit == "feet":
scale = 304.8
elif unit == "micron":
scale = 0.001
else:
Logger.log("w", "Unknown unit in amf: %s. Using mm instead." % unit)
scale = 1.0
nodes = []
for amf_object in amf_document.iter("object"):
for amf_mesh in amf_object.iter("mesh"):
amf_mesh_vertices = []
for vertices in amf_mesh.iter("vertices"):
for vertex in vertices.iter("vertex"):
for coordinates in vertex.iter("coordinates"):
v = [0.0, 0.0, 0.0]
for t in coordinates:
if t.tag == "x":
v[0] = float(t.text) * scale
elif t.tag == "y":
v[2] = float(t.text) * scale
elif t.tag == "z":
v[1] = float(t.text) * scale
amf_mesh_vertices.append(v)
if not amf_mesh_vertices:
continue
indices = []
for volume in amf_mesh.iter("volume"):
for triangle in volume.iter("triangle"):
f = [0, 0, 0]
for t in triangle:
if t.tag == "v1":
f[0] = int(t.text)
elif t.tag == "v2":
f[1] = int(t.text)
elif t.tag == "v3":
f[2] = int(t.text)
indices.append(f)
mesh = trimesh.base.Trimesh(vertices=numpy.array(amf_mesh_vertices, dtype=numpy.float32), faces=numpy.array(indices, dtype=numpy.int32))
mesh.merge_vertices()
mesh.remove_unreferenced_vertices()
mesh.fix_normals()
mesh_data = self._toMeshData(mesh)
new_node = CuraSceneNode()
new_node.setSelectable(True)
new_node.setMeshData(mesh_data)
new_node.setName(base_name if len(nodes)==0 else "%s %d" % (base_name, len(nodes)))
new_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
new_node.addDecorator(SliceableObjectDecorator())
nodes.append(new_node)
if not nodes:
Logger.log("e", "No meshes in file %s" % base_name)
return None
if len(nodes) == 1:
return nodes[0]
# Add all scenenodes to a group so they stay together
group_node = CuraSceneNode()
group_node.addDecorator(GroupDecorator())
group_node.addDecorator(ConvexHullDecorator())
group_node.addDecorator(BuildPlateDecorator(CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate))
for node in nodes:
node.setParent(group_node)
return group_node
def _toMeshData(self, tri_node: trimesh.base.Trimesh) -> MeshData:
tri_faces = tri_node.faces
tri_vertices = tri_node.vertices
indices = []
vertices = []
index_count = 0
face_count = 0
for tri_face in tri_faces:
face = []
for tri_index in tri_face:
vertices.append(tri_vertices[tri_index])
face.append(index_count)
index_count += 1
indices.append(face)
face_count += 1
vertices = numpy.asarray(vertices, dtype=numpy.float32)
indices = numpy.asarray(indices, dtype=numpy.int32)
normals = calculateNormalsFromIndexedVertices(vertices, indices, face_count)
mesh_data = MeshData(vertices=vertices, indices=indices, normals=normals)
return mesh_data

View file

@ -0,0 +1,21 @@
# Copyright (c) 2019 fieldOfView
# Cura is released under the terms of the LGPLv3 or higher.
from . import AMFReader
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("uranium")
def getMetaData():
return {
"mesh_reader": [
{
"extension": "amf",
"description": i18n_catalog.i18nc("@item:inlistbox", "AMF File")
}
]
}
def register(app):
return {"mesh_reader": AMFReader.AMFReader()}

View file

@ -0,0 +1,7 @@
{
"name": "AMF Reader",
"author": "fieldOfView",
"version": "1.0.0",
"description": "Provides support for reading AMF files.",
"api": "6.0.0"
}

View file

@ -45,7 +45,7 @@ class DriveApiService:
"Authorization": "Bearer {}".format(access_token) "Authorization": "Bearer {}".format(access_token)
}) })
except requests.exceptions.ConnectionError: except requests.exceptions.ConnectionError:
Logger.log("w", "Unable to connect with the server.") Logger.logException("w", "Unable to connect with the server.")
return [] return []
# HTTP status 300s mean redirection. 400s and 500s are errors. # HTTP status 300s mean redirection. 400s and 500s are errors.
@ -98,7 +98,12 @@ class DriveApiService:
# If there is no download URL, we can't restore the backup. # If there is no download URL, we can't restore the backup.
return self._emitRestoreError() return self._emitRestoreError()
try:
download_package = requests.get(download_url, stream = True) download_package = requests.get(download_url, stream = True)
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return self._emitRestoreError()
if download_package.status_code >= 300: if download_package.status_code >= 300:
# Something went wrong when attempting to download the backup. # Something went wrong when attempting to download the backup.
Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text) Logger.log("w", "Could not download backup from url %s: %s", download_url, download_package.text)
@ -142,9 +147,14 @@ class DriveApiService:
Logger.log("w", "Could not get access token.") Logger.log("w", "Could not get access token.")
return False return False
try:
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = { delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
"Authorization": "Bearer {}".format(access_token) "Authorization": "Bearer {}".format(access_token)
}) })
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return False
if delete_backup.status_code >= 300: if delete_backup.status_code >= 300:
Logger.log("w", "Could not delete backup: %s", delete_backup.text) Logger.log("w", "Could not delete backup: %s", delete_backup.text)
return False return False
@ -159,15 +169,19 @@ class DriveApiService:
if not access_token: if not access_token:
Logger.log("w", "Could not get access token.") Logger.log("w", "Could not get access token.")
return None return None
try:
backup_upload_request = requests.put(self.BACKUP_URL, json = { backup_upload_request = requests.put(
"data": { self.BACKUP_URL,
"backup_size": backup_size, json = {"data": {"backup_size": backup_size,
"metadata": backup_metadata "metadata": backup_metadata
} }
}, headers = { },
headers = {
"Authorization": "Bearer {}".format(access_token) "Authorization": "Bearer {}".format(access_token)
}) })
except requests.exceptions.ConnectionError:
Logger.logException("e", "Unable to connect with the server")
return None
# Any status code of 300 or above indicates an error. # Any status code of 300 or above indicates an error.
if backup_upload_request.status_code >= 300: if backup_upload_request.status_code >= 300:

View file

@ -517,9 +517,6 @@ class CuraEngineBackend(QObject, Backend):
self._build_plates_to_be_sliced.append(build_plate_number) self._build_plates_to_be_sliced.append(build_plate_number)
self.printDurationMessage.emit(source_build_plate_number, {}, []) self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0) self.processingProgress.emit(0.0)
self.setState(BackendState.NotStarted)
# if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData(build_plate_changed) self._clearLayerData(build_plate_changed)
self._invokeSlice() self._invokeSlice()
@ -563,10 +560,10 @@ class CuraEngineBackend(QObject, Backend):
## Convenient function: mark everything to slice, emit state and clear layer data ## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self) -> None: def needsSlicing(self) -> None:
self.determineAutoSlicing()
self.stopSlicing() self.stopSlicing()
self.markSliceAll() self.markSliceAll()
self.processingProgress.emit(0.0) self.processingProgress.emit(0.0)
self.setState(BackendState.NotStarted)
if not self._use_timer: if not self._use_timer:
# With manually having to slice, we want to clear the old invalid layer data. # With manually having to slice, we want to clear the old invalid layer data.
self._clearLayerData() self._clearLayerData()
@ -735,6 +732,7 @@ class CuraEngineBackend(QObject, Backend):
"support_interface": message.time_support_interface, "support_interface": message.time_support_interface,
"support": message.time_support, "support": message.time_support,
"skirt": message.time_skirt, "skirt": message.time_skirt,
"prime_tower": message.time_prime_tower,
"travel": message.time_travel, "travel": message.time_travel,
"retract": message.time_retract, "retract": message.time_retract,
"none": message.time_none "none": message.time_none

View file

@ -256,10 +256,7 @@ class StartSliceJob(Job):
self._buildGlobalInheritsStackMessage(stack) self._buildGlobalInheritsStackMessage(stack)
# Build messages for extruder stacks # Build messages for extruder stacks
# Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first, for extruder_stack in global_stack.extruderList:
# then CuraEngine can slice with the wrong settings. This I think should be fixed in CuraEngine as well.
extruder_stack_list = sorted(list(global_stack.extruders.items()), key = lambda item: int(item[0]))
for _, extruder_stack in extruder_stack_list:
self._buildExtruderMessage(extruder_stack) self._buildExtruderMessage(extruder_stack)
for group in filtered_object_groups: for group in filtered_object_groups:
@ -334,25 +331,29 @@ class StartSliceJob(Job):
return result return result
## Replace setting tokens in a piece of g-code. def _cacheAllExtruderSettings(self):
# \param value A piece of g-code to replace tokens in.
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
if not self._all_extruders_settings:
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack()) global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
# NB: keys must be strings for the string formatter # NB: keys must be strings for the string formatter
self._all_extruders_settings = { self._all_extruders_settings = {
"-1": self._buildReplacementTokens(global_stack) "-1": self._buildReplacementTokens(global_stack)
} }
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks(): for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_nr = extruder_stack.getProperty("extruder_nr", "value") extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack) self._all_extruders_settings[str(extruder_nr)] = self._buildReplacementTokens(extruder_stack)
## Replace setting tokens in a piece of g-code.
# \param value A piece of g-code to replace tokens in.
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
try: try:
# any setting can be used as a token # any setting can be used as a token
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr) fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
if self._all_extruders_settings is None:
return ""
settings = self._all_extruders_settings.copy() settings = self._all_extruders_settings.copy()
settings["default_extruder_nr"] = default_extruder_nr settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings)) return str(fmt.format(value, **settings))
@ -364,8 +365,14 @@ class StartSliceJob(Job):
def _buildExtruderMessage(self, stack: ContainerStack) -> None: def _buildExtruderMessage(self, stack: ContainerStack) -> None:
message = self._slice_message.addRepeatedMessage("extruders") message = self._slice_message.addRepeatedMessage("extruders")
message.id = int(stack.getMetaDataEntry("position")) message.id = int(stack.getMetaDataEntry("position"))
if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
settings = self._buildReplacementTokens(stack) if self._all_extruders_settings is None:
return
extruder_nr = stack.getProperty("extruder_nr", "value")
settings = self._all_extruders_settings[str(extruder_nr)].copy()
# Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it. # Also send the material GUID. This is a setting in fdmprinter, but we have no interface for it.
settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "") settings["material_guid"] = stack.material.getMetaDataEntry("GUID", "")
@ -389,7 +396,13 @@ class StartSliceJob(Job):
# The settings are taken from the global stack. This does not include any # The settings are taken from the global stack. This does not include any
# per-extruder settings or per-object settings. # per-extruder settings or per-object settings.
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None: def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
settings = self._buildReplacementTokens(stack) if not self._all_extruders_settings:
self._cacheAllExtruderSettings()
if self._all_extruders_settings is None:
return
settings = self._all_extruders_settings["-1"].copy()
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend # Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
start_gcode = settings["machine_start_gcode"] start_gcode = settings["machine_start_gcode"]

View file

@ -370,6 +370,8 @@ class FlavorParser:
self._layer_type = LayerPolygon.InfillType self._layer_type = LayerPolygon.InfillType
elif type == "SUPPORT-INTERFACE": elif type == "SUPPORT-INTERFACE":
self._layer_type = LayerPolygon.SupportInterfaceType self._layer_type = LayerPolygon.SupportInterfaceType
elif type == "PRIME-TOWER":
self._layer_type = LayerPolygon.PrimeTowerType
else: else:
Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type) Logger.log("w", "Encountered a unknown type (%s) while parsing g-code.", type)

View file

@ -26,7 +26,7 @@ Item
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0 property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
property int columnSpacing: 3 * screenScaleFactor property int columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property string extruderStackId: "" property string extruderStackId: ""
property int extruderPosition: 0 property int extruderPosition: 0

View file

@ -26,7 +26,7 @@ Item
property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0 property int columnWidth: ((parent.width - 2 * UM.Theme.getSize("default_margin").width) / 2) | 0
property int columnSpacing: 3 * screenScaleFactor property int columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property string machineStackId: Cura.MachineManager.activeMachineId property string machineStackId: Cura.MachineManager.activeMachineId
@ -285,18 +285,30 @@ Item
optionModel: ListModel optionModel: ListModel
{ {
id: extruderCountModel id: extruderCountModel
Component.onCompleted: Component.onCompleted:
{ {
extruderCountModel.clear() update()
}
function update()
{
clear()
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++) for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
{ {
// Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue() // Use String as value. JavaScript only has Number. PropertyProvider.setPropertyValue()
// takes a QVariant as value, and Number gets translated into a float. This will cause problem // takes a QVariant as value, and Number gets translated into a float. This will cause problem
// for integer settings such as "Number of Extruders". // for integer settings such as "Number of Extruders".
extruderCountModel.append({ text: String(i), value: String(i) }) append({ text: String(i), value: String(i) })
} }
} }
} }
Connections
{
target: Cura.MachineManager
onGlobalContainerChanged: extruderCountModel.update()
}
} }
} }
} }

View file

@ -12,7 +12,15 @@ Rectangle
id: viewportOverlay id: viewportOverlay
property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection property bool isConnected: Cura.MachineManager.activeMachineHasNetworkConnection || Cura.MachineManager.activeMachineHasCloudConnection
property bool isNetworkConfigurable: ["Ultimaker 3", "Ultimaker 3 Extended", "Ultimaker S5"].indexOf(Cura.MachineManager.activeMachineDefinitionName) > -1 property bool isNetworkConfigurable:
{
if(Cura.MachineManager.activeMachine === null)
{
return false
}
return Cura.MachineManager.activeMachine.supportsNetworkConnection
}
property bool isNetworkConfigured: property bool isNetworkConfigured:
{ {
// Readability: // Readability:
@ -98,7 +106,6 @@ Rectangle
width: contentWidth width: contentWidth
} }
// CASE 3: CAN NOT MONITOR
Label Label
{ {
id: noNetworkLabel id: noNetworkLabel
@ -106,24 +113,8 @@ Rectangle
{ {
horizontalCenter: parent.horizontalCenter horizontalCenter: parent.horizontalCenter
} }
visible: !isNetworkConfigured
text: catalog.i18nc("@info", "Please select a network connected printer to monitor.")
font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap
width: contentWidth
lineHeight: UM.Theme.getSize("monitor_text_line_large").height
lineHeightMode: Text.FixedHeight
}
Label
{
id: noNetworkUltimakerLabel
anchors
{
horizontalCenter: parent.horizontalCenter
}
visible: !isNetworkConfigured && isNetworkConfigurable visible: !isNetworkConfigured && isNetworkConfigurable
text: catalog.i18nc("@info", "Please connect your Ultimaker printer to your local network.") text: catalog.i18nc("@info", "Please connect your printer to the network.")
font: UM.Theme.getFont("medium") font: UM.Theme.getFont("medium")
color: UM.Theme.getColor("monitor_text_primary") color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
@ -135,7 +126,7 @@ Rectangle
{ {
anchors anchors
{ {
left: noNetworkUltimakerLabel.left left: noNetworkLabel.left
} }
visible: !isNetworkConfigured && isNetworkConfigurable visible: !isNetworkConfigured && isNetworkConfigurable
height: UM.Theme.getSize("monitor_text_line").height height: UM.Theme.getSize("monitor_text_line").height
@ -160,7 +151,7 @@ Rectangle
verticalCenter: externalLinkIcon.verticalCenter verticalCenter: externalLinkIcon.verticalCenter
} }
color: UM.Theme.getColor("monitor_text_link") color: UM.Theme.getColor("monitor_text_link")
font: UM.Theme.getFont("medium") // 14pt, regular font: UM.Theme.getFont("medium")
linkColor: UM.Theme.getColor("monitor_text_link") linkColor: UM.Theme.getColor("monitor_text_link")
text: catalog.i18nc("@label link to technical assistance", "View user manuals online") text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
renderType: Text.NativeRendering renderType: Text.NativeRendering
@ -170,14 +161,8 @@ Rectangle
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers") onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
onEntered: onEntered: manageQueueText.font.underline = true
{ onExited: manageQueueText.font.underline = false
manageQueueText.font.underline = true
}
onExited:
{
manageQueueText.font.underline = false
}
} }
} }
} }

View file

@ -1,9 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V. # Copyright (c) 2019 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher. # The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from typing import Optional, Tuple from typing import List
from UM.Logger import Logger
from ..Script import Script from ..Script import Script
class FilamentChange(Script): class FilamentChange(Script):
@ -65,9 +63,10 @@ class FilamentChange(Script):
} }
}""" }"""
def execute(self, data: list): ## Inserts the filament change g-code at specific layer numbers.
# \param data A list of layers of g-code.
"""data is a list. Each index contains a layer""" # \return A similar list, with filament change commands inserted.
def execute(self, data: List[str]):
layer_nums = self.getSettingValueByKey("layer_number") layer_nums = self.getSettingValueByKey("layer_number")
initial_retract = self.getSettingValueByKey("initial_retract") initial_retract = self.getSettingValueByKey("initial_retract")
later_retract = self.getSettingValueByKey("later_retract") later_retract = self.getSettingValueByKey("later_retract")
@ -88,32 +87,13 @@ class FilamentChange(Script):
if y_pos is not None: if y_pos is not None:
color_change = color_change + (" Y%.2f" % y_pos) color_change = color_change + (" Y%.2f" % y_pos)
color_change = color_change + " ; Generated by FilamentChange plugin" color_change = color_change + " ; Generated by FilamentChange plugin\n"
layer_targets = layer_nums.split(",") layer_targets = layer_nums.split(",")
if len(layer_targets) > 0: if len(layer_targets) > 0:
for layer_num in layer_targets: for layer_num in layer_targets:
layer_num = int(layer_num.strip()) layer_num = int(layer_num.strip()) + 1
if layer_num <= len(data): if layer_num <= len(data):
index, layer_data = self._searchLayerData(data, layer_num - 1) data[layer_num] = color_change + data[layer_num]
if layer_data is None:
Logger.log("e", "Could not find the layer {layer_num}".format(layer_num = layer_num))
continue
lines = layer_data.split("\n")
lines.insert(2, color_change)
final_line = "\n".join(lines)
data[index] = final_line
return data return data
## This method returns the data corresponding with the indicated layer number, looking in the gcode for
# the occurrence of this layer number.
def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]:
for index, layer_data in enumerate(data):
first_line = layer_data.split("\n")[0]
# The first line should contain the layer number at the beginning.
if first_line[:len(self._layer_keyword)] == self._layer_keyword:
# If found the layer that we are looking for, then return the data
if first_line[len(self._layer_keyword):] == str(layer_num):
return index, layer_data
return 0, None

View file

@ -20,11 +20,19 @@ Item
name: "cura" name: "cura"
} }
anchors
{
left: parent.left
right: parent.right
leftMargin: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("wide_margin").width
}
// Item to ensure that all of the buttons are nicely centered. // Item to ensure that all of the buttons are nicely centered.
Item Item
{ {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
width: openFileButton.width + itemRow.width + UM.Theme.getSize("default_margin").width width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
height: parent.height height: parent.height
RowLayout RowLayout
@ -32,9 +40,9 @@ Item
id: itemRow id: itemRow
anchors.left: openFileButton.right anchors.left: openFileButton.right
anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: Math.round(0.9 * prepareMenu.width)
height: parent.height height: parent.height
spacing: 0 spacing: 0

View file

@ -20,15 +20,21 @@ Item
name: "cura" name: "cura"
} }
anchors
{
left: parent.left
right: parent.right
leftMargin: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("wide_margin").width
}
Row Row
{ {
id: stageMenuRow id: stageMenuRow
anchors.centerIn: parent
height: parent.height
width: childrenRect.width
// We want this row to have a preferred with equals to the 85% of the parent anchors.horizontalCenter: parent.horizontalCenter
property int preferredWidth: Math.round(0.85 * previewMenu.width) width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
height: parent.height
Cura.ViewsSelector Cura.ViewsSelector
{ {
@ -49,12 +55,12 @@ Item
color: UM.Theme.getColor("lining") color: UM.Theme.getColor("lining")
} }
// This component will grow freely up to complete the preferredWidth of the row. // This component will grow freely up to complete the width of the row.
Loader Loader
{ {
id: viewPanel id: viewPanel
height: parent.height height: parent.height
width: source != "" ? (stageMenuRow.preferredWidth - viewsSelector.width - printSetupSelectorItem.width - 2 * UM.Theme.getSize("default_lining").width) : 0 width: source != "" ? (previewMenu.width - viewsSelector.width - printSetupSelectorItem.width - 2 * (UM.Theme.getSize("wide_margin").width + UM.Theme.getSize("default_lining").width)) : 0
source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : "" source: UM.Controller.activeView != null && UM.Controller.activeView.stageMenuComponent != null ? UM.Controller.activeView.stageMenuComponent : ""
} }

View file

@ -15,6 +15,8 @@ Cura.ExpandableComponent
{ {
id: base id: base
dragPreferencesNamePrefix: "view/colorscheme"
contentHeaderTitle: catalog.i18nc("@label", "Color scheme") contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
Connections Connections

View file

@ -71,7 +71,7 @@ Window
left: parent.left left: parent.left
right: parent.right right: parent.right
} }
text: catalog.i18nc("@text:window", "Cura sends anonymous data to Ultimaker in order to improve the print quality and user experience. Below is an example of all the data that is sent.") text: catalog.i18nc("@text:window", "Ultimaker Cura collects anonymous data in order to improve the print quality and user experience. Below is an example of all the data that is shared:")
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
renderType: Text.NativeRendering renderType: Text.NativeRendering
} }
@ -89,6 +89,8 @@ Window
} }
textArea.text: manager.getExampleData() textArea.text: manager.getExampleData()
textArea.textFormat: Text.RichText
textArea.wrapMode: Text.Wrap
textArea.readOnly: true textArea.readOnly: true
} }
} }
@ -104,7 +106,7 @@ Window
Cura.RadioButton Cura.RadioButton
{ {
id: dontSendButton id: dontSendButton
text: catalog.i18nc("@text:window", "I don't want to send this data") text: catalog.i18nc("@text:window", "I don't want to send anonymous data")
onClicked: onClicked:
{ {
baseDialog.allowSendData = !checked baseDialog.allowSendData = !checked
@ -113,7 +115,7 @@ Window
Cura.RadioButton Cura.RadioButton
{ {
id: allowSendButton id: allowSendButton
text: catalog.i18nc("@text:window", "Allow sending this data to Ultimaker and help us improve Cura") text: catalog.i18nc("@text:window", "Allow sending anonymous data")
onClicked: onClicked:
{ {
baseDialog.allowSendData = checked baseDialog.allowSendData = checked

View file

@ -77,7 +77,7 @@ class SliceInfo(QObject, Extension):
if not plugin_path: if not plugin_path:
Logger.log("e", "Could not get plugin path!", self.getPluginId()) Logger.log("e", "Could not get plugin path!", self.getPluginId())
return None return None
file_path = os.path.join(plugin_path, "example_data.json") file_path = os.path.join(plugin_path, "example_data.html")
if file_path: if file_path:
with open(file_path, "r", encoding = "utf-8") as f: with open(file_path, "r", encoding = "utf-8") as f:
self._example_data_content = f.read() self._example_data_content = f.read()

View file

@ -0,0 +1,64 @@
<html>
<body>
<b>Cura Version:</b> 4.0<br/>
<b>Operating System:</b> Windows 10<br/>
<b>Language:</b> en_US<br/>
<b>Machine Type:</b> Ultimaker S5<br/>
<b>Quality Profile:</b> Fast<br/>
<b>Using Custom Settings:</b> No
<h3>Extruder 1:</h3>
<ul>
<li><b>Material Type:</b> PLA</li>
<li><b>Print Core:</b> AA 0.4</li>
<li><b>Material Used:</b> 1240 mm</li>
</ul>
<h3>Extruder 2:</h3>
<ul>
<li><b>Material Type:</b> PVA</li>
<li><b>Print Core:</b> BB 0.4</li>
<li><b>Material Used:</b> 432 mm</li>
</ul>
<h3>Print Settings:</h3>
<ul>
<li><b>Layer Height:</b> 0.15</li>
<li><b>Wall Line Count:</b> 3</li>
<li><b>Enable Retraction:</b> no</li>
<li><b>Infill Density:</b> 20%</li>
<li><b>Infill Pattern:</b> triangles</li>
<li><b>Gradual Infill Steps:</b> 0</li>
<li><b>Printing Temperature:</b> 220 °C</li>
<li><b>Generate Support:</b> yes</li>
<li><b>Support Extruder:</b> 1</li>
<li><b>Build Plate Adhesion Type:</b> brim</li>
<li><b>Enable Prime Tower:</b> yes</li>
<li><b>Print Sequence:</b> All at once</li>
<li>...</li>
</ul>
<h3>Model Information:</h3>
<ul>
<li>
<b>Model 1</b>
<ul>
<li><b>Hash:</b> b72789b9b...</li>
<li><b>Transformation:</b> [transformation matrix]</li>
<li><b>Bounding Box:</b> [minimum x, y, z; maximum x, y, z]</li>
<li><b>Is Helper Mesh:</b> no</li>
<li><b>Helper Mesh Type:</b> support mesh</li>
</ul>
</li>
</ul>
<h3>Print Times:</h3>
<ul>
<li>Infill: 61200 sec.</li>
<li>Support: 25480 sec.</li>
<li>Travel: 6224 sec.</li>
<li>Walls: 10225 sec.</li>
<li>Total: 103129 sec.</li>
</ul>
</body>
</html>

View file

@ -1,114 +0,0 @@
{
"time_stamp": 1523973715.486928,
"schema_version": 0,
"cura_version": "3.3",
"active_mode": "custom",
"machine_settings_changed_by_user": true,
"language": "en_US",
"os": {
"type": "Linux",
"version": "#43~16.04.1-Ubuntu SMP Wed Mar 14 17:48:43 UTC 2018"
},
"active_machine": {
"definition_id": "ultimaker3",
"manufacturer": "Ultimaker B.V."
},
"extruders": [
{
"active": true,
"material": {
"GUID": "506c9f0d-e3aa-4bd4-b2d2-23e2425b1aa9",
"type": "PLA",
"brand": "Generic"
},
"material_used": 0.84,
"variant": "AA 0.4",
"nozzle_size": 0.4,
"extruder_settings": {
"wall_line_count": 3,
"retraction_enable": true,
"infill_sparse_density": 30,
"infill_pattern": "triangles",
"gradual_infill_steps": 0,
"default_material_print_temperature": 200,
"material_print_temperature": 200
}
},
{
"active": false,
"material": {
"GUID": "86a89ceb-4159-47f6-ab97-e9953803d70f",
"type": "PVA",
"brand": "Generic"
},
"material_used": 0.5,
"variant": "BB 0.4",
"nozzle_size": 0.4,
"extruder_settings": {
"wall_line_count": 3,
"retraction_enable": true,
"infill_sparse_density": 20,
"infill_pattern": "triangles",
"gradual_infill_steps": 0,
"default_material_print_temperature": 215,
"material_print_temperature": 220
}
}
],
"quality_profile": "fast",
"user_modified_setting_keys": ["layer_height", "wall_line_width", "infill_sparse_density"],
"models": [
{
"hash": "b72789b9beb5366dff20b1cf501020c3d4d4df7dc2295ecd0fddd0a6436df070",
"bounding_box": {
"minimum": {
"x": -10.0,
"y": 0.0,
"z": -5.0
},
"maximum": {
"x": 9.999999046325684,
"y": 40.0,
"z": 5.0
}
},
"transformation": {
"data": "[[ 1. 0. 0. 0.] [ 0. 1. 0. 20.] [ 0. 0. 1. 0.] [ 0. 0. 0. 1.]]"
},
"extruder": 0,
"model_settings": {
"support_enabled": true,
"support_extruder_nr": 1,
"infill_mesh": false,
"cutting_mesh": false,
"support_mesh": false,
"anti_overhang_mesh": false,
"wall_line_count": 3,
"retraction_enable": true,
"infill_sparse_density": 30,
"infill_pattern": "triangles",
"gradual_infill_steps": 0
}
}
],
"print_times": {
"travel": 187,
"support": 825,
"infill": 351,
"total": 7234
},
"print_settings": {
"layer_height": 0.15,
"support_enabled": true,
"support_extruder_nr": 1,
"adhesion_type": "brim",
"wall_line_count": 3,
"retraction_enable": true,
"prime_tower_enable": true,
"infill_sparse_density": 20,
"infill_pattern": "triangles",
"gradual_infill_steps": 0,
"print_sequence": "all_at_once"
},
"output_to": "LocalFileOutputDevice"
}

View file

@ -58,7 +58,7 @@ class SolidView(View):
global_container_stack = Application.getInstance().getGlobalContainerStack() global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack: if global_container_stack:
support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr") support_extruder_nr = global_container_stack.getExtruderPositionValueWithDefault("support_extruder_nr")
support_angle_stack = Application.getInstance().getExtruderManager().getExtruderStack(support_extruder_nr) support_angle_stack = global_container_stack.extruders.get(str(support_extruder_nr))
if support_angle_stack: if support_angle_stack:
self._support_angle = support_angle_stack.getProperty("support_angle", "value") self._support_angle = support_angle_stack.getProperty("support_angle", "value")

View file

@ -109,6 +109,8 @@ Item
top: description.bottom top: description.bottom
left: properties.right left: properties.right
leftMargin: UM.Theme.getSize("default_margin").width leftMargin: UM.Theme.getSize("default_margin").width
right: parent.right
rightMargin: UM.Theme.getSize("default_margin").width
topMargin: UM.Theme.getSize("default_margin").height topMargin: UM.Theme.getSize("default_margin").height
} }
spacing: Math.floor(UM.Theme.getSize("narrow_margin").height) spacing: Math.floor(UM.Theme.getSize("narrow_margin").height)
@ -123,6 +125,8 @@ Item
} }
return "" return ""
} }
width: parent.width
elide: Text.ElideRight
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
color: UM.Theme.getColor("text") color: UM.Theme.getColor("text")
linkColor: UM.Theme.getColor("text_link") linkColor: UM.Theme.getColor("text_link")

View file

@ -40,6 +40,7 @@ Column
Cura.SecondaryButton Cura.SecondaryButton
{ {
id: installedButton
visible: installed visible: installed
onClicked: toolbox.viewCategory = "installed" onClicked: toolbox.viewCategory = "installed"
text: catalog.i18nc("@action:button", "Installed") text: catalog.i18nc("@action:button", "Installed")

View file

@ -105,6 +105,7 @@ class Toolbox(QObject, Extension):
self._application.initializationFinished.connect(self._onAppInitialized) self._application.initializationFinished.connect(self._onAppInitialized)
self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader) self._application.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
self._application.getCuraAPI().account.accessTokenChanged.connect(self._updateRequestHeader)
# Signals: # Signals:
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------

View file

@ -1,10 +1,15 @@
#Copyright (c) 2019 Ultimaker B.V. #Copyright (c) 2019 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher. #Cura is released under the terms of the LGPLv3 or higher.
from UM.i18n import i18nCatalog import sys
from UM.Logger import Logger
try:
from . import UFPReader from . import UFPReader
except ImportError:
Logger.log("w", "Could not import UFPReader; libCharon may be missing")
from UM.i18n import i18nCatalog
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
@ -21,6 +26,9 @@ def getMetaData():
def register(app): def register(app):
if "UFPReader.UFPReader" not in sys.modules:
return {}
app.addNonSliceableExtension(".ufp") app.addNonSliceableExtension(".ufp")
return {"mesh_reader": UFPReader.UFPReader()} return {"mesh_reader": UFPReader.UFPReader()}

View file

@ -1,8 +1,8 @@
// Copyright (c) 2018 Ultimaker B.V. // Copyright (c) 2019 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher. // Cura is released under the terms of the LGPLv3 or higher.
import UM 1.2 as UM import UM 1.2 as UM
import Cura 1.0 as Cura import Cura 1.5 as Cura
import QtQuick 2.2 import QtQuick 2.2
import QtQuick.Controls 1.1 import QtQuick.Controls 1.1
@ -14,22 +14,27 @@ Cura.MachineAction
{ {
id: base id: base
anchors.fill: parent; anchors.fill: parent;
property alias currentItemIndex: listview.currentIndex
property var selectedDevice: null property var selectedDevice: null
property bool completeProperties: true property bool completeProperties: true
// For validating IP addresses
property var networkingUtil: Cura.NetworkingUtil {}
function connectToPrinter() function connectToPrinter()
{ {
if(base.selectedDevice && base.completeProperties) if(base.selectedDevice && base.completeProperties)
{ {
var printerKey = base.selectedDevice.key var printerKey = base.selectedDevice.key
var printerName = base.selectedDevice.name // TODO To change when the groups have a name var printerName = base.selectedDevice.name // TODO To change when the groups have a name
if (manager.getStoredKey() != printerKey) if (Cura.API.machines.getCurrentMachine().um_network_key != printerKey) // TODO: change to hostname
{ {
// Check if there is another instance with the same key // Check if there is another instance with the same key
if (!manager.existsKey(printerKey)) if (!manager.existsKey(printerKey))
{ {
manager.associateActiveMachineWithPrinterDevice(base.selectedDevice) Cura.API.machines.addOutputDeviceToCurrentMachine(base.selectedDevice)
manager.setGroupName(printerName) // TODO To change when the groups have a name Cura.API.machines.setCurrentMachineGroupName(printerName) // TODO To change when the groups have a name
manager.refreshConnections()
completed() completed()
} }
else else
@ -152,7 +157,7 @@ Cura.MachineAction
var selectedKey = manager.getLastManualEntryKey() var selectedKey = manager.getLastManualEntryKey()
// If there is no last manual entry key, then we select the stored key (if any) // If there is no last manual entry key, then we select the stored key (if any)
if (selectedKey == "") if (selectedKey == "")
selectedKey = manager.getStoredKey() selectedKey = Cura.API.machines.getCurrentMachine().um_network_key // TODO: change to host name
for(var i = 0; i < model.length; i++) { for(var i = 0; i < model.length; i++) {
if(model[i].key == selectedKey) if(model[i].key == selectedKey)
{ {
@ -342,6 +347,17 @@ Cura.MachineAction
} }
} }
MessageDialog
{
id: invalidIPAddressMessageDialog
x: (parent.x + (parent.width) / 2) | 0
y: (parent.y + (parent.height) / 2) | 0
title: catalog.i18nc("@title:window", "Invalid IP address")
text: catalog.i18nc("@text", "Please enter a valid IP address.")
icon: StandardIcon.Warning
standardButtons: StandardButton.Ok
}
UM.Dialog UM.Dialog
{ {
id: manualPrinterDialog id: manualPrinterDialog
@ -404,6 +420,26 @@ Cura.MachineAction
text: catalog.i18nc("@action:button", "OK") text: catalog.i18nc("@action:button", "OK")
onClicked: onClicked:
{ {
// Validate the input first
if (!networkingUtil.isValidIP(manualPrinterDialog.addressText))
{
invalidIPAddressMessageDialog.open()
return
}
// if the entered IP address has already been discovered, switch the current item to that item
// and do nothing else.
for (var i = 0; i < manager.foundDevices.length; i++)
{
var device = manager.foundDevices[i]
if (device.address == manualPrinterDialog.addressText)
{
currentItemIndex = i
manualPrinterDialog.hide()
return
}
}
manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText) manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText)
manualPrinterDialog.hide() manualPrinterDialog.hide()
} }

View file

@ -69,6 +69,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
} }

View file

@ -30,6 +30,21 @@ UM.Dialog
OutputDevice.forceSendJob(printer.activePrintJob.key) OutputDevice.forceSendJob(printer.activePrintJob.key)
overrideConfirmationDialog.close() overrideConfirmationDialog.close()
} }
visible:
{
if (!printer || !printer.activePrintJob)
{
return true
}
var canOverride = false
for (var i = 0; i < printer.activePrintJob.configurationChanges.length; i++)
{
var change = printer.activePrintJob.configurationChanges[i]
canOverride = canOverride || change.typeOfChange === "material_change";
}
return canOverride
}
}, },
Button Button
{ {
@ -52,6 +67,7 @@ UM.Dialog
bottomMargin: 56 * screenScaleFactor // TODO: Theme! bottomMargin: 56 * screenScaleFactor // TODO: Theme!
} }
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
renderType: Text.NativeRendering
text: text:
{ {
if (!printer || !printer.activePrintJob) if (!printer || !printer.activePrintJob)

View file

@ -23,6 +23,7 @@ Button
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: base.text text: base.text
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering;
} }
height: width height: width
hoverEnabled: enabled hoverEnabled: enabled

View file

@ -66,6 +66,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -95,6 +96,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
} }

View file

@ -48,5 +48,6 @@ Item
x: Math.round(size * 0.25) x: Math.round(size * 0.25)
y: Math.round(size * 0.15625) y: Math.round(size * 0.15625)
visible: position >= 0 visible: position >= 0
renderType: Text.NativeRendering
} }
} }

View file

@ -40,6 +40,7 @@ Item
width: 240 * screenScaleFactor // TODO: Theme! width: 240 * screenScaleFactor // TODO: Theme!
color: UM.Theme.getColor("monitor_tooltip_text") color: UM.Theme.getColor("monitor_tooltip_text")
font: UM.Theme.getFont("default") font: UM.Theme.getFont("default")
renderType: Text.NativeRendering
} }
} }
} }

View file

@ -71,6 +71,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -98,6 +99,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -143,6 +145,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
Row Row
@ -158,14 +161,9 @@ Item
spacing: 6 // TODO: Theme! spacing: 6 // TODO: Theme!
visible: printJob visible: printJob
Repeater MonitorPrinterPill
{ {
id: compatiblePills text: printJob.configuration.printerType
delegate: MonitorPrinterPill
{
text: modelData
}
model: printJob ? printJob.compatibleMachineFamilies : []
} }
} }
} }
@ -202,6 +200,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
} }

View file

@ -47,6 +47,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
Label Label
{ {
@ -99,5 +100,6 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }

View file

@ -112,6 +112,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: parent.height height: parent.height
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -315,6 +316,7 @@ Item
return "" return ""
} }
visible: text !== "" visible: text !== ""
renderType: Text.NativeRendering
} }
Item Item
@ -356,6 +358,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
Label Label
@ -376,6 +379,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -403,6 +407,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -437,6 +442,7 @@ Item
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
renderType: Text.NativeRendering
} }
implicitHeight: 32 * screenScaleFactor // TODO: Theme! implicitHeight: 32 * screenScaleFactor // TODO: Theme!
implicitWidth: 96 * screenScaleFactor // TODO: Theme! implicitWidth: 96 * screenScaleFactor // TODO: Theme!

View file

@ -43,5 +43,6 @@ Item
text: tagText text: tagText
font.pointSize: 10 // TODO: Theme! font.pointSize: 10 // TODO: Theme!
visible: text !== "" visible: text !== ""
renderType: Text.NativeRendering
} }
} }

View file

@ -29,6 +29,7 @@ Item
color: UM.Theme.getColor("monitor_text_primary") color: UM.Theme.getColor("monitor_text_primary")
font: UM.Theme.getFont("large") font: UM.Theme.getFont("large")
text: catalog.i18nc("@label", "Queued") text: catalog.i18nc("@label", "Queued")
renderType: Text.NativeRendering
} }
Item Item
@ -109,6 +110,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
Label Label
@ -123,6 +125,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
Label Label
@ -137,6 +140,7 @@ Item
// FIXED-LINE-HEIGHT: // FIXED-LINE-HEIGHT:
height: 18 * screenScaleFactor // TODO: Theme! height: 18 * screenScaleFactor // TODO: Theme!
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
renderType: Text.NativeRendering
} }
} }
@ -213,6 +217,7 @@ Item
text: i18n.i18nc("@info", "All jobs are printed.") text: i18n.i18nc("@info", "All jobs are printed.")
color: UM.Theme.getColor("monitor_text_primary") color: UM.Theme.getColor("monitor_text_primary")
font: UM.Theme.getFont("medium") // 14pt, regular font: UM.Theme.getFont("medium") // 14pt, regular
renderType: Text.NativeRendering
} }
Item Item

View file

@ -16,6 +16,7 @@ Button {
text: parent.text text: parent.text
horizontalAlignment: Text.AlignLeft; horizontalAlignment: Text.AlignLeft;
verticalAlignment: Text.AlignVCenter; verticalAlignment: Text.AlignVCenter;
renderType: Text.NativeRendering;
} }
height: visible ? 39 * screenScaleFactor : 0; // TODO: Theme! height: visible ? 39 * screenScaleFactor : 0; // TODO: Theme!
hoverEnabled: true; hoverEnabled: true;

View file

@ -78,6 +78,7 @@ UM.Dialog {
height: 20 * screenScaleFactor; height: 20 * screenScaleFactor;
text: catalog.i18nc("@label", "Printer selection"); text: catalog.i18nc("@label", "Printer selection");
wrapMode: Text.Wrap; wrapMode: Text.Wrap;
renderType: Text.NativeRendering;
} }
ComboBox { ComboBox {

View file

@ -535,6 +535,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {}))) extruder.setMaterial(self._createMaterialOutputModel(extruder_data.get("material", {})))
configuration.setExtruderConfigurations(extruders) configuration.setExtruderConfigurations(extruders)
configuration.setPrinterType(data.get("machine_variant", ""))
print_job.updateConfiguration(configuration) print_job.updateConfiguration(configuration)
print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", [])) print_job.setCompatibleMachineFamilies(data.get("compatible_machine_families", []))
print_job.stateChanged.connect(self._printJobStateChanged) print_job.stateChanged.connect(self._printJobStateChanged)

View file

@ -34,7 +34,10 @@ class DiscoverUM3Action(MachineAction):
self.__additional_components_view = None #type: Optional[QObject] self.__additional_components_view = None #type: Optional[QObject]
CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView) self._application = CuraApplication.getInstance()
self._api = self._application.getCuraAPI()
self._application.engineCreatedSignal.connect(self._createAdditionalComponentsView)
self._last_zero_conf_event_time = time.time() #type: float self._last_zero_conf_event_time = time.time() #type: float
@ -50,7 +53,7 @@ class DiscoverUM3Action(MachineAction):
def startDiscovery(self): def startDiscovery(self):
if not self._network_plugin: if not self._network_plugin:
Logger.log("d", "Starting device discovery.") Logger.log("d", "Starting device discovery.")
self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting") self._network_plugin = self._application.getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged) self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
self.discoveredDevicesChanged.emit() self.discoveredDevicesChanged.emit()
@ -105,72 +108,27 @@ class DiscoverUM3Action(MachineAction):
else: else:
return [] return []
@pyqtSlot(str) @pyqtSlot()
def setGroupName(self, group_name: str) -> None: def refreshConnections(self) -> None:
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "group_name" in meta_data:
previous_connect_group_name = meta_data["group_name"]
global_container_stack.setMetaDataEntry("group_name", group_name)
# Find all the places where there is the same group name and change it accordingly
self._replaceContainersMetadata(key = "group_name", value = previous_connect_group_name, new_value = group_name)
else:
global_container_stack.setMetaDataEntry("group_name", group_name)
# Set the default value for "hidden", which is used when you have a group with multiple types of printers
global_container_stack.setMetaDataEntry("hidden", False)
if self._network_plugin: if self._network_plugin:
# Ensure that the connection states are refreshed.
self._network_plugin.refreshConnections() self._network_plugin.refreshConnections()
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value' # TODO: Improve naming
def _replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None: # TODO: CHANGE TO HOSTNAME
machines = CuraContainerRegistry.getInstance().findContainerStacks(type="machine")
for machine in machines:
if machine.getMetaDataEntry(key) == value:
machine.setMetaDataEntry(key, new_value)
# Associates the currently active machine with the given printer device. The network connection information will be
# stored into the metadata of the currently active machine.
@pyqtSlot(QObject)
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
if self._network_plugin:
self._network_plugin.associateActiveMachineWithPrinterDevice(printer_device)
@pyqtSlot(result = str)
def getStoredKey(self) -> str:
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if global_container_stack:
meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data:
return global_container_stack.getMetaDataEntry("um_network_key")
return ""
@pyqtSlot(result = str) @pyqtSlot(result = str)
def getLastManualEntryKey(self) -> str: def getLastManualEntryKey(self) -> str:
if self._network_plugin: if self._network_plugin:
return self._network_plugin.getLastManualDevice() return self._network_plugin.getLastManualDevice()
return "" return ""
# TODO: Better naming needed. Exists where? On the current machine? On all machines?
# TODO: CHANGE TO HOSTNAME
@pyqtSlot(str, result = bool) @pyqtSlot(str, result = bool)
def existsKey(self, key: str) -> bool: def existsKey(self, key: str) -> bool:
metadata_filter = {"um_network_key": key} metadata_filter = {"um_network_key": key}
containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter) containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter)
return bool(containers) return bool(containers)
@pyqtSlot()
def loadConfigurationFromPrinter(self) -> None:
machine_manager = CuraApplication.getInstance().getMachineManager()
hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
for index in range(len(hotend_ids)):
machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
material_ids = machine_manager.printerOutputDevices[0].materialIds
for index in range(len(material_ids)):
machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])
def _createAdditionalComponentsView(self) -> None: def _createAdditionalComponentsView(self) -> None:
Logger.log("d", "Creating additional ui components for UM3.") Logger.log("d", "Creating additional ui components for UM3.")
@ -179,10 +137,10 @@ class DiscoverUM3Action(MachineAction):
if not plugin_path: if not plugin_path:
return return
path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml") path = os.path.join(plugin_path, "resources/qml/UM3InfoComponents.qml")
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self}) self.__additional_components_view = self._application.createQmlComponent(path, {"manager": self})
if not self.__additional_components_view: if not self.__additional_components_view:
Logger.log("w", "Could not create ui components for UM3.") Logger.log("w", "Could not create ui components for UM3.")
return return
# Create extra components # Create extra components
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton")) self._application.addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))

View file

@ -5,11 +5,11 @@ import os
from queue import Queue from queue import Queue
from threading import Event, Thread from threading import Event, Thread
from time import time from time import time
from typing import Optional, TYPE_CHECKING, Dict from typing import Optional, TYPE_CHECKING, Dict, Callable
from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo from zeroconf import Zeroconf, ServiceBrowser, ServiceStateChange, ServiceInfo
from PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from PyQt5.QtCore import QUrl from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices from PyQt5.QtGui import QDesktopServices
@ -39,6 +39,22 @@ if TYPE_CHECKING:
i18n_catalog = i18nCatalog("cura") i18n_catalog = i18nCatalog("cura")
#
# Represents a request for adding a manual printer. It has the following fields:
# - address: The string of the (IP) address of the manual printer
# - callback: (Optional) Once the HTTP request to the printer to get printer information is done, whether successful
# or not, this callback will be invoked to notify about the result. The callback must have a signature of
# func(success: bool, address: str) -> None
# - network_reply: This is the QNetworkReply instance for this request if the request has been issued and still in
# progress. It is kept here so we can cancel a request when needed.
#
class ManualPrinterRequest:
def __init__(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None:
self.address = address
self.callback = callback
self.network_reply = None # type: Optional["QNetworkReply"]
## This plugin handles the connection detection & creation of output device objects for the UM3 printer. ## This plugin handles the connection detection & creation of output device objects for the UM3 printer.
# Zero-Conf is used to detect printers, which are saved in a dict. # Zero-Conf is used to detect printers, which are saved in a dict.
# If we discover a printer that has the same key as the active machine instance a connection is made. # If we discover a printer that has the same key as the active machine instance a connection is made.
@ -51,11 +67,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self._zero_conf = None self._zero_conf = None
self._zero_conf_browser = None self._zero_conf_browser = None
self._application = CuraApplication.getInstance() self._application = CuraApplication.getInstance()
self._api = self._application.getCuraAPI()
# Create a cloud output device manager that abstracts all cloud connection logic away. # Create a cloud output device manager that abstracts all cloud connection logic away.
self._cloud_output_device_manager = CloudOutputDeviceManager() self._cloud_output_device_manager = CloudOutputDeviceManager()
@ -80,11 +96,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/" self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
# Get list of manual instances from preferences # Get list of manual instances from preferences
self._preferences = CuraApplication.getInstance().getPreferences() self._preferences = self._application.getPreferences()
self._preferences.addPreference("um3networkprinting/manual_instances", self._preferences.addPreference("um3networkprinting/manual_instances",
"") # A comma-separated list of ip adresses or hostnames "") # A comma-separated list of ip adresses or hostnames
self._manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",") manual_instances = self._preferences.getValue("um3networkprinting/manual_instances").split(",")
self._manual_instances = {address: ManualPrinterRequest(address)
for address in manual_instances} # type: Dict[str, ManualPrinterRequest]
# Store the last manual entry key # Store the last manual entry key
self._last_manual_entry_key = "" # type: str self._last_manual_entry_key = "" # type: str
@ -98,7 +116,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True) self._service_changed_request_thread = Thread(target=self._handleOnServiceChangedRequests, daemon=True)
self._service_changed_request_thread.start() self._service_changed_request_thread.start()
self._account = self._application.getCuraAPI().account self._account = self._api.account
# Check if cloud flow is possible when user logs in # Check if cloud flow is possible when user logs in
self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible) self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible)
@ -149,10 +167,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
for address in self._manual_instances: for address in self._manual_instances:
if address: if address:
self.addManualDevice(address) self.addManualDevice(address)
self.resetLastManualDevice() self.resetLastManu
# TODO: CHANGE TO HOSTNAME
def refreshConnections(self): def refreshConnections(self):
active_machine = CuraApplication.getInstance().getGlobalContainerStack() active_machine = self._application.getGlobalContainerStack()
if not active_machine: if not active_machine:
return return
@ -179,14 +198,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
return return
if self._discovered_devices[key].isConnected(): if self._discovered_devices[key].isConnected():
# Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine # Sometimes the status changes after changing the global container and maybe the device doesn't belong to this machine
um_network_key = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("um_network_key") um_network_key = self._application.getGlobalContainerStack().getMetaDataEntry("um_network_key")
if key == um_network_key: if key == um_network_key:
self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key]) self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
self.checkCloudFlowIsPossible(None) self.checkCloudFlowIsPossible(None)
else: else:
self.getOutputDeviceManager().removeOutputDevice(key) self.getOutputDeviceManager().removeOutputDevice(key)
if key.startswith("manual:"):
self.removeManualDeviceSignal.emit(self.getPluginId(), key, self._discovered_devices[key].address)
def stop(self): def stop(self):
if self._zero_conf is not None: if self._zero_conf is not None:
@ -198,7 +215,10 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
# This plugin should always be the fallback option (at least try it): # This plugin should always be the fallback option (at least try it):
return ManualDeviceAdditionAttempt.POSSIBLE return ManualDeviceAdditionAttempt.POSSIBLE
def removeManualDevice(self, key, address = None): def removeManualDevice(self, key: str, address: Optional[str] = None) -> None:
if key not in self._discovered_devices and address is not None:
key = "manual:%s" % address
if key in self._discovered_devices: if key in self._discovered_devices:
if not address: if not address:
address = self._discovered_devices[key].ipAddress address = self._discovered_devices[key].ipAddress
@ -206,15 +226,22 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.resetLastManualDevice() self.resetLastManualDevice()
if address in self._manual_instances: if address in self._manual_instances:
self._manual_instances.remove(address) manual_printer_request = self._manual_instances.pop(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys()))
self.removeManualDeviceSignal.emit(self.getPluginId(), key, address) if manual_printer_request.network_reply is not None:
manual_printer_request.network_reply.abort()
def addManualDevice(self, address): if manual_printer_request.callback is not None:
if address not in self._manual_instances: self._application.callLater(manual_printer_request.callback, False, address)
self._manual_instances.append(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances)) def addManualDevice(self, address: str, callback: Optional[Callable[[bool, str], None]] = None) -> None:
if address in self._manual_instances:
Logger.log("i", "Manual printer with address [%s] has already been added, do nothing", address)
return
self._manual_instances[address] = ManualPrinterRequest(address, callback = callback)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances.keys()))
instance_name = "manual:%s" % address instance_name = "manual:%s" % address
properties = { properties = {
@ -230,7 +257,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._onAddDevice(instance_name, address, properties) self._onAddDevice(instance_name, address, properties)
self._last_manual_entry_key = instance_name self._last_manual_entry_key = instance_name
self._checkManualDevice(address) reply = self._checkManualDevice(address)
self._manual_instances[address].network_reply = reply
def _createMachineFromDiscoveredPrinter(self, key: str) -> None: def _createMachineFromDiscoveredPrinter(self, key: str) -> None:
discovered_device = self._discovered_devices.get(key) discovered_device = self._discovered_devices.get(key)
@ -245,56 +273,22 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
key, group_name, machine_type_id) key, group_name, machine_type_id)
self._application.getMachineManager().addMachine(machine_type_id, group_name) self._application.getMachineManager().addMachine(machine_type_id, group_name)
# connect the new machine to that network printer # connect the new machine to that network printer
self.associateActiveMachineWithPrinterDevice(discovered_device) self._api.machines.addOutputDeviceToCurrentMachine(discovered_device)
# ensure that the connection states are refreshed. # ensure that the connection states are refreshed.
self.refreshConnections() self.refreshConnections()
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None: def _checkManualDevice(self, address: str) -> Optional[QNetworkReply]:
if not printer_device:
return
Logger.log("d", "Attempting to set the network key of the active machine to %s", printer_device.key)
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
if not global_container_stack:
return
meta_data = global_container_stack.getMetaData()
if "um_network_key" in meta_data: # Global stack already had a connection, but it's changed.
old_network_key = meta_data["um_network_key"]
# Since we might have a bunch of hidden stacks, we also need to change it there.
metadata_filter = {"um_network_key": old_network_key}
containers = self._application.getContainerRegistry().findContainerStacks(type = "machine", **metadata_filter)
for container in containers:
container.setMetaDataEntry("um_network_key", printer_device.key)
# Delete old authentication data.
Logger.log("d", "Removing old authentication id %s for device %s",
global_container_stack.getMetaDataEntry("network_authentication_id", None), printer_device.key)
container.removeMetaDataEntry("network_authentication_id")
container.removeMetaDataEntry("network_authentication_key")
# Ensure that these containers do know that they are configured for network connection
container.addConfiguredConnectionType(printer_device.connectionType.value)
else: # Global stack didn't have a connection yet, configure it.
global_container_stack.setMetaDataEntry("um_network_key", printer_device.key)
global_container_stack.addConfiguredConnectionType(printer_device.connectionType.value)
self.refreshConnections()
def _checkManualDevice(self, address: str) -> None:
# Check if a UM3 family device exists at this address. # Check if a UM3 family device exists at this address.
# If a printer responds, it will replace the preliminary printer created above # If a printer responds, it will replace the preliminary printer created above
# origin=manual is for tracking back the origin of the call # origin=manual is for tracking back the origin of the call
url = QUrl("http://" + address + self._api_prefix + "system") url = QUrl("http://" + address + self._api_prefix + "system")
name_request = QNetworkRequest(url) name_request = QNetworkRequest(url)
self._network_manager.get(name_request) return self._network_manager.get(name_request)
## This is the function which handles the above network request's reply when it comes back.
def _onNetworkRequestFinished(self, reply: "QNetworkReply") -> None: def _onNetworkRequestFinished(self, reply: "QNetworkReply") -> None:
reply_url = reply.url().toString() reply_url = reply.url().toString()
@ -319,6 +313,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
Logger.log("e", "Something went wrong converting the JSON.") Logger.log("e", "Something went wrong converting the JSON.")
return return
if address in self._manual_instances:
manual_printer_request = self._manual_instances[address]
manual_printer_request.network_reply = None
if manual_printer_request.callback is not None:
self._application.callLater(manual_printer_request.callback, True, address)
has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
instance_name = "manual:%s" % address instance_name = "manual:%s" % address
properties = { properties = {
@ -362,10 +362,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._onRemoveDevice(instance_name) self._onRemoveDevice(instance_name)
self._onAddDevice(instance_name, address, properties) self._onAddDevice(instance_name, address, properties)
if device and address in self._manual_instances:
self.getOutputDeviceManager().addOutputDevice(device)
self.addManualDeviceSignal.emit(self.getPluginId(), device.getId(), address, properties)
def _onRemoveDevice(self, device_id: str) -> None: def _onRemoveDevice(self, device_id: str) -> None:
device = self._discovered_devices.pop(device_id, None) device = self._discovered_devices.pop(device_id, None)
if device: if device:
@ -401,11 +397,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties) device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
else: else:
device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties) device = LegacyUM3OutputDevice.LegacyUM3OutputDevice(name, address, properties)
self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(address, device.getId(), name, self._createMachineFromDiscoveredPrinter, properties[b"printer_type"].decode("utf-8"), device) self._application.getDiscoveredPrintersModel().addDiscoveredPrinter(
address, device.getId(), properties[b"name"].decode("utf-8"), self._createMachineFromDiscoveredPrinter,
properties[b"printer_type"].decode("utf-8"), device)
self._discovered_devices[device.getId()] = device self._discovered_devices[device.getId()] = device
self.discoveredDevicesChanged.emit() self.discoveredDevicesChanged.emit()
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack() global_container_stack = self._application.getGlobalContainerStack()
if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"): if global_container_stack and device.getId() == global_container_stack.getMetaDataEntry("um_network_key"):
# Ensure that the configured connection type is set. # Ensure that the configured connection type is set.
global_container_stack.addConfiguredConnectionType(device.connectionType.value) global_container_stack.addConfiguredConnectionType(device.connectionType.value)
@ -425,7 +423,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_event.wait(timeout = 5.0) self._service_changed_request_event.wait(timeout = 5.0)
# Stop if the application is shutting down # Stop if the application is shutting down
if CuraApplication.getInstance().isShuttingDown(): if self._application.isShuttingDown():
return return
self._service_changed_request_event.clear() self._service_changed_request_event.clear()

View file

@ -4,6 +4,8 @@
import threading import threading
import time import time
import serial.tools.list_ports import serial.tools.list_ports
from os import environ
from re import search
from PyQt5.QtCore import QObject, pyqtSignal from PyQt5.QtCore import QObject, pyqtSignal
@ -112,6 +114,27 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
port = (port.device, port.description, port.hwid) port = (port.device, port.description, port.hwid)
if only_list_usb and not port[2].startswith("USB"): if only_list_usb and not port[2].startswith("USB"):
continue continue
# To prevent cura from messing with serial ports of other devices,
# filter by regular expressions passed in as environment variables.
# Get possible patterns with python3 -m serial.tools.list_ports -v
# set CURA_DEVICENAMES=USB[1-9] -> e.g. not matching /dev/ttyUSB0
pattern = environ.get('CURA_DEVICENAMES')
if pattern and not search(pattern, port[0]):
continue
# set CURA_DEVICETYPES=CP2102 -> match a type of serial converter
pattern = environ.get('CURA_DEVICETYPES')
if pattern and not search(pattern, port[1]):
continue
# set CURA_DEVICEINFOS=LOCATION=2-1.4 -> match a physical port
# set CURA_DEVICEINFOS=VID:PID=10C4:EA60 -> match a vendor:product
pattern = environ.get('CURA_DEVICEINFOS')
if pattern and not search(pattern, port[2]):
continue
base_list += [port[0]] base_list += [port[0]]
return list(base_list) return list(base_list)

View file

@ -3,6 +3,7 @@
import configparser import configparser
import io import io
import uuid
from typing import Dict, List, Tuple from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade from UM.VersionUpgrade import VersionUpgrade
@ -18,6 +19,7 @@ _renamed_quality_profiles = {
"gmax15plus_pla_very_thick": "gmax15plus_global_very_thick" "gmax15plus_pla_very_thick": "gmax15plus_global_very_thick"
} # type: Dict[str, str] } # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 4.0 to the ## Upgrades configurations from the state they were in at version 4.0 to the
# state they should be in at version 4.1. # state they should be in at version 4.1.
class VersionUpgrade40to41(VersionUpgrade): class VersionUpgrade40to41(VersionUpgrade):
@ -95,6 +97,13 @@ class VersionUpgrade40to41(VersionUpgrade):
if parser["containers"]["4"] in _renamed_quality_profiles: if parser["containers"]["4"] in _renamed_quality_profiles:
parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]] parser["containers"]["4"] = _renamed_quality_profiles[parser["containers"]["4"]]
# Assign a GlobalStack to a unique group_id. If the GlobalStack has a UM network connection, use the UM network
# key as the group_id.
if "um_network_key" in parser["metadata"]:
parser["metadata"]["group_id"] = parser["metadata"]["um_network_key"]
elif "group_id" not in parser["metadata"]:
parser["metadata"]["group_id"] = str(uuid.uuid4())
result = io.StringIO() result = io.StringIO()
parser.write(result) parser.write(result)
return [filename], [result.getvalue()] return [filename], [result.getvalue()]

View file

@ -10,21 +10,21 @@ from UM.Math.Color import Color
from UM.PluginRegistry import PluginRegistry from UM.PluginRegistry import PluginRegistry
from UM.Platform import Platform from UM.Platform import Platform
from UM.Event import Event from UM.Event import Event
from UM.View.View import View
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.View.RenderBatch import RenderBatch from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL from UM.View.GL.OpenGL import OpenGL
from cura.CuraApplication import CuraApplication from cura.CuraApplication import CuraApplication
from cura.CuraView import CuraView
from cura.Scene.ConvexHullNode import ConvexHullNode from cura.Scene.ConvexHullNode import ConvexHullNode
from . import XRayPass from . import XRayPass
## View used to display a see-through version of objects with errors highlighted. ## View used to display a see-through version of objects with errors highlighted.
class XRayView(View): class XRayView(CuraView):
def __init__(self): def __init__(self):
super().__init__() super().__init__(parent = None, use_empty_menu_placeholder = True)
self._xray_shader = None self._xray_shader = None
self._xray_pass = None self._xray_pass = None

View file

@ -144,7 +144,7 @@ class XmlMaterialProfile(InstanceContainer):
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file # setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
if ignored_metadata_keys is None: if ignored_metadata_keys is None:
ignored_metadata_keys = set() ignored_metadata_keys = set()
ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name"} ignored_metadata_keys |= {"setting_version", "definition", "status", "variant", "type", "base_file", "approximate_diameter", "id", "container_type", "name", "compatible"}
# remove the keys that we want to ignore in the metadata # remove the keys that we want to ignore in the metadata
for key in ignored_metadata_keys: for key in ignored_metadata_keys:
if key in metadata: if key in metadata:
@ -1179,6 +1179,7 @@ class XmlMaterialProfile(InstanceContainer):
"adhesion tendency": "material_adhesion_tendency", "adhesion tendency": "material_adhesion_tendency",
"surface energy": "material_surface_energy", "surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage", "shrinkage percentage": "material_shrinkage_percentage",
"build volume temperature": "build_volume_temperature",
} }
__unmapped_settings = [ __unmapped_settings = [
"hardware compatible", "hardware compatible",

View file

@ -33,6 +33,23 @@
} }
} }
}, },
"AMFReader": {
"package_info": {
"package_id": "AMFReader",
"package_type": "plugin",
"display_name": "AMF Reader",
"description": "Provides support for reading AMF files.",
"package_version": "1.0.0",
"sdk_version": "6.0.0",
"website": "https://ultimaker.com",
"author": {
"author_id": "fieldOfView",
"display_name": "fieldOfView",
"email": "plugins@ultimaker.com",
"website": "https://ultimaker.com"
}
}
},
"CuraDrive": { "CuraDrive": {
"package_info": { "package_info": {
"package_id": "CuraDrive", "package_id": "CuraDrive",

View file

@ -38,7 +38,7 @@
"speed_wall": { "value": "speed_print * 0.7" }, "speed_wall": { "value": "speed_print * 0.7" },
"speed_topbottom": { "value": "speed_print * 0.7" }, "speed_topbottom": { "value": "speed_print * 0.7" },
"speed_layer_0": { "value": "speed_print * 0.7" }, "speed_layer_0": { "value": "speed_print * 0.7" },
"gantry_height": { "default_value": 0 }, "gantry_height": { "value": "0" },
"retraction_speed": { "default_value" : 10 }, "retraction_speed": { "default_value" : 10 },
"retraction_amount": { "default_value" : 2.5 }, "retraction_amount": { "default_value" : 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" }, "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },

View file

@ -45,7 +45,7 @@
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 30 "value": "30"
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{speed_travel} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{speed_travel}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" }, "default_value": ";Sliced at: {day} {date} {time}\nM104 S{material_print_temperature} ;set temperatures\nM140 S{material_bed_temperature}\nM109 S{material_print_temperature} ;wait for temperatures\nM190 S{material_bed_temperature}\nG21 ;metric values\nG90 ;absolute positioning\nM82 ;set extruder to absolute mode\nM107 ;start with the fan off\nG28 Z0 ;move Z to min endstops\nG28 X0 Y0 ;move X/Y to min endstops\nG29 ;Auto Level\nG1 Z0.6 F{speed_travel} ;move the Nozzle near the Bed\nG92 E0\nG1 Y0 ;zero the extruded length\nG1 X10 E30 F500 ;printing a Line from right to left\nG92 E0 ;zero the extruded length again\nG1 Z2\nG1 F{speed_travel}\nM117 Printing...;Put printing message on LCD screen\nM150 R255 U255 B255 P4 ;Change LED Color to white" },

View file

@ -8,12 +8,12 @@
"author": "TheUltimakerCommunity", "author": "TheUltimakerCommunity",
"manufacturer": "Foehnsturm", "manufacturer": "Foehnsturm",
"category": "Other", "category": "Other",
"weight": 0,
"has_variants": true, "has_variants": true,
"has_materials": true, "has_materials": true,
"has_machine_materials": false, "has_machine_materials": false,
"has_machine_quality": false, "has_machine_quality": false,
"has_variant_materials": false, "has_variant_materials": false,
"weight": 2,
"file_formats": "text/x-gcode", "file_formats": "text/x-gcode",
"icon": "icon_ultimaker.png", "icon": "icon_ultimaker.png",
"platform": "ultimaker2_platform.obj", "platform": "ultimaker2_platform.obj",
@ -37,7 +37,7 @@
"default_value": 203 "default_value": 203
}, },
"gantry_height": { "gantry_height": {
"default_value": 52 "value": "52"
}, },
"machine_center_is_zero": { "machine_center_is_zero": {
"default_value": false "default_value": false

View file

@ -39,7 +39,7 @@
"default_value": false "default_value": false
}, },
"gantry_height": { "gantry_height": {
"default_value": 10 "value": "10"
}, },
"machine_gcode_flavor": { "machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)" "default_value": "RepRap (Marlin/Sprinter)"

View file

@ -36,7 +36,7 @@
"retraction_enable": { "default_value": true }, "retraction_enable": { "default_value": true },
"retraction_amount": { "default_value": 5 }, "retraction_amount": { "default_value": 5 },
"retraction_speed": { "default_value": 45 }, "retraction_speed": { "default_value": 45 },
"gantry_height": { "default_value": 25 }, "gantry_height": { "value": "25" },
"machine_width": { "default_value": 220 }, "machine_width": { "default_value": 220 },
"machine_height": { "default_value": 250 }, "machine_height": { "default_value": 250 },
"machine_depth": { "default_value": 220 }, "machine_depth": { "default_value": 220 },

View file

@ -30,7 +30,7 @@
"machine_height": { "default_value": 133 }, "machine_height": { "default_value": 133 },
"machine_depth": { "default_value": 100 }, "machine_depth": { "default_value": 100 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55"},
"retraction_amount": { "default_value": 1.5 }, "retraction_amount": { "default_value": 1.5 },
"support_enable": { "default_value": true}, "support_enable": { "default_value": true},
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {

View file

@ -30,7 +30,7 @@
"machine_height": { "default_value": 170 }, "machine_height": { "default_value": 170 },
"machine_depth": { "default_value": 160 }, "machine_depth": { "default_value": 160 },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55"},
"retraction_amount": { "default_value": 1.5 }, "retraction_amount": { "default_value": 1.5 },
"support_enable": { "default_value": true}, "support_enable": { "default_value": true},
"machine_head_with_fans_polygon": { "machine_head_with_fans_polygon": {

View file

@ -0,0 +1,45 @@
{
"version": 2,
"name": "Anet A6",
"inherits": "fdmprinter",
"metadata": {
"visible": true,
"author": "Mark",
"manufacturer": "Anet",
"file_formats": "text/x-gcode",
"platform": "aneta6_platform.stl",
"platform_offset": [0, -3.4, 0],
"machine_extruder_trains":
{
"0": "anet_a6_extruder_0"
}
},
"overrides": {
"machine_name": { "default_value": "Anet A6" },
"machine_heated_bed": {
"default_value": true
},
"machine_width": {
"default_value": 220
},
"machine_height": {
"default_value": 250
},
"machine_depth": {
"default_value": 220
},
"machine_center_is_zero": {
"default_value": false
},
"gantry_height": {
"value": "55"
},
"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\nM84 ;steppers off\nM0 S12 ;wait 12 seconds\nM17 ;turn steppers on\nG1 Z10.0 F300 ;move the platform down 10mm\nG92 E0 ;zero the extruded length\nG1 F200 E8 ;extrude 8mm of feed stock\nG92 E0 ;zero the extruded length again\nM0 S5 ;wait 5 seconds\nG1 F9000\nM117 Printing..."
},
"machine_end_gcode": {
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+4 E-5 X-20 Y-20 F9000 ;move Z up a bit and retract filament even more\nG28 X0 Y0 ;move X/Y to min endstops, so the head is out of the way\nG1 Y210 F9000 ;move out to get part off\nM84 ;steppers off\nG90 ;absolute positioning"
}
}
}

View file

@ -50,7 +50,7 @@
"jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" }, "jerk_wall": { "value": "math.ceil(jerk_print * 10 / 25)" },
"jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" }, "jerk_wall_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"gantry_height": { "default_value": 25.0 }, "gantry_height": { "value": "25.0" },
"skin_overlap": { "value": "10" }, "skin_overlap": { "value": "10" },
"acceleration_enabled": { "value": "True" }, "acceleration_enabled": { "value": "True" },

View file

@ -52,7 +52,7 @@
}, },
"gantry_height": "gantry_height":
{ {
"default_value": 35 "value": "35"
}, },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {

View file

@ -46,7 +46,7 @@
}, },
"gantry_height": "gantry_height":
{ {
"default_value": 0 "value": "0"
}, },
"machine_gcode_flavor": "machine_gcode_flavor":
{ {

View file

@ -64,7 +64,7 @@
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 12 "value": "12"
}, },
"machine_use_extruder_offset_to_offset_coords": { "machine_use_extruder_offset_to_offset_coords": {
"default_value": true "default_value": true

View file

@ -93,7 +93,7 @@
"machine_nozzle_heat_up_speed": { "default_value": 2 }, "machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 }, "machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] }, "machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 }, "machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 }, "machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 }, "machine_max_feedrate_z": { "default_value": 40 },

View file

@ -93,7 +93,7 @@
"machine_nozzle_heat_up_speed": { "default_value": 2 }, "machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 }, "machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] }, "machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 }, "machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 }, "machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 }, "machine_max_feedrate_z": { "default_value": 40 },

View file

@ -92,7 +92,7 @@
"machine_nozzle_heat_up_speed": { "default_value": 2 }, "machine_nozzle_heat_up_speed": { "default_value": 2 },
"machine_nozzle_cool_down_speed": { "default_value": 2 }, "machine_nozzle_cool_down_speed": { "default_value": 2 },
"machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] }, "machine_head_polygon": { "default_value": [[-75, -18],[-75, 35],[18, 35],[18, -18]] },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 }, "machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 }, "machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 }, "machine_max_feedrate_z": { "default_value": 40 },

View file

@ -35,7 +35,7 @@
"machine_extruder_count": { "default_value": 2 }, "machine_extruder_count": { "default_value": 2 },
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_center_is_zero": { "default_value": false }, "machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 35 }, "gantry_height": { "value": "35" },
"machine_height": { "default_value": 400 }, "machine_height": { "default_value": 400 },
"machine_depth": { "default_value": 270 }, "machine_depth": { "default_value": 270 },
"machine_width": { "default_value": 430 }, "machine_width": { "default_value": 430 },

View file

@ -39,7 +39,7 @@
"default_value": false "default_value": false
}, },
"gantry_height": { "gantry_height": {
"default_value": 10 "value": "10"
}, },
"machine_gcode_flavor": { "machine_gcode_flavor": {
"default_value": "RepRap (Marlin/Sprinter)" "default_value": "RepRap (Marlin/Sprinter)"

View file

@ -30,7 +30,7 @@
"retraction_amount": { "default_value": 3 }, "retraction_amount": { "default_value": 3 },
"retraction_speed": { "default_value": 70}, "retraction_speed": { "default_value": 70},
"adhesion_type": { "default_value": "skirt" }, "adhesion_type": { "default_value": "skirt" },
"gantry_height": { "default_value": 30 }, "gantry_height": { "value": "30" },
"speed_print": { "default_value": 60 }, "speed_print": { "default_value": 60 },
"speed_travel": { "default_value": 120 }, "speed_travel": { "default_value": 120 },
"machine_max_acceleration_x": { "default_value": 500 }, "machine_max_acceleration_x": { "default_value": 500 },

View file

@ -71,7 +71,7 @@
"default_value": true "default_value": true
}, },
"gantry_height": { "gantry_height": {
"default_value": 30 "value": "30"
}, },
"acceleration_enabled": { "acceleration_enabled": {
"default_value": true "default_value": true

View file

@ -31,7 +31,7 @@
"default_value": true "default_value": true
}, },
"gantry_height": { "gantry_height": {
"default_value": 30 "value": "30"
}, },
"machine_head_polygon": { "machine_head_polygon": {
"default_value": [ "default_value": [
@ -59,6 +59,9 @@
"jerk_travel": { "jerk_travel": {
"value": "jerk_print" "value": "jerk_print"
}, },
"machine_max_jerk_z": {
"default_value": 0.3
},
"layer_height_0": { "layer_height_0": {
"default_value": 0.2 "default_value": 0.2
}, },
@ -69,10 +72,10 @@
"default_value": 0.6 "default_value": 0.6
}, },
"retraction_amount": { "retraction_amount": {
"default_value": 5 "default_value": 6
}, },
"retraction_speed": { "retraction_speed": {
"default_value": 40 "default_value": 25
}, },
"cool_min_layer_time": { "cool_min_layer_time": {
"default_value": 10 "default_value": 10

View file

@ -25,7 +25,7 @@
"machine_heated_bed": { "default_value": true }, "machine_heated_bed": { "default_value": true },
"machine_shape": { "default_value": "elliptic" }, "machine_shape": { "default_value": "elliptic" },
"machine_max_feedrate_z": { "default_value": 300 }, "machine_max_feedrate_z": { "default_value": 300 },
"gantry_height": {"default_value": 43}, "gantry_height": {"value": "43"},
"layer_height": { "default_value": 0.1 }, "layer_height": { "default_value": 0.1 },
"relative_extrusion": { "default_value": false }, "relative_extrusion": { "default_value": false },
"retraction_combing": { "default_value": "off" }, "retraction_combing": { "default_value": "off" },

View file

@ -38,7 +38,7 @@
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 10 "value": "10"
}, },
"machine_start_gcode": { "machine_start_gcode": {
"default_value": ";Gcode by Cura\nG90\nM106 S255\nG28 X Y\nG1 X50\nM109 R90\nG28\nM104 S{material_print_temperature_layer_0}\nG29\nM107\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E10\nG92 E0\nG1 Z3\nG1 F6000\n" "default_value": ";Gcode by Cura\nG90\nM106 S255\nG28 X Y\nG1 X50\nM109 R90\nG28\nM104 S{material_print_temperature_layer_0}\nG29\nM107\nG1 X100 Y20 F3000\nG1 Z0.5\nM109 S{material_print_temperature_layer_0}\nM82\nG92 E0\nG1 F200 E10\nG92 E0\nG1 Z3\nG1 F6000\n"

View file

@ -38,7 +38,7 @@
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 0 "value": "0"
}, },
"machine_shape": { "machine_shape": {
"default_value": "elliptic" "default_value": "elliptic"

View file

@ -38,7 +38,7 @@
] ]
}, },
"gantry_height": { "gantry_height": {
"default_value": 0 "value": "0"
}, },
"machine_shape": { "machine_shape": {
"default_value": "elliptic" "default_value": "elliptic"

View file

@ -28,7 +28,7 @@
"machine_end_gcode": { "machine_end_gcode": {
"default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM300 S2 ;FAB bep bep (end print)" "default_value": "M104 S0 ;extruder heater off\nM140 S0 ;heated bed heater off (if you have it)\nG91 ;relative positioning\nG1 E-1 F300 ;retract the filament a bit before lifting the nozzle, to release some of the pressure\nG1 Z+0.5 E-3 X+5 Y+5 F5000 ;move Z up a bit and retract filament even more\n;end of the print\nM84 ;steppers off\nG90 ;absolute positioning\nM300 S2 ;FAB bep bep (end print)"
}, },
"gantry_height": { "default_value": 55 }, "gantry_height": { "value": "55" },
"machine_width": { "default_value": 214 }, "machine_width": { "default_value": 214 },
"machine_height": { "default_value": 241.5 }, "machine_height": { "default_value": 241.5 },
"machine_depth": { "default_value": 234 }, "machine_depth": { "default_value": 234 },

View file

@ -17,7 +17,8 @@
{ {
"0": "fdmextruder" "0": "fdmextruder"
}, },
"supports_usb_connection": true "supports_usb_connection": true,
"supports_network_connection": false
}, },
"settings": "settings":
{ {
@ -227,7 +228,7 @@
}, },
"extruders_enabled_count": "extruders_enabled_count":
{ {
"label": "Number of Extruders that are enabled", "label": "Number of Extruders That Are Enabled",
"description": "Number of extruder trains that are enabled; automatically set in software", "description": "Number of extruder trains that are enabled; automatically set in software",
"value": "machine_extruder_count", "value": "machine_extruder_count",
"default_value": 1, "default_value": 1,
@ -240,7 +241,7 @@
}, },
"machine_nozzle_tip_outer_diameter": "machine_nozzle_tip_outer_diameter":
{ {
"label": "Outer nozzle diameter", "label": "Outer Nozzle Diameter",
"description": "The outer diameter of the tip of the nozzle.", "description": "The outer diameter of the tip of the nozzle.",
"unit": "mm", "unit": "mm",
"default_value": 1, "default_value": 1,
@ -252,7 +253,7 @@
}, },
"machine_nozzle_head_distance": "machine_nozzle_head_distance":
{ {
"label": "Nozzle length", "label": "Nozzle Length",
"description": "The height difference between the tip of the nozzle and the lowest part of the print head.", "description": "The height difference between the tip of the nozzle and the lowest part of the print head.",
"unit": "mm", "unit": "mm",
"default_value": 3, "default_value": 3,
@ -263,7 +264,7 @@
}, },
"machine_nozzle_expansion_angle": "machine_nozzle_expansion_angle":
{ {
"label": "Nozzle angle", "label": "Nozzle Angle",
"description": "The angle between the horizontal plane and the conical part right above the tip of the nozzle.", "description": "The angle between the horizontal plane and the conical part right above the tip of the nozzle.",
"unit": "°", "unit": "°",
"type": "int", "type": "int",
@ -276,7 +277,7 @@
}, },
"machine_heat_zone_length": "machine_heat_zone_length":
{ {
"label": "Heat zone length", "label": "Heat Zone Length",
"description": "The distance from the tip of the nozzle in which heat from the nozzle is transferred to the filament.", "description": "The distance from the tip of the nozzle in which heat from the nozzle is transferred to the filament.",
"unit": "mm", "unit": "mm",
"default_value": 16, "default_value": 16,
@ -310,7 +311,7 @@
}, },
"machine_nozzle_heat_up_speed": "machine_nozzle_heat_up_speed":
{ {
"label": "Heat up speed", "label": "Heat Up Speed",
"description": "The speed (°C/s) by which the nozzle heats up averaged over the window of normal printing temperatures and the standby temperature.", "description": "The speed (°C/s) by which the nozzle heats up averaged over the window of normal printing temperatures and the standby temperature.",
"default_value": 2.0, "default_value": 2.0,
"unit": "°C/s", "unit": "°C/s",
@ -321,7 +322,7 @@
}, },
"machine_nozzle_cool_down_speed": "machine_nozzle_cool_down_speed":
{ {
"label": "Cool down speed", "label": "Cool Down Speed",
"description": "The speed (°C/s) by which the nozzle cools down averaged over the window of normal printing temperatures and the standby temperature.", "description": "The speed (°C/s) by which the nozzle cools down averaged over the window of normal printing temperatures and the standby temperature.",
"default_value": 2.0, "default_value": 2.0,
"unit": "°C/s", "unit": "°C/s",
@ -343,7 +344,7 @@
}, },
"machine_gcode_flavor": "machine_gcode_flavor":
{ {
"label": "G-code flavour", "label": "G-code Flavour",
"description": "The type of g-code to be generated.", "description": "The type of g-code to be generated.",
"type": "enum", "type": "enum",
"options": "options":
@ -376,7 +377,7 @@
}, },
"machine_disallowed_areas": "machine_disallowed_areas":
{ {
"label": "Disallowed areas", "label": "Disallowed Areas",
"description": "A list of polygons with areas the print head is not allowed to enter.", "description": "A list of polygons with areas the print head is not allowed to enter.",
"type": "polygons", "type": "polygons",
"default_value": "default_value":
@ -400,7 +401,7 @@
}, },
"machine_head_polygon": "machine_head_polygon":
{ {
"label": "Machine head polygon", "label": "Machine Head Polygon",
"description": "A 2D silhouette of the print head (fan caps excluded).", "description": "A 2D silhouette of the print head (fan caps excluded).",
"type": "polygon", "type": "polygon",
"default_value": "default_value":
@ -428,7 +429,7 @@
}, },
"machine_head_with_fans_polygon": "machine_head_with_fans_polygon":
{ {
"label": "Machine head & Fan polygon", "label": "Machine Head & Fan Polygon",
"description": "A 2D silhouette of the print head (fan caps included).", "description": "A 2D silhouette of the print head (fan caps included).",
"type": "polygon", "type": "polygon",
"default_value": "default_value":
@ -456,7 +457,7 @@
}, },
"gantry_height": "gantry_height":
{ {
"label": "Gantry height", "label": "Gantry Height",
"description": "The height difference between the tip of the nozzle and the gantry system (X and Y axes).", "description": "The height difference between the tip of the nozzle and the gantry system (X and Y axes).",
"default_value": 99999999999, "default_value": 99999999999,
"value": "machine_height", "value": "machine_height",
@ -488,7 +489,7 @@
}, },
"machine_use_extruder_offset_to_offset_coords": "machine_use_extruder_offset_to_offset_coords":
{ {
"label": "Offset With Extruder", "label": "Offset with Extruder",
"description": "Apply the extruder offset to the coordinate system.", "description": "Apply the extruder offset to the coordinate system.",
"type": "bool", "type": "bool",
"default_value": true, "default_value": true,
@ -523,7 +524,7 @@
"description": "The maximum speed for the motor of the X-direction.", "description": "The maximum speed for the motor of the X-direction.",
"unit": "mm/s", "unit": "mm/s",
"type": "float", "type": "float",
"default_value": 500, "default_value": 299792458000,
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
@ -534,7 +535,7 @@
"description": "The maximum speed for the motor of the Y-direction.", "description": "The maximum speed for the motor of the Y-direction.",
"unit": "mm/s", "unit": "mm/s",
"type": "float", "type": "float",
"default_value": 500, "default_value": 299792458000,
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
@ -545,7 +546,7 @@
"description": "The maximum speed for the motor of the Z-direction.", "description": "The maximum speed for the motor of the Z-direction.",
"unit": "mm/s", "unit": "mm/s",
"type": "float", "type": "float",
"default_value": 5, "default_value": 299792458000,
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false, "settable_per_extruder": false,
"settable_per_meshgroup": false "settable_per_meshgroup": false
@ -1432,7 +1433,8 @@
"z_seam_corner_none": "None", "z_seam_corner_none": "None",
"z_seam_corner_inner": "Hide Seam", "z_seam_corner_inner": "Hide Seam",
"z_seam_corner_outer": "Expose Seam", "z_seam_corner_outer": "Expose Seam",
"z_seam_corner_any": "Hide or Expose Seam" "z_seam_corner_any": "Hide or Expose Seam",
"z_seam_corner_weighted": "Smart Hiding"
}, },
"default_value": "z_seam_corner_inner", "default_value": "z_seam_corner_inner",
"enabled": "z_seam_type != 'random'", "enabled": "z_seam_type != 'random'",
@ -2055,6 +2057,21 @@
"settable_per_mesh": false, "settable_per_mesh": false,
"minimum_value": "-273.15" "minimum_value": "-273.15"
}, },
"build_volume_temperature":
{
"label": "Build Volume Temperature",
"description": "The temperature used for build volume. If this is 0, the build volume temperature will not be adjusted.",
"unit": "°C",
"type": "float",
"default_value": 35,
"resolve": "min(extruderValues('build_volume_temperature'))",
"minimum_value": "-273.15",
"minimum_value_warning": "0",
"maximum_value_warning": "285",
"enabled": true,
"settable_per_mesh": false,
"settable_per_extruder": true
},
"material_print_temperature": "material_print_temperature":
{ {
"label": "Printing Temperature", "label": "Printing Temperature",
@ -3527,6 +3544,20 @@
"enabled": "retraction_hop_enabled and extruders_enabled_count > 1", "enabled": "retraction_hop_enabled and extruders_enabled_count > 1",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": true "settable_per_extruder": true
},
"retraction_hop_after_extruder_switch_height":
{
"label": "Z Hop After Extruder Switch Height",
"description": "The height difference when performing a Z Hop after extruder switch.",
"unit": "mm",
"type": "float",
"default_value": 1,
"value": "retraction_hop",
"minimum_value_warning": "0",
"maximum_value_warning": "10",
"enabled": "retraction_enable and retraction_hop_after_extruder_switch",
"settable_per_mesh": false,
"settable_per_extruder": true
} }
} }
}, },
@ -5219,7 +5250,7 @@
"type": "bool", "type": "bool",
"enabled": "extruders_enabled_count > 1", "enabled": "extruders_enabled_count > 1",
"default_value": false, "default_value": false,
"resolve": "any(extruderValues('prime_tower_enable'))", "resolve": "(extruders_enabled_count > 1) and any(extruderValues('prime_tower_enable')) or (adhesion_type in ('none', 'skirt'))",
"settable_per_mesh": false, "settable_per_mesh": false,
"settable_per_extruder": false "settable_per_extruder": false
}, },
@ -6659,7 +6690,7 @@
}, },
"adaptive_layer_height_enabled": "adaptive_layer_height_enabled":
{ {
"label": "Use adaptive layers", "label": "Use Adaptive Layers",
"description": "Adaptive layers computes the layer heights depending on the shape of the model.", "description": "Adaptive layers computes the layer heights depending on the shape of the model.",
"type": "bool", "type": "bool",
"default_value": false, "default_value": false,
@ -6669,7 +6700,7 @@
}, },
"adaptive_layer_height_variation": "adaptive_layer_height_variation":
{ {
"label": "Adaptive layers maximum variation", "label": "Adaptive Layers Maximum Variation",
"description": "The maximum allowed height different from the base layer height.", "description": "The maximum allowed height different from the base layer height.",
"type": "float", "type": "float",
"enabled": "adaptive_layer_height_enabled", "enabled": "adaptive_layer_height_enabled",
@ -6681,7 +6712,7 @@
}, },
"adaptive_layer_height_variation_step": "adaptive_layer_height_variation_step":
{ {
"label": "Adaptive layers variation step size", "label": "Adaptive Layers Variation Step Size",
"description": "The difference in height of the next layer height compared to the previous one.", "description": "The difference in height of the next layer height compared to the previous one.",
"type": "float", "type": "float",
"enabled": "adaptive_layer_height_enabled", "enabled": "adaptive_layer_height_enabled",
@ -6694,7 +6725,7 @@
}, },
"adaptive_layer_height_threshold": "adaptive_layer_height_threshold":
{ {
"label": "Adaptive layers threshold", "label": "Adaptive Layers Threshold",
"description": "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer.", "description": "Threshold whether to use a smaller layer or not. This number is compared to the tan of the steepest slope in a layer.",
"type": "float", "type": "float",
"enabled": "adaptive_layer_height_enabled", "enabled": "adaptive_layer_height_enabled",
@ -7165,41 +7196,47 @@
} }
} }
}, },
"command_line_settings": { "command_line_settings":
{
"label": "Command Line Settings", "label": "Command Line Settings",
"description": "Settings which are only used if CuraEngine isn't called from the Cura frontend.", "description": "Settings which are only used if CuraEngine isn't called from the Cura frontend.",
"type": "category", "type": "category",
"enabled": false, "enabled": false,
"children": { "children": {
"center_object": { "center_object":
{
"description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.", "description": "Whether to center the object on the middle of the build platform (0,0), instead of using the coordinate system in which the object was saved.",
"type": "bool", "type": "bool",
"label": "Center Object", "label": "Center Object",
"default_value": false, "default_value": false,
"enabled": false "enabled": false
}, },
"mesh_position_x": { "mesh_position_x":
{
"description": "Offset applied to the object in the x direction.", "description": "Offset applied to the object in the x direction.",
"type": "float", "type": "float",
"label": "Mesh Position X", "label": "Mesh Position X",
"default_value": 0, "default_value": 0,
"enabled": false "enabled": false
}, },
"mesh_position_y": { "mesh_position_y":
{
"description": "Offset applied to the object in the y direction.", "description": "Offset applied to the object in the y direction.",
"type": "float", "type": "float",
"label": "Mesh Position Y", "label": "Mesh Position Y",
"default_value": 0, "default_value": 0,
"enabled": false "enabled": false
}, },
"mesh_position_z": { "mesh_position_z":
{
"description": "Offset applied to the object in the z direction. With this you can perform what was used to be called 'Object Sink'.", "description": "Offset applied to the object in the z direction. With this you can perform what was used to be called 'Object Sink'.",
"type": "float", "type": "float",
"label": "Mesh Position Z", "label": "Mesh Position Z",
"default_value": 0, "default_value": 0,
"enabled": false "enabled": false
}, },
"mesh_rotation_matrix": { "mesh_rotation_matrix":
{
"label": "Mesh Rotation Matrix", "label": "Mesh Rotation Matrix",
"description": "Transformation matrix to be applied to the model when loading it from file.", "description": "Transformation matrix to be applied to the model when loading it from file.",
"type": "str", "type": "str",

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