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:
Diego Prado Gesto 2018-05-25 09:39:51 +02:00
commit 5704a7b184
41 changed files with 1109 additions and 341 deletions

View file

@ -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

View file

@ -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() }

View file

@ -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"
}

View file

@ -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": [{

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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]:

View file

@ -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)