mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 22:47:29 -06:00
Merge remote-tracking branch 'origin/master' into shader_optimization
This commit is contained in:
commit
dab0a43214
304 changed files with 53275 additions and 18828 deletions
|
@ -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
139
cura/API/Machines.py
Normal 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
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,15 +66,26 @@ 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)
|
||||
if not readable_type:
|
||||
readable_type = catalog.i18nc("@label", "Unknown")
|
||||
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
|
||||
|
||||
@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])
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
4
cura/PrinterOutput/PrintJobOutputModel.py
Normal file
4
cura/PrinterOutput/PrintJobOutputModel.py
Normal 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
|
4
cura/PrinterOutput/PrinterOutputModel.py
Normal file
4
cura/PrinterOutput/PrinterOutputModel.py
Normal 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
|
4
cura/PrinterOutputDevice.py
Normal file
4
cura/PrinterOutputDevice.py
Normal 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
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -113,21 +113,17 @@ 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):
|
||||
# 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
|
||||
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 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()
|
||||
self._is_non_thumbnail_visible_mesh = self._evaluateIsNonThumbnailVisibleMesh()
|
||||
|
||||
if changed:
|
||||
Application.getInstance().getBackend().needsSlicing()
|
||||
Application.getInstance().getBackend().tickle()
|
||||
if self._is_non_printing_mesh != new_is_non_printing_mesh:
|
||||
self._is_non_printing_mesh = new_is_non_printing_mesh
|
||||
|
||||
Application.getInstance().getBackend().needsSlicing()
|
||||
Application.getInstance().getBackend().tickle()
|
||||
|
||||
## Makes sure that the stack upon which the container stack is placed is
|
||||
# kept up to date.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"),
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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")
|
||||
|
|
44
cura/Utils/NetworkingUtil.py
Normal file
44
cura/Utils/NetworkingUtil.py
Normal 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"]
|
|
@ -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()
|
||||
center_vector = Vector(extents.center.x, extents.center.y, extents.center.z)
|
||||
transform_matrix.setByTranslation(center_vector)
|
||||
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,17 +215,20 @@ 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
|
||||
if minimum_z_value < 0:
|
||||
um_node.addDecorator(ZOffsetDecorator())
|
||||
um_node.callDecoration("setZOffset", minimum_z_value)
|
||||
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)
|
||||
|
||||
result.append(um_node)
|
||||
|
||||
except Exception:
|
||||
Logger.logException("e", "An exception occurred in 3mf reader.")
|
||||
return None
|
||||
return []
|
||||
|
||||
return result
|
||||
|
||||
|
|
173
plugins/AMFReader/AMFReader.py
Normal file
173
plugins/AMFReader/AMFReader.py
Normal 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
|
21
plugins/AMFReader/__init__.py
Normal file
21
plugins/AMFReader/__init__.py
Normal 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()}
|
7
plugins/AMFReader/plugin.json
Normal file
7
plugins/AMFReader/plugin.json
Normal 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"
|
||||
}
|
|
@ -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()
|
||||
|
||||
download_package = requests.get(download_url, stream = True)
|
||||
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
|
||||
|
||||
delete_backup = requests.delete("{}/{}".format(self.BACKUP_URL, backup_id), headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
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,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
}, headers = {
|
||||
"Authorization": "Bearer {}".format(access_token)
|
||||
})
|
||||
try:
|
||||
backup_upload_request = requests.put(
|
||||
self.BACKUP_URL,
|
||||
json = {"data": {"backup_size": backup_size,
|
||||
"metadata": backup_metadata
|
||||
}
|
||||
},
|
||||
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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
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:
|
||||
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)
|
||||
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"]
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 : ""
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@ Cura.ExpandableComponent
|
|||
{
|
||||
id: base
|
||||
|
||||
dragPreferencesNamePrefix: "view/colorscheme"
|
||||
|
||||
contentHeaderTitle: catalog.i18nc("@label", "Color scheme")
|
||||
|
||||
Connections
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
64
plugins/SliceInfoPlugin/example_data.html
Normal file
64
plugins/SliceInfoPlugin/example_data.html
Normal 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>
|
|
@ -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"
|
||||
}
|
|
@ -19,7 +19,7 @@ import math
|
|||
class SolidView(View):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
application = Application.getInstance()
|
||||
application = Application.getInstance()
|
||||
application.getPreferences().addPreference("view/show_overhang", True)
|
||||
application.globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._enabled_shader = None
|
||||
|
@ -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")
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -40,6 +40,7 @@ Column
|
|||
|
||||
Cura.SecondaryButton
|
||||
{
|
||||
id: installedButton
|
||||
visible: installed
|
||||
onClicked: toolbox.viewCategory = "installed"
|
||||
text: catalog.i18nc("@action:button", "Installed")
|
||||
|
|
|
@ -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:
|
||||
# --------------------------------------------------------------------------
|
||||
|
|
|
@ -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()}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -69,6 +69,7 @@ Item
|
|||
// FIXED-LINE-HEIGHT:
|
||||
height: 18 * screenScaleFactor // TODO: Theme!
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -23,6 +23,7 @@ Button
|
|||
horizontalAlignment: Text.AlignHCenter
|
||||
text: base.text
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
renderType: Text.NativeRendering;
|
||||
}
|
||||
height: width
|
||||
hoverEnabled: enabled
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -48,5 +48,6 @@ Item
|
|||
x: Math.round(size * 0.25)
|
||||
y: Math.round(size * 0.15625)
|
||||
visible: position >= 0
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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!
|
||||
|
|
|
@ -43,5 +43,6 @@ Item
|
|||
text: tagText
|
||||
font.pointSize: 10 // TODO: Theme!
|
||||
visible: text !== ""
|
||||
renderType: Text.NativeRendering
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -78,6 +78,7 @@ UM.Dialog {
|
|||
height: 20 * screenScaleFactor;
|
||||
text: catalog.i18nc("@label", "Printer selection");
|
||||
wrapMode: Text.Wrap;
|
||||
renderType: Text.NativeRendering;
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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,14 +96,16 @@ 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
|
||||
self._last_manual_entry_key = "" # type: str
|
||||
|
||||
# The zero-conf service changed requests are handled in a separate thread, so we can re-schedule the requests
|
||||
# which fail to get detailed service info.
|
||||
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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)" },
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 10
|
||||
"value": "10"
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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": {
|
||||
|
|
|
@ -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": {
|
||||
|
|
45
resources/definitions/anet_a6.def.json
Normal file
45
resources/definitions/anet_a6.def.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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" },
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
},
|
||||
"gantry_height":
|
||||
{
|
||||
"default_value": 35
|
||||
"value": "35"
|
||||
},
|
||||
"machine_head_with_fans_polygon":
|
||||
{
|
||||
|
|
|
@ -46,7 +46,7 @@
|
|||
},
|
||||
"gantry_height":
|
||||
{
|
||||
"default_value": 0
|
||||
"value": "0"
|
||||
},
|
||||
"machine_gcode_flavor":
|
||||
{
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
]
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 12
|
||||
"value": "12"
|
||||
},
|
||||
"machine_use_extruder_offset_to_offset_coords": {
|
||||
"default_value": true
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
"default_value": false
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 10
|
||||
"value": "10"
|
||||
},
|
||||
"machine_gcode_flavor": {
|
||||
"default_value": "RepRap (Marlin/Sprinter)"
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -71,7 +71,7 @@
|
|||
"default_value": true
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 30
|
||||
"value": "30"
|
||||
},
|
||||
"acceleration_enabled": {
|
||||
"default_value": true
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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" },
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
]
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 0
|
||||
"value": "0"
|
||||
},
|
||||
"machine_shape": {
|
||||
"default_value": "elliptic"
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
]
|
||||
},
|
||||
"gantry_height": {
|
||||
"default_value": 0
|
||||
"value": "0"
|
||||
},
|
||||
"machine_shape": {
|
||||
"default_value": "elliptic"
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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
|
||||
|
@ -1429,10 +1430,11 @@
|
|||
"type": "enum",
|
||||
"options":
|
||||
{
|
||||
"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_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_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
Loading…
Add table
Add a link
Reference in a new issue