mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-11-02 20:52:20 -07:00
Merge remote-tracking branch 'origin/master' into feature_send_material_profiles
This commit is contained in:
commit
8f7370db6c
703 changed files with 272749 additions and 117038 deletions
|
|
@ -1,6 +1,7 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Optional
|
||||
import os.path
|
||||
import zipfile
|
||||
|
||||
|
|
@ -37,8 +38,8 @@ except ImportError:
|
|||
|
||||
## Base implementation for reading 3MF files. Has no support for textures. Only loads meshes!
|
||||
class ThreeMFReader(MeshReader):
|
||||
def __init__(self, application):
|
||||
super().__init__(application)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
MimeType(
|
||||
|
|
@ -168,6 +169,8 @@ class ThreeMFReader(MeshReader):
|
|||
archive = zipfile.ZipFile(file_name, "r")
|
||||
self._base_name = os.path.basename(file_name)
|
||||
parser = Savitar.ThreeMFParser()
|
||||
with open("/tmp/test.xml", "wb") as f:
|
||||
f.write(archive.open("3D/3dmodel.model").read())
|
||||
scene_3mf = parser.parse(archive.open("3D/3dmodel.model").read())
|
||||
self._unit = scene_3mf.getUnit()
|
||||
for node in scene_3mf.getSceneNodes():
|
||||
|
|
@ -198,9 +201,9 @@ class ThreeMFReader(MeshReader):
|
|||
# Second step: 3MF defines the left corner of the machine as center, whereas cura uses the center of the
|
||||
# build volume.
|
||||
if global_container_stack:
|
||||
translation_vector = Vector(x=-global_container_stack.getProperty("machine_width", "value") / 2,
|
||||
y=-global_container_stack.getProperty("machine_depth", "value") / 2,
|
||||
z=0)
|
||||
translation_vector = Vector(x = -global_container_stack.getProperty("machine_width", "value") / 2,
|
||||
y = -global_container_stack.getProperty("machine_depth", "value") / 2,
|
||||
z = 0)
|
||||
translation_matrix = Matrix()
|
||||
translation_matrix.setByTranslation(translation_vector)
|
||||
transformation_matrix.multiply(translation_matrix)
|
||||
|
|
@ -236,23 +239,20 @@ class ThreeMFReader(MeshReader):
|
|||
# * inch
|
||||
# * foot
|
||||
# * meter
|
||||
def _getScaleFromUnit(self, unit):
|
||||
def _getScaleFromUnit(self, unit: Optional[str]) -> Vector:
|
||||
conversion_to_mm = {
|
||||
"micron": 0.001,
|
||||
"millimeter": 1,
|
||||
"centimeter": 10,
|
||||
"meter": 1000,
|
||||
"inch": 25.4,
|
||||
"foot": 304.8
|
||||
}
|
||||
if unit is None:
|
||||
unit = "millimeter"
|
||||
if unit == "micron":
|
||||
scale = 0.001
|
||||
elif unit == "millimeter":
|
||||
scale = 1
|
||||
elif unit == "centimeter":
|
||||
scale = 10
|
||||
elif unit == "inch":
|
||||
scale = 25.4
|
||||
elif unit == "foot":
|
||||
scale = 304.8
|
||||
elif unit == "meter":
|
||||
scale = 1000
|
||||
else:
|
||||
Logger.log("w", "Unrecognised unit %s used. Assuming mm instead", unit)
|
||||
scale = 1
|
||||
elif unit not in conversion_to_mm:
|
||||
Logger.log("w", "Unrecognised unit {unit} used. Assuming mm instead.".format(unit = unit))
|
||||
unit = "millimeter"
|
||||
|
||||
return Vector(scale, scale, scale)
|
||||
scale = conversion_to_mm[unit]
|
||||
return Vector(scale, scale, scale)
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
from configparser import ConfigParser
|
||||
import zipfile
|
||||
import os
|
||||
from typing import List, Tuple
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
|
||||
import xml.etree.ElementTree as ET
|
||||
|
|
@ -38,7 +38,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
class ContainerInfo:
|
||||
def __init__(self, file_name: str, serialized: str, parser: ConfigParser):
|
||||
def __init__(self, file_name: str, serialized: str, parser: ConfigParser) -> None:
|
||||
self.file_name = file_name
|
||||
self.serialized = serialized
|
||||
self.parser = parser
|
||||
|
|
@ -47,14 +47,14 @@ class ContainerInfo:
|
|||
|
||||
|
||||
class QualityChangesInfo:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.name = None
|
||||
self.global_info = None
|
||||
self.extruder_info_dict = {}
|
||||
self.extruder_info_dict = {} # type: Dict[str, ContainerInfo]
|
||||
|
||||
|
||||
class MachineInfo:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.container_id = None
|
||||
self.name = None
|
||||
self.definition_id = None
|
||||
|
|
@ -66,11 +66,11 @@ class MachineInfo:
|
|||
self.definition_changes_info = None
|
||||
self.user_changes_info = None
|
||||
|
||||
self.extruder_info_dict = {}
|
||||
self.extruder_info_dict = {} # type: Dict[str, ExtruderInfo]
|
||||
|
||||
|
||||
class ExtruderInfo:
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
self.position = None
|
||||
self.enabled = True
|
||||
self.variant_info = None
|
||||
|
|
@ -82,7 +82,7 @@ class ExtruderInfo:
|
|||
|
||||
## Base implementation for reading 3MF workspace files.
|
||||
class ThreeMFWorkspaceReader(WorkspaceReader):
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
|
||||
MimeTypeDatabase.addMimeType(
|
||||
|
|
@ -112,28 +112,26 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
# - variant
|
||||
self._ignored_instance_container_types = {"quality", "variant"}
|
||||
|
||||
self._resolve_strategies = {}
|
||||
self._resolve_strategies = {} # type: Dict[str, str]
|
||||
|
||||
self._id_mapping = {}
|
||||
self._id_mapping = {} # type: Dict[str, str]
|
||||
|
||||
# In Cura 2.5 and 2.6, the empty profiles used to have those long names
|
||||
self._old_empty_profile_id_dict = {"empty_%s" % k: "empty" for k in ["material", "variant"]}
|
||||
|
||||
self._is_same_machine_type = False
|
||||
self._old_new_materials = {}
|
||||
self._materials_to_select = {}
|
||||
self._old_new_materials = {} # type: Dict[str, str]
|
||||
self._machine_info = None
|
||||
|
||||
def _clearState(self):
|
||||
self._is_same_machine_type = False
|
||||
self._id_mapping = {}
|
||||
self._old_new_materials = {}
|
||||
self._materials_to_select = {}
|
||||
self._machine_info = None
|
||||
|
||||
## Get a unique name based on the old_id. This is different from directly calling the registry in that it caches results.
|
||||
# This has nothing to do with speed, but with getting consistent new naming for instances & objects.
|
||||
def getNewId(self, old_id):
|
||||
def getNewId(self, old_id: str):
|
||||
if old_id not in self._id_mapping:
|
||||
self._id_mapping[old_id] = self._container_registry.uniqueName(old_id)
|
||||
return self._id_mapping[old_id]
|
||||
|
|
@ -671,7 +669,7 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
|
|||
else:
|
||||
material_container = materials[0]
|
||||
old_material_root_id = material_container.getMetaDataEntry("base_file")
|
||||
if not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
||||
if old_material_root_id is not None and not self._container_registry.isReadOnly(old_material_root_id): # Only create new materials if they are not read only.
|
||||
to_deserialize_material = True
|
||||
|
||||
if self._resolve_strategies["material"] == "override":
|
||||
|
|
|
|||
|
|
@ -187,7 +187,10 @@ class WorkspaceDialog(QObject):
|
|||
|
||||
@pyqtProperty(int, constant = True)
|
||||
def totalNumberOfSettings(self):
|
||||
return len(ContainerRegistry.getInstance().findDefinitionContainers(id="fdmprinter")[0].getAllKeys())
|
||||
general_definition_containers = ContainerRegistry.getInstance().findDefinitionContainers(id = "fdmprinter")
|
||||
if not general_definition_containers:
|
||||
return 0
|
||||
return len(general_definition_containers[0].getAllKeys())
|
||||
|
||||
@pyqtProperty(int, notify = numVisibleSettingsChanged)
|
||||
def numVisibleSettings(self):
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ catalog = i18nCatalog("cura")
|
|||
|
||||
|
||||
def getMetaData() -> Dict:
|
||||
# Workarround for osx not supporting double file extensions correctly.
|
||||
# Workaround for osx not supporting double file extensions correctly.
|
||||
if Platform.isOSX():
|
||||
workspace_extension = "3mf"
|
||||
else:
|
||||
|
|
@ -44,7 +44,7 @@ def getMetaData() -> Dict:
|
|||
|
||||
def register(app):
|
||||
if "3MFReader.ThreeMFReader" in sys.modules:
|
||||
return {"mesh_reader": ThreeMFReader.ThreeMFReader(app),
|
||||
return {"mesh_reader": ThreeMFReader.ThreeMFReader(),
|
||||
"workspace_reader": ThreeMFWorkspaceReader.ThreeMFWorkspaceReader()}
|
||||
else:
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ class ThreeMFWriter(MeshWriter):
|
|||
# Handle per object settings (if any)
|
||||
stack = um_node.callDecoration("getStack")
|
||||
if stack is not None:
|
||||
changed_setting_keys = set(stack.getTop().getAllKeys())
|
||||
changed_setting_keys = stack.getTop().getAllKeys()
|
||||
|
||||
# Ensure that we save the extruder used for this object in a multi-extrusion setup
|
||||
if stack.getProperty("machine_extruder_count", "value") > 1:
|
||||
|
|
|
|||
|
|
@ -1,8 +1,14 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from collections import defaultdict
|
||||
import os
|
||||
from PyQt5.QtCore import QObject, QTimer, pyqtSlot
|
||||
import sys
|
||||
from time import time
|
||||
from typing import Any, cast, Dict, List, Optional, Set, TYPE_CHECKING
|
||||
|
||||
from UM.Backend.Backend import Backend, BackendState
|
||||
from UM.Application import Application
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from UM.Signal import Signal
|
||||
from UM.Logger import Logger
|
||||
|
|
@ -10,29 +16,30 @@ from UM.Message import Message
|
|||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Resources import Resources
|
||||
from UM.Platform import Platform
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from PyQt5.QtCore import QObject, pyqtSlot
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
from UM.Settings.Interfaces import DefinitionContainerInterface
|
||||
from UM.Settings.SettingInstance import SettingInstance #For typing.
|
||||
from UM.Tool import Tool #For typing.
|
||||
|
||||
from collections import defaultdict
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
from . import ProcessSlicedLayersJob
|
||||
from . import StartSliceJob
|
||||
|
||||
import os
|
||||
import sys
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import QTimer
|
||||
from .ProcessSlicedLayersJob import ProcessSlicedLayersJob
|
||||
from .StartSliceJob import StartSliceJob, StartJobResult
|
||||
|
||||
import Arcus
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from cura.Machines.Models.MultiBuildPlateModel import MultiBuildPlateModel
|
||||
from cura.Machines.MachineErrorChecker import MachineErrorChecker
|
||||
from UM.Scene.Scene import Scene
|
||||
from UM.Settings.ContainerStack import ContainerStack
|
||||
|
||||
from UM.i18n import i18nCatalog
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class CuraEngineBackend(QObject, Backend):
|
||||
|
||||
backendError = Signal()
|
||||
|
||||
## Starts the back-end plug-in.
|
||||
|
|
@ -40,16 +47,16 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# This registers all the signal listeners and prepares for communication
|
||||
# with the back-end in general.
|
||||
# CuraEngineBackend is exposed to qml as well.
|
||||
def __init__(self, parent = None):
|
||||
super().__init__(parent = parent)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
# Find out where the engine is located, and how it is called.
|
||||
# This depends on how Cura is packaged and which OS we are running on.
|
||||
executable_name = "CuraEngine"
|
||||
if Platform.isWindows():
|
||||
executable_name += ".exe"
|
||||
default_engine_location = executable_name
|
||||
if os.path.exists(os.path.join(Application.getInstallPrefix(), "bin", executable_name)):
|
||||
default_engine_location = os.path.join(Application.getInstallPrefix(), "bin", executable_name)
|
||||
if os.path.exists(os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)):
|
||||
default_engine_location = os.path.join(CuraApplication.getInstallPrefix(), "bin", executable_name)
|
||||
if hasattr(sys, "frozen"):
|
||||
default_engine_location = os.path.join(os.path.dirname(os.path.abspath(sys.executable)), executable_name)
|
||||
if Platform.isLinux() and not default_engine_location:
|
||||
|
|
@ -61,9 +68,9 @@ class CuraEngineBackend(QObject, Backend):
|
|||
default_engine_location = execpath
|
||||
break
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._multi_build_plate_model = None
|
||||
self._machine_error_checker = None
|
||||
self._application = CuraApplication.getInstance() #type: CuraApplication
|
||||
self._multi_build_plate_model = None #type: MultiBuildPlateModel
|
||||
self._machine_error_checker = None #type: MachineErrorChecker
|
||||
|
||||
if not default_engine_location:
|
||||
raise EnvironmentError("Could not find CuraEngine")
|
||||
|
|
@ -71,16 +78,16 @@ class CuraEngineBackend(QObject, Backend):
|
|||
Logger.log("i", "Found CuraEngine at: %s", default_engine_location)
|
||||
|
||||
default_engine_location = os.path.abspath(default_engine_location)
|
||||
Application.getInstance().getPreferences().addPreference("backend/location", default_engine_location)
|
||||
self._application.getPreferences().addPreference("backend/location", default_engine_location)
|
||||
|
||||
# Workaround to disable layer view processing if layer view is not active.
|
||||
self._layer_view_active = False
|
||||
self._layer_view_active = False #type: bool
|
||||
self._onActiveViewChanged()
|
||||
|
||||
self._stored_layer_data = []
|
||||
self._stored_optimized_layer_data = {} # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||
self._stored_layer_data = [] #type: List[Arcus.PythonMessage]
|
||||
self._stored_optimized_layer_data = {} #type: Dict[int, List[Arcus.PythonMessage]] # key is build plate number, then arrays are stored until they go to the ProcessSlicesLayersJob
|
||||
|
||||
self._scene = self._application.getController().getScene()
|
||||
self._scene = self._application.getController().getScene() #type: Scene
|
||||
self._scene.sceneChanged.connect(self._onSceneChanged)
|
||||
|
||||
# Triggers for auto-slicing. Auto-slicing is triggered as follows:
|
||||
|
|
@ -91,7 +98,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# If there is an error check, stop the auto-slicing timer, and only wait for the error check to be finished
|
||||
# to start the auto-slicing timer again.
|
||||
#
|
||||
self._global_container_stack = None
|
||||
self._global_container_stack = None #type: Optional[ContainerStack]
|
||||
|
||||
# Listeners for receiving messages from the back-end.
|
||||
self._message_handlers["cura.proto.Layer"] = self._onLayerMessage
|
||||
|
|
@ -102,39 +109,39 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self._message_handlers["cura.proto.PrintTimeMaterialEstimates"] = self._onPrintTimeMaterialEstimates
|
||||
self._message_handlers["cura.proto.SlicingFinished"] = self._onSlicingFinishedMessage
|
||||
|
||||
self._start_slice_job = None
|
||||
self._start_slice_job_build_plate = None
|
||||
self._slicing = False # Are we currently slicing?
|
||||
self._restart = False # Back-end is currently restarting?
|
||||
self._tool_active = False # If a tool is active, some tasks do not have to do anything
|
||||
self._always_restart = True # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||
self._process_layers_job = None # The currently active job to process layers, or None if it is not processing layers.
|
||||
self._build_plates_to_be_sliced = [] # what needs slicing?
|
||||
self._engine_is_fresh = True # Is the newly started engine used before or not?
|
||||
self._start_slice_job = None #type: Optional[StartSliceJob]
|
||||
self._start_slice_job_build_plate = None #type: Optional[int]
|
||||
self._slicing = False #type: bool # Are we currently slicing?
|
||||
self._restart = False #type: bool # Back-end is currently restarting?
|
||||
self._tool_active = False #type: bool # If a tool is active, some tasks do not have to do anything
|
||||
self._always_restart = True #type: bool # Always restart the engine when starting a new slice. Don't keep the process running. TODO: Fix engine statelessness.
|
||||
self._process_layers_job = None #type: Optional[ProcessSlicedLayersJob] # The currently active job to process layers, or None if it is not processing layers.
|
||||
self._build_plates_to_be_sliced = [] #type: List[int] # what needs slicing?
|
||||
self._engine_is_fresh = True #type: bool # Is the newly started engine used before or not?
|
||||
|
||||
self._backend_log_max_lines = 20000 # Maximum number of lines to buffer
|
||||
self._error_message = None # Pop-up message that shows errors.
|
||||
self._last_num_objects = defaultdict(int) # Count number of objects to see if there is something changed
|
||||
self._postponed_scene_change_sources = [] # scene change is postponed (by a tool)
|
||||
self._backend_log_max_lines = 20000 #type: int # Maximum number of lines to buffer
|
||||
self._error_message = None #type: Message # Pop-up message that shows errors.
|
||||
self._last_num_objects = defaultdict(int) #type: Dict[int, int] # Count number of objects to see if there is something changed
|
||||
self._postponed_scene_change_sources = [] #type: List[SceneNode] # scene change is postponed (by a tool)
|
||||
|
||||
self._slice_start_time = None
|
||||
self._is_disabled = False
|
||||
self._slice_start_time = None #type: Optional[float]
|
||||
self._is_disabled = False #type: bool
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("general/auto_slice", False)
|
||||
self._application.getPreferences().addPreference("general/auto_slice", False)
|
||||
|
||||
self._use_timer = False
|
||||
self._use_timer = False #type: bool
|
||||
# When you update a setting and other settings get changed through inheritance, many propertyChanged signals are fired.
|
||||
# This timer will group them up, and only slice for the last setting changed signal.
|
||||
# TODO: Properly group propertyChanged signals by whether they are triggered by the same user interaction.
|
||||
self._change_timer = QTimer()
|
||||
self._change_timer = QTimer() #type: QTimer
|
||||
self._change_timer.setSingleShot(True)
|
||||
self._change_timer.setInterval(500)
|
||||
self.determineAutoSlicing()
|
||||
Application.getInstance().getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
self._application.getPreferences().preferenceChanged.connect(self._onPreferencesChanged)
|
||||
|
||||
self._application.initializationFinished.connect(self.initialize)
|
||||
|
||||
def initialize(self):
|
||||
def initialize(self) -> None:
|
||||
self._multi_build_plate_model = self._application.getMultiBuildPlateModel()
|
||||
|
||||
self._application.getController().activeViewChanged.connect(self._onActiveViewChanged)
|
||||
|
|
@ -160,16 +167,16 @@ class CuraEngineBackend(QObject, Backend):
|
|||
#
|
||||
# This function should terminate the engine process.
|
||||
# Called when closing the application.
|
||||
def close(self):
|
||||
def close(self) -> None:
|
||||
# Terminate CuraEngine if it is still running at this point
|
||||
self._terminate()
|
||||
|
||||
## Get the command that is used to call the engine.
|
||||
# This is useful for debugging and used to actually start the engine.
|
||||
# \return list of commands and args / parameters.
|
||||
def getEngineCommand(self):
|
||||
def getEngineCommand(self) -> List[str]:
|
||||
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
|
||||
return [Application.getInstance().getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
||||
return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
|
||||
|
||||
## Emitted when we get a message containing print duration and material amount.
|
||||
# This also implies the slicing has finished.
|
||||
|
|
@ -184,13 +191,13 @@ class CuraEngineBackend(QObject, Backend):
|
|||
slicingCancelled = Signal()
|
||||
|
||||
@pyqtSlot()
|
||||
def stopSlicing(self):
|
||||
def stopSlicing(self) -> None:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
if self._slicing: # We were already slicing. Stop the old job.
|
||||
self._terminate()
|
||||
self._createSocket()
|
||||
|
||||
if self._process_layers_job: # We were processing layers. Stop that, the layers are going to change soon.
|
||||
if self._process_layers_job is not None: # We were processing layers. Stop that, the layers are going to change soon.
|
||||
Logger.log("d", "Aborting process layers job...")
|
||||
self._process_layers_job.abort()
|
||||
self._process_layers_job = None
|
||||
|
|
@ -200,12 +207,12 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
## Manually triggers a reslice
|
||||
@pyqtSlot()
|
||||
def forceSlice(self):
|
||||
def forceSlice(self) -> None:
|
||||
self.markSliceAll()
|
||||
self.slice()
|
||||
|
||||
## Perform a slice of the scene.
|
||||
def slice(self):
|
||||
def slice(self) -> None:
|
||||
Logger.log("d", "Starting to slice...")
|
||||
self._slice_start_time = time()
|
||||
if not self._build_plates_to_be_sliced:
|
||||
|
|
@ -218,10 +225,10 @@ class CuraEngineBackend(QObject, Backend):
|
|||
return
|
||||
|
||||
if not hasattr(self._scene, "gcode_dict"):
|
||||
self._scene.gcode_dict = {}
|
||||
self._scene.gcode_dict = {} #type: ignore #Because we are creating the missing attribute here.
|
||||
|
||||
# see if we really have to slice
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||
build_plate_to_be_sliced = self._build_plates_to_be_sliced.pop(0)
|
||||
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
|
||||
num_objects = self._numObjectsPerBuildPlate()
|
||||
|
|
@ -230,14 +237,14 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self._stored_optimized_layer_data[build_plate_to_be_sliced] = []
|
||||
|
||||
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
|
||||
self._scene.gcode_dict[build_plate_to_be_sliced] = []
|
||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #Because we created this attribute above.
|
||||
Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
|
||||
if self._build_plates_to_be_sliced:
|
||||
self.slice()
|
||||
return
|
||||
|
||||
if Application.getInstance().getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
|
||||
Application.getInstance().getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
|
||||
if self._application.getPrintInformation() and build_plate_to_be_sliced == active_build_plate:
|
||||
self._application.getPrintInformation().setToZeroPrintInformation(build_plate_to_be_sliced)
|
||||
|
||||
if self._process is None:
|
||||
self._createSocket()
|
||||
|
|
@ -247,14 +254,14 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.processingProgress.emit(0.0)
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #[] indexed by build plate number
|
||||
self._scene.gcode_dict[build_plate_to_be_sliced] = [] #type: ignore #[] indexed by build plate number
|
||||
self._slicing = True
|
||||
self.slicingStarted.emit()
|
||||
|
||||
self.determineAutoSlicing() # Switch timer on or off if appropriate
|
||||
|
||||
slice_message = self._socket.createMessage("cura.proto.Slice")
|
||||
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
|
||||
self._start_slice_job = StartSliceJob(slice_message)
|
||||
self._start_slice_job_build_plate = build_plate_to_be_sliced
|
||||
self._start_slice_job.setBuildPlate(self._start_slice_job_build_plate)
|
||||
self._start_slice_job.start()
|
||||
|
|
@ -262,7 +269,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
|
||||
## Terminate the engine process.
|
||||
# Start the engine process by calling _createSocket()
|
||||
def _terminate(self):
|
||||
def _terminate(self) -> None:
|
||||
self._slicing = False
|
||||
self._stored_layer_data = []
|
||||
if self._start_slice_job_build_plate in self._stored_optimized_layer_data:
|
||||
|
|
@ -274,7 +281,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.processingProgress.emit(0)
|
||||
Logger.log("d", "Attempting to kill the engine process")
|
||||
|
||||
if Application.getInstance().getUseExternalBackend():
|
||||
if self._application.getUseExternalBackend():
|
||||
return
|
||||
|
||||
if self._process is not None:
|
||||
|
|
@ -295,7 +302,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# bootstrapping of a slice job.
|
||||
#
|
||||
# \param job The start slice job that was just finished.
|
||||
def _onStartSliceCompleted(self, job):
|
||||
def _onStartSliceCompleted(self, job: StartSliceJob) -> None:
|
||||
if self._error_message:
|
||||
self._error_message.hide()
|
||||
|
||||
|
|
@ -303,13 +310,13 @@ class CuraEngineBackend(QObject, Backend):
|
|||
if self._start_slice_job is job:
|
||||
self._start_slice_job = None
|
||||
|
||||
if job.isCancelled() or job.getError() or job.getResult() == StartSliceJob.StartJobResult.Error:
|
||||
if job.isCancelled() or job.getError() or job.getResult() == StartJobResult.Error:
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
self.backendError.emit(job)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.MaterialIncompatible:
|
||||
if Application.getInstance().platformActivity:
|
||||
if job.getResult() == StartJobResult.MaterialIncompatible:
|
||||
if self._application.platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status",
|
||||
"Unable to slice with the current material as it is incompatible with the selected machine or configuration."), title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
|
|
@ -319,10 +326,10 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.SettingError:
|
||||
if Application.getInstance().platformActivity:
|
||||
if job.getResult() == StartJobResult.SettingError:
|
||||
if self._application.platformActivity:
|
||||
extruders = list(ExtruderManager.getInstance().getMachineExtruders(self._global_container_stack.getId()))
|
||||
error_keys = []
|
||||
error_keys = [] #type: List[str]
|
||||
for extruder in extruders:
|
||||
error_keys.extend(extruder.getErrorKeys())
|
||||
if not extruders:
|
||||
|
|
@ -330,7 +337,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
error_labels = set()
|
||||
for key in error_keys:
|
||||
for stack in [self._global_container_stack] + extruders: #Search all container stacks for the definition of this setting. Some are only in an extruder stack.
|
||||
definitions = stack.getBottom().findDefinitions(key = key)
|
||||
definitions = cast(DefinitionContainerInterface, stack.getBottom()).findDefinitions(key = key)
|
||||
if definitions:
|
||||
break #Found it! No need to continue search.
|
||||
else: #No stack has a definition for this setting.
|
||||
|
|
@ -338,8 +345,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
continue
|
||||
error_labels.add(definitions[0].label)
|
||||
|
||||
error_labels = ", ".join(error_labels)
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(error_labels),
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice with the current settings. The following settings have errors: {0}").format(", ".join(error_labels)),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
|
|
@ -348,29 +354,27 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
return
|
||||
|
||||
elif job.getResult() == StartSliceJob.StartJobResult.ObjectSettingError:
|
||||
elif job.getResult() == StartJobResult.ObjectSettingError:
|
||||
errors = {}
|
||||
for node in DepthFirstIterator(Application.getInstance().getController().getScene().getRoot()):
|
||||
for node in DepthFirstIterator(self._application.getController().getScene().getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
stack = node.callDecoration("getStack")
|
||||
if not stack:
|
||||
continue
|
||||
for key in stack.getErrorKeys():
|
||||
definition = self._global_container_stack.getBottom().findDefinitions(key = key)
|
||||
definition = cast(DefinitionContainerInterface, self._global_container_stack.getBottom()).findDefinitions(key = key)
|
||||
if not definition:
|
||||
Logger.log("e", "When checking settings for errors, unable to find definition for key {key} in per-object stack.".format(key = key))
|
||||
continue
|
||||
definition = definition[0]
|
||||
errors[key] = definition.label
|
||||
error_labels = ", ".join(errors.values())
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = error_labels),
|
||||
errors[key] = definition[0].label
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice due to some per-model settings. The following settings have errors on one or more models: {error_labels}").format(error_labels = ", ".join(errors.values())),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
self.backendStateChange.emit(BackendState.Error)
|
||||
self.backendError.emit(job)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.BuildPlateError:
|
||||
if Application.getInstance().platformActivity:
|
||||
if job.getResult() == StartJobResult.BuildPlateError:
|
||||
if self._application.platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because the prime tower or prime position(s) are invalid."),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
|
|
@ -379,7 +383,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
else:
|
||||
self.backendStateChange.emit(BackendState.NotStarted)
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.ObjectsWithDisabledExtruder:
|
||||
if job.getResult() == StartJobResult.ObjectsWithDisabledExtruder:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Unable to slice because there are objects associated with disabled Extruder %s." % job.getMessage()),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
|
|
@ -387,8 +391,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self.backendError.emit(job)
|
||||
return
|
||||
|
||||
if job.getResult() == StartSliceJob.StartJobResult.NothingToSlice:
|
||||
if Application.getInstance().platformActivity:
|
||||
if job.getResult() == StartJobResult.NothingToSlice:
|
||||
if self._application.platformActivity:
|
||||
self._error_message = Message(catalog.i18nc("@info:status", "Nothing to slice because none of the models fit the build volume. Please scale or rotate models to fit."),
|
||||
title = catalog.i18nc("@info:title", "Unable to slice"))
|
||||
self._error_message.show()
|
||||
|
|
@ -411,20 +415,20 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# It disables when
|
||||
# - preference auto slice is off
|
||||
# - decorator isBlockSlicing is found (used in g-code reader)
|
||||
def determineAutoSlicing(self):
|
||||
def determineAutoSlicing(self) -> bool:
|
||||
enable_timer = True
|
||||
self._is_disabled = False
|
||||
|
||||
if not Application.getInstance().getPreferences().getValue("general/auto_slice"):
|
||||
if not self._application.getPreferences().getValue("general/auto_slice"):
|
||||
enable_timer = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if node.callDecoration("isBlockSlicing"):
|
||||
enable_timer = False
|
||||
self.backendStateChange.emit(BackendState.Disabled)
|
||||
self._is_disabled = True
|
||||
gcode_list = node.callDecoration("getGCodeList")
|
||||
if gcode_list is not None:
|
||||
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list
|
||||
self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list #type: ignore #Because we generate this attribute dynamically.
|
||||
|
||||
if self._use_timer == enable_timer:
|
||||
return self._use_timer
|
||||
|
|
@ -437,9 +441,9 @@ class CuraEngineBackend(QObject, Backend):
|
|||
return False
|
||||
|
||||
## Return a dict with number of objects per build plate
|
||||
def _numObjectsPerBuildPlate(self):
|
||||
num_objects = defaultdict(int)
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
def _numObjectsPerBuildPlate(self) -> Dict[int, int]:
|
||||
num_objects = defaultdict(int) #type: Dict[int, int]
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
# Only count sliceable objects
|
||||
if node.callDecoration("isSliceable"):
|
||||
build_plate_number = node.callDecoration("getBuildPlateNumber")
|
||||
|
|
@ -451,7 +455,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# This should start a slice if the scene is now ready to slice.
|
||||
#
|
||||
# \param source The scene node that was changed.
|
||||
def _onSceneChanged(self, source):
|
||||
def _onSceneChanged(self, source: SceneNode) -> None:
|
||||
if not isinstance(source, SceneNode):
|
||||
return
|
||||
|
||||
|
|
@ -506,8 +510,8 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when an error occurs in the socket connection towards the engine.
|
||||
#
|
||||
# \param error The exception that occurred.
|
||||
def _onSocketError(self, error):
|
||||
if Application.getInstance().isShuttingDown():
|
||||
def _onSocketError(self, error: Arcus.Error) -> None:
|
||||
if self._application.isShuttingDown():
|
||||
return
|
||||
|
||||
super()._onSocketError(error)
|
||||
|
|
@ -521,19 +525,19 @@ class CuraEngineBackend(QObject, Backend):
|
|||
Logger.log("w", "A socket error caused the connection to be reset")
|
||||
|
||||
## Remove old layer data (if any)
|
||||
def _clearLayerData(self, build_plate_numbers = set()):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if node.callDecoration("getLayerData"):
|
||||
if not build_plate_numbers or node.callDecoration("getBuildPlateNumber") in build_plate_numbers:
|
||||
node.getParent().removeChild(node)
|
||||
|
||||
def markSliceAll(self):
|
||||
for build_plate_number in range(Application.getInstance().getMultiBuildPlateModel().maxBuildPlate + 1):
|
||||
def markSliceAll(self) -> None:
|
||||
for build_plate_number in range(self._application.getMultiBuildPlateModel().maxBuildPlate + 1):
|
||||
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||
|
||||
## Convenient function: mark everything to slice, emit state and clear layer data
|
||||
def needsSlicing(self):
|
||||
def needsSlicing(self) -> None:
|
||||
self.stopSlicing()
|
||||
self.markSliceAll()
|
||||
self.processingProgress.emit(0.0)
|
||||
|
|
@ -545,7 +549,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## A setting has changed, so check if we must reslice.
|
||||
# \param instance The setting instance that has changed.
|
||||
# \param property The property of the setting instance that has changed.
|
||||
def _onSettingChanged(self, instance, property):
|
||||
def _onSettingChanged(self, instance: SettingInstance, property: str) -> None:
|
||||
if property == "value": # Only reslice if the value has changed.
|
||||
self.needsSlicing()
|
||||
self._onChanged()
|
||||
|
|
@ -554,7 +558,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
if self._use_timer:
|
||||
self._change_timer.stop()
|
||||
|
||||
def _onStackErrorCheckFinished(self):
|
||||
def _onStackErrorCheckFinished(self) -> None:
|
||||
self.determineAutoSlicing()
|
||||
if self._is_disabled:
|
||||
return
|
||||
|
|
@ -566,13 +570,13 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when a sliced layer data message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing sliced layer data.
|
||||
def _onLayerMessage(self, message):
|
||||
def _onLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
self._stored_layer_data.append(message)
|
||||
|
||||
## Called when an optimized sliced layer data message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing sliced layer data.
|
||||
def _onOptimizedLayerMessage(self, message):
|
||||
def _onOptimizedLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
if self._start_slice_job_build_plate not in self._stored_optimized_layer_data:
|
||||
self._stored_optimized_layer_data[self._start_slice_job_build_plate] = []
|
||||
self._stored_optimized_layer_data[self._start_slice_job_build_plate].append(message)
|
||||
|
|
@ -580,11 +584,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when a progress message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing the slicing progress.
|
||||
def _onProgressMessage(self, message):
|
||||
def _onProgressMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
self.processingProgress.emit(message.amount)
|
||||
self.backendStateChange.emit(BackendState.Processing)
|
||||
|
||||
def _invokeSlice(self):
|
||||
def _invokeSlice(self) -> None:
|
||||
if self._use_timer:
|
||||
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
||||
# otherwise business as usual
|
||||
|
|
@ -600,17 +604,17 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when the engine sends a message that slicing is finished.
|
||||
#
|
||||
# \param message The protobuf message signalling that slicing is finished.
|
||||
def _onSlicingFinishedMessage(self, message):
|
||||
def _onSlicingFinishedMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
self.backendStateChange.emit(BackendState.Done)
|
||||
self.processingProgress.emit(1.0)
|
||||
|
||||
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate]
|
||||
gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate] #type: ignore #Because we generate this attribute dynamically.
|
||||
for index, line in enumerate(gcode_list):
|
||||
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
|
||||
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
|
||||
replaced = replaced.replace("{filament_weight}", str(Application.getInstance().getPrintInformation().materialWeights))
|
||||
replaced = replaced.replace("{filament_cost}", str(Application.getInstance().getPrintInformation().materialCosts))
|
||||
replaced = replaced.replace("{jobname}", str(Application.getInstance().getPrintInformation().jobName))
|
||||
replaced = line.replace("{print_time}", str(self._application.getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
|
||||
replaced = replaced.replace("{filament_amount}", str(self._application.getPrintInformation().materialLengths))
|
||||
replaced = replaced.replace("{filament_weight}", str(self._application.getPrintInformation().materialWeights))
|
||||
replaced = replaced.replace("{filament_cost}", str(self._application.getPrintInformation().materialCosts))
|
||||
replaced = replaced.replace("{jobname}", str(self._application.getPrintInformation().jobName))
|
||||
|
||||
gcode_list[index] = replaced
|
||||
|
||||
|
|
@ -619,7 +623,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
Logger.log("d", "Number of models per buildplate: %s", dict(self._numObjectsPerBuildPlate()))
|
||||
|
||||
# See if we need to process the sliced layers job.
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||
if (
|
||||
self._layer_view_active and
|
||||
(self._process_layers_job is None or not self._process_layers_job.isRunning()) and
|
||||
|
|
@ -641,25 +645,27 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when a g-code message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing g-code, encoded as UTF-8.
|
||||
def _onGCodeLayerMessage(self, message):
|
||||
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
|
||||
def _onGCodeLayerMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||
|
||||
## Called when a g-code prefix message is received from the engine.
|
||||
#
|
||||
# \param message The protobuf message containing the g-code prefix,
|
||||
# encoded as UTF-8.
|
||||
def _onGCodePrefixMessage(self, message):
|
||||
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
|
||||
def _onGCodePrefixMessage(self, message: Arcus.PythonMessage) -> None:
|
||||
self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace")) #type: ignore #Because we generate this attribute dynamically.
|
||||
|
||||
## Creates a new socket connection.
|
||||
def _createSocket(self):
|
||||
super()._createSocket(os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto")))
|
||||
def _createSocket(self, protocol_file: str = None) -> None:
|
||||
if not protocol_file:
|
||||
protocol_file = os.path.abspath(os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "Cura.proto"))
|
||||
super()._createSocket(protocol_file)
|
||||
self._engine_is_fresh = True
|
||||
|
||||
## Called when anything has changed to the stuff that needs to be sliced.
|
||||
#
|
||||
# This indicates that we should probably re-slice soon.
|
||||
def _onChanged(self, *args, **kwargs):
|
||||
def _onChanged(self, *args: Any, **kwargs: Any) -> None:
|
||||
self.needsSlicing()
|
||||
if self._use_timer:
|
||||
# if the error check is scheduled, wait for the error check finish signal to trigger auto-slice,
|
||||
|
|
@ -677,7 +683,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
#
|
||||
# \param message The protobuf message containing the print time per feature and
|
||||
# material amount per extruder
|
||||
def _onPrintTimeMaterialEstimates(self, message):
|
||||
def _onPrintTimeMaterialEstimates(self, message: Arcus.PythonMessage) -> None:
|
||||
material_amounts = []
|
||||
for index in range(message.repeatedMessageCount("materialEstimates")):
|
||||
material_amounts.append(message.getRepeatedMessage("materialEstimates", index).material_amount)
|
||||
|
|
@ -688,7 +694,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called for parsing message to retrieve estimated time per feature
|
||||
#
|
||||
# \param message The protobuf message containing the print time per feature
|
||||
def _parseMessagePrintTimes(self, message):
|
||||
def _parseMessagePrintTimes(self, message: Arcus.PythonMessage) -> Dict[str, float]:
|
||||
result = {
|
||||
"inset_0": message.time_inset_0,
|
||||
"inset_x": message.time_inset_x,
|
||||
|
|
@ -705,7 +711,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
return result
|
||||
|
||||
## Called when the back-end connects to the front-end.
|
||||
def _onBackendConnected(self):
|
||||
def _onBackendConnected(self) -> None:
|
||||
if self._restart:
|
||||
self._restart = False
|
||||
self._onChanged()
|
||||
|
|
@ -716,7 +722,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# continuously slicing while the user is dragging some tool handle.
|
||||
#
|
||||
# \param tool The tool that the user is using.
|
||||
def _onToolOperationStarted(self, tool):
|
||||
def _onToolOperationStarted(self, tool: Tool) -> None:
|
||||
self._tool_active = True # Do not react on scene change
|
||||
self.disableTimer()
|
||||
# Restart engine as soon as possible, we know we want to slice afterwards
|
||||
|
|
@ -729,7 +735,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
# This indicates that we can safely start slicing again.
|
||||
#
|
||||
# \param tool The tool that the user was using.
|
||||
def _onToolOperationStopped(self, tool):
|
||||
def _onToolOperationStopped(self, tool: Tool) -> None:
|
||||
self._tool_active = False # React on scene change again
|
||||
self.determineAutoSlicing() # Switch timer on if appropriate
|
||||
# Process all the postponed scene changes
|
||||
|
|
@ -737,18 +743,17 @@ class CuraEngineBackend(QObject, Backend):
|
|||
source = self._postponed_scene_change_sources.pop(0)
|
||||
self._onSceneChanged(source)
|
||||
|
||||
def _startProcessSlicedLayersJob(self, build_plate_number):
|
||||
self._process_layers_job = ProcessSlicedLayersJob.ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
|
||||
def _startProcessSlicedLayersJob(self, build_plate_number: int) -> None:
|
||||
self._process_layers_job = ProcessSlicedLayersJob(self._stored_optimized_layer_data[build_plate_number])
|
||||
self._process_layers_job.setBuildPlate(build_plate_number)
|
||||
self._process_layers_job.finished.connect(self._onProcessLayersFinished)
|
||||
self._process_layers_job.start()
|
||||
|
||||
## Called when the user changes the active view mode.
|
||||
def _onActiveViewChanged(self):
|
||||
application = Application.getInstance()
|
||||
view = application.getController().getActiveView()
|
||||
def _onActiveViewChanged(self) -> None:
|
||||
view = self._application.getController().getActiveView()
|
||||
if view:
|
||||
active_build_plate = application.getMultiBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = self._application.getMultiBuildPlateModel().activeBuildPlate
|
||||
if view.getPluginId() == "SimulationView": # If switching to layer view, we should process the layers if that hasn't been done yet.
|
||||
self._layer_view_active = True
|
||||
# There is data and we're not slicing at the moment
|
||||
|
|
@ -766,14 +771,14 @@ class CuraEngineBackend(QObject, Backend):
|
|||
## Called when the back-end self-terminates.
|
||||
#
|
||||
# We should reset our state and start listening for new connections.
|
||||
def _onBackendQuit(self):
|
||||
def _onBackendQuit(self) -> None:
|
||||
if not self._restart:
|
||||
if self._process:
|
||||
Logger.log("d", "Backend quit with return code %s. Resetting process and socket.", self._process.wait())
|
||||
self._process = None
|
||||
|
||||
## Called when the global container stack changes
|
||||
def _onGlobalStackChanged(self):
|
||||
def _onGlobalStackChanged(self) -> None:
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.disconnect(self._onSettingChanged)
|
||||
self._global_container_stack.containersChanged.disconnect(self._onChanged)
|
||||
|
|
@ -783,7 +788,7 @@ class CuraEngineBackend(QObject, Backend):
|
|||
extruder.propertyChanged.disconnect(self._onSettingChanged)
|
||||
extruder.containersChanged.disconnect(self._onChanged)
|
||||
|
||||
self._global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
self._global_container_stack = self._application.getGlobalContainerStack()
|
||||
|
||||
if self._global_container_stack:
|
||||
self._global_container_stack.propertyChanged.connect(self._onSettingChanged) # Note: Only starts slicing when the value changed.
|
||||
|
|
@ -794,26 +799,26 @@ class CuraEngineBackend(QObject, Backend):
|
|||
extruder.containersChanged.connect(self._onChanged)
|
||||
self._onChanged()
|
||||
|
||||
def _onProcessLayersFinished(self, job):
|
||||
def _onProcessLayersFinished(self, job: ProcessSlicedLayersJob) -> None:
|
||||
del self._stored_optimized_layer_data[job.getBuildPlate()]
|
||||
self._process_layers_job = None
|
||||
Logger.log("d", "See if there is more to slice(2)...")
|
||||
self._invokeSlice()
|
||||
|
||||
## Connect slice function to timer.
|
||||
def enableTimer(self):
|
||||
def enableTimer(self) -> None:
|
||||
if not self._use_timer:
|
||||
self._change_timer.timeout.connect(self.slice)
|
||||
self._use_timer = True
|
||||
|
||||
## Disconnect slice function from timer.
|
||||
# This means that slicing will not be triggered automatically
|
||||
def disableTimer(self):
|
||||
def disableTimer(self) -> None:
|
||||
if self._use_timer:
|
||||
self._use_timer = False
|
||||
self._change_timer.timeout.disconnect(self.slice)
|
||||
|
||||
def _onPreferencesChanged(self, preference):
|
||||
def _onPreferencesChanged(self, preference: str) -> None:
|
||||
if preference != "general/auto_slice":
|
||||
return
|
||||
auto_slice = self.determineAutoSlicing()
|
||||
|
|
@ -821,11 +826,11 @@ class CuraEngineBackend(QObject, Backend):
|
|||
self._change_timer.start()
|
||||
|
||||
## Tickle the backend so in case of auto slicing, it starts the timer.
|
||||
def tickle(self):
|
||||
def tickle(self) -> None:
|
||||
if self._use_timer:
|
||||
self._change_timer.start()
|
||||
|
||||
def _extruderChanged(self):
|
||||
def _extruderChanged(self) -> None:
|
||||
for build_plate_number in range(self._multi_build_plate_model.maxBuildPlate + 1):
|
||||
if build_plate_number not in self._build_plates_to_be_sliced:
|
||||
self._build_plates_to_be_sliced.append(build_plate_number)
|
||||
|
|
|
|||
|
|
@ -1,21 +1,25 @@
|
|||
# Copyright (c) 2017 Ultimaker B.V.
|
||||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import numpy
|
||||
from string import Formatter
|
||||
from enum import IntEnum
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional, Set
|
||||
import re
|
||||
import Arcus #For typing.
|
||||
|
||||
from UM.Job import Job
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerStack import ContainerStack #For typing.
|
||||
from UM.Settings.SettingRelation import SettingRelation #For typing.
|
||||
|
||||
from UM.Scene.Iterator.DepthFirstIterator import DepthFirstIterator
|
||||
|
||||
from UM.Scene.Scene import Scene #For typing.
|
||||
from UM.Settings.Validator import ValidatorState
|
||||
from UM.Settings.SettingRelation import RelationType
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.OneAtATimeIterator import OneAtATimeIterator
|
||||
from cura.Settings.ExtruderManager import ExtruderManager
|
||||
|
|
@ -35,19 +39,19 @@ class StartJobResult(IntEnum):
|
|||
ObjectsWithDisabledExtruder = 8
|
||||
|
||||
|
||||
## Formatter class that handles token expansion in start/end gcod
|
||||
## Formatter class that handles token expansion in start/end gcode
|
||||
class GcodeStartEndFormatter(Formatter):
|
||||
def get_value(self, key, args, kwargs): # [CodeStyle: get_value is an overridden function from the Formatter class]
|
||||
def get_value(self, key: str, *args: str, **kwargs) -> 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
|
||||
|
||||
if isinstance(key, str):
|
||||
try:
|
||||
extruder_nr = kwargs["default_extruder_nr"]
|
||||
extruder_nr = int(kwargs["default_extruder_nr"])
|
||||
except ValueError:
|
||||
extruder_nr = -1
|
||||
|
||||
key_fragments = [fragment.strip() for fragment in key.split(',')]
|
||||
key_fragments = [fragment.strip() for fragment in key.split(",")]
|
||||
if len(key_fragments) == 2:
|
||||
try:
|
||||
extruder_nr = int(key_fragments[1])
|
||||
|
|
@ -74,25 +78,25 @@ class GcodeStartEndFormatter(Formatter):
|
|||
|
||||
## Job class that builds up the message of scene data to send to CuraEngine.
|
||||
class StartSliceJob(Job):
|
||||
def __init__(self, slice_message):
|
||||
def __init__(self, slice_message: Arcus.PythonMessage) -> None:
|
||||
super().__init__()
|
||||
|
||||
self._scene = Application.getInstance().getController().getScene()
|
||||
self._slice_message = slice_message
|
||||
self._is_cancelled = False
|
||||
self._build_plate_number = None
|
||||
self._scene = CuraApplication.getInstance().getController().getScene() #type: Scene
|
||||
self._slice_message = slice_message #type: Arcus.PythonMessage
|
||||
self._is_cancelled = False #type: bool
|
||||
self._build_plate_number = None #type: Optional[int]
|
||||
|
||||
self._all_extruders_settings = None # cache for all setting values from all stacks (global & extruder) for the current machine
|
||||
self._all_extruders_settings = None #type: Optional[Dict[str, Any]] # cache for all setting values from all stacks (global & extruder) for the current machine
|
||||
|
||||
def getSliceMessage(self):
|
||||
def getSliceMessage(self) -> Arcus.PythonMessage:
|
||||
return self._slice_message
|
||||
|
||||
def setBuildPlate(self, build_plate_number):
|
||||
def setBuildPlate(self, build_plate_number: int) -> None:
|
||||
self._build_plate_number = build_plate_number
|
||||
|
||||
## Check if a stack has any errors.
|
||||
## returns true if it has errors, false otherwise.
|
||||
def _checkStackForErrors(self, stack):
|
||||
def _checkStackForErrors(self, stack: ContainerStack) -> bool:
|
||||
if stack is None:
|
||||
return False
|
||||
|
||||
|
|
@ -105,28 +109,28 @@ class StartSliceJob(Job):
|
|||
return False
|
||||
|
||||
## Runs the job that initiates the slicing.
|
||||
def run(self):
|
||||
def run(self) -> None:
|
||||
if self._build_plate_number is None:
|
||||
self.setResult(StartJobResult.Error)
|
||||
return
|
||||
|
||||
stack = Application.getInstance().getGlobalContainerStack()
|
||||
stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not stack:
|
||||
self.setResult(StartJobResult.Error)
|
||||
return
|
||||
|
||||
# Don't slice if there is a setting with an error value.
|
||||
if Application.getInstance().getMachineManager().stacksHaveErrors:
|
||||
if CuraApplication.getInstance().getMachineManager().stacksHaveErrors:
|
||||
self.setResult(StartJobResult.SettingError)
|
||||
return
|
||||
|
||||
if Application.getInstance().getBuildVolume().hasErrors():
|
||||
if CuraApplication.getInstance().getBuildVolume().hasErrors():
|
||||
self.setResult(StartJobResult.BuildPlateError)
|
||||
return
|
||||
|
||||
# Don't slice if the buildplate or the nozzle type is incompatible with the materials
|
||||
if not Application.getInstance().getMachineManager().variantBuildplateCompatible and \
|
||||
not Application.getInstance().getMachineManager().variantBuildplateUsable:
|
||||
if not CuraApplication.getInstance().getMachineManager().variantBuildplateCompatible and \
|
||||
not CuraApplication.getInstance().getMachineManager().variantBuildplateUsable:
|
||||
self.setResult(StartJobResult.MaterialIncompatible)
|
||||
return
|
||||
|
||||
|
|
@ -141,7 +145,7 @@ class StartSliceJob(Job):
|
|||
|
||||
|
||||
# Don't slice if there is a per object setting with an error value.
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if not isinstance(node, CuraSceneNode) or not node.isSelectable():
|
||||
continue
|
||||
|
||||
|
|
@ -151,7 +155,7 @@ class StartSliceJob(Job):
|
|||
|
||||
with self._scene.getSceneLock():
|
||||
# Remove old layer data.
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if node.callDecoration("getLayerData") and node.callDecoration("getBuildPlateNumber") == self._build_plate_number:
|
||||
node.getParent().removeChild(node)
|
||||
break
|
||||
|
|
@ -159,7 +163,7 @@ class StartSliceJob(Job):
|
|||
# Get the objects in their groups to print.
|
||||
object_groups = []
|
||||
if stack.getProperty("print_sequence", "value") == "one_at_a_time":
|
||||
for node in OneAtATimeIterator(self._scene.getRoot()):
|
||||
for node in OneAtATimeIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
temp_list = []
|
||||
|
||||
# Node can't be printed, so don't bother sending it.
|
||||
|
|
@ -185,7 +189,7 @@ class StartSliceJob(Job):
|
|||
else:
|
||||
temp_list = []
|
||||
has_printing_mesh = False
|
||||
for node in DepthFirstIterator(self._scene.getRoot()):
|
||||
for node in DepthFirstIterator(self._scene.getRoot()): #type: ignore #Ignore type error because iter() should get called automatically by Python syntax.
|
||||
if node.callDecoration("isSliceable") and node.getMeshData() and node.getMeshData().getVertices() is not None:
|
||||
per_object_stack = node.callDecoration("getStack")
|
||||
is_non_printing_mesh = False
|
||||
|
|
@ -212,27 +216,26 @@ class StartSliceJob(Job):
|
|||
if temp_list:
|
||||
object_groups.append(temp_list)
|
||||
|
||||
extruders_enabled = {position: stack.isEnabled for position, stack in Application.getInstance().getGlobalContainerStack().extruders.items()}
|
||||
extruders_enabled = {position: stack.isEnabled for position, stack in CuraApplication.getInstance().getGlobalContainerStack().extruders.items()}
|
||||
filtered_object_groups = []
|
||||
has_model_with_disabled_extruders = False
|
||||
associated_siabled_extruders = set()
|
||||
associated_disabled_extruders = set()
|
||||
for group in object_groups:
|
||||
stack = Application.getInstance().getGlobalContainerStack()
|
||||
stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
skip_group = False
|
||||
for node in group:
|
||||
extruder_position = node.callDecoration("getActiveExtruderPosition")
|
||||
if not extruders_enabled[extruder_position]:
|
||||
skip_group = True
|
||||
has_model_with_disabled_extruders = True
|
||||
associated_siabled_extruders.add(extruder_position)
|
||||
break
|
||||
associated_disabled_extruders.add(extruder_position)
|
||||
if not skip_group:
|
||||
filtered_object_groups.append(group)
|
||||
|
||||
if has_model_with_disabled_extruders:
|
||||
self.setResult(StartJobResult.ObjectsWithDisabledExtruder)
|
||||
associated_siabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_siabled_extruders])]
|
||||
self.setMessage(", ".join(associated_siabled_extruders))
|
||||
associated_disabled_extruders = [str(c) for c in sorted([int(p) + 1 for p in associated_disabled_extruders])]
|
||||
self.setMessage(", ".join(associated_disabled_extruders))
|
||||
return
|
||||
|
||||
# There are cases when there is nothing to slice. This can happen due to one at a time slicing not being
|
||||
|
|
@ -284,11 +287,11 @@ class StartSliceJob(Job):
|
|||
|
||||
self.setResult(StartJobResult.Finished)
|
||||
|
||||
def cancel(self):
|
||||
def cancel(self) -> None:
|
||||
super().cancel()
|
||||
self._is_cancelled = True
|
||||
|
||||
def isCancelled(self):
|
||||
def isCancelled(self) -> bool:
|
||||
return self._is_cancelled
|
||||
|
||||
## Creates a dictionary of tokens to replace in g-code pieces.
|
||||
|
|
@ -298,7 +301,7 @@ class StartSliceJob(Job):
|
|||
# with.
|
||||
# \return A dictionary of replacement tokens to the values they should be
|
||||
# replaced with.
|
||||
def _buildReplacementTokens(self, stack) -> dict:
|
||||
def _buildReplacementTokens(self, stack: ContainerStack) -> Dict[str, Any]:
|
||||
result = {}
|
||||
for key in stack.getAllKeys():
|
||||
value = stack.getProperty(key, "value")
|
||||
|
|
@ -311,7 +314,7 @@ class StartSliceJob(Job):
|
|||
result["date"] = time.strftime("%d-%m-%Y")
|
||||
result["day"] = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"][int(time.strftime("%w"))]
|
||||
|
||||
initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||
initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
||||
result["initial_extruder_nr"] = initial_extruder_nr
|
||||
|
||||
|
|
@ -320,9 +323,9 @@ class StartSliceJob(Job):
|
|||
## Replace setting tokens in a piece of g-code.
|
||||
# \param value A piece of g-code to replace tokens in.
|
||||
# \param default_extruder_nr Stack nr to use when no stack nr is specified, defaults to the global stack
|
||||
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1):
|
||||
def _expandGcodeTokens(self, value: str, default_extruder_nr: int = -1) -> str:
|
||||
if not self._all_extruders_settings:
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
|
||||
# NB: keys must be strings for the string formatter
|
||||
self._all_extruders_settings = {
|
||||
|
|
@ -344,7 +347,7 @@ class StartSliceJob(Job):
|
|||
return str(value)
|
||||
|
||||
## Create extruder message from stack
|
||||
def _buildExtruderMessage(self, stack):
|
||||
def _buildExtruderMessage(self, stack: ContainerStack) -> None:
|
||||
message = self._slice_message.addRepeatedMessage("extruders")
|
||||
message.id = int(stack.getMetaDataEntry("position"))
|
||||
|
||||
|
|
@ -371,7 +374,7 @@ class StartSliceJob(Job):
|
|||
#
|
||||
# The settings are taken from the global stack. This does not include any
|
||||
# per-extruder settings or per-object settings.
|
||||
def _buildGlobalSettingsMessage(self, stack):
|
||||
def _buildGlobalSettingsMessage(self, stack: ContainerStack) -> None:
|
||||
settings = self._buildReplacementTokens(stack)
|
||||
|
||||
# Pre-compute material material_bed_temp_prepend and material_print_temp_prepend
|
||||
|
|
@ -385,7 +388,7 @@ class StartSliceJob(Job):
|
|||
|
||||
# Replace the setting tokens in start and end g-code.
|
||||
# Use values from the first used extruder by default so we get the expected temperatures
|
||||
initial_extruder_stack = Application.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||
initial_extruder_stack = CuraApplication.getInstance().getExtruderManager().getUsedExtruderStacks()[0]
|
||||
initial_extruder_nr = initial_extruder_stack.getProperty("extruder_nr", "value")
|
||||
|
||||
settings["machine_start_gcode"] = self._expandGcodeTokens(settings["machine_start_gcode"], initial_extruder_nr)
|
||||
|
|
@ -406,7 +409,7 @@ class StartSliceJob(Job):
|
|||
#
|
||||
# \param stack The global stack with all settings, from which to read the
|
||||
# limit_to_extruder property.
|
||||
def _buildGlobalInheritsStackMessage(self, stack):
|
||||
def _buildGlobalInheritsStackMessage(self, stack: ContainerStack) -> None:
|
||||
for key in stack.getAllKeys():
|
||||
extruder_position = int(round(float(stack.getProperty(key, "limit_to_extruder"))))
|
||||
if extruder_position >= 0: # Set to a specific extruder.
|
||||
|
|
@ -416,9 +419,9 @@ class StartSliceJob(Job):
|
|||
Job.yieldThread()
|
||||
|
||||
## Check if a node has per object settings and ensure that they are set correctly in the message
|
||||
# \param node \type{SceneNode} Node to check.
|
||||
# \param node Node to check.
|
||||
# \param message object_lists message to put the per object settings in
|
||||
def _handlePerObjectSettings(self, node, message):
|
||||
def _handlePerObjectSettings(self, node: CuraSceneNode, message: Arcus.PythonMessage):
|
||||
stack = node.callDecoration("getStack")
|
||||
|
||||
# Check if the node has a stack attached to it and the stack has any settings in the top container.
|
||||
|
|
@ -427,7 +430,7 @@ class StartSliceJob(Job):
|
|||
|
||||
# Check all settings for relations, so we can also calculate the correct values for dependent settings.
|
||||
top_of_stack = stack.getTop() # Cache for efficiency.
|
||||
changed_setting_keys = set(top_of_stack.getAllKeys())
|
||||
changed_setting_keys = top_of_stack.getAllKeys()
|
||||
|
||||
# Add all relations to changed settings as well.
|
||||
for key in top_of_stack.getAllKeys():
|
||||
|
|
@ -456,9 +459,9 @@ class StartSliceJob(Job):
|
|||
Job.yieldThread()
|
||||
|
||||
## Recursive function to put all settings that require each other for value changes in a list
|
||||
# \param relations_set \type{set} Set of keys (strings) of settings that are influenced
|
||||
# \param relations_set Set of keys of settings that are influenced
|
||||
# \param relations list of relation objects that need to be checked.
|
||||
def _addRelations(self, relations_set, relations):
|
||||
def _addRelations(self, relations_set: Set[str], relations: List[SettingRelation]):
|
||||
for relation in filter(lambda r: r.role == "value" or r.role == "limit_to_extruder", relations):
|
||||
if relation.type == RelationType.RequiresTarget:
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -11,9 +11,8 @@ from UM.PluginRegistry import PluginRegistry
|
|||
#
|
||||
# If you're zipping g-code, you might as well use gzip!
|
||||
class GCodeGzReader(MeshReader):
|
||||
|
||||
def __init__(self, application):
|
||||
super().__init__(application)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._supported_extensions = [".gcode.gz"]
|
||||
|
||||
def _read(self, file_name):
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ def getMetaData():
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
app.addNonSliceableExtension(".gz")
|
||||
return { "mesh_reader": GCodeGzReader.GCodeGzReader(app) }
|
||||
return {"mesh_reader": GCodeGzReader.GCodeGzReader()}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
import gzip
|
||||
from io import StringIO, BufferedIOBase #To write the g-code to a temporary buffer, and for typing.
|
||||
from typing import List
|
||||
from typing import cast, List
|
||||
|
||||
from UM.Logger import Logger
|
||||
from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementing.
|
||||
|
|
@ -32,7 +32,7 @@ class GCodeGzWriter(MeshWriter):
|
|||
|
||||
#Get the g-code from the g-code writer.
|
||||
gcode_textio = StringIO() #We have to convert the g-code into bytes.
|
||||
success = PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
|
||||
success = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")).write(gcode_textio, None)
|
||||
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,9 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Backend import Backend
|
||||
from UM.Job import Job
|
||||
from UM.Logger import Logger
|
||||
from UM.Math.AxisAlignedBox import AxisAlignedBox
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Message import Message
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
|
@ -13,7 +11,8 @@ from UM.i18n import i18nCatalog
|
|||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
from cura import LayerDataBuilder
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.LayerDataBuilder import LayerDataBuilder
|
||||
from cura.LayerDataDecorator import LayerDataDecorator
|
||||
from cura.LayerPolygon import LayerPolygon
|
||||
from cura.Scene.GCodeListDecorator import GCodeListDecorator
|
||||
|
|
@ -23,16 +22,16 @@ import numpy
|
|||
import math
|
||||
import re
|
||||
from typing import Dict, List, NamedTuple, Optional, Union
|
||||
from collections import namedtuple
|
||||
|
||||
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", float)])
|
||||
PositionOptional = NamedTuple("Position", [("x", Optional[float]), ("y", Optional[float]), ("z", Optional[float]), ("f", Optional[float]), ("e", Optional[float])])
|
||||
Position = NamedTuple("Position", [("x", float), ("y", float), ("z", float), ("f", float), ("e", List[float])])
|
||||
|
||||
## This parser is intended to interpret the common firmware codes among all the
|
||||
# different flavors
|
||||
class FlavorParser:
|
||||
|
||||
def __init__(self) -> None:
|
||||
Application.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||
CuraApplication.getInstance().hideMessageSignal.connect(self._onHideMessage)
|
||||
self._cancelled = False
|
||||
self._message = None
|
||||
self._layer_number = 0
|
||||
|
|
@ -40,21 +39,21 @@ class FlavorParser:
|
|||
self._clearValues()
|
||||
self._scene_node = None
|
||||
# X, Y, Z position, F feedrate and E extruder values are stored
|
||||
self._position = namedtuple('Position', ['x', 'y', 'z', 'f', 'e'])
|
||||
self._position = Position
|
||||
self._is_layers_in_file = False # Does the Gcode have the layers comment?
|
||||
self._extruder_offsets = {} # Offsets for multi extruders. key is index, value is [x-offset, y-offset]
|
||||
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
|
||||
|
||||
Application.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||
CuraApplication.getInstance().getPreferences().addPreference("gcodereader/show_caution", True)
|
||||
|
||||
def _clearValues(self) -> None:
|
||||
self._extruder_number = 0
|
||||
self._extrusion_length_offset = [0]
|
||||
self._extrusion_length_offset = [0] # type: List[float]
|
||||
self._layer_type = LayerPolygon.Inset0Type
|
||||
self._layer_number = 0
|
||||
self._previous_z = 0
|
||||
self._layer_data_builder = LayerDataBuilder.LayerDataBuilder()
|
||||
self._previous_z = 0 # type: float
|
||||
self._layer_data_builder = LayerDataBuilder()
|
||||
self._is_absolute_positioning = True # It can be absolute (G90) or relative (G91)
|
||||
self._is_absolute_extrusion = True # It can become absolute (M82, default) or relative (M83)
|
||||
|
||||
|
|
@ -77,14 +76,14 @@ class FlavorParser:
|
|||
def _getInt(self, line: str, code: str) -> Optional[int]:
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return int(value)
|
||||
return int(value) # type: ignore
|
||||
except:
|
||||
return None
|
||||
|
||||
def _getFloat(self, line: str, code: str) -> Optional[float]:
|
||||
value = self._getValue(line, code)
|
||||
try:
|
||||
return float(value)
|
||||
return float(value) # type: ignore
|
||||
except:
|
||||
return None
|
||||
|
||||
|
|
@ -92,10 +91,6 @@ class FlavorParser:
|
|||
if message == self._message:
|
||||
self._cancelled = True
|
||||
|
||||
@staticmethod
|
||||
def _getNullBoundingBox() -> AxisAlignedBox:
|
||||
return AxisAlignedBox(minimum=Vector(0, 0, 0), maximum=Vector(10, 10, 10))
|
||||
|
||||
def _createPolygon(self, layer_thickness: float, path: List[List[Union[float, int]]], extruder_offsets: List[float]) -> bool:
|
||||
countvalid = 0
|
||||
for point in path:
|
||||
|
|
@ -169,7 +164,7 @@ class FlavorParser:
|
|||
return 0.35
|
||||
return line_width
|
||||
|
||||
def _gCode0(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
def _gCode0(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||
x, y, z, f, e = position
|
||||
|
||||
if self._is_absolute_positioning:
|
||||
|
|
@ -205,7 +200,7 @@ class FlavorParser:
|
|||
_gCode1 = _gCode0
|
||||
|
||||
## Home the head.
|
||||
def _gCode28(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
def _gCode28(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||
return self._position(
|
||||
params.x if params.x is not None else position.x,
|
||||
params.y if params.y is not None else position.y,
|
||||
|
|
@ -214,20 +209,20 @@ class FlavorParser:
|
|||
position.e)
|
||||
|
||||
## Set the absolute positioning
|
||||
def _gCode90(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
def _gCode90(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||
self._is_absolute_positioning = True
|
||||
self._is_absolute_extrusion = True
|
||||
return position
|
||||
|
||||
## Set the relative positioning
|
||||
def _gCode91(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
def _gCode91(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||
self._is_absolute_positioning = False
|
||||
self._is_absolute_extrusion = False
|
||||
return position
|
||||
|
||||
## Reset the current position to the values specified.
|
||||
# For example: G92 X10 will set the X to 10 without any physical motion.
|
||||
def _gCode92(self, position: Position, params: Position, path: List[List[Union[float, int]]]) -> Position:
|
||||
def _gCode92(self, position: Position, params: PositionOptional, path: List[List[Union[float, int]]]) -> Position:
|
||||
if params.e is not None:
|
||||
# Sometimes a G92 E0 is introduced in the middle of the GCode so we need to keep those offsets for calculate the line_width
|
||||
self._extrusion_length_offset[self._extruder_number] += position.e[self._extruder_number] - params.e
|
||||
|
|
@ -260,7 +255,7 @@ class FlavorParser:
|
|||
f = float(item[1:]) / 60
|
||||
if item[0] == "E":
|
||||
e = float(item[1:])
|
||||
params = self._position(x, y, z, f, e)
|
||||
params = PositionOptional(x, y, z, f, e)
|
||||
return func(position, params, path)
|
||||
return position
|
||||
|
||||
|
|
@ -290,13 +285,10 @@ class FlavorParser:
|
|||
Logger.log("d", "Preparing to load GCode")
|
||||
self._cancelled = False
|
||||
# We obtain the filament diameter from the selected extruder to calculate line widths
|
||||
global_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
self._filament_diameter = global_stack.extruders[str(self._extruder_number)].getProperty("material_diameter", "value")
|
||||
|
||||
scene_node = CuraSceneNode()
|
||||
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
|
||||
# real data to calculate it from.
|
||||
scene_node.getBoundingBox = self._getNullBoundingBox
|
||||
|
||||
gcode_list = []
|
||||
self._is_layers_in_file = False
|
||||
|
|
@ -322,13 +314,14 @@ class FlavorParser:
|
|||
lifetime=0,
|
||||
title = catalog.i18nc("@info:title", "G-code Details"))
|
||||
|
||||
assert(self._message is not None) # use for typing purposes
|
||||
self._message.setProgress(0)
|
||||
self._message.show()
|
||||
|
||||
Logger.log("d", "Parsing Gcode...")
|
||||
|
||||
current_position = self._position(0, 0, 0, 0, [0])
|
||||
current_path = []
|
||||
current_position = Position(0, 0, 0, 0, [0])
|
||||
current_path = [] #type: List[List[float]]
|
||||
min_layer_number = 0
|
||||
negative_layers = 0
|
||||
previous_layer = 0
|
||||
|
|
@ -443,9 +436,9 @@ class FlavorParser:
|
|||
scene_node.addDecorator(gcode_list_decorator)
|
||||
|
||||
# gcode_dict stores gcode_lists for a number of build plates.
|
||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_dict = {active_build_plate_id: gcode_list}
|
||||
Application.getInstance().getController().getScene().gcode_dict = gcode_dict
|
||||
CuraApplication.getInstance().getController().getScene().gcode_dict = gcode_dict #type: ignore #Because gcode_dict is generated dynamically.
|
||||
|
||||
Logger.log("d", "Finished parsing Gcode")
|
||||
self._message.hide()
|
||||
|
|
@ -453,7 +446,7 @@ class FlavorParser:
|
|||
if self._layer_number == 0:
|
||||
Logger.log("w", "File doesn't contain any valid layers")
|
||||
|
||||
settings = Application.getInstance().getGlobalContainerStack()
|
||||
settings = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if not settings.getProperty("machine_center_is_zero", "value"):
|
||||
machine_width = settings.getProperty("machine_width", "value")
|
||||
machine_depth = settings.getProperty("machine_depth", "value")
|
||||
|
|
@ -461,7 +454,7 @@ class FlavorParser:
|
|||
|
||||
Logger.log("d", "GCode loading finished")
|
||||
|
||||
if Application.getInstance().getPreferences().getValue("gcodereader/show_caution"):
|
||||
if CuraApplication.getInstance().getPreferences().getValue("gcodereader/show_caution"):
|
||||
caution_message = Message(catalog.i18nc(
|
||||
"@info:generic",
|
||||
"Make sure the g-code is suitable for your printer and printer configuration before sending the file to it. The g-code representation may not be accurate."),
|
||||
|
|
@ -470,7 +463,7 @@ class FlavorParser:
|
|||
caution_message.show()
|
||||
|
||||
# The "save/print" button's state is bound to the backend state.
|
||||
backend = Application.getInstance().getBackend()
|
||||
backend = CuraApplication.getInstance().getBackend()
|
||||
backend.backendStateChange.emit(Backend.BackendState.Disabled)
|
||||
|
||||
return scene_node
|
||||
|
|
|
|||
|
|
@ -19,16 +19,16 @@ MimeTypeDatabase.addMimeType(
|
|||
)
|
||||
)
|
||||
|
||||
|
||||
# Class for loading and parsing G-code files
|
||||
class GCodeReader(MeshReader):
|
||||
|
||||
_flavor_default = "Marlin"
|
||||
_flavor_keyword = ";FLAVOR:"
|
||||
_flavor_readers_dict = {"RepRap" : RepRapFlavorParser.RepRapFlavorParser(),
|
||||
"Marlin" : MarlinFlavorParser.MarlinFlavorParser()}
|
||||
|
||||
def __init__(self, application):
|
||||
super(GCodeReader, self).__init__(application)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._supported_extensions = [".gcode", ".g"]
|
||||
self._flavor_reader = None
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ def getMetaData():
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
app.addNonSliceableExtension(".gcode")
|
||||
app.addNonSliceableExtension(".g")
|
||||
return { "mesh_reader": GCodeReader.GCodeReader(app) }
|
||||
return {"mesh_reader": GCodeReader.GCodeReader()}
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ class GCodeWriter(MeshWriter):
|
|||
serialized = flat_global_container.serialize()
|
||||
data = {"global_quality": serialized}
|
||||
|
||||
all_setting_keys = set(flat_global_container.getAllKeys())
|
||||
all_setting_keys = flat_global_container.getAllKeys()
|
||||
for extruder in sorted(stack.extruders.values(), key = lambda k: int(k.getMetaDataEntry("position"))):
|
||||
extruder_quality = extruder.qualityChanges
|
||||
if extruder_quality.getId() == "empty_quality_changes":
|
||||
|
|
@ -167,7 +167,7 @@ class GCodeWriter(MeshWriter):
|
|||
extruder_serialized = flat_extruder_quality.serialize()
|
||||
data.setdefault("extruder_quality", []).append(extruder_serialized)
|
||||
|
||||
all_setting_keys.update(set(flat_extruder_quality.getAllKeys()))
|
||||
all_setting_keys.update(flat_extruder_quality.getAllKeys())
|
||||
|
||||
# Check if there is any profiles
|
||||
if not all_setting_keys:
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ from cura.Scene.CuraSceneNode import CuraSceneNode as SceneNode
|
|||
|
||||
|
||||
class ImageReader(MeshReader):
|
||||
def __init__(self, application):
|
||||
super(ImageReader, self).__init__(application)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._supported_extensions = [".jpg", ".jpeg", ".bmp", ".gif", ".png"]
|
||||
self._ui = ImageReaderUI(self)
|
||||
|
||||
|
|
|
|||
|
|
@ -32,5 +32,6 @@ def getMetaData():
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
return { "mesh_reader": ImageReader.ImageReader(app) }
|
||||
return {"mesh_reader": ImageReader.ImageReader()}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,14 @@
|
|||
# This PostProcessing Plugin script is released
|
||||
# under the terms of the AGPLv3 or higher
|
||||
from typing import Optional, Tuple
|
||||
|
||||
from UM.Logger import Logger
|
||||
from ..Script import Script
|
||||
|
||||
class FilamentChange(Script):
|
||||
|
||||
_layer_keyword = ";LAYER:"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -64,11 +69,26 @@ class FilamentChange(Script):
|
|||
if len(layer_targets) > 0:
|
||||
for layer_num in layer_targets:
|
||||
layer_num = int(layer_num.strip())
|
||||
if layer_num < len(data):
|
||||
layer = data[layer_num - 1]
|
||||
lines = layer.split("\n")
|
||||
if layer_num <= len(data):
|
||||
index, layer_data = self._searchLayerData(data, layer_num - 1)
|
||||
if layer_data is None:
|
||||
Logger.log("e", "Could not found the layer")
|
||||
continue
|
||||
lines = layer_data.split("\n")
|
||||
lines.insert(2, color_change)
|
||||
final_line = "\n".join(lines)
|
||||
data[layer_num - 1] = final_line
|
||||
data[index] = final_line
|
||||
|
||||
return data
|
||||
|
||||
## This method returns the data corresponding with the indicated layer number, looking in the gcode for
|
||||
# the occurrence of this layer number.
|
||||
def _searchLayerData(self, data: list, layer_num: int) -> Tuple[int, Optional[str]]:
|
||||
for index, layer_data in enumerate(data):
|
||||
first_line = layer_data.split("\n")[0]
|
||||
# The first line should contain the layer number at the beginning.
|
||||
if first_line[:len(self._layer_keyword)] == self._layer_keyword:
|
||||
# If found the layer that we are looking for, then return the data
|
||||
if first_line[len(self._layer_keyword):] == str(layer_num):
|
||||
return index, layer_data
|
||||
return 0, None
|
||||
|
|
@ -105,14 +105,6 @@ class PauseAtHeight(Script):
|
|||
"unit": "°C",
|
||||
"type": "int",
|
||||
"default_value": 0
|
||||
},
|
||||
"resume_temperature":
|
||||
{
|
||||
"label": "Resume Temperature",
|
||||
"description": "Change the temperature after the pause",
|
||||
"unit": "°C",
|
||||
"type": "int",
|
||||
"default_value": 0
|
||||
}
|
||||
}
|
||||
}"""
|
||||
|
|
@ -144,7 +136,6 @@ class PauseAtHeight(Script):
|
|||
layers_started = False
|
||||
redo_layers = self.getSettingValueByKey("redo_layers")
|
||||
standby_temperature = self.getSettingValueByKey("standby_temperature")
|
||||
resume_temperature = self.getSettingValueByKey("resume_temperature")
|
||||
|
||||
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
|
||||
|
||||
|
|
@ -152,6 +143,8 @@ class PauseAtHeight(Script):
|
|||
layer_0_z = 0.
|
||||
current_z = 0
|
||||
got_first_g_cmd_on_layer_0 = False
|
||||
current_t = 0 #Tracks the current extruder for tracking the target temperature.
|
||||
target_temperature = {} #Tracks the current target temperature for each extruder.
|
||||
|
||||
nbr_negative_layers = 0
|
||||
|
||||
|
|
@ -169,6 +162,16 @@ class PauseAtHeight(Script):
|
|||
if not layers_started:
|
||||
continue
|
||||
|
||||
#Track the latest printing temperature in order to resume at the correct temperature.
|
||||
if line.startswith("T"):
|
||||
current_t = self.getValue(line, "T")
|
||||
m = self.getValue(line, "M")
|
||||
if m is not None and (m == 104 or m == 109) and self.getValue(line, "S") is not None:
|
||||
extruder = current_t
|
||||
if self.getValue(line, "T") is not None:
|
||||
extruder = self.getValue(line, "T")
|
||||
target_temperature[extruder] = self.getValue(line, "S")
|
||||
|
||||
# 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")
|
||||
|
|
@ -262,9 +265,6 @@ class PauseAtHeight(Script):
|
|||
if current_z < 15:
|
||||
prepend_gcode += self.putValue(G=1, Z=15, F=300) + "\n"
|
||||
|
||||
# Disable the E steppers
|
||||
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"
|
||||
|
||||
|
|
@ -272,7 +272,7 @@ class PauseAtHeight(Script):
|
|||
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 = int(target_temperature.get(current_t, default = 0))) + "; resume temperature\n"
|
||||
|
||||
# Push the filament back,
|
||||
if retraction_amount != 0:
|
||||
|
|
|
|||
|
|
@ -1,22 +1,17 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os
|
||||
import os.path
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
|
||||
from UM.Math.Vector import Vector
|
||||
from UM.Tool import Tool
|
||||
from UM.Application import Application
|
||||
from UM.Event import Event, MouseEvent
|
||||
|
||||
from UM.Mesh.MeshBuilder import MeshBuilder
|
||||
from UM.Scene.Selection import Selection
|
||||
from UM.Scene.Iterator.BreadthFirstIterator import BreadthFirstIterator
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.Scene.CuraSceneNode import CuraSceneNode
|
||||
from cura.PickingPass import PickingPass
|
||||
|
||||
from UM.Operations.GroupedOperation import GroupedOperation
|
||||
|
|
@ -26,8 +21,6 @@ from cura.Operations.SetParentOperation import SetParentOperation
|
|||
|
||||
from cura.Scene.SliceableObjectDecorator import SliceableObjectDecorator
|
||||
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
|
||||
from UM.Scene.GroupDecorator import GroupDecorator
|
||||
from cura.Settings.SettingOverrideDecorator import SettingOverrideDecorator
|
||||
|
||||
from UM.Settings.SettingInstance import SettingInstance
|
||||
|
||||
|
|
@ -38,7 +31,7 @@ class SupportEraser(Tool):
|
|||
self._controller = self.getController()
|
||||
|
||||
self._selection_pass = None
|
||||
Application.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
||||
CuraApplication.getInstance().globalContainerStackChanged.connect(self._updateEnabled)
|
||||
|
||||
# Note: if the selection is cleared with this tool active, there is no way to switch to
|
||||
# another tool than to reselect an object (by clicking it) because the tool buttons in the
|
||||
|
|
@ -106,7 +99,7 @@ class SupportEraser(Tool):
|
|||
mesh.addCube(10,10,10)
|
||||
node.setMeshData(mesh.build())
|
||||
|
||||
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
node.addDecorator(BuildPlateDecorator(active_build_plate))
|
||||
node.addDecorator(SliceableObjectDecorator())
|
||||
|
||||
|
|
@ -126,7 +119,7 @@ class SupportEraser(Tool):
|
|||
op.push()
|
||||
node.setPosition(position, CuraSceneNode.TransformSpace.World)
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
def _removeEraserMesh(self, node: CuraSceneNode):
|
||||
parent = node.getParent()
|
||||
|
|
@ -139,16 +132,16 @@ class SupportEraser(Tool):
|
|||
if parent and not Selection.isSelected(parent):
|
||||
Selection.add(parent)
|
||||
|
||||
Application.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
CuraApplication.getInstance().getController().getScene().sceneChanged.emit(node)
|
||||
|
||||
def _updateEnabled(self):
|
||||
plugin_enabled = False
|
||||
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
plugin_enabled = global_container_stack.getProperty("anti_overhang_mesh", "enabled")
|
||||
|
||||
Application.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
||||
CuraApplication.getInstance().getController().toolEnabledChanged.emit(self._plugin_id, plugin_enabled)
|
||||
|
||||
def _onSelectionChanged(self):
|
||||
# When selection is passed from one object to another object, first the selection is cleared
|
||||
|
|
|
|||
|
|
@ -9,4 +9,4 @@ def getMetaData():
|
|||
|
||||
|
||||
def register(app):
|
||||
return {"extension": Toolbox.Toolbox()}
|
||||
return {"extension": Toolbox.Toolbox(app)}
|
||||
|
|
|
|||
|
|
@ -92,6 +92,12 @@ Item
|
|||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: catalog.i18nc("@label", "Downloads") + ":"
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text_medium")
|
||||
}
|
||||
}
|
||||
Column
|
||||
{
|
||||
|
|
@ -138,6 +144,12 @@ Item
|
|||
linkColor: UM.Theme.getColor("text_link")
|
||||
onLinkActivated: Qt.openUrlExternally(link)
|
||||
}
|
||||
Label
|
||||
{
|
||||
text: details.download_count || catalog.i18nc("@label", "Unknown")
|
||||
font: UM.Theme.getFont("very_small")
|
||||
color: UM.Theme.getColor("text")
|
||||
}
|
||||
}
|
||||
Rectangle
|
||||
{
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ Column
|
|||
height: childrenRect.height
|
||||
width: parent.width
|
||||
spacing: UM.Theme.getSize("default_margin").height
|
||||
/* Hidden for 3.4
|
||||
|
||||
Label
|
||||
{
|
||||
id: heading
|
||||
|
|
@ -21,7 +21,6 @@ Column
|
|||
color: UM.Theme.getColor("text_medium")
|
||||
font: UM.Theme.getFont("medium")
|
||||
}
|
||||
*/
|
||||
GridLayout
|
||||
{
|
||||
id: grid
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class PackagesModel(ListModel):
|
|||
self.addRoleName(Qt.UserRole + 15, "is_installed") # Scheduled pkgs are included in the model but should not be marked as actually installed
|
||||
self.addRoleName(Qt.UserRole + 16, "has_configs")
|
||||
self.addRoleName(Qt.UserRole + 17, "supported_configs")
|
||||
self.addRoleName(Qt.UserRole + 18, "download_count")
|
||||
|
||||
# List of filters for queries. The result is the union of the each list of results.
|
||||
self._filter = {} # type: Dict[str, str]
|
||||
|
|
@ -76,7 +77,9 @@ class PackagesModel(ListModel):
|
|||
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
|
||||
"is_installed": package["is_installed"] if "is_installed" in package else False,
|
||||
"has_configs": has_configs,
|
||||
"supported_configs": configs_model
|
||||
"supported_configs": configs_model,
|
||||
"download_count": package["download_count"] if "download_count" in package else 0
|
||||
|
||||
})
|
||||
|
||||
# Filter on all the key-word arguments.
|
||||
|
|
|
|||
|
|
@ -1,19 +1,20 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Toolbox is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Dict, Optional, Union, Any
|
||||
from typing import Dict, Optional, Union, Any, cast
|
||||
import json
|
||||
import os
|
||||
import tempfile
|
||||
import platform
|
||||
from typing import List
|
||||
|
||||
from PyQt5.QtCore import QUrl, QObject, pyqtProperty, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtNetwork import QNetworkAccessManager, QNetworkRequest, QNetworkReply
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.Logger import Logger
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Extension import Extension
|
||||
from UM.Qt.ListModel import ListModel
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Version import Version
|
||||
|
||||
|
|
@ -27,44 +28,39 @@ i18n_catalog = i18nCatalog("cura")
|
|||
|
||||
## The Toolbox class is responsible of communicating with the server through the API
|
||||
class Toolbox(QObject, Extension):
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com" #type: str
|
||||
DEFAULT_CLOUD_API_VERSION = 1 #type: int
|
||||
|
||||
DEFAULT_CLOUD_API_ROOT = "https://api.ultimaker.com"
|
||||
DEFAULT_CLOUD_API_VERSION = 1
|
||||
def __init__(self, application: CuraApplication) -> None:
|
||||
super().__init__()
|
||||
|
||||
def __init__(self, parent=None) -> None:
|
||||
super().__init__(parent)
|
||||
self._application = application #type: CuraApplication
|
||||
|
||||
self._application = Application.getInstance()
|
||||
self._package_manager = None
|
||||
self._plugin_registry = Application.getInstance().getPluginRegistry()
|
||||
|
||||
self._sdk_version = None
|
||||
self._cloud_api_version = None
|
||||
self._cloud_api_root = None
|
||||
self._api_url = None
|
||||
self._sdk_version = None # type: Optional[int]
|
||||
self._cloud_api_version = None # type: Optional[int]
|
||||
self._cloud_api_root = None # type: Optional[str]
|
||||
self._api_url = None # type: Optional[str]
|
||||
|
||||
# Network:
|
||||
self._get_packages_request = None
|
||||
self._get_showcase_request = None
|
||||
self._download_request = None
|
||||
self._download_reply = None
|
||||
self._download_progress = 0
|
||||
self._is_downloading = False
|
||||
self._network_manager = None
|
||||
self._download_request = None #type: Optional[QNetworkRequest]
|
||||
self._download_reply = None #type: Optional[QNetworkReply]
|
||||
self._download_progress = 0 #type: float
|
||||
self._is_downloading = False #type: bool
|
||||
self._network_manager = None #type: Optional[QNetworkAccessManager]
|
||||
self._request_header = [
|
||||
b"User-Agent",
|
||||
str.encode(
|
||||
"%s/%s (%s %s)" % (
|
||||
Application.getInstance().getApplicationName(),
|
||||
Application.getInstance().getVersion(),
|
||||
self._application.getApplicationName(),
|
||||
self._application.getVersion(),
|
||||
platform.system(),
|
||||
platform.machine(),
|
||||
)
|
||||
)
|
||||
]
|
||||
self._request_urls = {}
|
||||
self._to_update = [] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = []
|
||||
self._request_urls = {} # type: Dict[str, QUrl]
|
||||
self._to_update = [] # type: List[str] # Package_ids that are waiting to be updated
|
||||
self._old_plugin_ids = [] # type: List[str]
|
||||
|
||||
# Data:
|
||||
self._metadata = {
|
||||
|
|
@ -76,7 +72,7 @@ class Toolbox(QObject, Extension):
|
|||
"materials_showcase": [],
|
||||
"materials_available": [],
|
||||
"materials_installed": []
|
||||
}
|
||||
} # type: Dict[str, List[Any]]
|
||||
|
||||
# Models:
|
||||
self._models = {
|
||||
|
|
@ -88,33 +84,33 @@ class Toolbox(QObject, Extension):
|
|||
"materials_showcase": AuthorsModel(self),
|
||||
"materials_available": PackagesModel(self),
|
||||
"materials_installed": PackagesModel(self)
|
||||
}
|
||||
} # type: Dict[str, ListModel]
|
||||
|
||||
# These properties are for keeping track of the UI state:
|
||||
# ----------------------------------------------------------------------
|
||||
# View category defines which filter to use, and therefore effectively
|
||||
# which category is currently being displayed. For example, possible
|
||||
# values include "plugin" or "material", but also "installed".
|
||||
self._view_category = "plugin"
|
||||
self._view_category = "plugin" #type: str
|
||||
|
||||
# View page defines which type of page layout to use. For example,
|
||||
# possible values include "overview", "detail" or "author".
|
||||
self._view_page = "loading"
|
||||
self._view_page = "loading" #type: str
|
||||
|
||||
# Active package refers to which package is currently being downloaded,
|
||||
# installed, or otherwise modified.
|
||||
self._active_package = None
|
||||
self._active_package = None # type: Optional[Dict[str, Any]]
|
||||
|
||||
self._dialog = None
|
||||
self._restart_required = False
|
||||
self._dialog = None #type: Optional[QObject]
|
||||
self._restart_required = False #type: bool
|
||||
|
||||
# variables for the license agreement dialog
|
||||
self._license_dialog_plugin_name = ""
|
||||
self._license_dialog_license_content = ""
|
||||
self._license_dialog_plugin_file_location = ""
|
||||
self._restart_dialog_message = ""
|
||||
self._license_dialog_plugin_name = "" #type: str
|
||||
self._license_dialog_license_content = "" #type: str
|
||||
self._license_dialog_plugin_file_location = "" #type: str
|
||||
self._restart_dialog_message = "" #type: str
|
||||
|
||||
Application.getInstance().initializationFinished.connect(self._onAppInitialized)
|
||||
self._application.initializationFinished.connect(self._onAppInitialized)
|
||||
|
||||
|
||||
|
||||
|
|
@ -156,7 +152,8 @@ class Toolbox(QObject, Extension):
|
|||
# This is a plugin, so most of the components required are not ready when
|
||||
# this is initialized. Therefore, we wait until the application is ready.
|
||||
def _onAppInitialized(self) -> None:
|
||||
self._package_manager = Application.getInstance().getPackageManager()
|
||||
self._plugin_registry = self._application.getPluginRegistry()
|
||||
self._package_manager = self._application.getPackageManager()
|
||||
self._sdk_version = self._getSDKVersion()
|
||||
self._cloud_api_version = self._getCloudAPIVersion()
|
||||
self._cloud_api_root = self._getCloudAPIRoot()
|
||||
|
|
@ -178,38 +175,38 @@ class Toolbox(QObject, Extension):
|
|||
def _getCloudAPIRoot(self) -> str:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"):
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIRoot"): # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
if not cura.CuraVersion.CuraCloudAPIRoot:
|
||||
if not cura.CuraVersion.CuraCloudAPIRoot: # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_ROOT
|
||||
return cura.CuraVersion.CuraCloudAPIRoot
|
||||
return cura.CuraVersion.CuraCloudAPIRoot # type: ignore
|
||||
|
||||
# Get the cloud API version from CuraVersion
|
||||
def _getCloudAPIVersion(self) -> int:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"):
|
||||
if not hasattr(cura.CuraVersion, "CuraCloudAPIVersion"): # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
if not cura.CuraVersion.CuraCloudAPIVersion:
|
||||
if not cura.CuraVersion.CuraCloudAPIVersion: # type: ignore
|
||||
return self.DEFAULT_CLOUD_API_VERSION
|
||||
return cura.CuraVersion.CuraCloudAPIVersion
|
||||
return cura.CuraVersion.CuraCloudAPIVersion # type: ignore
|
||||
|
||||
# Get the packages version depending on Cura version settings.
|
||||
def _getSDKVersion(self) -> int:
|
||||
if not hasattr(cura, "CuraVersion"):
|
||||
return self._plugin_registry.APIVersion
|
||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"):
|
||||
if not hasattr(cura.CuraVersion, "CuraSDKVersion"): # type: ignore
|
||||
return self._plugin_registry.APIVersion
|
||||
if not cura.CuraVersion.CuraSDKVersion:
|
||||
if not cura.CuraVersion.CuraSDKVersion: # type: ignore
|
||||
return self._plugin_registry.APIVersion
|
||||
return cura.CuraVersion.CuraSDKVersion
|
||||
return cura.CuraVersion.CuraSDKVersion # type: ignore
|
||||
|
||||
@pyqtSlot()
|
||||
def browsePackages(self) -> None:
|
||||
# Create the network manager:
|
||||
# This was formerly its own function but really had no reason to be as
|
||||
# it was never called more than once ever.
|
||||
if self._network_manager:
|
||||
if self._network_manager is not None:
|
||||
self._network_manager.finished.disconnect(self._onRequestFinished)
|
||||
self._network_manager.networkAccessibleChanged.disconnect(self._onNetworkAccessibleChanged)
|
||||
self._network_manager = QNetworkAccessManager()
|
||||
|
|
@ -235,11 +232,11 @@ class Toolbox(QObject, Extension):
|
|||
def _createDialog(self, qml_name: str) -> Optional[QObject]:
|
||||
Logger.log("d", "Toolbox: Creating dialog [%s].", qml_name)
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath(self.getPluginId()), "resources", "qml", qml_name)
|
||||
dialog = Application.getInstance().createQmlComponent(path, {"toolbox": self})
|
||||
dialog = self._application.createQmlComponent(path, {"toolbox": self})
|
||||
return dialog
|
||||
|
||||
|
||||
def _convertPluginMetadata(self, plugin: dict) -> dict:
|
||||
def _convertPluginMetadata(self, plugin: Dict[str, Any]) -> Dict[str, Any]:
|
||||
formatted = {
|
||||
"package_id": plugin["id"],
|
||||
"package_type": "plugin",
|
||||
|
|
@ -257,7 +254,6 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
@pyqtSlot()
|
||||
def _updateInstalledModels(self) -> None:
|
||||
|
||||
# This is moved here to avoid code duplication and so that after installing plugins they get removed from the
|
||||
# list of old plugins
|
||||
old_plugin_ids = self._plugin_registry.getInstalledPlugins()
|
||||
|
|
@ -265,7 +261,7 @@ class Toolbox(QObject, Extension):
|
|||
scheduled_to_remove_package_ids = self._package_manager.getToRemovePackageIDs()
|
||||
|
||||
self._old_plugin_ids = []
|
||||
self._old_plugin_metadata = []
|
||||
self._old_plugin_metadata = [] # type: List[Dict[str, Any]]
|
||||
|
||||
for plugin_id in old_plugin_ids:
|
||||
# Neither the installed packages nor the packages that are scheduled to remove are old plugins
|
||||
|
|
@ -353,8 +349,8 @@ class Toolbox(QObject, Extension):
|
|||
return self._restart_required
|
||||
|
||||
@pyqtSlot()
|
||||
def restart(self):
|
||||
CuraApplication.getInstance().windowClosed()
|
||||
def restart(self) -> None:
|
||||
self._application.windowClosed()
|
||||
|
||||
def getRemotePackage(self, package_id: str) -> Optional[Dict]:
|
||||
# TODO: make the lookup in a dict, not a loop. canUpdate is called for every item.
|
||||
|
|
@ -432,7 +428,8 @@ class Toolbox(QObject, Extension):
|
|||
Logger.log("i", "Toolbox: Requesting %s metadata from server.", type)
|
||||
request = QNetworkRequest(self._request_urls[type])
|
||||
request.setRawHeader(*self._request_header)
|
||||
self._network_manager.get(request)
|
||||
if self._network_manager:
|
||||
self._network_manager.get(request)
|
||||
|
||||
@pyqtSlot(str)
|
||||
def startDownload(self, url: str) -> None:
|
||||
|
|
@ -441,15 +438,15 @@ class Toolbox(QObject, Extension):
|
|||
self._download_request = QNetworkRequest(url)
|
||||
if hasattr(QNetworkRequest, "FollowRedirectsAttribute"):
|
||||
# Patch for Qt 5.6-5.8
|
||||
self._download_request.setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.FollowRedirectsAttribute, True)
|
||||
if hasattr(QNetworkRequest, "RedirectPolicyAttribute"):
|
||||
# Patch for Qt 5.9+
|
||||
self._download_request.setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||
self._download_request.setRawHeader(*self._request_header)
|
||||
self._download_reply = self._network_manager.get(self._download_request)
|
||||
cast(QNetworkRequest, self._download_request).setAttribute(QNetworkRequest.RedirectPolicyAttribute, True)
|
||||
cast(QNetworkRequest, self._download_request).setRawHeader(*self._request_header)
|
||||
self._download_reply = cast(QNetworkAccessManager, self._network_manager).get(self._download_request)
|
||||
self.setDownloadProgress(0)
|
||||
self.setIsDownloading(True)
|
||||
self._download_reply.downloadProgress.connect(self._onDownloadProgress)
|
||||
cast(QNetworkReply, self._download_reply).downloadProgress.connect(self._onDownloadProgress)
|
||||
|
||||
@pyqtSlot()
|
||||
def cancelDownload(self) -> None:
|
||||
|
|
@ -475,7 +472,6 @@ class Toolbox(QObject, Extension):
|
|||
self.resetDownload()
|
||||
|
||||
def _onRequestFinished(self, reply: QNetworkReply) -> None:
|
||||
|
||||
if reply.error() == QNetworkReply.TimeoutError:
|
||||
Logger.log("w", "Got a timeout.")
|
||||
self.setViewPage("errored")
|
||||
|
|
@ -551,12 +547,12 @@ class Toolbox(QObject, Extension):
|
|||
self.setDownloadProgress(new_progress)
|
||||
if bytes_sent == bytes_total:
|
||||
self.setIsDownloading(False)
|
||||
self._download_reply.downloadProgress.disconnect(self._onDownloadProgress)
|
||||
cast(QNetworkReply, self._download_reply).downloadProgress.disconnect(self._onDownloadProgress)
|
||||
# Must not delete the temporary file on Windows
|
||||
self._temp_plugin_file = tempfile.NamedTemporaryFile(mode = "w+b", suffix = ".curapackage", delete = False)
|
||||
file_path = self._temp_plugin_file.name
|
||||
# Write first and close, otherwise on Windows, it cannot read the file
|
||||
self._temp_plugin_file.write(self._download_reply.readAll())
|
||||
self._temp_plugin_file.write(cast(QNetworkReply, self._download_reply).readAll())
|
||||
self._temp_plugin_file.close()
|
||||
self._onDownloadComplete(file_path)
|
||||
|
||||
|
|
@ -577,13 +573,13 @@ class Toolbox(QObject, Extension):
|
|||
|
||||
# Getter & Setters for Properties:
|
||||
# --------------------------------------------------------------------------
|
||||
def setDownloadProgress(self, progress: Union[int, float]) -> None:
|
||||
def setDownloadProgress(self, progress: float) -> None:
|
||||
if progress != self._download_progress:
|
||||
self._download_progress = progress
|
||||
self.onDownloadProgressChanged.emit()
|
||||
|
||||
@pyqtProperty(int, fset = setDownloadProgress, notify = onDownloadProgressChanged)
|
||||
def downloadProgress(self) -> int:
|
||||
def downloadProgress(self) -> float:
|
||||
return self._download_progress
|
||||
|
||||
def setIsDownloading(self, is_downloading: bool) -> None:
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from typing import Any, cast, Set, Tuple, Union
|
||||
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.FileHandler.FileWriter import FileWriter #To choose based on the output file mode (text vs. binary).
|
||||
from UM.FileHandler.WriteFileJob import WriteFileJob #To call the file writer asynchronously.
|
||||
from UM.Logger import Logger
|
||||
from UM.Application import Application
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Message import Message
|
||||
|
|
@ -13,6 +15,7 @@ from UM.OutputDevice import OutputDeviceError #To show that something went wrong
|
|||
from UM.Scene.SceneNode import SceneNode #For typing.
|
||||
from UM.Version import Version #To check against firmware versions for support.
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
|
@ -45,15 +48,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Inheritance doesn't seem to work. Tying them together does work, but i'm open for better suggestions.
|
||||
clusterPrintersChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, device_id, address, properties, parent = None):
|
||||
def __init__(self, device_id, address, properties, parent = None) -> None:
|
||||
super().__init__(device_id = device_id, address = address, properties=properties, parent = parent)
|
||||
self._api_prefix = "/cluster-api/v1/"
|
||||
|
||||
self._number_of_extruders = 2
|
||||
|
||||
self._dummy_lambdas = set()
|
||||
self._dummy_lambdas = ("", {}, io.BytesIO()) #type: Tuple[str, Dict, Union[io.StringIO, io.BytesIO]]
|
||||
|
||||
self._print_jobs = []
|
||||
self._print_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._monitor_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterMonitorItem.qml")
|
||||
self._control_view_qml_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "ClusterControlItem.qml")
|
||||
|
|
@ -61,18 +64,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# See comments about this hack with the clusterPrintersChanged signal
|
||||
self.printersChanged.connect(self.clusterPrintersChanged)
|
||||
|
||||
self._accepts_commands = True
|
||||
self._accepts_commands = True #type: bool
|
||||
|
||||
# Cluster does not have authentication, so default to authenticated
|
||||
self._authentication_state = AuthState.Authenticated
|
||||
|
||||
self._error_message = None
|
||||
self._write_job_progress_message = None
|
||||
self._progress_message = None
|
||||
self._error_message = None #type: Optional[Message]
|
||||
self._write_job_progress_message = None #type: Optional[Message]
|
||||
self._progress_message = None #type: Optional[Message]
|
||||
|
||||
self._active_printer = None # type: Optional[PrinterOutputModel]
|
||||
|
||||
self._printer_selection_dialog = None
|
||||
self._printer_selection_dialog = None #type: QObject
|
||||
|
||||
self.setPriority(3) # Make sure the output device gets selected above local file output
|
||||
self.setName(self._id)
|
||||
|
|
@ -81,15 +84,15 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self.setConnectionText(i18n_catalog.i18nc("@info:status", "Connected over the network"))
|
||||
|
||||
self._printer_uuid_to_unique_name_mapping = {}
|
||||
self._printer_uuid_to_unique_name_mapping = {} # type: Dict[str, str]
|
||||
|
||||
self._finished_jobs = []
|
||||
self._finished_jobs = [] # type: List[PrintJobOutputModel]
|
||||
|
||||
self._cluster_size = int(properties.get(b"cluster_size", 0))
|
||||
|
||||
self._latest_reply_handler = None
|
||||
self._latest_reply_handler = None #type: Optional[QNetworkReply]
|
||||
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
self.writeStarted.emit(self)
|
||||
|
||||
self.sendMaterialProfiles()
|
||||
|
|
@ -98,10 +101,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
if file_handler:
|
||||
file_formats = file_handler.getSupportedFileTypesWrite()
|
||||
else:
|
||||
file_formats = Application.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||
file_formats = CuraApplication.getInstance().getMeshFileHandler().getSupportedFileTypesWrite()
|
||||
|
||||
#Create a list from the supported file formats string.
|
||||
machine_file_formats = Application.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
|
||||
machine_file_formats = CuraApplication.getInstance().getGlobalContainerStack().getMetaDataEntry("file_formats").split(";")
|
||||
machine_file_formats = [file_type.strip() for file_type in machine_file_formats]
|
||||
#Exception for UM3 firmware version >=4.4: UFP is now supported and should be the preferred file format.
|
||||
if "application/x-ufp" not in machine_file_formats and self.printerType == "ultimaker3" and Version(self.firmwareVersion) >= Version("4.4"):
|
||||
|
|
@ -118,9 +121,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
#Just take the first file format available.
|
||||
if file_handler is not None:
|
||||
writer = file_handler.getWriterByMimeType(preferred_format["mime_type"])
|
||||
writer = file_handler.getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||
else:
|
||||
writer = Application.getInstance().getMeshFileHandler().getWriterByMimeType(preferred_format["mime_type"])
|
||||
writer = CuraApplication.getInstance().getMeshFileHandler().getWriterByMimeType(cast(str, preferred_format["mime_type"]))
|
||||
|
||||
#This function pauses with the yield, waiting on instructions on which printer it needs to print with.
|
||||
self._sending_job = self._sendPrintJob(writer, preferred_format, nodes)
|
||||
|
|
@ -136,7 +139,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def _spawnPrinterSelectionDialog(self):
|
||||
if self._printer_selection_dialog is None:
|
||||
path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "PrintWindow.qml")
|
||||
self._printer_selection_dialog = Application.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||
self._printer_selection_dialog = CuraApplication.getInstance().createQmlComponent(path, {"OutputDevice": self})
|
||||
if self._printer_selection_dialog is not None:
|
||||
self._printer_selection_dialog.show()
|
||||
|
||||
|
|
@ -182,10 +185,10 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
target_printer = yield #Potentially wait on the user to select a target printer.
|
||||
|
||||
# Using buffering greatly reduces the write time for many lines of gcode
|
||||
|
||||
stream = io.BytesIO() # type: Union[io.BytesIO, io.StringIO]# Binary mode.
|
||||
if preferred_format["mode"] == FileWriter.OutputMode.TextMode:
|
||||
stream = io.StringIO()
|
||||
else: #Binary mode.
|
||||
stream = io.BytesIO()
|
||||
|
||||
job = WriteFileJob(writer, stream, nodes, preferred_format["mode"])
|
||||
|
||||
|
|
@ -201,7 +204,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
yield True #Return that we had success!
|
||||
yield #To prevent having to catch the StopIteration exception.
|
||||
|
||||
def _sendPrintJobWaitOnWriteJobFinished(self, job):
|
||||
def _sendPrintJobWaitOnWriteJobFinished(self, job: WriteFileJob) -> None:
|
||||
self._write_job_progress_message.hide()
|
||||
|
||||
self._progress_message = Message(i18n_catalog.i18nc("@info:status", "Sending data to printer"), lifetime = 0, dismissable = False, progress = -1,
|
||||
|
|
@ -222,40 +225,41 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Add user name to the print_job
|
||||
parts.append(self.createFormPart("name=owner", bytes(self._getUserName(), "utf-8"), "text/plain"))
|
||||
|
||||
file_name = Application.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
||||
file_name = CuraApplication.getInstance().getPrintInformation().jobName + "." + preferred_format["extension"]
|
||||
|
||||
output = stream.getvalue() #Either str or bytes depending on the output mode.
|
||||
if isinstance(stream, io.StringIO):
|
||||
output = output.encode("utf-8")
|
||||
output = cast(str, output).encode("utf-8")
|
||||
output = cast(bytes, output)
|
||||
|
||||
parts.append(self.createFormPart("name=\"file\"; filename=\"%s\"" % file_name, output))
|
||||
|
||||
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, onFinished=self._onPostPrintJobFinished, onProgress=self._onUploadPrintJobProgress)
|
||||
self._latest_reply_handler = self.postFormWithParts("print_jobs/", parts, on_finished = self._onPostPrintJobFinished, on_progress = self._onUploadPrintJobProgress)
|
||||
|
||||
@pyqtProperty(QObject, notify=activePrinterChanged)
|
||||
@pyqtProperty(QObject, notify = activePrinterChanged)
|
||||
def activePrinter(self) -> Optional[PrinterOutputModel]:
|
||||
return self._active_printer
|
||||
|
||||
@pyqtSlot(QObject)
|
||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]):
|
||||
def setActivePrinter(self, printer: Optional[PrinterOutputModel]) -> None:
|
||||
if self._active_printer != printer:
|
||||
if self._active_printer and self._active_printer.camera:
|
||||
self._active_printer.camera.stop()
|
||||
self._active_printer = printer
|
||||
self.activePrinterChanged.emit()
|
||||
|
||||
def _onPostPrintJobFinished(self, reply):
|
||||
def _onPostPrintJobFinished(self, reply: QNetworkReply) -> None:
|
||||
self._progress_message.hide()
|
||||
self._compressing_gcode = False
|
||||
self._sending_gcode = False
|
||||
|
||||
def _onUploadPrintJobProgress(self, bytes_sent:int, bytes_total:int):
|
||||
def _onUploadPrintJobProgress(self, bytes_sent: int, bytes_total: int) -> None:
|
||||
if bytes_total > 0:
|
||||
new_progress = bytes_sent / bytes_total * 100
|
||||
# Treat upload progress as response. Uploading can take more than 10 seconds, so if we don't, we can get
|
||||
# timeout responses if this happens.
|
||||
self._last_response_time = time()
|
||||
if new_progress > self._progress_message.getProgress():
|
||||
if self._progress_message and new_progress > self._progress_message.getProgress():
|
||||
self._progress_message.show() # Ensure that the message is visible.
|
||||
self._progress_message.setProgress(bytes_sent / bytes_total * 100)
|
||||
|
||||
|
|
@ -272,16 +276,18 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._success_message.actionTriggered.connect(self._successMessageActionTriggered)
|
||||
self._success_message.show()
|
||||
else:
|
||||
self._progress_message.setProgress(0)
|
||||
self._progress_message.hide()
|
||||
if self._progress_message is not None:
|
||||
self._progress_message.setProgress(0)
|
||||
self._progress_message.hide()
|
||||
|
||||
def _progressMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
|
||||
def _progressMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
||||
if action_id == "Abort":
|
||||
Logger.log("d", "User aborted sending print to remote.")
|
||||
self._progress_message.hide()
|
||||
if self._progress_message is not None:
|
||||
self._progress_message.hide()
|
||||
self._compressing_gcode = False
|
||||
self._sending_gcode = False
|
||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||
|
||||
# After compressing the sliced model Cura sends data to printer, to stop receiving updates from the request
|
||||
# the "reply" should be disconnected
|
||||
|
|
@ -289,9 +295,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._latest_reply_handler.disconnect()
|
||||
self._latest_reply_handler = None
|
||||
|
||||
def _successMessageActionTriggered(self, message_id: Optional[str]=None, action_id: Optional[str]=None) -> None:
|
||||
def _successMessageActionTriggered(self, message_id: Optional[str] = None, action_id: Optional[str] = None) -> None:
|
||||
if action_id == "View":
|
||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
@pyqtSlot()
|
||||
def openPrintJobControlPanel(self) -> None:
|
||||
|
|
@ -303,21 +309,21 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
Logger.log("d", "Opening printer control panel...")
|
||||
QDesktopServices.openUrl(QUrl("http://" + self._address + "/printers"))
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
def printJobs(self)-> List[PrintJobOutputModel] :
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def printJobs(self)-> List[PrintJobOutputModel]:
|
||||
return self._print_jobs
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def queuedPrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
return [print_job for print_job in self._print_jobs if print_job.state == "queued"]
|
||||
|
||||
@pyqtProperty("QVariantList", notify=printJobsChanged)
|
||||
@pyqtProperty("QVariantList", notify = printJobsChanged)
|
||||
def activePrintJobs(self) -> List[PrintJobOutputModel]:
|
||||
return [print_job for print_job in self._print_jobs if print_job.assignedPrinter is not None and print_job.state != "queued"]
|
||||
|
||||
@pyqtProperty("QVariantList", notify=clusterPrintersChanged)
|
||||
def connectedPrintersTypeCount(self) -> List[PrinterOutputModel]:
|
||||
printer_count = {}
|
||||
@pyqtProperty("QVariantList", notify = clusterPrintersChanged)
|
||||
def connectedPrintersTypeCount(self) -> List[Dict[str, str]]:
|
||||
printer_count = {} # type: Dict[str, int]
|
||||
for printer in self._printers:
|
||||
if printer.type in printer_count:
|
||||
printer_count[printer.type] += 1
|
||||
|
|
@ -325,20 +331,20 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
printer_count[printer.type] = 1
|
||||
result = []
|
||||
for machine_type in printer_count:
|
||||
result.append({"machine_type": machine_type, "count": printer_count[machine_type]})
|
||||
result.append({"machine_type": machine_type, "count": str(printer_count[machine_type])})
|
||||
return result
|
||||
|
||||
@pyqtSlot(int, result=str)
|
||||
@pyqtSlot(int, result = str)
|
||||
def formatDuration(self, seconds: int) -> str:
|
||||
return Duration(seconds).getDisplayString(DurationFormat.Format.Short)
|
||||
|
||||
@pyqtSlot(int, result=str)
|
||||
@pyqtSlot(int, result = str)
|
||||
def getTimeCompleted(self, time_remaining: int) -> str:
|
||||
current_time = time()
|
||||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||
return "{hour:02d}:{minute:02d}".format(hour=datetime_completed.hour, minute=datetime_completed.minute)
|
||||
|
||||
@pyqtSlot(int, result=str)
|
||||
@pyqtSlot(int, result = str)
|
||||
def getDateCompleted(self, time_remaining: int) -> str:
|
||||
current_time = time()
|
||||
datetime_completed = datetime.fromtimestamp(current_time + time_remaining)
|
||||
|
|
@ -373,10 +379,9 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self.sendMaterialProfiles()
|
||||
|
||||
def _update(self) -> None:
|
||||
if not super()._update():
|
||||
return
|
||||
self.get("printers/", onFinished=self._onGetPrintersDataFinished)
|
||||
self.get("print_jobs/", onFinished=self._onGetPrintJobsFinished)
|
||||
super()._update()
|
||||
self.get("printers/", on_finished = self._onGetPrintersDataFinished)
|
||||
self.get("print_jobs/", on_finished = self._onGetPrintJobsFinished)
|
||||
|
||||
def _onGetPrintJobsFinished(self, reply: QNetworkReply) -> None:
|
||||
if not checkValidGetReply(reply):
|
||||
|
|
@ -415,7 +420,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
removed_jobs = [print_job for print_job in self._print_jobs if print_job not in print_jobs_seen]
|
||||
|
||||
for removed_job in removed_jobs:
|
||||
job_list_changed |= self._removeJob(removed_job)
|
||||
job_list_changed = job_list_changed or self._removeJob(removed_job)
|
||||
|
||||
if job_list_changed:
|
||||
self.printJobsChanged.emit() # Do a single emit for all print job changes.
|
||||
|
|
@ -449,27 +454,27 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
if removed_printers or printer_list_changed:
|
||||
self.printersChanged.emit()
|
||||
|
||||
def _createPrinterModel(self, data: Dict) -> PrinterOutputModel:
|
||||
printer = PrinterOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||
number_of_extruders=self._number_of_extruders)
|
||||
def _createPrinterModel(self, data: Dict[str, Any]) -> PrinterOutputModel:
|
||||
printer = PrinterOutputModel(output_controller = ClusterUM3PrinterOutputController(self),
|
||||
number_of_extruders = self._number_of_extruders)
|
||||
printer.setCamera(NetworkCamera("http://" + data["ip_address"] + ":8080/?action=stream"))
|
||||
self._printers.append(printer)
|
||||
return printer
|
||||
|
||||
def _createPrintJobModel(self, data: Dict) -> PrintJobOutputModel:
|
||||
def _createPrintJobModel(self, data: Dict[str, Any]) -> PrintJobOutputModel:
|
||||
print_job = PrintJobOutputModel(output_controller=ClusterUM3PrinterOutputController(self),
|
||||
key=data["uuid"], name= data["name"])
|
||||
print_job.stateChanged.connect(self._printJobStateChanged)
|
||||
self._print_jobs.append(print_job)
|
||||
return print_job
|
||||
|
||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict) -> None:
|
||||
def _updatePrintJob(self, print_job: PrintJobOutputModel, data: Dict[str, Any]) -> None:
|
||||
print_job.updateTimeTotal(data["time_total"])
|
||||
print_job.updateTimeElapsed(data["time_elapsed"])
|
||||
print_job.updateState(data["status"])
|
||||
print_job.updateOwner(data["owner"])
|
||||
|
||||
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict) -> None:
|
||||
def _updatePrinter(self, printer: PrinterOutputModel, data: Dict[str, Any]) -> None:
|
||||
# For some unknown reason the cluster wants UUID for everything, except for sending a job directly to a printer.
|
||||
# Then we suddenly need the unique name. So in order to not have to mess up all the other code, we save a mapping.
|
||||
self._printer_uuid_to_unique_name_mapping[data["uuid"]] = data["unique_name"]
|
||||
|
|
@ -485,7 +490,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
printer.updateKey(data["uuid"])
|
||||
printer.updateType(data["machine_variant"])
|
||||
|
||||
# Do not store the buildplate information that comes from connect if the current printer has not buildplate information
|
||||
# Do not store the build plate information that comes from connect if the current printer has not build plate information
|
||||
if "build_plate" in data and machine_definition.getMetaDataEntry("has_variant_buildplates", False):
|
||||
printer.updateBuildplateName(data["build_plate"]["type"])
|
||||
if not data["enabled"]:
|
||||
|
|
@ -524,7 +529,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
brand=brand, color=color, name=name)
|
||||
extruder.updateActiveMaterial(material)
|
||||
|
||||
def _removeJob(self, job: PrintJobOutputModel):
|
||||
def _removeJob(self, job: PrintJobOutputModel) -> bool:
|
||||
if job not in self._print_jobs:
|
||||
return False
|
||||
|
||||
|
|
@ -535,7 +540,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
return True
|
||||
|
||||
def _removePrinter(self, printer: PrinterOutputModel):
|
||||
def _removePrinter(self, printer: PrinterOutputModel) -> None:
|
||||
self._printers.remove(printer)
|
||||
if self._active_printer == printer:
|
||||
self._active_printer = None
|
||||
|
|
@ -549,16 +554,16 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
job = SendMaterialJob(device = self)
|
||||
job.run()
|
||||
|
||||
def loadJsonFromReply(reply):
|
||||
def loadJsonFromReply(reply: QNetworkReply) -> Optional[List[Dict[str, Any]]]:
|
||||
try:
|
||||
result = json.loads(bytes(reply.readAll()).decode("utf-8"))
|
||||
except json.decoder.JSONDecodeError:
|
||||
Logger.logException("w", "Unable to decode JSON from reply.")
|
||||
return
|
||||
return None
|
||||
return result
|
||||
|
||||
|
||||
def checkValidGetReply(reply):
|
||||
def checkValidGetReply(reply: QNetworkReply) -> bool:
|
||||
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
|
||||
|
||||
if status_code != 200:
|
||||
|
|
@ -567,7 +572,8 @@ def checkValidGetReply(reply):
|
|||
return True
|
||||
|
||||
|
||||
def findByKey(list, key):
|
||||
def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: str) -> Optional[PrintJobOutputModel]:
|
||||
for item in list:
|
||||
if item.key == key:
|
||||
return item
|
||||
return item
|
||||
return None
|
||||
|
|
@ -1,43 +1,47 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import os.path
|
||||
import time
|
||||
from typing import Optional
|
||||
|
||||
from PyQt5.QtCore import pyqtSignal, pyqtProperty, pyqtSlot, QObject
|
||||
|
||||
from UM.Application import Application
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.MachineAction import MachineAction
|
||||
|
||||
from .UM3OutputDevicePlugin import UM3OutputDevicePlugin
|
||||
|
||||
catalog = i18nCatalog("cura")
|
||||
|
||||
|
||||
class DiscoverUM3Action(MachineAction):
|
||||
discoveredDevicesChanged = pyqtSignal()
|
||||
|
||||
def __init__(self):
|
||||
def __init__(self) -> None:
|
||||
super().__init__("DiscoverUM3Action", catalog.i18nc("@action","Connect via Network"))
|
||||
self._qml_url = "DiscoverUM3Action.qml"
|
||||
|
||||
self._network_plugin = None
|
||||
self._network_plugin = None #type: Optional[UM3OutputDevicePlugin]
|
||||
|
||||
self.__additional_components_context = None
|
||||
self.__additional_component = None
|
||||
self.__additional_components_view = None
|
||||
self.__additional_components_view = None #type: Optional[QObject]
|
||||
|
||||
Application.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
|
||||
CuraApplication.getInstance().engineCreatedSignal.connect(self._createAdditionalComponentsView)
|
||||
|
||||
self._last_zero_conf_event_time = time.time()
|
||||
self._last_zero_conf_event_time = time.time() #type: float
|
||||
|
||||
# Time to wait after a zero-conf service change before allowing a zeroconf reset
|
||||
self._zero_conf_change_grace_period = 0.25
|
||||
self._zero_conf_change_grace_period = 0.25 #type: float
|
||||
|
||||
@pyqtSlot()
|
||||
def startDiscovery(self):
|
||||
if not self._network_plugin:
|
||||
Logger.log("d", "Starting device discovery.")
|
||||
self._network_plugin = Application.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
|
||||
self._network_plugin = CuraApplication.getInstance().getOutputDeviceManager().getOutputDevicePlugin("UM3NetworkPrinting")
|
||||
self._network_plugin.discoveredDevicesChanged.connect(self._onDeviceDiscoveryChanged)
|
||||
self.discoveredDevicesChanged.emit()
|
||||
|
||||
|
|
@ -93,16 +97,16 @@ class DiscoverUM3Action(MachineAction):
|
|||
return []
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setGroupName(self, group_name):
|
||||
def setGroupName(self, group_name: str) -> None:
|
||||
Logger.log("d", "Attempting to set the group name of the active machine to %s", group_name)
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
meta_data = global_container_stack.getMetaData()
|
||||
if "connect_group_name" in meta_data:
|
||||
previous_connect_group_name = meta_data["connect_group_name"]
|
||||
global_container_stack.setMetaDataEntry("connect_group_name", group_name)
|
||||
# Find all the places where there is the same group name and change it accordingly
|
||||
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
|
||||
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "connect_group_name", value = previous_connect_group_name, new_value = group_name)
|
||||
else:
|
||||
global_container_stack.addMetaDataEntry("connect_group_name", group_name)
|
||||
global_container_stack.addMetaDataEntry("hidden", False)
|
||||
|
|
@ -112,9 +116,9 @@ class DiscoverUM3Action(MachineAction):
|
|||
self._network_plugin.reCheckConnections()
|
||||
|
||||
@pyqtSlot(str)
|
||||
def setKey(self, key):
|
||||
def setKey(self, key: str) -> None:
|
||||
Logger.log("d", "Attempting to set the network key of the active machine to %s", key)
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
meta_data = global_container_stack.getMetaData()
|
||||
if "um_network_key" in meta_data:
|
||||
|
|
@ -124,7 +128,7 @@ class DiscoverUM3Action(MachineAction):
|
|||
Logger.log("d", "Removing old authentication id %s for device %s", global_container_stack.getMetaDataEntry("network_authentication_id", None), key)
|
||||
global_container_stack.removeMetaDataEntry("network_authentication_id")
|
||||
global_container_stack.removeMetaDataEntry("network_authentication_key")
|
||||
Application.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
|
||||
CuraApplication.getInstance().getMachineManager().replaceContainersMetadata(key = "um_network_key", value = previous_network_key, new_value = key)
|
||||
else:
|
||||
global_container_stack.addMetaDataEntry("um_network_key", key)
|
||||
|
||||
|
|
@ -134,7 +138,7 @@ class DiscoverUM3Action(MachineAction):
|
|||
|
||||
@pyqtSlot(result = str)
|
||||
def getStoredKey(self) -> str:
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
meta_data = global_container_stack.getMetaData()
|
||||
if "um_network_key" in meta_data:
|
||||
|
|
@ -149,12 +153,12 @@ class DiscoverUM3Action(MachineAction):
|
|||
return ""
|
||||
|
||||
@pyqtSlot(str, result = bool)
|
||||
def existsKey(self, key) -> bool:
|
||||
return Application.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
||||
def existsKey(self, key: str) -> bool:
|
||||
return CuraApplication.getInstance().getMachineManager().existNetworkInstances(network_key = key)
|
||||
|
||||
@pyqtSlot()
|
||||
def loadConfigurationFromPrinter(self):
|
||||
machine_manager = Application.getInstance().getMachineManager()
|
||||
def loadConfigurationFromPrinter(self) -> None:
|
||||
machine_manager = CuraApplication.getInstance().getMachineManager()
|
||||
hotend_ids = machine_manager.printerOutputDevices[0].hotendIds
|
||||
for index in range(len(hotend_ids)):
|
||||
machine_manager.printerOutputDevices[0].hotendIdChanged.emit(index, hotend_ids[index])
|
||||
|
|
@ -162,16 +166,16 @@ class DiscoverUM3Action(MachineAction):
|
|||
for index in range(len(material_ids)):
|
||||
machine_manager.printerOutputDevices[0].materialIdChanged.emit(index, material_ids[index])
|
||||
|
||||
def _createAdditionalComponentsView(self):
|
||||
def _createAdditionalComponentsView(self) -> None:
|
||||
Logger.log("d", "Creating additional ui components for UM3.")
|
||||
|
||||
# Create networking dialog
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("UM3NetworkPrinting"), "UM3InfoComponents.qml")
|
||||
self.__additional_components_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
self.__additional_components_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||
if not self.__additional_components_view:
|
||||
Logger.log("w", "Could not create ui components for UM3.")
|
||||
return
|
||||
|
||||
# Create extra components
|
||||
Application.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||
Application.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
||||
CuraApplication.getInstance().addAdditionalComponent("monitorButtons", self.__additional_components_view.findChild(QObject, "networkPrinterConnectButton"))
|
||||
CuraApplication.getInstance().addAdditionalComponent("machinesDetailPane", self.__additional_components_view.findChild(QObject, "networkPrinterConnectionInfo"))
|
||||
|
|
|
|||
|
|
@ -1,3 +1,8 @@
|
|||
from typing import List, Optional
|
||||
|
||||
from UM.FileHandler.FileHandler import FileHandler
|
||||
from UM.Scene.SceneNode import SceneNode
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutput.NetworkedPrinterOutputDevice import NetworkedPrinterOutputDevice, AuthState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
|
@ -9,12 +14,11 @@ from cura.Settings.ExtruderManager import ExtruderManager
|
|||
|
||||
from UM.Logger import Logger
|
||||
from UM.Settings.ContainerRegistry import ContainerRegistry
|
||||
from UM.Application import Application
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Message import Message
|
||||
|
||||
from PyQt5.QtNetwork import QNetworkRequest
|
||||
from PyQt5.QtCore import QTimer, QCoreApplication
|
||||
from PyQt5.QtCore import QTimer
|
||||
from PyQt5.QtWidgets import QMessageBox
|
||||
|
||||
from .LegacyUM3PrinterOutputController import LegacyUM3PrinterOutputController
|
||||
|
|
@ -39,7 +43,7 @@ i18n_catalog = i18nCatalog("cura")
|
|||
# 4. At this point the machine either has the state Authenticated or AuthenticationDenied.
|
||||
# 5. As a final step, we verify the authentication, as this forces the QT manager to setup the authenticator.
|
||||
class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
||||
def __init__(self, device_id, address: str, properties, parent = None):
|
||||
def __init__(self, device_id, address: str, properties, parent = None) -> None:
|
||||
super().__init__(device_id = device_id, address = address, properties = properties, parent = parent)
|
||||
self._api_prefix = "/api/v1/"
|
||||
self._number_of_extruders = 2
|
||||
|
|
@ -125,7 +129,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
def connect(self):
|
||||
super().connect()
|
||||
self._setupMessages()
|
||||
global_container = Application.getInstance().getGlobalContainerStack()
|
||||
global_container = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container:
|
||||
self._authentication_id = global_container.getMetaDataEntry("network_authentication_id", None)
|
||||
self._authentication_key = global_container.getMetaDataEntry("network_authentication_key", None)
|
||||
|
|
@ -168,7 +172,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# NotImplementedError. We can simply ignore these.
|
||||
pass
|
||||
|
||||
def requestWrite(self, nodes, file_name=None, filter_by_machine=False, file_handler=None, **kwargs):
|
||||
def requestWrite(self, nodes: List[SceneNode], file_name: Optional[str] = None, limit_mimetypes: bool = False, file_handler: Optional[FileHandler] = None, **kwargs: str) -> None:
|
||||
if not self.activePrinter:
|
||||
# No active printer. Unable to write
|
||||
return
|
||||
|
|
@ -183,8 +187,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
self.writeStarted.emit(self)
|
||||
|
||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict", [])
|
||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict", [])
|
||||
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_list = gcode_dict[active_build_plate_id]
|
||||
|
||||
if not gcode_list:
|
||||
|
|
@ -203,7 +207,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
for error in errors:
|
||||
detailed_text += error + "\n"
|
||||
|
||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||
text,
|
||||
informative_text,
|
||||
detailed_text,
|
||||
|
|
@ -225,7 +229,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
for warning in warnings:
|
||||
detailed_text += warning + "\n"
|
||||
|
||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Mismatched configuration"),
|
||||
text,
|
||||
informative_text,
|
||||
detailed_text,
|
||||
|
|
@ -239,7 +243,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._startPrint()
|
||||
|
||||
# Notify the UI that a switch to the print monitor should happen
|
||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
def _startPrint(self):
|
||||
Logger.log("i", "Sending print job to printer.")
|
||||
|
|
@ -264,7 +268,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
# Abort was called.
|
||||
return
|
||||
|
||||
file_name = "%s.gcode.gz" % Application.getInstance().getPrintInformation().jobName
|
||||
file_name = "%s.gcode.gz" % CuraApplication.getInstance().getPrintInformation().jobName
|
||||
self.postForm("print_job", "form-data; name=\"file\";filename=\"%s\"" % file_name, compressed_gcode,
|
||||
onFinished=self._onPostPrintJobFinished)
|
||||
|
||||
|
|
@ -276,7 +280,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._progress_message.hide()
|
||||
self._compressing_gcode = False
|
||||
self._sending_gcode = False
|
||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||
|
||||
def _onPostPrintJobFinished(self, reply):
|
||||
self._progress_message.hide()
|
||||
|
|
@ -301,7 +305,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
if button == QMessageBox.Yes:
|
||||
self._startPrint()
|
||||
else:
|
||||
Application.getInstance().getController().setActiveStage("PrepareStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("PrepareStage")
|
||||
# For some unknown reason Cura on OSX will hang if we do the call back code
|
||||
# immediately without first returning and leaving QML's event system.
|
||||
|
||||
|
|
@ -309,7 +313,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
def _checkForErrors(self):
|
||||
errors = []
|
||||
print_information = Application.getInstance().getPrintInformation()
|
||||
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||
if not print_information.materialLengths:
|
||||
Logger.log("w", "There is no material length information. Unable to check for errors.")
|
||||
return errors
|
||||
|
|
@ -329,7 +333,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
|
||||
def _checkForWarnings(self):
|
||||
warnings = []
|
||||
print_information = Application.getInstance().getPrintInformation()
|
||||
print_information = CuraApplication.getInstance().getPrintInformation()
|
||||
|
||||
if not print_information.materialLengths:
|
||||
Logger.log("w", "There is no material length information. Unable to check for warnings.")
|
||||
|
|
@ -452,7 +456,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._authentication_failed_message.show()
|
||||
|
||||
def _saveAuthentication(self):
|
||||
global_container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
global_container_stack = CuraApplication.getInstance().getGlobalContainerStack()
|
||||
if global_container_stack:
|
||||
if "network_authentication_key" in global_container_stack.getMetaData():
|
||||
global_container_stack.setMetaDataEntry("network_authentication_key", self._authentication_key)
|
||||
|
|
@ -465,7 +469,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
global_container_stack.addMetaDataEntry("network_authentication_id", self._authentication_id)
|
||||
|
||||
# Force save so we are sure the data is not lost.
|
||||
Application.getInstance().saveStack(global_container_stack)
|
||||
CuraApplication.getInstance().saveStack(global_container_stack)
|
||||
Logger.log("i", "Authentication succeeded for id %s and key %s", self._authentication_id,
|
||||
self._getSafeAuthKey())
|
||||
else:
|
||||
|
|
@ -496,7 +500,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
self._authentication_id = None
|
||||
|
||||
self.post("auth/request",
|
||||
json.dumps({"application": "Cura-" + Application.getInstance().getVersion(),
|
||||
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
|
||||
"user": self._getUserName()}).encode(),
|
||||
onFinished=self._onRequestAuthenticationFinished)
|
||||
|
||||
|
|
@ -542,7 +546,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
|
|||
"Got status code {status_code} while trying to get printer data".format(status_code=status_code))
|
||||
|
||||
def materialHotendChangedMessage(self, callback):
|
||||
Application.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
|
||||
CuraApplication.getInstance().messageBox(i18n_catalog.i18nc("@window:title", "Sync with your printer"),
|
||||
i18n_catalog.i18nc("@label",
|
||||
"Would you like to use your current printer configuration in Cura?"),
|
||||
i18n_catalog.i18nc("@label",
|
||||
|
|
|
|||
|
|
@ -3,10 +3,10 @@
|
|||
|
||||
from UM.Logger import Logger
|
||||
from UM.i18n import i18nCatalog
|
||||
from UM.Application import Application
|
||||
from UM.Qt.Duration import DurationFormat
|
||||
from UM.PluginRegistry import PluginRegistry
|
||||
|
||||
from cura.CuraApplication import CuraApplication
|
||||
from cura.PrinterOutputDevice import PrinterOutputDevice, ConnectionState
|
||||
from cura.PrinterOutput.PrinterOutputModel import PrinterOutputModel
|
||||
from cura.PrinterOutput.PrintJobOutputModel import PrintJobOutputModel
|
||||
|
|
@ -22,7 +22,7 @@ from threading import Thread, Event
|
|||
from time import time, sleep
|
||||
from queue import Queue
|
||||
from enum import IntEnum
|
||||
from typing import Union, Optional, List
|
||||
from typing import Union, Optional, List, cast
|
||||
|
||||
import re
|
||||
import functools # Used for reduce
|
||||
|
|
@ -35,7 +35,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
firmwareProgressChanged = pyqtSignal()
|
||||
firmwareUpdateStateChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, serial_port: str, baud_rate: Optional[int] = None):
|
||||
def __init__(self, serial_port: str, baud_rate: Optional[int] = None) -> None:
|
||||
super().__init__(serial_port)
|
||||
self.setName(catalog.i18nc("@item:inmenu", "USB printing"))
|
||||
self.setShortDescription(catalog.i18nc("@action:button Preceded by 'Ready to'.", "Print via USB"))
|
||||
|
|
@ -68,7 +68,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._is_printing = False # A print is being sent.
|
||||
|
||||
## Set when print is started in order to check running time.
|
||||
self._print_start_time = None # type: Optional[int]
|
||||
self._print_start_time = None # type: Optional[float]
|
||||
self._print_estimated_time = None # type: Optional[int]
|
||||
|
||||
self._accepts_commands = True
|
||||
|
|
@ -83,7 +83,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self.setConnectionText(catalog.i18nc("@info:status", "Connected via USB"))
|
||||
|
||||
# Queue for commands that need to be sent.
|
||||
self._command_queue = Queue()
|
||||
self._command_queue = Queue() # type: Queue
|
||||
# Event to indicate that an "ok" was received from the printer after sending a command.
|
||||
self._command_received = Event()
|
||||
self._command_received.set()
|
||||
|
|
@ -107,11 +107,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
# cancel any ongoing preheat timer before starting a print
|
||||
self._printers[0].getController().stopPreheatTimers()
|
||||
|
||||
Application.getInstance().getController().setActiveStage("MonitorStage")
|
||||
CuraApplication.getInstance().getController().setActiveStage("MonitorStage")
|
||||
|
||||
# find the G-code for the active build plate to print
|
||||
active_build_plate_id = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
|
||||
active_build_plate_id = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
|
||||
gcode_dict = getattr(CuraApplication.getInstance().getController().getScene(), "gcode_dict")
|
||||
gcode_list = gcode_dict[active_build_plate_id]
|
||||
|
||||
self._printGCode(gcode_list)
|
||||
|
|
@ -121,7 +121,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
def showFirmwareInterface(self):
|
||||
if self._firmware_view is None:
|
||||
path = os.path.join(PluginRegistry.getInstance().getPluginPath("USBPrinting"), "FirmwareUpdateWindow.qml")
|
||||
self._firmware_view = Application.getInstance().createQmlComponent(path, {"manager": self})
|
||||
self._firmware_view = CuraApplication.getInstance().createQmlComponent(path, {"manager": self})
|
||||
|
||||
self._firmware_view.show()
|
||||
|
||||
|
|
@ -180,7 +180,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
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.
|
||||
Application.getInstance().callLater(self.connect)
|
||||
CuraApplication.getInstance().callLater(self.connect)
|
||||
|
||||
@pyqtProperty(float, notify = firmwareProgressChanged)
|
||||
def firmwareProgress(self):
|
||||
|
|
@ -214,7 +214,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
self._gcode_position = 0
|
||||
self._print_start_time = time()
|
||||
|
||||
self._print_estimated_time = int(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
|
||||
self._print_estimated_time = int(CuraApplication.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.Seconds))
|
||||
|
||||
for i in range(0, 4): # Push first 4 entries before accepting other inputs
|
||||
self._sendNextGcodeLine()
|
||||
|
|
@ -250,7 +250,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
except SerialException:
|
||||
Logger.log("w", "An exception occured while trying to create serial connection")
|
||||
return
|
||||
container_stack = Application.getInstance().getGlobalContainerStack()
|
||||
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)]
|
||||
|
|
@ -277,13 +277,12 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
|
|||
if self._serial is None or self._connection_state != ConnectionState.connected:
|
||||
return
|
||||
|
||||
if type(command == str):
|
||||
command = command.encode()
|
||||
if not command.endswith(b"\n"):
|
||||
command += b"\n"
|
||||
new_command = cast(bytes, command) if type(command) is bytes else cast(str, command).encode() # type: bytes
|
||||
if not new_command.endswith(b"\n"):
|
||||
new_command += b"\n"
|
||||
try:
|
||||
self._command_received.clear()
|
||||
self._serial.write(command)
|
||||
self._serial.write(new_command)
|
||||
except SerialTimeoutException:
|
||||
Logger.log("w", "Timeout when sending command to printer via USB.")
|
||||
self._command_received.set()
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ class USBPrinterOutputDeviceManager(QObject, OutputDevicePlugin):
|
|||
|
||||
return list(base_list)
|
||||
|
||||
__instance = None
|
||||
__instance = None # type: USBPrinterOutputDeviceManager
|
||||
|
||||
@classmethod
|
||||
def getInstance(cls, *args, **kwargs) -> "USBPrinterOutputDeviceManager":
|
||||
|
|
|
|||
|
|
@ -44,20 +44,18 @@ class Profile:
|
|||
|
||||
# Parse the general section.
|
||||
self._name = parser.get("general", "name")
|
||||
self._type = parser.get("general", "type", fallback = None)
|
||||
self._type = parser.get("general", "type")
|
||||
self._weight = None
|
||||
if "weight" in parser["general"]:
|
||||
self._weight = int(parser.get("general", "weight"))
|
||||
else:
|
||||
self._weight = None
|
||||
self._machine_type_id = parser.get("general", "machine_type", fallback = None)
|
||||
self._machine_variant_name = parser.get("general", "machine_variant", fallback = None)
|
||||
self._machine_instance_name = parser.get("general", "machine_instance", fallback = None)
|
||||
self._machine_type_id = parser.get("general", "machine_type")
|
||||
self._machine_variant_name = parser.get("general", "machine_variant")
|
||||
self._machine_instance_name = parser.get("general", "machine_instance")
|
||||
self._material_name = None
|
||||
if "material" in parser["general"]: #Note: Material name is unused in this upgrade.
|
||||
self._material_name = parser.get("general", "material")
|
||||
elif self._type == "material":
|
||||
self._material_name = parser.get("general", "name", fallback = None)
|
||||
else:
|
||||
self._material_name = None
|
||||
self._material_name = parser.get("general", "name")
|
||||
|
||||
# Parse the settings.
|
||||
self._settings = {} # type: Dict[str,str]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
import configparser
|
||||
import io
|
||||
|
||||
from UM.VersionUpgrade import VersionUpgrade
|
||||
|
||||
|
||||
## Upgrades configurations from the state they were in at version 3.4 to the
|
||||
# state they should be in at version 4.0.
|
||||
class VersionUpgrade34to40(VersionUpgrade):
|
||||
|
||||
## Gets the version number from a CFG file in Uranium's 3.3 format.
|
||||
#
|
||||
# Since the format may change, this is implemented for the 3.3 format only
|
||||
# and needs to be included in the version upgrade system rather than
|
||||
# globally in Uranium.
|
||||
#
|
||||
# \param serialised The serialised form of a CFG file.
|
||||
# \return The version number stored in the CFG file.
|
||||
# \raises ValueError The format of the version number in the file is
|
||||
# incorrect.
|
||||
# \raises KeyError The format of the file is incorrect.
|
||||
def getCfgVersion(self, serialised):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialised)
|
||||
format_version = int(parser.get("general", "version")) #Explicitly give an exception when this fails. That means that the file format is not recognised.
|
||||
setting_version = int(parser.get("metadata", "setting_version", fallback = 0))
|
||||
return format_version * 1000000 + setting_version
|
||||
|
||||
## Upgrades Preferences to have the new version number.
|
||||
def upgradePreferences(self, serialized, filename):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
if "metadata" not in parser:
|
||||
parser["metadata"] = {}
|
||||
parser["metadata"]["setting_version"] = "5"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
## Upgrades stacks to have the new version number.
|
||||
def upgradeStack(self, serialized, filename):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "5"
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
## Upgrades instance containers to have the new version
|
||||
# number.
|
||||
def upgradeInstanceContainer(self, serialized, filename):
|
||||
parser = configparser.ConfigParser(interpolation = None)
|
||||
parser.read_string(serialized)
|
||||
|
||||
# Update version number.
|
||||
parser["general"]["version"] = "4"
|
||||
parser["metadata"]["setting_version"] = "5"
|
||||
|
||||
self._resetConcentric3DInfillPattern(parser)
|
||||
|
||||
result = io.StringIO()
|
||||
parser.write(result)
|
||||
return [filename], [result.getvalue()]
|
||||
|
||||
def _resetConcentric3DInfillPattern(self, parser):
|
||||
if "values" not in parser:
|
||||
return
|
||||
|
||||
# Reset the patterns which are concentric 3d
|
||||
for key in ("infill_pattern",
|
||||
"support_pattern",
|
||||
"support_interface_pattern",
|
||||
"support_roof_pattern",
|
||||
"support_bottom_pattern",
|
||||
):
|
||||
if key not in parser["values"]:
|
||||
continue
|
||||
if parser["values"][key] == "concentric_3d":
|
||||
del parser["values"][key]
|
||||
|
||||
52
plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
Normal file
52
plugins/VersionUpgrade/VersionUpgrade34to40/__init__.py
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
# Copyright (c) 2018 Ultimaker B.V.
|
||||
# Cura is released under the terms of the LGPLv3 or higher.
|
||||
|
||||
from . import VersionUpgrade34to40
|
||||
|
||||
upgrade = VersionUpgrade34to40.VersionUpgrade34to40()
|
||||
|
||||
|
||||
def getMetaData():
|
||||
return {
|
||||
"version_upgrade": {
|
||||
# From To Upgrade function
|
||||
("preferences", 6000004): ("preferences", 6000005, upgrade.upgradePreferences),
|
||||
|
||||
("definition_changes", 4000004): ("definition_changes", 4000005, upgrade.upgradeInstanceContainer),
|
||||
("quality_changes", 4000004): ("quality_changes", 4000005, upgrade.upgradeInstanceContainer),
|
||||
("user", 4000004): ("user", 4000005, upgrade.upgradeInstanceContainer),
|
||||
|
||||
("machine_stack", 4000005): ("machine_stack", 4000005, upgrade.upgradeStack),
|
||||
("extruder_train", 4000005): ("extruder_train", 4000005, upgrade.upgradeStack),
|
||||
},
|
||||
"sources": {
|
||||
"preferences": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"."}
|
||||
},
|
||||
"machine_stack": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./machine_instances"}
|
||||
},
|
||||
"extruder_train": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./extruders"}
|
||||
},
|
||||
"definition_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./definition_changes"}
|
||||
},
|
||||
"quality_changes": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./quality_changes"}
|
||||
},
|
||||
"user": {
|
||||
"get_version": upgrade.getCfgVersion,
|
||||
"location": {"./user"}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
return { "version_upgrade": upgrade }
|
||||
8
plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
Normal file
8
plugins/VersionUpgrade/VersionUpgrade34to40/plugin.json
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"name": "Version Upgrade 3.4 to 4.0",
|
||||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
|
||||
"api": 4,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
@ -26,8 +26,8 @@ except ImportError:
|
|||
DEFAULT_SUBDIV = 16 # Default subdivision factor for spheres, cones, and cylinders
|
||||
EPSILON = 0.000001
|
||||
|
||||
class Shape:
|
||||
|
||||
class Shape:
|
||||
# Expects verts in MeshBuilder-ready format, as a n by 3 mdarray
|
||||
# with vertices stored in rows
|
||||
def __init__(self, verts, faces, index_base, name):
|
||||
|
|
@ -37,9 +37,10 @@ class Shape:
|
|||
self.index_base = index_base
|
||||
self.name = name
|
||||
|
||||
|
||||
class X3DReader(MeshReader):
|
||||
def __init__(self, application):
|
||||
super().__init__(application)
|
||||
def __init__(self) -> None:
|
||||
super().__init__()
|
||||
self._supported_extensions = [".x3d"]
|
||||
self._namespaces = {}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,5 +15,6 @@ def getMetaData():
|
|||
]
|
||||
}
|
||||
|
||||
|
||||
def register(app):
|
||||
return { "mesh_reader": X3DReader.X3DReader(app) }
|
||||
return {"mesh_reader": X3DReader.X3DReader()}
|
||||
|
|
|
|||
|
|
@ -6,8 +6,10 @@ import io
|
|||
import json #To parse the product-to-id mapping file.
|
||||
import os.path #To find the product-to-id mapping.
|
||||
import sys
|
||||
from typing import Any, Dict, List, Optional
|
||||
from typing import Any, Dict, List, Optional, cast
|
||||
import xml.etree.ElementTree as ET
|
||||
from typing import Dict
|
||||
from typing import Iterator
|
||||
|
||||
from UM.Resources import Resources
|
||||
from UM.Logger import Logger
|
||||
|
|
@ -132,7 +134,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
"version": self.CurrentFdmMaterialVersion})
|
||||
|
||||
## Begin Metadata Block
|
||||
builder.start("metadata")
|
||||
builder.start("metadata") # type: ignore
|
||||
|
||||
metadata = copy.deepcopy(self.getMetaData())
|
||||
# setting_version is derived from the "version" tag in the schema, so don't serialize it into a file
|
||||
|
|
@ -156,21 +158,21 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
metadata.pop("name", "")
|
||||
|
||||
## Begin Name Block
|
||||
builder.start("name")
|
||||
builder.start("name") # type: ignore
|
||||
|
||||
builder.start("brand")
|
||||
builder.start("brand") # type: ignore
|
||||
builder.data(metadata.pop("brand", ""))
|
||||
builder.end("brand")
|
||||
|
||||
builder.start("material")
|
||||
builder.start("material") # type: ignore
|
||||
builder.data(metadata.pop("material", ""))
|
||||
builder.end("material")
|
||||
|
||||
builder.start("color")
|
||||
builder.start("color") # type: ignore
|
||||
builder.data(metadata.pop("color_name", ""))
|
||||
builder.end("color")
|
||||
|
||||
builder.start("label")
|
||||
builder.start("label") # type: ignore
|
||||
builder.data(self.getName())
|
||||
builder.end("label")
|
||||
|
||||
|
|
@ -178,7 +180,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
## End Name Block
|
||||
|
||||
for key, value in metadata.items():
|
||||
builder.start(key)
|
||||
builder.start(key) # type: ignore
|
||||
if value is not None: #Nones get handled well by the builder.
|
||||
#Otherwise the builder always expects a string.
|
||||
#Deserialize expects the stringified version.
|
||||
|
|
@ -190,10 +192,10 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
## End Metadata Block
|
||||
|
||||
## Begin Properties Block
|
||||
builder.start("properties")
|
||||
builder.start("properties") # type: ignore
|
||||
|
||||
for key, value in properties.items():
|
||||
builder.start(key)
|
||||
builder.start(key) # type: ignore
|
||||
builder.data(value)
|
||||
builder.end(key)
|
||||
|
||||
|
|
@ -201,14 +203,14 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
## End Properties Block
|
||||
|
||||
## Begin Settings Block
|
||||
builder.start("settings")
|
||||
builder.start("settings") # type: ignore
|
||||
|
||||
if self.getMetaDataEntry("definition") == "fdmprinter":
|
||||
for instance in self.findInstances():
|
||||
self._addSettingElement(builder, instance)
|
||||
|
||||
machine_container_map = {}
|
||||
machine_variant_map = {}
|
||||
machine_container_map = {} # type: Dict[str, InstanceContainer]
|
||||
machine_variant_map = {} # type: Dict[str, Dict[str, Any]]
|
||||
|
||||
variant_manager = CuraApplication.getInstance().getVariantManager()
|
||||
|
||||
|
|
@ -248,7 +250,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
product = product_name
|
||||
break
|
||||
|
||||
builder.start("machine")
|
||||
builder.start("machine") # type: ignore
|
||||
builder.start("machine_identifier", {
|
||||
"manufacturer": container.getMetaDataEntry("machine_manufacturer",
|
||||
definition_metadata.get("manufacturer", "Unknown")),
|
||||
|
|
@ -264,7 +266,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
self._addSettingElement(builder, instance)
|
||||
|
||||
# Find all hotend sub-profiles corresponding to this material and machine and add them to this profile.
|
||||
buildplate_dict = {}
|
||||
buildplate_dict = {} # type: Dict[str, Any]
|
||||
for variant_name, variant_dict in machine_variant_map[definition_id].items():
|
||||
variant_type = variant_dict["variant_node"].metadata["hardware_type"]
|
||||
from cura.Machines.VariantManager import VariantType
|
||||
|
|
@ -839,11 +841,14 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
if label is not None and label.text is not None:
|
||||
base_metadata["name"] = label.text
|
||||
else:
|
||||
base_metadata["name"] = cls._profile_name(material.text, color.text)
|
||||
if material is not None and color is not None:
|
||||
base_metadata["name"] = cls._profile_name(material.text, color.text)
|
||||
else:
|
||||
base_metadata["name"] = "Unknown Material"
|
||||
|
||||
base_metadata["brand"] = brand.text if brand.text is not None else "Unknown Brand"
|
||||
base_metadata["material"] = material.text if material.text is not None else "Unknown Type"
|
||||
base_metadata["color_name"] = color.text if color.text is not None else "Unknown Color"
|
||||
base_metadata["brand"] = brand.text if brand is not None and brand.text is not None else "Unknown Brand"
|
||||
base_metadata["material"] = material.text if material is not None and material.text is not None else "Unknown Type"
|
||||
base_metadata["color_name"] = color.text if color is not None and color.text is not None else "Unknown Color"
|
||||
continue
|
||||
|
||||
#Setting_version is derived from the "version" tag in the schema earlier, so don't set it here.
|
||||
|
|
@ -863,13 +868,13 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
tag_name = _tag_without_namespace(entry)
|
||||
property_values[tag_name] = entry.text
|
||||
|
||||
base_metadata["approximate_diameter"] = str(round(float(property_values.get("diameter", 2.85)))) # In mm
|
||||
base_metadata["approximate_diameter"] = str(round(float(cast(float, property_values.get("diameter", 2.85))))) # In mm
|
||||
base_metadata["properties"] = property_values
|
||||
base_metadata["definition"] = "fdmprinter"
|
||||
|
||||
compatible_entries = data.iterfind("./um:settings/um:setting[@key='hardware compatible']", cls.__namespaces)
|
||||
try:
|
||||
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text)
|
||||
common_compatibility = cls._parseCompatibleValue(next(compatible_entries).text) # type: ignore
|
||||
except StopIteration: #No 'hardware compatible' setting.
|
||||
common_compatibility = True
|
||||
base_metadata["compatible"] = common_compatibility
|
||||
|
|
@ -883,7 +888,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
for entry in machine.iterfind("./um:setting", cls.__namespaces):
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
if entry.text is not None:
|
||||
machine_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
|
||||
for identifier in machine.iterfind("./um:machine_identifier", cls.__namespaces):
|
||||
machine_id_list = product_id_map.get(identifier.get("product"), [])
|
||||
|
|
@ -891,11 +897,11 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
machine_id_list = cls.getPossibleDefinitionIDsFromName(identifier.get("product"))
|
||||
|
||||
for machine_id in machine_id_list:
|
||||
definition_metadata = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
|
||||
if not definition_metadata:
|
||||
definition_metadatas = ContainerRegistry.getInstance().findDefinitionContainersMetadata(id = machine_id)
|
||||
if not definition_metadatas:
|
||||
continue
|
||||
|
||||
definition_metadata = definition_metadata[0]
|
||||
definition_metadata = definition_metadatas[0]
|
||||
|
||||
machine_manufacturer = identifier.get("manufacturer", definition_metadata.get("manufacturer", "Unknown")) #If the XML material doesn't specify a manufacturer, use the one in the actual printer definition.
|
||||
|
||||
|
|
@ -918,7 +924,7 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
result_metadata.append(new_material_metadata)
|
||||
|
||||
buildplates = machine.iterfind("./um:buildplate", cls.__namespaces)
|
||||
buildplate_map = {}
|
||||
buildplate_map = {} # type: Dict[str, Dict[str, bool]]
|
||||
buildplate_map["buildplate_compatible"] = {}
|
||||
buildplate_map["buildplate_recommended"] = {}
|
||||
for buildplate in buildplates:
|
||||
|
|
@ -939,10 +945,11 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
buildplate_recommended = True
|
||||
for entry in settings:
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
elif key == "hardware recommended":
|
||||
buildplate_recommended = cls._parseCompatibleValue(entry.text)
|
||||
if entry.text is not None:
|
||||
if key == "hardware compatible":
|
||||
buildplate_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
elif key == "hardware recommended":
|
||||
buildplate_recommended = cls._parseCompatibleValue(entry.text)
|
||||
|
||||
buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
|
||||
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
|
||||
|
|
@ -956,7 +963,8 @@ class XmlMaterialProfile(InstanceContainer):
|
|||
for entry in hotend.iterfind("./um:setting", cls.__namespaces):
|
||||
key = entry.get("key")
|
||||
if key == "hardware compatible":
|
||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
if entry.text is not None:
|
||||
hotend_compatibility = cls._parseCompatibleValue(entry.text)
|
||||
|
||||
new_hotend_specific_material_id = container_id + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue