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):
# Signal emitted when user logged in or out.
loginStateChanged = pyqtSignal(bool)
accessTokenChanged = pyqtSignal()
def __init__(self, application: "CuraApplication", parent = None) -> None:
super().__init__(parent)
@ -59,8 +60,12 @@ class Account(QObject):
self._authorization_service.initialize(self._application.getPreferences())
self._authorization_service.onAuthStateChanged.connect(self._onLoginStateChanged)
self._authorization_service.onAuthenticationError.connect(self._onLoginStateChanged)
self._authorization_service.accessTokenChanged.connect(self._onAccessTokenChanged)
self._authorization_service.loadAuthDataFromPreferences()
def _onAccessTokenChanged(self):
self.accessTokenChanged.emit()
## Returns a boolean indicating whether the given authentication is applied against staging or not.
@property
def is_staging(self) -> bool:
@ -105,7 +110,7 @@ class Account(QObject):
return None
return user_profile.profile_image_url
@pyqtProperty(str, notify=loginStateChanged)
@pyqtProperty(str, notify=accessTokenChanged)
def accessToken(self) -> Optional[str]:
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.Interface import Interface
from cura.API.Machines import Machines
from cura.API.Account import Account
if TYPE_CHECKING:
@ -44,6 +45,9 @@ class CuraAPI(QObject):
# Backups API
self._backups = Backups(self._application)
# Machines API
self._machines = Machines(self._application)
# Interface API
self._interface = Interface(self._application)
@ -58,6 +62,10 @@ class CuraAPI(QObject):
def backups(self) -> "Backups":
return self._backups
@pyqtProperty(QObject)
def machines(self) -> "Machines":
return self._machines
@property
def interface(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.setSingleShot(True)
self._enabled = True
self._saving = False
def initialize(self):
@ -32,6 +33,13 @@ class AutoSave:
if not self._saving:
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):
if self._global_stack:
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
# restoring a back-up.
def _disableAutoSave(self) -> None:
self._application.setSaveDataEnabled(False)
self._application.getAutoSave().setEnabled(False)
## Re-enable auto-save after we're done.
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.Resources import Resources
from UM.Preferences import Preferences
from UM.Qt.Bindings import MainWindow
from UM.Qt.QtApplication import QtApplication # The class we're inheriting from.
import UM.Util
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.WhatsNewPagesModel import WhatsNewPagesModel
from cura.Utils.NetworkingUtil import NetworkingUtil
from .SingleInstance import SingleInstance
from .AutoSave import AutoSave
from . import PlatformPhysics
@ -216,7 +219,7 @@ class CuraApplication(QtApplication):
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._welcome_pages_model = WelcomePagesModel(self, parent = self)
self._add_printer_pages_model = AddPrinterPagesModel(self, parent = self)
@ -258,7 +261,6 @@ class CuraApplication(QtApplication):
# Backups
self._auto_save = None
self._save_data_enabled = True
from cura.Settings.CuraContainerRegistry import CuraContainerRegistry
self._container_registry_class = CuraContainerRegistry
@ -523,6 +525,10 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("view/settings_list_height", 400)
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/material_settings", "{}")
@ -669,12 +675,9 @@ class CuraApplication(QtApplication):
self._message_box_callback = None
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.
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.
return
ContainerRegistry.getInstance().saveDirtyContainers()
@ -1027,6 +1030,8 @@ class CuraApplication(QtApplication):
qmlRegisterSingletonType(SimpleModeSettingsManager, "Cura", 1, 0, "SimpleModeSettingsManager", self.getSimpleModeSettingsManager)
qmlRegisterSingletonType(MachineActionManager.MachineActionManager, "Cura", 1, 0, "MachineActionManager", self.getMachineActionManager)
qmlRegisterType(NetworkingUtil, "Cura", 1, 5, "NetworkingUtil")
qmlRegisterType(WelcomePagesModel, "Cura", 1, 0, "WelcomePagesModel")
qmlRegisterType(WhatsNewPagesModel, "Cura", 1, 0, "WhatsNewPagesModel")
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
show_whatsnew_only = has_active_machine and has_app_just_upgraded
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 UM.Resources import Resources
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
# 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
# to actually do something with this.
class CuraView(View):
def __init__(self, parent = None) -> None:
def __init__(self, parent = None, use_empty_menu_placeholder: bool = False) -> None:
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)
def mainComponent(self) -> QUrl:
return self.getDisplayComponent("main")
@pyqtProperty(QUrl, constant = True)
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
MoveRetractionType = 9
SupportInterfaceType = 10
PrimeTower = 11
PrimeTowerType = 11
__number_of_types = 12
__jump_map = numpy.logical_or(numpy.logical_or(numpy.arange(__number_of_types) == NoneType, numpy.arange(__number_of_types) == MoveCombingType), numpy.arange(__number_of_types) == MoveRetractionType)
@ -245,7 +245,7 @@ class LayerPolygon:
theme.getColor("layerview_move_combing").getRgbF(), # MoveCombingType
theme.getColor("layerview_move_retraction").getRgbF(), # MoveRetractionType
theme.getColor("layerview_support_interface").getRgbF(), # SupportInterfaceType
theme.getColor("layerview_prime_tower").getRgbF()
theme.getColor("layerview_prime_tower").getRgbF() # PrimeTowerType
])
return cls.__color_map

View file

@ -2,8 +2,9 @@
# Cura is released under the terms of the LGPLv3 or higher.
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.PluginObject import PluginObject
@ -72,18 +73,26 @@ class MachineAction(QObject, PluginObject):
return self._finished
## 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())
if plugin_path is None:
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)
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)
def displayItem(self):
if not self._view:
self._createViewFromQML()
return self._view
@pyqtProperty(QUrl, constant = True)
def qmlPath(self) -> "QUrl":
plugin_path = PluginRegistry.getInstance().getPluginPath(self.getPluginId())
if plugin_path is None:
Logger.log("e", "Cannot create QML view: cannot find plugin path for plugin [%s]", self.getPluginId())
return 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
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:
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)

View file

@ -3,15 +3,17 @@
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.Logger import Logger
from UM.Util import parseBool
from UM.OutputDevice.OutputDeviceManager import ManualDeviceAdditionAttempt
if TYPE_CHECKING:
from PyQt5.QtCore import QObject
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
from cura.CuraApplication import CuraApplication
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice
@ -45,6 +47,10 @@ class DiscoveredPrinter(QObject):
self._name = name
self.nameChanged.emit()
@pyqtProperty(str, constant = True)
def address(self) -> str:
return self._ip_address
machineTypeChanged = pyqtSignal()
@pyqtProperty(str, notify = machineTypeChanged)
@ -60,7 +66,14 @@ class DiscoveredPrinter(QObject):
@pyqtProperty(str, notify = machineTypeChanged)
def readableMachineType(self) -> str:
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:
readable_type = catalog.i18nc("@label", "Unknown")
return readable_type
@ -68,7 +81,11 @@ class DiscoveredPrinter(QObject):
@pyqtProperty(bool, notify = machineTypeChanged)
def isUnknownMachineType(self) -> bool:
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
@pyqtProperty(QObject, constant = True)
@ -94,14 +111,88 @@ class DiscoveredPrinter(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)
self._application = application
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()
@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"]:
item_list = list(
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")
def createMachineFromDiscoveredPrinter(self, discovered_printer: "DiscoveredPrinter") -> None:
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.initializationFinished.connect(self._initialize)
self._previous_global_stack = None
def _initialize(self) -> None:
self._application.getMachineManager().globalContainerChanged.connect(self._update)
self._update()
@ -86,13 +88,19 @@ class FirstStartMachineActionsModel(ListModel):
self.setItems([])
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()
first_start_actions = self._application.getMachineActionManager().getFirstStartActions(definition_id)
item_list = []
for item in first_start_actions:
item_list.append({"title": item.label,
"content": item.displayItem,
"content": item.getDisplayItem(),
"action": item,
})
item.reset()

View file

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

View file

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

View file

@ -62,7 +62,24 @@ class ExtruderConfigurationModel(QObject):
return " ".join(message_chunks)
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
# unique within a set

View file

@ -71,7 +71,23 @@ class PrinterConfigurationModel(QObject):
return "\n".join(message_chunks)
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
# 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:
self._aabb = self._mesh_data.getExtents(self.getWorldTransformation())
for child in self._children:
for child in self.getAllChildren():
if child.callDecoration("isNonPrintingMesh"):
# Non-printing-meshes inside a group should not affect push apart or drop to build plate
continue
if not child._mesh_data:
if not child.getMeshData():
# Nodes without mesh data should not affect bounding boxes of their parents.
continue
if self._aabb is None:

View file

@ -4,6 +4,8 @@
from collections import defaultdict
import threading
from typing import Any, Dict, Optional, Set, TYPE_CHECKING, List
import uuid
from PyQt5.QtCore import pyqtProperty, pyqtSlot, pyqtSignal
from UM.Decorators import override
@ -34,6 +36,12 @@ class GlobalStack(CuraContainerStack):
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"]
# This property is used to track which settings we are calculating the "resolve" for
@ -68,6 +76,10 @@ class GlobalStack(CuraContainerStack):
def maxExtruderCount(self):
return len(self.getMetaDataEntry("machine_extruder_trains"))
@pyqtProperty(bool, notify=configuredConnectionTypesChanged)
def supportsNetworkConnection(self):
return self.getMetaDataEntry("supports_network_connection", False)
@classmethod
def getLoadingPriority(cls) -> int:
return 2

View file

@ -720,7 +720,7 @@ class MachineManager(QObject):
extruder_stack.userChanges.setProperty(key, "value", new_value)
@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:
if self._active_container_stack:
variant = self._active_container_stack.variant
@ -730,7 +730,7 @@ class MachineManager(QObject):
return ""
@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:
if self._active_container_stack:
variant = self._active_container_stack.variant
@ -740,7 +740,7 @@ class MachineManager(QObject):
return ""
@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:
if self._global_container_stack:
variant = self._global_container_stack.variant
@ -750,7 +750,7 @@ class MachineManager(QObject):
return ""
@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:
if self._global_container_stack:
return self._global_container_stack.definition.id
@ -797,7 +797,6 @@ class MachineManager(QObject):
self.setActiveMachine(other_machine_stacks[0]["id"])
metadata = CuraContainerRegistry.getInstance().findContainerStacksMetadata(id = machine_id)[0]
network_key = metadata.get("um_network_key", None)
ExtruderManager.getInstance().removeMachineExtruders(machine_id)
containers = CuraContainerRegistry.getInstance().findInstanceContainersMetadata(type = "user", machine = machine_id)
for container in containers:
@ -805,8 +804,9 @@ class MachineManager(QObject):
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 network_key:
metadata_filter = {"um_network_key": network_key}
group_id = metadata.get("group_id", None)
if group_id:
metadata_filter = {"group_id": group_id}
hidden_containers = CuraContainerRegistry.getInstance().findContainerStacks(type = "machine", **metadata_filter)
if hidden_containers:
# 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 Util.parseBool(self._global_container_stack.getMetaDataEntry("has_materials", False)):
for position, extruder in self._global_container_stack.extruders.items():
if extruder.isEnabled and not extruder.material.getMetaDataEntry("compatible"):
return False
if not extruder.isEnabled:
continue
if not extruder.material.getMetaDataEntry("compatible"):
return False
return True
@ -1360,21 +1360,24 @@ class MachineManager(QObject):
# Get the definition id corresponding to this machine name
machine_definition_id = CuraContainerRegistry.getInstance().findDefinitionContainers(name = machine_name)[0].getId()
# 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 not new_machine:
new_machine = CuraStackBuilder.createMachine(machine_definition_id + "_sync", machine_definition_id)
if not new_machine:
return
new_machine.setMetaDataEntry("group_id", self._global_container_stack.getMetaDataEntry("group_id"))
new_machine.setMetaDataEntry("um_network_key", self.activeMachineNetworkKey())
new_machine.setMetaDataEntry("group_name", self.activeMachineNetworkGroupName)
new_machine.setMetaDataEntry("hidden", False)
new_machine.setMetaDataEntry("connection_type", self._global_container_stack.getMetaDataEntry("connection_type"))
else:
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)
new_machine.setMetaDataEntry("hidden", False)
self._global_container_stack.setMetaDataEntry("hidden", True)
self.setActiveMachine(new_machine.getId())
@ -1640,6 +1643,13 @@ class MachineManager(QObject):
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)
def getMachineTypeNameFromId(self, machine_type_id: str) -> str:
machine_type_name = ""
@ -1647,3 +1657,7 @@ class MachineManager(QObject):
if results:
machine_type_name = results[0]["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
# 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.
new_is_non_printing_mesh = self._evaluateIsNonPrintingMesh()
new_is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
changed = False
self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
if 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().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.
import copy
from UM.Settings.constant_instance_containers import EMPTY_CONTAINER_ID, empty_container
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
# Empty definition changes
@ -28,7 +30,7 @@ empty_material_container.setMetaDataEntry("type", "material")
EMPTY_QUALITY_CONTAINER_ID = "empty_quality"
empty_quality_container = copy.deepcopy(empty_container)
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("type", "quality")
empty_quality_container.setMetaDataEntry("supported", False)

View file

@ -15,6 +15,7 @@ class AddPrinterPagesModel(WelcomePagesModel):
"page_url": self._getBuiltinWelcomePagePath("AddNetworkOrLocalPrinterContent.qml"),
"next_page_id": "machine_actions",
"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",
"page_url": self._getBuiltinWelcomePagePath("AddPrinterByIpContent.qml"),

View file

@ -81,6 +81,7 @@ class PrintInformation(QObject):
"support_interface": catalog.i18nc("@tooltip", "Support Interface"),
"support": catalog.i18nc("@tooltip", "Support"),
"skirt": catalog.i18nc("@tooltip", "Skirt"),
"prime_tower": catalog.i18nc("@tooltip", "Prime Tower"),
"travel": catalog.i18nc("@tooltip", "Travel"),
"retract": catalog.i18nc("@tooltip", "Retractions"),
"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
NextPageIdRole = Qt.UserRole + 3 # The next page ID it should go to
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:
super().__init__(parent)
@ -47,6 +48,7 @@ class WelcomePagesModel(ListModel):
self.addRoleName(self.PageUrlRole, "page_url")
self.addRoleName(self.NextPageIdRole, "next_page_id")
self.addRoleName(self.NextPageButtonTextRole, "next_page_button_text")
self.addRoleName(self.PreviousPageButtonTextRole, "previous_page_button_text")
self._application = application
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.
from typing import Optional
from typing import List, Optional, Union, TYPE_CHECKING
import os.path
import zipfile
@ -9,15 +9,16 @@ import numpy
import Savitar
from UM.Application import Application
from UM.Logger import Logger
from UM.Math.Matrix import Matrix
from UM.Math.Vector import Vector
from UM.Mesh.MeshBuilder import MeshBuilder
from UM.Mesh.MeshReader import MeshReader
from UM.Scene.GroupDecorator import GroupDecorator
from UM.Scene.SceneNode import SceneNode #For typing.
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from cura.CuraApplication import CuraApplication
from cura.Settings.ExtruderManager import ExtruderManager
from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
@ -25,11 +26,9 @@ from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
from cura.Scene.ZOffsetDecorator import ZOffsetDecorator
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
MYPY = False
try:
if not MYPY:
if not TYPE_CHECKING:
import xml.etree.cElementTree as ET
except ImportError:
Logger.log("w", "Unable to load cElementTree, switching to slower version")
@ -55,7 +54,7 @@ class ThreeMFReader(MeshReader):
self._unit = None
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 == "":
return Matrix()
@ -85,13 +84,13 @@ class ThreeMFReader(MeshReader):
return temp_mat
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a Uranium scene node.
# \returns Uranium scene node.
def _convertSavitarNodeToUMNode(self, savitar_node):
## Convenience function that converts a SceneNode object (as obtained from libSavitar) to a scene node.
# \returns Scene node.
def _convertSavitarNodeToUMNode(self, savitar_node: Savitar.SceneNode) -> Optional[SceneNode]:
self._object_count += 1
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.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.
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.
if global_container_stack:
@ -161,7 +160,7 @@ class ThreeMFReader(MeshReader):
um_node.addDecorator(sliceable_decorator)
return um_node
def _read(self, file_name):
def _read(self, file_name: str) -> Union[SceneNode, List[SceneNode]]:
result = []
self._object_count = 0 # Used to name objects as there is no node name yet.
# The base object of 3mf is a zipped archive.
@ -181,12 +180,13 @@ class ThreeMFReader(MeshReader):
mesh_data = um_node.getMeshData()
if mesh_data is not None:
extents = mesh_data.getExtents()
if extents is not None:
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
transform_matrix.setByTranslation(center_vector)
transform_matrix.multiply(um_node.getLocalTransformation())
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.
# First step: flip the y and z axis.
@ -215,8 +215,11 @@ class ThreeMFReader(MeshReader):
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.
if um_node.getMeshData() is not None:
minimum_z_value = um_node.getMeshData().getExtents(um_node.getWorldTransformation()).minimum.y # y is z in transformation coordinates
node_meshdata = um_node.getMeshData()
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:
um_node.addDecorator(ZOffsetDecorator())
um_node.callDecoration("setZOffset", minimum_z_value)
@ -225,7 +228,7 @@ class ThreeMFReader(MeshReader):
except Exception:
Logger.logException("e", "An exception occurred in 3mf reader.")
return None
return []
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)
})
except requests.exceptions.ConnectionError:
Logger.log("w", "Unable to connect with the server.")
Logger.logException("w", "Unable to connect with the server.")
return []
# 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.
return self._emitRestoreError()
try:
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:
# 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)
@ -142,9 +147,14 @@ class DriveApiService:
Logger.log("w", "Could not get access token.")
return False
try:
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
"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:
Logger.log("w", "Could not delete backup: %s", delete_backup.text)
return False
@ -159,15 +169,19 @@ class DriveApiService:
if not access_token:
Logger.log("w", "Could not get access token.")
return None
backup_upload_request = requests.put(self.BACKUP_URL, json = {
"data": {
"backup_size": backup_size,
try:
backup_upload_request = requests.put(
self.BACKUP_URL,
json = {"data": {"backup_size": backup_size,
"metadata": backup_metadata
}
}, headers = {
},
headers = {
"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.
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.printDurationMessage.emit(source_build_plate_number, {}, [])
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._invokeSlice()
@ -563,10 +560,10 @@ class CuraEngineBackend(QObject, Backend):
## Convenient function: mark everything to slice, emit state and clear layer data
def needsSlicing(self) -> None:
self.determineAutoSlicing()
self.stopSlicing()
self.markSliceAll()
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()
@ -735,6 +732,7 @@ class CuraEngineBackend(QObject, Backend):
"support_interface": message.time_support_interface,
"support": message.time_support,
"skirt": message.time_skirt,
"prime_tower": message.time_prime_tower,
"travel": message.time_travel,
"retract": message.time_retract,
"none": message.time_none

View file

@ -256,10 +256,7 @@ class StartSliceJob(Job):
self._buildGlobalInheritsStackMessage(stack)
# Build messages for extruder stacks
# Send the extruder settings in the order of extruder positions. Somehow, if you send e.g. extruder 3 first,
# 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:
for extruder_stack in global_stack.extruderList:
self._buildExtruderMessage(extruder_stack)
for group in filtered_object_groups:
@ -334,25 +331,29 @@ class StartSliceJob(Job):
return result
## 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:
def _cacheAllExtruderSettings(self):
global_stack = cast(ContainerStack, CuraApplication.getInstance().getGlobalContainerStack())
# NB: keys must be strings for the string formatter
self._all_extruders_settings = {
"-1": self._buildReplacementTokens(global_stack)
}
for extruder_stack in ExtruderManager.getInstance().getActiveExtruderStacks():
extruder_nr = extruder_stack.getProperty("extruder_nr", "value")
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:
# any setting can be used as a token
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
if self._all_extruders_settings is None:
return ""
settings = self._all_extruders_settings.copy()
settings["default_extruder_nr"] = default_extruder_nr
return str(fmt.format(value, **settings))
@ -364,8 +365,14 @@ class StartSliceJob(Job):
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
message = self._slice_message.addRepeatedMessage("extruders")
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.
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
# per-extruder settings or per-object settings.
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
start_gcode = settings["machine_start_gcode"]

View file

@ -370,6 +370,8 @@ class FlavorParser:
self._layer_type = LayerPolygon.InfillType
elif type == "SUPPORT-INTERFACE":
self._layer_type = LayerPolygon.SupportInterfaceType
elif type == "PRIME-TOWER":
self._layer_type = LayerPolygon.PrimeTowerType
else:
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 columnSpacing: 3 * screenScaleFactor
property int propertyStoreIndex: manager.storeContainerIndex // definition_changes
property int propertyStoreIndex: manager ? manager.storeContainerIndex : 1 // definition_changes
property string extruderStackId: ""
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 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
@ -285,18 +285,30 @@ Item
optionModel: ListModel
{
id: extruderCountModel
Component.onCompleted:
{
extruderCountModel.clear()
update()
}
function update()
{
clear()
for (var i = 1; i <= Cura.MachineManager.activeMachine.maxExtruderCount; i++)
{
// 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
// 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
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:
{
// Readability:
@ -98,7 +106,6 @@ Rectangle
width: contentWidth
}
// CASE 3: CAN NOT MONITOR
Label
{
id: noNetworkLabel
@ -106,24 +113,8 @@ Rectangle
{
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
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")
color: UM.Theme.getColor("monitor_text_primary")
wrapMode: Text.WordWrap
@ -135,7 +126,7 @@ Rectangle
{
anchors
{
left: noNetworkUltimakerLabel.left
left: noNetworkLabel.left
}
visible: !isNetworkConfigured && isNetworkConfigurable
height: UM.Theme.getSize("monitor_text_line").height
@ -160,7 +151,7 @@ Rectangle
verticalCenter: externalLinkIcon.verticalCenter
}
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")
text: catalog.i18nc("@label link to technical assistance", "View user manuals online")
renderType: Text.NativeRendering
@ -170,14 +161,8 @@ Rectangle
anchors.fill: parent
hoverEnabled: true
onClicked: Qt.openUrlExternally("https://ultimaker.com/en/resources/manuals/ultimaker-3d-printers")
onEntered:
{
manageQueueText.font.underline = true
}
onExited:
{
manageQueueText.font.underline = false
}
onEntered: manageQueueText.font.underline = true
onExited: manageQueueText.font.underline = false
}
}
}

View file

@ -1,9 +1,7 @@
# Copyright (c) 2019 Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from typing import Optional, Tuple
from UM.Logger import Logger
from typing import List
from ..Script import Script
class FilamentChange(Script):
@ -65,9 +63,10 @@ class FilamentChange(Script):
}
}"""
def execute(self, data: list):
"""data is a list. Each index contains a layer"""
## Inserts the filament change g-code at specific layer numbers.
# \param data A list of layers of g-code.
# \return A similar list, with filament change commands inserted.
def execute(self, data: List[str]):
layer_nums = self.getSettingValueByKey("layer_number")
initial_retract = self.getSettingValueByKey("initial_retract")
later_retract = self.getSettingValueByKey("later_retract")
@ -88,32 +87,13 @@ class FilamentChange(Script):
if y_pos is not None:
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(",")
if len(layer_targets) > 0:
for layer_num in layer_targets:
layer_num = int(layer_num.strip())
layer_num = int(layer_num.strip()) + 1
if layer_num <= len(data):
index, layer_data = self._searchLayerData(data, layer_num - 1)
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
data[layer_num] = color_change + data[layer_num]
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"
}
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
{
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
RowLayout
@ -32,9 +40,9 @@ Item
id: itemRow
anchors.left: openFileButton.right
anchors.right: parent.right
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: Math.round(0.9 * prepareMenu.width)
height: parent.height
spacing: 0

View file

@ -20,15 +20,21 @@ Item
name: "cura"
}
anchors
{
left: parent.left
right: parent.right
leftMargin: UM.Theme.getSize("wide_margin").width
rightMargin: UM.Theme.getSize("wide_margin").width
}
Row
{
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
property int preferredWidth: Math.round(0.85 * previewMenu.width)
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width - 2 * UM.Theme.getSize("wide_margin").width
height: parent.height
Cura.ViewsSelector
{
@ -49,12 +55,12 @@ Item
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
{
id: viewPanel
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 : ""
}

View file

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

View file

@ -71,7 +71,7 @@ Window
left: parent.left
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
renderType: Text.NativeRendering
}
@ -89,6 +89,8 @@ Window
}
textArea.text: manager.getExampleData()
textArea.textFormat: Text.RichText
textArea.wrapMode: Text.Wrap
textArea.readOnly: true
}
}
@ -104,7 +106,7 @@ Window
Cura.RadioButton
{
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:
{
baseDialog.allowSendData = !checked
@ -113,7 +115,7 @@ Window
Cura.RadioButton
{
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:
{
baseDialog.allowSendData = checked

View file

@ -77,7 +77,7 @@ class SliceInfo(QObject, Extension):
if not plugin_path:
Logger.log("e", "Could not get plugin path!", self.getPluginId())
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:
with open(file_path, "r", encoding = "utf-8") as f:
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()
if global_container_stack:
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:
self._support_angle = support_angle_stack.getProperty("support_angle", "value")

View file

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

View file

@ -40,6 +40,7 @@ Column
Cura.SecondaryButton
{
id: installedButton
visible: installed
onClicked: toolbox.viewCategory = "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.getCuraAPI().account.loginStateChanged.connect(self._updateRequestHeader)
self._application.getCuraAPI().account.accessTokenChanged.connect(self._updateRequestHeader)
# Signals:
# --------------------------------------------------------------------------

View file

@ -1,10 +1,15 @@
#Copyright (c) 2019 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher.
import sys
from UM.Logger import Logger
try:
from . import UFPReader
except ImportError:
Logger.log("w", "Could not import UFPReader; libCharon may be missing")
from UM.i18n import i18nCatalog
from . import UFPReader
i18n_catalog = i18nCatalog("cura")
@ -21,6 +26,9 @@ def getMetaData():
def register(app):
if "UFPReader.UFPReader" not in sys.modules:
return {}
app.addNonSliceableExtension(".ufp")
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.
import UM 1.2 as UM
import Cura 1.0 as Cura
import Cura 1.5 as Cura
import QtQuick 2.2
import QtQuick.Controls 1.1
@ -14,22 +14,27 @@ Cura.MachineAction
{
id: base
anchors.fill: parent;
property alias currentItemIndex: listview.currentIndex
property var selectedDevice: null
property bool completeProperties: true
// For validating IP addresses
property var networkingUtil: Cura.NetworkingUtil {}
function connectToPrinter()
{
if(base.selectedDevice && base.completeProperties)
{
var printerKey = base.selectedDevice.key
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
if (!manager.existsKey(printerKey))
{
manager.associateActiveMachineWithPrinterDevice(base.selectedDevice)
manager.setGroupName(printerName) // TODO To change when the groups have a name
Cura.API.machines.addOutputDeviceToCurrentMachine(base.selectedDevice)
Cura.API.machines.setCurrentMachineGroupName(printerName) // TODO To change when the groups have a name
manager.refreshConnections()
completed()
}
else
@ -152,7 +157,7 @@ Cura.MachineAction
var selectedKey = manager.getLastManualEntryKey()
// If there is no last manual entry key, then we select the stored key (if any)
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++) {
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
{
id: manualPrinterDialog
@ -404,6 +420,26 @@ Cura.MachineAction
text: catalog.i18nc("@action:button", "OK")
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)
manualPrinterDialog.hide()
}

View file

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

View file

@ -30,6 +30,21 @@ UM.Dialog
OutputDevice.forceSendJob(printer.activePrintJob.key)
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
{
@ -52,6 +67,7 @@ UM.Dialog
bottomMargin: 56 * screenScaleFactor // TODO: Theme!
}
wrapMode: Text.WordWrap
renderType: Text.NativeRendering
text:
{
if (!printer || !printer.activePrintJob)

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,7 +34,10 @@ class DiscoverUM3Action(MachineAction):
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
@ -50,7 +53,7 @@ class DiscoverUM3Action(MachineAction):
def startDiscovery(self):
if not self._network_plugin:
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.discoveredDevicesChanged.emit()
@ -105,72 +108,27 @@ class DiscoverUM3Action(MachineAction):
else:
return []
@pyqtSlot(str)
def setGroupName(self, group_name: str) -> 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)
@pyqtSlot()
def refreshConnections(self) -> None:
if self._network_plugin:
# Ensure that the connection states are refreshed.
self._network_plugin.refreshConnections()
## Find all container stacks that has the pair 'key = value' in its metadata and replaces the value with 'new_value'
def _replaceContainersMetadata(self, key: str, value: str, new_value: str) -> None:
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 ""
# TODO: Improve naming
# TODO: CHANGE TO HOSTNAME
@pyqtSlot(result = str)
def getLastManualEntryKey(self) -> str:
if self._network_plugin:
return self._network_plugin.getLastManualDevice()
return ""
# TODO: Better naming needed. Exists where? On the current machine? On all machines?
# TODO: CHANGE TO HOSTNAME
@pyqtSlot(str, result = bool)
def existsKey(self, key: str) -> bool:
metadata_filter = {"um_network_key": key}
containers = CuraContainerRegistry.getInstance().findContainerStacks(type="machine", **metadata_filter)
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:
Logger.log("d", "Creating additional ui components for UM3.")
@ -179,10 +137,10 @@ class DiscoverUM3Action(MachineAction):
if not plugin_path:
return
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:
Logger.log("w", "Could not create ui components for UM3.")
return
# 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 threading import Event, Thread
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 PyQt5.QtNetwork import QNetworkRequest, QNetworkAccessManager
from PyQt5.QtNetwork import QNetworkRequest, QNetworkReply, QNetworkAccessManager
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
@ -39,6 +39,22 @@ if TYPE_CHECKING:
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.
# 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.
@ -51,11 +67,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
def __init__(self):
super().__init__()
self._zero_conf = None
self._zero_conf_browser = None
self._application = CuraApplication.getInstance()
self._api = self._application.getCuraAPI()
# Create a cloud output device manager that abstracts all cloud connection logic away.
self._cloud_output_device_manager = CloudOutputDeviceManager()
@ -80,11 +96,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._cluster_api_prefix = "/cluster-api/v" + self._cluster_api_version + "/"
# Get list of manual instances from preferences
self._preferences = CuraApplication.getInstance().getPreferences()
self._preferences = self._application.getPreferences()
self._preferences.addPreference("um3networkprinting/manual_instances",
"") # 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
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.start()
self._account = self._application.getCuraAPI().account
self._account = self._api.account
# Check if cloud flow is possible when user logs in
self._account.loginStateChanged.connect(self.checkCloudFlowIsPossible)
@ -149,10 +167,11 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
for address in self._manual_instances:
if address:
self.addManualDevice(address)
self.resetLastManualDevice()
self.resetLastManu
# TODO: CHANGE TO HOSTNAME
def refreshConnections(self):
active_machine = CuraApplication.getInstance().getGlobalContainerStack()
active_machine = self._application.getGlobalContainerStack()
if not active_machine:
return
@ -179,14 +198,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
return
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
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:
self.getOutputDeviceManager().addOutputDevice(self._discovered_devices[key])
self.checkCloudFlowIsPossible(None)
else:
self.getOutputDeviceManager().removeOutputDevice(key)
if key.startswith("manual:"):
self.removeManualDeviceSignal.emit(self.getPluginId(), key, self._discovered_devices[key].address)
def stop(self):
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):
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 not address:
address = self._discovered_devices[key].ipAddress
@ -206,15 +226,22 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self.resetLastManualDevice()
if address in self._manual_instances:
self._manual_instances.remove(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
manual_printer_request = self._manual_instances.pop(address)
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 address not in self._manual_instances:
self._manual_instances.append(address)
self._preferences.setValue("um3networkprinting/manual_instances", ",".join(self._manual_instances))
if manual_printer_request.callback is not None:
self._application.callLater(manual_printer_request.callback, False, address)
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
properties = {
@ -230,7 +257,8 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._onAddDevice(instance_name, address, properties)
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:
discovered_device = self._discovered_devices.get(key)
@ -245,56 +273,22 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
key, group_name, machine_type_id)
self._application.getMachineManager().addMachine(machine_type_id, group_name)
# 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.
self.refreshConnections()
def associateActiveMachineWithPrinterDevice(self, printer_device: Optional["PrinterOutputDevice"]) -> None:
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:
def _checkManualDevice(self, address: str) -> Optional[QNetworkReply]:
# Check if a UM3 family device exists at this address.
# If a printer responds, it will replace the preliminary printer created above
# origin=manual is for tracking back the origin of the call
url = QUrl("http://" + address + self._api_prefix + "system")
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:
reply_url = reply.url().toString()
@ -319,6 +313,12 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
Logger.log("e", "Something went wrong converting the JSON.")
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
instance_name = "manual:%s" % address
properties = {
@ -362,10 +362,6 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._onRemoveDevice(instance_name)
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:
device = self._discovered_devices.pop(device_id, None)
if device:
@ -401,11 +397,13 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
device = ClusterUM3OutputDevice.ClusterUM3OutputDevice(name, address, properties)
else:
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.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"):
# Ensure that the configured connection type is set.
global_container_stack.addConfiguredConnectionType(device.connectionType.value)
@ -425,7 +423,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
self._service_changed_request_event.wait(timeout = 5.0)
# Stop if the application is shutting down
if CuraApplication.getInstance().isShuttingDown():
if self._application.isShuttingDown():
return
self._service_changed_request_event.clear()

View file

@ -4,6 +4,8 @@
import threading
import time
import serial.tools.list_ports
from os import environ
from re import search
from PyQt5.QtCore import QObject, pyqtSignal
@ -112,6 +114,27 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
port = (port.device, port.description, port.hwid)
if only_list_usb and not port[2].startswith("USB"):
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]]
return list(base_list)

View file

@ -3,6 +3,7 @@
import configparser
import io
import uuid
from typing import Dict, List, Tuple
from UM.VersionUpgrade import VersionUpgrade
@ -18,6 +19,7 @@ _renamed_quality_profiles = {
"gmax15plus_pla_very_thick": "gmax15plus_global_very_thick"
} # type: Dict[str, str]
## Upgrades configurations from the state they were in at version 4.0 to the
# state they should be in at version 4.1.
class VersionUpgrade40to41(VersionUpgrade):
@ -95,6 +97,13 @@ class VersionUpgrade40to41(VersionUpgrade):
if parser["containers"]["4"] in _renamed_quality_profiles:
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()
parser.write(result)
return [filename], [result.getvalue()]

View file

@ -10,21 +10,21 @@ from UM.Math.Color import Color
from UM.PluginRegistry import PluginRegistry
from UM.Platform import Platform
from UM.Event import Event
from UM.View.View import View
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
from UM.View.RenderBatch import RenderBatch
from UM.View.GL.OpenGL import OpenGL
from cura.CuraApplication import CuraApplication
from cura.CuraView import CuraView
from cura.Scene.ConvexHullNode import ConvexHullNode
from . import XRayPass
## View used to display a see-through version of objects with errors highlighted.
class XRayView(View):
class XRayView(CuraView):
def __init__(self):
super().__init__()
super().__init__(parent = None, use_empty_menu_placeholder = True)
self._xray_shader = 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
if ignored_metadata_keys is None:
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
for key in ignored_metadata_keys:
if key in metadata:
@ -1179,6 +1179,7 @@ class XmlMaterialProfile(InstanceContainer):
"adhesion tendency": "material_adhesion_tendency",
"surface energy": "material_surface_energy",
"shrinkage percentage": "material_shrinkage_percentage",
"build volume temperature": "build_volume_temperature",
}
__unmapped_settings = [
"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": {
"package_info": {
"package_id": "CuraDrive",

View file

@ -38,7 +38,7 @@
"speed_wall": { "value": "speed_print * 0.7" },
"speed_topbottom": { "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_amount": { "default_value" : 2.5 },
"machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },

View file

@ -45,7 +45,7 @@
]
},
"gantry_height": {
"default_value": 30
"value": "30"
},
"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" },

View file

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

View file

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

View file

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

View file

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

View file

@ -30,7 +30,7 @@
"machine_height": { "default_value": 170 },
"machine_depth": { "default_value": 160 },
"machine_center_is_zero": { "default_value": false },
"gantry_height": { "default_value": 55 },
"gantry_height": { "value": "55"},
"retraction_amount": { "default_value": 1.5 },
"support_enable": { "default_value": true},
"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_0": { "value": "math.ceil(jerk_wall * 5 / 10)" },
"gantry_height": { "default_value": 25.0 },
"gantry_height": { "value": "25.0" },
"skin_overlap": { "value": "10" },
"acceleration_enabled": { "value": "True" },

View file

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

View file

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

View file

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

View file

@ -93,7 +93,7 @@
"machine_nozzle_heat_up_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]] },
"gantry_height": { "default_value": 55 },
"gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },

View file

@ -93,7 +93,7 @@
"machine_nozzle_heat_up_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]] },
"gantry_height": { "default_value": 55 },
"gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },

View file

@ -92,7 +92,7 @@
"machine_nozzle_heat_up_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]] },
"gantry_height": { "default_value": 55 },
"gantry_height": { "value": "55" },
"machine_max_feedrate_x": { "default_value": 300 },
"machine_max_feedrate_y": { "default_value": 300 },
"machine_max_feedrate_z": { "default_value": 40 },

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -38,7 +38,7 @@
]
},
"gantry_height": {
"default_value": 10
"value": "10"
},
"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"

View file

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

View file

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

View file

@ -28,7 +28,7 @@
"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)"
},
"gantry_height": { "default_value": 55 },
"gantry_height": { "value": "55" },
"machine_width": { "default_value": 214 },
"machine_height": { "default_value": 241.5 },
"machine_depth": { "default_value": 234 },

View file

@ -17,7 +17,8 @@
{
"0": "fdmextruder"
},
"supports_usb_connection": true
"supports_usb_connection": true,
"supports_network_connection": false
},
"settings":
{
@ -227,7 +228,7 @@
},
"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",
"value": "machine_extruder_count",
"default_value": 1,
@ -240,7 +241,7 @@
},
"machine_nozzle_tip_outer_diameter":
{
"label": "Outer nozzle diameter",
"label": "Outer Nozzle Diameter",
"description": "The outer diameter of the tip of the nozzle.",
"unit": "mm",
"default_value": 1,
@ -252,7 +253,7 @@
},
"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.",
"unit": "mm",
"default_value": 3,
@ -263,7 +264,7 @@
},
"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.",
"unit": "°",
"type": "int",
@ -276,7 +277,7 @@
},
"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.",
"unit": "mm",
"default_value": 16,
@ -310,7 +311,7 @@
},
"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.",
"default_value": 2.0,
"unit": "°C/s",
@ -321,7 +322,7 @@
},
"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.",
"default_value": 2.0,
"unit": "°C/s",
@ -343,7 +344,7 @@
},
"machine_gcode_flavor":
{
"label": "G-code flavour",
"label": "G-code Flavour",
"description": "The type of g-code to be generated.",
"type": "enum",
"options":
@ -376,7 +377,7 @@
},
"machine_disallowed_areas":
{
"label": "Disallowed areas",
"label": "Disallowed Areas",
"description": "A list of polygons with areas the print head is not allowed to enter.",
"type": "polygons",
"default_value":
@ -400,7 +401,7 @@
},
"machine_head_polygon":
{
"label": "Machine head polygon",
"label": "Machine Head Polygon",
"description": "A 2D silhouette of the print head (fan caps excluded).",
"type": "polygon",
"default_value":
@ -428,7 +429,7 @@
},
"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).",
"type": "polygon",
"default_value":
@ -456,7 +457,7 @@
},
"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).",
"default_value": 99999999999,
"value": "machine_height",
@ -488,7 +489,7 @@
},
"machine_use_extruder_offset_to_offset_coords":
{
"label": "Offset With Extruder",
"label": "Offset with Extruder",
"description": "Apply the extruder offset to the coordinate system.",
"type": "bool",
"default_value": true,
@ -523,7 +524,7 @@
"description": "The maximum speed for the motor of the X-direction.",
"unit": "mm/s",
"type": "float",
"default_value": 500,
"default_value": 299792458000,
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
@ -534,7 +535,7 @@
"description": "The maximum speed for the motor of the Y-direction.",
"unit": "mm/s",
"type": "float",
"default_value": 500,
"default_value": 299792458000,
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
@ -545,7 +546,7 @@
"description": "The maximum speed for the motor of the Z-direction.",
"unit": "mm/s",
"type": "float",
"default_value": 5,
"default_value": 299792458000,
"settable_per_mesh": false,
"settable_per_extruder": false,
"settable_per_meshgroup": false
@ -1432,7 +1433,8 @@
"z_seam_corner_none": "None",
"z_seam_corner_inner": "Hide 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",
"enabled": "z_seam_type != 'random'",
@ -2055,6 +2057,21 @@
"settable_per_mesh": false,
"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":
{
"label": "Printing Temperature",
@ -3527,6 +3544,20 @@
"enabled": "retraction_hop_enabled and extruders_enabled_count > 1",
"settable_per_mesh": false,
"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",
"enabled": "extruders_enabled_count > 1",
"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_extruder": false
},
@ -6659,7 +6690,7 @@
},
"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.",
"type": "bool",
"default_value": false,
@ -6669,7 +6700,7 @@
},
"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.",
"type": "float",
"enabled": "adaptive_layer_height_enabled",
@ -6681,7 +6712,7 @@
},
"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.",
"type": "float",
"enabled": "adaptive_layer_height_enabled",
@ -6694,7 +6725,7 @@
},
"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.",
"type": "float",
"enabled": "adaptive_layer_height_enabled",
@ -7165,41 +7196,47 @@
}
}
},
"command_line_settings": {
"command_line_settings":
{
"label": "Command Line Settings",
"description": "Settings which are only used if CuraEngine isn't called from the Cura frontend.",
"type": "category",
"enabled": false,
"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.",
"type": "bool",
"label": "Center Object",
"default_value": false,
"enabled": false
},
"mesh_position_x": {
"mesh_position_x":
{
"description": "Offset applied to the object in the x direction.",
"type": "float",
"label": "Mesh Position X",
"default_value": 0,
"enabled": false
},
"mesh_position_y": {
"mesh_position_y":
{
"description": "Offset applied to the object in the y direction.",
"type": "float",
"label": "Mesh Position Y",
"default_value": 0,
"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'.",
"type": "float",
"label": "Mesh Position Z",
"default_value": 0,
"enabled": false
},
"mesh_rotation_matrix": {
"mesh_rotation_matrix":
{
"label": "Mesh Rotation Matrix",
"description": "Transformation matrix to be applied to the model when loading it from file.",
"type": "str",

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