mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-08-06 05:23:58 -06:00
Merge branch 'master' into fix_layer_number_width
This commit is contained in:
commit
a8cb1e25b4
294 changed files with 5983 additions and 2588 deletions
|
@ -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
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Checks for firmware updates.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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,
|
||||
}]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,6 @@
|
|||
"author": "Ultimaker B.V.",
|
||||
"version": "1.0.0",
|
||||
"description": "Provides the Simulation view.",
|
||||
"api": 4,
|
||||
"api": 5,
|
||||
"i18n-catalog": "cura"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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."
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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() != ""
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -26,6 +26,10 @@ UM.Dialog
|
|||
{
|
||||
resetPrintersModel()
|
||||
}
|
||||
else
|
||||
{
|
||||
OutputDevice.cancelPrintSelection()
|
||||
}
|
||||
}
|
||||
title: catalog.i18nc("@title:window", "Print over network")
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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()]
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue