Merge branch 'master' into fix_layer_number_width

This commit is contained in:
Diego Prado Gesto 2018-08-31 10:56:23 +02:00
commit a8cb1e25b4
294 changed files with 5983 additions and 2588 deletions

View file

@ -59,7 +59,7 @@ class ThreeMFReader(MeshReader):
if transformation == "":
return Matrix()
splitted_transformation = transformation.split()
split_transformation = transformation.split()
## Transformation is saved as:
## M00 M01 M02 0.0
## M10 M11 M12 0.0
@ -68,20 +68,20 @@ class ThreeMFReader(MeshReader):
## We switch the row & cols as that is how everyone else uses matrices!
temp_mat = Matrix()
# Rotation & Scale
temp_mat._data[0, 0] = splitted_transformation[0]
temp_mat._data[1, 0] = splitted_transformation[1]
temp_mat._data[2, 0] = splitted_transformation[2]
temp_mat._data[0, 1] = splitted_transformation[3]
temp_mat._data[1, 1] = splitted_transformation[4]
temp_mat._data[2, 1] = splitted_transformation[5]
temp_mat._data[0, 2] = splitted_transformation[6]
temp_mat._data[1, 2] = splitted_transformation[7]
temp_mat._data[2, 2] = splitted_transformation[8]
temp_mat._data[0, 0] = split_transformation[0]
temp_mat._data[1, 0] = split_transformation[1]
temp_mat._data[2, 0] = split_transformation[2]
temp_mat._data[0, 1] = split_transformation[3]
temp_mat._data[1, 1] = split_transformation[4]
temp_mat._data[2, 1] = split_transformation[5]
temp_mat._data[0, 2] = split_transformation[6]
temp_mat._data[1, 2] = split_transformation[7]
temp_mat._data[2, 2] = split_transformation[8]
# Translation
temp_mat._data[0, 3] = splitted_transformation[9]
temp_mat._data[1, 3] = splitted_transformation[10]
temp_mat._data[2, 3] = splitted_transformation[11]
temp_mat._data[0, 3] = split_transformation[9]
temp_mat._data[1, 3] = split_transformation[10]
temp_mat._data[2, 3] = split_transformation[11]
return temp_mat

View file

@ -24,6 +24,7 @@ from UM.MimeTypeDatabase import MimeTypeDatabase, MimeType
from UM.Job import Job
from UM.Preferences import Preferences
from cura.Machines.VariantType import VariantType
from cura.Settings.CuraStackBuilder import CuraStackBuilder
from cura.Settings.ExtruderStack import ExtruderStack
from cura.Settings.GlobalStack import GlobalStack
@ -84,14 +85,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
def __init__(self) -> None:
super().__init__()
MimeTypeDatabase.addMimeType(
MimeType(
name="application/x-curaproject+xml",
comment="Cura Project File",
suffixes=["curaproject.3mf"]
)
)
self._supported_extensions = [".3mf"]
self._dialog = WorkspaceDialog()
self._3mf_mesh_reader = None
@ -629,6 +622,11 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
type = "extruder_train")
extruder_stack_dict = {stack.getMetaDataEntry("position"): stack for stack in extruder_stacks}
# Make sure that those extruders have the global stack as the next stack or later some value evaluation
# will fail.
for stack in extruder_stacks:
stack.setNextStack(global_stack, connect_signals = False)
Logger.log("d", "Workspace loading is checking definitions...")
# Get all the definition files & check if they exist. If not, add them.
definition_container_files = [name for name in cura_file_names if name.endswith(self._definition_container_suffix)]
@ -720,8 +718,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
nodes = []
base_file_name = os.path.basename(file_name)
if base_file_name.endswith(".curaproject.3mf"):
base_file_name = base_file_name[:base_file_name.rfind(".curaproject.3mf")]
self.setWorkspaceName(base_file_name)
return nodes
@ -889,7 +885,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
parser = self._machine_info.variant_info.parser
variant_name = parser["general"]["name"]
from cura.Machines.VariantManager import VariantType
variant_type = VariantType.BUILD_PLATE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
@ -905,7 +900,6 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
parser = extruder_info.variant_info.parser
variant_name = parser["general"]["name"]
from cura.Machines.VariantManager import VariantType
variant_type = VariantType.NOZZLE
node = variant_manager.getVariantNode(global_stack.definition.getId(), variant_name, variant_type)
@ -929,12 +923,16 @@ class ThreeMFWorkspaceReader(WorkspaceReader):
root_material_id = extruder_info.root_material_id
root_material_id = self._old_new_materials.get(root_material_id, root_material_id)
build_plate_id = global_stack.variant.getId()
# get material diameter of this extruder
machine_material_diameter = extruder_stack.materialDiameter
material_node = material_manager.getMaterialNode(global_stack.definition.getId(),
extruder_stack.variant.getName(),
build_plate_id,
machine_material_diameter,
root_material_id)
if material_node is not None and material_node.getContainer() is not None:
extruder_stack.material = material_node.getContainer()

View file

@ -18,11 +18,7 @@ catalog = i18nCatalog("cura")
def getMetaData() -> Dict:
# Workaround for osx not supporting double file extensions correctly.
if Platform.isOSX():
workspace_extension = "3mf"
else:
workspace_extension = "curaproject.3mf"
workspace_extension = "3mf"
metaData = {}
if "3MFReader.ThreeMFReader" in sys.modules:

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for reading 3MF files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -25,6 +25,9 @@ except ImportError:
import zipfile
import UM.Application
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class ThreeMFWriter(MeshWriter):
def __init__(self):
@ -173,6 +176,7 @@ class ThreeMFWriter(MeshWriter):
archive.writestr(relations_file, b'<?xml version="1.0" encoding="UTF-8"?> \n' + ET.tostring(relations_element))
except Exception as e:
Logger.logException("e", "Error writing zip file")
self.setInformation(catalog.i18nc("@error:zip", "Error writing 3mf file."))
return False
finally:
if not self._store_archive:

View file

