diff --git a/.gitignore b/.gitignore
index 71e83433cf..a91d3f9377 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,7 +47,6 @@ plugins/Doodle3D-cura-plugin
plugins/FlatProfileExporter
plugins/GodMode
plugins/OctoPrintPlugin
-plugins/PostProcessingPlugin
plugins/ProfileFlattener
plugins/X3GWriter
diff --git a/cura/CuraApplication.py b/cura/CuraApplication.py
index 8aeeb9c1e8..30fd461868 100755
--- a/cura/CuraApplication.py
+++ b/cura/CuraApplication.py
@@ -319,7 +319,7 @@ class CuraApplication(QtApplication):
preferences.addPreference("cura/asked_dialog_on_project_save", False)
preferences.addPreference("cura/choice_on_profile_override", "always_ask")
preferences.addPreference("cura/choice_on_open_project", "always_ask")
- preferences.addPreference("cura/arrange_objects_on_load", True)
+ preferences.addPreference("cura/not_arrange_objects_on_load", False)
preferences.addPreference("cura/use_multi_build_plate", False)
preferences.addPreference("cura/currency", "€")
@@ -1428,11 +1428,13 @@ class CuraApplication(QtApplication):
self.fileLoaded.emit(filename)
arrange_objects_on_load = (
not Preferences.getInstance().getValue("cura/use_multi_build_plate") or
- Preferences.getInstance().getValue("cura/arrange_objects_on_load"))
+ not Preferences.getInstance().getValue("cura/not_arrange_objects_on_load"))
target_build_plate = self.getBuildPlateModel().activeBuildPlate if arrange_objects_on_load else -1
for original_node in nodes:
- node = CuraSceneNode() # We want our own CuraSceneNode
+
+ # Create a CuraSceneNode just if the original node is not that type
+ node = original_node if isinstance(original_node, CuraSceneNode) else CuraSceneNode()
node.setMeshData(original_node.getMeshData())
node.setSelectable(True)
@@ -1477,7 +1479,14 @@ class CuraApplication(QtApplication):
# Step is for skipping tests to make it a lot faster. it also makes the outcome somewhat rougher
node, _ = arranger.findNodePlacement(node, offset_shape_arr, hull_shape_arr, step = 10)
- node.addDecorator(BuildPlateDecorator(target_build_plate))
+ # This node is deepcopied from some other node which already has a BuildPlateDecorator, but the deepcopy
+ # of BuildPlateDecorator produces one that's assoicated with build plate -1. So, here we need to check if
+ # the BuildPlateDecorator exists or not and always set the correct build plate number.
+ build_plate_decorator = node.getDecorator(BuildPlateDecorator)
+ if build_plate_decorator is None:
+ build_plate_decorator = BuildPlateDecorator(target_build_plate)
+ node.addDecorator(build_plate_decorator)
+ build_plate_decorator.setBuildPlateNumber(target_build_plate)
op = AddSceneNodeOperation(node, scene.getRoot())
op.push()
diff --git a/cura/OneAtATimeIterator.py b/cura/OneAtATimeIterator.py
index 44f8d2766a..5653c8f1fb 100644
--- a/cura/OneAtATimeIterator.py
+++ b/cura/OneAtATimeIterator.py
@@ -18,7 +18,7 @@ class OneAtATimeIterator(Iterator.Iterator):
def _fillStack(self):
node_list = []
for node in self._scene_node.getChildren():
- if not type(node) is SceneNode:
+ if not isinstance(node, SceneNode):
continue
if node.callDecoration("getConvexHull"):
diff --git a/cura/PrintInformation.py b/cura/PrintInformation.py
index 60d3c11a49..838628e37c 100644
--- a/cura/PrintInformation.py
+++ b/cura/PrintInformation.py
@@ -11,6 +11,7 @@ from UM.Preferences import Preferences
from UM.Settings.ContainerRegistry import ContainerRegistry
from cura.Settings.ExtruderManager import ExtruderManager
+from typing import Dict
import math
import os.path
@@ -177,7 +178,7 @@ class PrintInformation(QObject):
self._material_amounts = material_amounts
self._calculateInformation(build_plate_number)
- def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time):
+ def _updateTotalPrintTimePerFeature(self, build_plate_number, print_time: Dict[str, int]):
total_estimated_time = 0
if build_plate_number not in self._print_time_message_values:
diff --git a/cura/Scene/CuraSceneController.py b/cura/Scene/CuraSceneController.py
index 65723db52c..c3e27ca3dd 100644
--- a/cura/Scene/CuraSceneController.py
+++ b/cura/Scene/CuraSceneController.py
@@ -41,6 +41,14 @@ class CuraSceneController(QObject):
self._build_plate_model.setMaxBuildPlate(self._max_build_plate)
build_plates = [{"name": "Build Plate %d" % (i + 1), "buildPlateNumber": i} for i in range(self._max_build_plate + 1)]
self._build_plate_model.setItems(build_plates)
+ if self._active_build_plate > self._max_build_plate:
+ build_plate_number = 0
+ if self._last_selected_index >= 0: # go to the buildplate of the item you last selected
+ item = self._objects_model.getItem(self._last_selected_index)
+ if "node" in item:
+ node = item["node"]
+ build_plate_number = node.callDecoration("getBuildPlateNumber")
+ self.setActiveBuildPlate(build_plate_number)
# self.buildPlateItemsChanged.emit() # TODO: necessary after setItems?
def _calcMaxBuildPlate(self):
@@ -75,11 +83,11 @@ class CuraSceneController(QObject):
# Single select
item = self._objects_model.getItem(index)
node = item["node"]
- Selection.clear()
- Selection.add(node)
build_plate_number = node.callDecoration("getBuildPlateNumber")
if build_plate_number is not None and build_plate_number != -1:
- self._build_plate_model.setActiveBuildPlate(build_plate_number)
+ self.setActiveBuildPlate(build_plate_number)
+ Selection.clear()
+ Selection.add(node)
self._last_selected_index = index
diff --git a/cura/Settings/ContainerManager.py b/cura/Settings/ContainerManager.py
index 209e1ec8fd..eefc109cbc 100644
--- a/cura/Settings/ContainerManager.py
+++ b/cura/Settings/ContainerManager.py
@@ -816,6 +816,22 @@ class ContainerManager(QObject):
ContainerRegistry.getInstance().addContainer(container_to_add)
return self._getMaterialContainerIdForActiveMachine(clone_of_original)
+ ## Create a duplicate of a material or it's original entry
+ #
+ # \return \type{str} the id of the newly created container.
+ @pyqtSlot(str, result = str)
+ def duplicateOriginalMaterial(self, material_id):
+
+ # check if the given material has a base file (i.e. was shipped by default)
+ base_file = self.getContainerMetaDataEntry(material_id, "base_file")
+
+ if base_file == "":
+ # there is no base file, so duplicate by ID
+ return self.duplicateMaterial(material_id)
+ else:
+ # there is a base file, so duplicate the original material
+ return self.duplicateMaterial(base_file)
+
## Create a new material by cloning Generic PLA for the current material diameter and setting the GUID to something unqiue
#
# \return \type{str} the id of the newly created container.
diff --git a/cura/Settings/CuraContainerRegistry.py b/cura/Settings/CuraContainerRegistry.py
index a078240d80..9202e57285 100644
--- a/cura/Settings/CuraContainerRegistry.py
+++ b/cura/Settings/CuraContainerRegistry.py
@@ -202,7 +202,6 @@ class CuraContainerRegistry(ContainerRegistry):
for plugin_id, meta_data in self._getIOPlugins("profile_reader"):
if meta_data["profile_reader"][0]["extension"] != extension:
continue
-
profile_reader = plugin_registry.getPluginObject(plugin_id)
try:
profile_or_list = profile_reader.read(file_name) # Try to open the file with the profile reader.
@@ -269,6 +268,10 @@ class CuraContainerRegistry(ContainerRegistry):
profile._id = new_id
profile.setName(new_name)
+ # Set the unique Id to the profile, so it's generating a new one even if the user imports the same profile
+ # It also solves an issue with importing profiles from G-Codes
+ profile.setMetaDataEntry("id", new_id)
+
if "type" in profile.getMetaData():
profile.setMetaDataEntry("type", "quality_changes")
else:
@@ -515,6 +518,7 @@ class CuraContainerRegistry(ContainerRegistry):
extruder_quality_changes_container = self.findInstanceContainers(name = machine.qualityChanges.getName(), extruder = extruder_id)
if extruder_quality_changes_container:
extruder_quality_changes_container = extruder_quality_changes_container[0]
+
quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id)
else:
@@ -525,15 +529,92 @@ class CuraContainerRegistry(ContainerRegistry):
if extruder_quality_changes_container:
quality_changes_id = extruder_quality_changes_container.getId()
extruder_stack.setQualityChangesById(quality_changes_id)
+ else:
+ # if we still cannot find a quality changes container for the extruder, create a new one
+ container_id = self.uniqueName(extruder_stack.getId() + "_user")
+ container_name = machine.qualityChanges.getName()
+ extruder_quality_changes_container = InstanceContainer(container_id)
+ extruder_quality_changes_container.setName(container_name)
+ extruder_quality_changes_container.addMetaDataEntry("type", "quality_changes")
+ extruder_quality_changes_container.addMetaDataEntry("setting_version", CuraApplication.SettingVersion)
+ extruder_quality_changes_container.addMetaDataEntry("extruder", extruder_stack.definition.getId())
+ extruder_quality_changes_container.addMetaDataEntry("quality_type", machine.qualityChanges.getMetaDataEntry("quality_type"))
+ extruder_quality_changes_container.setDefinition(machine.qualityChanges.getDefinition().getId())
if not extruder_quality_changes_container:
Logger.log("w", "Could not find quality_changes named [%s] for extruder [%s]",
machine.qualityChanges.getName(), extruder_stack.getId())
+ else:
+ # move all per-extruder settings to the extruder's quality changes
+ for qc_setting_key in machine.qualityChanges.getAllKeys():
+ settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
+ if settable_per_extruder:
+ setting_value = machine.qualityChanges.getProperty(qc_setting_key, "value")
+
+ setting_definition = machine.getSettingDefinition(qc_setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ extruder_quality_changes_container.addInstance(new_instance)
+ extruder_quality_changes_container.setDirty(True)
+
+ machine.qualityChanges.removeInstance(qc_setting_key, postpone_emit=True)
else:
extruder_stack.setQualityChangesById("empty_quality_changes")
self.addContainer(extruder_stack)
+ # Also need to fix the other qualities that are suitable for this machine. Those quality changes may still have
+ # per-extruder settings in the container for the machine instead of the extruder.
+ if machine.qualityChanges.getId() not in ("empty", "empty_quality_changes"):
+ quality_changes_machine_definition_id = machine.qualityChanges.getDefinition().getId()
+ else:
+ whole_machine_definition = machine.definition
+ machine_entry = machine.definition.getMetaDataEntry("machine")
+ if machine_entry is not None:
+ container_registry = ContainerRegistry.getInstance()
+ whole_machine_definition = container_registry.findDefinitionContainers(id = machine_entry)[0]
+
+ quality_changes_machine_definition_id = "fdmprinter"
+ if whole_machine_definition.getMetaDataEntry("has_machine_quality"):
+ quality_changes_machine_definition_id = machine.definition.getMetaDataEntry("quality_definition",
+ whole_machine_definition.getId())
+ qcs = self.findInstanceContainers(type = "quality_changes", definition = quality_changes_machine_definition_id)
+ qc_groups = {} # map of qc names -> qc containers
+ for qc in qcs:
+ qc_name = qc.getName()
+ if qc_name not in qc_groups:
+ qc_groups[qc_name] = []
+ qc_groups[qc_name].append(qc)
+ # try to find from the quality changes cura directory too
+ quality_changes_container = self._findQualityChangesContainerInCuraFolder(machine.qualityChanges.getName())
+ if quality_changes_container:
+ qc_groups[qc_name].append(quality_changes_container)
+
+ for qc_name, qc_list in qc_groups.items():
+ qc_dict = {"global": None, "extruders": []}
+ for qc in qc_list:
+ extruder_def_id = qc.getMetaDataEntry("extruder")
+ if extruder_def_id is not None:
+ qc_dict["extruders"].append(qc)
+ else:
+ qc_dict["global"] = qc
+ if qc_dict["global"] is not None and len(qc_dict["extruders"]) == 1:
+ # move per-extruder settings
+ for qc_setting_key in qc_dict["global"].getAllKeys():
+ settable_per_extruder = machine.getProperty(qc_setting_key, "settable_per_extruder")
+ if settable_per_extruder:
+ setting_value = qc_dict["global"].getProperty(qc_setting_key, "value")
+
+ setting_definition = machine.getSettingDefinition(qc_setting_key)
+ new_instance = SettingInstance(setting_definition, definition_changes)
+ new_instance.setProperty("value", setting_value)
+ new_instance.resetState() # Ensure that the state is not seen as a user state.
+ qc_dict["extruders"][0].addInstance(new_instance)
+ qc_dict["extruders"][0].setDirty(True)
+
+ qc_dict["global"].removeInstance(qc_setting_key, postpone_emit=True)
+
# Set next stack at the end
extruder_stack.setNextStack(machine)
@@ -562,6 +643,9 @@ class CuraContainerRegistry(ContainerRegistry):
if parser["general"]["name"] == name:
# load the container
container_id = os.path.basename(file_path).replace(".inst.cfg", "")
+ if self.findInstanceContainers(id = container_id):
+ # this container is already in the registry, skip it
+ continue
instance_container = InstanceContainer(container_id)
with open(file_path, "r") as f:
diff --git a/plugins/CuraEngineBackend/CuraEngineBackend.py b/plugins/CuraEngineBackend/CuraEngineBackend.py
index 0ca500ecec..e8c830b901 100755
--- a/plugins/CuraEngineBackend/CuraEngineBackend.py
+++ b/plugins/CuraEngineBackend/CuraEngineBackend.py
@@ -205,8 +205,8 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", " ## Process layers job still busy, trying later")
return
- if not hasattr(self._scene, "gcode_list"):
- self._scene.gcode_list = {}
+ if not hasattr(self._scene, "gcode_dict"):
+ self._scene.gcode_dict = {}
# see if we really have to slice
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
@@ -214,8 +214,10 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "Going to slice build plate [%s]!" % build_plate_to_be_sliced)
num_objects = self._numObjects()
if build_plate_to_be_sliced not in num_objects or num_objects[build_plate_to_be_sliced] == 0:
- self._scene.gcode_list[build_plate_to_be_sliced] = []
- Logger.log("d", "Build plate %s has 0 objects to be sliced, skipping", build_plate_to_be_sliced)
+ self._scene.gcode_dict[build_plate_to_be_sliced] = []
+ Logger.log("d", "Build plate %s has no objects to be sliced, skipping", build_plate_to_be_sliced)
+ if self._build_plates_to_be_sliced:
+ self.slice()
return
self._stored_layer_data = []
@@ -232,10 +234,12 @@ class CuraEngineBackend(QObject, Backend):
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
- self._scene.gcode_list[build_plate_to_be_sliced] = [] #[] indexed by build plate number
+ self._scene.gcode_dict[build_plate_to_be_sliced] = [] #[] indexed by build plate number
self._slicing = True
self.slicingStarted.emit()
+ self.determineAutoSlicing() # Switch timer on or off if appropriate
+
slice_message = self._socket.createMessage("cura.proto.Slice")
self._start_slice_job = StartSliceJob.StartSliceJob(slice_message)
self._start_slice_job_build_plate = build_plate_to_be_sliced
@@ -391,7 +395,7 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Disabled)
gcode_list = node.callDecoration("getGCodeList")
if gcode_list is not None:
- self._scene.gcode_list[node.callDecoration("getBuildPlateNumber")] = gcode_list
+ self._scene.gcode_dict[node.callDecoration("getBuildPlateNumber")] = gcode_list
if self._use_timer == enable_timer:
return self._use_timer
@@ -456,6 +460,7 @@ class CuraEngineBackend(QObject, Backend):
for build_plate_number in build_plate_changed:
if build_plate_number not in self._build_plates_to_be_sliced:
self._build_plates_to_be_sliced.append(build_plate_number)
+ self.printDurationMessage.emit(source_build_plate_number, {}, [])
self.processingProgress.emit(0.0)
self.backendStateChange.emit(BackendState.NotStarted)
# if not self._use_timer:
@@ -518,7 +523,7 @@ class CuraEngineBackend(QObject, Backend):
def _onStackErrorCheckFinished(self):
self._is_error_check_scheduled = False
- if not self._slicing and self._build_plates_to_be_sliced: #self._need_slicing:
+ if not self._slicing and self._build_plates_to_be_sliced:
self.needsSlicing()
self._onChanged()
@@ -558,7 +563,7 @@ class CuraEngineBackend(QObject, Backend):
self.backendStateChange.emit(BackendState.Done)
self.processingProgress.emit(1.0)
- gcode_list = self._scene.gcode_list[self._start_slice_job_build_plate]
+ gcode_list = self._scene.gcode_dict[self._start_slice_job_build_plate]
for index, line in enumerate(gcode_list):
replaced = line.replace("{print_time}", str(Application.getInstance().getPrintInformation().currentPrintTime.getDisplayString(DurationFormat.Format.ISO8601)))
replaced = replaced.replace("{filament_amount}", str(Application.getInstance().getPrintInformation().materialLengths))
@@ -581,21 +586,23 @@ class CuraEngineBackend(QObject, Backend):
Logger.log("d", "See if there is more to slice...")
# Somehow this results in an Arcus Error
# self.slice()
- # Testing call slice again, allow backend to restart by using the timer
- self._invokeSlice()
+ # Call slice again using the timer, allowing the backend to restart
+ if self._build_plates_to_be_sliced:
+ self.enableTimer() # manually enable timer to be able to invoke slice, also when in manual slice mode
+ self._invokeSlice()
## Called when a g-code message is received from the engine.
#
# \param message The protobuf message containing g-code, encoded as UTF-8.
def _onGCodeLayerMessage(self, message):
- self._scene.gcode_list[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
+ self._scene.gcode_dict[self._start_slice_job_build_plate].append(message.data.decode("utf-8", "replace"))
## Called when a g-code prefix message is received from the engine.
#
# \param message The protobuf message containing the g-code prefix,
# encoded as UTF-8.
def _onGCodePrefixMessage(self, message):
- self._scene.gcode_list[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
+ self._scene.gcode_dict[self._start_slice_job_build_plate].insert(0, message.data.decode("utf-8", "replace"))
## Creates a new socket connection.
def _createSocket(self):
diff --git a/plugins/CuraEngineBackend/ProcessGCodeJob.py b/plugins/CuraEngineBackend/ProcessGCodeJob.py
index 4974907c30..ed430f8fa9 100644
--- a/plugins/CuraEngineBackend/ProcessGCodeJob.py
+++ b/plugins/CuraEngineBackend/ProcessGCodeJob.py
@@ -12,4 +12,6 @@ class ProcessGCodeLayerJob(Job):
self._message = message
def run(self):
- self._scene.gcode_list.append(self._message.data.decode("utf-8", "replace"))
+ active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
+ gcode_list = self._scene.gcode_dict[active_build_plate_id]
+ gcode_list.append(self._message.data.decode("utf-8", "replace"))
diff --git a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
index be9c3f73f0..c1fc597d80 100644
--- a/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
+++ b/plugins/CuraEngineBackend/ProcessSlicedLayersJob.py
@@ -4,7 +4,6 @@
import gc
from UM.Job import Job
-from UM.Scene.SceneNode import SceneNode
from UM.Application import Application
from UM.Mesh.MeshData import MeshData
from UM.Preferences import Preferences
@@ -17,6 +16,7 @@ from UM.Logger import Logger
from UM.Math.Vector import Vector
from cura.Scene.BuildPlateDecorator import BuildPlateDecorator
+from cura.Scene.CuraSceneNode import CuraSceneNode
from cura.Settings.ExtruderManager import ExtruderManager
from cura import LayerDataBuilder
from cura import LayerDataDecorator
@@ -81,7 +81,7 @@ class ProcessSlicedLayersJob(Job):
Application.getInstance().getController().activeViewChanged.connect(self._onActiveViewChanged)
- new_node = SceneNode()
+ new_node = CuraSceneNode()
new_node.addDecorator(BuildPlateDecorator(self._build_plate_number))
# Force garbage collection.
diff --git a/plugins/GCodeReader/FlavorParser.py b/plugins/GCodeReader/FlavorParser.py
index fa5d6da243..f63ba3ca69 100644
--- a/plugins/GCodeReader/FlavorParser.py
+++ b/plugins/GCodeReader/FlavorParser.py
@@ -8,14 +8,14 @@ from UM.Logger import Logger
from UM.Math.AxisAlignedBox import AxisAlignedBox
from UM.Math.Vector import Vector
from UM.Message import Message
-from UM.Scene.SceneNode import SceneNode
+from cura.Scene.CuraSceneNode import CuraSceneNode
from UM.i18n import i18nCatalog
from UM.Preferences import Preferences
catalog = i18nCatalog("cura")
from cura import LayerDataBuilder
-from cura import LayerDataDecorator
+from cura.LayerDataDecorator import LayerDataDecorator
from cura.LayerPolygon import LayerPolygon
from cura.Scene.GCodeListDecorator import GCodeListDecorator
from cura.Settings.ExtruderManager import ExtruderManager
@@ -292,7 +292,7 @@ class FlavorParser:
# We obtain the filament diameter from the selected printer to calculate line widths
self._filament_diameter = Application.getInstance().getGlobalContainerStack().getProperty("material_diameter", "value")
- scene_node = SceneNode()
+ scene_node = CuraSceneNode()
# Override getBoundingBox function of the sceneNode, as this node should return a bounding box, but there is no
# real data to calculate it from.
scene_node.getBoundingBox = self._getNullBoundingBox
@@ -418,11 +418,17 @@ class FlavorParser:
self._layer_number += 1
current_path.clear()
- material_color_map = numpy.zeros((10, 4), dtype = numpy.float32)
+ material_color_map = numpy.zeros((8, 4), dtype = numpy.float32)
material_color_map[0, :] = [0.0, 0.7, 0.9, 1.0]
material_color_map[1, :] = [0.7, 0.9, 0.0, 1.0]
+ material_color_map[2, :] = [0.9, 0.0, 0.7, 1.0]
+ material_color_map[3, :] = [0.7, 0.0, 0.0, 1.0]
+ material_color_map[4, :] = [0.0, 0.7, 0.0, 1.0]
+ material_color_map[5, :] = [0.0, 0.0, 0.7, 1.0]
+ material_color_map[6, :] = [0.3, 0.3, 0.3, 1.0]
+ material_color_map[7, :] = [0.7, 0.7, 0.7, 1.0]
layer_mesh = self._layer_data_builder.build(material_color_map)
- decorator = LayerDataDecorator.LayerDataDecorator()
+ decorator = LayerDataDecorator()
decorator.setLayerData(layer_mesh)
scene_node.addDecorator(decorator)
@@ -430,7 +436,10 @@ class FlavorParser:
gcode_list_decorator.setGCodeList(gcode_list)
scene_node.addDecorator(gcode_list_decorator)
- Application.getInstance().getController().getScene().gcode_list = gcode_list
+ # gcode_dict stores gcode_lists for a number of build plates.
+ active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
+ gcode_dict = {active_build_plate_id: gcode_list}
+ Application.getInstance().getController().getScene().gcode_dict = gcode_dict
Logger.log("d", "Finished parsing %s" % file_name)
self._message.hide()
diff --git a/plugins/GCodeWriter/GCodeWriter.py b/plugins/GCodeWriter/GCodeWriter.py
index ad23f2c8ee..95c48c4d9e 100644
--- a/plugins/GCodeWriter/GCodeWriter.py
+++ b/plugins/GCodeWriter/GCodeWriter.py
@@ -61,8 +61,11 @@ class GCodeWriter(MeshWriter):
active_build_plate = Application.getInstance().getBuildPlateModel().activeBuildPlate
scene = Application.getInstance().getController().getScene()
- gcode_list = getattr(scene, "gcode_list")[active_build_plate]
- if gcode_list:
+ gcode_dict = getattr(scene, "gcode_dict")
+ if not gcode_dict:
+ return False
+ gcode_list = gcode_dict.get(active_build_plate, None)
+ if gcode_list is not None:
for gcode in gcode_list:
stream.write(gcode)
# Serialise the current container stack and put it at the end of the file.
diff --git a/plugins/MonitorStage/MonitorMainView.qml b/plugins/MonitorStage/MonitorMainView.qml
index 15b05bed0a..c48f6d0aab 100644
--- a/plugins/MonitorStage/MonitorMainView.qml
+++ b/plugins/MonitorStage/MonitorMainView.qml
@@ -8,8 +8,9 @@ import Cura 1.0 as Cura
Item
{
- width: parent.width
- height: parent.height
+ // parent could be undefined as this component is not visible at all times
+ width: parent ? parent.width : 0
+ height: parent ? parent.height : 0
// We show a nice overlay on the 3D viewer when the current output device has no monitor view
Rectangle
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
index badca13468..3e1df1c7b8 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingVisibilityHandler.py
@@ -2,6 +2,7 @@
# Cura is released under the terms of the LGPLv3 or higher.
from PyQt5.QtCore import QObject, pyqtProperty, pyqtSignal
+from UM.FlameProfiler import pyqtSlot
from UM.Application import Application
from UM.Settings.ContainerRegistry import ContainerRegistry
@@ -22,6 +23,9 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
self._node = None
self._stack = None
+ # this is a set of settings that will be skipped if the user chooses to reset.
+ self._skip_reset_setting_set = set()
+
def setSelectedObjectId(self, id):
if id != self._selected_object_id:
self._selected_object_id = id
@@ -36,6 +40,10 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
def selectedObjectId(self):
return self._selected_object_id
+ @pyqtSlot(str)
+ def addSkipResetSetting(self, setting_name):
+ self._skip_reset_setting_set.add(setting_name)
+
def setVisible(self, visible):
if not self._node:
return
@@ -50,6 +58,9 @@ class PerObjectSettingVisibilityHandler(UM.Settings.Models.SettingVisibilityHand
# Remove all instances that are not in visibility list
for instance in all_instances:
+ # exceptionally skip setting
+ if instance.definition.key in self._skip_reset_setting_set:
+ continue
if instance.definition.key not in visible:
settings.removeInstance(instance.definition.key)
visibility_changed = True
diff --git a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
index 5bdb6d4cb0..eb492d8de2 100644
--- a/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
+++ b/plugins/PerObjectSettingsTool/PerObjectSettingsPanel.qml
@@ -18,6 +18,9 @@ Item {
width: childrenRect.width;
height: childrenRect.height;
+ property var all_categories_except_support: [ "machine_settings", "resolution", "shell", "infill", "material", "speed",
+ "travel", "cooling", "platform_adhesion", "dual", "meshfix", "blackmagic", "experimental"]
+
Column
{
id: items
@@ -39,6 +42,13 @@ Item {
verticalAlignment: Text.AlignVCenter
}
+ UM.SettingPropertyProvider
+ {
+ id: meshTypePropertyProvider
+ containerStackId: Cura.MachineManager.activeMachineId
+ watchedProperties: [ "enabled" ]
+ }
+
ComboBox
{
id: meshTypeSelection
@@ -49,36 +59,55 @@ Item {
model: ListModel
{
id: meshTypeModel
- Component.onCompleted:
+ Component.onCompleted: meshTypeSelection.populateModel()
+ }
+
+ function populateModel()
+ {
+ meshTypeModel.append({
+ type: "",
+ text: catalog.i18nc("@label", "Normal model")
+ });
+ meshTypePropertyProvider.key = "support_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
{
- meshTypeModel.append({
- type: "",
- text: catalog.i18nc("@label", "Normal model")
- });
meshTypeModel.append({
type: "support_mesh",
text: catalog.i18nc("@label", "Print as support")
});
+ }
+ meshTypePropertyProvider.key = "anti_overhang_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "anti_overhang_mesh",
text: catalog.i18nc("@label", "Don't support overlap with other models")
});
+ }
+ meshTypePropertyProvider.key = "cutting_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "cutting_mesh",
text: catalog.i18nc("@label", "Modify settings for overlap with other models")
});
+ }
+ meshTypePropertyProvider.key = "infill_mesh";
+ if(meshTypePropertyProvider.properties.enabled == "True")
+ {
meshTypeModel.append({
type: "infill_mesh",
text: catalog.i18nc("@label", "Modify settings for infill of other models")
});
-
- meshTypeSelection.updateCurrentIndex();
}
+
+ meshTypeSelection.updateCurrentIndex();
}
function updateCurrentIndex()
{
var mesh_type = UM.ActiveTool.properties.getValue("MeshType");
+ meshTypeSelection.currentIndex = -1;
for(var index=0; index < meshTypeSelection.model.count; index++)
{
if(meshTypeSelection.model.get(index).type == mesh_type)
@@ -91,6 +120,16 @@ Item {
}
}
+ Connections
+ {
+ target: Cura.MachineManager
+ onGlobalContainerChanged:
+ {
+ meshTypeSelection.model.clear();
+ meshTypeSelection.populateModel();
+ }
+ }
+
Connections
{
target: UM.Selection
@@ -106,7 +145,7 @@ Item {
id: currentSettings
property int maximumHeight: 200 * screenScaleFactor
height: Math.min(contents.count * (UM.Theme.getSize("section").height + UM.Theme.getSize("default_lining").height), maximumHeight)
- visible: ["support_mesh", "anti_overhang_mesh"].indexOf(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type) == -1
+ visible: meshTypeSelection.model.get(meshTypeSelection.currentIndex).type != "anti_overhang_mesh"
ScrollView
{
@@ -124,7 +163,15 @@ Item {
id: addedSettingsModel;
containerId: Cura.MachineManager.activeDefinitionId
expanded: [ "*" ]
- exclude: [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
+ exclude: {
+ var excluded_settings = [ "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
+
+ if(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
+ {
+ excluded_settings = excluded_settings.concat(base.all_categories_except_support);
+ }
+ return excluded_settings;
+ }
visibilityHandler: Cura.PerObjectSettingVisibilityHandler
{
@@ -306,7 +353,18 @@ Item {
}
}
- onClicked: settingPickDialog.visible = true;
+ onClicked:
+ {
+ settingPickDialog.visible = true;
+ if (meshTypeSelection.model.get(meshTypeSelection.currentIndex).type == "support_mesh")
+ {
+ settingPickDialog.additional_excluded_settings = base.all_categories_except_support;
+ }
+ else
+ {
+ settingPickDialog.additional_excluded_settings = []
+ }
+ }
}
}
@@ -315,15 +373,18 @@ Item {
id: settingPickDialog
title: catalog.i18nc("@title:window", "Select Settings to Customize for this model")
- width: screenScaleFactor * 360;
+ width: screenScaleFactor * 360
property string labelFilter: ""
+ property var additional_excluded_settings
onVisibilityChanged:
{
// force updating the model to sync it with addedSettingsModel
if(visible)
{
+ // Set skip setting, it will prevent from resetting selected mesh_type
+ contents.model.visibilityHandler.addSkipResetSetting(meshTypeSelection.model.get(meshTypeSelection.currentIndex).type)
listview.model.forceUpdate()
}
}
@@ -394,7 +455,12 @@ Item {
}
visibilityHandler: UM.SettingPreferenceVisibilityHandler {}
expanded: [ "*" ]
- exclude: [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ]
+ exclude:
+ {
+ var excluded_settings = [ "machine_settings", "command_line_settings", "support_mesh", "anti_overhang_mesh", "cutting_mesh", "infill_mesh" ];
+ excluded_settings = excluded_settings.concat(settingPickDialog.additional_excluded_settings);
+ return excluded_settings;
+ }
}
delegate:Loader
{
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.py b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
new file mode 100644
index 0000000000..657e5c5387
--- /dev/null
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.py
@@ -0,0 +1,206 @@
+# Copyright (c) 2015 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
+from UM.Resources import Resources
+from UM.Application import Application
+from UM.Extension import Extension
+from UM.Logger import Logger
+
+import os.path
+import pkgutil
+import sys
+import importlib.util
+
+from UM.i18n import i18nCatalog
+i18n_catalog = i18nCatalog("cura")
+
+
+## The post processing plugin is an Extension type plugin that enables pre-written scripts to post process generated
+# g-code files.
+class PostProcessingPlugin(QObject, Extension):
+ def __init__(self, parent = None):
+ super().__init__(parent)
+ self.addMenuItem(i18n_catalog.i18n("Modify G-Code"), self.showPopup)
+ self._view = None
+
+ # Loaded scripts are all scripts that can be used
+ self._loaded_scripts = {}
+ self._script_labels = {}
+
+ # Script list contains instances of scripts in loaded_scripts.
+ # There can be duplicates, which will be executed in sequence.
+ self._script_list = []
+ self._selected_script_index = -1
+
+ Application.getInstance().getOutputDeviceManager().writeStarted.connect(self.execute)
+
+ selectedIndexChanged = pyqtSignal()
+ @pyqtProperty("QVariant", notify = selectedIndexChanged)
+ def selectedScriptDefinitionId(self):
+ try:
+ return self._script_list[self._selected_script_index].getDefinitionId()
+ except:
+ return ""
+
+ @pyqtProperty("QVariant", notify=selectedIndexChanged)
+ def selectedScriptStackId(self):
+ try:
+ return self._script_list[self._selected_script_index].getStackId()
+ except:
+ return ""
+
+ ## Execute all post-processing scripts on the gcode.
+ def execute(self, output_device):
+ scene = Application.getInstance().getController().getScene()
+ gcode_dict = getattr(scene, "gcode_dict")
+ if not gcode_dict:
+ return
+
+ # get gcode list for the active build plate
+ active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
+ gcode_list = gcode_dict[active_build_plate_id]
+ if not gcode_list:
+ return
+
+ if ";POSTPROCESSED" not in gcode_list[0]:
+ for script in self._script_list:
+ try:
+ gcode_list = script.execute(gcode_list)
+ except Exception:
+ Logger.logException("e", "Exception in post-processing script.")
+ if len(self._script_list): # Add comment to g-code if any changes were made.
+ gcode_list[0] += ";POSTPROCESSED\n"
+ gcode_dict[active_build_plate_id] = gcode_list
+ setattr(scene, "gcode_dict", gcode_dict)
+ else:
+ Logger.log("e", "Already post processed")
+
+ @pyqtSlot(int)
+ def setSelectedScriptIndex(self, index):
+ self._selected_script_index = index
+ self.selectedIndexChanged.emit()
+
+ @pyqtProperty(int, notify = selectedIndexChanged)
+ def selectedScriptIndex(self):
+ return self._selected_script_index
+
+ @pyqtSlot(int, int)
+ def moveScript(self, index, new_index):
+ if new_index < 0 or new_index > len(self._script_list) - 1:
+ return # nothing needs to be done
+ else:
+ # Magical switch code.
+ self._script_list[new_index], self._script_list[index] = self._script_list[index], self._script_list[new_index]
+ self.scriptListChanged.emit()
+ self.selectedIndexChanged.emit() #Ensure that settings are updated
+ self._propertyChanged()
+
+ ## Remove a script from the active script list by index.
+ @pyqtSlot(int)
+ def removeScriptByIndex(self, index):
+ self._script_list.pop(index)
+ if len(self._script_list) - 1 < self._selected_script_index:
+ self._selected_script_index = len(self._script_list) - 1
+ self.scriptListChanged.emit()
+ self.selectedIndexChanged.emit() # Ensure that settings are updated
+ self._propertyChanged()
+
+ ## Load all scripts from provided path.
+ # This should probably only be done on init.
+ # \param path Path to check for scripts.
+ def loadAllScripts(self, path):
+ scripts = pkgutil.iter_modules(path = [path])
+ for loader, script_name, ispkg in scripts:
+ # Iterate over all scripts.
+ if script_name not in sys.modules:
+ spec = importlib.util.spec_from_file_location(__name__ + "." + script_name, os.path.join(path, script_name + ".py"))
+ loaded_script = importlib.util.module_from_spec(spec)
+ spec.loader.exec_module(loaded_script)
+ sys.modules[script_name] = loaded_script
+
+ loaded_class = getattr(loaded_script, script_name)
+ temp_object = loaded_class()
+ Logger.log("d", "Begin loading of script: %s", script_name)
+ try:
+ setting_data = temp_object.getSettingData()
+ if "name" in setting_data and "key" in setting_data:
+ self._script_labels[setting_data["key"]] = setting_data["name"]
+ self._loaded_scripts[setting_data["key"]] = loaded_class
+ else:
+ Logger.log("w", "Script %s.py has no name or key", script_name)
+ self._script_labels[script_name] = script_name
+ self._loaded_scripts[script_name] = loaded_class
+ except AttributeError:
+ Logger.log("e", "Script %s.py is not a recognised script type. Ensure it inherits Script", script_name)
+ except NotImplementedError:
+ Logger.log("e", "Script %s.py has no implemented settings", script_name)
+ self.loadedScriptListChanged.emit()
+
+ loadedScriptListChanged = pyqtSignal()
+ @pyqtProperty("QVariantList", notify = loadedScriptListChanged)
+ def loadedScriptList(self):
+ return sorted(list(self._loaded_scripts.keys()))
+
+ @pyqtSlot(str, result = str)
+ def getScriptLabelByKey(self, key):
+ return self._script_labels[key]
+
+ scriptListChanged = pyqtSignal()
+ @pyqtProperty("QVariantList", notify = scriptListChanged)
+ def scriptList(self):
+ script_list = [script.getSettingData()["key"] for script in self._script_list]
+ return script_list
+
+ @pyqtSlot(str)
+ def addScriptToList(self, key):
+ Logger.log("d", "Adding script %s to list.", key)
+ new_script = self._loaded_scripts[key]()
+ self._script_list.append(new_script)
+ self.setSelectedScriptIndex(len(self._script_list) - 1)
+ self.scriptListChanged.emit()
+ self._propertyChanged()
+
+ ## Creates the view used by show popup. The view is saved because of the fairly aggressive garbage collection.
+ def _createView(self):
+ Logger.log("d", "Creating post processing plugin view.")
+
+ ## Load all scripts in the scripts folders
+ for root in [PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), Resources.getStoragePath(Resources.Preferences)]:
+ try:
+ path = os.path.join(root, "scripts")
+ if not os.path.isdir(path):
+ try:
+ os.makedirs(path)
+ except OSError:
+ Logger.log("w", "Unable to create a folder for scripts: " + path)
+ continue
+
+ self.loadAllScripts(path)
+ except Exception as e:
+ Logger.logException("e", "Exception occurred while loading post processing plugin: {error_msg}".format(error_msg = str(e)))
+
+ # Create the plugin dialog component
+ path = os.path.join(PluginRegistry.getInstance().getPluginPath("PostProcessingPlugin"), "PostProcessingPlugin.qml")
+ self._view = Application.getInstance().createQmlComponent(path, {"manager": self})
+ Logger.log("d", "Post processing view created.")
+
+ # Create the save button component
+ Application.getInstance().addAdditionalComponent("saveButton", self._view.findChild(QObject, "postProcessingSaveAreaButton"))
+
+ ## Show the (GUI) popup of the post processing plugin.
+ def showPopup(self):
+ if self._view is None:
+ self._createView()
+ self._view.show()
+
+ ## Property changed: trigger re-slice
+ # To do this we use the global container stack propertyChanged.
+ # Re-slicing is necessary for setting changes in this plugin, because the changes
+ # are applied only once per "fresh" gcode
+ def _propertyChanged(self):
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_container_stack.propertyChanged.emit("post_processing_plugin", "value")
+
+
diff --git a/plugins/PostProcessingPlugin/PostProcessingPlugin.qml b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
new file mode 100644
index 0000000000..d64d60a04a
--- /dev/null
+++ b/plugins/PostProcessingPlugin/PostProcessingPlugin.qml
@@ -0,0 +1,501 @@
+// Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+// The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+import QtQuick 2.2
+import QtQuick.Controls 1.1
+import QtQuick.Controls.Styles 1.1
+import QtQuick.Layouts 1.1
+import QtQuick.Dialogs 1.1
+import QtQuick.Window 2.2
+
+import UM 1.2 as UM
+import Cura 1.0 as Cura
+
+UM.Dialog
+{
+ id: dialog
+
+ title: catalog.i18nc("@title:window", "Post Processing Plugin")
+ width: 700 * screenScaleFactor;
+ height: 500 * screenScaleFactor;
+ minimumWidth: 400 * screenScaleFactor;
+ minimumHeight: 250 * screenScaleFactor;
+
+ Item
+ {
+ UM.I18nCatalog{id: catalog; name:"cura"}
+ id: base
+ property int columnWidth: Math.floor((base.width / 2) - UM.Theme.getSize("default_margin").width)
+ property int textMargin: Math.floor(UM.Theme.getSize("default_margin").width / 2)
+ property string activeScriptName
+ SystemPalette{ id: palette }
+ SystemPalette{ id: disabledPalette; colorGroup: SystemPalette.Disabled }
+ anchors.fill: parent
+
+ ExclusiveGroup
+ {
+ id: selectedScriptGroup
+ }
+ Item
+ {
+ id: activeScripts
+ anchors.left: parent.left
+ width: base.columnWidth
+ height: parent.height
+
+ Label
+ {
+ id: activeScriptsHeader
+ text: catalog.i18nc("@label", "Post Processing Scripts")
+ anchors.top: parent.top
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ font: UM.Theme.getFont("large")
+ }
+ ListView
+ {
+ id: activeScriptsList
+ anchors.top: activeScriptsHeader.bottom
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ height: childrenRect.height
+ model: manager.scriptList
+ delegate: Item
+ {
+ width: parent.width
+ height: activeScriptButton.height
+ Button
+ {
+ id: activeScriptButton
+ text: manager.getScriptLabelByKey(modelData.toString())
+ exclusiveGroup: selectedScriptGroup
+ checkable: true
+ checked: {
+ if (manager.selectedScriptIndex == index)
+ {
+ base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+ return true
+ }
+ else
+ {
+ return false
+ }
+ }
+ onClicked:
+ {
+ forceActiveFocus()
+ manager.setSelectedScriptIndex(index)
+ base.activeScriptName = manager.getScriptLabelByKey(modelData.toString())
+ }
+ width: parent.width
+ height: UM.Theme.getSize("setting").height
+ style: ButtonStyle
+ {
+ background: Rectangle
+ {
+ color: activeScriptButton.checked ? palette.highlight : "transparent"
+ width: parent.width
+ height: parent.height
+ }
+ label: Label
+ {
+ wrapMode: Text.Wrap
+ text: control.text
+ color: activeScriptButton.checked ? palette.highlightedText : palette.text
+ }
+ }
+ }
+ Button
+ {
+ id: removeButton
+ text: "x"
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ anchors.right:parent.right
+ anchors.rightMargin: base.textMargin
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked: manager.removeScriptByIndex(index)
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.floor(control.width / 2.7)
+ height: Math.floor(control.height / 2.7)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: palette.text
+ source: UM.Theme.getIcon("cross1")
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: downButton
+ text: ""
+ anchors.right: removeButton.left
+ anchors.verticalCenter: parent.verticalCenter
+ enabled: index != manager.scriptList.length - 1
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ onClicked:
+ {
+ if (manager.selectedScriptIndex == index)
+ {
+ manager.setSelectedScriptIndex(index + 1)
+ }
+ return manager.moveScript(index, index + 1)
+ }
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.floor(control.width / 2.5)
+ height: Math.floor(control.height / 2.5)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: control.enabled ? palette.text : disabledPalette.text
+ source: UM.Theme.getIcon("arrow_bottom")
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: upButton
+ text: ""
+ enabled: index != 0
+ width: 20 * screenScaleFactor
+ height: 20 * screenScaleFactor
+ anchors.right: downButton.left
+ anchors.verticalCenter: parent.verticalCenter
+ onClicked:
+ {
+ if (manager.selectedScriptIndex == index)
+ {
+ manager.setSelectedScriptIndex(index - 1)
+ }
+ return manager.moveScript(index, index - 1)
+ }
+ style: ButtonStyle
+ {
+ label: Item
+ {
+ UM.RecolorImage
+ {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.floor(control.width / 2.5)
+ height: Math.floor(control.height / 2.5)
+ sourceSize.width: width
+ sourceSize.height: width
+ color: control.enabled ? palette.text : disabledPalette.text
+ source: UM.Theme.getIcon("arrow_top")
+ }
+ }
+ }
+ }
+ }
+ }
+ Button
+ {
+ id: addButton
+ text: catalog.i18nc("@action", "Add a script")
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.top: activeScriptsList.bottom
+ anchors.topMargin: base.textMargin
+ menu: scriptsMenu
+ style: ButtonStyle
+ {
+ label: Label
+ {
+ text: control.text
+ }
+ }
+ }
+ Menu
+ {
+ id: scriptsMenu
+
+ Instantiator
+ {
+ model: manager.loadedScriptList
+
+ MenuItem
+ {
+ text: manager.getScriptLabelByKey(modelData.toString())
+ onTriggered: manager.addScriptToList(modelData.toString())
+ }
+
+ onObjectAdded: scriptsMenu.insertItem(index, object);
+ onObjectRemoved: scriptsMenu.removeItem(object);
+ }
+ }
+ }
+
+ Rectangle
+ {
+ color: UM.Theme.getColor("sidebar")
+ anchors.left: activeScripts.right
+ anchors.leftMargin: UM.Theme.getSize("default_margin").width
+ anchors.right: parent.right
+ height: parent.height
+ id: settingsPanel
+
+ Label
+ {
+ id: scriptSpecsHeader
+ text: manager.selectedScriptIndex == -1 ? catalog.i18nc("@label", "Settings") : base.activeScriptName
+ anchors.top: parent.top
+ anchors.topMargin: base.textMargin
+ anchors.left: parent.left
+ anchors.leftMargin: base.textMargin
+ anchors.right: parent.right
+ anchors.rightMargin: base.textMargin
+ height: 20 * screenScaleFactor
+ font: UM.Theme.getFont("large")
+ color: UM.Theme.getColor("text")
+ }
+
+ ScrollView
+ {
+ id: scrollView
+ anchors.top: scriptSpecsHeader.bottom
+ anchors.topMargin: settingsPanel.textMargin
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ visible: manager.selectedScriptDefinitionId != ""
+ style: UM.Theme.styles.scrollview;
+
+ ListView
+ {
+ id: listview
+ spacing: UM.Theme.getSize("default_lining").height
+ model: UM.SettingDefinitionsModel
+ {
+ id: definitionsModel;
+ containerId: manager.selectedScriptDefinitionId
+ showAll: true
+ }
+ delegate:Loader
+ {
+ id: settingLoader
+
+ width: parent.width
+ height:
+ {
+ if(provider.properties.enabled == "True")
+ {
+ if(model.type != undefined)
+ {
+ return UM.Theme.getSize("section").height;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+
+ }
+ Behavior on height { NumberAnimation { duration: 100 } }
+ opacity: provider.properties.enabled == "True" ? 1 : 0
+ Behavior on opacity { NumberAnimation { duration: 100 } }
+ enabled: opacity > 0
+ property var definition: model
+ property var settingDefinitionsModel: definitionsModel
+ property var propertyProvider: provider
+ property var globalPropertyProvider: inheritStackProvider
+
+ //Qt5.4.2 and earlier has a bug where this causes a crash: https://bugreports.qt.io/browse/QTBUG-35989
+ //In addition, while it works for 5.5 and higher, the ordering of the actual combo box drop down changes,
+ //causing nasty issues when selecting different options. So disable asynchronous loading of enum type completely.
+ asynchronous: model.type != "enum" && model.type != "extruder"
+
+ onLoaded: {
+ settingLoader.item.showRevertButton = false
+ settingLoader.item.showInheritButton = false
+ settingLoader.item.showLinkedSettingIcon = false
+ settingLoader.item.doDepthIndentation = true
+ settingLoader.item.doQualityUserSettingEmphasis = false
+ }
+
+ sourceComponent:
+ {
+ switch(model.type)
+ {
+ case "int":
+ return settingTextField
+ case "float":
+ return settingTextField
+ case "enum":
+ return settingComboBox
+ case "extruder":
+ return settingExtruder
+ case "bool":
+ return settingCheckBox
+ case "str":
+ return settingTextField
+ case "category":
+ return settingCategory
+ default:
+ return settingUnknown
+ }
+ }
+
+ UM.SettingPropertyProvider
+ {
+ id: provider
+ containerStackId: manager.selectedScriptStackId
+ key: model.key ? model.key : "None"
+ watchedProperties: [ "value", "enabled", "state", "validationState" ]
+ storeIndex: 0
+ }
+
+ // Specialty provider that only watches global_inherits (we cant filter on what property changed we get events
+ // so we bypass that to make a dedicated provider).
+ UM.SettingPropertyProvider
+ {
+ id: inheritStackProvider
+ containerStackId: Cura.MachineManager.activeMachineId
+ key: model.key ? model.key : "None"
+ watchedProperties: [ "limit_to_extruder" ]
+ }
+
+ Connections
+ {
+ target: item
+
+ onShowTooltip:
+ {
+ tooltip.text = text;
+ var position = settingLoader.mapToItem(settingsPanel, settingsPanel.x, 0);
+ tooltip.show(position);
+ tooltip.target.x = position.x + 1
+ }
+
+ onHideTooltip:
+ {
+ tooltip.hide();
+ }
+ }
+
+ }
+ }
+ }
+ }
+
+ Cura.SidebarTooltip
+ {
+ id: tooltip
+ }
+
+ Component
+ {
+ id: settingTextField;
+
+ Cura.SettingTextField { }
+ }
+
+ Component
+ {
+ id: settingComboBox;
+
+ Cura.SettingComboBox { }
+ }
+
+ Component
+ {
+ id: settingExtruder;
+
+ Cura.SettingExtruder { }
+ }
+
+ Component
+ {
+ id: settingCheckBox;
+
+ Cura.SettingCheckBox { }
+ }
+
+ Component
+ {
+ id: settingCategory;
+
+ Cura.SettingCategory { }
+ }
+
+ Component
+ {
+ id: settingUnknown;
+
+ Cura.SettingUnknown { }
+ }
+ }
+ rightButtons: Button
+ {
+ text: catalog.i18nc("@action:button", "Close")
+ iconName: "dialog-close"
+ onClicked: dialog.accept()
+ }
+
+ Button {
+ objectName: "postProcessingSaveAreaButton"
+ visible: activeScriptsList.count > 0
+ height: UM.Theme.getSize("save_button_save_to_button").height
+ width: height
+ tooltip: catalog.i18nc("@info:tooltip", "Change active post-processing scripts")
+ onClicked: dialog.show()
+
+ style: ButtonStyle {
+ background: Rectangle {
+ id: deviceSelectionIcon
+ border.width: UM.Theme.getSize("default_lining").width
+ border.color: !control.enabled ? UM.Theme.getColor("action_button_disabled_border") :
+ control.pressed ? UM.Theme.getColor("action_button_active_border") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_border") : UM.Theme.getColor("action_button_border")
+ color: !control.enabled ? UM.Theme.getColor("action_button_disabled") :
+ control.pressed ? UM.Theme.getColor("action_button_active") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered") : UM.Theme.getColor("action_button")
+ Behavior on color { ColorAnimation { duration: 50; } }
+ anchors.left: parent.left
+ anchors.leftMargin: Math.floor(UM.Theme.getSize("save_button_text_margin").width / 2);
+ width: parent.height
+ height: parent.height
+
+ UM.RecolorImage {
+ anchors.verticalCenter: parent.verticalCenter
+ anchors.horizontalCenter: parent.horizontalCenter
+ width: Math.floor(parent.width / 2)
+ height: Math.floor(parent.height / 2)
+ sourceSize.width: width
+ sourceSize.height: height
+ color: !control.enabled ? UM.Theme.getColor("action_button_disabled_text") :
+ control.pressed ? UM.Theme.getColor("action_button_active_text") :
+ control.hovered ? UM.Theme.getColor("action_button_hovered_text") : UM.Theme.getColor("action_button_text");
+ source: "postprocessing.svg"
+ }
+ }
+ label: Label{ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/README.md b/plugins/PostProcessingPlugin/README.md
new file mode 100644
index 0000000000..988f40007d
--- /dev/null
+++ b/plugins/PostProcessingPlugin/README.md
@@ -0,0 +1,2 @@
+# PostProcessingPlugin
+A post processing plugin for Cura
diff --git a/plugins/PostProcessingPlugin/Script.py b/plugins/PostProcessingPlugin/Script.py
new file mode 100644
index 0000000000..7d603ba11f
--- /dev/null
+++ b/plugins/PostProcessingPlugin/Script.py
@@ -0,0 +1,111 @@
+# Copyright (c) 2015 Jaime van Kessel
+# Copyright (c) 2017 Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from UM.Logger import Logger
+from UM.Signal import Signal, signalemitter
+from UM.i18n import i18nCatalog
+
+# Setting stuff import
+from UM.Application import Application
+from UM.Settings.ContainerStack import ContainerStack
+from UM.Settings.InstanceContainer import InstanceContainer
+from UM.Settings.DefinitionContainer import DefinitionContainer
+from UM.Settings.ContainerRegistry import ContainerRegistry
+
+import re
+import json
+import collections
+i18n_catalog = i18nCatalog("cura")
+
+
+## Base class for scripts. All scripts should inherit the script class.
+@signalemitter
+class Script:
+ def __init__(self):
+ super().__init__()
+ self._settings = None
+ self._stack = None
+
+ setting_data = self.getSettingData()
+ self._stack = ContainerStack(stack_id = str(id(self)))
+ self._stack.setDirty(False) # This stack does not need to be saved.
+
+
+ ## Check if the definition of this script already exists. If not, add it to the registry.
+ if "key" in setting_data:
+ definitions = ContainerRegistry.getInstance().findDefinitionContainers(id = setting_data["key"])
+ if definitions:
+ # Definition was found
+ self._definition = definitions[0]
+ else:
+ self._definition = DefinitionContainer(setting_data["key"])
+ self._definition.deserialize(json.dumps(setting_data))
+ ContainerRegistry.getInstance().addContainer(self._definition)
+ self._stack.addContainer(self._definition)
+ self._instance = InstanceContainer(container_id="ScriptInstanceContainer")
+ self._instance.setDefinition(self._definition.getId())
+ self._instance.addMetaDataEntry("setting_version", self._definition.getMetaDataEntry("setting_version", default = 0))
+ self._stack.addContainer(self._instance)
+ self._stack.propertyChanged.connect(self._onPropertyChanged)
+
+ ContainerRegistry.getInstance().addContainer(self._stack)
+
+ settingsLoaded = Signal()
+ valueChanged = Signal() # Signal emitted whenever a value of a setting is changed
+
+ def _onPropertyChanged(self, key, property_name):
+ if property_name == "value":
+ self.valueChanged.emit()
+
+ # Property changed: trigger reslice
+ # To do this we use the global container stack propertyChanged.
+ # Reslicing is necessary for setting changes in this plugin, because the changes
+ # are applied only once per "fresh" gcode
+ global_container_stack = Application.getInstance().getGlobalContainerStack()
+ global_container_stack.propertyChanged.emit(key, property_name)
+
+ ## Needs to return a dict that can be used to construct a settingcategory file.
+ # See the example script for an example.
+ # It follows the same style / guides as the Uranium settings.
+ # Scripts can either override getSettingData directly, or use getSettingDataString
+ # to return a string that will be parsed as json. The latter has the benefit over
+ # returning a dict in that the order of settings is maintained.
+ def getSettingData(self):
+ setting_data = self.getSettingDataString()
+ if type(setting_data) == str:
+ setting_data = json.loads(setting_data, object_pairs_hook = collections.OrderedDict)
+ return setting_data
+
+ def getSettingDataString(self):
+ raise NotImplementedError()
+
+ def getDefinitionId(self):
+ if self._stack:
+ return self._stack.getBottom().getId()
+
+ def getStackId(self):
+ if self._stack:
+ return self._stack.getId()
+
+ ## Convenience function that retrieves value of a setting from the stack.
+ def getSettingValueByKey(self, key):
+ return self._stack.getProperty(key, "value")
+
+ ## Convenience function that finds the value in a line of g-code.
+ # When requesting key = x from line "G1 X100" the value 100 is returned.
+ def getValue(self, line, key, default = None):
+ if not key in line or (';' in line and line.find(key) > line.find(';')):
+ return default
+ sub_part = line[line.find(key) + 1:]
+ m = re.search('^-?[0-9]+\.?[0-9]*', sub_part)
+ if m is None:
+ return default
+ try:
+ return float(m.group(0))
+ except:
+ return default
+
+ ## This is called when the script is executed.
+ # It gets a list of g-code strings and needs to return a (modified) list.
+ def execute(self, data):
+ raise NotImplementedError()
diff --git a/plugins/PostProcessingPlugin/__init__.py b/plugins/PostProcessingPlugin/__init__.py
new file mode 100644
index 0000000000..85f1126136
--- /dev/null
+++ b/plugins/PostProcessingPlugin/__init__.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+from . import PostProcessingPlugin
+from UM.i18n import i18nCatalog
+catalog = i18nCatalog("cura")
+def getMetaData():
+ return {}
+
+def register(app):
+ return {"extension": PostProcessingPlugin.PostProcessingPlugin()}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/plugin.json b/plugins/PostProcessingPlugin/plugin.json
new file mode 100644
index 0000000000..ebfef8145a
--- /dev/null
+++ b/plugins/PostProcessingPlugin/plugin.json
@@ -0,0 +1,8 @@
+{
+ "name": "Post Processing",
+ "author": "Ultimaker",
+ "version": "2.2",
+ "api": 4,
+ "description": "Extension that allows for user created scripts for post processing",
+ "catalog": "cura"
+}
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/postprocessing.svg b/plugins/PostProcessingPlugin/postprocessing.svg
new file mode 100644
index 0000000000..f55face4a9
--- /dev/null
+++ b/plugins/PostProcessingPlugin/postprocessing.svg
@@ -0,0 +1,47 @@
+
+
+
+
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
new file mode 100644
index 0000000000..fb59378206
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
@@ -0,0 +1,48 @@
+from ..Script import Script
+class BQ_PauseAtHeight(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Pause at height (BQ Printers)",
+ "key": "BQ_PauseAtHeight",
+ "metadata":{},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ x = 0.
+ y = 0.
+ current_z = 0.
+ pause_z = self.getSettingValueByKey("pause_height")
+ for layer in data:
+ lines = layer.split("\n")
+ for line in lines:
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ if current_z != None:
+ if current_z >= pause_z:
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += "; -- Pause at height (%.2f mm) --\n" % pause_z
+
+ # Insert Pause gcode
+ prepend_gcode += "M25 ; Pauses the print and waits for the user to resume it\n"
+
+ index = data.index(layer)
+ layer = prepend_gcode + layer
+ data[index] = layer # Override the data of this layer with the modified data
+ return data
+ break
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ColorChange.py b/plugins/PostProcessingPlugin/scripts/ColorChange.py
new file mode 100644
index 0000000000..8db45f4033
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ColorChange.py
@@ -0,0 +1,76 @@
+# This PostProcessing Plugin script is released
+# under the terms of the AGPLv3 or higher
+
+from ..Script import Script
+#from UM.Logger import Logger
+# from cura.Settings.ExtruderManager import ExtruderManager
+
+class ColorChange(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Color Change",
+ "key": "ColorChange",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "layer_number":
+ {
+ "label": "Layer",
+ "description": "At what layer should color change occur. This will be before the layer starts printing. Specify multiple color changes with a comma.",
+ "unit": "",
+ "type": "str",
+ "default_value": "1"
+ },
+
+ "initial_retract":
+ {
+ "label": "Initial Retraction",
+ "description": "Initial filament retraction distance",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 300.0
+ },
+ "later_retract":
+ {
+ "label": "Later Retraction Distance",
+ "description": "Later filament retraction distance for removal",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 30.0
+ }
+ }
+ }"""
+
+ def execute(self, data: list):
+
+ """data is a list. Each index contains a layer"""
+ layer_nums = self.getSettingValueByKey("layer_number")
+ initial_retract = self.getSettingValueByKey("initial_retract")
+ later_retract = self.getSettingValueByKey("later_retract")
+
+ color_change = "M600"
+
+ if initial_retract is not None and initial_retract > 0.:
+ 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 + " ; Generated by ColorChange plugin"
+
+ layer_targets = layer_nums.split(',')
+ if len(layer_targets) > 0:
+ for layer_num in layer_targets:
+ layer_num = int( layer_num.strip() )
+ if layer_num < len(data):
+ layer = data[ layer_num - 1 ]
+ lines = layer.split("\n")
+ lines.insert(2, color_change )
+ final_line = "\n".join( lines )
+ data[ layer_num - 1 ] = final_line
+
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/ExampleScript.py b/plugins/PostProcessingPlugin/scripts/ExampleScript.py
new file mode 100644
index 0000000000..416a5f5404
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/ExampleScript.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2015 Jaime van Kessel, Ultimaker B.V.
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+from ..Script import Script
+
+class ExampleScript(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Example script",
+ "key": "ExampleScript",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "test":
+ {
+ "label": "Test",
+ "description": "None",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.5,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "1"
+ },
+ "derp":
+ {
+ "label": "zomg",
+ "description": "afgasgfgasfgasf",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.5,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "1"
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ return data
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
new file mode 100644
index 0000000000..925a5a7ac5
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
@@ -0,0 +1,221 @@
+from ..Script import Script
+# from cura.Settings.ExtruderManager import ExtruderManager
+
+class PauseAtHeight(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Pause at height",
+ "key": "PauseAtHeight",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause Height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_park_x":
+ {
+ "label": "Park Print Head X",
+ "description": "What X location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 190
+ },
+ "head_park_y":
+ {
+ "label": "Park Print Head Y",
+ "description": "What Y location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 190
+ },
+ "retraction_amount":
+ {
+ "label": "Retraction",
+ "description": "How much filament must be retracted at pause.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
+ },
+ "retraction_speed":
+ {
+ "label": "Retraction Speed",
+ "description": "How fast to retract the filament.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 25
+ },
+ "extrude_amount":
+ {
+ "label": "Extrude Amount",
+ "description": "How much filament should be extruded after pause. This is needed when doing a material change on Ultimaker2's to compensate for the retraction after the change. In that case 128+ is recommended.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0
+ },
+ "extrude_speed":
+ {
+ "label": "Extrude Speed",
+ "description": "How fast to extrude the material after pause.",
+ "unit": "mm/s",
+ "type": "float",
+ "default_value": 3.3333
+ },
+ "redo_layers":
+ {
+ "label": "Redo Layers",
+ "description": "Redo a number of previous layers after a pause to increases adhesion.",
+ "unit": "layers",
+ "type": "int",
+ "default_value": 0
+ },
+ "standby_temperature":
+ {
+ "label": "Standby Temperature",
+ "description": "Change the temperature during the pause",
+ "unit": "°C",
+ "type": "int",
+ "default_value": 0
+ },
+ "resume_temperature":
+ {
+ "label": "Resume Temperature",
+ "description": "Change the temperature after the pause",
+ "unit": "°C",
+ "type": "int",
+ "default_value": 0
+ }
+ }
+ }"""
+
+ def execute(self, data: list):
+
+ """data is a list. Each index contains a layer"""
+
+ x = 0.
+ y = 0.
+ current_z = 0.
+ pause_height = self.getSettingValueByKey("pause_height")
+ retraction_amount = self.getSettingValueByKey("retraction_amount")
+ retraction_speed = self.getSettingValueByKey("retraction_speed")
+ extrude_amount = self.getSettingValueByKey("extrude_amount")
+ extrude_speed = self.getSettingValueByKey("extrude_speed")
+ park_x = self.getSettingValueByKey("head_park_x")
+ park_y = self.getSettingValueByKey("head_park_y")
+ layers_started = False
+ redo_layers = self.getSettingValueByKey("redo_layers")
+ standby_temperature = self.getSettingValueByKey("standby_temperature")
+ resume_temperature = self.getSettingValueByKey("resume_temperature")
+
+ # T = ExtruderManager.getInstance().getActiveExtruderStack().getProperty("material_print_temperature", "value")
+ # with open("out.txt", "w") as f:
+ # f.write(T)
+
+ # use offset to calculate the current height: = -
+ layer_0_z = 0.
+ got_first_g_cmd_on_layer_0 = False
+ for layer in data:
+ lines = layer.split("\n")
+ for line in lines:
+ if ";LAYER:0" in line:
+ layers_started = True
+ continue
+
+ if not layers_started:
+ continue
+
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ if not got_first_g_cmd_on_layer_0:
+ layer_0_z = current_z
+ got_first_g_cmd_on_layer_0 = True
+
+ x = self.getValue(line, 'X', x)
+ y = self.getValue(line, 'Y', y)
+ if current_z is not None:
+ current_height = current_z - layer_0_z
+ if current_height >= pause_height:
+ index = data.index(layer)
+ prevLayer = data[index - 1]
+ prevLines = prevLayer.split("\n")
+ current_e = 0.
+ for prevLine in reversed(prevLines):
+ current_e = self.getValue(prevLine, 'E', -1)
+ if current_e >= 0:
+ break
+
+ # include a number of previous layers
+ for i in range(1, redo_layers + 1):
+ prevLayer = data[index - i]
+ layer = prevLayer + layer
+
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += ";added code by post processing\n"
+ prepend_gcode += ";script: PauseAtHeight.py\n"
+ prepend_gcode += ";current z: %f \n" % current_z
+ prepend_gcode += ";current height: %f \n" % current_height
+
+ # Retraction
+ prepend_gcode += "M83\n"
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F%f\n" % (retraction_amount, retraction_speed * 60)
+
+ # Move the head away
+ prepend_gcode += "G1 Z%f F300\n" % (current_z + 1)
+ prepend_gcode += "G1 X%f Y%f F9000\n" % (park_x, park_y)
+ if current_z < 15:
+ prepend_gcode += "G1 Z15 F300\n"
+
+ # Disable the E steppers
+ prepend_gcode += "M84 E0\n"
+
+ # Set extruder standby temperature
+ prepend_gcode += "M104 S%i; standby temperature\n" % (standby_temperature)
+
+ # Wait till the user continues printing
+ prepend_gcode += "M0 ;Do the actual pause\n"
+
+ # Set extruder resume temperature
+ prepend_gcode += "M109 S%i; resume temperature\n" % (resume_temperature)
+
+ # Push the filament back,
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E%f F%f\n" % (retraction_amount, retraction_speed * 60)
+
+ # Optionally extrude material
+ if extrude_amount != 0:
+ prepend_gcode += "G1 E%f F%f\n" % (extrude_amount, extrude_speed * 60)
+
+ # and retract again, the properly primes the nozzle
+ # when changing filament.
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F%f\n" % (retraction_amount, retraction_speed * 60)
+
+ # Move the head back
+ prepend_gcode += "G1 Z%f F300\n" % (current_z + 1)
+ prepend_gcode += "G1 X%f Y%f F9000\n" % (x, y)
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E%f F%f\n" % (retraction_amount, retraction_speed * 60)
+ prepend_gcode += "G1 F9000\n"
+ prepend_gcode += "M82\n"
+
+ # reset extrude value to pre pause value
+ prepend_gcode += "G92 E%f\n" % (current_e)
+
+ layer = prepend_gcode + layer
+
+
+ # Override the data of this layer with the
+ # modified data
+ data[index] = layer
+ return data
+ break
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py b/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
new file mode 100644
index 0000000000..710baab26a
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
@@ -0,0 +1,169 @@
+from ..Script import Script
+class PauseAtHeightforRepetier(Script):
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Pause at height for repetier",
+ "key": "PauseAtHeightforRepetier",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "pause_height":
+ {
+ "label": "Pause height",
+ "description": "At what height should the pause occur",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_park_x":
+ {
+ "label": "Park print head X",
+ "description": "What x location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_park_y":
+ {
+ "label": "Park print head Y",
+ "description": "What y location does the head move to when pausing.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "head_move_Z":
+ {
+ "label": "Head move Z",
+ "description": "The Hieght of Z-axis retraction before parking.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 15.0
+ },
+ "retraction_amount":
+ {
+ "label": "Retraction",
+ "description": "How much fillament must be retracted at pause.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0
+ },
+ "extrude_amount":
+ {
+ "label": "Extrude amount",
+ "description": "How much filament should be extruded after pause. This is needed when doing a material change on Ultimaker2's to compensate for the retraction after the change. In that case 128+ is recommended.",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 90.0
+ },
+ "redo_layers":
+ {
+ "label": "Redo layers",
+ "description": "Redo a number of previous layers after a pause to increases adhesion.",
+ "unit": "layers",
+ "type": "int",
+ "default_value": 0
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ x = 0.
+ y = 0.
+ current_z = 0.
+ pause_z = self.getSettingValueByKey("pause_height")
+ retraction_amount = self.getSettingValueByKey("retraction_amount")
+ extrude_amount = self.getSettingValueByKey("extrude_amount")
+ park_x = self.getSettingValueByKey("head_park_x")
+ park_y = self.getSettingValueByKey("head_park_y")
+ move_Z = self.getSettingValueByKey("head_move_Z")
+ layers_started = False
+ redo_layers = self.getSettingValueByKey("redo_layers")
+ for layer in data:
+ lines = layer.split("\n")
+ for line in lines:
+ if ";LAYER:0" in line:
+ layers_started = True
+ continue
+
+ if not layers_started:
+ continue
+
+ if self.getValue(line, 'G') == 1 or self.getValue(line, 'G') == 0:
+ current_z = self.getValue(line, 'Z')
+ x = self.getValue(line, 'X', x)
+ y = self.getValue(line, 'Y', y)
+ if current_z != None:
+ if current_z >= pause_z:
+
+ index = data.index(layer)
+ prevLayer = data[index-1]
+ prevLines = prevLayer.split("\n")
+ current_e = 0.
+ for prevLine in reversed(prevLines):
+ current_e = self.getValue(prevLine, 'E', -1)
+ if current_e >= 0:
+ break
+
+ prepend_gcode = ";TYPE:CUSTOM\n"
+ prepend_gcode += ";added code by post processing\n"
+ prepend_gcode += ";script: PauseAtHeightforRepetier.py\n"
+ prepend_gcode += ";current z: %f \n" % (current_z)
+ prepend_gcode += ";current X: %f \n" % (x)
+ prepend_gcode += ";current Y: %f \n" % (y)
+
+ #Retraction
+ prepend_gcode += "M83\n"
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F6000\n" % (retraction_amount)
+
+ #Move the head away
+ prepend_gcode += "G1 Z%f F300\n" % (1 + current_z)
+ prepend_gcode += "G1 X%f Y%f F9000\n" % (park_x, park_y)
+ if current_z < move_Z:
+ prepend_gcode += "G1 Z%f F300\n" % (current_z + move_Z)
+
+ #Disable the E steppers
+ prepend_gcode += "M84 E0\n"
+ #Wait till the user continues printing
+ prepend_gcode += "@pause now change filament and press continue printing ;Do the actual pause\n"
+
+ #Push the filament back,
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E%f F6000\n" % (retraction_amount)
+
+ # Optionally extrude material
+ if extrude_amount != 0:
+ prepend_gcode += "G1 E%f F200\n" % (extrude_amount)
+ prepend_gcode += "@info wait for cleaning nozzle from previous filament\n"
+ prepend_gcode += "@pause remove the waste filament from parking area and press continue printing\n"
+
+ # and retract again, the properly primes the nozzle when changing filament.
+ if retraction_amount != 0:
+ prepend_gcode += "G1 E-%f F6000\n" % (retraction_amount)
+
+ #Move the head back
+ prepend_gcode += "G1 Z%f F300\n" % (1 + current_z)
+ prepend_gcode +="G1 X%f Y%f F9000\n" % (x, y)
+ if retraction_amount != 0:
+ prepend_gcode +="G1 E%f F6000\n" % (retraction_amount)
+ prepend_gcode +="G1 F9000\n"
+ prepend_gcode +="M82\n"
+
+ # reset extrude value to pre pause value
+ prepend_gcode +="G92 E%f\n" % (current_e)
+
+ layer = prepend_gcode + layer
+
+ # include a number of previous layers
+ for i in range(1, redo_layers + 1):
+ prevLayer = data[index-i]
+ layer = prevLayer + layer
+
+ data[index] = layer #Override the data of this layer with the modified data
+ return data
+ break
+ return data
diff --git a/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py b/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
new file mode 100644
index 0000000000..68d697e470
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
@@ -0,0 +1,56 @@
+# Copyright (c) 2017 Ruben Dulek
+# The PostProcessingPlugin is released under the terms of the AGPLv3 or higher.
+
+import re #To perform the search and replace.
+
+from ..Script import Script
+
+## Performs a search-and-replace on all g-code.
+#
+# Due to technical limitations, the search can't cross the border between
+# layers.
+class SearchAndReplace(Script):
+ def getSettingDataString(self):
+ return """{
+ "name": "Search and Replace",
+ "key": "SearchAndReplace",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "search":
+ {
+ "label": "Search",
+ "description": "All occurrences of this text will get replaced by the replacement text.",
+ "type": "str",
+ "default_value": ""
+ },
+ "replace":
+ {
+ "label": "Replace",
+ "description": "The search text will get replaced by this text.",
+ "type": "str",
+ "default_value": ""
+ },
+ "is_regex":
+ {
+ "label": "Use Regular Expressions",
+ "description": "When enabled, the search text will be interpreted as a regular expression.",
+ "type": "bool",
+ "default_value": false
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ search_string = self.getSettingValueByKey("search")
+ if not self.getSettingValueByKey("is_regex"):
+ search_string = re.escape(search_string) #Need to search for the actual string, not as a regex.
+ search_regex = re.compile(search_string)
+
+ replace_string = self.getSettingValueByKey("replace")
+
+ for layer_number, layer in enumerate(data):
+ data[layer_number] = re.sub(search_regex, replace_string, layer) #Replace all.
+
+ return data
\ No newline at end of file
diff --git a/plugins/PostProcessingPlugin/scripts/Stretch.py b/plugins/PostProcessingPlugin/scripts/Stretch.py
new file mode 100644
index 0000000000..bcb923d3ff
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/Stretch.py
@@ -0,0 +1,469 @@
+# This PostProcessingPlugin script is released under the terms of the AGPLv3 or higher.
+"""
+Copyright (c) 2017 Christophe Baribaud 2017
+Python implementation of https://github.com/electrocbd/post_stretch
+Correction of hole sizes, cylinder diameters and curves
+See the original description in https://github.com/electrocbd/post_stretch
+
+WARNING This script has never been tested with several extruders
+"""
+from ..Script import Script
+import numpy as np
+from UM.Logger import Logger
+from UM.Application import Application
+import re
+
+def _getValue(line, key, default=None):
+ """
+ Convenience function that finds the value in a line of g-code.
+ When requesting key = x from line "G1 X100" the value 100 is returned.
+ It is a copy of Stript's method, so it is no DontRepeatYourself, but
+ I split the class into setup part (Stretch) and execution part (Strecher)
+ and only the setup part inherits from Script
+ """
+ if not key in line or (";" in line and line.find(key) > line.find(";")):
+ return default
+ sub_part = line[line.find(key) + 1:]
+ number = re.search(r"^-?[0-9]+\.?[0-9]*", sub_part)
+ if number is None:
+ return default
+ return float(number.group(0))
+
+class GCodeStep():
+ """
+ Class to store the current value of each G_Code parameter
+ for any G-Code step
+ """
+ def __init__(self, step):
+ self.step = step
+ self.step_x = 0
+ self.step_y = 0
+ self.step_z = 0
+ self.step_e = 0
+ self.step_f = 0
+ 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
+
+ def copyPosFrom(self, step):
+ """
+ Copies positions of step into self
+ """
+ self.step_x = step.step_x
+ self.step_y = step.step_y
+ self.step_z = step.step_z
+ self.step_e = step.step_e
+ self.step_f = step.step_f
+ self.comment = step.comment
+ return
+
+
+# Execution part of the stretch plugin
+class Stretcher():
+ """
+ Execution part of the stretch algorithm
+ """
+ def __init__(self, line_width, wc_stretch, pw_stretch):
+ self.line_width = line_width
+ self.wc_stretch = wc_stretch
+ self.pw_stretch = pw_stretch
+ if self.pw_stretch > line_width / 4:
+ self.pw_stretch = line_width / 4 # Limit value of pushwall stretch distance
+ self.outpos = GCodeStep(0)
+ self.vd1 = np.empty((0, 2)) # Start points of segments
+ # of already deposited material for current layer
+ self.vd2 = np.empty((0, 2)) # End points of segments
+ # of already deposited material for current layer
+ self.layer_z = 0 # Z position of the extrusion moves of the current layer
+ self.layergcode = ""
+
+ def execute(self, data):
+ """
+ Computes the new X and Y coordinates of all g-code steps
+ """
+ Logger.log("d", "Post stretch with line width = " + str(self.line_width)
+ + "mm wide circle stretch = " + str(self.wc_stretch)+ "mm"
+ + "and push wall stretch = " + str(self.pw_stretch) + "mm")
+ retdata = []
+ layer_steps = []
+ current = GCodeStep(0)
+ self.layer_z = 0.
+ current_e = 0.
+ for layer in data:
+ lines = layer.rstrip("\n").split("\n")
+ for line in lines:
+ current.comment = ""
+ if line.find(";") >= 0:
+ current.comment = line[line.find(";"):]
+ if _getValue(line, "G") == 0:
+ current.readStep(line)
+ onestep = GCodeStep(0)
+ onestep.copyPosFrom(current)
+ elif _getValue(line, "G") == 1:
+ current.readStep(line)
+ onestep = GCodeStep(1)
+ onestep.copyPosFrom(current)
+ elif _getValue(line, "G") == 92:
+ current.readStep(line)
+ onestep = GCodeStep(-1)
+ onestep.copyPosFrom(current)
+ else:
+ onestep = GCodeStep(-1)
+ 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)
+ + " " + str(len(layer_steps)) + " steps")
+ retdata.append(self.processLayer(layer_steps))
+ layer_steps = []
+ layer_steps.append(onestep)
+ # self.layer_z is the z position of the last extrusion move (not travel move)
+ if current.step_z != self.layer_z and current.step_e != current_e:
+ self.layer_z = current.step_z
+ current_e = current.step_e
+ if len(layer_steps): # Force a new item in the array
+ Logger.log("d", "Layer Z " + "{:.3f}".format(self.layer_z)
+ + " " + str(len(layer_steps)) + " steps")
+ retdata.append(self.processLayer(layer_steps))
+ layer_steps = []
+ retdata.append(";Wide circle stretch distance " + str(self.wc_stretch) + "\n")
+ retdata.append(";Push wall stretch distance " + str(self.pw_stretch) + "\n")
+ return retdata
+
+ def extrusionBreak(self, layer_steps, i_pos):
+ """
+ Returns true if the command layer_steps[i_pos] breaks the extruded filament
+ i.e. it is a travel move
+ """
+ if i_pos == 0:
+ return True # Begining a layer always breaks filament (for simplicity)
+ step = layer_steps[i_pos]
+ prev_step = layer_steps[i_pos - 1]
+ if step.step_e != prev_step.step_e:
+ return False
+ delta_x = step.step_x - prev_step.step_x
+ delta_y = step.step_y - prev_step.step_y
+ if delta_x * delta_x + delta_y * delta_y < self.line_width * self.line_width / 4:
+ # This is a very short movement, less than 0.5 * line_width
+ # It does not break filament, we should stay in the same extrusion sequence
+ return False
+ return True # New sequence
+
+
+ def processLayer(self, layer_steps):
+ """
+ Computes the new coordinates of g-code steps
+ for one layer (all the steps at the same Z coordinate)
+ """
+ self.outpos.step_x = -1000 # Force output of X and Y coordinates
+ self.outpos.step_y = -1000 # at each start of layer
+ self.layergcode = ""
+ self.vd1 = np.empty((0, 2))
+ self.vd2 = np.empty((0, 2))
+ orig_seq = np.empty((0, 2))
+ modif_seq = np.empty((0, 2))
+ iflush = 0
+ for i, step in enumerate(layer_steps):
+ if step.step == 0 or step.step == 1:
+ if self.extrusionBreak(layer_steps, i):
+ # No extrusion since the previous step, so it is a travel move
+ # Let process steps accumulated into orig_seq,
+ # which are a sequence of continuous extrusion
+ modif_seq = np.copy(orig_seq)
+ if len(orig_seq) >= 2:
+ self.workOnSequence(orig_seq, modif_seq)
+ self.generate(layer_steps, iflush, i, modif_seq)
+ iflush = i
+ orig_seq = np.empty((0, 2))
+ orig_seq = np.concatenate([orig_seq, np.array([[step.step_x, step.step_y]])])
+ if len(orig_seq):
+ modif_seq = np.copy(orig_seq)
+ if len(orig_seq) >= 2:
+ self.workOnSequence(orig_seq, modif_seq)
+ self.generate(layer_steps, iflush, len(layer_steps), modif_seq)
+ return self.layergcode
+
+ def stepToGcode(self, onestep):
+ """
+ Converts a step into G-Code
+ For each of the X, Y, Z, E and F parameter,
+ the parameter is written only if its value changed since the
+ previous g-code step.
+ """
+ sout = ""
+ if onestep.step_f != self.outpos.step_f:
+ self.outpos.step_f = onestep.step_f
+ sout += " F{:.0f}".format(self.outpos.step_f).rstrip(".")
+ if onestep.step_x != self.outpos.step_x or onestep.step_y != self.outpos.step_y:
+ assert onestep.step_x >= -1000 and onestep.step_x < 1000 # If this assertion fails,
+ # something went really wrong !
+ self.outpos.step_x = onestep.step_x
+ sout += " X{:.3f}".format(self.outpos.step_x).rstrip("0").rstrip(".")
+ assert onestep.step_y >= -1000 and onestep.step_y < 1000 # If this assertion fails,
+ # something went really wrong !
+ self.outpos.step_y = onestep.step_y
+ sout += " Y{:.3f}".format(self.outpos.step_y).rstrip("0").rstrip(".")
+ if onestep.step_z != self.outpos.step_z or onestep.step_z != self.layer_z:
+ self.outpos.step_z = onestep.step_z
+ sout += " Z{:.3f}".format(self.outpos.step_z).rstrip("0").rstrip(".")
+ if onestep.step_e != self.outpos.step_e:
+ self.outpos.step_e = onestep.step_e
+ sout += " E{:.5f}".format(self.outpos.step_e).rstrip("0").rstrip(".")
+ return sout
+
+ def generate(self, layer_steps, ibeg, iend, orig_seq):
+ """
+ Appends g-code lines to the plugin's returned string
+ starting from step ibeg included and until step iend excluded
+ """
+ ipos = 0
+ for i in range(ibeg, iend):
+ if layer_steps[i].step == 0:
+ layer_steps[i].step_x = orig_seq[ipos][0]
+ layer_steps[i].step_y = orig_seq[ipos][1]
+ sout = "G0" + self.stepToGcode(layer_steps[i])
+ self.layergcode = self.layergcode + sout + "\n"
+ ipos = ipos + 1
+ elif layer_steps[i].step == 1:
+ layer_steps[i].step_x = orig_seq[ipos][0]
+ layer_steps[i].step_y = orig_seq[ipos][1]
+ sout = "G1" + self.stepToGcode(layer_steps[i])
+ self.layergcode = self.layergcode + sout + "\n"
+ ipos = ipos + 1
+ else:
+ self.layergcode = self.layergcode + layer_steps[i].comment + "\n"
+
+
+ def workOnSequence(self, orig_seq, modif_seq):
+ """
+ Computes new coordinates for a sequence
+ A sequence is a list of consecutive g-code steps
+ of continuous material extrusion
+ """
+ d_contact = self.line_width / 2.0
+ if (len(orig_seq) > 2 and
+ ((orig_seq[len(orig_seq) - 1] - orig_seq[0]) ** 2).sum(0) < d_contact * d_contact):
+ # Starting and ending point of the sequence are nearby
+ # It is a closed loop
+ #self.layergcode = self.layergcode + ";wideCircle\n"
+ self.wideCircle(orig_seq, modif_seq)
+ else:
+ #self.layergcode = self.layergcode + ";wideTurn\n"
+ self.wideTurn(orig_seq, modif_seq) # It is an open curve
+ if len(orig_seq) > 6: # Don't try push wall on a short sequence
+ self.pushWall(orig_seq, modif_seq)
+ if len(orig_seq):
+ self.vd1 = np.concatenate([self.vd1, np.array(orig_seq[:-1])])
+ self.vd2 = np.concatenate([self.vd2, np.array(orig_seq[1:])])
+
+ def wideCircle(self, orig_seq, modif_seq):
+ """
+ Similar to wideTurn
+ The first and last point of the sequence are the same,
+ so it is possible to extend the end of the sequence
+ with its beginning when seeking for triangles
+
+ It is necessary to find the direction of the curve, knowing three points (a triangle)
+ If the triangle is not wide enough, there is a huge risk of finding
+ an incorrect orientation, due to insufficient accuracy.
+ So, when the consecutive points are too close, the method
+ use following and preceding points to form a wider triangle around
+ the current point
+ dmin_tri is the minimum distance between two consecutive points
+ of an acceptable triangle
+ """
+ dmin_tri = self.line_width / 2.0
+ iextra_base = np.floor_divide(len(orig_seq), 3) # Nb of extra points
+ ibeg = 0 # Index of first point of the triangle
+ iend = 0 # Index of the third point of the triangle
+ for i, step in enumerate(orig_seq):
+ if i == 0 or i == len(orig_seq) - 1:
+ # First and last point of the sequence are the same,
+ # so it is necessary to skip one of these two points
+ # when creating a triangle containing the first or the last point
+ iextra = iextra_base + 1
+ else:
+ iextra = iextra_base
+ # i is the index of the second point of the triangle
+ # pos_after is the array of positions of the original sequence
+ # after the current point
+ pos_after = np.resize(np.roll(orig_seq, -i-1, 0), (iextra, 2))
+ # Vector of distances between the current point and each following point
+ dist_from_point = ((step - pos_after) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ iend = np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ # pos_before is the array of positions of the original sequence
+ # before the current point
+ pos_before = np.resize(np.roll(orig_seq, -i, 0)[::-1], (iextra, 2))
+ # This time, vector of distances between the current point and each preceding point
+ dist_from_point = ((step - pos_before) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ ibeg = np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ # See https://github.com/electrocbd/post_stretch for explanations
+ # relpos is the relative position of the projection of the second point
+ # of the triangle on the segment from the first to the third point
+ # 0 means the position of the first point, 1 means the position of the third,
+ # intermediate values are positions between
+ length_base = ((pos_after[iend] - pos_before[ibeg]) ** 2).sum(0)
+ relpos = ((step - pos_before[ibeg])
+ * (pos_after[iend] - pos_before[ibeg])).sum(0)
+ if np.fabs(relpos) < 1000.0 * np.fabs(length_base):
+ relpos /= length_base
+ else:
+ relpos = 0.5 # To avoid division by zero or precision loss
+ projection = (pos_before[ibeg] + relpos * (pos_after[iend] - pos_before[ibeg]))
+ dist_from_proj = np.sqrt(((projection - step) ** 2).sum(0))
+ if dist_from_proj > 0.001: # Move central point only if points are not aligned
+ modif_seq[i] = (step - (self.wc_stretch / dist_from_proj)
+ * (projection - step))
+ return
+
+ def wideTurn(self, orig_seq, modif_seq):
+ '''
+ We have to select three points in order to form a triangle
+ These three points should be far enough from each other to have
+ a reliable estimation of the orientation of the current turn
+ '''
+ dmin_tri = self.line_width / 2.0
+ ibeg = 0
+ iend = 2
+ for i in range(1, len(orig_seq) - 1):
+ dist_from_point = ((orig_seq[i] - orig_seq[i+1:]) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ iend = i + 1 + np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ dist_from_point = ((orig_seq[i] - orig_seq[i-1::-1]) ** 2).sum(1)
+ if np.amax(dist_from_point) < dmin_tri * dmin_tri:
+ continue
+ ibeg = i - 1 - np.argmax(dist_from_point >= dmin_tri * dmin_tri)
+ length_base = ((orig_seq[iend] - orig_seq[ibeg]) ** 2).sum(0)
+ relpos = ((orig_seq[i] - orig_seq[ibeg]) * (orig_seq[iend] - orig_seq[ibeg])).sum(0)
+ if np.fabs(relpos) < 1000.0 * np.fabs(length_base):
+ relpos /= length_base
+ else:
+ relpos = 0.5
+ projection = orig_seq[ibeg] + relpos * (orig_seq[iend] - orig_seq[ibeg])
+ dist_from_proj = np.sqrt(((projection - orig_seq[i]) ** 2).sum(0))
+ if dist_from_proj > 0.001:
+ modif_seq[i] = (orig_seq[i] - (self.wc_stretch / dist_from_proj)
+ * (projection - orig_seq[i]))
+ return
+
+ def pushWall(self, orig_seq, modif_seq):
+ """
+ The algorithm tests for each segment if material was
+ already deposited at one or the other side of this segment.
+ If material was deposited at one side but not both,
+ the segment is moved into the direction of the deposited material,
+ to "push the wall"
+
+ Already deposited material is stored as segments.
+ vd1 is the array of the starting points of the segments
+ vd2 is the array of the ending points of the segments
+ For example, segment nr 8 starts at position self.vd1[8]
+ and ends at position self.vd2[8]
+ """
+ dist_palp = self.line_width # Palpation distance to seek for a wall
+ mrot = np.array([[0, -1], [1, 0]]) # Rotation matrix for a quarter turn
+ for i in range(len(orig_seq)):
+ ibeg = i # Index of the first point of the segment
+ iend = i + 1 # Index of the last point of the segment
+ if iend == len(orig_seq):
+ iend = i - 1
+ xperp = np.dot(mrot, orig_seq[iend] - orig_seq[ibeg])
+ xperp = xperp / np.sqrt((xperp ** 2).sum(-1))
+ testleft = orig_seq[ibeg] + xperp * dist_palp
+ materialleft = False # Is there already extruded material at the left of the segment
+ testright = orig_seq[ibeg] - xperp * dist_palp
+ materialright = False # Is there already extruded material at the right of the segment
+ if self.vd1.shape[0]:
+ relpos = np.clip(((testleft - self.vd1) * (self.vd2 - self.vd1)).sum(1)
+ / ((self.vd2 - self.vd1) * (self.vd2 - self.vd1)).sum(1), 0., 1.)
+ nearpoints = self.vd1 + relpos[:, np.newaxis] * (self.vd2 - self.vd1)
+ # nearpoints is the array of the nearest points of each segment
+ # from the point testleft
+ dist = ((testleft - nearpoints) * (testleft - nearpoints)).sum(1)
+ # dist is the array of the squares of the distances between testleft
+ # and each segment
+ if np.amin(dist) <= dist_palp * dist_palp:
+ materialleft = True
+ # Now the same computation with the point testright at the other side of the
+ # current segment
+ relpos = np.clip(((testright - self.vd1) * (self.vd2 - self.vd1)).sum(1)
+ / ((self.vd2 - self.vd1) * (self.vd2 - self.vd1)).sum(1), 0., 1.)
+ nearpoints = self.vd1 + relpos[:, np.newaxis] * (self.vd2 - self.vd1)
+ dist = ((testright - nearpoints) * (testright - nearpoints)).sum(1)
+ if np.amin(dist) <= dist_palp * dist_palp:
+ materialright = True
+ if materialleft and not materialright:
+ modif_seq[ibeg] = modif_seq[ibeg] + xperp * self.pw_stretch
+ elif not materialleft and materialright:
+ modif_seq[ibeg] = modif_seq[ibeg] - xperp * self.pw_stretch
+ if materialleft and materialright:
+ modif_seq[ibeg] = orig_seq[ibeg] # Surrounded by walls, don't move
+
+# Setup part of the stretch plugin
+class Stretch(Script):
+ """
+ Setup part of the stretch algorithm
+ The only parameter is the stretch distance
+ """
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"Post stretch script",
+ "key": "Stretch",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "wc_stretch":
+ {
+ "label": "Wide circle stretch distance",
+ "description": "Distance by which the points are moved by the correction effect in corners. The higher this value, the higher the effect",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.08,
+ "minimum_value": 0,
+ "minimum_value_warning": 0,
+ "maximum_value_warning": 0.2
+ },
+ "pw_stretch":
+ {
+ "label": "Push Wall stretch distance",
+ "description": "Distance by which the points are moved by the correction effect when two lines are nearby. The higher this value, the higher the effect",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 0.08,
+ "minimum_value": 0,
+ "minimum_value_warning": 0,
+ "maximum_value_warning": 0.2
+ }
+ }
+ }"""
+
+ def execute(self, data):
+ """
+ Entry point of the plugin.
+ data is the list of original g-code instructions,
+ the returned string is the list of modified g-code instructions
+ """
+ stretcher = Stretcher(
+ Application.getInstance().getGlobalContainerStack().getProperty("line_width", "value")
+ , self.getSettingValueByKey("wc_stretch"), self.getSettingValueByKey("pw_stretch"))
+ return stretcher.execute(data)
+
diff --git a/plugins/PostProcessingPlugin/scripts/TweakAtZ.py b/plugins/PostProcessingPlugin/scripts/TweakAtZ.py
new file mode 100644
index 0000000000..7b714f6ee0
--- /dev/null
+++ b/plugins/PostProcessingPlugin/scripts/TweakAtZ.py
@@ -0,0 +1,495 @@
+# TweakAtZ script - Change printing parameters at a given height
+# This script is the successor of the TweakAtZ plugin for legacy Cura.
+# It contains code from the TweakAtZ plugin V1.0-V4.x and from the ExampleScript by Jaime van Kessel, Ultimaker B.V.
+# It runs with the PostProcessingPlugin which is released under the terms of the AGPLv3 or higher.
+# This script is licensed under the Creative Commons - Attribution - Share Alike (CC BY-SA) terms
+
+#Authors of the TweakAtZ plugin / script:
+# Written by Steven Morlock, smorloc@gmail.com
+# Modified by Ricardo Gomez, ricardoga@otulook.com, to add Bed Temperature and make it work with Cura_13.06.04+
+# Modified by Stefan Heule, Dim3nsioneer@gmx.ch since V3.0 (see changelog below)
+# Modified by Jaime van Kessel (Ultimaker), j.vankessel@ultimaker.com to make it work for 15.10 / 2.x
+# Modified by Ruben Dulek (Ultimaker), r.dulek@ultimaker.com, to debug.
+
+##history / changelog:
+##V3.0.1: TweakAtZ-state default 1 (i.e. the plugin works without any TweakAtZ comment)
+##V3.1: Recognizes UltiGCode and deactivates value reset, fan speed added, alternatively layer no. to tweak at,
+## extruder three temperature disabled by "#Ex3"
+##V3.1.1: Bugfix reset flow rate
+##V3.1.2: Bugfix disable TweakAtZ on Cool Head Lift
+##V3.2: Flow rate for specific extruder added (only for 2 extruders), bugfix parser,
+## added speed reset at the end of the print
+##V4.0: Progress bar, tweaking over multiple layers, M605&M606 implemented, reset after one layer option,
+## extruder three code removed, tweaking print speed, save call of Publisher class,
+## uses previous value from other plugins also on UltiGCode
+##V4.0.1: Bugfix for doubled G1 commands
+##V4.0.2: uses Cura progress bar instead of its own
+##V4.0.3: Bugfix for cool head lift (contributed by luisonoff)
+##V4.9.91: First version for Cura 15.06.x and PostProcessingPlugin
+##V4.9.92: Modifications for Cura 15.10
+##V4.9.93: Minor bugfixes (input settings) / documentation
+##V4.9.94: Bugfix Combobox-selection; remove logger
+##V5.0: Bugfix for fall back after one layer and doubled G0 commands when using print speed tweak, Initial version for Cura 2.x
+##V5.0.1: Bugfix for calling unknown property 'bedTemp' of previous settings storage and unkown variable 'speed'
+##V5.1: API Changes included for use with Cura 2.2
+
+## Uses -
+## M220 S - set speed factor override percentage
+## M221 S - set flow factor override percentage
+## M221 S T<0-#toolheads> - set flow factor override percentage for single extruder
+## M104 S T<0-#toolheads> - set extruder to target temperature
+## M140 S - set bed target temperature
+## M106 S - set fan speed to target speed
+## M605/606 to save and recall material settings on the UM2
+
+from ..Script import Script
+#from UM.Logger import Logger
+import re
+
+class TweakAtZ(Script):
+ version = "5.1.1"
+ def __init__(self):
+ super().__init__()
+
+ def getSettingDataString(self):
+ return """{
+ "name":"TweakAtZ """ + self.version + """ (Experimental)",
+ "key":"TweakAtZ",
+ "metadata": {},
+ "version": 2,
+ "settings":
+ {
+ "a_trigger":
+ {
+ "label": "Trigger",
+ "description": "Trigger at height or at layer no.",
+ "type": "enum",
+ "options": {"height":"Height","layer_no":"Layer No."},
+ "default_value": "height"
+ },
+ "b_targetZ":
+ {
+ "label": "Tweak Height",
+ "description": "Z height to tweak at",
+ "unit": "mm",
+ "type": "float",
+ "default_value": 5.0,
+ "minimum_value": "0",
+ "minimum_value_warning": "0.1",
+ "maximum_value_warning": "230",
+ "enabled": "a_trigger == 'height'"
+ },
+ "b_targetL":
+ {
+ "label": "Tweak Layer",
+ "description": "Layer no. to tweak at",
+ "unit": "",
+ "type": "int",
+ "default_value": 1,
+ "minimum_value": "-100",
+ "minimum_value_warning": "-1",
+ "enabled": "a_trigger == 'layer_no'"
+ },
+ "c_behavior":
+ {
+ "label": "Behavior",
+ "description": "Select behavior: Tweak value and keep it for the rest, Tweak value for single layer only",
+ "type": "enum",
+ "options": {"keep_value":"Keep value","single_layer":"Single Layer"},
+ "default_value": "keep_value"
+ },
+ "d_twLayers":
+ {
+ "label": "No. Layers",
+ "description": "No. of layers used to tweak",
+ "unit": "",
+ "type": "int",
+ "default_value": 1,
+ "minimum_value": "1",
+ "maximum_value_warning": "50",
+ "enabled": "c_behavior == 'keep_value'"
+ },
+ "e1_Tweak_speed":
+ {
+ "label": "Tweak Speed",
+ "description": "Select if total speed (print and travel) has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "e2_speed":
+ {
+ "label": "Speed",
+ "description": "New total speed (print and travel)",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "e1_Tweak_speed"
+ },
+ "f1_Tweak_printspeed":
+ {
+ "label": "Tweak Print Speed",
+ "description": "Select if print speed has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "f2_printspeed":
+ {
+ "label": "Print Speed",
+ "description": "New print speed",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "f1_Tweak_printspeed"
+ },
+ "g1_Tweak_flowrate":
+ {
+ "label": "Tweak Flow Rate",
+ "description": "Select if flow rate has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "g2_flowrate":
+ {
+ "label": "Flow Rate",
+ "description": "New Flow rate",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g1_Tweak_flowrate"
+ },
+ "g3_Tweak_flowrateOne":
+ {
+ "label": "Tweak Flow Rate 1",
+ "description": "Select if first extruder flow rate has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "g4_flowrateOne":
+ {
+ "label": "Flow Rate One",
+ "description": "New Flow rate Extruder 1",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g3_Tweak_flowrateOne"
+ },
+ "g5_Tweak_flowrateTwo":
+ {
+ "label": "Tweak Flow Rate 2",
+ "description": "Select if second extruder flow rate has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "g6_flowrateTwo":
+ {
+ "label": "Flow Rate two",
+ "description": "New Flow rate Extruder 2",
+ "unit": "%",
+ "type": "int",
+ "default_value": 100,
+ "minimum_value": "1",
+ "minimum_value_warning": "10",
+ "maximum_value_warning": "200",
+ "enabled": "g5_Tweak_flowrateTwo"
+ },
+ "h1_Tweak_bedTemp":
+ {
+ "label": "Tweak Bed Temp",
+ "description": "Select if Bed Temperature has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "h2_bedTemp":
+ {
+ "label": "Bed Temp",
+ "description": "New Bed Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 60,
+ "minimum_value": "0",
+ "minimum_value_warning": "30",
+ "maximum_value_warning": "120",
+ "enabled": "h1_Tweak_bedTemp"
+ },
+ "i1_Tweak_extruderOne":
+ {
+ "label": "Tweak Extruder 1 Temp",
+ "description": "Select if First Extruder Temperature has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "i2_extruderOne":
+ {
+ "label": "Extruder 1 Temp",
+ "description": "New First Extruder Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 190,
+ "minimum_value": "0",
+ "minimum_value_warning": "160",
+ "maximum_value_warning": "250",
+ "enabled": "i1_Tweak_extruderOne"
+ },
+ "i3_Tweak_extruderTwo":
+ {
+ "label": "Tweak Extruder 2 Temp",
+ "description": "Select if Second Extruder Temperature has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "i4_extruderTwo":
+ {
+ "label": "Extruder 2 Temp",
+ "description": "New Second Extruder Temperature",
+ "unit": "C",
+ "type": "float",
+ "default_value": 190,
+ "minimum_value": "0",
+ "minimum_value_warning": "160",
+ "maximum_value_warning": "250",
+ "enabled": "i3_Tweak_extruderTwo"
+ },
+ "j1_Tweak_fanSpeed":
+ {
+ "label": "Tweak Fan Speed",
+ "description": "Select if Fan Speed has to be tweaked",
+ "type": "bool",
+ "default_value": false
+ },
+ "j2_fanSpeed":
+ {
+ "label": "Fan Speed",
+ "description": "New Fan Speed (0-255)",
+ "unit": "PWM",
+ "type": "int",
+ "default_value": 255,
+ "minimum_value": "0",
+ "minimum_value_warning": "15",
+ "maximum_value_warning": "255",
+ "enabled": "j1_Tweak_fanSpeed"
+ }
+ }
+ }"""
+
+ def getValue(self, line, key, default = None): #replace default getvalue due to comment-reading feature
+ if not key in line or (";" in line and line.find(key) > line.find(";") and
+ not ";TweakAtZ" in key and not ";LAYER:" in key):
+ return default
+ subPart = line[line.find(key) + len(key):] #allows for string lengths larger than 1
+ if ";TweakAtZ" in key:
+ m = re.search("^[0-4]", subPart)
+ elif ";LAYER:" in key:
+ m = re.search("^[+-]?[0-9]*", subPart)
+ else:
+ #the minus at the beginning allows for negative values, e.g. for delta printers
+ m = re.search("^[-]?[0-9]*\.?[0-9]*", subPart)
+ if m == None:
+ return default
+ try:
+ return float(m.group(0))
+ except:
+ return default
+
+ def execute(self, data):
+ #Check which tweaks should apply
+ TweakProp = {"speed": self.getSettingValueByKey("e1_Tweak_speed"),
+ "flowrate": self.getSettingValueByKey("g1_Tweak_flowrate"),
+ "flowrateOne": self.getSettingValueByKey("g3_Tweak_flowrateOne"),
+ "flowrateTwo": self.getSettingValueByKey("g5_Tweak_flowrateTwo"),
+ "bedTemp": self.getSettingValueByKey("h1_Tweak_bedTemp"),
+ "extruderOne": self.getSettingValueByKey("i1_Tweak_extruderOne"),
+ "extruderTwo": self.getSettingValueByKey("i3_Tweak_extruderTwo"),
+ "fanSpeed": self.getSettingValueByKey("j1_Tweak_fanSpeed")}
+ TweakPrintSpeed = self.getSettingValueByKey("f1_Tweak_printspeed")
+ TweakStrings = {"speed": "M220 S%f\n",
+ "flowrate": "M221 S%f\n",
+ "flowrateOne": "M221 T0 S%f\n",
+ "flowrateTwo": "M221 T1 S%f\n",
+ "bedTemp": "M140 S%f\n",
+ "extruderOne": "M104 S%f T0\n",
+ "extruderTwo": "M104 S%f T1\n",
+ "fanSpeed": "M106 S%d\n"}
+ target_values = {"speed": self.getSettingValueByKey("e2_speed"),
+ "printspeed": self.getSettingValueByKey("f2_printspeed"),
+ "flowrate": self.getSettingValueByKey("g2_flowrate"),
+ "flowrateOne": self.getSettingValueByKey("g4_flowrateOne"),
+ "flowrateTwo": self.getSettingValueByKey("g6_flowrateTwo"),
+ "bedTemp": self.getSettingValueByKey("h2_bedTemp"),
+ "extruderOne": self.getSettingValueByKey("i2_extruderOne"),
+ "extruderTwo": self.getSettingValueByKey("i4_extruderTwo"),
+ "fanSpeed": self.getSettingValueByKey("j2_fanSpeed")}
+ old = {"speed": -1, "flowrate": -1, "flowrateOne": -1, "flowrateTwo": -1, "platformTemp": -1, "extruderOne": -1,
+ "extruderTwo": -1, "bedTemp": -1, "fanSpeed": -1, "state": -1}
+ twLayers = self.getSettingValueByKey("d_twLayers")
+ if self.getSettingValueByKey("c_behavior") == "single_layer":
+ behavior = 1
+ else:
+ behavior = 0
+ try:
+ twLayers = max(int(twLayers),1) #for the case someone entered something as "funny" as -1
+ except:
+ twLayers = 1
+ pres_ext = 0
+ done_layers = 0
+ z = 0
+ x = None
+ y = None
+ layer = -100000 #layer no. may be negative (raft) but never that low
+ # state 0: deactivated, state 1: activated, state 2: active, but below z,
+ # state 3: active and partially executed (multi layer), state 4: active and passed z
+ state = 1
+ # IsUM2: Used for reset of values (ok for Marlin/Sprinter),
+ # has to be set to 1 for UltiGCode (work-around for missing default values)
+ IsUM2 = False
+ oldValueUnknown = False
+ TWinstances = 0
+
+ if self.getSettingValueByKey("a_trigger") == "layer_no":
+ targetL_i = int(self.getSettingValueByKey("b_targetL"))
+ targetZ = 100000
+ else:
+ targetL_i = -100000
+ targetZ = self.getSettingValueByKey("b_targetZ")
+ index = 0
+ for active_layer in data:
+ modified_gcode = ""
+ lines = active_layer.split("\n")
+ for line in lines:
+ if ";Generated with Cura_SteamEngine" in line:
+ TWinstances += 1
+ modified_gcode += ";TweakAtZ instances: %d\n" % TWinstances
+ if not ("M84" in line or "M25" in line or ("G1" in line and TweakPrintSpeed and (state==3 or state==4)) or
+ ";TweakAtZ instances:" in line):
+ modified_gcode += line + "\n"
+ IsUM2 = ("FLAVOR:UltiGCode" in line) or IsUM2 #Flavor is UltiGCode!
+ if ";TweakAtZ-state" in line: #checks for state change comment
+ state = self.getValue(line, ";TweakAtZ-state", state)
+ if ";TweakAtZ instances:" in line:
+ try:
+ tempTWi = int(line[20:])
+ except:
+ tempTWi = TWinstances
+ TWinstances = tempTWi
+ if ";Small layer" in line: #checks for begin of Cool Head Lift
+ old["state"] = state
+ state = 0
+ if ";LAYER:" in line: #new layer no. found
+ if state == 0:
+ state = old["state"]
+ layer = self.getValue(line, ";LAYER:", layer)
+ if targetL_i > -100000: #target selected by layer no.
+ if (state == 2 or targetL_i == 0) and layer == targetL_i: #determine targetZ from layer no.; checks for tweak on layer 0
+ state = 2
+ targetZ = z + 0.001
+ if (self.getValue(line, "T", None) is not None) and (self.getValue(line, "M", None) is None): #looking for single T-cmd
+ pres_ext = self.getValue(line, "T", pres_ext)
+ if "M190" in line or "M140" in line and state < 3: #looking for bed temp, stops after target z is passed
+ old["bedTemp"] = self.getValue(line, "S", old["bedTemp"])
+ if "M109" in line or "M104" in line and state < 3: #looking for extruder temp, stops after target z is passed
+ if self.getValue(line, "T", pres_ext) == 0:
+ old["extruderOne"] = self.getValue(line, "S", old["extruderOne"])
+ elif self.getValue(line, "T", pres_ext) == 1:
+ old["extruderTwo"] = self.getValue(line, "S", old["extruderTwo"])
+ if "M107" in line: #fan is stopped; is always updated in order not to miss switch off for next object
+ old["fanSpeed"] = 0
+ if "M106" in line and state < 3: #looking for fan speed
+ old["fanSpeed"] = self.getValue(line, "S", old["fanSpeed"])
+ if "M221" in line and state < 3: #looking for flow rate
+ tmp_extruder = self.getValue(line,"T",None)
+ if tmp_extruder == None: #check if extruder is specified
+ old["flowrate"] = self.getValue(line, "S", old["flowrate"])
+ elif tmp_extruder == 0: #first extruder
+ old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
+ elif tmp_extruder == 1: #second extruder
+ old["flowrateOne"] = self.getValue(line, "S", old["flowrateOne"])
+ if ("M84" in line or "M25" in line):
+ if state>0 and TweakProp["speed"]: #"finish" commands for UM Original and UM2
+ modified_gcode += "M220 S100 ; speed reset to 100% at the end of print\n"
+ modified_gcode += "M117 \n"
+ modified_gcode += line + "\n"
+ if "G1" in line or "G0" in line:
+ newZ = self.getValue(line, "Z", z)
+ x = self.getValue(line, "X", None)
+ y = self.getValue(line, "Y", None)
+ e = self.getValue(line, "E", None)
+ f = self.getValue(line, "F", None)
+ if 'G1' in line and TweakPrintSpeed and (state==3 or state==4):
+ # check for pure print movement in target range:
+ if x != None and y != None and f != None and e != None and newZ==z:
+ modified_gcode += "G1 F%d X%1.3f Y%1.3f E%1.5f\n" % (int(f / 100.0 * float(target_values["printspeed"])), self.getValue(line, "X"),
+ self.getValue(line, "Y"), self.getValue(line, "E"))
+ else: #G1 command but not a print movement
+ modified_gcode += line + "\n"
+ # no tweaking on retraction hops which have no x and y coordinate:
+ if (newZ != z) and (x is not None) and (y is not None):
+ z = newZ
+ if z < targetZ and state == 1:
+ state = 2
+ if z >= targetZ and state == 2:
+ state = 3
+ done_layers = 0
+ for key in TweakProp:
+ if TweakProp[key] and old[key]==-1: #old value is not known
+ oldValueUnknown = True
+ if oldValueUnknown: #the tweaking has to happen within one layer
+ twLayers = 1
+ if IsUM2: #Parameters have to be stored in the printer (UltiGCode=UM2)
+ modified_gcode += "M605 S%d;stores parameters before tweaking\n" % (TWinstances-1)
+ if behavior == 1: #single layer tweak only and then reset
+ twLayers = 1
+ if TweakPrintSpeed and behavior == 0:
+ twLayers = done_layers + 1
+ if state==3:
+ if twLayers-done_layers>0: #still layers to go?
+ if targetL_i > -100000:
+ modified_gcode += ";TweakAtZ V%s: executed at Layer %d\n" % (self.version,layer)
+ modified_gcode += "M117 Printing... tw@L%4d\n" % layer
+ else:
+ modified_gcode += (";TweakAtZ V%s: executed at %1.2f mm\n" % (self.version,z))
+ modified_gcode += "M117 Printing... tw@%5.1f\n" % z
+ for key in TweakProp:
+ if TweakProp[key]:
+ modified_gcode += TweakStrings[key] % float(old[key]+(float(target_values[key])-float(old[key]))/float(twLayers)*float(done_layers+1))
+ done_layers += 1
+ else:
+ state = 4
+ if behavior == 1: #reset values after one layer
+ if targetL_i > -100000:
+ modified_gcode += ";TweakAtZ V%s: reset on Layer %d\n" % (self.version,layer)
+ else:
+ modified_gcode += ";TweakAtZ V%s: reset at %1.2f mm\n" % (self.version,z)
+ if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
+ modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
+ else: #executes on RepRap, UM2 with Ultigcode and Cura setting
+ for key in TweakProp:
+ if TweakProp[key]:
+ modified_gcode += TweakStrings[key] % float(old[key])
+ # re-activates the plugin if executed by pre-print G-command, resets settings:
+ if (z < targetZ or layer == 0) and state >= 3: #resets if below tweak level or at level 0
+ state = 2
+ done_layers = 0
+ if targetL_i > -100000:
+ modified_gcode += ";TweakAtZ V%s: reset below Layer %d\n" % (self.version,targetL_i)
+ else:
+ modified_gcode += ";TweakAtZ V%s: reset below %1.2f mm\n" % (self.version,targetZ)
+ if IsUM2 and oldValueUnknown: #executes on UM2 with Ultigcode and machine setting
+ modified_gcode += "M606 S%d;recalls saved settings\n" % (TWinstances-1)
+ else: #executes on RepRap, UM2 with Ultigcode and Cura setting
+ for key in TweakProp:
+ if TweakProp[key]:
+ modified_gcode += TweakStrings[key] % float(old[key])
+ data[index] = modified_gcode
+ index += 1
+ return data
diff --git a/plugins/SimulationView/SimulationPass.py b/plugins/SimulationView/SimulationPass.py
index b453020ffa..c9c1443bfe 100644
--- a/plugins/SimulationView/SimulationPass.py
+++ b/plugins/SimulationView/SimulationPass.py
@@ -106,7 +106,7 @@ class SimulationPass(RenderPass):
nozzle_node = node
nozzle_node.setVisible(False)
- elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible() and node.callDecoration("getBuildPlateNumber") == active_build_plate:
+ elif issubclass(type(node), SceneNode) and (node.getMeshData() or node.callDecoration("isBlockSlicing")) and node.isVisible():
layer_data = node.callDecoration("getLayerData")
if not layer_data:
continue
diff --git a/plugins/SimulationView/SimulationView.py b/plugins/SimulationView/SimulationView.py
index 7a716d3b2b..f667aff998 100644
--- a/plugins/SimulationView/SimulationView.py
+++ b/plugins/SimulationView/SimulationView.py
@@ -104,7 +104,7 @@ class SimulationView(View):
title = catalog.i18nc("@info:title", "Simulation View"))
def _resetSettings(self):
- self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed
+ self._layer_view_type = 0 # 0 is material color, 1 is color by linetype, 2 is speed, 3 is layer thickness
self._extruder_count = 0
self._extruder_opacity = [1.0, 1.0, 1.0, 1.0]
self._show_travel_moves = 0
diff --git a/plugins/SimulationView/SimulationView.qml b/plugins/SimulationView/SimulationView.qml
index 19ae81a6e3..11b985f77c 100644
--- a/plugins/SimulationView/SimulationView.qml
+++ b/plugins/SimulationView/SimulationView.qml
@@ -176,7 +176,6 @@ Item
viewSettings.show_feedrate_gradient = viewSettings.show_gradient && (type_id == 2);
viewSettings.show_thickness_gradient = viewSettings.show_gradient && (type_id == 3);
}
-
}
Label
diff --git a/plugins/SolidView/SolidView.py b/plugins/SolidView/SolidView.py
index e156e655ce..50ff2864b7 100644
--- a/plugins/SolidView/SolidView.py
+++ b/plugins/SolidView/SolidView.py
@@ -28,6 +28,7 @@ class SolidView(View):
self._enabled_shader = None
self._disabled_shader = None
self._non_printing_shader = None
+ self._support_mesh_shader = None
self._extruders_model = ExtrudersModel()
self._theme = None
@@ -54,6 +55,11 @@ class SolidView(View):
self._non_printing_shader.setUniformValue("u_diffuseColor", Color(*self._theme.getColor("model_non_printing").getRgb()))
self._non_printing_shader.setUniformValue("u_opacity", 0.6)
+ if not self._support_mesh_shader:
+ self._support_mesh_shader = OpenGL.getInstance().createShaderProgram(Resources.getPath(Resources.Shaders, "striped.shader"))
+ self._support_mesh_shader.setUniformValue("u_vertical_stripes", True)
+ self._support_mesh_shader.setUniformValue("u_width", 5.0)
+
global_container_stack = Application.getInstance().getGlobalContainerStack()
if global_container_stack:
support_extruder_nr = global_container_stack.getProperty("support_extruder_nr", "value")
@@ -117,6 +123,16 @@ class SolidView(View):
renderer.queueNode(node, shader = self._non_printing_shader, transparent = True)
elif getattr(node, "_outside_buildarea", False):
renderer.queueNode(node, shader = self._disabled_shader)
+ elif per_mesh_stack and per_mesh_stack.getProperty("support_mesh", "value"):
+ # Render support meshes with a vertical stripe that is darker
+ shade_factor = 0.6
+ uniforms["diffuse_color_2"] = [
+ uniforms["diffuse_color"][0] * shade_factor,
+ uniforms["diffuse_color"][1] * shade_factor,
+ uniforms["diffuse_color"][2] * shade_factor,
+ 1.0
+ ]
+ renderer.queueNode(node, shader = self._support_mesh_shader, uniforms = uniforms)
else:
renderer.queueNode(node, shader = self._enabled_shader, uniforms = uniforms)
if node.callDecoration("isGroup") and Selection.isSelected(node):
diff --git a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
index 126dbbbde3..786b97d034 100644
--- a/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
+++ b/plugins/UM3NetworkPrinting/LegacyUM3OutputDevice.py
@@ -115,7 +115,6 @@ class LegacyUM3OutputDevice(NetworkedPrinterOutputDevice):
self._not_authenticated_message.hide()
self._requestAuthentication()
- pass # Cura Connect doesn't do any authorization
def connect(self):
super().connect()
diff --git a/plugins/USBPrinting/USBPrinterOutputDevice.py b/plugins/USBPrinting/USBPrinterOutputDevice.py
index c43d9a826b..6c03450a88 100644
--- a/plugins/USBPrinting/USBPrinterOutputDevice.py
+++ b/plugins/USBPrinting/USBPrinterOutputDevice.py
@@ -96,9 +96,12 @@ class USBPrinterOutputDevice(PrinterOutputDevice):
Application.getInstance().getController().setActiveStage("MonitorStage")
- gcode_list = getattr(Application.getInstance().getController().getScene(), "gcode_list")
- self._printGCode(gcode_list)
+ # find the G-code for the active build plate to print
+ active_build_plate_id = Application.getInstance().getBuildPlateModel().activeBuildPlate
+ gcode_dict = getattr(Application.getInstance().getController().getScene(), "gcode_dict")
+ gcode_list = gcode_dict[active_build_plate_id]
+ self._printGCode(gcode_list)
## Show firmware interface.
# This will create the view if its not already created.
diff --git a/resources/definitions/fdmprinter.def.json b/resources/definitions/fdmprinter.def.json
index 6eef6b1e9b..87b72928ca 100644
--- a/resources/definitions/fdmprinter.def.json
+++ b/resources/definitions/fdmprinter.def.json
@@ -3633,7 +3633,7 @@
"minimum_value": "0",
"maximum_value_warning": "100",
"default_value": 15,
- "value": "15 if support_enable else 0",
+ "value": "15 if support_enable else 0 if support_tree_enable else 15",
"enabled": "support_enable or support_tree_enable",
"limit_to_extruder": "support_infill_extruder_nr",
"settable_per_mesh": false,
@@ -4233,6 +4233,18 @@
"limit_to_extruder": "support_infill_extruder_nr",
"enabled": "support_enable and support_use_towers",
"settable_per_mesh": true
+ },
+ "support_mesh_drop_down":
+ {
+ "label": "Drop Down Support Mesh",
+ "description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.",
+ "type": "bool",
+ "default_value": true,
+ "enabled": "support_mesh",
+ "settable_per_mesh": true,
+ "settable_per_extruder": false,
+ "settable_per_meshgroup": false,
+ "settable_globally": false
}
}
},
@@ -5261,18 +5273,6 @@
"settable_per_meshgroup": false,
"settable_globally": false
},
- "support_mesh_drop_down":
- {
- "label": "Drop Down Support Mesh",
- "description": "Make support everywhere below the support mesh, so that there's no overhang in the support mesh.",
- "type": "bool",
- "default_value": true,
- "enabled": "support_mesh",
- "settable_per_mesh": true,
- "settable_per_extruder": false,
- "settable_per_meshgroup": false,
- "settable_globally": false
- },
"anti_overhang_mesh":
{
"label": "Anti Overhang Mesh",
diff --git a/resources/definitions/m180.def.json b/resources/definitions/malyan_m180.def.json
similarity index 98%
rename from resources/definitions/m180.def.json
rename to resources/definitions/malyan_m180.def.json
index 71aa729b7e..5e0a6038dd 100644
--- a/resources/definitions/m180.def.json
+++ b/resources/definitions/malyan_m180.def.json
@@ -1,4 +1,5 @@
{
+ "id": "malyan_m180",
"version": 2,
"name": "Malyan M180",
"inherits": "fdmprinter",
diff --git a/resources/definitions/malyan_m200.def.json b/resources/definitions/malyan_m200.def.json
new file mode 100644
index 0000000000..9aae3a5244
--- /dev/null
+++ b/resources/definitions/malyan_m200.def.json
@@ -0,0 +1,85 @@
+{
+ "id": "malyan_m200",
+ "version": 2,
+ "name": "Malyan M200",
+ "inherits": "fdmprinter",
+ "metadata": {
+ "author": "Brian Corbino, Tyler Gibson",
+ "manufacturer": "Malyan",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "platform": "malyan_m200_platform.stl",
+ "has_machine_quality": true,
+ "has_materials": true,
+ "preferred_quality": "*normal*",
+ "supports_usb_connection": true,
+ "visible": true,
+ "first_start_actions": ["MachineSettingsAction"],
+ "supported_actions": ["MachineSettingsAction"]
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Malyan M200" },
+ "speed_print": { "default_value": 50 },
+ "speed_wall_0": { "value": "round(speed_print * 0.75, 2)" },
+ "speed_wall_x": { "value": "speed_print" },
+ "speed_support": { "value": "speed_wall_0" },
+ "speed_layer_0": { "value": "round(speed_print / 2.0, 2)" },
+ "speed_travel": { "default_value": 50 },
+ "speed_travel_layer_0": { "default_value": 40 },
+ "speed_infill": { "value": "speed_print" },
+ "speed_topbottom": {"value": "speed_print / 2"},
+
+ "layer_height": { "minimum_value": "0.04375", "maximum_value": "machine_nozzle_size * 0.875", "maximum_value_warning": "machine_nozzle_size * 0.48125 + 0.0875", "default_value": 0.13125 },
+ "line_width": { "value": "round(machine_nozzle_size * 0.875, 2)" },
+
+ "material_print_temperature": { "minimum_value": "0" },
+ "material_print_temperature_layer_0": { "value": "min(material_print_temperature + 5, 245)" },
+ "material_bed_temperature": { "minimum_value": "0" },
+ "material_bed_temperature_layer_0": { "value": "min(material_bed_temperature + 5, 70)" },
+ "material_standby_temperature": { "minimum_value": "0" },
+ "machine_show_variants": { "default_value": true },
+ "machine_gcode_flavor": { "default_value": "RepRap (Marlin/Sprinter)" },
+ "machine_start_gcode" : {
+ "default_value": "G21;(metric values)\nG90;(absolute positioning)\nM82;(set extruder to absolute mode)\nM107;(start with the fan off)\nG28;(Home the printer)\nG92 E0;(Reset the extruder to 0)\nG0 Z5 E5 F500;(Move up and prime the nozzle)\nG0 X-1 Z0;(Move outside the printable area)\nG1 Y60 E8 F500;(Draw a priming/wiping line to the rear)\nG1 X-1;(Move a little closer to the print area)\nG1 Y10 E16 F500;(draw more priming/wiping)\nG1 E15 F250;(Small retract)\nG92 E0;(Zero the extruder)"
+ },
+ "machine_end_gcode" : {
+ "default_value": "G0 X0 Y127;(Stick out the part)\nM190 S0;(Turn off heat bed, don't wait.)\nG92 E10;(Set extruder to 10)\nG1 E7 F200;(retract 3mm)\nM104 S0;(Turn off nozzle, don't wait)\nG4 S300;(Delay 5 minutes)\nM107;(Turn off part fan)\nM84;(Turn off stepper motors.)"
+ },
+ "machine_width": { "default_value": 120 },
+ "machine_depth": { "default_value": 120 },
+ "machine_height": { "default_value": 120 },
+ "machine_heated_bed": { "default_value": true },
+ "machine_center_is_zero": { "default_value": false },
+ "material_diameter": { "value": 1.75 },
+ "machine_nozzle_size": {
+ "default_value": 0.4,
+ "minimum_value": 0.15
+ },
+ "machine_max_feedrate_x": { "default_value": 150 },
+ "machine_max_feedrate_y": { "default_value": 150 },
+ "machine_max_feedrate_z": { "default_value": 1.5 },
+ "machine_max_feedrate_e": { "default_value": 100 },
+ "machine_max_acceleration_x": { "default_value": 800 },
+ "machine_max_acceleration_y": { "default_value": 800 },
+ "machine_max_acceleration_z": { "default_value": 20 },
+ "machine_max_acceleration_e": { "default_value": 10000 },
+ "machine_max_jerk_xy": { "default_value": 20 },
+ "machine_max_jerk_z": { "default_value": 0.4 },
+ "machine_max_jerk_e": { "default_value": 5},
+ "adhesion_type": { "default_value": "raft" },
+ "raft_margin": { "default_value": 5 },
+ "raft_airgap": { "default_value": 0.2625 },
+ "raft_base_thickness": { "value": "0.30625" },
+ "raft_interface_thickness": { "value": "0.21875" },
+ "raft_surface_layers": { "default_value": 1 },
+ "skirt_line_count": { "default_value": 2},
+ "brim_width" : { "default_value": 5},
+ "start_layers_at_same_position": { "default_value": true},
+ "retraction_combing": { "default_value": "noskin" },
+ "retraction_amount" : { "default_value": 4.5},
+ "retraction_speed" : { "default_value": 40},
+ "coasting_enable": { "default_value": true },
+ "prime_tower_enable": { "default_value": false}
+ }
+}
diff --git a/resources/definitions/monoprice_select_mini_v1.def.json b/resources/definitions/monoprice_select_mini_v1.def.json
new file mode 100644
index 0000000000..7264f0a6fc
--- /dev/null
+++ b/resources/definitions/monoprice_select_mini_v1.def.json
@@ -0,0 +1,18 @@
+{
+ "id": "monoprice_select_mini_v1",
+ "version": 2,
+ "name": "Monoprice Select Mini V1",
+ "inherits": "malyan_m200",
+ "metadata": {
+ "author": "Brian Corbino, Tyler Gibson",
+ "manufacturer": "Monoprice",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "quality_definition": "malyan_m200",
+ "visible": true
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Monoprice Select Mini V1" }
+ }
+}
diff --git a/resources/definitions/monoprice_select_mini_v2.def.json b/resources/definitions/monoprice_select_mini_v2.def.json
new file mode 100644
index 0000000000..87014c136b
--- /dev/null
+++ b/resources/definitions/monoprice_select_mini_v2.def.json
@@ -0,0 +1,25 @@
+{
+ "id": "monoprice_select_mini_v2",
+ "version": 2,
+ "name": "Monoprice Select Mini V2 (E3D)",
+ "inherits": "malyan_m200",
+ "metadata": {
+ "author": "Tyler Gibson",
+ "manufacturer": "Monoprice",
+ "category": "Other",
+ "file_formats": "text/x-gcode",
+ "has_machine_quality": true,
+ "has_materials": true,
+ "preferred_quality": "*normal*",
+ "visible": true
+ },
+
+ "overrides": {
+ "machine_name": { "default_value": "Monoprice Select Mini V2" },
+ "adhesion_type": { "default_value": "brim" },
+ "retraction_combing": { "default_value": "noskin" },
+ "retraction_amount" : { "default_value": 2.5},
+ "retraction_speed" : { "default_value": 40},
+ "material_print_temperature_layer_0": { "value": "material_print_temperature + 5" }
+ }
+}
diff --git a/resources/meshes/malyan_m200_platform.stl b/resources/meshes/malyan_m200_platform.stl
new file mode 100644
index 0000000000..32b19a0911
Binary files /dev/null and b/resources/meshes/malyan_m200_platform.stl differ
diff --git a/resources/qml/Menus/ContextMenu.qml b/resources/qml/Menus/ContextMenu.qml
index 1a4b421572..b5f51f4d63 100644
--- a/resources/qml/Menus/ContextMenu.qml
+++ b/resources/qml/Menus/ContextMenu.qml
@@ -47,6 +47,7 @@ Menu
{
model: Cura.BuildPlateModel
MenuItem {
+ enabled: UM.Selection.hasSelection
text: Cura.BuildPlateModel.getItem(index).name;
onTriggered: CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.getItem(index).buildPlateNumber);
checkable: true
@@ -58,6 +59,7 @@ Menu
}
MenuItem {
+ enabled: UM.Selection.hasSelection
text: "New build plate";
onTriggered: {
CuraActions.setBuildPlateForSelection(Cura.BuildPlateModel.maxBuildPlate + 1);
diff --git a/resources/qml/ObjectsList.qml b/resources/qml/ObjectsList.qml
index a02ea2288d..489e38e8d7 100644
--- a/resources/qml/ObjectsList.qml
+++ b/resources/qml/ObjectsList.qml
@@ -105,7 +105,6 @@ Rectangle
topMargin: UM.Theme.getSize("default_margin").height;
left: parent.left;
leftMargin: UM.Theme.getSize("default_margin").height;
- //bottom: objectsList.top;
bottomMargin: UM.Theme.getSize("default_margin").height;
}
@@ -139,7 +138,7 @@ Rectangle
anchors.left: parent.left
anchors.leftMargin: UM.Theme.getSize("default_margin").width
width: parent.width - 2 * UM.Theme.getSize("default_margin").width - 30
- text: Cura.ObjectsModel.getItem(index) ? Cura.ObjectsModel.getItem(index).name : "";
+ text: (index >= 0) && Cura.ObjectsModel.getItem(index) ? Cura.ObjectsModel.getItem(index).name : "";
color: Cura.ObjectsModel.getItem(index).isSelected ? palette.highlightedText : (Cura.ObjectsModel.getItem(index).isOutsideBuildArea ? palette.mid : palette.text)
elide: Text.ElideRight
}
diff --git a/resources/qml/Preferences/GeneralPage.qml b/resources/qml/Preferences/GeneralPage.qml
index e5ed9e46c5..ac5cacdbf6 100644
--- a/resources/qml/Preferences/GeneralPage.qml
+++ b/resources/qml/Preferences/GeneralPage.qml
@@ -453,34 +453,6 @@ UM.PreferencesPage
text: catalog.i18nc("@label","Opening and saving files")
}
- UM.TooltipArea {
- width: childrenRect.width
- height: childrenRect.height
- text: catalog.i18nc("@info:tooltip","Use multi build plate functionality (EXPERIMENTAL)")
-
- CheckBox
- {
- id: useMultiBuildPlateCheckbox
- text: catalog.i18nc("@option:check","Use multi build plate functionality (EXPERIMENTAL, restart)")
- checked: boolCheck(UM.Preferences.getValue("cura/use_multi_build_plate"))
- onCheckedChanged: UM.Preferences.setValue("cura/use_multi_build_plate", checked)
- }
- }
-
- UM.TooltipArea {
- width: childrenRect.width
- height: childrenRect.height
- text: catalog.i18nc("@info:tooltip","Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)")
-
- CheckBox
- {
- id: arrangeOnLoadCheckbox
- text: catalog.i18nc("@option:check","Arrange objects on load (EXPERIMENTAL)")
- checked: boolCheck(UM.Preferences.getValue("cura/arrange_objects_on_load"))
- onCheckedChanged: UM.Preferences.setValue("cura/arrange_objects_on_load", checked)
- }
- }
-
UM.TooltipArea {
width: childrenRect.width
height: childrenRect.height
@@ -688,6 +660,49 @@ UM.PreferencesPage
onCheckedChanged: UM.Preferences.setValue("info/send_slice_info", checked)
}
}
+
+ Item
+ {
+ //: Spacer
+ height: UM.Theme.getSize("default_margin").height
+ width: UM.Theme.getSize("default_margin").height
+ }
+
+ Label
+ {
+ font.bold: true
+ text: catalog.i18nc("@label","Experimental")
+ }
+
+ UM.TooltipArea {
+ width: childrenRect.width
+ height: childrenRect.height
+ text: catalog.i18nc("@info:tooltip","Use multi build plate functionality")
+
+ CheckBox
+ {
+ id: useMultiBuildPlateCheckbox
+ text: catalog.i18nc("@option:check","Use multi build plate functionality (restart required)")
+ checked: boolCheck(UM.Preferences.getValue("cura/use_multi_build_plate"))
+ onCheckedChanged: UM.Preferences.setValue("cura/use_multi_build_plate", checked)
+ }
+ }
+
+ UM.TooltipArea {
+ width: childrenRect.width
+ height: childrenRect.height
+ text: catalog.i18nc("@info:tooltip","Should newly loaded models be arranged on the build plate? Used in conjunction with multi build plate (EXPERIMENTAL)")
+
+ CheckBox
+ {
+ id: arrangeOnLoadCheckbox
+ text: catalog.i18nc("@option:check","Do not arrange objects on load")
+ checked: boolCheck(UM.Preferences.getValue("cura/not_arrange_objects_on_load"))
+ onCheckedChanged: UM.Preferences.setValue("cura/not_arrange_objects_on_load", checked)
+ }
+ }
+
+
}
}
}
diff --git a/resources/qml/Preferences/MaterialView.qml b/resources/qml/Preferences/MaterialView.qml
index 311150c6b9..c3f36f5125 100644
--- a/resources/qml/Preferences/MaterialView.qml
+++ b/resources/qml/Preferences/MaterialView.qml
@@ -104,14 +104,13 @@ TabView
Label { width: scrollView.columnWidth; height: parent.rowHeight; verticalAlignment: Qt.AlignVCenter; text: catalog.i18nc("@label", "Color") }
- Row
- {
- width: scrollView.columnWidth;
- height: parent.rowHeight;
+ Row {
+ width: scrollView.columnWidth
+ height: parent.rowHeight
spacing: Math.floor(UM.Theme.getSize("default_margin").width/2)
- Rectangle
- {
+ // color indicator square
+ Rectangle {
id: colorSelector
color: properties.color_code
@@ -121,17 +120,36 @@ TabView
anchors.verticalCenter: parent.verticalCenter
- MouseArea { anchors.fill: parent; onClicked: colorDialog.open(); enabled: base.editingEnabled }
+ // open the color selection dialog on click
+ MouseArea {
+ anchors.fill: parent
+ onClicked: colorDialog.open()
+ enabled: base.editingEnabled
+ }
}
- ReadOnlyTextField
- {
+
+ // make sure the color stays connected after changing the color
+ Binding {
+ target: colorSelector
+ property: "color"
+ value: properties.color_code
+ }
+
+ // pretty color name text field
+ ReadOnlyTextField {
id: colorLabel;
text: properties.color_name;
readOnly: !base.editingEnabled
onEditingFinished: base.setMetaDataEntry("color_name", properties.color_name, text)
}
- ColorDialog { id: colorDialog; color: properties.color_code; onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color) }
+ // popup dialog to select a new color
+ // if successful it sets the properties.color_code value to the new color
+ ColorDialog {
+ id: colorDialog
+ color: properties.color_code
+ onAccepted: base.setMetaDataEntry("color_code", properties.color_code, color)
+ }
}
Item { width: parent.width; height: UM.Theme.getSize("default_margin").height }
@@ -401,11 +419,11 @@ TabView
}
// Tiny convenience function to check if a value really changed before trying to set it.
- function setMetaDataEntry(entry_name, old_value, new_value)
- {
- if(old_value != new_value)
- {
- Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, entry_name, new_value);
+ function setMetaDataEntry(entry_name, old_value, new_value) {
+ if (old_value != new_value) {
+ Cura.ContainerManager.setContainerMetaDataEntry(base.containerId, entry_name, new_value)
+ // make sure the UI properties are updated as well since we don't re-fetch the entire model here
+ properties[entry_name] = new_value
}
}
diff --git a/resources/qml/Preferences/MaterialsPage.qml b/resources/qml/Preferences/MaterialsPage.qml
index 81c1bd711a..6b041b895a 100644
--- a/resources/qml/Preferences/MaterialsPage.qml
+++ b/resources/qml/Preferences/MaterialsPage.qml
@@ -132,93 +132,82 @@ UM.ManagementPage
}
buttons: [
- Button
- {
- text: catalog.i18nc("@action:button", "Activate");
+
+ // Activate button
+ Button {
+ text: catalog.i18nc("@action:button", "Activate")
iconName: "list-activate";
enabled: base.currentItem != null && base.currentItem.id != Cura.MachineManager.activeMaterialId && Cura.MachineManager.hasMaterials
- onClicked:
- {
- forceActiveFocus();
+ onClicked: {
+ forceActiveFocus()
Cura.MachineManager.setActiveMaterial(base.currentItem.id)
currentItem = base.model.getItem(base.objectList.currentIndex) // Refresh the current item.
}
},
- Button
- {
+
+ // Create button
+ Button {
text: catalog.i18nc("@action:button", "Create")
iconName: "list-add"
- onClicked:
+ onClicked: {
+ forceActiveFocus()
+ Cura.ContainerManager.createMaterial()
+ }
+
+ Connections
{
- forceActiveFocus();
- var material_id = Cura.ContainerManager.createMaterial()
- if(material_id == "")
+ target: base.objectList.model
+ onItemsChanged:
{
- return
+ base.objectList.currentIndex = base.getIndexById(Cura.MachineManager.activeMaterialId);
}
- if(Cura.MachineManager.hasMaterials)
- {
- Cura.MachineManager.setActiveMaterial(material_id)
- }
- base.objectList.currentIndex = base.getIndexById(material_id);
}
},
- Button
- {
+
+ // Duplicate button
+ Button {
text: catalog.i18nc("@action:button", "Duplicate");
iconName: "list-add";
enabled: base.currentItem != null
- onClicked:
- {
- forceActiveFocus();
- var base_file = Cura.ContainerManager.getContainerMetaDataEntry(base.currentItem.id, "base_file")
- // We need to copy the base container instead of the specific variant.
- var material_id = base_file == "" ? Cura.ContainerManager.duplicateMaterial(base.currentItem.id): Cura.ContainerManager.duplicateMaterial(base_file)
- if(material_id == "")
- {
- return
- }
- if(Cura.MachineManager.hasMaterials)
- {
- Cura.MachineManager.setActiveMaterial(material_id)
- }
- // TODO: this doesn't work because the source is a bit delayed
- base.objectList.currentIndex = base.getIndexById(material_id);
+ onClicked: {
+ forceActiveFocus()
+ Cura.ContainerManager.duplicateOriginalMaterial(base.currentItem.id)
}
},
- Button
- {
- text: catalog.i18nc("@action:button", "Remove");
- iconName: "list-remove";
+
+ // Remove button
+ Button {
+ text: catalog.i18nc("@action:button", "Remove")
+ iconName: "list-remove"
enabled: base.currentItem != null && !base.currentItem.readOnly && !Cura.ContainerManager.isContainerUsed(base.currentItem.id)
- onClicked:
- {
- forceActiveFocus();
- confirmDialog.open();
+ onClicked: {
+ forceActiveFocus()
+ confirmDialog.open()
}
},
- Button
- {
- text: catalog.i18nc("@action:button", "Import");
- iconName: "document-import";
- onClicked:
- {
- forceActiveFocus();
- importDialog.open();
+
+ // Import button
+ Button {
+ text: catalog.i18nc("@action:button", "Import")
+ iconName: "document-import"
+ onClicked: {
+ forceActiveFocus()
+ importDialog.open()
}
- visible: true;
+ visible: true
},
- Button
- {
+
+ // Export button
+ Button {
text: catalog.i18nc("@action:button", "Export")
iconName: "document-export"
- onClicked:
- {
- forceActiveFocus();
- exportDialog.open();
+ onClicked: {
+ forceActiveFocus()
+ exportDialog.open()
}
enabled: currentItem != null
}
+
]
Item {
diff --git a/resources/qml/SidebarSimple.qml b/resources/qml/SidebarSimple.qml
index 9c38c1e066..b96c40d9ea 100644
--- a/resources/qml/SidebarSimple.qml
+++ b/resources/qml/SidebarSimple.qml
@@ -195,11 +195,22 @@ Item
text:
{
var result = ""
- if (Cura.MachineManager.activeMachine != null) {
+ if(Cura.MachineManager.activeMachine != null)
+ {
result = Cura.ProfilesModel.getItem(index).layer_height_without_unit
- if (result == undefined)
- result = ""
+ if(result == undefined)
+ {
+ result = "";
+ }
+ else
+ {
+ result = Number(Math.round(result + "e+2") + "e-2"); //Round to 2 decimals. Javascript makes this difficult...
+ if (result == undefined || result != result) //Parse failure.
+ {
+ result = "";
+ }
+ }
}
return result
}
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg
new file mode 100644
index 0000000000..19cc9fd00d
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_draft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fast
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_abs_175
+weight = -2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg
new file mode 100644
index 0000000000..5677a0d58d
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_fast.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Normal
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_abs_175
+weight = -1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg
new file mode 100644
index 0000000000..7798b3f545
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_high.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Finer
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_abs_175
+weight = 1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg
new file mode 100644
index 0000000000..c87c66c813
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_normal.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_abs_175
+weight = 0
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg
new file mode 100644
index 0000000000..e6e3cfcd6c
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_superdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_abs_175
+weight = -5
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..fb08013809
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_thickerdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_abs_175
+weight = -3
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg
new file mode 100644
index 0000000000..385d852688
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_ultra.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_abs_175
+weight = 2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg b/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg
new file mode 100644
index 0000000000..7026391fb6
--- /dev/null
+++ b/resources/quality/malyan_m200/abs/malyan_m200_abs_verydraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_abs_175
+weight = -4
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg
new file mode 100644
index 0000000000..54be6ecbcc
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.04375.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M1 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 2
+quality_type = fine
+setting_version = 4
+
+[values]
+layer_height = 0.04375
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg
new file mode 100644
index 0000000000..568dd796f3
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.0875.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M2 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 1
+quality_type = high
+setting_version = 4
+
+[values]
+layer_height = 0.0875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg
new file mode 100644
index 0000000000..1dc436502b
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.13125.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M3 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 0
+quality_type = normal
+setting_version = 4
+
+[values]
+layer_height = 0.13125
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg
new file mode 100644
index 0000000000..314a8acd83
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.175.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = M4 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -1
+quality_type = fast
+global_quality = true
+setting_version = 4
+
+[values]
+layer_height = 0.175
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg
new file mode 100644
index 0000000000..a7fedb7e04
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.21875.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M5 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -2
+quality_type = faster
+setting_version = 4
+
+[values]
+layer_height = 0.21875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg
new file mode 100644
index 0000000000..441abc3070
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.2625.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M6 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -3
+quality_type = draft
+setting_version = 4
+
+[values]
+layer_height = 0.2625
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg
new file mode 100644
index 0000000000..2588838174
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.30625.inst.cfg
@@ -0,0 +1,22 @@
+[general]
+version = 2
+name = M7 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -4
+quality_type = turbo
+setting_version = 4
+
+[values]
+layer_height = 0.30625
+layer_height_0 = 0.30625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg b/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg
new file mode 100644
index 0000000000..800b6104d9
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_0.35.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = M8 Quality
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -5
+quality_type = hyper
+global_quality = true
+setting_version = 4
+
+[values]
+layer_height = 0.35
+layer_height_0 = 0.35
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg
new file mode 100644
index 0000000000..d3104caa87
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_Draft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Fast
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -2
+quality_type = draft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.21875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg
new file mode 100644
index 0000000000..aec535bd71
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_Fast_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Normal
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -1
+quality_type = fast
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.175
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg
new file mode 100644
index 0000000000..ca202862a2
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_High_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Finer
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 1
+quality_type = high
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.0875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg
new file mode 100644
index 0000000000..7076718903
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_Normal_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 0
+quality_type = normal
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.13125
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..7dfbdb5886
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_SuperDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -5
+quality_type = superdraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.35
+layer_height_0 = 0.35
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..2fbf82b128
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_ThickerDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -3
+quality_type = thickerdraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.2625
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg
new file mode 100644
index 0000000000..90e589cca5
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_Ultra_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = 2
+quality_type = ultra
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.04375
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg b/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..1210ee214b
--- /dev/null
+++ b/resources/quality/malyan_m200/malyan_m200_global_VeryDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+weight = -4
+quality_type = verydraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.30625
+layer_height_0 = 0.30625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg
new file mode 100644
index 0000000000..aef83471ba
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_draft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fast
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_petg_175
+weight = -2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg
new file mode 100644
index 0000000000..3c7fc2c239
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_fast.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Normal
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_petg_175
+weight = -1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg
new file mode 100644
index 0000000000..eb1654eae3
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_high.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Finer
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_petg_175
+weight = 1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg
new file mode 100644
index 0000000000..53e60d2d62
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_normal.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_petg_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg
new file mode 100644
index 0000000000..d2a96386ae
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_superdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_petg_175
+weight = -5
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..e2f37ae43b
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_thickerdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_petg_175
+weight = -3
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg
new file mode 100644
index 0000000000..0fa89f2569
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_ultra.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_petg_175
+weight = 2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg b/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg
new file mode 100644
index 0000000000..84bedf5c14
--- /dev/null
+++ b/resources/quality/malyan_m200/petg/malyan_m200_petg_verydraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_petg_175
+weight = -4
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg
new file mode 100644
index 0000000000..4f221eceb7
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_draft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fast
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_pla_175
+weight = -2
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg
new file mode 100644
index 0000000000..3097fe055a
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_fast.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Normal
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_pla_175
+weight = -1
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg
new file mode 100644
index 0000000000..062c120ad0
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_high.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Finer
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_pla_175
+weight = 1
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg
new file mode 100644
index 0000000000..e01141ed9e
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_normal.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_pla_175
+weight = 0
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg
new file mode 100644
index 0000000000..53eb4380eb
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_superdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_pla_175
+weight = -5
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..32d2b419bc
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_thickerdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_pla_175
+weight = -3
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg
new file mode 100644
index 0000000000..3865059254
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_ultra.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_pla_175
+weight = 2
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg b/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg
new file mode 100644
index 0000000000..a624c056be
--- /dev/null
+++ b/resources/quality/malyan_m200/pla/malyan_m200_pla_verydraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = malyan_m200
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_pla_175
+weight = -4
+setting_version = 4
+
+[values]
+material_bed_temperature = 60
+material_bed_temperature_layer_0 = 60
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg
new file mode 100644
index 0000000000..a63256573a
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_draft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_abs_175
+weight = -2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg
new file mode 100644
index 0000000000..49f4486596
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_fast.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_abs_175
+weight = -1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg
new file mode 100644
index 0000000000..eab16a8e2b
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_high.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_abs_175
+weight = 1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg
new file mode 100644
index 0000000000..03aeb4067b
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_normal.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_abs_175
+weight = 0
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg
new file mode 100644
index 0000000000..148f53ba73
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_superdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_abs_175
+weight = -5
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..e2ad71a360
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_thickerdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_abs_175
+weight = -3
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg
new file mode 100644
index 0000000000..7ebdf80baf
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_ultra.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_abs_175
+weight = 2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg
new file mode 100644
index 0000000000..9965ae8bcf
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/abs/monoprice_select_mini_v2_abs_verydraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_abs_175
+weight = -4
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg
new file mode 100644
index 0000000000..b7d0faa2c7
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Draft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = -2
+quality_type = draft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.21875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg
new file mode 100644
index 0000000000..f7f338e4c9
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Fast_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = -1
+quality_type = fast
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.175
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg
new file mode 100644
index 0000000000..4a37a1afd8
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_High_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = 1
+quality_type = high
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.0875
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg
new file mode 100644
index 0000000000..b8e545adcf
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Normal_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = 0
+quality_type = normal
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.13125
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..0ef9db5875
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_SuperDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = -5
+quality_type = superdraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.35
+layer_height_0 = 0.35
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..4dd3a7aafe
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_ThickerDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = -3
+quality_type = thickerdraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.2625
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg
new file mode 100644
index 0000000000..337f0d06bc
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_Ultra_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = 2
+quality_type = ultra
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.04375
+layer_height_0 = 0.2625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg
new file mode 100644
index 0000000000..e884077069
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/monoprice_select_mini_v2_global_VeryDraft_Quality.inst.cfg
@@ -0,0 +1,23 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+weight = -4
+quality_type = verydraft
+global_quality = True
+setting_version = 4
+
+[values]
+layer_height = 0.30625
+layer_height_0 = 0.30625
+wall_thickness = 1.05
+top_bottom_thickness = 0.72
+infill_sparse_density = 22
+speed_print = 50
+speed_layer_0 = =round(speed_print * 30 / 50)
+speed_topbottom = 20
+cool_min_layer_time = 5
+cool_min_speed = 10
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg
new file mode 100644
index 0000000000..4a03c17a63
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_draft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_nylon_175
+weight = -2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg
new file mode 100644
index 0000000000..1c04f77b8b
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_fast.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_nylon_175
+weight = -1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg
new file mode 100644
index 0000000000..d57516598a
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_high.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_nylon_175
+weight = 1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg
new file mode 100644
index 0000000000..308ea86311
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_normal.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_nylon_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg
new file mode 100644
index 0000000000..db4f3ca907
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_superdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_nylon_175
+weight = -5
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..9a1afc0e48
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_thickerdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_nylon_175
+weight = -3
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg
new file mode 100644
index 0000000000..3453671a72
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_ultra.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_nylon_175
+weight = 2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg
new file mode 100644
index 0000000000..ee2531fc4e
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/nylon/monoprice_select_mini_v2_nylon_verydraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_nylon_175
+weight = -4
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg
new file mode 100644
index 0000000000..aa5fc7844d
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_draft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_pc_175
+weight = -2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg
new file mode 100644
index 0000000000..232c4ab6f3
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_fast.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_pc_175
+weight = -1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg
new file mode 100644
index 0000000000..aa9da322fb
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_high.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_pc_175
+weight = 1
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg
new file mode 100644
index 0000000000..145b21221b
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_normal.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_pc_175
+weight = 0
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg
new file mode 100644
index 0000000000..b6e53bda62
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_superdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_pc_175
+weight = -5
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..055228ab13
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_thickerdraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_pc_175
+weight = -3
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg
new file mode 100644
index 0000000000..a3e99b998e
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_ultra.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_pc_175
+weight = 2
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg
new file mode 100644
index 0000000000..73f5a2f2c9
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pc/monoprice_select_mini_v2_pc_verydraft.inst.cfg
@@ -0,0 +1,15 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_pc_175
+weight = -4
+setting_version = 4
+
+[values]
+material_bed_temperature = 70
+material_bed_temperature_layer_0 = 70
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg
new file mode 100644
index 0000000000..8a33e03310
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_draft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_petg_175
+weight = -2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg
new file mode 100644
index 0000000000..fb084fa08e
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_fast.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_petg_175
+weight = -1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg
new file mode 100644
index 0000000000..16891f6f43
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_high.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_petg_175
+weight = 1
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg
new file mode 100644
index 0000000000..bb2f0b47a8
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_normal.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_petg_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg
new file mode 100644
index 0000000000..78ca1b6b7a
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_superdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_petg_175
+weight = -5
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..69606ff913
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_thickerdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_petg_175
+weight = -3
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg
new file mode 100644
index 0000000000..7c5ac599c8
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_ultra.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_petg_175
+weight = 2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg
new file mode 100644
index 0000000000..ed0c2510f5
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/petg/monoprice_select_mini_v2_petg_verydraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_petg_175
+weight = -4
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg
new file mode 100644
index 0000000000..04a955cf6c
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_draft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fast
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = draft
+material = generic_pla_175
+weight = -2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg
new file mode 100644
index 0000000000..6efc0935e2
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_fast.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Normal
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = fast
+material = generic_pla_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg
new file mode 100644
index 0000000000..8fe2371e5d
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_high.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Finer
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = high
+material = generic_pla_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg
new file mode 100644
index 0000000000..01351154c4
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_normal.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = normal
+material = generic_pla_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg
new file mode 100644
index 0000000000..adfced9787
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_superdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Lowest Quality Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = superdraft
+material = generic_pla_175
+weight = -5
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg
new file mode 100644
index 0000000000..f4522c9778
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_thickerdraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = thickerdraft
+material = generic_pla_175
+weight = -3
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg
new file mode 100644
index 0000000000..2fa8eb7f81
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_ultra.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Ultra Fine
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = ultra
+material = generic_pla_175
+weight = 2
+setting_version = 4
\ No newline at end of file
diff --git a/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg
new file mode 100644
index 0000000000..e59cf4a490
--- /dev/null
+++ b/resources/quality/monoprice_select_mini_v2/pla/monoprice_select_mini_v2_pla_verydraft.inst.cfg
@@ -0,0 +1,11 @@
+[general]
+version = 2
+name = Low Detail Draft
+definition = monoprice_select_mini_v2
+
+[metadata]
+type = quality
+quality_type = verydraft
+material = generic_pla_175
+weight = 0
+setting_version = 4
\ No newline at end of file
diff --git a/resources/shaders/striped.shader b/resources/shaders/striped.shader
index ce7d14e39e..7cf5a62c3f 100644
--- a/resources/shaders/striped.shader
+++ b/resources/shaders/striped.shader
@@ -32,6 +32,7 @@ fragment =
uniform highp vec3 u_viewPosition;
uniform mediump float u_width;
+ uniform mediump bool u_vertical_stripes;
varying highp vec3 v_position;
varying highp vec3 v_vertex;
@@ -40,7 +41,9 @@ fragment =
void main()
{
mediump vec4 finalColor = vec4(0.0);
- mediump vec4 diffuseColor = (mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2;
+ mediump vec4 diffuseColor = u_vertical_stripes ?
+ (((mod(v_vertex.x, u_width) < (u_width / 2.)) ^^ (mod(v_vertex.z, u_width) < (u_width / 2.))) ? u_diffuseColor1 : u_diffuseColor2) :
+ ((mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2);
/* Ambient Component */
finalColor += u_ambientColor;
@@ -98,6 +101,7 @@ fragment41core =
uniform highp vec3 u_viewPosition;
uniform mediump float u_width;
+ uniform mediump bool u_vertical_stripes;
in highp vec3 v_position;
in highp vec3 v_vertex;
@@ -108,7 +112,9 @@ fragment41core =
void main()
{
mediump vec4 finalColor = vec4(0.0);
- mediump vec4 diffuseColor = (mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2;
+ mediump vec4 diffuseColor = u_vertical_stripes ?
+ (((mod(v_vertex.x, u_width) < (u_width / 2.)) ^^ (mod(v_vertex.z, u_width) < (u_width / 2.))) ? u_diffuseColor1 : u_diffuseColor2) :
+ ((mod((-v_position.x + v_position.y), u_width) < (u_width / 2.)) ? u_diffuseColor1 : u_diffuseColor2);
/* Ambient Component */
finalColor += u_ambientColor;
@@ -138,6 +144,7 @@ u_diffuseColor2 = [0.5, 0.5, 0.5, 1.0]
u_specularColor = [0.4, 0.4, 0.4, 1.0]
u_shininess = 20.0
u_width = 5.0
+u_vertical_stripes = 0
[bindings]
u_modelMatrix = model_matrix
@@ -145,7 +152,8 @@ u_viewProjectionMatrix = view_projection_matrix
u_normalMatrix = normal_matrix
u_viewPosition = view_position
u_lightPosition = light_0_position
-u_diffuseColor = diffuse_color
+u_diffuseColor1 = diffuse_color
+u_diffuseColor2 = diffuse_color_2
[attributes]
a_vertex = vertex