mirror of
https://github.com/Ultimaker/Cura.git
synced 2025-07-06 14:37:29 -06:00
Add post processing source files
This commit is contained in:
parent
9c9c46aade
commit
6c0fb110fe
15 changed files with 2463 additions and 0 deletions
206
plugins/PostProcessingPlugin/PostProcessingPlugin.py
Normal file
206
plugins/PostProcessingPlugin/PostProcessingPlugin.py
Normal file
|
@ -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")
|
||||
|
||||
|
501
plugins/PostProcessingPlugin/PostProcessingPlugin.qml
Normal file
501
plugins/PostProcessingPlugin/PostProcessingPlugin.qml
Normal file
|
@ -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{ }
|
||||
}
|
||||
}
|
||||
}
|
2
plugins/PostProcessingPlugin/README.md
Normal file
2
plugins/PostProcessingPlugin/README.md
Normal file
|
@ -0,0 +1,2 @@
|
|||
# PostProcessingPlugin
|
||||
A post processing plugin for Cura
|
111
plugins/PostProcessingPlugin/Script.py
Normal file
111
plugins/PostProcessingPlugin/Script.py
Normal file
|
@ -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()
|
11
plugins/PostProcessingPlugin/__init__.py
Normal file
11
plugins/PostProcessingPlugin/__init__.py
Normal file
|
@ -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()}
|
8
plugins/PostProcessingPlugin/plugin.json
Normal file
8
plugins/PostProcessingPlugin/plugin.json
Normal file
|
@ -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"
|
||||
}
|
47
plugins/PostProcessingPlugin/postprocessing.svg
Normal file
47
plugins/PostProcessingPlugin/postprocessing.svg
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.2.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="512px"
|
||||
height="512px"
|
||||
viewBox="0 0 512 512"
|
||||
style="enable-background:new 0 0 512 512;"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="postprocessing.svg"><metadata
|
||||
id="metadata9"><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
|
||||
id="defs7" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1104"
|
||||
inkscape:window-height="1006"
|
||||
id="namedview5"
|
||||
showgrid="false"
|
||||
inkscape:zoom="1.3359375"
|
||||
inkscape:cx="256"
|
||||
inkscape:cy="256"
|
||||
inkscape:window-x="701"
|
||||
inkscape:window-y="121"
|
||||
inkscape:window-maximized="0"
|
||||
inkscape:current-layer="Layer_1" /><path
|
||||
d="M 402.15234 0 C 371.74552 4.7369516e-015 345.79114 10.752017 324.21875 32.324219 C 302.57788 53.89652 291.82617 79.851497 291.82617 110.18945 C 291.82617 127.34315 295.26662 143.09419 302.16602 157.44531 L 238.38477 221.20312 C 227.77569 210.95036 218.04331 201.50935 209.66016 193.32422 C 207.33386 190.99792 202.68042 189.48707 198.60938 191.92969 L 191.74609 196.11719 C 165.34252 169.24836 154.17609 158.42965 150.57031 145.40234 C 146.84822 131.79345 150.22148 113.64862 153.71094 106.90234 C 156.61882 101.55183 165.69233 96.550326 173.36914 95.96875 L 183.37109 106.20508 C 185.69739 108.53139 189.30456 108.53139 191.63086 106.20508 L 227.57227 69.681641 C 229.89858 67.355335 229.89858 63.517712 227.57227 61.191406 L 169.53125 2.21875 C 167.20494 -0.10755598 163.48147 -0.10755598 161.27148 2.21875 L 125.33008 38.742188 C 123.00378 41.068494 123.00378 44.906116 125.33008 47.232422 L 129.16992 51.1875 C 129.16992 56.88695 128.35573 65.727167 123.70312 70.496094 C 116.49157 77.823958 102.18413 69.332919 92.878906 75.962891 C 83.689998 82.476548 72.05746 92.944493 64.613281 100.38867 C 57.285417 107.83285 29.138171 137.37722 9.015625 187.16016 C -11.106922 236.94311 4.3632369 283.12 15.296875 295.2168 C 21.11264 301.61414 31.696982 308.12804 29.835938 296.03125 C 27.974892 283.81815 24.951448 241.47942 38.792969 224.14844 C 52.634489 206.81746 70.894726 192.62799 94.623047 191.46484 C 117.42084 190.30169 130.56529 198.09417 160.10938 228.10352 L 156.85156 234.15234 C 154.75788 238.10706 155.92175 243.10728 158.24805 245.43359 C 161.95717 248.74082 172.37305 258.96006 186.52539 273.04297 L 6.9511719 452.54883 C 2.2984329 457.14417 1.1842379e-015 462.71497 0 469.14844 C -1.1842379e-015 475.69681 2.2984329 481.15473 6.9511719 485.51953 L 26.308594 505.22266 C 31.018838 509.76054 36.589603 512 42.908203 512 C 49.341623 512 54.800053 509.76054 59.337891 505.22266 L 238.96875 325.6582 C 317.6609 404.95524 424.21289 513.40234 424.21289 513.40234 L 482.25391 454.43164 C 437.71428 411.9686 358.71135 336.76293 291.93164 272.71484 L 354.68945 209.98047 C 369.08663 216.91399 384.90203 220.37891 402.15234 220.37891 C 425.29988 220.37891 446.52947 213.53073 465.77344 199.83398 C 485.08493 186.1372 498.57775 168.33291 506.31641 146.34961 C 510.08303 135.39222 512 126.69334 512 120.25586 C 512 117.79044 511.24662 115.80572 509.87695 114.16211 C 508.50726 112.5185 506.59041 111.69531 504.125 111.69531 C 502.61835 111.69531 496.86414 114.5734 486.72852 120.39453 C 476.6614 126.21564 465.50054 132.85752 453.37891 140.32227 C 441.18878 147.78698 434.7515 151.75888 433.92969 152.23828 L 386.40234 125.94141 L 386.40234 70.8125 L 458.51562 29.242188 C 461.18649 27.461587 462.48633 25.202356 462.48633 22.394531 C 462.48633 19.586706 461.1865 17.325625 458.51562 15.476562 C 451.32484 10.545729 442.4896 6.780346 432.08008 4.0410156 C 421.60206 1.3701797 411.67159 0 402.15234 0 z "
|
||||
id="path3" /></svg>
|
After Width: | Height: | Size: 4.4 KiB |
48
plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
Normal file
48
plugins/PostProcessingPlugin/scripts/BQ_PauseAtHeight.py
Normal file
|
@ -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
|
76
plugins/PostProcessingPlugin/scripts/ColorChange.py
Normal file
76
plugins/PostProcessingPlugin/scripts/ColorChange.py
Normal file
|
@ -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
|
43
plugins/PostProcessingPlugin/scripts/ExampleScript.py
Normal file
43
plugins/PostProcessingPlugin/scripts/ExampleScript.py
Normal file
|
@ -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
|
221
plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Normal file
221
plugins/PostProcessingPlugin/scripts/PauseAtHeight.py
Normal file
|
@ -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: <current_height> = <current_z> - <layer_0_z>
|
||||
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
|
169
plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
Normal file
169
plugins/PostProcessingPlugin/scripts/PauseAtHeightforRepetier.py
Normal file
|
@ -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
|
56
plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
Normal file
56
plugins/PostProcessingPlugin/scripts/SearchAndReplace.py
Normal file
|
@ -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
|
469
plugins/PostProcessingPlugin/scripts/Stretch.py
Normal file
469
plugins/PostProcessingPlugin/scripts/Stretch.py
Normal file
|
@ -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)
|
||||
|
495
plugins/PostProcessingPlugin/scripts/TweakAtZ.py
Normal file
495
plugins/PostProcessingPlugin/scripts/TweakAtZ.py
Normal file
|
@ -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<factor in percent> - set speed factor override percentage
|
||||
## M221 S<factor in percent> - set flow factor override percentage
|
||||
## M221 S<factor in percent> T<0-#toolheads> - set flow factor override percentage for single extruder
|
||||
## M104 S<temp> T<0-#toolheads> - set extruder <T> to target temperature <S>
|
||||
## M140 S<temp> - set bed target temperature
|
||||
## M106 S<PWM> - set fan speed to target speed <S>
|
||||
## 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
|
Loading…
Add table
Add a link
Reference in a new issue