@ -15,11 +15,7 @@ from UM.Platform import Platform
i18n_catalog = i18nCatalog("uranium")
def getMetaData():
# Workarround for osx not supporting double file extensions correctly.
if Platform.isOSX():
workspace_extension = "3mf"
else:
workspace_extension = "curaproject.3mf"
workspace_extension = "3mf"
metaData = {}
@ -36,7 +32,7 @@ def getMetaData():
"output": [{
"extension": workspace_extension,
"description": i18n_catalog.i18nc("@item:inlistbox", "Cura Project 3MF file"),
"mime_type": "application/x-curaproject+xml",
"mime_type": "application/vnd.ms-package.3dmanufacturing-3dmodel+xml",
"mode": ThreeMFWorkspaceWriter.ThreeMFWorkspaceWriter.OutputMode.BinaryMode
}]
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for writing 3MF files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Shows changes since latest checked version.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -1,6 +1,7 @@
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
import argparse #To run the engine in debug mode if the front-end is in debug mode.
from collections import defaultdict
import os
from PyQt5.QtCore import QObject, QTimer, pyqtSlot
@ -179,7 +180,15 @@ class CuraEngineBackend(QObject, Backend):
# \return list of commands and args / parameters.
def getEngineCommand(self) -> List[str]:
json_path = Resources.getPath(Resources.DefinitionContainers, "fdmprinter.def.json")
return [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
command = [self._application.getPreferences().getValue("backend/location"), "connect", "127.0.0.1:{0}".format(self._port), "-j", json_path, ""]
parser = argparse.ArgumentParser(prog = "cura", add_help = False)
parser.add_argument("--debug", action = "store_true", default = False, help = "Turn on the debug mode by setting this option.")
known_args = vars(parser.parse_known_args()[0])
if known_args["debug"]:
command.append("-vvv")
return command
## Emitted when we get a message containing print duration and material amount.
# This also implies the slicing has finished.
@ -541,6 +550,9 @@ class CuraEngineBackend(QObject, Backend):
## Remove old layer data (if any)
def _clearLayerData(self, build_plate_numbers: Set = None) -> None:
# Clear out any old gcode
self._scene.gcode_dict = {} # type: ignore
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:

View file

@ -41,7 +41,7 @@ class StartJobResult(IntEnum):
## Formatter class that handles token expansion in start/end gcode
class GcodeStartEndFormatter(Formatter):
def get_value(self, key: str, *args: str, default_extruder_nr: str = "-1", **kwargs) -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
def get_value(self, key: str, args: str, kwargs: dict, default_extruder_nr: str = "-1") -> str: #type: ignore # [CodeStyle: get_value is an overridden function from the Formatter class]
# 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
@ -220,8 +220,10 @@ class StartSliceJob(Job):
stack = global_stack
skip_group = False
for node in group:
# Only check if the printing extruder is enabled for printing meshes
is_non_printing_mesh = node.callDecoration("evaluateIsNonPrintingMesh")
extruder_position = node.callDecoration("getActiveExtruderPosition")
if not extruders_enabled[extruder_position]:
if not is_non_printing_mesh and not extruders_enabled[extruder_position]:
skip_group = True
has_model_with_disabled_extruders = True
associated_disabled_extruders.add(extruder_position)

View file

@ -2,7 +2,7 @@
"name": "CuraEngine Backend",
"author": "Ultimaker B.V.",
"description": "Provides the link to the CuraEngine slicing backend.",
"api": 4,
"api": 5,
"version": "1.0.0",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing Cura profiles.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for exporting Cura profiles.",
"api": 4,
"api": 5,
"i18n-catalog":"cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Checks for firmware updates.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Reads g-code from a compressed archive.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -10,10 +10,17 @@ from UM.Mesh.MeshWriter import MeshWriter #The class we're extending/implementin
from UM.PluginRegistry import PluginRegistry
from UM.Scene.SceneNode import SceneNode #For typing.
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## A file writer that writes gzipped g-code.
#
# If you're zipping g-code, you might as well use gzip!
class GCodeGzWriter(MeshWriter):
def __init__(self) -> None:
super().__init__(add_to_recent_files = False)
## Writes the gzipped g-code to a stream.
#
# Note that even though the function accepts a collection of nodes, the
@ -28,12 +35,15 @@ class GCodeGzWriter(MeshWriter):
def write(self, stream: BufferedIOBase, nodes: List[SceneNode], mode = MeshWriter.OutputMode.BinaryMode) -> bool:
if mode != MeshWriter.OutputMode.BinaryMode:
Logger.log("e", "GCodeGzWriter does not support text mode.")
self.setInformation(catalog.i18nc("@error:not supported", "GCodeGzWriter does not support text mode."))
return False
#Get the g-code from the g-code writer.
gcode_textio = StringIO() #We have to convert the g-code into bytes.
success = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter")).write(gcode_textio, None)
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
success = gcode_writer.write(gcode_textio, None)
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
self.setInformation(gcode_writer.getInformation())
return False
result = gzip.compress(gcode_textio.getvalue().encode("utf-8"))

View file

@ -16,7 +16,8 @@ def getMetaData():
"extension": file_extension,
"description": catalog.i18nc("@item:inlistbox", "Compressed G-code File"),
"mime_type": "application/gzip",
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode
"mode": GCodeGzWriter.GCodeGzWriter.OutputMode.BinaryMode,
"hide_in_file_dialog": True,
}]
}
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Writes g-code to a compressed archive.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing profiles from g-code files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Victor Larchenko",
"version": "1.0.0",
"description": "Allows loading and displaying G-code files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -12,6 +12,8 @@ from UM.Settings.InstanceContainer import InstanceContainer
from cura.Machines.QualityManager import getMachineDefinitionIDForQualitySearch
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
## Writes g-code to a file.
#
@ -45,7 +47,7 @@ class GCodeWriter(MeshWriter):
_setting_keyword = ";SETTING_"
def __init__(self):
super().__init__()
super().__init__(add_to_recent_files = False)
self._application = Application.getInstance()
@ -62,11 +64,13 @@ class GCodeWriter(MeshWriter):
def write(self, stream, nodes, mode = MeshWriter.OutputMode.TextMode):
if mode != MeshWriter.OutputMode.TextMode:
Logger.log("e", "GCodeWriter does not support non-text mode.")
self.setInformation(catalog.i18nc("@error:not supported", "GCodeWriter does not support non-text mode."))
return False
active_build_plate = Application.getInstance().getMultiBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
if not hasattr(scene, "gcode_dict"):
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
return False
gcode_dict = getattr(scene, "gcode_dict")
gcode_list = gcode_dict.get(active_build_plate, None)
@ -82,6 +86,7 @@ class GCodeWriter(MeshWriter):
stream.write(settings)
return True
self.setInformation(catalog.i18nc("@warning:status", "Please generate G-code before saving."))
return False
## Create a new container with container 2 as base and container 1 written over it.

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Writes g-code to a file.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Enables ability to generate printable geometry from 2D image files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for importing profiles from legacy Cura versions.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "fieldOfView",
"version": "1.0.0",
"description": "Provides a way to change machine settings (such as build volume, nozzle size, etc.).",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -2,7 +2,7 @@
"name": "Model Checker",
"author": "Ultimaker B.V.",
"version": "0.1",
"api": 4,
"api": 5,
"description": "Checks models and print configuration for possible printing issues and give suggestions.",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a monitor stage in Cura.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides the Per Model Settings.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -1,5 +1,6 @@
# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
# Copyright (c) 2018 Jaime van Kessel, Ultimaker B.V.
# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal, pyqtSlot
from UM.PluginRegistry import PluginRegistry
@ -260,6 +261,9 @@ class PostProcessingPlugin(QObject, Extension):
# Create the plugin dialog component
path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
if self._view is None:
Logger.log("e", "Not creating PostProcessing button near save button because the QML component failed to be created.")
return
Logger.log("d", "Post processing view created.")
# Create the save button component
@ -269,6 +273,9 @@ class PostProcessingPlugin(QObject, Extension):
def showPopup(self):
if self._view is None:
self._createView()
if self._view is None:
Logger.log("e", "Not creating PostProcessing window since the QML component failed to be created.")
return
self._view.show()
## Property changed: trigger re-slice

View file

@ -2,7 +2,7 @@
"name": "Post Processing",
"author": "Ultimaker",
"version": "2.2",
"api": 4,
"api": 5,
"description": "Extension that allows for user created scripts for post processing",
"catalog": "cura"
}

View file

@ -58,10 +58,10 @@ class FilamentChange(Script):
color_change = "M600"
if initial_retract is not None and initial_retract > 0.:
color_change = color_change + (" E%.2f" % initial_retract)
color_change = color_change + (" E-%.2f" % initial_retract)
if later_retract is not None and later_retract > 0.:
color_change = color_change + (" L%.2f" % later_retract)
color_change = color_change + (" L-%.2f" % later_retract)
color_change = color_change + " ; Generated by FilamentChange plugin"

View file

@ -28,7 +28,7 @@ class PauseAtHeight(Script):
"pause_height":
{
"label": "Pause Height",
"description": "At what height should the pause occur",
"description": "At what height should the pause occur?",
"unit": "mm",
"type": "float",
"default_value": 5.0,
@ -39,7 +39,7 @@ class PauseAtHeight(Script):
"pause_layer":
{
"label": "Pause Layer",
"description": "At what layer should the pause occur",
"description": "At what layer should the pause occur?",
"type": "int",
"value": "math.floor((pause_height - 0.27) / 0.1) + 1",
"minimum_value": "0",
@ -142,13 +142,14 @@ class PauseAtHeight(Script):
standby_temperature = self.getSettingValueByKey("standby_temperature")
firmware_retract = Application.getInstance().getGlobalContainerStack().getProperty("machine_firmware_retract", "value")
control_temperatures = Application.getInstance().getGlobalContainerStack().getProperty("machine_nozzle_temp_enabled", "value")
initial_layer_height = Application.getInstance().getGlobalContainerStack().getProperty("layer_height_0", "value")
is_griffin = False
# T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
# use offset to calculate the current height: <current_height> = <current_z> - <layer_0_z>
layer_0_z = 0.
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.
@ -195,11 +196,10 @@ class PauseAtHeight(Script):
# This block is executed once, the first time there is a G
# command, to get the z offset (z for first positive layer)
if not got_first_g_cmd_on_layer_0:
layer_0_z = current_z
layer_0_z = current_z - initial_layer_height
got_first_g_cmd_on_layer_0 = True
current_height = current_z - layer_0_z
if current_height < pause_height:
break # Try the next layer.

View file

@ -35,25 +35,39 @@ class GCodeStep():
Class to store the current value of each G_Code parameter
for any G-Code step
"""
def __init__(self, step):
def __init__(self, step, in_relative_movement: bool = False):
self.step = step
self.step_x = 0
self.step_y = 0
self.step_z = 0
self.step_e = 0
self.step_f = 0
self.in_relative_movement = in_relative_movement
self.comment = ""
def readStep(self, line):
"""
Reads gcode from line into self
"""
self.step_x = _getValue(line, "X", self.step_x)
self.step_y = _getValue(line, "Y", self.step_y)
self.step_z = _getValue(line, "Z", self.step_z)
self.step_e = _getValue(line, "E", self.step_e)
self.step_f = _getValue(line, "F", self.step_f)
return
if not self.in_relative_movement:
self.step_x = _getValue(line, "X", self.step_x)
self.step_y = _getValue(line, "Y", self.step_y)
self.step_z = _getValue(line, "Z", self.step_z)
self.step_e = _getValue(line, "E", self.step_e)
self.step_f = _getValue(line, "F", self.step_f)
else:
delta_step_x = _getValue(line, "X", 0)
delta_step_y = _getValue(line, "Y", 0)
delta_step_z = _getValue(line, "Z", 0)
delta_step_e = _getValue(line, "E", 0)
self.step_x += delta_step_x
self.step_y += delta_step_y
self.step_z += delta_step_z
self.step_e += delta_step_e
self.step_f = _getValue(line, "F", self.step_f) # the feedrate is not relative
def copyPosFrom(self, step):
"""
@ -65,7 +79,9 @@ class GCodeStep():
self.step_e = step.step_e
self.step_f = step.step_f
self.comment = step.comment
return
def setInRelativeMovement(self, value: bool) -> None:
self.in_relative_movement = value
# Execution part of the stretch plugin
@ -86,6 +102,7 @@ class Stretcher():
# of already deposited material for current layer
self.layer_z = 0 # Z position of the extrusion moves of the current layer
self.layergcode = ""
self._in_relative_movement = False
def execute(self, data):
"""
@ -96,7 +113,8 @@ class Stretcher():
+ " and push wall stretch " + str(self.pw_stretch) + "mm")
retdata = []
layer_steps = []
current = GCodeStep(0)
in_relative_movement = False
current = GCodeStep(0, in_relative_movement)
self.layer_z = 0.
current_e = 0.
for layer in data:
@ -107,20 +125,31 @@ class Stretcher():
current.comment = line[line.find(";"):]
if _getValue(line, "G") == 0:
current.readStep(line)
onestep = GCodeStep(0)
onestep = GCodeStep(0, in_relative_movement)
onestep.copyPosFrom(current)
elif _getValue(line, "G") == 1:
current.readStep(line)
onestep = GCodeStep(1)
onestep = GCodeStep(1, in_relative_movement)
onestep.copyPosFrom(current)
# end of relative movement
elif _getValue(line, "G") == 90:
in_relative_movement = False
current.setInRelativeMovement(in_relative_movement)
# start of relative movement
elif _getValue(line, "G") == 91:
in_relative_movement = True
current.setInRelativeMovement(in_relative_movement)
elif _getValue(line, "G") == 92:
current.readStep(line)
onestep = GCodeStep(-1)
onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current)
else:
onestep = GCodeStep(-1)
onestep = GCodeStep(-1, in_relative_movement)
onestep.copyPosFrom(current)
onestep.comment = line
if line.find(";LAYER:") >= 0 and len(layer_steps):
# Previous plugin "forgot" to separate two layers...
Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a prepare stage in Cura.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"description": "Provides removable drive hotplugging and writing support.",
"version": "1.0.0",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -40,33 +40,37 @@ Item {
property bool layersVisible: true
function getUpperValueFromSliderHandle () {
function getUpperValueFromSliderHandle() {
return upperHandle.getValue()
}
function setUpperValue (value) {
function setUpperValue(value) {
upperHandle.setValue(value)
updateRangeHandle()
}
function getLowerValueFromSliderHandle () {
function getLowerValueFromSliderHandle() {
return lowerHandle.getValue()
}
function setLowerValue (value) {
function setLowerValue(value) {
lowerHandle.setValue(value)
updateRangeHandle()
}
function updateRangeHandle () {
function updateRangeHandle() {
rangeHandle.height = lowerHandle.y - (upperHandle.y + upperHandle.height)
}
// set the active handle to show only one label at a time
function setActiveHandle (handle) {
function setActiveHandle(handle) {
activeHandle = handle
}
function normalizeValue(value) {
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
Rectangle {
id: track
@ -188,6 +192,8 @@ Item {
// set the slider position based on the upper value
function setValue (value) {
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
UM.SimulationView.setCurrentLayer(value)
@ -274,6 +280,8 @@ Item {
// set the slider position based on the lower value
function setValue (value) {
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
UM.SimulationView.setMinimumLayer(value)

View file

@ -29,6 +29,7 @@ Item {
// value properties
property real maximumValue: 100
property real minimumValue: 0
property bool roundValues: true
property real handleValue: maximumValue
@ -47,6 +48,10 @@ Item {
rangeHandle.width = handle.x - sliderRoot.handleSize
}
function normalizeValue(value) {
return Math.min(Math.max(value, sliderRoot.minimumValue), sliderRoot.maximumValue)
}
// slider track
Rectangle {
id: track
@ -110,6 +115,8 @@ Item {
// set the slider position based on the value
function setValue (value) {
// Normalize values between range, since using arrow keys will create out-of-the-range values
value = sliderRoot.normalizeValue(value)
UM.SimulationView.setCurrentPath(value)

View file

@ -25,10 +25,6 @@ UM.PointingRectangle {
width: valueLabel.width + UM.Theme.getSize("default_margin").width
visible: false
// make sure the text field is focussed when pressing the parent handle
// needed to connect the key bindings when switching active handle
onVisibleChanged: if (visible) valueLabel.forceActiveFocus()
color: UM.Theme.getColor("tool_panel_background")
borderColor: UM.Theme.getColor("lining")
borderWidth: UM.Theme.getSize("default_lining").width

View file

@ -1,4 +1,4 @@
// Copyright (c) 2017 Ultimaker B.V.
// Copyright (c) 2018 Ultimaker B.V.
// Cura is released under the terms of the LGPLv3 or higher.
import QtQuick 2.4
@ -12,30 +12,43 @@ import Cura 1.0 as Cura
Item
{
id: base
width: {
if (UM.SimulationView.compatibilityMode) {
width:
{
if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility").width;
} else {
}
else
{
return UM.Theme.getSize("layerview_menu_size").width;
}
}
height: {
if (viewSettings.collapsed) {
if (UM.SimulationView.compatibilityMode) {
if (viewSettings.collapsed)
{
if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility_collapsed").height;
}
return UM.Theme.getSize("layerview_menu_size_collapsed").height;
} else if (UM.SimulationView.compatibilityMode) {
}
else if (UM.SimulationView.compatibilityMode)
{
return UM.Theme.getSize("layerview_menu_size_compatibility").height;
} else if (UM.Preferences.getValue("layerview/layer_view_type") == 0) {
}
else if (UM.Preferences.getValue("layerview/layer_view_type") == 0)
{
return UM.Theme.getSize("layerview_menu_size_material_color_mode").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
} else {
}
else
{
return UM.Theme.getSize("layerview_menu_size").height + UM.SimulationView.extruderCount * (UM.Theme.getSize("layerview_row").height + UM.Theme.getSize("layerview_row_spacing").height)
}
}
Behavior on height { NumberAnimation { duration: 100 } }
property var buttonTarget: {
property var buttonTarget:
{
if(parent != null)
{
var force_binding = parent.y; // ensure this gets reevaluated when the panel moves
@ -44,7 +57,8 @@ Item
return Qt.point(0,0)
}
Rectangle {
Rectangle
{
id: layerViewMenu
anchors.right: parent.right
anchors.top: parent.top
@ -83,7 +97,8 @@ Item
}
}
ColumnLayout {
ColumnLayout
{
id: viewSettings
property bool collapsed: false
@ -195,7 +210,8 @@ Item
width: width
}
Connections {
Connections
{
target: UM.Preferences
onPreferenceChanged:
{
@ -212,18 +228,22 @@ Item
}
}
Repeater {
Repeater
{
model: Cura.ExtrudersModel{}
CheckBox {
CheckBox
{
id: extrudersModelCheckBox
checked: viewSettings.extruder_opacities[index] > 0.5 || viewSettings.extruder_opacities[index] == undefined || viewSettings.extruder_opacities[index] == ""
onClicked: {
onClicked:
{
viewSettings.extruder_opacities[index] = checked ? 1.0 : 0.0
UM.Preferences.setValue("layerview/extruder_opacities", viewSettings.extruder_opacities.join("|"));
}
visible: !UM.SimulationView.compatibilityMode
enabled: index + 1 <= 4
Rectangle {
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: extrudersModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
@ -253,8 +273,10 @@ Item
}
}
Repeater {
model: ListModel {
Repeater
{
model: ListModel
{
id: typesLegendModel
Component.onCompleted:
{
@ -285,13 +307,16 @@ Item
}
}
CheckBox {
CheckBox
{
id: legendModelCheckBox
checked: model.initialValue
onClicked: {
onClicked:
{
UM.Preferences.setValue(model.preference, checked);
}
Rectangle {
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: legendModelCheckBox.right
width: UM.Theme.getSize("layerview_legend_size").width
@ -320,18 +345,22 @@ Item
}
}
CheckBox {
CheckBox
{
checked: viewSettings.only_show_top_layers
onClicked: {
onClicked:
{
UM.Preferences.setValue("view/only_show_top_layers", checked ? 1.0 : 0.0);
}
text: catalog.i18nc("@label", "Only Show Top Layers")
visible: UM.SimulationView.compatibilityMode
style: UM.Theme.styles.checkbox
}
CheckBox {
CheckBox
{
checked: viewSettings.top_layer_count == 5
onClicked: {
onClicked:
{
UM.Preferences.setValue("view/top_layer_count", checked ? 5 : 1);
}
text: catalog.i18nc("@label", "Show 5 Detailed Layers On Top")
@ -339,8 +368,10 @@ Item
style: UM.Theme.styles.checkbox
}
Repeater {
model: ListModel {
Repeater
{
model: ListModel
{
id: typesLegendModelNoCheck
Component.onCompleted:
{
@ -355,11 +386,13 @@ Item
}
}
Label {
Label
{
text: label
visible: viewSettings.show_legend
id: typesLegendModelLabel
Rectangle {
Rectangle
{
anchors.verticalCenter: parent.verticalCenter
anchors.right: typesLegendModelLabel.right
width: UM.Theme.getSize("layerview_legend_size").width
@ -378,30 +411,37 @@ Item
}
// Text for the minimum, maximum and units for the feedrates and layer thickness
Item {
Item
{
id: gradientLegend
visible: viewSettings.show_gradient
width: parent.width
height: UM.Theme.getSize("layerview_row").height
anchors {
anchors
{
topMargin: UM.Theme.getSize("slider_layerview_margin").height
horizontalCenter: parent.horizontalCenter
}
Label {
Label
{
text: minText()
anchors.left: parent.left
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function minText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
function minText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMinFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMinThickness()).toFixed(2)
}
}
@ -409,20 +449,25 @@ Item
}
}
Label {
Label
{
text: unitsText()
anchors.horizontalCenter: parent.horizontalCenter
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function unitsText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
function unitsText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return "mm/s"
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return "mm"
}
}
@ -430,20 +475,25 @@ Item
}
}
Label {
Label
{
text: maxText()
anchors.right: parent.right
color: UM.Theme.getColor("setting_control_text")
font: UM.Theme.getFont("default")
function maxText() {
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity) {
function maxText()
{
if (UM.SimulationView.layerActivity && CuraApplication.platformActivity)
{
// Feedrate selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 2) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 2)
{
return parseFloat(UM.SimulationView.getMaxFeedrate()).toFixed(2)
}
// Layer thickness selected
if (UM.Preferences.getValue("layerview/layer_view_type") == 3) {
if (UM.Preferences.getValue("layerview/layer_view_type") == 3)
{
return parseFloat(UM.SimulationView.getMaxThickness()).toFixed(2)
}
}
@ -453,7 +503,8 @@ Item
}
// Gradient colors for feedrate
Rectangle { // In QML 5.9 can be changed by LinearGradient
Rectangle
{ // In QML 5.9 can be changed by LinearGradient
// Invert values because then the bar is rotated 90 degrees
id: feedrateGradient
visible: viewSettings.show_feedrate_gradient
@ -463,20 +514,25 @@ Item
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
gradient: Gradient
{
GradientStop
{
position: 0.000
color: Qt.rgba(1, 0.5, 0, 1)
}
GradientStop {
GradientStop
{
position: 0.625
color: Qt.rgba(0.375, 0.5, 0, 1)
}
GradientStop {
GradientStop
{
position: 0.75
color: Qt.rgba(0.25, 1, 0, 1)
}
GradientStop {
GradientStop
{
position: 1.0
color: Qt.rgba(0, 0, 1, 1)
}
@ -484,7 +540,8 @@ Item
}
// Gradient colors for layer thickness (similar to parula colormap)
Rectangle { // In QML 5.9 can be changed by LinearGradient
Rectangle // In QML 5.9 can be changed by LinearGradient
{
// Invert values because then the bar is rotated 90 degrees
id: thicknessGradient
visible: viewSettings.show_thickness_gradient
@ -494,24 +551,30 @@ Item
border.width: UM.Theme.getSize("default_lining").width
border.color: UM.Theme.getColor("lining")
transform: Rotation {origin.x: 0; origin.y: 0; angle: 90}
gradient: Gradient {
GradientStop {
gradient: Gradient
{
GradientStop
{
position: 0.000
color: Qt.rgba(1, 1, 0, 1)
}
GradientStop {
GradientStop
{
position: 0.25
color: Qt.rgba(1, 0.75, 0.25, 1)
}
GradientStop {
GradientStop
{
position: 0.5
color: Qt.rgba(0, 0.75, 0.5, 1)
}
GradientStop {
GradientStop
{
position: 0.75
color: Qt.rgba(0, 0.375, 0.75, 1)
}
GradientStop {
GradientStop
{
position: 1.0
color: Qt.rgba(0, 0, 0.5, 1)
}
@ -520,19 +583,22 @@ Item
}
}
Item {
Item
{
id: slidersBox
width: parent.width
visible: UM.SimulationView.layerActivity && CuraApplication.platformActivity
anchors {
anchors
{
top: parent.bottom
topMargin: UM.Theme.getSize("slider_layerview_margin").height
left: parent.left
}
PathSlider {
PathSlider
{
id: pathSlider
height: UM.Theme.getSize("slider_handle").width
@ -553,25 +619,29 @@ Item
rangeColor: UM.Theme.getColor("slider_groove_fill")
// update values when layer data changes
Connections {
Connections
{
target: UM.SimulationView
onMaxPathsChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
onCurrentPathChanged: pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
Component.onCompleted:
{
pathSlider.setHandleValue(UM.SimulationView.currentPath)
}
}
LayerSlider {
LayerSlider
{
id: layerSlider
width: UM.Theme.getSize("slider_handle").width
height: UM.Theme.getSize("layerview_menu_size").height
anchors {
anchors
{
top: !UM.SimulationView.compatibilityMode ? pathSlider.bottom : parent.top
topMargin: !UM.SimulationView.compatibilityMode ? UM.Theme.getSize("default_margin").height : 0
right: parent.right
@ -593,7 +663,8 @@ Item
handleLabelWidth: UM.Theme.getSize("slider_layerview_background").width
// update values when layer data changes
Connections {
Connections
{
target: UM.SimulationView
onMaxLayersChanged: layerSlider.setUpperValue(UM.SimulationView.currentLayer)
onMinimumLayerChanged: layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
@ -601,45 +672,54 @@ Item
}
// make sure the slider handlers show the correct value after switching views
Component.onCompleted: {
Component.onCompleted:
{
layerSlider.setLowerValue(UM.SimulationView.minimumLayer)
layerSlider.setUpperValue(UM.SimulationView.currentLayer)
}
}
// Play simulation button
Button {
Button
{
id: playButton
iconSource: "./resources/simulation_resume.svg"
style: UM.Theme.styles.small_tool_button
visible: !UM.SimulationView.compatibilityMode
anchors {
anchors
{
verticalCenter: pathSlider.verticalCenter
}
property var status: 0 // indicates if it's stopped (0) or playing (1)
onClicked: {
switch(status) {
case 0: {
onClicked:
{
switch(status)
{
case 0:
{
resumeSimulation()
break
}
case 1: {
case 1:
{
pauseSimulation()
break
}
}
}
function pauseSimulation() {
function pauseSimulation()
{
UM.SimulationView.setSimulationRunning(false)
iconSource = "./resources/simulation_resume.svg"
simulationTimer.stop()
status = 0
}
function resumeSimulation() {
function resumeSimulation()
{
UM.SimulationView.setSimulationRunning(true)
iconSource = "./resources/simulation_pause.svg"
simulationTimer.start()
@ -652,7 +732,8 @@ Item
interval: 100
running: false
repeat: true
onTriggered: {
onTriggered:
{
var currentPath = UM.SimulationView.currentPath
var numPaths = UM.SimulationView.numPaths
var currentLayer = UM.SimulationView.currentLayer
@ -697,7 +778,8 @@ Item
}
}
FontMetrics {
FontMetrics
{
id: fontMetrics
font: UM.Theme.getFont("default")
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides the Simulation view.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Submits anonymous slice info. Can be disabled through preferences.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides a normal solid mesh view.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -25,10 +25,12 @@ from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
from UM.Settings.SettingInstance import SettingInstance
import numpy
class SupportEraser(Tool):
def __init__(self):
super().__init__()
self._shortcut_key = Qt.Key_G
self._shortcut_key = Qt.Key_E
self._controller = self.getController()
self._selection_pass = None
@ -96,8 +98,7 @@ class SupportEraser(Tool):
node.setName("Eraser")
node.setSelectable(True)
mesh = MeshBuilder()
mesh.addCube(10,10,10)
mesh = self._createCube(10)
node.setMeshData(mesh.build())
active_build_plate = CuraApplication.getInstance().getMultiBuildPlateModel().activeBuildPlate
@ -160,3 +161,28 @@ class SupportEraser(Tool):
self._skip_press = False
self._had_selection = has_selection
def _createCube(self, size):
mesh = MeshBuilder()
# Can't use MeshBuilder.addCube() because that does not get per-vertex normals
# Per-vertex normals require duplication of vertices
s = size / 2
verts = [ # 6 faces with 4 corners each
[-s, -s, s], [-s, s, s], [ s, s, s], [ s, -s, s],
[-s, s, -s], [-s, -s, -s], [ s, -s, -s], [ s, s, -s],
[ s, -s, -s], [-s, -s, -s], [-s, -s, s], [ s, -s, s],
[-s, s, -s], [ s, s, -s], [ s, s, s], [-s, s, s],
[-s, -s, s], [-s, -s, -s], [-s, s, -s], [-s, s, s],
[ s, -s, -s], [ s, -s, s], [ s, s, s], [ s, s, -s]
]
mesh.setVertices(numpy.asarray(verts, dtype=numpy.float32))
indices = []
for i in range(0, 24, 4): # All 6 quads (12 triangles)
indices.append([i, i+2, i+1])
indices.append([i, i+3, i+2])
mesh.setIndices(numpy.asarray(indices, dtype=numpy.int32))
mesh.calculateNormals()
return mesh

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Creates an eraser mesh to block the printing of support in certain places",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -2,6 +2,6 @@
"name": "Toolbox",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 4,
"api": 5,
"description": "Find, manage and install new Cura packages."
}

View file

@ -29,6 +29,16 @@ Item
anchors.topMargin: UM.Theme.getSize("default_margin").height
width: parent.width
frameVisible: false
// Workaround for scroll issues (QTBUG-49652)
flickableItem.interactive: false
Component.onCompleted:
{
for (var i = 0; i < flickableItem.children.length; ++i)
{
flickableItem.children[i].enabled = false
}
}
selectionMode: 0
model: packageData.supported_configs
headerDelegate: Rectangle

View file

@ -114,7 +114,10 @@ Item
else
{
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_id", model.id)
toolbox.setFilters("packages", {
"author_id": model.id,
"type": "material"
})
}
break
default:

View file

@ -105,8 +105,21 @@ Rectangle
switch(toolbox.viewCategory)
{
case "material":
toolbox.viewPage = "author"
toolbox.filterModelByProp("packages", "author_name", model.name)
// If model has a type, it must be a package
if (model.type !== undefined)
{
toolbox.viewPage = "detail"
toolbox.filterModelByProp("packages", "id", model.id)
}
else
{
toolbox.viewPage = "author"
toolbox.setFilters("packages", {
"author_id": model.id,
"type": "material"
})
}
break
default:
toolbox.viewPage = "detail"

View file

@ -28,7 +28,7 @@ class PackagesModel(ListModel):
self.addRoleName(Qt.UserRole + 11, "download_url")
self.addRoleName(Qt.UserRole + 12, "last_updated")
self.addRoleName(Qt.UserRole + 13, "is_bundled")
self.addRoleName(Qt.UserRole + 14, "is_enabled")
self.addRoleName(Qt.UserRole + 14, "is_active")
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")
@ -75,7 +75,7 @@ class PackagesModel(ListModel):
"download_url": package["download_url"] if "download_url" in package else None,
"last_updated": package["last_updated"] if "last_updated" in package else None,
"is_bundled": package["is_bundled"] if "is_bundled" in package else False,
"is_enabled": package["is_enabled"] if "is_enabled" in package else False,
"is_active": package["is_active"] if "is_active" in package else False,
"is_installed": package["is_installed"] if "is_installed" in package else False,
"has_configs": has_configs,
"supported_configs": configs_model,

View file

@ -776,17 +776,25 @@ class Toolbox(QObject, Extension):
# Filter Models:
# --------------------------------------------------------------------------
@pyqtSlot(str, str, str)
def filterModelByProp(self, modelType: str, filterType: str, parameter: str):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", modelType)
def filterModelByProp(self, model_type: str, filter_type: str, parameter: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[modelType].setFilter({ filterType: parameter })
self._models[model_type].setFilter({filter_type: parameter})
self.filterChanged.emit()
@pyqtSlot()
def removeFilters(self, modelType: str):
if not self._models[modelType]:
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", modelType)
@pyqtSlot(str, "QVariantMap")
def setFilters(self, model_type: str, filter_dict: dict) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't filter %s model because it doesn't exist.", model_type)
return
self._models[modelType].setFilter({})
self._models[model_type].setFilter(filter_dict)
self.filterChanged.emit()
@pyqtSlot(str)
def removeFilters(self, model_type: str) -> None:
if not self._models[model_type]:
Logger.log("w", "Toolbox: Couldn't remove filters on %s model because it doesn't exist.", model_type)
return
self._models[model_type].setFilter({})
self.filterChanged.emit()

View file

@ -1,5 +1,6 @@
#Copyright (c) 2018 Ultimaker B.V.
#Cura is released under the terms of the LGPLv3 or higher.
from typing import cast
from Charon.VirtualFile import VirtualFile #To open UFP files.
from Charon.OpenMode import OpenMode #To indicate that we want to write to UFP files.
@ -12,11 +13,15 @@ from UM.PluginRegistry import PluginRegistry #To get the g-code writer.
from PyQt5.QtCore import QBuffer
from cura.Snapshot import Snapshot
from cura.Utils.Threading import call_on_qt_thread
from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")
class UFPWriter(MeshWriter):
def __init__(self):
super().__init__()
super().__init__(add_to_recent_files = False)
self._snapshot = None
Application.getInstance().getOutputDeviceManager().writeStarted.connect(self._createSnapshot)
@ -25,6 +30,11 @@ class UFPWriter(MeshWriter):
Logger.log("d", "Creating thumbnail image...")
self._snapshot = Snapshot.snapshot(width = 300, height = 300)
# This needs to be called on the main thread (Qt thread) because the serialization of material containers can
# trigger loading other containers. Because those loaded containers are QtObjects, they must be created on the
# Qt thread. The File read/write operations right now are executed on separated threads because they are scheduled
# by the Job class.
@call_on_qt_thread
def write(self, stream, nodes, mode = MeshWriter.OutputMode.BinaryMode):
archive = VirtualFile()
archive.openStream(stream, "application/x-ufp", OpenMode.WriteOnly)
@ -32,7 +42,11 @@ class UFPWriter(MeshWriter):
#Store the g-code from the scene.
archive.addContentType(extension = "gcode", mime_type = "text/x-gcode")
gcode_textio = StringIO() #We have to convert the g-code into bytes.
PluginRegistry.getInstance().getPluginObject("GCodeWriter").write(gcode_textio, None)
gcode_writer = cast(MeshWriter, PluginRegistry.getInstance().getPluginObject("GCodeWriter"))
success = gcode_writer.write(gcode_textio, None)
if not success: #Writing the g-code failed. Then I can also not write the gzipped g-code.
self.setInformation(gcode_writer.getInformation())
return False
gcode = archive.getStream("/3D/model.gcode")
gcode.write(gcode_textio.getvalue().encode("UTF-8"))
archive.addRelation(virtual_path = "/3D/model.gcode", relation_type = "http://schemas.ultimaker.org/package/2018/relationships/gcode")
@ -52,5 +66,50 @@ class UFPWriter(MeshWriter):
else:
Logger.log("d", "Thumbnail not created, cannot save it")
# Store the material.
application = Application.getInstance()
machine_manager = application.getMachineManager()
material_manager = application.getMaterialManager()
global_stack = machine_manager.activeMachine
material_extension = "xml.fdm_material"
material_mime_type = "application/x-ultimaker-material-profile"
try:
archive.addContentType(extension = material_extension, mime_type = material_mime_type)
except:
Logger.log("w", "The material extension: %s was already added", material_extension)
added_materials = []
for extruder_stack in global_stack.extruders.values():
material = extruder_stack.material
material_file_name = material.getMetaData()["base_file"] + ".xml.fdm_material"
material_file_name = "/Materials/" + material_file_name
#Same material cannot be added
if material_file_name in added_materials:
continue
material_root_id = material.getMetaDataEntry("base_file")
material_group = material_manager.getMaterialGroup(material_root_id)
if material_group is None:
Logger.log("e", "Cannot find material container with root id [%s]", material_root_id)
return False
material_container = material_group.root_material_node.getContainer()
try:
serialized_material = material_container.serialize()
except NotImplementedError:
Logger.log("e", "Unable serialize material container with root id: %s", material_root_id)
return False
material_file = archive.getStream(material_file_name)
material_file.write(serialized_material.encode("UTF-8"))
archive.addRelation(virtual_path = material_file_name,
relation_type = "http://schemas.ultimaker.org/package/2018/relationships/material",
origin = "/3D/model.gcode")
added_materials.append(material_file_name)
archive.close()
return True

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides support for writing Ultimaker Format Packages.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -30,7 +30,12 @@ Component
anchors.horizontalCenter: parent.horizontalCenter
anchors.topMargin: UM.Theme.getSize("default_margin").height
anchors.top: parent.top
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
anchors.right:parent.right
anchors.rightMargin: UM.Theme.getSize("default_margin").width
text: Cura.MachineManager.printerOutputDevices[0].name
elide: Text.ElideRight
}
Rectangle

View file

@ -9,6 +9,7 @@ Component
{
Rectangle
{
id: monitorFrame
width: maximumWidth
height: maximumHeight
color: UM.Theme.getColor("viewport_background")
@ -103,5 +104,15 @@ Component
visible: OutputDevice.activePrinter != null
anchors.fill:parent
}
onVisibleChanged:
{
if (!monitorFrame.visible)
{
// After switching the Tab ensure that active printer is Null, the video stream image
// might be active
OutputDevice.setActivePrinter(null)
}
}
}
}

View file

@ -113,7 +113,7 @@ class ClusterUM3OutputDevice(NetworkedPrinterOutputDevice):
machine_file_formats = global_stack.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"):
if "application/x-ufp" not in machine_file_formats and Version(self.firmwareVersion) >= Version("4.4"):
machine_file_formats = ["application/x-ufp"] + machine_file_formats
# Take the intersection between file_formats and machine_file_formats.
@ -590,4 +590,4 @@ def findByKey(list: List[Union[PrintJobOutputModel, PrinterOutputModel]], key: s
for item in list:
if item.key == key:
return item
return None
return None

View file

@ -19,5 +19,5 @@ class ClusterUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"action\": \"%s\"}" % state
self._output_device.put("print_jobs/%s/action" % job.key, data, onFinished=None)
self._output_device.put("print_jobs/%s/action" % job.key, data, on_finished=None)

View file

@ -299,11 +299,11 @@ Cura.MachineAction
}
else if (base.selectedDevice.clusterSize === 0)
{
return catalog.i18nc("@label", "This printer is not set up to host a group of Ultimaker 3 printers.");
return catalog.i18nc("@label", "This printer is not set up to host a group of printers.");
}
else
{
return catalog.i18nc("@label", "This printer is the host for a group of %1 Ultimaker 3 printers.".arg(base.selectedDevice.clusterSize));
return catalog.i18nc("@label", "This printer is the host for a group of %1 printers.".arg(base.selectedDevice.clusterSize));
}
}
@ -349,11 +349,6 @@ Cura.MachineAction
addressField.focus = true;
}
onAccepted:
{
manager.setManualDevice(printerKey, addressText)
}
Column {
anchors.fill: parent
spacing: UM.Theme.getSize("default_margin").height
@ -369,7 +364,6 @@ Cura.MachineAction
{
id: addressField
width: parent.width
maximumLength: 40
validator: RegExpValidator
{
regExp: /[a-zA-Z0-9\.\-\_]*/
@ -393,7 +387,7 @@ Cura.MachineAction
text: catalog.i18nc("@action:button", "OK")
onClicked:
{
manualPrinterDialog.accept()
manager.setManualDevice(manualPrinterDialog.printerKey, manualPrinterDialog.addressText)
manualPrinterDialog.hide()
}
enabled: manualPrinterDialog.addressText.trim() != ""

View file

@ -165,7 +165,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
file_name = "none.xml"
self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), onFinished=None)
self.postForm("materials", "form-data; name=\"file\";filename=\"%s\"" % file_name, xml_data.encode(), on_finished=None)
except NotImplementedError:
# If the material container is not the most "generic" one it can't be serialized an will raise a
@ -270,7 +270,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
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)
on_finished=self._onPostPrintJobFinished)
return
@ -381,8 +381,8 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self._checkAuthentication()
# We don't need authentication for requesting info, so we can go right ahead with requesting this.
self.get("printer", onFinished=self._onGetPrinterDataFinished)
self.get("print_job", onFinished=self._onGetPrintJobFinished)
self.get("printer", on_finished=self._onGetPrinterDataFinished)
self.get("print_job", on_finished=self._onGetPrintJobFinished)
def _resetAuthenticationRequestedMessage(self):
if self._authentication_requested_message:
@ -404,7 +404,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _verifyAuthentication(self):
Logger.log("d", "Attempting to verify authentication")
# This will ensure that the "_onAuthenticationRequired" is triggered, which will setup the authenticator.
self.get("auth/verify", onFinished=self._onVerifyAuthenticationCompleted)
self.get("auth/verify", on_finished=self._onVerifyAuthenticationCompleted)
def _onVerifyAuthenticationCompleted(self, reply):
status_code = reply.attribute(QNetworkRequest.HttpStatusCodeAttribute)
@ -426,7 +426,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
def _checkAuthentication(self):
Logger.log("d", "Checking if authentication is correct for id %s and key %s", self._authentication_id, self._getSafeAuthKey())
self.get("auth/check/" + str(self._authentication_id), onFinished=self._onCheckAuthenticationFinished)
self.get("auth/check/" + str(self._authentication_id), on_finished=self._onCheckAuthenticationFinished)
def _onCheckAuthenticationFinished(self, reply):
if str(self._authentication_id) not in reply.url().toString():
@ -502,7 +502,7 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self.post("auth/request",
json.dumps({"application": "Cura-" + CuraApplication.getInstance().getVersion(),
"user": self._getUserName()}).encode(),
onFinished=self._onRequestAuthenticationFinished)
on_finished=self._onRequestAuthenticationFinished)
self.setAuthenticationState(AuthState.AuthenticationRequested)

View file

@ -31,11 +31,11 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
def setJobState(self, job: "PrintJobOutputModel", state: str):
data = "{\"target\": \"%s\"}" % state
self._output_device.put("print_job/state", data, onFinished=None)
self._output_device.put("print_job/state", data, on_finished=None)
def setTargetBedTemperature(self, printer: "PrinterOutputModel", temperature: int):
data = str(temperature)
self._output_device.put("printer/bed/temperature/target", data, onFinished=self._onPutBedTemperatureCompleted)
self._output_device.put("printer/bed/temperature/target", data, on_finished=self._onPutBedTemperatureCompleted)
def _onPutBedTemperatureCompleted(self, reply):
if Version(self._preheat_printer.firmwareVersion) < Version("3.5.92"):
@ -51,10 +51,10 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
new_y = head_pos.y + y
new_z = head_pos.z + z
data = "{\n\"x\":%s,\n\"y\":%s,\n\"z\":%s\n}" %(new_x, new_y, new_z)
self._output_device.put("printer/heads/0/position", data, onFinished=None)
self._output_device.put("printer/heads/0/position", data, on_finished=None)
def homeBed(self, printer):
self._output_device.put("printer/heads/0/position/z", "0", onFinished=None)
self._output_device.put("printer/heads/0/position/z", "0", on_finished=None)
def _onPreheatBedTimerFinished(self):
self.setTargetBedTemperature(self._preheat_printer, 0)
@ -89,7 +89,7 @@ class LegacyUM3PrinterOutputController(PrinterOutputController):
printer.updateIsPreheating(True)
return
self._output_device.put("printer/bed/pre_heat", data, onFinished = self._onPutPreheatBedCompleted)
self._output_device.put("printer/bed/pre_heat", data, on_finished = self._onPutPreheatBedCompleted)
printer.updateIsPreheating(True)
self._preheat_request_in_progress = True

View file

@ -26,6 +26,10 @@ UM.Dialog
{
resetPrintersModel()
}
else
{
OutputDevice.cancelPrintSelection()
}
}
title: catalog.i18nc("@title:window", "Print over network")

View file

@ -16,9 +16,9 @@ Item
MouseArea
{
anchors.fill: parent
onClicked: OutputDevice.setActivePrinter(null)
z: 0
anchors.fill: parent
onClicked: OutputDevice.setActivePrinter(null)
z: 0
}
Button
@ -28,7 +28,7 @@ Item
anchors.bottomMargin: UM.Theme.getSize("default_margin").width
anchors.right: cameraImage.right
// TODO: Harcoded sizes
// TODO: Hardcoded sizes
width: 20 * screenScaleFactor
height: 20 * screenScaleFactor
@ -89,9 +89,11 @@ Item
MouseArea
{
anchors.fill: cameraImage
onClicked: { /* no-op */ }
z: 1
anchors.fill: cameraImage
onClicked:
{
OutputDevice.setActivePrinter(null)
}
z: 1
}
}

View file

@ -39,12 +39,12 @@ class SendMaterialJob(Job):
try:
remote_materials_list = json.loads(remote_materials_list)
except json.JSONDecodeError:
Logger.log("e", "Current material storage on printer was a corrupted reply.")
Logger.log("e", "Request material storage on printer: I didn't understand the printer's answer.")
return
try:
remote_materials_by_guid = {material["guid"]: material for material in remote_materials_list} #Index by GUID.
except KeyError:
Logger.log("e", "Current material storage on printer was an invalid reply (missing GUIDs).")
Logger.log("e", "Request material storage on printer: Printer's answer was missing GUIDs.")
return
container_registry = ContainerRegistry.getInstance()

View file

@ -198,7 +198,7 @@ class UM3OutputDevicePlugin(OutputDevicePlugin):
has_cluster_capable_firmware = Version(system_info["firmware"]) > self._min_cluster_version
instance_name = "manual:%s" % address
properties = {
b"name": system_info["name"].encode("utf-8"),
b"name": (system_info["name"] + " (manual)").encode("utf-8"),
b"address": address.encode("utf-8"),
b"firmware_version": system_info["firmware"].encode("utf-8"),
b"manual": b"true",

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"description": "Manages network connections to Ultimaker 3 printers.",
"version": "1.0.0",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -1,4 +1,4 @@
# Copyright (c) 2017 Ultimaker B.V.
# Copyright (c) 2018 Ultimaker B.V.
# Cura is released under the terms of the LGPLv3 or higher.
from UM.Job import Job
@ -21,7 +21,6 @@ class AutoDetectBaudJob(Job):
def run(self):
Logger.log("d", "Auto detect baud rate started.")
timeout = 3
wait_response_timeouts = [3, 15, 30]
wait_bootloader_times = [1.5, 5, 15]
write_timeout = 3
@ -52,7 +51,7 @@ class AutoDetectBaudJob(Job):
if serial is None:
try:
serial = Serial(str(self._serial_port), baud_rate, timeout = read_timeout, writeTimeout = write_timeout)
except SerialException as e:
except SerialException:
Logger.logException("w", "Unable to create serial")
continue
else:
@ -72,7 +71,7 @@ class AutoDetectBaudJob(Job):
while timeout_time > time():
line = serial.readline()
if b"ok T:" in line:
if b"ok " in line and b"T:" in line:
successful_responses += 1
if successful_responses >= 3:
self.setResult(baud_rate)

View file

@ -15,7 +15,7 @@ from cura.PrinterOutput.GenericOutputController import GenericOutputController
from .AutoDetectBaudJob import AutoDetectBaudJob
from .avr_isp import stk500v2, intelHex
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty
from PyQt5.QtCore import pyqtSlot, pyqtSignal, pyqtProperty, QUrl
from serial import Serial, SerialException, SerialTimeoutException
from threading import Thread, Event
@ -146,8 +146,11 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
@pyqtSlot(str)
def updateFirmware(self, file):
# the file path is qurl encoded.
self._firmware_location = file.replace("file://", "")
# the file path could be url-encoded.
if file.startswith("file://"):
self._firmware_location = QUrl(file).toLocalFile()
else:
self._firmware_location = file
self.showFirmwareInterface()
self.setFirmwareUpdateState(FirmwareUpdateState.updating)
self._update_firmware_thread.start()
@ -323,7 +326,7 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
if self._firmware_name is None:
self.sendCommand("M115")
if b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
if (b"ok " in line and b"T:" in line) or b"ok T:" in line or line.startswith(b"T:") or b"ok B:" in line or line.startswith(b"B:"): # Temperature message. 'T:' for extruder and 'B:' for bed
extruder_temperature_matches = re.findall(b"T(\d*): ?([\d\.]+) ?\/?([\d\.]+)?", line)
# Update all temperature values
matched_extruder_nrs = []

View file

@ -2,7 +2,7 @@
"name": "USB printing",
"author": "Ultimaker B.V.",
"version": "1.0.0",
"api": 4,
"api": 5,
"description": "Accepts G-Code and sends them to a printer. Plugin can also update firmware.",
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides machine actions for Ultimaker machines (such as bed leveling wizard, selecting upgrades, etc.).",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Ask the user once if he/she agrees with our license.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.1 to Cura 2.2.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.2 to Cura 2.4.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.5 to Cura 2.6.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.6 to Cura 2.7.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 2.7 to Cura 3.0.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.0 to Cura 3.1.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.2 to Cura 3.3.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.3 to Cura 3.4.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -8,6 +8,9 @@ from UM.VersionUpgrade import VersionUpgrade
deleted_settings = {"prime_tower_wall_thickness", "dual_pre_wipe", "prime_tower_purge_volume"}
changed_settings = {'retraction_combing': 'noskin'}
updated_settings = {'retraction_combing': 'infill'}
_RENAMED_MATERIAL_PROFILES = {
"dsm_arnitel2045_175_cartesio_0.25_mm": "dsm_arnitel2045_175_cartesio_0.25mm_thermoplastic_extruder",
"dsm_arnitel2045_175_cartesio_0.4_mm": "dsm_arnitel2045_175_cartesio_0.4mm_thermoplastic_extruder",
@ -127,6 +130,13 @@ class VersionUpgrade34to40(VersionUpgrade):
continue
del parser["values"][deleted_setting]
for setting_key in changed_settings:
if setting_key not in parser["values"]:
continue
if parser["values"][setting_key] == changed_settings[setting_key]:
parser["values"][setting_key] = updated_settings[setting_key]
result = io.StringIO()
parser.write(result)
return [filename], [result.getvalue()]

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Upgrades configurations from Cura 3.4 to Cura 4.0.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Seva Alekseyev",
"version": "0.5.0",
"description": "Provides support for reading X3D files.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides the X-Ray view.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}

View file

@ -6,21 +6,22 @@ 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, cast
from typing import Any, Dict, List, Optional, Tuple, 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
from cura.CuraApplication import CuraApplication
import UM.Dictionary
from UM.Settings.InstanceContainer import InstanceContainer
from UM.Settings.ContainerRegistry import ContainerRegistry
from UM.ConfigurationErrorMessage import ConfigurationErrorMessage
from cura.CuraApplication import CuraApplication
from cura.Machines.VariantType import VariantType
from .XmlMaterialValidator import XmlMaterialValidator
## Handles serializing and deserializing material containers from an XML file
class XmlMaterialProfile(InstanceContainer):
CurrentFdmMaterialVersion = "1.3"
@ -269,7 +270,6 @@ class XmlMaterialProfile(InstanceContainer):
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
variant_type = VariantType(variant_type)
if variant_type == VariantType.NOZZLE:
# The hotend identifier is not the containers name, but its "name".
@ -693,74 +693,38 @@ class XmlMaterialProfile(InstanceContainer):
if buildplate_id is None:
continue
from cura.Machines.VariantManager import VariantType
variant_manager = CuraApplication.getInstance().getVariantManager()
variant_node = variant_manager.getVariantNode(machine_id, buildplate_id,
variant_type = VariantType.BUILD_PLATE)
if not variant_node:
continue
buildplate_compatibility = machine_compatibility
buildplate_recommended = machine_compatibility
settings = buildplate.iterfind("./um:setting", self.__namespaces)
for entry in settings:
key = entry.get("key")
if key in self.__unmapped_settings:
if key == "hardware compatible":
buildplate_compatibility = self._parseCompatibleValue(entry.text)
elif key == "hardware recommended":
buildplate_recommended = self._parseCompatibleValue(entry.text)
else:
Logger.log("d", "Unsupported material setting %s", key)
_, buildplate_unmapped_settings_dict = self._getSettingsDictForNode(buildplate)
buildplate_compatibility = buildplate_unmapped_settings_dict.get("hardware compatible",
machine_compatibility)
buildplate_recommended = buildplate_unmapped_settings_dict.get("hardware recommended",
machine_compatibility)
buildplate_map["buildplate_compatible"][buildplate_id] = buildplate_compatibility
buildplate_map["buildplate_recommended"][buildplate_id] = buildplate_recommended
hotends = machine.iterfind("./um:hotend", self.__namespaces)
for hotend in hotends:
# The "id" field for hotends in material profiles are actually
# The "id" field for hotends in material profiles is actually name
hotend_name = hotend.get("id")
if hotend_name is None:
continue
variant_manager = CuraApplication.getInstance().getVariantManager()
variant_node = variant_manager.getVariantNode(machine_id, hotend_name)
variant_node = variant_manager.getVariantNode(machine_id, hotend_name, VariantType.NOZZLE)
if not variant_node:
continue
hotend_compatibility = machine_compatibility
hotend_setting_values = {}
settings = hotend.iterfind("./um:setting", self.__namespaces)
for entry in settings:
key = entry.get("key")
if key in self.__material_settings_setting_map:
if key == "processing temperature graph": #This setting has no setting text but subtags.
graph_nodes = entry.iterfind("./um:point", self.__namespaces)
graph_points = []
for graph_node in graph_nodes:
flow = float(graph_node.get("flow"))
temperature = float(graph_node.get("temperature"))
graph_points.append([flow, temperature])
hotend_setting_values[self.__material_settings_setting_map[key]] = str(graph_points)
else:
hotend_setting_values[self.__material_settings_setting_map[key]] = entry.text
elif key in self.__unmapped_settings:
if key == "hardware compatible":
hotend_compatibility = self._parseCompatibleValue(entry.text)
else:
Logger.log("d", "Unsupported material setting %s", key)
# Add namespaced Cura-specific settings
settings = hotend.iterfind("./cura:setting", self.__namespaces)
for entry in settings:
value = entry.text
if value.lower() == "yes":
value = True
elif value.lower() == "no":
value = False
key = entry.get("key")
hotend_setting_values[key] = value
hotend_mapped_settings, hotend_unmapped_settings = self._getSettingsDictForNode(hotend)
hotend_compatibility = hotend_unmapped_settings.get("hardware compatible", machine_compatibility)
# Generate container ID for the hotend-specific material container
new_hotend_specific_material_id = self.getId() + "_" + machine_id + "_" + hotend_name.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent material
@ -785,7 +749,7 @@ class XmlMaterialProfile(InstanceContainer):
new_hotend_material.getMetaData()["buildplate_recommended"] = buildplate_map["buildplate_recommended"]
cached_hotend_setting_properties = cached_machine_setting_properties.copy()
cached_hotend_setting_properties.update(hotend_setting_values)
cached_hotend_setting_properties.update(hotend_mapped_settings)
new_hotend_material.setCachedValues(cached_hotend_setting_properties)
@ -794,6 +758,61 @@ class XmlMaterialProfile(InstanceContainer):
if is_new_material:
containers_to_add.append(new_hotend_material)
#
# Build plates in hotend
#
buildplates = hotend.iterfind("./um:buildplate", self.__namespaces)
for buildplate in buildplates:
# The "id" field for buildplate in material profiles is actually name
buildplate_name = buildplate.get("id")
if buildplate_name is None:
continue
variant_manager = CuraApplication.getInstance().getVariantManager()
variant_node = variant_manager.getVariantNode(machine_id, buildplate_name, VariantType.BUILD_PLATE)
if not variant_node:
continue
buildplate_mapped_settings, buildplate_unmapped_settings = self._getSettingsDictForNode(buildplate)
buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible",
buildplate_map["buildplate_compatible"])
buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended",
buildplate_map["buildplate_recommended"])
# Generate container ID for the hotend-and-buildplate-specific material container
new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace(" ", "_")
# Same as machine compatibility, keep the derived material containers consistent with the parent material
if ContainerRegistry.getInstance().isLoaded(new_hotend_and_buildplate_specific_material_id):
new_hotend_and_buildplate_material = ContainerRegistry.getInstance().findContainers(id = new_hotend_and_buildplate_specific_material_id)[0]
is_new_material = False
else:
new_hotend_and_buildplate_material = XmlMaterialProfile(new_hotend_and_buildplate_specific_material_id)
is_new_material = True
new_hotend_and_buildplate_material.setMetaData(copy.deepcopy(new_hotend_material.getMetaData()))
new_hotend_and_buildplate_material.getMetaData()["id"] = new_hotend_and_buildplate_specific_material_id
new_hotend_and_buildplate_material.getMetaData()["name"] = self.getName()
new_hotend_and_buildplate_material.getMetaData()["variant_name"] = hotend_name
new_hotend_and_buildplate_material.getMetaData()["buildplate_name"] = buildplate_name
new_hotend_and_buildplate_material.setDefinition(machine_id)
# Don't use setMetadata, as that overrides it for all materials with same base file
new_hotend_and_buildplate_material.getMetaData()["compatible"] = buildplate_compatibility
new_hotend_and_buildplate_material.getMetaData()["machine_manufacturer"] = machine_manufacturer
new_hotend_and_buildplate_material.getMetaData()["definition"] = machine_id
new_hotend_and_buildplate_material.getMetaData()["buildplate_compatible"] = buildplate_compatibility
new_hotend_and_buildplate_material.getMetaData()["buildplate_recommended"] = buildplate_recommended
cached_hotend_and_buildplate_setting_properties = cached_hotend_setting_properties.copy()
cached_hotend_and_buildplate_setting_properties.update(buildplate_mapped_settings)
new_hotend_and_buildplate_material.setCachedValues(cached_hotend_and_buildplate_setting_properties)
new_hotend_and_buildplate_material._dirty = False
if is_new_material:
containers_to_add.append(new_hotend_and_buildplate_material)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break
@ -801,6 +820,54 @@ class XmlMaterialProfile(InstanceContainer):
for container_to_add in containers_to_add:
ContainerRegistry.getInstance().addContainer(container_to_add)
@classmethod
def _getSettingsDictForNode(cls, node) -> Tuple[dict, dict]:
node_mapped_settings_dict = dict()
node_unmapped_settings_dict = dict()
# Fetch settings in the "um" namespace
um_settings = node.iterfind("./um:setting", cls.__namespaces)
for um_setting_entry in um_settings:
setting_key = um_setting_entry.get("key")
# Mapped settings
if setting_key in cls.__material_settings_setting_map:
if setting_key == "processing temperature graph": # This setting has no setting text but subtags.
graph_nodes = um_setting_entry.iterfind("./um:point", cls.__namespaces)
graph_points = []
for graph_node in graph_nodes:
flow = float(graph_node.get("flow"))
temperature = float(graph_node.get("temperature"))
graph_points.append([flow, temperature])
node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = str(
graph_points)
else:
node_mapped_settings_dict[cls.__material_settings_setting_map[setting_key]] = um_setting_entry.text
# Unmapped settings
elif setting_key in cls.__unmapped_settings:
if setting_key in ("hardware compatible", "hardware recommended"):
node_unmapped_settings_dict[setting_key] = cls._parseCompatibleValue(um_setting_entry.text)
# Unknown settings
else:
Logger.log("w", "Unsupported material setting %s", setting_key)
# Fetch settings in the "cura" namespace
cura_settings = node.iterfind("./cura:setting", cls.__namespaces)
for cura_setting_entry in cura_settings:
value = cura_setting_entry.text
if value.lower() == "yes":
value = True
elif value.lower() == "no":
value = False
key = cura_setting_entry.get("key")
# Cura settings are all mapped
node_mapped_settings_dict[key] = value
return node_mapped_settings_dict, node_unmapped_settings_dict
@classmethod
def deserializeMetadata(cls, serialized: str, container_id: str) -> List[Dict[str, Any]]:
result_metadata = [] #All the metadata that we found except the base (because the base is returned).
@ -983,6 +1050,36 @@ class XmlMaterialProfile(InstanceContainer):
result_metadata.append(new_hotend_material_metadata)
#
# Buildplates in Hotends
#
buildplates = hotend.iterfind("./um:buildplate", cls.__namespaces)
for buildplate in buildplates:
# The "id" field for buildplate in material profiles is actually name
buildplate_name = buildplate.get("id")
if buildplate_name is None:
continue
buildplate_mapped_settings, buildplate_unmapped_settings = cls._getSettingsDictForNode(buildplate)
buildplate_compatibility = buildplate_unmapped_settings.get("hardware compatible",
buildplate_map["buildplate_compatible"])
buildplate_recommended = buildplate_unmapped_settings.get("hardware recommended",
buildplate_map["buildplate_recommended"])
# Generate container ID for the hotend-and-buildplate-specific material container
new_hotend_and_buildplate_specific_material_id = new_hotend_specific_material_id + "_" + buildplate_name.replace(
" ", "_")
new_hotend_and_buildplate_material_metadata = {}
new_hotend_and_buildplate_material_metadata.update(new_hotend_material_metadata)
new_hotend_and_buildplate_material_metadata["id"] = new_hotend_and_buildplate_specific_material_id
new_hotend_and_buildplate_material_metadata["buildplate_name"] = buildplate_name
new_hotend_and_buildplate_material_metadata["compatible"] = buildplate_compatibility
new_hotend_and_buildplate_material_metadata["buildplate_compatible"] = buildplate_compatibility
new_hotend_and_buildplate_material_metadata["buildplate_recommended"] = buildplate_recommended
result_metadata.append(new_hotend_and_buildplate_material_metadata)
# there is only one ID for a machine. Once we have reached here, it means we have already found
# a workable ID for that machine, so there is no need to continue
break

View file

@ -3,6 +3,6 @@
"author": "Ultimaker B.V.",
"version": "1.0.0",
"description": "Provides capabilities to read and write XML-based material profiles.",
"api": 4,
"api": 5,
"i18n-catalog": "cura"
}