mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-09 06:45:09 -06:00
Merge branch 'master' into WIP_improve_initialization
Conflicts: cura/AutoSave.py cura/BuildVolume.py cura/CuraApplication.py Contributes to CURA-5164
This commit is contained in:
commit
5704a7b184
41 changed files with 1109 additions and 341 deletions
|
@ -1,76 +0,0 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
|
||||
from UM.Extension import Extension
|
||||
from UM.Application import Application
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
|
||||
|
||||
class AutoSave(Extension):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
Application.getInstance().getPreferences().preferenceChanged.connect(self._triggerTimer)
|
||||
|
||||
self._global_stack = None
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("cura/autosave_delay", 1000 * 10)
|
||||
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer.setInterval(Application.getInstance().getPreferences().getValue("cura/autosave_delay"))
|
||||
self._change_timer.setSingleShot(True)
|
||||
|
||||
self._saving = False
|
||||
|
||||
# At this point, the Application instance has not finished its constructor call yet, so directly using something
|
||||
# like Application.getInstance() is not correct. The initialisation now will only gets triggered after the
|
||||
# application finishes its start up successfully.
|
||||
self._init_timer = QTimer()
|
||||
self._init_timer.setInterval(1000)
|
||||
self._init_timer.setSingleShot(True)
|
||||
self._init_timer.timeout.connect(self.initialize)
|
||||
self._init_timer.start()
|
||||
|
||||
def initialize(self):
|
||||
# only initialise if the application is created and has started
|
||||
from cura.CuraApplication import CuraApplication
|
||||
if not CuraApplication.Created:
|
||||
self._init_timer.start()
|
||||
return
|
||||
if not CuraApplication.getInstance().started:
|
||||
self._init_timer.start()
|
||||
return
|
||||
|
||||
self._change_timer.timeout.connect(self._onTimeout)
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._onGlobalStackChanged)
|
||||
self._onGlobalStackChanged()
|
||||
|
||||
self._triggerTimer()
|
||||
|
||||
def _triggerTimer(self, *args):
|
||||
if not self._saving:
|
||||
self._change_timer.start()
|
||||
|
||||
def _onGlobalStackChanged(self):
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.disconnect(self._triggerTimer)
|
||||
self._global_stack.containersChanged.disconnect(self._triggerTimer)
|
||||
|
||||
self._global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
|
||||
if self._global_stack:
|
||||
self._global_stack.propertyChanged.connect(self._triggerTimer)
|
||||
self._global_stack.containersChanged.connect(self._triggerTimer)
|
||||
|
||||
def _onTimeout(self):
|
||||
self._saving = True # To prevent the save process from triggering another autosave.
|
||||
Logger.log("d", "Autosaving preferences, instances and profiles")
|
||||
|
||||
Application.getInstance().saveSettings()
|
||||
|
||||
Application.getInstance().getPreferences().writeToFile(Resources.getStoragePath(Resources.Preferences, Application.getInstance().getApplicationName() + ".cfg"))
|
||||
|
||||
self._saving = False
|
|
@ -1,13 +0,0 @@
|
|||
# Copyright (c) 2016 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import AutoSave
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
return {}
|
||||
|
||||
def register(app):
|
||||
return { "extension": AutoSave.AutoSave() }
|
|
@ -1,8 +0,0 @@
|
|||
{
|
||||
"name": "Auto Save",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Automatically saves Preferences, Machines and Profiles after changes.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
|
@ -9,7 +9,7 @@ from . import GCodeGzWriter
|
|||
catalog = i18nCatalog("cura")
|
||||
|
||||
def getMetaData():
|
||||
file_extension = "gz" if Platform.isOSX() else "gcode.gz"
|
||||
file_extension = "gcode.gz"
|
||||
return {
|
||||
"mesh_writer": {
|
||||
"output": [{
|
||||
|
|
|
@ -69,10 +69,11 @@ class MonitorStage(CuraStage):
|
|||
self._updateSidebar()
|
||||
|
||||
def _updateMainOverlay(self):
|
||||
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"), "MonitorMainView.qml")
|
||||
main_component_path = os.path.join(PluginRegistry.getInstance().getPluginPath("MonitorStage"),
|
||||
"MonitorMainView.qml")
|
||||
self.addDisplayComponent("main", main_component_path)
|
||||
|
||||
def _updateSidebar(self):
|
||||
# TODO: currently the sidebar component for prepare and monitor stages is the same, this will change with the printer output device refactor!
|
||||
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
|
||||
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
|
||||
"MonitorSidebar.qml")
|
||||
self.addDisplayComponent("sidebar", sidebar_component_path)
|
||||
|
|
|
@ -117,12 +117,21 @@ class PauseAtHeight(Script):
|
|||
}
|
||||
}"""
|
||||
|
||||
def getNextXY(self, layer: str):
|
||||
"""
|
||||
Get the X and Y values for a layer (will be used to get X and Y of
|
||||
the layer after the pause
|
||||
"""
|
||||
lines = layer.split("\n")
|
||||
for line in lines:
|
||||
if self.getValue(line, "X") is not None and self.getValue(line, "Y") is not None:
|
||||
x = self.getValue(line, "X")
|
||||
y = self.getValue(line, "Y")
|
||||
return x, y
|
||||
return 0, 0
|
||||
|
||||
def execute(self, data: list):
|
||||
|
||||
"""data is a list. Each index contains a layer"""
|
||||
|
||||
x = 0.
|
||||
y = 0.
|
||||
pause_at = self.getSettingValueByKey("pause_at")
|
||||
pause_height = self.getSettingValueByKey("pause_height")
|
||||
pause_layer = self.getSettingValueByKey("pause_layer")
|
||||
|
@ -138,73 +147,94 @@ class PauseAtHeight(Script):
|
|||
resume_temperature = self.getSettingValueByKey("resume_temperature")
|
||||
|
||||
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
||||
# with open("out.txt", "w") as f:
|
||||
# f.write(T)
|
||||
|
||||
# use offset to calculate the current height: <current_height> = <current_z> - <layer_0_z>
|
||||
layer_0_z = 0.
|
||||
current_z = 0
|
||||
got_first_g_cmd_on_layer_0 = False
|
||||
|
||||
nbr_negative_layers = 0
|
||||
|
||||
for index, layer in enumerate(data):
|
||||
lines = layer.split("\n")
|
||||
|
||||
# Scroll each line of instruction for each layer in the G-code
|
||||
for line in lines:
|
||||
# Fist positive layer reached
|
||||
if ";LAYER:0" in line:
|
||||
layers_started = True
|
||||
# Count nbr of negative layers (raft)
|
||||
elif ";LAYER:-" in line:
|
||||
nbr_negative_layers += 1
|
||||
if not layers_started:
|
||||
continue
|
||||
|
||||
# If a Z instruction is in the line, read the current Z
|
||||
if self.getValue(line, "Z") is not None:
|
||||
current_z = self.getValue(line, "Z")
|
||||
|
||||
if pause_at == "height":
|
||||
# Ignore if the line is not G1 or G0
|
||||
if self.getValue(line, "G") != 1 and self.getValue(line, "G") != 0:
|
||||
continue
|
||||
|
||||
# This block is executed once, the first time there is a G
|
||||
# command, to get the z offset (z for first positive layer)
|
||||
if not got_first_g_cmd_on_layer_0:
|
||||
layer_0_z = current_z
|
||||
got_first_g_cmd_on_layer_0 = True
|
||||
|
||||
x = self.getValue(line, "X", x)
|
||||
y = self.getValue(line, "Y", y)
|
||||
|
||||
current_height = current_z - layer_0_z
|
||||
|
||||
if current_height < pause_height:
|
||||
break #Try the next layer.
|
||||
else: #Pause at layer.
|
||||
break # Try the next layer.
|
||||
|
||||
# Pause at layer
|
||||
else:
|
||||
if not line.startswith(";LAYER:"):
|
||||
continue
|
||||
current_layer = line[len(";LAYER:"):]
|
||||
try:
|
||||
current_layer = int(current_layer)
|
||||
except ValueError: #Couldn't cast to int. Something is wrong with this g-code data.
|
||||
continue
|
||||
if current_layer < pause_layer:
|
||||
break #Try the next layer.
|
||||
|
||||
prevLayer = data[index - 1]
|
||||
prevLines = prevLayer.split("\n")
|
||||
# Couldn't cast to int. Something is wrong with this
|
||||
# g-code data
|
||||
except ValueError:
|
||||
continue
|
||||
if current_layer < pause_layer - nbr_negative_layers:
|
||||
continue
|
||||
|
||||
# Get X and Y from the next layer (better position for
|
||||
# the nozzle)
|
||||
next_layer = data[index + 1]
|
||||
x, y = self.getNextXY(next_layer)
|
||||
|
||||
prev_layer = data[index - 1]
|
||||
prev_lines = prev_layer.split("\n")
|
||||
current_e = 0.
|
||||
|
||||
# Access last layer, browse it backwards to find
|
||||
# last extruder absolute position
|
||||
for prevLine in reversed(prevLines):
|
||||
for prevLine in reversed(prev_lines):
|
||||
current_e = self.getValue(prevLine, "E", -1)
|
||||
if current_e >= 0:
|
||||
break
|
||||
|
||||
# include a number of previous layers
|
||||
for i in range(1, redo_layers + 1):
|
||||
prevLayer = data[index - i]
|
||||
layer = prevLayer + layer
|
||||
prev_layer = data[index - i]
|
||||
layer = prev_layer + layer
|
||||
|
||||
# Get extruder's absolute position at the
|
||||
# begining of the first layer redone
|
||||
# beginning of the first layer redone
|
||||
# see https://github.com/nallath/PostProcessingPlugin/issues/55
|
||||
if i == redo_layers:
|
||||
prevLines = prevLayer.split("\n")
|
||||
for line in prevLines:
|
||||
# Get X and Y from the next layer (better position for
|
||||
# the nozzle)
|
||||
x, y = self.getNextXY(layer)
|
||||
prev_lines = prev_layer.split("\n")
|
||||
for line in prev_lines:
|
||||
new_e = self.getValue(line, 'E', current_e)
|
||||
|
||||
if new_e != current_e:
|
||||
current_e = new_e
|
||||
break
|
||||
|
@ -213,61 +243,63 @@ class PauseAtHeight(Script):
|
|||
prepend_gcode += ";added code by post processing\n"
|
||||
prepend_gcode += ";script: PauseAtHeight.py\n"
|
||||
if pause_at == "height":
|
||||
prepend_gcode += ";current z: {z}\n".format(z = current_z)
|
||||
prepend_gcode += ";current height: {height}\n".format(height = current_height)
|
||||
prepend_gcode += ";current z: {z}\n".format(z=current_z)
|
||||
prepend_gcode += ";current height: {height}\n".format(height=current_height)
|
||||
else:
|
||||
prepend_gcode += ";current layer: {layer}\n".format(layer = current_layer)
|
||||
prepend_gcode += ";current layer: {layer}\n".format(layer=current_layer)
|
||||
|
||||
# Retraction
|
||||
prepend_gcode += self.putValue(M = 83) + "\n"
|
||||
prepend_gcode += self.putValue(M=83) + "\n"
|
||||
if retraction_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
|
||||
|
||||
# Move the head away
|
||||
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
|
||||
prepend_gcode += self.putValue(G = 1, X = park_x, Y = park_y, F = 9000) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n"
|
||||
|
||||
# This line should be ok
|
||||
prepend_gcode += self.putValue(G=1, X=park_x, Y=park_y, F=9000) + "\n"
|
||||
|
||||
if current_z < 15:
|
||||
prepend_gcode += self.putValue(G = 1, Z = 15, F = 300) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
|
||||
|
||||
# Disable the E steppers
|
||||
prepend_gcode += self.putValue(M = 84, E = 0) + "\n"
|
||||
prepend_gcode += self.putValue(M=84, E=0) + "\n"
|
||||
|
||||
# Set extruder standby temperature
|
||||
prepend_gcode += self.putValue(M = 104, S = standby_temperature) + "; standby temperature\n"
|
||||
prepend_gcode += self.putValue(M=104, S=standby_temperature) + "; standby temperature\n"
|
||||
|
||||
# Wait till the user continues printing
|
||||
prepend_gcode += self.putValue(M = 0) + ";Do the actual pause\n"
|
||||
prepend_gcode += self.putValue(M=0) + ";Do the actual pause\n"
|
||||
|
||||
# Set extruder resume temperature
|
||||
prepend_gcode += self.putValue(M = 109, S = resume_temperature) + "; resume temperature\n"
|
||||
prepend_gcode += self.putValue(M=109, S=resume_temperature) + "; resume temperature\n"
|
||||
|
||||
# Push the filament back,
|
||||
if retraction_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n"
|
||||
|
||||
# Optionally extrude material
|
||||
if extrude_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = extrude_amount, F = extrude_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, E=extrude_amount, F=extrude_speed * 60) + "\n"
|
||||
|
||||
# and retract again, the properly primes the nozzle
|
||||
# when changing filament.
|
||||
if retraction_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = -retraction_amount, F = retraction_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, E=-retraction_amount, F=retraction_speed * 60) + "\n"
|
||||
|
||||
# Move the head back
|
||||
prepend_gcode += self.putValue(G = 1, Z = current_z + 1, F = 300) + "\n"
|
||||
prepend_gcode += self.putValue(G = 1, X = x, Y = y, F = 9000) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, Z=current_z + 1, F=300) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, X=x, Y=y, F=9000) + "\n"
|
||||
if retraction_amount != 0:
|
||||
prepend_gcode += self.putValue(G = 1, E = retraction_amount, F = retraction_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G = 1, F = 9000) + "\n"
|
||||
prepend_gcode += self.putValue(M = 82) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, E=retraction_amount, F=retraction_speed * 60) + "\n"
|
||||
prepend_gcode += self.putValue(G=1, F=9000) + "\n"
|
||||
prepend_gcode += self.putValue(M=82) + "\n"
|
||||
|
||||
# reset extrude value to pre pause value
|
||||
prepend_gcode += self.putValue(G = 92, E = current_e) + "\n"
|
||||
prepend_gcode += self.putValue(G=92, E=current_e) + "\n"
|
||||
|
||||
layer = prepend_gcode + layer
|
||||
|
||||
|
||||
# Override the data of this layer with the
|
||||
# modified data
|
||||
data[index] = layer
|
||||
|
|
|
@ -14,5 +14,6 @@ class PrepareStage(CuraStage):
|
|||
Application.getInstance().engineCreatedSignal.connect(self._engineCreated)
|
||||
|
||||
def _engineCreated(self):
|
||||
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles), "Sidebar.qml")
|
||||
sidebar_component_path = os.path.join(Resources.getPath(Application.getInstance().ResourceTypes.QmlFiles),
|
||||
"PrepareSidebar.qml")
|
||||
self.addDisplayComponent("sidebar", sidebar_component_path)
|
||||
|
|
|
@ -24,17 +24,26 @@ from .PackagesModel import PackagesModel
|
|||
|
||||
i18n_catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
## The Toolbox class is responsible of communicating with the server through the API
|
||||
class Toolbox(QObject, Extension):
|
||||
|
||||
DEFAULT_PACKAGES_API_ROOT = "https://api.ultimaker.com"
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._package_manager = None
|
||||
self._plugin_registry = Application.getInstance().getPluginRegistry()
|
||||
self._packages_api_root = self._getPackagesApiRoot()
|
||||
self._packages_version = self._getPackagesVersion()
|
||||
self._api_version = 1
|
||||
self._api_url = "https://api.ultimaker.com/cura-packages/v{api_version}/cura/v{package_version}".format( api_version = self._api_version, package_version = self._packages_version)
|
||||
self._api_url = "{api_root}/cura-packages/v{api_version}/cura/v{package_version}".format(
|
||||
api_root = self._packages_api_root,
|
||||
api_version = self._api_version,
|
||||
package_version = self._packages_version
|
||||
)
|
||||
|
||||
# Network:
|
||||
self._get_packages_request = None
|
||||
|
@ -153,6 +162,15 @@ class Toolbox(QObject, Extension):
|
|||
def _onAppInitialized(self) -> None:
|
||||
self._package_manager = Application.getInstance().getCuraPackageManager()
|
||||
|
||||
# Get the API root for the packages API depending on Cura version settings.
|
||||
def _getPackagesApiRoot(self) -> str:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self.DEFAULT_PACKAGES_API_ROOT
|
||||
if not hasattr(cura.CuraVersion, "CuraPackagesApiRoot"):
|
||||
return self.DEFAULT_PACKAGES_API_ROOT
|
||||
return cura.CuraVersion.CuraPackagesApiRoot
|
||||
|
||||
# Get the packages version depending on Cura version settings.
|
||||
def _getPackagesVersion(self) -> int:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self._plugin_registry.APIVersion
|
||||
|
|
|
@ -308,7 +308,21 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
|
||||
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
|
||||
# Update all temperature values
|
||||
for match, extruder in zip(extruder_temperature_matches, self._printers[0].extruders):
|
||||
matched_extruder_nrs = []
|
||||
for match in extruder_temperature_matches:
|
||||
extruder_nr = 0
|
||||
if match[0] != b"":
|
||||
extruder_nr = int(match[0])
|
||||
|
||||
if extruder_nr in matched_extruder_nrs:
|
||||
continue
|
||||
matched_extruder_nrs.append(extruder_nr)
|
||||
|
||||
if extruder_nr >= len(self._printers[0].extruders):
|
||||
Logger.log("w", "Printer reports more temperatures than the number of configured extruders")
|
||||
continue
|
||||
|
||||
extruder = self._printers[0].extruders[extruder_nr]
|
||||
if match[1]:
|
||||
extruder.updateHotendTemperature(float(match[1]))
|
||||
if match[2]:
|
||||
|
|
|
@ -60,7 +60,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
self._check_updates = True
|
||||
self._update_thread.start()
|
||||
|
||||
def stop(self):
|
||||
def stop(self, store_data: bool = True):
|
||||
self._check_updates = False
|
||||
|
||||
def _onConnectionStateChanged(self, serial_port):
|
||||
|
@ -79,10 +79,11 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
if container_stack is None:
|
||||
time.sleep(5)
|
||||
continue
|
||||
port_list = [] # Just an empty list; all USB devices will be removed.
|
||||
if container_stack.getMetaDataEntry("supports_usb_connection"):
|
||||
port_list = self.getSerialPortList(only_list_usb=True)
|
||||
else:
|
||||
port_list = [] # Just use an empty list; all USB devices will be removed.
|
||||
machine_file_formats = [file_type.strip() for file_type in container_stack.getMetaDataEntry("file_formats").split(";")]
|
||||
if "text/x-gcode" in machine_file_formats:
|
||||
port_list = self.getSerialPortList(only_list_usb=True)
|
||||
self._addRemovePorts(port_list)
|
||||
time.sleep(5)
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue