mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-12 09:17:50 -06:00
Merge branch 'master' into fix_marlin_press_to_resume
This commit is contained in:
commit
f4c88aff0f
112 changed files with 1546 additions and 1282 deletions
|
@ -1,15 +1,26 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtGui import QImage
|
||||
from PyQt5.QtQuick import QQuickImageProvider
|
||||
from PyQt5.QtCore import QSize
|
||||
|
||||
from UM.Application import Application
|
||||
|
||||
|
||||
## Creates screenshots of the current scene.
|
||||
class CameraImageProvider(QQuickImageProvider):
|
||||
def __init__(self):
|
||||
super().__init__(QQuickImageProvider.Image)
|
||||
|
||||
## Request a new image.
|
||||
#
|
||||
# The image will be taken using the current camera position.
|
||||
# Only the actual objects in the scene will get rendered. Not the build
|
||||
# plate and such!
|
||||
# \param id The ID for the image to create. This is the requested image
|
||||
# source, with the "image:" scheme and provider identifier removed. It's
|
||||
# a Qt thing, they'll provide this parameter.
|
||||
# \param size The dimensions of the image to scale to.
|
||||
def requestImage(self, id, size):
|
||||
for output_device in Application.getInstance().getOutputDeviceManager().getOutputDevices():
|
||||
try:
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
import os
|
||||
import sys
|
||||
import time
|
||||
from typing import cast, TYPE_CHECKING
|
||||
from typing import cast, TYPE_CHECKING, Optional
|
||||
|
||||
import numpy
|
||||
|
||||
|
@ -109,6 +109,7 @@ from cura.Settings.MaterialSettingsVisibilityHandler import MaterialSettingsVisi
|
|||
from cura.Settings.ContainerManager import ContainerManager
|
||||
from cura.Settings.SidebarCustomMenuItemsModel import SidebarCustomMenuItemsModel
|
||||
import cura.Settings.cura_empty_instance_containers
|
||||
from cura.Settings.CuraFormulaFunctions import CuraFormulaFunctions
|
||||
|
||||
from cura.ObjectsModel import ObjectsModel
|
||||
|
||||
|
@ -176,6 +177,8 @@ class CuraApplication(QtApplication):
|
|||
|
||||
self._single_instance = None
|
||||
|
||||
self._cura_formula_functions = None # type: Optional[CuraFormulaFunctions]
|
||||
|
||||
self._cura_package_manager = None
|
||||
|
||||
self._machine_action_manager = None
|
||||
|
@ -325,6 +328,8 @@ class CuraApplication(QtApplication):
|
|||
# Adds custom property types, settings types, and extra operators (functions) that need to be registered in
|
||||
# SettingDefinition and SettingFunction.
|
||||
def __initializeSettingDefinitionsAndFunctions(self):
|
||||
self._cura_formula_functions = CuraFormulaFunctions(self)
|
||||
|
||||
# Need to do this before ContainerRegistry tries to load the machines
|
||||
SettingDefinition.addSupportedProperty("settable_per_mesh", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
SettingDefinition.addSupportedProperty("settable_per_extruder", DefinitionPropertyType.Any, default = True, read_only = True)
|
||||
|
@ -345,10 +350,10 @@ class CuraApplication(QtApplication):
|
|||
SettingDefinition.addSettingType("optional_extruder", None, str, None)
|
||||
SettingDefinition.addSettingType("[int]", None, str, None)
|
||||
|
||||
SettingFunction.registerOperator("extruderValues", ExtruderManager.getExtruderValues)
|
||||
SettingFunction.registerOperator("extruderValue", ExtruderManager.getExtruderValue)
|
||||
SettingFunction.registerOperator("resolveOrValue", ExtruderManager.getResolveOrValue)
|
||||
SettingFunction.registerOperator("defaultExtruderPosition", ExtruderManager.getDefaultExtruderPosition)
|
||||
SettingFunction.registerOperator("extruderValue", self._cura_formula_functions.getValueInExtruder)
|
||||
SettingFunction.registerOperator("extruderValues", self._cura_formula_functions.getValuesInAllExtruders)
|
||||
SettingFunction.registerOperator("resolveOrValue", self._cura_formula_functions.getResolveOrValue)
|
||||
SettingFunction.registerOperator("defaultExtruderPosition", self._cura_formula_functions.getDefaultExtruderPosition)
|
||||
|
||||
# Adds all resources and container related resources.
|
||||
def __addAllResourcesAndContainerResources(self) -> None:
|
||||
|
@ -709,10 +714,8 @@ class CuraApplication(QtApplication):
|
|||
self._print_information = PrintInformation.PrintInformation(self)
|
||||
self._cura_actions = CuraActions.CuraActions(self)
|
||||
|
||||
# Initialize setting visibility presets model
|
||||
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self)
|
||||
default_visibility_profile = self._setting_visibility_presets_model.getItem(0)
|
||||
self.getPreferences().setDefault("general/visible_settings", ";".join(default_visibility_profile["settings"]))
|
||||
# Initialize setting visibility presets model.
|
||||
self._setting_visibility_presets_model = SettingVisibilityPresetsModel(self.getPreferences(), parent = self)
|
||||
|
||||
# Initialize Cura API
|
||||
self._cura_API.initialize()
|
||||
|
@ -815,6 +818,11 @@ class CuraApplication(QtApplication):
|
|||
def getSettingVisibilityPresetsModel(self, *args) -> SettingVisibilityPresetsModel:
|
||||
return self._setting_visibility_presets_model
|
||||
|
||||
def getCuraFormulaFunctions(self, *args) -> "CuraFormulaFunctions":
|
||||
if self._cura_formula_functions is None:
|
||||
self._cura_formula_functions = CuraFormulaFunctions(self)
|
||||
return self._cura_formula_functions
|
||||
|
||||
def getMachineErrorChecker(self, *args) -> MachineErrorChecker:
|
||||
return self._machine_error_checker
|
||||
|
||||
|
|
|
@ -1,135 +1,109 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional, List, Dict, Union
|
||||
import os
|
||||
import urllib.parse
|
||||
from configparser import ConfigParser
|
||||
from typing import Optional, List
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, Qt, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.Resources import Resources
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase, MimeTypeNotFoundError
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from cura.Settings.SettingVisibilityPreset import SettingVisibilityPreset
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class SettingVisibilityPresetsModel(ListModel):
|
||||
IdRole = Qt.UserRole + 1
|
||||
NameRole = Qt.UserRole + 2
|
||||
SettingsRole = Qt.UserRole + 3
|
||||
class SettingVisibilityPresetsModel(QObject):
|
||||
onItemsChanged = pyqtSignal()
|
||||
activePresetChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, preferences, parent = None):
|
||||
super().__init__(parent)
|
||||
self.addRoleName(self.IdRole, "id")
|
||||
self.addRoleName(self.NameRole, "name")
|
||||
self.addRoleName(self.SettingsRole, "settings")
|
||||
|
||||
self._items = [] # type: List[SettingVisibilityPreset]
|
||||
self._populate()
|
||||
basic_item = self.items[1]
|
||||
basic_visibile_settings = ";".join(basic_item["settings"])
|
||||
|
||||
self._preferences = Application.getInstance().getPreferences()
|
||||
basic_item = self.getVisibilityPresetById("basic")
|
||||
basic_visibile_settings = ";".join(basic_item.settings)
|
||||
|
||||
self._preferences = preferences
|
||||
|
||||
# Preference to store which preset is currently selected
|
||||
self._preferences.addPreference("cura/active_setting_visibility_preset", "basic")
|
||||
|
||||
# Preference that stores the "custom" set so it can always be restored (even after a restart)
|
||||
self._preferences.addPreference("cura/custom_visible_settings", basic_visibile_settings)
|
||||
self._preferences.preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._active_preset_item = self._getItem(self._preferences.getValue("cura/active_setting_visibility_preset"))
|
||||
self._active_preset_item = self.getVisibilityPresetById(self._preferences.getValue("cura/active_setting_visibility_preset"))
|
||||
|
||||
# Initialize visible settings if it is not done yet
|
||||
visible_settings = self._preferences.getValue("general/visible_settings")
|
||||
|
||||
if not visible_settings:
|
||||
self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item["settings"]))
|
||||
self._preferences.setValue("general/visible_settings", ";".join(self._active_preset_item.settings))
|
||||
else:
|
||||
self._onPreferencesChanged("general/visible_settings")
|
||||
|
||||
self.activePresetChanged.emit()
|
||||
|
||||
def _getItem(self, item_id: str) -> Optional[dict]:
|
||||
def getVisibilityPresetById(self, item_id: str) -> Optional[SettingVisibilityPreset]:
|
||||
result = None
|
||||
for item in self.items:
|
||||
if item["id"] == item_id:
|
||||
for item in self._items:
|
||||
if item.presetId == item_id:
|
||||
result = item
|
||||
break
|
||||
return result
|
||||
|
||||
def _populate(self) -> None:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
items = [] # type: List[Dict[str, Union[str, int, List[str]]]]
|
||||
items = [] # type: List[SettingVisibilityPreset]
|
||||
|
||||
custom_preset = SettingVisibilityPreset(preset_id="custom", name ="Custom selection", weight = -100)
|
||||
items.append(custom_preset)
|
||||
for file_path in Resources.getAllResourcesOfType(CuraApplication.ResourceTypes.SettingVisibilityPreset):
|
||||
setting_visibility_preset = SettingVisibilityPreset()
|
||||
try:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
|
||||
except MimeTypeNotFoundError:
|
||||
Logger.log("e", "Could not determine mime type of file %s", file_path)
|
||||
continue
|
||||
|
||||
item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
|
||||
if not os.path.isfile(file_path):
|
||||
Logger.log("e", "[%s] is not a file", file_path)
|
||||
continue
|
||||
|
||||
parser = ConfigParser(allow_no_value = True) # accept options without any value,
|
||||
try:
|
||||
parser.read([file_path])
|
||||
if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
|
||||
continue
|
||||
|
||||
settings = [] # type: List[str]
|
||||
for section in parser.sections():
|
||||
if section == 'general':
|
||||
continue
|
||||
|
||||
settings.append(section)
|
||||
for option in parser[section].keys():
|
||||
settings.append(option)
|
||||
|
||||
items.append({
|
||||
"id": item_id,
|
||||
"name": catalog.i18nc("@action:inmenu", parser["general"]["name"]),
|
||||
"weight": parser["general"]["weight"],
|
||||
"settings": settings,
|
||||
})
|
||||
|
||||
setting_visibility_preset.loadFromFile(file_path)
|
||||
except Exception:
|
||||
Logger.logException("e", "Failed to load setting preset %s", file_path)
|
||||
|
||||
items.sort(key = lambda k: (int(k["weight"]), k["id"])) # type: ignore
|
||||
# Put "custom" at the top
|
||||
items.insert(0, {"id": "custom",
|
||||
"name": "Custom selection",
|
||||
"weight": -100,
|
||||
"settings": []})
|
||||
items.append(setting_visibility_preset)
|
||||
|
||||
# Sort them on weight (and if that fails, use ID)
|
||||
items.sort(key = lambda k: (int(k.weight), k.presetId))
|
||||
|
||||
self.setItems(items)
|
||||
|
||||
@pyqtProperty("QVariantList", notify = onItemsChanged)
|
||||
def items(self):
|
||||
return self._items
|
||||
|
||||
def setItems(self, items: List[SettingVisibilityPreset]) -> None:
|
||||
if self._items != items:
|
||||
self._items = items
|
||||
self.onItemsChanged.emit()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setActivePreset(self, preset_id: str):
|
||||
if preset_id == self._active_preset_item["id"]:
|
||||
def setActivePreset(self, preset_id: str) -> None:
|
||||
if preset_id == self._active_preset_item.presetId:
|
||||
Logger.log("d", "Same setting visibility preset [%s] selected, do nothing.", preset_id)
|
||||
return
|
||||
|
||||
preset_item = None
|
||||
for item in self.items:
|
||||
if item["id"] == preset_id:
|
||||
preset_item = item
|
||||
break
|
||||
preset_item = self.getVisibilityPresetById(preset_id)
|
||||
if preset_item is None:
|
||||
Logger.log("w", "Tried to set active preset to unknown id [%s]", preset_id)
|
||||
return
|
||||
|
||||
need_to_save_to_custom = self._active_preset_item["id"] == "custom" and preset_id != "custom"
|
||||
need_to_save_to_custom = self._active_preset_item.presetId == "custom" and preset_id != "custom"
|
||||
if need_to_save_to_custom:
|
||||
# Save the current visibility settings to custom
|
||||
current_visibility_string = self._preferences.getValue("general/visible_settings")
|
||||
if current_visibility_string:
|
||||
self._preferences.setValue("cura/custom_visible_settings", current_visibility_string)
|
||||
|
||||
new_visibility_string = ";".join(preset_item["settings"])
|
||||
new_visibility_string = ";".join(preset_item.settings)
|
||||
if preset_id == "custom":
|
||||
# Get settings from the stored custom data
|
||||
new_visibility_string = self._preferences.getValue("cura/custom_visible_settings")
|
||||
|
@ -141,11 +115,9 @@ class SettingVisibilityPresetsModel(ListModel):
|
|||
self._active_preset_item = preset_item
|
||||
self.activePresetChanged.emit()
|
||||
|
||||
activePresetChanged = pyqtSignal()
|
||||
|
||||
@pyqtProperty(str, notify = activePresetChanged)
|
||||
def activePreset(self) -> str:
|
||||
return self._active_preset_item["id"]
|
||||
return self._active_preset_item.presetId
|
||||
|
||||
def _onPreferencesChanged(self, name: str) -> None:
|
||||
if name != "general/visible_settings":
|
||||
|
@ -158,25 +130,26 @@ class SettingVisibilityPresetsModel(ListModel):
|
|||
|
||||
visibility_set = set(visibility_string.split(";"))
|
||||
matching_preset_item = None
|
||||
for item in self.items:
|
||||
if item["id"] == "custom":
|
||||
for item in self._items:
|
||||
if item.presetId == "custom":
|
||||
continue
|
||||
if set(item["settings"]) == visibility_set:
|
||||
if set(item.settings) == visibility_set:
|
||||
matching_preset_item = item
|
||||
break
|
||||
|
||||
item_to_set = self._active_preset_item
|
||||
if matching_preset_item is None:
|
||||
# The new visibility setup is "custom" should be custom
|
||||
if self._active_preset_item["id"] == "custom":
|
||||
if self._active_preset_item.presetId == "custom":
|
||||
# We are already in custom, just save the settings
|
||||
self._preferences.setValue("cura/custom_visible_settings", visibility_string)
|
||||
else:
|
||||
item_to_set = self.items[0] # 0 is custom
|
||||
# We need to move to custom preset.
|
||||
item_to_set = self.getVisibilityPresetById("custom")
|
||||
else:
|
||||
item_to_set = matching_preset_item
|
||||
|
||||
if self._active_preset_item is None or self._active_preset_item["id"] != item_to_set["id"]:
|
||||
if self._active_preset_item is None or self._active_preset_item.presetId != item_to_set.presetId:
|
||||
self._active_preset_item = item_to_set
|
||||
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item["id"])
|
||||
self._preferences.setValue("cura/active_setting_visibility_preset", self._active_preset_item.presetId)
|
||||
self.activePresetChanged.emit()
|
||||
|
|
78
cura/PrinterOutput/FirmwareUpdater.py
Normal file
78
cura/PrinterOutput/FirmwareUpdater.py
Normal file
|
@ -0,0 +1,78 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QObject, QUrl, pyqtSignal, pyqtProperty
|
||||
|
||||
from enum import IntEnum
|
||||
from threading import Thread
|
||||
from typing import Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
class FirmwareUpdater(QObject):
|
||||
firmwareProgressChanged = pyqtSignal()
|
||||
firmwareUpdateStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__()
|
||||
|
||||
self._output_device = output_device
|
||||
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
|
||||
self._firmware_file = ""
|
||||
self._firmware_progress = 0
|
||||
self._firmware_update_state = FirmwareUpdateState.idle
|
||||
|
||||
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
|
||||
# the file path could be url-encoded.
|
||||
if firmware_file.startswith("file://"):
|
||||
self._firmware_file = QUrl(firmware_file).toLocalFile()
|
||||
else:
|
||||
self._firmware_file = firmware_file
|
||||
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.updating)
|
||||
|
||||
self._update_firmware_thread.start()
|
||||
|
||||
def _updateFirmware(self) -> None:
|
||||
raise NotImplementedError("_updateFirmware needs to be implemented")
|
||||
|
||||
## Cleanup after a succesful update
|
||||
def _cleanupAfterUpdate(self) -> None:
|
||||
# Clean up for next attempt.
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
self._firmware_file = ""
|
||||
self._onFirmwareProgress(100)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.completed)
|
||||
|
||||
@pyqtProperty(int, notify = firmwareProgressChanged)
|
||||
def firmwareProgress(self) -> int:
|
||||
return self._firmware_progress
|
||||
|
||||
@pyqtProperty(int, notify=firmwareUpdateStateChanged)
|
||||
def firmwareUpdateState(self) -> "FirmwareUpdateState":
|
||||
return self._firmware_update_state
|
||||
|
||||
def _setFirmwareUpdateState(self, state: "FirmwareUpdateState") -> None:
|
||||
if self._firmware_update_state != state:
|
||||
self._firmware_update_state = state
|
||||
self.firmwareUpdateStateChanged.emit()
|
||||
|
||||
# Callback function for firmware update progress.
|
||||
def _onFirmwareProgress(self, progress: int, max_progress: int = 100) -> None:
|
||||
self._firmware_progress = int(progress * 100 / max_progress) # Convert to scale of 0-100
|
||||
self.firmwareProgressChanged.emit()
|
||||
|
||||
|
||||
class FirmwareUpdateState(IntEnum):
|
||||
idle = 0
|
||||
updating = 1
|
||||
completed = 2
|
||||
unknown_error = 3
|
||||
communication_error = 4
|
||||
io_error = 5
|
||||
firmware_not_found_error = 6
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import TYPE_CHECKING, Set, Union, Optional
|
||||
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
@ -9,27 +9,28 @@ from PyQt5.QtCore import QTimer
|
|||
if TYPE_CHECKING:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
|
||||
|
||||
class GenericOutputController(PrinterOutputController):
|
||||
def __init__(self, output_device):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__(output_device)
|
||||
|
||||
self._preheat_bed_timer = QTimer()
|
||||
self._preheat_bed_timer.setSingleShot(True)
|
||||
self._preheat_bed_timer.timeout.connect(self._onPreheatBedTimerFinished)
|
||||
self._preheat_printer = None
|
||||
self._preheat_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
self._preheat_hotends_timer = QTimer()
|
||||
self._preheat_hotends_timer.setSingleShot(True)
|
||||
self._preheat_hotends_timer.timeout.connect(self._onPreheatHotendsTimerFinished)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
|
||||
|
||||
self._output_device.printersChanged.connect(self._onPrintersChanged)
|
||||
self._active_printer = None
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
def _onPrintersChanged(self):
|
||||
def _onPrintersChanged(self) -> None:
|
||||
if self._active_printer:
|
||||
self._active_printer.stateChanged.disconnect(self._onPrinterStateChanged)
|
||||
self._active_printer.targetBedTemperatureChanged.disconnect(self._onTargetBedTemperatureChanged)
|
||||
|
@ -43,32 +44,33 @@ class GenericOutputController(PrinterOutputController):
|
|||
for extruder in self._active_printer.extruders:
|
||||
extruder.targetHotendTemperatureChanged.connect(self._onTargetHotendTemperatureChanged)
|
||||
|
||||
def _onPrinterStateChanged(self):
|
||||
if self._active_printer.state != "idle":
|
||||
def _onPrinterStateChanged(self) -> None:
|
||||
if self._active_printer and self._active_printer.state != "idle":
|
||||
if self._preheat_bed_timer.isActive():
|
||||
self._preheat_bed_timer.stop()
|
||||
if self._preheat_printer:
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
if self._preheat_hotends_timer.isActive():
|
||||
self._preheat_hotends_timer.stop()
|
||||
for extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() # type: Set[ExtruderOutputModel]
|
||||
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
self._output_device.sendCommand("G91")
|
||||
self._output_device.sendCommand("G0 X%s Y%s Z%s F%s" % (x, y, z, speed))
|
||||
self._output_device.sendCommand("G90")
|
||||
|
||||
def homeHead(self, printer):
|
||||
def homeHead(self, printer: "PrinterOutputModel") -> None:
|
||||
self._output_device.sendCommand("G28 X Y")
|
||||
|
||||
def homeBed(self, printer):
|
||||
def homeBed(self, printer: "PrinterOutputModel") -> None:
|
||||
self._output_device.sendCommand("G28 Z")
|
||||
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
|
||||
self._output_device.sendCommand(command.upper()) #Most printers only understand uppercase g-code commands.
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||
if state == "pause":
|
||||
self._output_device.pausePrint()
|
||||
job.updateState("paused")
|
||||
|
@ -79,15 +81,15 @@ class GenericOutputController(PrinterOutputController):
|
|||
self._output_device.cancelPrint()
|
||||
pass
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
self._output_device.sendCommand("M140 S%s" % temperature)
|
||||
|
||||
def _onTargetBedTemperatureChanged(self):
|
||||
if self._preheat_bed_timer.isActive() and self._preheat_printer.targetBedTemperature == 0:
|
||||
def _onTargetBedTemperatureChanged(self) -> None:
|
||||
if self._preheat_bed_timer.isActive() and self._preheat_printer and self._preheat_printer.targetBedTemperature == 0:
|
||||
self._preheat_bed_timer.stop()
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
|
||||
try:
|
||||
temperature = round(temperature) # The API doesn't allow floating point.
|
||||
duration = round(duration)
|
||||
|
@ -100,21 +102,25 @@ class GenericOutputController(PrinterOutputController):
|
|||
self._preheat_printer = printer
|
||||
printer.updateIsPreheating(True)
|
||||
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
|
||||
self.setTargetBedTemperature(printer, temperature=0)
|
||||
self._preheat_bed_timer.stop()
|
||||
printer.updateIsPreheating(False)
|
||||
|
||||
def _onPreheatBedTimerFinished(self):
|
||||
def _onPreheatBedTimerFinished(self) -> None:
|
||||
if not self._preheat_printer:
|
||||
return
|
||||
self.setTargetBedTemperature(self._preheat_printer, 0)
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: int):
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
|
||||
self._output_device.sendCommand("M104 S%s T%s" % (temperature, position))
|
||||
|
||||
def _onTargetHotendTemperatureChanged(self):
|
||||
def _onTargetHotendTemperatureChanged(self) -> None:
|
||||
if not self._preheat_hotends_timer.isActive():
|
||||
return
|
||||
if not self._active_printer:
|
||||
return
|
||||
|
||||
for extruder in self._active_printer.extruders:
|
||||
if extruder in self._preheat_hotends and extruder.targetHotendTemperature == 0:
|
||||
|
@ -123,7 +129,7 @@ class GenericOutputController(PrinterOutputController):
|
|||
if not self._preheat_hotends:
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
|
||||
position = extruder.getPosition()
|
||||
number_of_extruders = len(extruder.getPrinter().extruders)
|
||||
if position >= number_of_extruders:
|
||||
|
@ -141,7 +147,7 @@ class GenericOutputController(PrinterOutputController):
|
|||
self._preheat_hotends.add(extruder)
|
||||
extruder.updateIsPreheating(True)
|
||||
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
|
||||
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), temperature=0)
|
||||
if extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
|
@ -149,21 +155,22 @@ class GenericOutputController(PrinterOutputController):
|
|||
if not self._preheat_hotends and self._preheat_hotends_timer.isActive():
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
def _onPreheatHotendsTimerFinished(self):
|
||||
def _onPreheatHotendsTimerFinished(self) -> None:
|
||||
for extruder in self._preheat_hotends:
|
||||
self.setTargetHotendTemperature(extruder.getPrinter(), extruder.getPosition(), 0)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
||||
|
||||
# Cancel any ongoing preheating timers, without setting back the temperature to 0
|
||||
# This can be used eg at the start of a print
|
||||
def stopPreheatTimers(self):
|
||||
def stopPreheatTimers(self) -> None:
|
||||
if self._preheat_hotends_timer.isActive():
|
||||
for extruder in self._preheat_hotends:
|
||||
extruder.updateIsPreheating(False)
|
||||
self._preheat_hotends = set()
|
||||
self._preheat_hotends = set() #type: Set[ExtruderOutputModel]
|
||||
|
||||
self._preheat_hotends_timer.stop()
|
||||
|
||||
if self._preheat_bed_timer.isActive():
|
||||
if self._preheat_printer:
|
||||
self._preheat_printer.updateIsPreheating(False)
|
||||
self._preheat_bed_timer.stop()
|
||||
|
|
|
@ -130,9 +130,7 @@ class NetworkedPrinterOutputDevice(PrinterOutputDevice):
|
|||
# We need to check if the manager needs to be re-created. If we don't, we get some issues when OSX goes to
|
||||
# sleep.
|
||||
if time_since_last_response > self._recreate_network_manager_time:
|
||||
if self._last_manager_create_time is None:
|
||||
self._createNetworkManager()
|
||||
elif time() - self._last_manager_create_time > self._recreate_network_manager_time:
|
||||
if self._last_manager_create_time is None or time() - self._last_manager_create_time > self._recreate_network_manager_time:
|
||||
self._createNetworkManager()
|
||||
assert(self._manager is not None)
|
||||
elif self._connection_state == ConnectionState.closed:
|
||||
|
|
|
@ -91,7 +91,7 @@ class PrintJobOutputModel(QObject):
|
|||
def assignedPrinter(self):
|
||||
return self._assigned_printer
|
||||
|
||||
def updateAssignedPrinter(self, assigned_printer: "PrinterOutputModel"):
|
||||
def updateAssignedPrinter(self, assigned_printer: Optional["PrinterOutputModel"]) -> None:
|
||||
if self._assigned_printer != assigned_printer:
|
||||
old_printer = self._assigned_printer
|
||||
self._assigned_printer = assigned_printer
|
||||
|
|
|
@ -1,57 +1,68 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Signal import Signal
|
||||
|
||||
from typing import Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
|
||||
class PrinterOutputController:
|
||||
def __init__(self, output_device):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
self.can_pause = True
|
||||
self.can_abort = True
|
||||
self.can_pre_heat_bed = True
|
||||
self.can_pre_heat_hotends = True
|
||||
self.can_send_raw_gcode = True
|
||||
self.can_control_manually = True
|
||||
self.can_update_firmware = False
|
||||
self._output_device = output_device
|
||||
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", extruder: "ExtruderOutputModel", temperature: int):
|
||||
def setTargetHotendTemperature(self, printer: "PrinterOutputModel", position: int, temperature: Union[int, float]) -> None:
|
||||
Logger.log("w", "Set target hotend temperature not implemented in controller")
|
||||
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
|
||||
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int) -> None:
|
||||
Logger.log("w", "Set target bed temperature not implemented in controller")
|
||||
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str):
|
||||
def setJobState(self, job: "PrintJobOutputModel", state: str) -> None:
|
||||
Logger.log("w", "Set job state not implemented in controller")
|
||||
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel"):
|
||||
def cancelPreheatBed(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Cancel preheat bed not implemented in controller")
|
||||
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration):
|
||||
def preheatBed(self, printer: "PrinterOutputModel", temperature, duration) -> None:
|
||||
Logger.log("w", "Preheat bed not implemented in controller")
|
||||
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel"):
|
||||
def cancelPreheatHotend(self, extruder: "ExtruderOutputModel") -> None:
|
||||
Logger.log("w", "Cancel preheat hotend not implemented in controller")
|
||||
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration):
|
||||
def preheatHotend(self, extruder: "ExtruderOutputModel", temperature, duration) -> None:
|
||||
Logger.log("w", "Preheat hotend not implemented in controller")
|
||||
|
||||
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def setHeadPosition(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
Logger.log("w", "Set head position not implemented in controller")
|
||||
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed):
|
||||
def moveHead(self, printer: "PrinterOutputModel", x, y, z, speed) -> None:
|
||||
Logger.log("w", "Move head not implemented in controller")
|
||||
|
||||
def homeBed(self, printer: "PrinterOutputModel"):
|
||||
def homeBed(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Home bed not implemented in controller")
|
||||
|
||||
def homeHead(self, printer: "PrinterOutputModel"):
|
||||
def homeHead(self, printer: "PrinterOutputModel") -> None:
|
||||
Logger.log("w", "Home head not implemented in controller")
|
||||
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str):
|
||||
def sendRawCommand(self, printer: "PrinterOutputModel", command: str) -> None:
|
||||
Logger.log("w", "Custom command not implemented in controller")
|
||||
|
||||
canUpdateFirmwareChanged = Signal()
|
||||
def setCanUpdateFirmware(self, can_update_firmware: bool) -> None:
|
||||
if can_update_firmware != self.can_update_firmware:
|
||||
self.can_update_firmware = can_update_firmware
|
||||
self.canUpdateFirmwareChanged.emit()
|
|
@ -2,7 +2,7 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject, QVariant, pyqtSlot
|
||||
from typing import Optional
|
||||
from typing import List, Dict, Optional
|
||||
from UM.Math.Vector import Vector
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.ExtruderOutputModel import ExtruderOutputModel
|
||||
|
@ -11,6 +11,7 @@ MYPY = False
|
|||
if MYPY:
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
from cura.PrinterOutput.PrinterOutputController import PrinterOutputController
|
||||
from cura.PrinterOutput.NetworkCamera import NetworkCamera
|
||||
|
||||
|
||||
class PrinterOutputModel(QObject):
|
||||
|
@ -26,6 +27,7 @@ class PrinterOutputModel(QObject):
|
|||
buildplateChanged = pyqtSignal()
|
||||
cameraChanged = pyqtSignal()
|
||||
configurationChanged = pyqtSignal()
|
||||
canUpdateFirmwareChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, output_controller: "PrinterOutputController", number_of_extruders: int = 1, parent=None, firmware_version = "") -> None:
|
||||
super().__init__(parent)
|
||||
|
@ -34,6 +36,7 @@ class PrinterOutputModel(QObject):
|
|||
self._name = ""
|
||||
self._key = "" # Unique identifier
|
||||
self._controller = output_controller
|
||||
self._controller.canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
||||
self._extruders = [ExtruderOutputModel(printer = self, position = i) for i in range(number_of_extruders)]
|
||||
self._printer_configuration = ConfigurationModel() # Indicates the current configuration setup in this printer
|
||||
self._head_position = Vector(0, 0, 0)
|
||||
|
@ -42,7 +45,7 @@ class PrinterOutputModel(QObject):
|
|||
self._printer_state = "unknown"
|
||||
self._is_preheating = False
|
||||
self._printer_type = ""
|
||||
self._buildplate_name = None
|
||||
self._buildplate_name = ""
|
||||
|
||||
self._printer_configuration.extruderConfigurations = [extruder.extruderConfiguration for extruder in
|
||||
self._extruders]
|
||||
|
@ -50,32 +53,32 @@ class PrinterOutputModel(QObject):
|
|||
self._camera = None
|
||||
|
||||
@pyqtProperty(str, constant = True)
|
||||
def firmwareVersion(self):
|
||||
def firmwareVersion(self) -> str:
|
||||
return self._firmware_version
|
||||
|
||||
def setCamera(self, camera):
|
||||
def setCamera(self, camera: Optional["NetworkCamera"]) -> None:
|
||||
if self._camera is not camera:
|
||||
self._camera = camera
|
||||
self.cameraChanged.emit()
|
||||
|
||||
def updateIsPreheating(self, pre_heating):
|
||||
def updateIsPreheating(self, pre_heating: bool) -> None:
|
||||
if self._is_preheating != pre_heating:
|
||||
self._is_preheating = pre_heating
|
||||
self.isPreheatingChanged.emit()
|
||||
|
||||
@pyqtProperty(bool, notify=isPreheatingChanged)
|
||||
def isPreheating(self):
|
||||
def isPreheating(self) -> bool:
|
||||
return self._is_preheating
|
||||
|
||||
@pyqtProperty(QObject, notify=cameraChanged)
|
||||
def camera(self):
|
||||
def camera(self) -> Optional["NetworkCamera"]:
|
||||
return self._camera
|
||||
|
||||
@pyqtProperty(str, notify = printerTypeChanged)
|
||||
def type(self):
|
||||
def type(self) -> str:
|
||||
return self._printer_type
|
||||
|
||||
def updateType(self, printer_type):
|
||||
def updateType(self, printer_type: str) -> None:
|
||||
if self._printer_type != printer_type:
|
||||
self._printer_type = printer_type
|
||||
self._printer_configuration.printerType = self._printer_type
|
||||
|
@ -83,10 +86,10 @@ class PrinterOutputModel(QObject):
|
|||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify = buildplateChanged)
|
||||
def buildplate(self):
|
||||
def buildplate(self) -> str:
|
||||
return self._buildplate_name
|
||||
|
||||
def updateBuildplateName(self, buildplate_name):
|
||||
def updateBuildplateName(self, buildplate_name: str) -> None:
|
||||
if self._buildplate_name != buildplate_name:
|
||||
self._buildplate_name = buildplate_name
|
||||
self._printer_configuration.buildplateConfiguration = self._buildplate_name
|
||||
|
@ -94,66 +97,66 @@ class PrinterOutputModel(QObject):
|
|||
self.configurationChanged.emit()
|
||||
|
||||
@pyqtProperty(str, notify=keyChanged)
|
||||
def key(self):
|
||||
def key(self) -> str:
|
||||
return self._key
|
||||
|
||||
def updateKey(self, key: str):
|
||||
def updateKey(self, key: str) -> None:
|
||||
if self._key != key:
|
||||
self._key = key
|
||||
self.keyChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def homeHead(self):
|
||||
def homeHead(self) -> None:
|
||||
self._controller.homeHead(self)
|
||||
|
||||
@pyqtSlot()
|
||||
def homeBed(self):
|
||||
def homeBed(self) -> None:
|
||||
self._controller.homeBed(self)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def sendRawCommand(self, command: str):
|
||||
def sendRawCommand(self, command: str) -> None:
|
||||
self._controller.sendRawCommand(self, command)
|
||||
|
||||
@pyqtProperty("QVariantList", constant = True)
|
||||
def extruders(self):
|
||||
def extruders(self) -> List["ExtruderOutputModel"]:
|
||||
return self._extruders
|
||||
|
||||
@pyqtProperty(QVariant, notify = headPositionChanged)
|
||||
def headPosition(self):
|
||||
def headPosition(self) -> Dict[str, float]:
|
||||
return {"x": self._head_position.x, "y": self._head_position.y, "z": self.head_position.z}
|
||||
|
||||
def updateHeadPosition(self, x, y, z):
|
||||
def updateHeadPosition(self, x: float, y: float, z: float) -> None:
|
||||
if self._head_position.x != x or self._head_position.y != y or self._head_position.z != z:
|
||||
self._head_position = Vector(x, y, z)
|
||||
self.headPositionChanged.emit()
|
||||
|
||||
@pyqtProperty(float, float, float)
|
||||
@pyqtProperty(float, float, float, float)
|
||||
def setHeadPosition(self, x, y, z, speed = 3000):
|
||||
def setHeadPosition(self, x: float, y: float, z: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(x, y, z)
|
||||
self._controller.setHeadPosition(self, x, y, z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadX(self, x, speed = 3000):
|
||||
def setHeadX(self, x: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(x, self._head_position.y, self._head_position.z)
|
||||
self._controller.setHeadPosition(self, x, self._head_position.y, self._head_position.z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadY(self, y, speed = 3000):
|
||||
def setHeadY(self, y: float, speed: float = 3000) -> None:
|
||||
self.updateHeadPosition(self._head_position.x, y, self._head_position.z)
|
||||
self._controller.setHeadPosition(self, self._head_position.x, y, self._head_position.z, speed)
|
||||
|
||||
@pyqtProperty(float)
|
||||
@pyqtProperty(float, float)
|
||||
def setHeadZ(self, z, speed = 3000):
|
||||
def setHeadZ(self, z: float, speed:float = 3000) -> None:
|
||||
self.updateHeadPosition(self._head_position.x, self._head_position.y, z)
|
||||
self._controller.setHeadPosition(self, self._head_position.x, self._head_position.y, z, speed)
|
||||
|
||||
@pyqtSlot(float, float, float)
|
||||
@pyqtSlot(float, float, float, float)
|
||||
def moveHead(self, x = 0, y = 0, z = 0, speed = 3000):
|
||||
def moveHead(self, x: float = 0, y: float = 0, z: float = 0, speed: float = 3000) -> None:
|
||||
self._controller.moveHead(self, x, y, z, speed)
|
||||
|
||||
## Pre-heats the heated bed of the printer.
|
||||
|
@ -162,47 +165,47 @@ class PrinterOutputModel(QObject):
|
|||
# Celsius.
|
||||
# \param duration How long the bed should stay warm, in seconds.
|
||||
@pyqtSlot(float, float)
|
||||
def preheatBed(self, temperature, duration):
|
||||
def preheatBed(self, temperature: float, duration: float) -> None:
|
||||
self._controller.preheatBed(self, temperature, duration)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelPreheatBed(self):
|
||||
def cancelPreheatBed(self) -> None:
|
||||
self._controller.cancelPreheatBed(self)
|
||||
|
||||
def getController(self):
|
||||
def getController(self) -> "PrinterOutputController":
|
||||
return self._controller
|
||||
|
||||
@pyqtProperty(str, notify=nameChanged)
|
||||
def name(self):
|
||||
@pyqtProperty(str, notify = nameChanged)
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name):
|
||||
def setName(self, name: str) -> None:
|
||||
self._setName(name)
|
||||
self.updateName(name)
|
||||
|
||||
def updateName(self, name):
|
||||
def updateName(self, name: str) -> None:
|
||||
if self._name != name:
|
||||
self._name = name
|
||||
self.nameChanged.emit()
|
||||
|
||||
## Update the bed temperature. This only changes it locally.
|
||||
def updateBedTemperature(self, temperature):
|
||||
def updateBedTemperature(self, temperature: int) -> None:
|
||||
if self._bed_temperature != temperature:
|
||||
self._bed_temperature = temperature
|
||||
self.bedTemperatureChanged.emit()
|
||||
|
||||
def updateTargetBedTemperature(self, temperature):
|
||||
def updateTargetBedTemperature(self, temperature: int) -> None:
|
||||
if self._target_bed_temperature != temperature:
|
||||
self._target_bed_temperature = temperature
|
||||
self.targetBedTemperatureChanged.emit()
|
||||
|
||||
## Set the target bed temperature. This ensures that it's actually sent to the remote.
|
||||
@pyqtSlot(int)
|
||||
def setTargetBedTemperature(self, temperature):
|
||||
def setTargetBedTemperature(self, temperature: int) -> None:
|
||||
self._controller.setTargetBedTemperature(self, temperature)
|
||||
self.updateTargetBedTemperature(temperature)
|
||||
|
||||
def updateActivePrintJob(self, print_job):
|
||||
def updateActivePrintJob(self, print_job: Optional["PrintJobOutputModel"]) -> None:
|
||||
if self._active_print_job != print_job:
|
||||
old_print_job = self._active_print_job
|
||||
|
||||
|
@ -214,72 +217,83 @@ class PrinterOutputModel(QObject):
|
|||
old_print_job.updateAssignedPrinter(None)
|
||||
self.activePrintJobChanged.emit()
|
||||
|
||||
def updateState(self, printer_state):
|
||||
def updateState(self, printer_state: str) -> None:
|
||||
if self._printer_state != printer_state:
|
||||
self._printer_state = printer_state
|
||||
self.stateChanged.emit()
|
||||
|
||||
@pyqtProperty(QObject, notify = activePrintJobChanged)
|
||||
def activePrintJob(self):
|
||||
def activePrintJob(self) -> Optional["PrintJobOutputModel"]:
|
||||
return self._active_print_job
|
||||
|
||||
@pyqtProperty(str, notify=stateChanged)
|
||||
def state(self):
|
||||
def state(self) -> str:
|
||||
return self._printer_state
|
||||
|
||||
@pyqtProperty(int, notify = bedTemperatureChanged)
|
||||
def bedTemperature(self):
|
||||
@pyqtProperty(int, notify=bedTemperatureChanged)
|
||||
def bedTemperature(self) -> int:
|
||||
return self._bed_temperature
|
||||
|
||||
@pyqtProperty(int, notify=targetBedTemperatureChanged)
|
||||
def targetBedTemperature(self):
|
||||
def targetBedTemperature(self) -> int:
|
||||
return self._target_bed_temperature
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPreHeatBed(self):
|
||||
def canPreHeatBed(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pre_heat_bed
|
||||
return False
|
||||
|
||||
# Does the printer support pre-heating the bed at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPreHeatHotends(self):
|
||||
def canPreHeatHotends(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pre_heat_hotends
|
||||
return False
|
||||
|
||||
# Does the printer support sending raw G-code at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canSendRawGcode(self):
|
||||
def canSendRawGcode(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_send_raw_gcode
|
||||
return False
|
||||
|
||||
# Does the printer support pause at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canPause(self):
|
||||
def canPause(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_pause
|
||||
return False
|
||||
|
||||
# Does the printer support abort at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canAbort(self):
|
||||
def canAbort(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_abort
|
||||
return False
|
||||
|
||||
# Does the printer support manual control at all
|
||||
@pyqtProperty(bool, constant=True)
|
||||
def canControlManually(self):
|
||||
def canControlManually(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_control_manually
|
||||
return False
|
||||
|
||||
# Does the printer support upgrading firmware
|
||||
@pyqtProperty(bool, notify = canUpdateFirmwareChanged)
|
||||
def canUpdateFirmware(self) -> bool:
|
||||
if self._controller:
|
||||
return self._controller.can_update_firmware
|
||||
return False
|
||||
|
||||
# Stub to connect UM.Signal to pyqtSignal
|
||||
def _onControllerCanUpdateFirmwareChanged(self) -> None:
|
||||
self.canUpdateFirmwareChanged.emit()
|
||||
|
||||
# Returns the configuration (material, variant and buildplate) of the current printer
|
||||
@pyqtProperty(QObject, notify = configurationChanged)
|
||||
def printerConfiguration(self):
|
||||
def printerConfiguration(self) -> Optional[ConfigurationModel]:
|
||||
if self._printer_configuration.isValid():
|
||||
return self._printer_configuration
|
||||
return None
|
|
@ -4,22 +4,24 @@
|
|||
from UM.Decorators import deprecated
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.OutputDevice.OutputDevice import OutputDevice
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, QTimer, pyqtSignal
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSignal, QObject, QTimer, QUrl
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.FileHandler.FileHandler import FileHandler #For typing.
|
||||
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||
from UM.Signal import signalemitter
|
||||
from UM.Qt.QtApplication import QtApplication
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
||||
from enum import IntEnum # For the connection state tracking.
|
||||
from typing import Callable, List, Optional
|
||||
from typing import Callable, List, Optional, Union
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.ConfigurationModel import ConfigurationModel
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -83,6 +85,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
self._connection_state = ConnectionState.closed #type: ConnectionState
|
||||
|
||||
self._firmware_updater = None #type: Optional[FirmwareUpdater]
|
||||
self._firmware_name = None #type: Optional[str]
|
||||
self._address = "" #type: str
|
||||
self._connection_text = "" #type: str
|
||||
|
@ -128,7 +131,7 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
|
||||
return None
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
def requestWrite(self, nodes: List["SceneNode"], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional["FileHandler"] = None, **kwargs: str) -> None:
|
||||
raise NotImplementedError("requestWrite needs to be implemented")
|
||||
|
||||
@pyqtProperty(QObject, notify = printersChanged)
|
||||
|
@ -226,3 +229,13 @@ class PrinterOutputDevice(QObject, OutputDevice):
|
|||
# This name can be used to define device type
|
||||
def getFirmwareName(self) -> Optional[str]:
|
||||
return self._firmware_name
|
||||
|
||||
def getFirmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
return self._firmware_updater
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateFirmware(self, firmware_file: Union[str, QUrl]) -> None:
|
||||
if not self._firmware_updater:
|
||||
return
|
||||
|
||||
self._firmware_updater.updateFirmware(firmware_file)
|
|
@ -187,11 +187,11 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
try:
|
||||
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
|
||||
except NoProfileException:
|
||||
return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "No custom profile to import in file <filename>{0}</filename>", file_name)}
|
||||
return { "status": "ok", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "No custom profile to import in file <filename>{0}</filename>", file_name)}
|
||||
except Exception as e:
|
||||
# Note that this will fail quickly. That is, if any profile reader throws an exception, it will stop reading. It will only continue reading if the reader returned None.
|
||||
Logger.log("e", "Failed to import profile from %s: %s while using profile reader. Got exception %s", file_name, profile_reader.getPluginId(), str(e))
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "Failed to import profile from <filename>{0}</filename>: <message>{1}</message>", file_name, "\n" + str(e))}
|
||||
return { "status": "error", "message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "Failed to import profile from <filename>{0}</filename>:", file_name) + "\n<message>" + str(e) + "</message>"}
|
||||
|
||||
if profile_or_list:
|
||||
# Ensure it is always a list of profiles
|
||||
|
@ -215,7 +215,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if not global_profile:
|
||||
Logger.log("e", "Incorrect profile [%s]. Could not find global profile", file_name)
|
||||
return { "status": "error",
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)}
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)}
|
||||
profile_definition = global_profile.getMetaDataEntry("definition")
|
||||
|
||||
# Make sure we have a profile_definition in the file:
|
||||
|
@ -225,7 +225,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if not machine_definition:
|
||||
Logger.log("e", "Incorrect profile [%s]. Unknown machine type [%s]", file_name, profile_definition)
|
||||
return {"status": "error",
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "This profile <filename>{0}</filename> contains incorrect data, could not import it.", file_name)
|
||||
}
|
||||
machine_definition = machine_definition[0]
|
||||
|
||||
|
@ -238,7 +238,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if profile_definition != expected_machine_definition:
|
||||
Logger.log("e", "Profile [%s] is for machine [%s] but the current active machine is [%s]. Will not import the profile", file_name, profile_definition, expected_machine_definition)
|
||||
return { "status": "error",
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename> or <message>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
|
||||
"message": catalog.i18nc("@info:status Don't translate the XML tags <filename>!", "The machine defined in profile <filename>{0}</filename> ({1}) doesn't match with your current machine ({2}), could not import it.", file_name, profile_definition, expected_machine_definition)}
|
||||
|
||||
# Fix the global quality profile's definition field in case it's not correct
|
||||
global_profile.setMetaDataEntry("definition", expected_machine_definition)
|
||||
|
@ -269,8 +269,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if idx == 0:
|
||||
# move all per-extruder settings to the first extruder's quality_changes
|
||||
for qc_setting_key in global_profile.getAllKeys():
|
||||
settable_per_extruder = global_stack.getProperty(qc_setting_key,
|
||||
"settable_per_extruder")
|
||||
settable_per_extruder = global_stack.getProperty(qc_setting_key, "settable_per_extruder")
|
||||
if settable_per_extruder:
|
||||
setting_value = global_profile.getProperty(qc_setting_key, "value")
|
||||
|
||||
|
@ -310,8 +309,8 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if result is not None:
|
||||
return {"status": "error", "message": catalog.i18nc(
|
||||
"@info:status Don't translate the XML tags <filename> or <message>!",
|
||||
"Failed to import profile from <filename>{0}</filename>: <message>{1}</message>",
|
||||
file_name, result)}
|
||||
"Failed to import profile from <filename>{0}</filename>:",
|
||||
file_name) + " <message>" + result + "</message>"}
|
||||
|
||||
return {"status": "ok", "message": catalog.i18nc("@info:status", "Successfully imported profile {0}", profile_or_list[0].getName())}
|
||||
|
||||
|
@ -686,7 +685,7 @@ class CuraContainerRegistry(ContainerRegistry):
|
|||
if not os.path.isfile(file_path):
|
||||
continue
|
||||
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
try:
|
||||
parser.read([file_path])
|
||||
except:
|
||||
|
|
130
cura/Settings/CuraFormulaFunctions.py
Normal file
130
cura/Settings/CuraFormulaFunctions.py
Normal file
|
@ -0,0 +1,130 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Any, List, Optional, TYPE_CHECKING
|
||||
|
||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.CuraContainerStack import CuraContainerStack
|
||||
|
||||
|
||||
#
|
||||
# This class contains all Cura-related custom functions that can be used in formulas. Some functions requires
|
||||
# information such as the currently active machine, so this is made into a class instead of standalone functions.
|
||||
#
|
||||
class CuraFormulaFunctions:
|
||||
|
||||
def __init__(self, application: "CuraApplication") -> None:
|
||||
self._application = application
|
||||
|
||||
# ================
|
||||
# Custom Functions
|
||||
# ================
|
||||
|
||||
# Gets the default extruder position of the currently active machine.
|
||||
def getDefaultExtruderPosition(self) -> str:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
return machine_manager.defaultExtruderPosition
|
||||
|
||||
# Gets the given setting key from the given extruder position.
|
||||
def getValueInExtruder(self, extruder_position: int, property_key: str,
|
||||
context: Optional["PropertyEvaluationContext"] = None) -> Any:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
|
||||
if extruder_position == -1:
|
||||
extruder_position = int(machine_manager.defaultExtruderPosition)
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
extruder_stack = global_stack.extruders[str(extruder_position)]
|
||||
|
||||
value = extruder_stack.getRawProperty(property_key, "value", context = context)
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder_stack, context = context)
|
||||
|
||||
return value
|
||||
|
||||
# Gets all extruder values as a list for the given property.
|
||||
def getValuesInAllExtruders(self, property_key: str,
|
||||
context: Optional["PropertyEvaluationContext"] = None) -> List[Any]:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
extruder_manager = self._application.getExtruderManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
result = []
|
||||
for extruder in extruder_manager.getActiveExtruderStacks():
|
||||
if not extruder.isEnabled:
|
||||
continue
|
||||
# only include values from extruders that are "active" for the current machine instance
|
||||
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
|
||||
continue
|
||||
|
||||
value = extruder.getRawProperty(property_key, "value", context = context)
|
||||
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder, context = context)
|
||||
|
||||
result.append(value)
|
||||
|
||||
if not result:
|
||||
result.append(global_stack.getProperty(property_key, "value", context = context))
|
||||
|
||||
return result
|
||||
|
||||
# Get the resolve value or value for a given key.
|
||||
def getResolveOrValue(self, property_key: str, context: Optional["PropertyEvaluationContext"] = None) -> Any:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
resolved_value = global_stack.getProperty(property_key, "value", context = context)
|
||||
|
||||
return resolved_value
|
||||
|
||||
# Gets the default setting value from given extruder position. The default value is what excludes the values in
|
||||
# the user_changes container.
|
||||
def getDefaultValueInExtruder(self, extruder_position: int, property_key: str) -> Any:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
extruder_stack = global_stack.extruders[str(extruder_position)]
|
||||
|
||||
context = self.createContextForDefaultValueEvaluation(extruder_stack)
|
||||
|
||||
return self.getValueInExtruder(extruder_position, property_key, context = context)
|
||||
|
||||
# Gets all default setting values as a list from all extruders of the currently active machine.
|
||||
# The default values are those excluding the values in the user_changes container.
|
||||
def getDefaultValuesInAllExtruders(self, property_key: str) -> List[Any]:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
context = self.createContextForDefaultValueEvaluation(global_stack)
|
||||
|
||||
return self.getValuesInAllExtruders(property_key, context = context)
|
||||
|
||||
# Gets the resolve value or value for a given key without looking the first container (user container).
|
||||
def getDefaultResolveOrValue(self, property_key: str) -> Any:
|
||||
machine_manager = self._application.getMachineManager()
|
||||
|
||||
global_stack = machine_manager.activeMachine
|
||||
|
||||
context = self.createContextForDefaultValueEvaluation(global_stack)
|
||||
return self.getResolveOrValue(property_key, context = context)
|
||||
|
||||
# Creates a context for evaluating default values (skip the user_changes container).
|
||||
def createContextForDefaultValueEvaluation(self, source_stack: "CuraContainerStack") -> "PropertyEvaluationContext":
|
||||
context = PropertyEvaluationContext(source_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
"extruderValue": self.getDefaultValueInExtruder,
|
||||
"extruderValues": self.getDefaultValuesInAllExtruders,
|
||||
"resolveOrValue": self.getDefaultResolveOrValue,
|
||||
}
|
||||
return context
|
|
@ -12,9 +12,7 @@ from UM.Scene.SceneNode import SceneNode
|
|||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry # Finding containers by ID.
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
|
||||
from typing import Any, cast, Dict, List, Optional, TYPE_CHECKING, Union
|
||||
|
||||
|
@ -69,16 +67,6 @@ class ExtruderManager(QObject):
|
|||
except KeyError: # Extruder index could be -1 if the global tab is selected, or the entry doesn't exist if the machine definition is wrong.
|
||||
return None
|
||||
|
||||
## Return extruder count according to extruder trains.
|
||||
@pyqtProperty(int, notify = extrudersChanged)
|
||||
def extruderCount(self) -> int:
|
||||
if not self._application.getGlobalContainerStack():
|
||||
return 0 # No active machine, so no extruders.
|
||||
try:
|
||||
return len(self._extruder_trains[self._application.getGlobalContainerStack().getId()])
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
## Gets a dict with the extruder stack ids with the extruder number as the key.
|
||||
@pyqtProperty("QVariantMap", notify = extrudersChanged)
|
||||
def extruderIds(self) -> Dict[str, str]:
|
||||
|
@ -386,85 +374,7 @@ class ExtruderManager(QObject):
|
|||
extruder_definition = container_registry.findDefinitionContainers(id = expected_extruder_definition_0_id)[0]
|
||||
extruder_stack_0.definition = extruder_definition
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
# This is exposed to SettingFunction so it can be used in value functions.
|
||||
#
|
||||
# \param key The key of the setting to retrieve values for.
|
||||
#
|
||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||
# If no extruder has the value, the list will contain the global value.
|
||||
@staticmethod
|
||||
def getExtruderValues(key: str) -> List[Any]:
|
||||
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values.
|
||||
|
||||
result = []
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
if not extruder.isEnabled:
|
||||
continue
|
||||
# only include values from extruders that are "active" for the current machine instance
|
||||
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value"):
|
||||
continue
|
||||
|
||||
value = extruder.getRawProperty(key, "value")
|
||||
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder)
|
||||
|
||||
result.append(value)
|
||||
|
||||
if not result:
|
||||
result.append(global_stack.getProperty(key, "value"))
|
||||
|
||||
return result
|
||||
|
||||
## Get all extruder values for a certain setting. This function will skip the user settings container.
|
||||
#
|
||||
# This is exposed to SettingFunction so it can be used in value functions.
|
||||
#
|
||||
# \param key The key of the setting to retrieve values for.
|
||||
#
|
||||
# \return A list of values for all extruders. If an extruder does not have a value, it will not be in the list.
|
||||
# If no extruder has the value, the list will contain the global value.
|
||||
@staticmethod
|
||||
def getDefaultExtruderValues(key: str) -> List[Any]:
|
||||
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()) #We know that there must be a global stack by the time you're requesting setting values.
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
"extruderValue": ExtruderManager.getDefaultExtruderValue,
|
||||
"extruderValues": ExtruderManager.getDefaultExtruderValues,
|
||||
"resolveOrValue": ExtruderManager.getDefaultResolveOrValue
|
||||
}
|
||||
|
||||
result = []
|
||||
for extruder in ExtruderManager.getInstance().getActiveExtruderStacks():
|
||||
# only include values from extruders that are "active" for the current machine instance
|
||||
if int(extruder.getMetaDataEntry("position")) >= global_stack.getProperty("machine_extruder_count", "value", context = context):
|
||||
continue
|
||||
|
||||
value = extruder.getRawProperty(key, "value", context = context)
|
||||
|
||||
if value is None:
|
||||
continue
|
||||
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder, context = context)
|
||||
|
||||
result.append(value)
|
||||
|
||||
if not result:
|
||||
result.append(global_stack.getProperty(key, "value", context = context))
|
||||
|
||||
return result
|
||||
|
||||
## Return the default extruder position from the machine manager
|
||||
@staticmethod
|
||||
def getDefaultExtruderPosition() -> str:
|
||||
return cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition
|
||||
extruder_stack_0.setNextStack(global_stack)
|
||||
|
||||
## Get all extruder values for a certain setting.
|
||||
#
|
||||
|
@ -474,62 +384,8 @@ class ExtruderManager(QObject):
|
|||
#
|
||||
# \return String representing the extruder values
|
||||
@pyqtSlot(str, result="QVariant")
|
||||
def getInstanceExtruderValues(self, key) -> List:
|
||||
return ExtruderManager.getExtruderValues(key)
|
||||
|
||||
## Get the value for a setting from a specific extruder.
|
||||
#
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
#
|
||||
# \param extruder_index The index of the extruder to get the value from.
|
||||
# \param key The key of the setting to get the value of.
|
||||
#
|
||||
# \return The value of the setting for the specified extruder or for the
|
||||
# global stack if not found.
|
||||
@staticmethod
|
||||
def getExtruderValue(extruder_index: int, key: str) -> Any:
|
||||
if extruder_index == -1:
|
||||
extruder_index = int(cura.CuraApplication.CuraApplication.getInstance().getMachineManager().defaultExtruderPosition)
|
||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
|
||||
if extruder:
|
||||
value = extruder.getRawProperty(key, "value")
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder)
|
||||
else:
|
||||
# Just a value from global.
|
||||
value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value")
|
||||
|
||||
return value
|
||||
|
||||
## Get the default value from the given extruder. This function will skip the user settings container.
|
||||
#
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
#
|
||||
# \param extruder_index The index of the extruder to get the value from.
|
||||
# \param key The key of the setting to get the value of.
|
||||
#
|
||||
# \return The value of the setting for the specified extruder or for the
|
||||
# global stack if not found.
|
||||
@staticmethod
|
||||
def getDefaultExtruderValue(extruder_index: int, key: str) -> Any:
|
||||
extruder = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
context = PropertyEvaluationContext(extruder)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
"extruderValue": ExtruderManager.getDefaultExtruderValue,
|
||||
"extruderValues": ExtruderManager.getDefaultExtruderValues,
|
||||
"resolveOrValue": ExtruderManager.getDefaultResolveOrValue
|
||||
}
|
||||
|
||||
if extruder:
|
||||
value = extruder.getRawProperty(key, "value", context = context)
|
||||
if isinstance(value, SettingFunction):
|
||||
value = value(extruder, context = context)
|
||||
else: # Just a value from global.
|
||||
value = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack()).getProperty(key, "value", context = context)
|
||||
|
||||
return value
|
||||
def getInstanceExtruderValues(self, key: str) -> List:
|
||||
return self._application.getCuraFormulaFunctions().getValuesInAllExtruders(key)
|
||||
|
||||
## Get the resolve value or value for a given key
|
||||
#
|
||||
|
@ -545,28 +401,6 @@ class ExtruderManager(QObject):
|
|||
|
||||
return resolved_value
|
||||
|
||||
## Get the resolve value or value for a given key without looking the first container (user container)
|
||||
#
|
||||
# This is the effective value for a given key, it is used for values in the global stack.
|
||||
# This is exposed to SettingFunction to use in value functions.
|
||||
# \param key The key of the setting to get the value of.
|
||||
#
|
||||
# \return The effective value
|
||||
@staticmethod
|
||||
def getDefaultResolveOrValue(key: str) -> Any:
|
||||
global_stack = cast(GlobalStack, cura.CuraApplication.CuraApplication.getInstance().getGlobalContainerStack())
|
||||
context = PropertyEvaluationContext(global_stack)
|
||||
context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
context.context["override_operators"] = {
|
||||
"extruderValue": ExtruderManager.getDefaultExtruderValue,
|
||||
"extruderValues": ExtruderManager.getDefaultExtruderValues,
|
||||
"resolveOrValue": ExtruderManager.getDefaultResolveOrValue
|
||||
}
|
||||
|
||||
resolved_value = global_stack.getProperty(key, "value", context = context)
|
||||
|
||||
return resolved_value
|
||||
|
||||
__instance = None # type: ExtruderManager
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
from collections import defaultdict
|
||||
import threading
|
||||
from typing import Any, Dict, Optional, Set, TYPE_CHECKING
|
||||
from PyQt5.QtCore import pyqtProperty
|
||||
from PyQt5.QtCore import pyqtProperty, pyqtSlot
|
||||
|
||||
from UM.Decorators import override
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
|
@ -13,6 +13,8 @@ from UM.Settings.SettingInstance import InstanceState
|
|||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Settings.Interfaces import PropertyEvaluationContext
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from UM.Platform import Platform
|
||||
from UM.Util import parseBool
|
||||
|
||||
import cura.CuraApplication
|
||||
|
@ -200,6 +202,31 @@ class GlobalStack(CuraContainerStack):
|
|||
def getHasMachineQuality(self) -> bool:
|
||||
return parseBool(self.getMetaDataEntry("has_machine_quality", False))
|
||||
|
||||
## Get default firmware file name if one is specified in the firmware
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self) -> str:
|
||||
machine_has_heated_bed = self.getProperty("machine_heated_bed", "value")
|
||||
|
||||
baudrate = 250000
|
||||
if Platform.isLinux():
|
||||
# Linux prefers a baudrate of 115200 here because older versions of
|
||||
# pySerial did not support a baudrate of 250000
|
||||
baudrate = 115200
|
||||
|
||||
# If a firmware file is available, it should be specified in the definition for the printer
|
||||
hex_file = self.getMetaDataEntry("firmware_file", None)
|
||||
if machine_has_heated_bed:
|
||||
hex_file = self.getMetaDataEntry("firmware_hbk_file", hex_file)
|
||||
|
||||
if not hex_file:
|
||||
Logger.log("w", "There is no firmware for machine %s.", self.getBottom().id)
|
||||
return ""
|
||||
|
||||
try:
|
||||
return Resources.getPath(cura.CuraApplication.CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
|
||||
except FileNotFoundError:
|
||||
Logger.log("w", "Firmware file %s not found.", hex_file)
|
||||
return ""
|
||||
|
||||
## private:
|
||||
global_stack_mime = MimeType(
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
from typing import List
|
||||
from typing import List, Optional, TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtProperty, pyqtSignal
|
||||
from UM.FlameProfiler import pyqtSlot
|
||||
|
@ -20,13 +20,18 @@ from UM.Settings.SettingInstance import InstanceState
|
|||
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Settings.ExtruderStack import ExtruderStack
|
||||
from UM.Settings.SettingDefinition import SettingDefinition
|
||||
|
||||
|
||||
class SettingInheritanceManager(QObject):
|
||||
def __init__(self, parent = None):
|
||||
def __init__(self, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerChanged)
|
||||
self._global_container_stack = None
|
||||
self._settings_with_inheritance_warning = []
|
||||
self._active_container_stack = None
|
||||
self._global_container_stack = None # type: Optional[ContainerStack]
|
||||
self._settings_with_inheritance_warning = [] # type: List[str]
|
||||
self._active_container_stack = None # type: Optional[ExtruderStack]
|
||||
self._onGlobalContainerChanged()
|
||||
|
||||
ExtruderManager.getInstance().activeExtruderChanged.connect(self._onActiveExtruderChanged)
|
||||
|
@ -41,7 +46,9 @@ class SettingInheritanceManager(QObject):
|
|||
|
||||
## Get the keys of all children settings with an override.
|
||||
@pyqtSlot(str, result = "QStringList")
|
||||
def getChildrenKeysWithOverride(self, key):
|
||||
def getChildrenKeysWithOverride(self, key: str) -> List[str]:
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key=key)
|
||||
if not definitions:
|
||||
Logger.log("w", "Could not find definition for key [%s]", key)
|
||||
|
@ -53,9 +60,11 @@ class SettingInheritanceManager(QObject):
|
|||
return result
|
||||
|
||||
@pyqtSlot(str, str, result = "QStringList")
|
||||
def getOverridesForExtruder(self, key, extruder_index):
|
||||
result = []
|
||||
def getOverridesForExtruder(self, key: str, extruder_index: str) -> List[str]:
|
||||
if self._global_container_stack is None:
|
||||
return []
|
||||
|
||||
result = [] # type: List[str]
|
||||
extruder_stack = ExtruderManager.getInstance().getExtruderStack(extruder_index)
|
||||
if not extruder_stack:
|
||||
Logger.log("w", "Unable to find extruder for current machine with index %s", extruder_index)
|
||||
|
@ -73,16 +82,16 @@ class SettingInheritanceManager(QObject):
|
|||
return result
|
||||
|
||||
@pyqtSlot(str)
|
||||
def manualRemoveOverride(self, key):
|
||||
def manualRemoveOverride(self, key: str) -> None:
|
||||
if key in self._settings_with_inheritance_warning:
|
||||
self._settings_with_inheritance_warning.remove(key)
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
@pyqtSlot()
|
||||
def forceUpdate(self):
|
||||
def forceUpdate(self) -> None:
|
||||
self._update()
|
||||
|
||||
def _onActiveExtruderChanged(self):
|
||||
def _onActiveExtruderChanged(self) -> None:
|
||||
new_active_stack = ExtruderManager.getInstance().getActiveExtruderStack()
|
||||
if not new_active_stack:
|
||||
self._active_container_stack = None
|
||||
|
@ -94,13 +103,14 @@ class SettingInheritanceManager(QObject):
|
|||
self._active_container_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||
|
||||
self._active_container_stack = new_active_stack
|
||||
if self._active_container_stack is not None:
|
||||
self._active_container_stack.propertyChanged.connect(self._onPropertyChanged)
|
||||
self._active_container_stack.containersChanged.connect(self._onContainersChanged)
|
||||
self._update() # Ensure that the settings_with_inheritance_warning list is populated.
|
||||
|
||||
def _onPropertyChanged(self, key, property_name):
|
||||
def _onPropertyChanged(self, key: str, property_name: str) -> None:
|
||||
if (property_name == "value" or property_name == "enabled") and self._global_container_stack:
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key = key)
|
||||
definitions = self._global_container_stack.definition.findDefinitions(key = key) # type: List["SettingDefinition"]
|
||||
if not definitions:
|
||||
return
|
||||
|
||||
|
@ -139,7 +149,7 @@ class SettingInheritanceManager(QObject):
|
|||
if settings_with_inheritance_warning_changed:
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
def _recursiveCheck(self, definition):
|
||||
def _recursiveCheck(self, definition: "SettingDefinition") -> bool:
|
||||
for child in definition.children:
|
||||
if child.key in self._settings_with_inheritance_warning:
|
||||
return True
|
||||
|
@ -149,7 +159,7 @@ class SettingInheritanceManager(QObject):
|
|||
return False
|
||||
|
||||
@pyqtProperty("QVariantList", notify = settingsWithIntheritanceChanged)
|
||||
def settingsWithInheritanceWarning(self):
|
||||
def settingsWithInheritanceWarning(self) -> List[str]:
|
||||
return self._settings_with_inheritance_warning
|
||||
|
||||
## Check if a setting has an inheritance function that is overwritten
|
||||
|
@ -157,8 +167,13 @@ class SettingInheritanceManager(QObject):
|
|||
has_setting_function = False
|
||||
if not stack:
|
||||
stack = self._active_container_stack
|
||||
if not stack: #No active container stack yet!
|
||||
if not stack: # No active container stack yet!
|
||||
return False
|
||||
|
||||
if self._active_container_stack is None:
|
||||
return False
|
||||
all_keys = self._active_container_stack.getAllKeys()
|
||||
|
||||
containers = [] # type: List[ContainerInterface]
|
||||
|
||||
## Check if the setting has a user state. If not, it is never overwritten.
|
||||
|
@ -190,7 +205,7 @@ class SettingInheritanceManager(QObject):
|
|||
has_setting_function = isinstance(value, SettingFunction)
|
||||
if has_setting_function:
|
||||
for setting_key in value.getUsedSettingKeys():
|
||||
if setting_key in self._active_container_stack.getAllKeys():
|
||||
if setting_key in all_keys:
|
||||
break # We found an actual setting. So has_setting_function can remain true
|
||||
else:
|
||||
# All of the setting_keys turned out to not be setting keys at all!
|
||||
|
@ -205,7 +220,7 @@ class SettingInheritanceManager(QObject):
|
|||
break # There is a setting function somewhere, stop looking deeper.
|
||||
return has_setting_function and has_non_function_value
|
||||
|
||||
def _update(self):
|
||||
def _update(self) -> None:
|
||||
self._settings_with_inheritance_warning = [] # Reset previous data.
|
||||
|
||||
# Make sure that the GlobalStack is not None. sometimes the globalContainerChanged signal gets here late.
|
||||
|
@ -226,7 +241,7 @@ class SettingInheritanceManager(QObject):
|
|||
# Notify others that things have changed.
|
||||
self.settingsWithIntheritanceChanged.emit()
|
||||
|
||||
def _onGlobalContainerChanged(self):
|
||||
def _onGlobalContainerChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onPropertyChanged)
|
||||
self._global_container_stack.containersChanged.disconnect(self._onContainersChanged)
|
||||
|
|
90
cura/Settings/SettingVisibilityPreset.py
Normal file
90
cura/Settings/SettingVisibilityPreset.py
Normal file
|
@ -0,0 +1,90 @@
|
|||
import os
|
||||
import urllib.parse
|
||||
from configparser import ConfigParser
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import pyqtProperty, QObject, pyqtSignal
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.MimeTypeDatabase import MimeTypeDatabase
|
||||
|
||||
|
||||
class SettingVisibilityPreset(QObject):
|
||||
onSettingsChanged = pyqtSignal()
|
||||
onNameChanged = pyqtSignal()
|
||||
onWeightChanged = pyqtSignal()
|
||||
onIdChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, preset_id: str = "", name: str = "", weight: int = 0, parent = None) -> None:
|
||||
super().__init__(parent)
|
||||
self._settings = [] # type: List[str]
|
||||
self._id = preset_id
|
||||
self._weight = weight
|
||||
self._name = name
|
||||
|
||||
@pyqtProperty("QStringList", notify = onSettingsChanged)
|
||||
def settings(self) -> List[str]:
|
||||
return self._settings
|
||||
|
||||
@pyqtProperty(str, notify = onIdChanged)
|
||||
def presetId(self) -> str:
|
||||
return self._id
|
||||
|
||||
@pyqtProperty(int, notify = onWeightChanged)
|
||||
def weight(self) -> int:
|
||||
return self._weight
|
||||
|
||||
@pyqtProperty(str, notify = onNameChanged)
|
||||
def name(self) -> str:
|
||||
return self._name
|
||||
|
||||
def setName(self, name: str) -> None:
|
||||
if name != self._name:
|
||||
self._name = name
|
||||
self.onNameChanged.emit()
|
||||
|
||||
def setId(self, id: str) -> None:
|
||||
if id != self._id:
|
||||
self._id = id
|
||||
self.onIdChanged.emit()
|
||||
|
||||
def setWeight(self, weight: int) -> None:
|
||||
if weight != self._weight:
|
||||
self._weight = weight
|
||||
self.onWeightChanged.emit()
|
||||
|
||||
def setSettings(self, settings: List[str]) -> None:
|
||||
if set(settings) != set(self._settings):
|
||||
self._settings = list(set(settings)) # filter out non unique
|
||||
self.onSettingsChanged.emit()
|
||||
|
||||
# Load a preset from file. We expect a file that can be parsed by means of the config parser.
|
||||
# The sections indicate the categories and the parameters placed in it (which don't need values) are the settings
|
||||
# that should be considered visible.
|
||||
def loadFromFile(self, file_path: str) -> None:
|
||||
mime_type = MimeTypeDatabase.getMimeTypeForFile(file_path)
|
||||
|
||||
item_id = urllib.parse.unquote_plus(mime_type.stripExtension(os.path.basename(file_path)))
|
||||
if not os.path.isfile(file_path):
|
||||
Logger.log("e", "[%s] is not a file", file_path)
|
||||
return None
|
||||
|
||||
parser = ConfigParser(interpolation = None, allow_no_value = True) # Accept options without any value,
|
||||
|
||||
parser.read([file_path])
|
||||
if not parser.has_option("general", "name") or not parser.has_option("general", "weight"):
|
||||
return None
|
||||
|
||||
settings = [] # type: List[str]
|
||||
for section in parser.sections():
|
||||
if section == "general":
|
||||
continue
|
||||
|
||||
settings.append(section)
|
||||
for option in parser[section].keys():
|
||||
settings.append(option)
|
||||
self.setSettings(settings)
|
||||
self.setId(item_id)
|
||||
self.setName(parser["general"]["name"])
|
||||
self.setWeight(int(parser["general"]["weight"]))
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
from UM.Qt.ListModel import ListModel
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from collections import OrderedDict
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, Qt
|
||||
|
||||
from UM.Application import Application
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.SettingFunction import SettingFunction
|
||||
from UM.Settings.PropertyEvaluationContext import PropertyEvaluationContext
|
||||
|
||||
from collections import OrderedDict
|
||||
import os
|
||||
from UM.Qt.ListModel import ListModel
|
||||
|
||||
|
||||
class UserChangesModel(ListModel):
|
||||
|
@ -38,9 +40,13 @@ class UserChangesModel(ListModel):
|
|||
self._update()
|
||||
|
||||
def _update(self):
|
||||
application = Application.getInstance()
|
||||
machine_manager = application.getMachineManager()
|
||||
cura_formula_functions = application.getCuraFormulaFunctions()
|
||||
|
||||
item_dict = OrderedDict()
|
||||
item_list = []
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = machine_manager.activeMachine
|
||||
if not global_stack:
|
||||
return
|
||||
|
||||
|
@ -71,13 +77,7 @@ class UserChangesModel(ListModel):
|
|||
|
||||
# Override "getExtruderValue" with "getDefaultExtruderValue" so we can get the default values
|
||||
user_changes = containers.pop(0)
|
||||
default_value_resolve_context = PropertyEvaluationContext(stack)
|
||||
default_value_resolve_context.context["evaluate_from_container_index"] = 1 # skip the user settings container
|
||||
default_value_resolve_context.context["override_operators"] = {
|
||||
"extruderValue": ExtruderManager.getDefaultExtruderValue,
|
||||
"extruderValues": ExtruderManager.getDefaultExtruderValues,
|
||||
"resolveOrValue": ExtruderManager.getDefaultResolveOrValue
|
||||
}
|
||||
default_value_resolve_context = cura_formula_functions.createContextForDefaultValueEvaluation(stack)
|
||||
|
||||
for setting_key in user_changes.getAllKeys():
|
||||
original_value = None
|
||||
|
|
|
@ -1012,7 +1012,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
|
||||
## Get the list of ID's of all containers in a container stack by partially parsing it's serialized data.
|
||||
def _getContainerIdListFromSerialized(self, serialized):
|
||||
parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
|
||||
parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
|
||||
parser.read_string(serialized)
|
||||
|
||||
container_ids = []
|
||||
|
@ -1033,7 +1033,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
return container_ids
|
||||
|
||||
def _getMachineNameFromSerializedStack(self, serialized):
|
||||
parser = ConfigParser(interpolation=None, empty_lines_in_values=False)
|
||||
parser = ConfigParser(interpolation = None, empty_lines_in_values = False)
|
||||
parser.read_string(serialized)
|
||||
return parser["general"].get("name", "")
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ from . import ThreeMFWorkspaceWriter
|
|||
from UM.i18n import i18nCatalog
|
||||
from UM.Platform import Platform
|
||||
|
||||
i18n_catalog = i18nCatalog("uranium")
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
workspace_extension = "3mf"
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
from . import ChangeLog
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import gc
|
||||
import sys
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Application import Application
|
||||
|
@ -95,12 +96,16 @@ class ProcessSlicedLayersJob(Job):
|
|||
layer_count = len(self._layers)
|
||||
|
||||
# Find the minimum layer number
|
||||
# When disabling the remove empty first layers setting, the minimum layer number will be a positive
|
||||
# value. In that case the first empty layers will be discarded and start processing layers from the
|
||||
# first layer with data.
|
||||
# When using a raft, the raft layers are sent as layers < 0. Instead of allowing layers < 0, we
|
||||
# instead simply offset all other layers so the lowest layer is always 0. It could happens that
|
||||
# the first raft layer has value -8 but there are just 4 raft (negative) layers.
|
||||
min_layer_number = 0
|
||||
# simply offset all other layers so the lowest layer is always 0. It could happens that the first
|
||||
# raft layer has value -8 but there are just 4 raft (negative) layers.
|
||||
min_layer_number = sys.maxsize
|
||||
negative_layers = 0
|
||||
for layer in self._layers:
|
||||
if layer.repeatedMessageCount("path_segment") > 0:
|
||||
if layer.id < min_layer_number:
|
||||
min_layer_number = layer.id
|
||||
if layer.id < 0:
|
||||
|
@ -109,9 +114,17 @@ class ProcessSlicedLayersJob(Job):
|
|||
current_layer = 0
|
||||
|
||||
for layer in self._layers:
|
||||
# Negative layers are offset by the minimum layer number, but the positive layers are just
|
||||
# offset by the number of negative layers so there is no layer gap between raft and model
|
||||
abs_layer_number = layer.id + abs(min_layer_number) if layer.id < 0 else layer.id + negative_layers
|
||||
# If the layer is below the minimum, it means that there is no data, so that we don't create a layer
|
||||
# data. However, if there are empty layers in between, we compute them.
|
||||
if layer.id < min_layer_number:
|
||||
continue
|
||||
|
||||
# Layers are offset by the minimum layer number. In case the raft (negative layers) is being used,
|
||||
# then the absolute layer number is adjusted by removing the empty layers that can be in between raft
|
||||
# and the model
|
||||
abs_layer_number = layer.id - min_layer_number
|
||||
if layer.id >= 0 and negative_layers != 0:
|
||||
abs_layer_number += (min_layer_number + negative_layers)
|
||||
|
||||
layer_data.addLayer(abs_layer_number)
|
||||
this_layer = layer_data.getLayer(abs_layer_number)
|
||||
|
|
|
@ -41,11 +41,15 @@ class StartJobResult(IntEnum):
|
|||
|
||||
## Formatter class that handles token expansion in start/end gcode
|
||||
class GcodeStartEndFormatter(Formatter):
|
||||
def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
|
||||
def __init__(self, default_extruder_nr: int = -1) -> None:
|
||||
super().__init__()
|
||||
self._default_extruder_nr = default_extruder_nr
|
||||
|
||||
def get_value(self, key: str, args: str, kwargs: dict) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
|
||||
# The kwargs dictionary contains a dictionary for each stack (with a string of the extruder_nr as their key),
|
||||
# and a default_extruder_nr to use when no extruder_nr is specified
|
||||
|
||||
extruder_nr = int(default_extruder_nr)
|
||||
extruder_nr = self._default_extruder_nr
|
||||
|
||||
key_fragments = [fragment.strip() for fragment in key.split(",")]
|
||||
if len(key_fragments) == 2:
|
||||
|
@ -247,7 +251,10 @@ class StartSliceJob(Job):
|
|||
self._buildGlobalInheritsStackMessage(stack)
|
||||
|
||||
# Build messages for extruder stacks
|
||||
for extruder_stack in ExtruderManager.getInstance().getMachineExtruders(stack.getId()):
|
||||
# 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:
|
||||
self._buildExtruderMessage(extruder_stack)
|
||||
|
||||
for group in filtered_object_groups:
|
||||
|
@ -339,7 +346,7 @@ class StartSliceJob(Job):
|
|||
|
||||
try:
|
||||
# any setting can be used as a token
|
||||
fmt = GcodeStartEndFormatter()
|
||||
fmt = GcodeStartEndFormatter(default_extruder_nr = default_extruder_nr)
|
||||
settings = self._all_extruders_settings.copy()
|
||||
settings["default_extruder_nr"] = default_extruder_nr
|
||||
return str(fmt.format(value, **settings))
|
||||
|
|
|
@ -50,7 +50,7 @@ class CuraProfileReader(ProfileReader):
|
|||
# \param profile_id \type{str} The name of the profile.
|
||||
# \return \type{List[Tuple[str,str]]} List of serialized profile strings and matching profile names.
|
||||
def _upgradeProfile(self, serialized, profile_id):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
if "general" not in parser:
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
|
||||
from typing import Set
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
|
@ -13,6 +16,7 @@ from UM.Settings.ContainerRegistry import ContainerRegistry
|
|||
from cura.Settings.GlobalStack import GlobalStack
|
||||
|
||||
from .FirmwareUpdateCheckerJob import FirmwareUpdateCheckerJob
|
||||
from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
@ -21,32 +25,31 @@ i18n_catalog = i18nCatalog("cura")
|
|||
# The plugin is currently only usable for applications maintained by Ultimaker. But it should be relatively easy
|
||||
# to change it to work for other applications.
|
||||
class FirmwareUpdateChecker(Extension):
|
||||
JEDI_VERSION_URL = "http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources"
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
# Initialize the Preference called `latest_checked_firmware` that stores the last version
|
||||
# checked for the UM3. In the future if we need to check other printers' firmware
|
||||
Application.getInstance().getPreferences().addPreference("info/latest_checked_firmware", "")
|
||||
|
||||
# Listen to a Signal that indicates a change in the list of printers, just if the user has enabled the
|
||||
# 'check for updates' option
|
||||
# "check for updates" option
|
||||
Application.getInstance().getPreferences().addPreference("info/automatic_update_check", True)
|
||||
if Application.getInstance().getPreferences().getValue("info/automatic_update_check"):
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
self._download_url = None
|
||||
self._check_job = None
|
||||
self._checked_printer_names = set() # type: Set[str]
|
||||
|
||||
## Callback for the message that is spawned when there is a new version.
|
||||
def _onActionTriggered(self, message, action):
|
||||
if action == "download":
|
||||
if self._download_url is not None:
|
||||
QDesktopServices.openUrl(QUrl(self._download_url))
|
||||
|
||||
def _onSetDownloadUrl(self, download_url):
|
||||
self._download_url = download_url
|
||||
if action == FirmwareUpdateCheckerMessage.STR_ACTION_DOWNLOAD:
|
||||
machine_id = message.getMachineId()
|
||||
download_url = message.getDownloadUrl()
|
||||
if download_url is not None:
|
||||
if QDesktopServices.openUrl(QUrl(download_url)):
|
||||
Logger.log("i", "Redirected browser to {0} to show newly available firmware.".format(download_url))
|
||||
else:
|
||||
Logger.log("e", "Can't reach URL: {0}".format(download_url))
|
||||
else:
|
||||
Logger.log("e", "Can't find URL for {0}".format(machine_id))
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Only take care when a new GlobalStack was added
|
||||
|
@ -63,13 +66,18 @@ class FirmwareUpdateChecker(Extension):
|
|||
# \param silent type(boolean) Suppresses messages other than "new version found" messages.
|
||||
# This is used when checking for a new firmware version at startup.
|
||||
def checkFirmwareVersion(self, container = None, silent = False):
|
||||
# Do not run multiple check jobs in parallel
|
||||
if self._check_job is not None:
|
||||
Logger.log("i", "A firmware update check is already running, do nothing.")
|
||||
container_name = container.definition.getName()
|
||||
if container_name in self._checked_printer_names:
|
||||
return
|
||||
self._checked_printer_names.add(container_name)
|
||||
|
||||
metadata = container.definition.getMetaData().get("firmware_update_info")
|
||||
if metadata is None:
|
||||
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(container_name))
|
||||
return
|
||||
|
||||
self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent, url = self.JEDI_VERSION_URL,
|
||||
callback = self._onActionTriggered,
|
||||
set_download_url_callback = self._onSetDownloadUrl)
|
||||
self._check_job = FirmwareUpdateCheckerJob(container = container, silent = silent,
|
||||
machine_name = container_name, metadata = metadata,
|
||||
callback = self._onActionTriggered)
|
||||
self._check_job.start()
|
||||
self._check_job.finished.connect(self._onJobFinished)
|
||||
|
|
|
@ -1,13 +1,18 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Message import Message
|
||||
from UM.Logger import Logger
|
||||
from UM.Job import Job
|
||||
from UM.Version import Version
|
||||
|
||||
import urllib.request
|
||||
import codecs
|
||||
from urllib.error import URLError
|
||||
from typing import Dict, Optional
|
||||
|
||||
from .FirmwareUpdateCheckerLookup import FirmwareUpdateCheckerLookup, getSettingsKeyForMachine
|
||||
from .FirmwareUpdateCheckerMessage import FirmwareUpdateCheckerMessage
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
@ -15,46 +20,86 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
## This job checks if there is an update available on the provided URL.
|
||||
class FirmwareUpdateCheckerJob(Job):
|
||||
def __init__(self, container = None, silent = False, url = None, callback = None, set_download_url_callback = None):
|
||||
STRING_ZERO_VERSION = "0.0.0"
|
||||
STRING_EPSILON_VERSION = "0.0.1"
|
||||
ZERO_VERSION = Version(STRING_ZERO_VERSION)
|
||||
EPSILON_VERSION = Version(STRING_EPSILON_VERSION)
|
||||
|
||||
def __init__(self, container, silent, machine_name, metadata, callback) -> None:
|
||||
super().__init__()
|
||||
self._container = container
|
||||
self.silent = silent
|
||||
self._url = url
|
||||
self._callback = callback
|
||||
self._set_download_url_callback = set_download_url_callback
|
||||
|
||||
def run(self):
|
||||
if not self._url:
|
||||
Logger.log("e", "Can not check for a new release. URL not set!")
|
||||
return
|
||||
self._machine_name = machine_name
|
||||
self._metadata = metadata
|
||||
self._lookups = None # type:Optional[FirmwareUpdateCheckerLookup]
|
||||
self._headers = {} # type:Dict[str, str] # Don't set headers yet.
|
||||
|
||||
def getUrlResponse(self, url: str) -> str:
|
||||
result = self.STRING_ZERO_VERSION
|
||||
|
||||
try:
|
||||
request = urllib.request.Request(url, headers = self._headers)
|
||||
response = urllib.request.urlopen(request)
|
||||
result = response.read().decode("utf-8")
|
||||
except URLError:
|
||||
Logger.log("w", "Could not reach '{0}', if this URL is old, consider removal.".format(url))
|
||||
|
||||
return result
|
||||
|
||||
def parseVersionResponse(self, response: str) -> Version:
|
||||
raw_str = response.split("\n", 1)[0].rstrip()
|
||||
return Version(raw_str)
|
||||
|
||||
def getCurrentVersion(self) -> Version:
|
||||
max_version = self.ZERO_VERSION
|
||||
if self._lookups is None:
|
||||
return max_version
|
||||
|
||||
machine_urls = self._lookups.getCheckUrls()
|
||||
if machine_urls is not None:
|
||||
for url in machine_urls:
|
||||
version = self.parseVersionResponse(self.getUrlResponse(url))
|
||||
if version > max_version:
|
||||
max_version = version
|
||||
|
||||
if max_version < self.EPSILON_VERSION:
|
||||
Logger.log("w", "MachineID {0} not handled!".format(self._lookups.getMachineName()))
|
||||
|
||||
return max_version
|
||||
|
||||
def run(self):
|
||||
if self._lookups is None:
|
||||
self._lookups = FirmwareUpdateCheckerLookup(self._machine_name, self._metadata)
|
||||
|
||||
try:
|
||||
# Initialize a Preference that stores the last version checked for this printer.
|
||||
Application.getInstance().getPreferences().addPreference(
|
||||
getSettingsKeyForMachine(self._lookups.getMachineId()), "")
|
||||
|
||||
# Get headers
|
||||
application_name = Application.getInstance().getApplicationName()
|
||||
headers = {"User-Agent": "%s - %s" % (application_name, Application.getInstance().getVersion())}
|
||||
request = urllib.request.Request(self._url, headers = headers)
|
||||
current_version_file = urllib.request.urlopen(request)
|
||||
reader = codecs.getreader("utf-8")
|
||||
application_version = Application.getInstance().getVersion()
|
||||
self._headers = {"User-Agent": "%s - %s" % (application_name, application_version)}
|
||||
|
||||
# get machine name from the definition container
|
||||
machine_name = self._container.definition.getName()
|
||||
machine_name_parts = machine_name.lower().split(" ")
|
||||
|
||||
# If it is not None, then we compare between the checked_version and the current_version
|
||||
# Now we just do that if the active printer is Ultimaker 3 or Ultimaker 3 Extended or any
|
||||
# other Ultimaker 3 that will come in the future
|
||||
if len(machine_name_parts) >= 2 and machine_name_parts[:2] == ["ultimaker", "3"]:
|
||||
Logger.log("i", "You have a UM3 in printer list. Let's check the firmware!")
|
||||
machine_id = self._lookups.getMachineId()
|
||||
if machine_id is not None:
|
||||
Logger.log("i", "You have a(n) {0} in the printer list. Let's check the firmware!".format(machine_name))
|
||||
|
||||
# Nothing to parse, just get the string
|
||||
# TODO: In the future may be done by parsing a JSON file with diferent version for each printer model
|
||||
current_version = reader(current_version_file).readline().rstrip()
|
||||
current_version = self.getCurrentVersion()
|
||||
|
||||
# If it is the first time the version is checked, the checked_version is ''
|
||||
checked_version = Application.getInstance().getPreferences().getValue("info/latest_checked_firmware")
|
||||
# If it is the first time the version is checked, the checked_version is ""
|
||||
setting_key_str = getSettingsKeyForMachine(machine_id)
|
||||
checked_version = Version(Application.getInstance().getPreferences().getValue(setting_key_str))
|
||||
|
||||
# If the checked_version is '', it's because is the first time we check firmware and in this case
|
||||
# If the checked_version is "", it's because is the first time we check firmware and in this case
|
||||
# we will not show the notification, but we will store it for the next time
|
||||
Application.getInstance().getPreferences().setValue("info/latest_checked_firmware", current_version)
|
||||
Application.getInstance().getPreferences().setValue(setting_key_str, current_version)
|
||||
Logger.log("i", "Reading firmware version of %s: checked = %s - latest = %s", machine_name, checked_version, current_version)
|
||||
|
||||
# The first time we want to store the current version, the notification will not be shown,
|
||||
|
@ -62,28 +107,11 @@ class FirmwareUpdateCheckerJob(Job):
|
|||
# notify the user when no new firmware version is available.
|
||||
if (checked_version != "") and (checked_version != current_version):
|
||||
Logger.log("i", "SHOWING FIRMWARE UPDATE MESSAGE")
|
||||
|
||||
message = Message(i18n_catalog.i18nc(
|
||||
"@info Don't translate {machine_name}, since it gets replaced by a printer name!",
|
||||
"New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
|
||||
machine_name=machine_name),
|
||||
title=i18n_catalog.i18nc(
|
||||
"@info:title The %s gets replaced with the printer name.",
|
||||
"New %s firmware available") % machine_name)
|
||||
|
||||
message.addAction("download",
|
||||
i18n_catalog.i18nc("@action:button", "How to update"),
|
||||
"[no_icon]",
|
||||
"[no_description]",
|
||||
button_style=Message.ActionButtonStyle.LINK,
|
||||
button_align=Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
|
||||
|
||||
|
||||
# If we do this in a cool way, the download url should be available in the JSON file
|
||||
if self._set_download_url_callback:
|
||||
self._set_download_url_callback("https://ultimaker.com/en/resources/20500-upgrade-firmware")
|
||||
message = FirmwareUpdateCheckerMessage(machine_id, machine_name, self._lookups.getRedirectUserUrl())
|
||||
message.actionTriggered.connect(self._callback)
|
||||
message.show()
|
||||
else:
|
||||
Logger.log("i", "No machine with name {0} in list of firmware to check.".format(machine_name))
|
||||
|
||||
except Exception as e:
|
||||
Logger.log("w", "Failed to check for new version: %s", e)
|
||||
|
|
35
plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
Normal file
35
plugins/FirmwareUpdateChecker/FirmwareUpdateCheckerLookup.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import List, Optional
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getSettingsKeyForMachine(machine_id: int) -> str:
|
||||
return "info/latest_checked_firmware_for_{0}".format(machine_id)
|
||||
|
||||
|
||||
class FirmwareUpdateCheckerLookup:
|
||||
|
||||
def __init__(self, machine_name, machine_json) -> None:
|
||||
# Parse all the needed lookup-tables from the ".json" file(s) in the resources folder.
|
||||
self._machine_id = machine_json.get("id")
|
||||
self._machine_name = machine_name.lower() # Lower in-case upper-case chars are added to the original json.
|
||||
self._check_urls = [] # type:List[str]
|
||||
for check_url in machine_json.get("check_urls"):
|
||||
self._check_urls.append(check_url)
|
||||
self._redirect_user = machine_json.get("update_url")
|
||||
|
||||
def getMachineId(self) -> Optional[int]:
|
||||
return self._machine_id
|
||||
|
||||
def getMachineName(self) -> Optional[int]:
|
||||
return self._machine_name
|
||||
|
||||
def getCheckUrls(self) -> Optional[List[str]]:
|
||||
return self._check_urls
|
||||
|
||||
def getRedirectUserUrl(self) -> Optional[str]:
|
||||
return self._redirect_user
|
|
@ -0,0 +1,37 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Message import Message
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
# Make a separate class, since we need an extra field: The machine-id that this message is about.
|
||||
class FirmwareUpdateCheckerMessage(Message):
|
||||
STR_ACTION_DOWNLOAD = "download"
|
||||
|
||||
def __init__(self, machine_id: int, machine_name: str, download_url: str) -> None:
|
||||
super().__init__(i18n_catalog.i18nc(
|
||||
"@info Don't translate {machine_name}, since it gets replaced by a printer name!",
|
||||
"New features are available for your {machine_name}! It is recommended to update the firmware on your printer.").format(
|
||||
machine_name = machine_name),
|
||||
title = i18n_catalog.i18nc(
|
||||
"@info:title The %s gets replaced with the printer name.",
|
||||
"New %s firmware available") % machine_name)
|
||||
|
||||
self._machine_id = machine_id
|
||||
self._download_url = download_url
|
||||
|
||||
self.addAction(self.STR_ACTION_DOWNLOAD,
|
||||
i18n_catalog.i18nc("@action:button", "How to update"),
|
||||
"[no_icon]",
|
||||
"[no_description]",
|
||||
button_style = Message.ActionButtonStyle.LINK,
|
||||
button_align = Message.ActionButtonStyle.BUTTON_ALIGN_LEFT)
|
||||
|
||||
def getMachineId(self) -> int:
|
||||
return self._machine_id
|
||||
|
||||
def getDownloadUrl(self) -> str:
|
||||
return self._download_url
|
|
@ -1,12 +1,8 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from . import FirmwareUpdateChecker
|
||||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
69
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
Normal file
69
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.py
Normal file
|
@ -0,0 +1,69 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from cura.MachineAction import MachineAction
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdateState
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, QObject
|
||||
from typing import Optional
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater
|
||||
from cura.PrinterOutput.PrinterOutputDevice import PrinterOutputDevice
|
||||
from UM.Settings.ContainerInterface import ContainerInterface
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Upgrade the firmware of a machine by USB with this action.
|
||||
class FirmwareUpdaterMachineAction(MachineAction):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Update Firmware"))
|
||||
self._qml_url = "FirmwareUpdaterMachineAction.qml"
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
self._active_output_device = None # type: Optional[PrinterOutputDevice]
|
||||
self._active_firmware_updater = None # type: Optional[FirmwareUpdater]
|
||||
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._onEngineCreated)
|
||||
|
||||
def _onEngineCreated(self) -> None:
|
||||
CuraApplication.getInstance().getMachineManager().outputDevicesChanged.connect(self._onOutputDevicesChanged)
|
||||
|
||||
def _onContainerAdded(self, container: "ContainerInterface") -> None:
|
||||
# Add this action as a supported action to all machine definitions if they support USB connection
|
||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||
CuraApplication.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
||||
|
||||
def _onOutputDevicesChanged(self) -> None:
|
||||
if self._active_output_device and self._active_output_device.activePrinter:
|
||||
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.disconnect(self._onControllerCanUpdateFirmwareChanged)
|
||||
|
||||
output_devices = CuraApplication.getInstance().getMachineManager().printerOutputDevices
|
||||
self._active_output_device = output_devices[0] if output_devices else None
|
||||
|
||||
if self._active_output_device and self._active_output_device.activePrinter:
|
||||
self._active_output_device.activePrinter.getController().canUpdateFirmwareChanged.connect(self._onControllerCanUpdateFirmwareChanged)
|
||||
|
||||
self.outputDeviceCanUpdateFirmwareChanged.emit()
|
||||
|
||||
def _onControllerCanUpdateFirmwareChanged(self) -> None:
|
||||
self.outputDeviceCanUpdateFirmwareChanged.emit()
|
||||
|
||||
outputDeviceCanUpdateFirmwareChanged = pyqtSignal()
|
||||
@pyqtProperty(QObject, notify = outputDeviceCanUpdateFirmwareChanged)
|
||||
def firmwareUpdater(self) -> Optional["FirmwareUpdater"]:
|
||||
if self._active_output_device and self._active_output_device.activePrinter.getController().can_update_firmware:
|
||||
self._active_firmware_updater = self._active_output_device.getFirmwareUpdater()
|
||||
return self._active_firmware_updater
|
||||
|
||||
elif self._active_firmware_updater and self._active_firmware_updater.firmwareUpdateState not in [FirmwareUpdateState.idle, FirmwareUpdateState.completed]:
|
||||
# During a firmware update, the PrinterOutputDevice is disconnected but the FirmwareUpdater is still there
|
||||
return self._active_firmware_updater
|
||||
|
||||
self._active_firmware_updater = None
|
||||
return None
|
191
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
Normal file
191
plugins/FirmwareUpdater/FirmwareUpdaterMachineAction.qml
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) 2018 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2 // For filedialog
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||
property bool canUpdateFirmware: activeOutputDevice ? activeOutputDevice.activePrinter.canUpdateFirmware : false
|
||||
|
||||
Column
|
||||
{
|
||||
id: firmwareUpdaterMachineAction
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Update Firmware")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18
|
||||
}
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property string firmwareName: Cura.MachineManager.activeMachine.getDefaultFirmwareName()
|
||||
Button
|
||||
{
|
||||
id: autoUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
|
||||
enabled: parent.firmwareName != "" && canUpdateFirmware
|
||||
onClicked:
|
||||
{
|
||||
updateProgressDialog.visible = true;
|
||||
activeOutputDevice.updateFirmware(parent.firmwareName);
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: manualUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Upload custom Firmware");
|
||||
enabled: canUpdateFirmware
|
||||
onClicked:
|
||||
{
|
||||
customFirmwareDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
visible: !printerConnected && !updateProgressDialog.visible
|
||||
text: catalog.i18nc("@label", "Firmware can not be updated because there is no connection with the printer.");
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
visible: printerConnected && !canUpdateFirmware
|
||||
text: catalog.i18nc("@label", "Firmware can not be updated because the connection with the printer does not support upgrading firmware.");
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: customFirmwareDialog
|
||||
title: catalog.i18nc("@title:window", "Select custom firmware")
|
||||
nameFilters: "Firmware image files (*.hex)"
|
||||
selectExisting: true
|
||||
onAccepted:
|
||||
{
|
||||
updateProgressDialog.visible = true;
|
||||
activeOutputDevice.updateFirmware(fileUrl);
|
||||
}
|
||||
}
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: updateProgressDialog
|
||||
|
||||
width: minimumWidth
|
||||
minimumWidth: 500 * screenScaleFactor
|
||||
height: minimumHeight
|
||||
minimumHeight: 100 * screenScaleFactor
|
||||
|
||||
modality: Qt.ApplicationModal
|
||||
|
||||
title: catalog.i18nc("@title:window","Firmware Update")
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
|
||||
text: {
|
||||
if(manager.firmwareUpdater == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
switch (manager.firmwareUpdater.firmwareUpdateState)
|
||||
{
|
||||
case 0:
|
||||
return ""; //Not doing anything (eg; idling)
|
||||
case 1:
|
||||
return catalog.i18nc("@label","Updating firmware.");
|
||||
case 2:
|
||||
return catalog.i18nc("@label","Firmware update completed.");
|
||||
case 3:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an unknown error.");
|
||||
case 4:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an communication error.");
|
||||
case 5:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an input/output error.");
|
||||
case 6:
|
||||
return catalog.i18nc("@label","Firmware update failed due to missing firmware.");
|
||||
}
|
||||
}
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
id: prog
|
||||
value: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareProgress : 0
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
indeterminate:
|
||||
{
|
||||
if(manager.firmwareUpdater == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return manager.firmwareUpdater.firmwareProgress < 1 && manager.firmwareUpdater.firmwareProgress > 0;
|
||||
}
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button","Close");
|
||||
enabled: (manager.firmwareUpdater != null) ? manager.firmwareUpdater.firmwareUpdateState != 1 : true;
|
||||
onClicked: updateProgressDialog.visible = false;
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
12
plugins/FirmwareUpdater/__init__.py
Normal file
12
plugins/FirmwareUpdater/__init__.py
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import FirmwareUpdaterMachineAction
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": [
|
||||
FirmwareUpdaterMachineAction.FirmwareUpdaterMachineAction()
|
||||
]}
|
8
plugins/FirmwareUpdater/plugin.json
Normal file
8
plugins/FirmwareUpdater/plugin.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Firmware Updater",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -44,6 +44,7 @@ class FlavorParser:
|
|||
self._extruder_offsets = {} # type: Dict[int, List[float]] # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
||||
self._current_layer_thickness = 0.2 # default
|
||||
self._filament_diameter = 2.85 # default
|
||||
self._previous_extrusion_value = 0.0 # keep track of the filament retractions
|
||||
|
||||
CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||
|
||||
|
@ -182,6 +183,7 @@ class FlavorParser:
|
|||
new_extrusion_value = params.e if self._is_absolute_extrusion else e[self._extruder_number] + params.e
|
||||
if new_extrusion_value > e[self._extruder_number]:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], self._layer_type]) # extrusion
|
||||
self._previous_extrusion_value = new_extrusion_value
|
||||
else:
|
||||
path.append([x, y, z, f, new_extrusion_value + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType]) # retraction
|
||||
e[self._extruder_number] = new_extrusion_value
|
||||
|
@ -191,6 +193,12 @@ class FlavorParser:
|
|||
if z > self._previous_z and (z - self._previous_z < 1.5):
|
||||
self._current_layer_thickness = z - self._previous_z # allow a tiny overlap
|
||||
self._previous_z = z
|
||||
elif self._previous_extrusion_value > e[self._extruder_number]:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
|
||||
|
||||
# This case only for initial start, for the first coordinate in GCode
|
||||
elif e[self._extruder_number] == 0 and self._previous_extrusion_value == 0:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveRetractionType])
|
||||
else:
|
||||
path.append([x, y, z, f, e[self._extruder_number] + self._extrusion_length_offset[self._extruder_number], LayerPolygon.MoveCombingType])
|
||||
return self._position(x, y, z, f, e)
|
||||
|
@ -235,6 +243,7 @@ class FlavorParser:
|
|||
position.e)
|
||||
|
||||
def processGCode(self, G: int, line: str, position: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
self._previous_extrusion_value = 0.0
|
||||
func = getattr(self, "_gCode%s" % G, None)
|
||||
line = line.split(";", 1)[0] # Remove comments (if any)
|
||||
if func is not None:
|
||||
|
|
|
@ -35,7 +35,7 @@ UM.Dialog
|
|||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label","Height (mm)")
|
||||
text: catalog.i18nc("@action:label", "Height (mm)")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ UM.Dialog
|
|||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label","Base (mm)")
|
||||
text: catalog.i18nc("@action:label", "Base (mm)")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -81,7 +81,7 @@ UM.Dialog
|
|||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label","Width (mm)")
|
||||
text: catalog.i18nc("@action:label", "Width (mm)")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -105,7 +105,7 @@ UM.Dialog
|
|||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label","Depth (mm)")
|
||||
text: catalog.i18nc("@action:label", "Depth (mm)")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
@ -151,7 +151,7 @@ UM.Dialog
|
|||
width: parent.width
|
||||
|
||||
Label {
|
||||
text: catalog.i18nc("@action:label","Smoothing")
|
||||
text: catalog.i18nc("@action:label", "Smoothing")
|
||||
width: 150 * screenScaleFactor
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
|
|
@ -152,7 +152,7 @@ class LegacyProfileReader(ProfileReader):
|
|||
profile.setDirty(True)
|
||||
|
||||
#Serialise and deserialise in order to perform the version upgrade.
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
data = profile.serialize()
|
||||
parser.read_string(data)
|
||||
parser["general"]["version"] = "1"
|
||||
|
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
from . import MachineSettingsAction
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# This example is released under the terms of the AGPLv3 or higher.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import ModelChecker
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
from . import MonitorStage
|
||||
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
|
|
@ -62,6 +62,7 @@ UM.Dialog
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: base.textMargin
|
||||
font: UM.Theme.getFont("large")
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
ListView
|
||||
{
|
||||
|
@ -115,6 +116,7 @@ UM.Dialog
|
|||
{
|
||||
wrapMode: Text.Wrap
|
||||
text: control.text
|
||||
elide: Text.ElideRight
|
||||
color: activeScriptButton.checked ? palette.highlightedText : palette.text
|
||||
}
|
||||
}
|
||||
|
@ -275,6 +277,7 @@ UM.Dialog
|
|||
anchors.leftMargin: base.textMargin
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: base.textMargin
|
||||
elide: Text.ElideRight
|
||||
height: 20 * screenScaleFactor
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
|
||||
|
||||
from . import PostProcessingPlugin
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
|
|
|
@ -3,12 +3,10 @@
|
|||
|
||||
from UM.Platform import Platform
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
if Platform.isWindows():
|
||||
|
|
|
@ -1,12 +1,11 @@
|
|||
# Copyright (c) 2015 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import SliceInfo
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "extension": SliceInfo.SliceInfo()}
|
|
@ -17,7 +17,7 @@ UM.Dialog
|
|||
// This dialog asks the user whether he/she wants to open a project file as a project or import models.
|
||||
id: base
|
||||
|
||||
title: catalog.i18nc("@title:window", "Confirm uninstall ") + toolbox.pluginToUninstall
|
||||
title: catalog.i18nc("@title:window", "Confirm uninstall") + toolbox.pluginToUninstall
|
||||
width: 450 * screenScaleFactor
|
||||
height: 50 * screenScaleFactor + dialogText.height + buttonBar.height
|
||||
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from .src import DiscoverUM3Action
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from .src import UM3OutputDevicePlugin
|
||||
|
||||
def getMetaData():
|
||||
|
|
|
@ -100,8 +100,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
title=i18n_catalog.i18nc("@info:title",
|
||||
"Authentication status"))
|
||||
|
||||
self._authentication_failed_message = Message(i18n_catalog.i18nc("@info:status", ""),
|
||||
title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
|
||||
self._authentication_failed_message = Message("", title=i18n_catalog.i18nc("@info:title", "Authentication Status"))
|
||||
self._authentication_failed_message.addAction("Retry", i18n_catalog.i18nc("@action:button", "Retry"), None,
|
||||
i18n_catalog.i18nc("@info:tooltip", "Re-send the access request"))
|
||||
self._authentication_failed_message.actionTriggered.connect(self._messageCallback)
|
||||
|
|
68
plugins/USBPrinting/AvrFirmwareUpdater.py
Normal file
68
plugins/USBPrinting/AvrFirmwareUpdater.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Logger import Logger
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.FirmwareUpdater import FirmwareUpdater, FirmwareUpdateState
|
||||
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
from serial import SerialException
|
||||
|
||||
from time import sleep
|
||||
|
||||
MYPY = False
|
||||
if MYPY:
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice
|
||||
|
||||
|
||||
class AvrFirmwareUpdater(FirmwareUpdater):
|
||||
def __init__(self, output_device: "PrinterOutputDevice") -> None:
|
||||
super().__init__(output_device)
|
||||
|
||||
def _updateFirmware(self) -> None:
|
||||
try:
|
||||
hex_file = intelHex.readHex(self._firmware_file)
|
||||
assert len(hex_file) > 0
|
||||
except (FileNotFoundError, AssertionError):
|
||||
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
|
||||
return
|
||||
|
||||
programmer = stk500v2.Stk500v2()
|
||||
programmer.progress_callback = self._onFirmwareProgress
|
||||
|
||||
# Ensure that other connections are closed.
|
||||
if self._output_device.isConnected():
|
||||
self._output_device.close()
|
||||
|
||||
try:
|
||||
programmer.connect(self._output_device._serial_port)
|
||||
except:
|
||||
programmer.close()
|
||||
Logger.logException("e", "Failed to update firmware")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
return
|
||||
|
||||
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
sleep(1)
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
try:
|
||||
programmer.programChip(hex_file)
|
||||
except SerialException as e:
|
||||
Logger.log("e", "A serial port exception occured during firmware update: %s" % e)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.io_error)
|
||||
return
|
||||
except Exception as e:
|
||||
Logger.log("e", "An unknown exception occured during firmware update: %s" % e)
|
||||
self._setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
|
||||
return
|
||||
|
||||
programmer.close()
|
||||
|
||||
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
||||
CuraApplication.getInstance().callLater(self._output_device.connect)
|
||||
|
||||
self._cleanupAfterUpdate()
|
|
@ -1,89 +0,0 @@
|
|||
// Copyright (c) 2017 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Window 2.2
|
||||
import QtQuick.Controls 1.2
|
||||
|
||||
import UM 1.1 as UM
|
||||
|
||||
UM.Dialog
|
||||
{
|
||||
id: base;
|
||||
|
||||
width: minimumWidth;
|
||||
minimumWidth: 500 * screenScaleFactor;
|
||||
height: minimumHeight;
|
||||
minimumHeight: 100 * screenScaleFactor;
|
||||
|
||||
visible: true;
|
||||
modality: Qt.ApplicationModal;
|
||||
|
||||
title: catalog.i18nc("@title:window","Firmware Update");
|
||||
|
||||
Column
|
||||
{
|
||||
anchors.fill: parent;
|
||||
|
||||
Label
|
||||
{
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
|
||||
text: {
|
||||
switch (manager.firmwareUpdateState)
|
||||
{
|
||||
case 0:
|
||||
return "" //Not doing anything (eg; idling)
|
||||
case 1:
|
||||
return catalog.i18nc("@label","Updating firmware.")
|
||||
case 2:
|
||||
return catalog.i18nc("@label","Firmware update completed.")
|
||||
case 3:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an unknown error.")
|
||||
case 4:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an communication error.")
|
||||
case 5:
|
||||
return catalog.i18nc("@label","Firmware update failed due to an input/output error.")
|
||||
case 6:
|
||||
return catalog.i18nc("@label","Firmware update failed due to missing firmware.")
|
||||
}
|
||||
}
|
||||
|
||||
wrapMode: Text.Wrap;
|
||||
}
|
||||
|
||||
ProgressBar
|
||||
{
|
||||
id: prog
|
||||
value: manager.firmwareProgress
|
||||
minimumValue: 0
|
||||
maximumValue: 100
|
||||
indeterminate: manager.firmwareProgress < 1 && manager.firmwareProgress > 0
|
||||
anchors
|
||||
{
|
||||
left: parent.left;
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
SystemPalette
|
||||
{
|
||||
id: palette;
|
||||
}
|
||||
|
||||
UM.I18nCatalog { id: catalog; name: "cura"; }
|
||||
}
|
||||
|
||||
rightButtons: [
|
||||
Button
|
||||
{
|
||||
text: catalog.i18nc("@action:button","Close");
|
||||
enabled: manager.firmwareUpdateCompleteStatus;
|
||||
onClicked: base.visible = false;
|
||||
}
|
||||
]
|
||||
}
|
|
@ -4,7 +4,6 @@
|
|||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
|
@ -13,28 +12,21 @@ from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
|||
from cura.PrinterOutput.GenericOutputController import GenericOutputController
|
||||
|
||||
from .AutoDetectBaudJob import AutoDetectBaudJob
|
||||
from .avr_isp import stk500v2, intelHex
|
||||
|
||||
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
|
||||
from .AvrFirmwareUpdater import AvrFirmwareUpdater
|
||||
|
||||
from serial import Serial, SerialException, SerialTimeoutException
|
||||
from threading import Thread, Event
|
||||
from time import time, sleep
|
||||
from time import time
|
||||
from queue import Queue
|
||||
from enum import IntEnum
|
||||
from typing import Union, Optional, List, cast
|
||||
|
||||
import re
|
||||
import functools # Used for reduce
|
||||
import os
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class USBPrinterOutputDevice(PrinterOutputDevice):
|
||||
firmwareProgressChanged = pyqtSignal()
|
||||
firmwareUpdateStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
|
||||
super().__init__(serial_port)
|
||||
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
|
||||
|
@ -59,9 +51,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._all_baud_rates = [115200, 250000, 230400, 57600, 38400, 19200, 9600]
|
||||
|
||||
# Instead of using a timer, we really need the update to be as a thread, as reading from serial can block.
|
||||
self._update_thread = Thread(target=self._update, daemon = True)
|
||||
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon = True)
|
||||
self._update_thread = Thread(target = self._update, daemon = True)
|
||||
|
||||
self._last_temperature_request = None # type: Optional[int]
|
||||
|
||||
|
@ -76,11 +66,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._paused = False
|
||||
self._printer_busy = False # when printer is preheating and waiting (M190/M109), or when waiting for action on the printer
|
||||
|
||||
self._firmware_view = None
|
||||
self._firmware_location = None
|
||||
self._firmware_progress = 0
|
||||
self._firmware_update_state = FirmwareUpdateState.idle
|
||||
|
||||
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
|
||||
|
||||
# Queue for commands that need to be sent.
|
||||
|
@ -89,6 +74,8 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._command_received = Event()
|
||||
self._command_received.set()
|
||||
|
||||
self._firmware_updater = AvrFirmwareUpdater(self)
|
||||
|
||||
CuraApplication.getInstance().getOnExitCallbackManager().addCallback(self._checkActivePrintingUponAppExit)
|
||||
|
||||
# This is a callback function that checks if there is any printing in progress via USB when the application tries
|
||||
|
@ -110,7 +97,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
## Reset USB device settings
|
||||
#
|
||||
def resetDeviceSettings(self):
|
||||
def resetDeviceSettings(self) -> None:
|
||||
self._firmware_name = None
|
||||
|
||||
## Request the current scene to be sent to a USB-connected printer.
|
||||
|
@ -136,93 +123,6 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
|
||||
self._printGCode(gcode_list)
|
||||
|
||||
## Show firmware interface.
|
||||
# This will create the view if its not already created.
|
||||
def showFirmwareInterface(self):
|
||||
if self._firmware_view is None:
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||
self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateFirmware(self, file):
|
||||
# the file path could be url-encoded.
|
||||
if file.startswith("file://"):
|
||||
self._firmware_location = QUrl(file).toLocalFile()
|
||||
else:
|
||||
self._firmware_location = file
|
||||
self.showFirmwareInterface()
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
|
||||
self._update_firmware_thread.start()
|
||||
|
||||
def _updateFirmware(self):
|
||||
# Ensure that other connections are closed.
|
||||
if self._connection_state != ConnectionState.closed:
|
||||
self.close()
|
||||
|
||||
try:
|
||||
hex_file = intelHex.readHex(self._firmware_location)
|
||||
assert len(hex_file) > 0
|
||||
except (FileNotFoundError, AssertionError):
|
||||
Logger.log("e", "Unable to read provided hex file. Could not update firmware.")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.firmware_not_found_error)
|
||||
return
|
||||
|
||||
programmer = stk500v2.Stk500v2()
|
||||
programmer.progress_callback = self._onFirmwareProgress
|
||||
|
||||
try:
|
||||
programmer.connect(self._serial_port)
|
||||
except:
|
||||
programmer.close()
|
||||
Logger.logException("e", "Failed to update firmware")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
return
|
||||
|
||||
# Give programmer some time to connect. Might need more in some cases, but this worked in all tested cases.
|
||||
sleep(1)
|
||||
if not programmer.isConnected():
|
||||
Logger.log("e", "Unable to connect with serial. Could not update firmware")
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.communication_error)
|
||||
try:
|
||||
programmer.programChip(hex_file)
|
||||
except SerialException:
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.io_error)
|
||||
return
|
||||
except:
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.unknown_error)
|
||||
return
|
||||
|
||||
programmer.close()
|
||||
|
||||
# Clean up for next attempt.
|
||||
self._update_firmware_thread = Thread(target=self._updateFirmware, daemon=True)
|
||||
self._firmware_location = ""
|
||||
self._onFirmwareProgress(100)
|
||||
self.setFirmwareUpdateState(FirmwareUpdateState.completed)
|
||||
|
||||
# Try to re-connect with the machine again, which must be done on the Qt thread, so we use call later.
|
||||
CuraApplication.getInstance().callLater(self.connect)
|
||||
|
||||
@pyqtProperty(float, notify = firmwareProgressChanged)
|
||||
def firmwareProgress(self):
|
||||
return self._firmware_progress
|
||||
|
||||
@pyqtProperty(int, notify=firmwareUpdateStateChanged)
|
||||
def firmwareUpdateState(self):
|
||||
return self._firmware_update_state
|
||||
|
||||
def setFirmwareUpdateState(self, state):
|
||||
if self._firmware_update_state != state:
|
||||
self._firmware_update_state = state
|
||||
self.firmwareUpdateStateChanged.emit()
|
||||
|
||||
# Callback function for firmware update progress.
|
||||
def _onFirmwareProgress(self, progress, max_progress = 100):
|
||||
self._firmware_progress = (progress / max_progress) * 100 # Convert to scale of 0-100
|
||||
self.firmwareProgressChanged.emit()
|
||||
|
||||
## Start a print based on a g-code.
|
||||
# \param gcode_list List with gcode (strings).
|
||||
def _printGCode(self, gcode_list: List[str]):
|
||||
|
@ -273,13 +173,19 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
except SerialException:
|
||||
Logger.log("w", "An exception occured while trying to create serial connection")
|
||||
return
|
||||
CuraApplication.getInstance().globalContainerStackChanged.connect(self._onGlobalContainerStackChanged)
|
||||
self._onGlobalContainerStackChanged()
|
||||
self.setConnectionState(ConnectionState.connected)
|
||||
self._update_thread.start()
|
||||
|
||||
def _onGlobalContainerStackChanged(self):
|
||||
container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
num_extruders = container_stack.getProperty("machine_extruder_count", "value")
|
||||
# Ensure that a printer is created.
|
||||
self._printers = [PrinterOutputModel(output_controller=GenericOutputController(self), number_of_extruders=num_extruders)]
|
||||
controller = GenericOutputController(self)
|
||||
controller.setCanUpdateFirmware(True)
|
||||
self._printers = [PrinterOutputModel(output_controller = controller, number_of_extruders = num_extruders)]
|
||||
self._printers[0].updateName(container_stack.getName())
|
||||
self.setConnectionState(ConnectionState.connected)
|
||||
self._update_thread.start()
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
|
@ -296,6 +202,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._command_queue.put(command)
|
||||
else:
|
||||
self._sendCommand(command)
|
||||
|
||||
def _sendCommand(self, command: Union[str, bytes]):
|
||||
if self._serial is None or self._connection_state != ConnectionState.connected:
|
||||
return
|
||||
|
@ -452,7 +359,9 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
elapsed_time = int(time() - self._print_start_time)
|
||||
print_job = self._printers[0].activePrintJob
|
||||
if print_job is None:
|
||||
print_job = PrintJobOutputModel(output_controller = GenericOutputController(self), name= CuraApplication.getInstance().getPrintInformation().jobName)
|
||||
controller = GenericOutputController(self)
|
||||
controller.setCanUpdateFirmware(True)
|
||||
print_job = PrintJobOutputModel(output_controller=controller, name=CuraApplication.getInstance().getPrintInformation().jobName)
|
||||
print_job.updateState("printing")
|
||||
self._printers[0].updateActivePrintJob(print_job)
|
||||
|
||||
|
@ -463,13 +372,3 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
print_job.updateTimeTotal(estimated_time)
|
||||
|
||||
self._gcode_position += 1
|
||||
|
||||
|
||||
class FirmwareUpdateState(IntEnum):
|
||||
idle = 0
|
||||
updating = 1
|
||||
completed = 2
|
||||
unknown_error = 3
|
||||
communication_error = 4
|
||||
io_error = 5
|
||||
firmware_not_found_error = 6
|
||||
|
|
|
@ -2,14 +2,12 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import threading
|
||||
import platform
|
||||
import time
|
||||
import serial.tools.list_ports
|
||||
|
||||
from PyQt5.QtCore import QObject, pyqtSlot, pyqtProperty, pyqtSignal
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Resources import Resources
|
||||
from UM.Signal import Signal, signalemitter
|
||||
from UM.OutputDevice.OutputDevicePlugin import OutputDevicePlugin
|
||||
from UM.i18n import i18nCatalog
|
||||
|
@ -87,65 +85,6 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
self._addRemovePorts(port_list)
|
||||
time.sleep(5)
|
||||
|
||||
@pyqtSlot(result = str)
|
||||
def getDefaultFirmwareName(self):
|
||||
# Check if there is a valid global container stack
|
||||
global_container_stack = self._application.getGlobalContainerStack()
|
||||
if not global_container_stack:
|
||||
Logger.log("e", "There is no global container stack. Can not update firmware.")
|
||||
self._firmware_view.close()
|
||||
return ""
|
||||
|
||||
# The bottom of the containerstack is the machine definition
|
||||
machine_id = global_container_stack.getBottom().id
|
||||
|
||||
machine_has_heated_bed = global_container_stack.getProperty("machine_heated_bed", "value")
|
||||
|
||||
if platform.system() == "Linux":
|
||||
baudrate = 115200
|
||||
else:
|
||||
baudrate = 250000
|
||||
|
||||
# NOTE: The keyword used here is the id of the machine. You can find the id of your machine in the *.json file, eg.
|
||||
# https://github.com/Ultimaker/Cura/blob/master/resources/machines/ultimaker_original.json#L2
|
||||
# The *.hex files are stored at a seperate repository:
|
||||
# https://github.com/Ultimaker/cura-binary-data/tree/master/cura/resources/firmware
|
||||
machine_without_extras = {"bq_witbox" : "MarlinWitbox.hex",
|
||||
"bq_hephestos_2" : "MarlinHephestos2.hex",
|
||||
"ultimaker_original" : "MarlinUltimaker-{baudrate}.hex",
|
||||
"ultimaker_original_plus" : "MarlinUltimaker-UMOP-{baudrate}.hex",
|
||||
"ultimaker_original_dual" : "MarlinUltimaker-{baudrate}-dual.hex",
|
||||
"ultimaker2" : "MarlinUltimaker2.hex",
|
||||
"ultimaker2_go" : "MarlinUltimaker2go.hex",
|
||||
"ultimaker2_plus" : "MarlinUltimaker2plus.hex",
|
||||
"ultimaker2_extended" : "MarlinUltimaker2extended.hex",
|
||||
"ultimaker2_extended_plus" : "MarlinUltimaker2extended-plus.hex",
|
||||
}
|
||||
machine_with_heated_bed = {"ultimaker_original" : "MarlinUltimaker-HBK-{baudrate}.hex",
|
||||
"ultimaker_original_dual" : "MarlinUltimaker-HBK-{baudrate}-dual.hex",
|
||||
}
|
||||
##TODO: Add check for multiple extruders
|
||||
hex_file = None
|
||||
if machine_id in machine_without_extras.keys(): # The machine needs to be defined here!
|
||||
if machine_id in machine_with_heated_bed.keys() and machine_has_heated_bed:
|
||||
Logger.log("d", "Choosing firmware with heated bed enabled for machine %s.", machine_id)
|
||||
hex_file = machine_with_heated_bed[machine_id] # Return firmware with heated bed enabled
|
||||
else:
|
||||
Logger.log("d", "Choosing basic firmware for machine %s.", machine_id)
|
||||
hex_file = machine_without_extras[machine_id] # Return "basic" firmware
|
||||
else:
|
||||
Logger.log("w", "There is no firmware for machine %s.", machine_id)
|
||||
|
||||
if hex_file:
|
||||
try:
|
||||
return Resources.getPath(CuraApplication.ResourceTypes.Firmware, hex_file.format(baudrate=baudrate))
|
||||
except FileNotFoundError:
|
||||
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
|
||||
return ""
|
||||
else:
|
||||
Logger.log("w", "Could not find any firmware for machine %s.", machine_id)
|
||||
return ""
|
||||
|
||||
## Helper to identify serial ports (and scan for them)
|
||||
def _addRemovePorts(self, serial_ports):
|
||||
# First, find and add all new or changed keys
|
||||
|
|
|
@ -2,9 +2,6 @@
|
|||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import USBPrinterOutputDeviceManager
|
||||
from PyQt5.QtQml import qmlRegisterSingletonType
|
||||
from UM.i18n import i18nCatalog
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
def getMetaData():
|
||||
|
@ -14,5 +11,4 @@ def getMetaData():
|
|||
def register(app):
|
||||
# We are violating the QT API here (as we use a factory, which is technically not allowed).
|
||||
# but we don't really have another means for doing this (and it seems to you know -work-)
|
||||
qmlRegisterSingletonType(USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager, "Cura", 1, 0, "USBPrinterManager", USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager.getInstance)
|
||||
return {"output_device": USBPrinterOutputDeviceManager.USBPrinterOutputDeviceManager(app)}
|
||||
|
|
|
@ -17,7 +17,7 @@ Cura.MachineAction
|
|||
property int rightRow: (checkupMachineAction.width * 0.60) | 0
|
||||
property bool heatupHotendStarted: false
|
||||
property bool heatupBedStarted: false
|
||||
property bool usbConnected: Cura.USBPrinterManager.connectedPrinterList.rowCount() > 0
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
Label
|
||||
|
@ -86,7 +86,7 @@ Cura.MachineAction
|
|||
anchors.left: connectionLabel.right
|
||||
anchors.top: parent.top
|
||||
wrapMode: Text.WordWrap
|
||||
text: checkupMachineAction.usbConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
|
||||
text: checkupMachineAction.printerConnected ? catalog.i18nc("@info:status","Connected"): catalog.i18nc("@info:status","Not connected")
|
||||
}
|
||||
//////////////////////////////////////////////////////////
|
||||
Label
|
||||
|
@ -97,7 +97,7 @@ Cura.MachineAction
|
|||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop X: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
@ -107,7 +107,7 @@ Cura.MachineAction
|
|||
anchors.top: connectionLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.xMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
//////////////////////////////////////////////////////////////
|
||||
Label
|
||||
|
@ -118,7 +118,7 @@ Cura.MachineAction
|
|||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Y: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
@ -128,7 +128,7 @@ Cura.MachineAction
|
|||
anchors.top: endstopXLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.yMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
|
@ -139,7 +139,7 @@ Cura.MachineAction
|
|||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Min endstop Z: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
@ -149,7 +149,7 @@ Cura.MachineAction
|
|||
anchors.top: endstopYLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.zMinEndstopTestCompleted ? catalog.i18nc("@info:status","Works") : catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
////////////////////////////////////////////////////////////
|
||||
Label
|
||||
|
@ -161,7 +161,7 @@ Cura.MachineAction
|
|||
anchors.top: endstopZLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Nozzle temperature check: ")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
@ -171,7 +171,7 @@ Cura.MachineAction
|
|||
anchors.left: nozzleTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@info:status","Not checked")
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
Item
|
||||
{
|
||||
|
@ -181,7 +181,7 @@ Cura.MachineAction
|
|||
anchors.top: nozzleTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupHotendStarted ? catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
|
@ -209,7 +209,7 @@ Cura.MachineAction
|
|||
wrapMode: Text.WordWrap
|
||||
text: manager.hotendTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.usbConnected
|
||||
visible: checkupMachineAction.printerConnected
|
||||
}
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
Label
|
||||
|
@ -221,7 +221,7 @@ Cura.MachineAction
|
|||
anchors.top: nozzleTempLabel.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label","Build plate temperature check:")
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
|
||||
Label
|
||||
|
@ -232,7 +232,7 @@ Cura.MachineAction
|
|||
anchors.left: bedTempLabel.right
|
||||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTestCompleted ? catalog.i18nc("@info:status","Not checked"): catalog.i18nc("@info:status","Checked")
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Item
|
||||
{
|
||||
|
@ -242,7 +242,7 @@ Cura.MachineAction
|
|||
anchors.top: bedTempLabel.top
|
||||
anchors.left: bedTempStatus.right
|
||||
anchors.leftMargin: Math.round(UM.Theme.getSize("default_margin").width/2)
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
Button
|
||||
{
|
||||
text: checkupMachineAction.heatupBedStarted ?catalog.i18nc("@action:button","Stop Heating") : catalog.i18nc("@action:button","Start Heating")
|
||||
|
@ -270,7 +270,7 @@ Cura.MachineAction
|
|||
wrapMode: Text.WordWrap
|
||||
text: manager.bedTemperature + "°C"
|
||||
font.bold: true
|
||||
visible: checkupMachineAction.usbConnected && manager.hasHeatedBed
|
||||
visible: checkupMachineAction.printerConnected && manager.hasHeatedBed
|
||||
}
|
||||
Label
|
||||
{
|
||||
|
|
|
@ -1,19 +0,0 @@
|
|||
from UM.Application import Application
|
||||
from UM.Settings.DefinitionContainer import DefinitionContainer
|
||||
from cura.MachineAction import MachineAction
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
## Upgrade the firmware of a machine by USB with this action.
|
||||
class UpgradeFirmwareMachineAction(MachineAction):
|
||||
def __init__(self):
|
||||
super().__init__("UpgradeFirmware", catalog.i18nc("@action", "Upgrade Firmware"))
|
||||
self._qml_url = "UpgradeFirmwareMachineAction.qml"
|
||||
ContainerRegistry.getInstance().containerAdded.connect(self._onContainerAdded)
|
||||
|
||||
def _onContainerAdded(self, container):
|
||||
# Add this action as a supported action to all machine definitions if they support USB connection
|
||||
if isinstance(container, DefinitionContainer) and container.getMetaDataEntry("type") == "machine" and container.getMetaDataEntry("supports_usb_connection"):
|
||||
Application.getInstance().getMachineActionManager().addSupportedAction(container.getId(), self.getKey())
|
|
@ -1,93 +0,0 @@
|
|||
// Copyright (c) 2016 Ultimaker B.V.
|
||||
// Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import QtQuick 2.2
|
||||
import QtQuick.Controls 1.1
|
||||
import QtQuick.Layouts 1.1
|
||||
import QtQuick.Window 2.1
|
||||
import QtQuick.Dialogs 1.2 // For filedialog
|
||||
|
||||
import UM 1.2 as UM
|
||||
import Cura 1.0 as Cura
|
||||
|
||||
|
||||
Cura.MachineAction
|
||||
{
|
||||
anchors.fill: parent;
|
||||
property bool printerConnected: Cura.MachineManager.printerConnected
|
||||
property var activeOutputDevice: printerConnected ? Cura.MachineManager.printerOutputDevices[0] : null
|
||||
|
||||
Item
|
||||
{
|
||||
id: upgradeFirmwareMachineAction
|
||||
anchors.fill: parent;
|
||||
UM.I18nCatalog { id: catalog; name:"cura"}
|
||||
|
||||
Label
|
||||
{
|
||||
id: pageTitle
|
||||
width: parent.width
|
||||
text: catalog.i18nc("@title", "Upgrade Firmware")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pointSize: 18
|
||||
}
|
||||
Label
|
||||
{
|
||||
id: pageDescription
|
||||
anchors.top: pageTitle.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "Firmware is the piece of software running directly on your 3D printer. This firmware controls the step motors, regulates the temperature and ultimately makes your printer work.")
|
||||
}
|
||||
|
||||
Label
|
||||
{
|
||||
id: upgradeText1
|
||||
anchors.top: pageDescription.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
width: parent.width
|
||||
wrapMode: Text.WordWrap
|
||||
text: catalog.i18nc("@label", "The firmware shipping with new printers works, but new versions tend to have more features and improvements.");
|
||||
}
|
||||
|
||||
Row
|
||||
{
|
||||
anchors.top: upgradeText1.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: childrenRect.width
|
||||
spacing: UM.Theme.getSize("default_margin").width
|
||||
property var firmwareName: Cura.USBPrinterManager.getDefaultFirmwareName()
|
||||
Button
|
||||
{
|
||||
id: autoUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Automatically upgrade Firmware");
|
||||
enabled: parent.firmwareName != "" && activeOutputDevice
|
||||
onClicked:
|
||||
{
|
||||
activeOutputDevice.updateFirmware(parent.firmwareName)
|
||||
}
|
||||
}
|
||||
Button
|
||||
{
|
||||
id: manualUpgradeButton
|
||||
text: catalog.i18nc("@action:button", "Upload custom Firmware");
|
||||
enabled: activeOutputDevice != null
|
||||
onClicked:
|
||||
{
|
||||
customFirmwareDialog.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog
|
||||
{
|
||||
id: customFirmwareDialog
|
||||
title: catalog.i18nc("@title:window", "Select custom firmware")
|
||||
nameFilters: "Firmware image files (*.hex)"
|
||||
selectExisting: true
|
||||
onAccepted: activeOutputDevice.updateFirmware(fileUrl)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,22 +1,16 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import BedLevelMachineAction
|
||||
from . import UpgradeFirmwareMachineAction
|
||||
from . import UMOUpgradeSelection
|
||||
from . import UM2UpgradeSelection
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
}
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "machine_action": [
|
||||
BedLevelMachineAction.BedLevelMachineAction(),
|
||||
UpgradeFirmwareMachineAction.UpgradeFirmwareMachineAction(),
|
||||
UMOUpgradeSelection.UMOUpgradeSelection(),
|
||||
UM2UpgradeSelection.UM2UpgradeSelection()
|
||||
]}
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
from . import VersionUpgrade21to22
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
upgrade = VersionUpgrade21to22.VersionUpgrade21to22()
|
||||
|
||||
def getMetaData():
|
||||
|
|
|
@ -73,7 +73,7 @@ class VersionUpgrade22to24(VersionUpgrade):
|
|||
|
||||
def __convertVariant(self, variant_path):
|
||||
# Copy the variant to the machine_instances/*_settings.inst.cfg
|
||||
variant_config = configparser.ConfigParser(interpolation=None)
|
||||
variant_config = configparser.ConfigParser(interpolation = None)
|
||||
with open(variant_path, "r", encoding = "utf-8") as fhandle:
|
||||
variant_config.read_file(fhandle)
|
||||
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
from . import VersionUpgrade
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
upgrade = VersionUpgrade.VersionUpgrade22to24()
|
||||
|
||||
def getMetaData():
|
||||
|
|
|
@ -117,7 +117,7 @@ class VersionUpgrade25to26(VersionUpgrade):
|
|||
# \param serialised The serialised form of a quality profile.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradeMachineStack(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
# NOTE: This is for Custom FDM printers
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
from . import VersionUpgrade25to26
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
upgrade = VersionUpgrade25to26.VersionUpgrade25to26()
|
||||
|
||||
def getMetaData():
|
||||
|
|
|
@ -3,9 +3,6 @@
|
|||
|
||||
from . import VersionUpgrade26to27
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
upgrade = VersionUpgrade26to27.VersionUpgrade26to27()
|
||||
|
||||
def getMetaData():
|
||||
|
|
|
@ -84,7 +84,7 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
# \param serialised The serialised form of a preferences file.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradePreferences(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
# Update version numbers
|
||||
|
@ -105,7 +105,7 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
# \param serialised The serialised form of the container file.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradeInstanceContainer(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
for each_section in ("general", "metadata"):
|
||||
|
@ -130,7 +130,7 @@ class VersionUpgrade30to31(VersionUpgrade):
|
|||
# \param serialised The serialised form of a container stack.
|
||||
# \param filename The name of the file to upgrade.
|
||||
def upgradeStack(self, serialised, filename):
|
||||
parser = configparser.ConfigParser(interpolation=None)
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
|
||||
for each_section in ("general", "metadata"):
|
||||
|
|
|
@ -5,7 +5,6 @@ from . import VersionUpgrade33to34
|
|||
|
||||
upgrade = VersionUpgrade33to34.VersionUpgrade33to34()
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"version_upgrade": {
|
||||
|
|
|
@ -5,7 +5,6 @@ from . import VersionUpgrade34to35
|
|||
|
||||
upgrade = VersionUpgrade34to35.VersionUpgrade34to35()
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"version_upgrade": {
|
||||
|
|
|
@ -5,10 +5,7 @@ from . import XmlMaterialProfile
|
|||
from . import XmlMaterialUpgrader
|
||||
|
||||
from UM.MimeTypeDatabase import MimeType, MimeTypeDatabase
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
upgrader = XmlMaterialUpgrader.XmlMaterialUpgrader()
|
||||
|
||||
|
||||
|
|
|
@ -118,6 +118,23 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"FirmwareUpdater": {
|
||||
"package_info": {
|
||||
"package_id": "FirmwareUpdater",
|
||||
"package_type": "plugin",
|
||||
"display_name": "Firmware Updater",
|
||||
"description": "Provides a machine actions for updating firmware.",
|
||||
"package_version": "1.0.0",
|
||||
"sdk_version": 5,
|
||||
"website": "https://ultimaker.com",
|
||||
"author": {
|
||||
"author_id": "Ultimaker",
|
||||
"display_name": "Ultimaker B.V.",
|
||||
"email": "plugins@ultimaker.com",
|
||||
"website": "https://ultimaker.com"
|
||||
}
|
||||
}
|
||||
},
|
||||
"GCodeGzReader": {
|
||||
"package_info": {
|
||||
"package_id": "GCodeGzReader",
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "bq_hephestos_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinHephestos2.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "bq_witbox_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinWitbox.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -9,7 +9,8 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"platform": "creality_ender3_platform.stl",
|
||||
"preferred_quality_type": "draft",
|
||||
"machine_extruder_trains": {
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "creality_ender3_extruder_0"
|
||||
}
|
||||
},
|
||||
|
@ -40,6 +41,9 @@
|
|||
[30, 34]
|
||||
]
|
||||
},
|
||||
"material_diameter": {
|
||||
"default_value": 1.75
|
||||
},
|
||||
"acceleration_enabled": {
|
||||
"default_value": true
|
||||
},
|
||||
|
@ -55,6 +59,9 @@
|
|||
"jerk_travel": {
|
||||
"default_value": 20
|
||||
},
|
||||
"layer_height": {
|
||||
"default_value": 0.10
|
||||
},
|
||||
"layer_height_0": {
|
||||
"default_value": 0.2
|
||||
},
|
||||
|
|
|
@ -1217,11 +1217,11 @@
|
|||
"connect_skin_polygons":
|
||||
{
|
||||
"label": "Connect Top/Bottom Polygons",
|
||||
"description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happend midway over infill this feature can reduce the top surface quality.",
|
||||
"description": "Connect top/bottom skin paths where they run next to each other. For the concentric pattern enabling this setting greatly reduces the travel time, but because the connections can happen midway over infill this feature can reduce the top surface quality.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "(top_layers > 0 or bottom_layers > 0) and top_bottom_pattern == 'concentric'",
|
||||
"limit_to_extruder": "infill_extruder_nr",
|
||||
"limit_to_extruder": "top_bottom_extruder_nr",
|
||||
"settable_per_mesh": true
|
||||
},
|
||||
"skin_angles":
|
||||
|
@ -3918,6 +3918,48 @@
|
|||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_brim_enable":
|
||||
{
|
||||
"label": "Enable Support Brim",
|
||||
"description": "Generate a brim within the support infill regions of the first layer. This brim is printed underneath the support, not around it. Enabling this setting increases the adhesion of support to the build plate.",
|
||||
"type": "bool",
|
||||
"default_value": false,
|
||||
"enabled": "support_enable or support_tree_enable",
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true
|
||||
},
|
||||
"support_brim_width":
|
||||
{
|
||||
"label": "Support Brim Width",
|
||||
"description": "The width of the brim to print underneath the support. A larger brim enhances adhesion to the build plate, at the cost of some extra material.",
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
"default_value": 8.0,
|
||||
"minimum_value": "0.0",
|
||||
"maximum_value_warning": "50.0",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "support_infill_extruder_nr",
|
||||
"children":
|
||||
{
|
||||
"support_brim_line_count":
|
||||
{
|
||||
"label": "Support Brim Line Count",
|
||||
"description": "The number of lines used for the support brim. More brim lines enhance adhesion to the build plate, at the cost of some extra material.",
|
||||
"type": "int",
|
||||
"default_value": 20,
|
||||
"minimum_value": "0",
|
||||
"maximum_value_warning": "50 / skirt_brim_line_width",
|
||||
"value": "math.ceil(support_brim_width / (skirt_brim_line_width * initial_layer_line_width_factor / 100.0))",
|
||||
"enabled": "support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "support_infill_extruder_nr"
|
||||
}
|
||||
}
|
||||
},
|
||||
"support_z_distance":
|
||||
{
|
||||
"label": "Support Z Distance",
|
||||
|
@ -4568,6 +4610,17 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"brim_replaces_support":
|
||||
{
|
||||
"label": "Brim Replaces Support",
|
||||
"description": "Enforce brim to be printed around the model even if that space would otherwise be occupied by support. This replaces some regions of the first layer of support by brim regions.",
|
||||
"type": "bool",
|
||||
"default_value": true,
|
||||
"enabled": "resolveOrValue('adhesion_type') == 'brim' and support_enable",
|
||||
"settable_per_mesh": false,
|
||||
"settable_per_extruder": true,
|
||||
"limit_to_extruder": "adhesion_extruder_nr"
|
||||
},
|
||||
"brim_outside_only":
|
||||
{
|
||||
"label": "Brim Only on Outside",
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"manufacturer": "NA",
|
||||
"file_formats": "text/x-gcode",
|
||||
"has_materials": false,
|
||||
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "makeit_l_dual_1st",
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
"manufacturer": "NA",
|
||||
"file_formats": "text/x-gcode",
|
||||
"has_materials": false,
|
||||
"supported_actions": [ "MachineSettingsAction", "UpgradeFirmware" ],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "makeit_dual_1st",
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
"visible": true,
|
||||
"author": "Ultimaker",
|
||||
"manufacturer": "MakerBot",
|
||||
"machine_x3g_variant": "r1",
|
||||
"file_formats": "application/x3g",
|
||||
"platform_offset": [ 0, 0, 0],
|
||||
"machine_extruder_trains":
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
"platform": "tam_series1.stl",
|
||||
"platform_offset": [-580.0, -6.23, 253.5],
|
||||
"has_materials": false,
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "tam_extruder_0"
|
||||
|
|
|
@ -17,11 +17,12 @@
|
|||
"preferred_variant_name": "0.4 mm",
|
||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||
"first_start_actions": ["UM2UpgradeSelection"],
|
||||
"supported_actions":["UM2UpgradeSelection", "UpgradeFirmware"],
|
||||
"supported_actions":["UM2UpgradeSelection"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2.hex"
|
||||
},
|
||||
"overrides": {
|
||||
"machine_name": { "default_value": "Ultimaker 2" },
|
||||
|
|
|
@ -14,7 +14,8 @@
|
|||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extended_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2extended.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -10,11 +10,11 @@
|
|||
"file_formats": "text/x-gcode",
|
||||
"platform": "ultimaker2_platform.obj",
|
||||
"platform_texture": "Ultimaker2ExtendedPlusbackplate.png",
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_extended_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2extended-plus.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -13,11 +13,11 @@
|
|||
"platform_texture": "Ultimaker2Gobackplate.png",
|
||||
"platform_offset": [0, 0, 0],
|
||||
"first_start_actions": [],
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_go_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2go.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -15,11 +15,11 @@
|
|||
"has_machine_materials": true,
|
||||
"has_machine_quality": true,
|
||||
"first_start_actions": [],
|
||||
"supported_actions": ["UpgradeFirmware"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker2_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker2plus.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -24,7 +24,16 @@
|
|||
},
|
||||
"first_start_actions": [ "DiscoverUM3Action" ],
|
||||
"supported_actions": [ "DiscoverUM3Action" ],
|
||||
"supports_usb_connection": false
|
||||
"supports_usb_connection": false,
|
||||
"firmware_update_info": {
|
||||
"id": 9066,
|
||||
"check_urls":
|
||||
[
|
||||
"http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources",
|
||||
"http://software.ultimaker.com/releases/firmware/9066/stable/version.txt"
|
||||
],
|
||||
"update_url": "https://ultimaker.com/firmware"
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
|
|
|
@ -23,7 +23,16 @@
|
|||
"1": "ultimaker3_extended_extruder_right"
|
||||
},
|
||||
"first_start_actions": [ "DiscoverUM3Action" ],
|
||||
"supported_actions": [ "DiscoverUM3Action" ]
|
||||
"supported_actions": [ "DiscoverUM3Action" ],
|
||||
"firmware_update_info": {
|
||||
"id": 9511,
|
||||
"check_urls":
|
||||
[
|
||||
"http://software.ultimaker.com/jedi/releases/latest.version?utm_source=cura&utm_medium=software&utm_campaign=resources",
|
||||
"http://software.ultimaker.com/releases/firmware/9511/stable/version.txt"
|
||||
],
|
||||
"update_url": "https://ultimaker.com/firmware"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -14,11 +14,13 @@
|
|||
"has_machine_quality": true,
|
||||
"exclude_materials": ["generic_hips", "generic_petg", "generic_bam", "ultimaker_bam", "generic_pva", "ultimaker_pva", "generic_tough_pla", "ultimaker_tough_pla_black", "ultimaker_tough_pla_green", "ultimaker_tough_pla_red", "ultimaker_tough_pla_white"],
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker_original_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-{baudrate}.hex",
|
||||
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -19,8 +19,10 @@
|
|||
"0": "ultimaker_original_dual_1st",
|
||||
"1": "ultimaker_original_dual_2nd"
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-{baudrate}-dual.hex",
|
||||
"firmware_hbk_file": "MarlinUltimaker-HBK-{baudrate}-dual.hex",
|
||||
"first_start_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel", "UpgradeFirmware"]
|
||||
"supported_actions": ["UMOUpgradeSelection", "UMOCheckup", "BedLevel"]
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -12,11 +12,12 @@
|
|||
"platform_texture": "UltimakerPlusbackplate.png",
|
||||
"quality_definition": "ultimaker_original",
|
||||
"first_start_actions": ["UMOCheckup", "BedLevel"],
|
||||
"supported_actions": ["UMOCheckup", "BedLevel", "UpgradeFirmware"],
|
||||
"supported_actions": ["UMOCheckup", "BedLevel"],
|
||||
"machine_extruder_trains":
|
||||
{
|
||||
"0": "ultimaker_original_plus_extruder_0"
|
||||
}
|
||||
},
|
||||
"firmware_file": "MarlinUltimaker-UMOP-{baudrate}.hex"
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
|
|
@ -30,7 +30,12 @@
|
|||
"first_start_actions": [ "DiscoverUM3Action" ],
|
||||
"supported_actions": [ "DiscoverUM3Action" ],
|
||||
"supports_usb_connection": false,
|
||||
"weight": -1
|
||||
"weight": -1,
|
||||
"firmware_update_info": {
|
||||
"id": 9051,
|
||||
"check_urls": ["http://software.ultimaker.com/releases/firmware/9051/stable/version.txt"],
|
||||
"update_url": "https://ultimaker.com/firmware"
|
||||
}
|
||||
},
|
||||
|
||||
"overrides": {
|
||||
|
@ -63,7 +68,7 @@
|
|||
"machine_end_gcode": { "default_value": "" },
|
||||
"prime_tower_position_x": { "default_value": 345 },
|
||||
"prime_tower_position_y": { "default_value": 222.5 },
|
||||
"prime_blob_enable": { "enabled": false },
|
||||
"prime_blob_enable": { "enabled": true, "default_value": false },
|
||||
|
||||
"speed_travel":
|
||||
{
|
||||
|
|
|
@ -18,9 +18,6 @@
|
|||
0,
|
||||
-28,
|
||||
0
|
||||
],
|
||||
"supported_actions": [
|
||||
"UpgradeFirmware"
|
||||
]
|
||||
},
|
||||
"overrides": {
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,14 +9,8 @@
|
|||
},
|
||||
|
||||
"overrides": {
|
||||
"extruder_nr": {
|
||||
"default_value": 0
|
||||
},
|
||||
"machine_nozzle_size": {
|
||||
"default_value": 0.4
|
||||
},
|
||||
"material_diameter": {
|
||||
"default_value": 1.75
|
||||
}
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.5 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.5 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,6 @@
|
|||
"overrides": {
|
||||
"extruder_nr": { "default_value": 0 },
|
||||
"machine_nozzle_size": { "default_value": 0.4 },
|
||||
"material_diameter": { "default_value": 2.85 }
|
||||
"material_diameter": { "default_value": 1.75 }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,18 @@ UM.Dialog
|
|||
width: minimumWidth
|
||||
height: minimumHeight
|
||||
|
||||
Rectangle
|
||||
{
|
||||
width: parent.width + 2 * margin // margin from Dialog.qml
|
||||
height: version.y + version.height + margin
|
||||
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: - margin
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
color: UM.Theme.getColor("viewport_background")
|
||||
}
|
||||
|
||||
Image
|
||||
{
|
||||
id: logo
|
||||
|
@ -42,6 +54,7 @@ UM.Dialog
|
|||
|
||||
text: catalog.i18nc("@label","version: %1").arg(UM.Application.version)
|
||||
font: UM.Theme.getFont("large")
|
||||
color: UM.Theme.getColor("text")
|
||||
anchors.right : logo.right
|
||||
anchors.top: logo.bottom
|
||||
anchors.topMargin: (UM.Theme.getSize("default_margin").height / 2) | 0
|
||||
|
@ -75,6 +88,7 @@ UM.Dialog
|
|||
|
||||
ScrollView
|
||||
{
|
||||
id: credits
|
||||
anchors.top: creditsNotes.bottom
|
||||
anchors.topMargin: UM.Theme.getSize("default_margin").height
|
||||
|
||||
|
@ -128,7 +142,11 @@ UM.Dialog
|
|||
projectsModel.append({ name:"SciPy", description: catalog.i18nc("@label", "Support library for scientific computing"), license: "BSD-new", url: "https://www.scipy.org/" });
|
||||
projectsModel.append({ name:"NumPy", description: catalog.i18nc("@label", "Support library for faster math"), license: "BSD", url: "http://www.numpy.org/" });
|
||||
projectsModel.append({ name:"NumPy-STL", description: catalog.i18nc("@label", "Support library for handling STL files"), license: "BSD", url: "https://github.com/WoLpH/numpy-stl" });
|
||||
projectsModel.append({ name:"Shapely", description: catalog.i18nc("@label", "Support library for handling planar objects"), license: "BSD", url: "https://github.com/Toblerity/Shapely" });
|
||||
projectsModel.append({ name:"Trimesh", description: catalog.i18nc("@label", "Support library for handling triangular meshes"), license: "MIT", url: "https://trimsh.org" });
|
||||
projectsModel.append({ name:"NetworkX", description: catalog.i18nc("@label", "Support library for analysis of complex networks"), license: "3-clause BSD", url: "https://networkx.github.io/" });
|
||||
projectsModel.append({ name:"libSavitar", description: catalog.i18nc("@label", "Support library for handling 3MF files"), license: "LGPLv3", url: "https://github.com/ultimaker/libsavitar" });
|
||||
projectsModel.append({ name:"libCharon", description: catalog.i18nc("@label", "Support library for file metadata and streaming"), license: "LGPLv3", url: "https://github.com/ultimaker/libcharon" });
|
||||
projectsModel.append({ name:"PySerial", description: catalog.i18nc("@label", "Serial communication library"), license: "Python", url: "http://pyserial.sourceforge.net/" });
|
||||
projectsModel.append({ name:"python-zeroconf", description: catalog.i18nc("@label", "ZeroConf discovery library"), license: "LGPL", url: "https://github.com/jstasiak/python-zeroconf" });
|
||||
projectsModel.append({ name:"Clipper", description: catalog.i18nc("@label", "Polygon clipping library"), license: "Boost", url: "http://www.angusj.com/delphi/clipper.php" });
|
||||
|
|
|
@ -86,7 +86,7 @@ Item {
|
|||
printJobTextfield.focus = false;
|
||||
}
|
||||
validator: RegExpValidator {
|
||||
regExp: /^[^\\ \/ \*\?\|\[\]]*$/
|
||||
regExp: /^[^\\\/\*\?\|\[\]]*$/
|
||||
}
|
||||
style: TextFieldStyle{
|
||||
textColor: UM.Theme.getColor("text_scene");
|
||||
|
|
|
@ -18,17 +18,17 @@ Menu
|
|||
|
||||
Instantiator
|
||||
{
|
||||
model: settingVisibilityPresetsModel
|
||||
model: settingVisibilityPresetsModel.items
|
||||
|
||||
MenuItem
|
||||
{
|
||||
text: model.name
|
||||
text: modelData.name
|
||||
checkable: true
|
||||
checked: model.id == settingVisibilityPresetsModel.activePreset
|
||||
checked: modelData.presetId == settingVisibilityPresetsModel.activePreset
|
||||
exclusiveGroup: group
|
||||
onTriggered:
|
||||
{
|
||||
settingVisibilityPresetsModel.setActivePreset(model.id);
|
||||
settingVisibilityPresetsModel.setActivePreset(modelData.presetId);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
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