mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-12 03:27:55 +00:00
feat(plugin)!: merge G-code post-processing into the slicing pipeline as psGCodePostProcess
G-code post-processing is now a step of the slicing-pipeline plugin rather than a separate capability type. One capability class can transform slices at the geometry seams AND edit the final G-code, behind a single picker/option. - Add SlicingPipelineStepPlugin::psGCodePostProcess (bound as orca.slicing.Step.psGCodePostProcess). Unlike the geometry steps it fires from the GUI export path in PostProcessor.cpp, not from Print::process(): ctx.print/ctx.object are None and the plugin edits the file at ctx.gcode_path in place. It may run more than once per slice (file export and/or upload) and its output is not shown in the preview. - Extend SlicingPipelineContext with gcode_path/host/output_name and a C++-only full_config; config_value() falls back to it when there is no live Print. - PostProcessor.cpp dispatches SlicingPipelinePluginCapability at psGCodePostProcess, driven by the existing slicing_pipeline_plugin option. - The exported G-code lives outside data_dir(), so the plugin audit sandbox would block the write; the trampoline's audit setup grants ctx.gcode_path's folder as a scoped allowed root, gated on a non-empty gcode_path so the geometry-step hooks gain no extra filesystem access. BREAKING CHANGE: the separate G-code post-processing capability type is removed. - orca.gcode.GCodePluginCapabilityBase and orca.PluginType.PostProcessing are gone; post-processing plugins migrate to orca.slicing.SlicingPipelineCapabilityBase + Step.psGCodePostProcess (and gain ctx.params / ctx.config_value()). - The post_process_plugin config option is removed; use slicing_pipeline_plugin. Presets carrying the old key degrade to the standard unknown-key warning. - Manifest type = "post-processing" now maps to Unknown (advisory only; the loader dispatches on the C++ get_type()). Also repairs two latent build breaks the branch carried: stale Step enum value usages in test_slicing_pipeline_hook.cpp and a reference to the removed ConfigOptionDef::PluginType::None in Tab::on_value_change (now is_plugin_backed()). Adds the orca_gcode_stamp sample plugin and a psGCodePostProcess binding test.
This commit is contained in:
74
sandboxes/orca_gcode_stamp_plugin_any.py
Normal file
74
sandboxes/orca_gcode_stamp_plugin_any.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
# /// script
|
||||||
|
# requires-python = ">=3.12"
|
||||||
|
#
|
||||||
|
# [tool.orcaslicer.plugin]
|
||||||
|
# name = "G-code Stamp"
|
||||||
|
# description = "Stamps a comment line into the exported G-code at the post-process step (demo)."
|
||||||
|
# author = "OrcaSlicer"
|
||||||
|
# version = "0.01"
|
||||||
|
# type = "slicing-pipeline"
|
||||||
|
#
|
||||||
|
# [tool.orcaslicer.plugin.settings]
|
||||||
|
# stamp_text = "processed by the OrcaSlicer G-code Stamp plugin"
|
||||||
|
# ///
|
||||||
|
"""G-code Stamp -- the post-processing half of the slicing-pipeline plugin.
|
||||||
|
|
||||||
|
Post-processing is now a step of the slicing pipeline: Step.psGCodePostProcess.
|
||||||
|
It fires from the G-code export path AFTER the classic post_process scripts, on the
|
||||||
|
exported G-code file -- NOT from Print::process(). So unlike the geometry steps
|
||||||
|
(posSlice, posPerimeters, ...) there is no live slicing graph here: ctx.print and
|
||||||
|
ctx.object are None. Instead the context carries ctx.gcode_path (the working G-code
|
||||||
|
file on disk, edited IN PLACE), ctx.host ("File", "OctoPrint", ...) and
|
||||||
|
ctx.output_name (the final file name). ctx.params and ctx.config_value() still work.
|
||||||
|
|
||||||
|
This sample inserts a single comment line near the top of the file. Because the same
|
||||||
|
capability class can also implement the geometry steps, one plugin can transform slices
|
||||||
|
AND stamp the final G-code; a geometry-only plugin just returns success here.
|
||||||
|
|
||||||
|
The step may fire more than once per slice (file export and/or upload each run it on a
|
||||||
|
separate working copy), and its output is not reflected in the G-code preview -- the
|
||||||
|
viewer maps the pre-post-process file.
|
||||||
|
"""
|
||||||
|
import orca
|
||||||
|
|
||||||
|
_DEFAULT_STAMP = "processed by the OrcaSlicer G-code Stamp plugin"
|
||||||
|
|
||||||
|
|
||||||
|
def _stamp_text(ctx):
|
||||||
|
try:
|
||||||
|
text = dict(ctx.params).get("stamp_text", _DEFAULT_STAMP)
|
||||||
|
except (AttributeError, TypeError):
|
||||||
|
text = _DEFAULT_STAMP
|
||||||
|
return str(text).replace("\n", " ").strip() or _DEFAULT_STAMP
|
||||||
|
|
||||||
|
|
||||||
|
class GCodeStamp(orca.slicing.SlicingPipelineCapabilityBase):
|
||||||
|
def get_name(self):
|
||||||
|
return "G-code Stamp"
|
||||||
|
|
||||||
|
def execute(self, ctx):
|
||||||
|
# Only act at the post-process seam; at every geometry step this is a no-op.
|
||||||
|
if ctx.step != orca.slicing.Step.psGCodePostProcess:
|
||||||
|
return orca.ExecutionResult.success()
|
||||||
|
if not ctx.gcode_path:
|
||||||
|
return orca.ExecutionResult.success("G-code Stamp: no gcode_path, nothing to do")
|
||||||
|
|
||||||
|
comment = "; " + _stamp_text(ctx) + " (host=" + (ctx.host or "?") + ")\n"
|
||||||
|
|
||||||
|
# Edit the exported G-code in place: keep the original first line first (some flavors
|
||||||
|
# expect a specific leading line), then insert the stamp right after it.
|
||||||
|
with open(ctx.gcode_path, "r", encoding="utf-8", errors="replace") as f:
|
||||||
|
lines = f.readlines()
|
||||||
|
insert_at = 1 if lines else 0
|
||||||
|
lines.insert(insert_at, comment)
|
||||||
|
with open(ctx.gcode_path, "w", encoding="utf-8") as f:
|
||||||
|
f.writelines(lines)
|
||||||
|
|
||||||
|
return orca.ExecutionResult.success(
|
||||||
|
"G-code Stamp: stamped '" + (ctx.output_name or ctx.gcode_path) + "'")
|
||||||
|
|
||||||
|
|
||||||
|
@orca.plugin
|
||||||
|
class GCodeStampPackage(orca.base):
|
||||||
|
def register_capabilities(self):
|
||||||
|
orca.register_capability(GCodeStamp)
|
||||||
@@ -2444,9 +2444,9 @@ public:
|
|||||||
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
|
// "serialized" - vector valued option is entered in a single edit field. Values are separated by a semicolon.
|
||||||
// "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
|
// "show_value" - even if enum_values / enum_labels are set, still display the value, not the enum label.
|
||||||
std::string gui_flags;
|
std::string gui_flags;
|
||||||
// Capability type of a plugin-backed option, e.g. "post-processing" / "slicing-pipeline" /
|
// Capability type of a plugin-backed option, e.g. "slicing-pipeline" / "printer-connection"
|
||||||
// "printer-connection" (empty for ordinary options). GUIType::plugin_picker filters the plugin
|
// (empty for ordinary options). GUIType::plugin_picker filters the plugin list by it, and it
|
||||||
// list by it, and it resolves the option's "plugins" manifest reference; see is_plugin_backed().
|
// resolves the option's "plugins" manifest reference; see is_plugin_backed().
|
||||||
std::string plugin_type;
|
std::string plugin_type;
|
||||||
// Whether this option holds plugin capability name(s) that feed the "plugins" manifest -- true
|
// Whether this option holds plugin capability name(s) that feed the "plugins" manifest -- true
|
||||||
// iff it declares a plugin_type. Setting plugin_type is the only step needed to add one.
|
// iff it declares a plugin_type. Setting plugin_type is the only step needed to add one.
|
||||||
|
|||||||
@@ -1192,7 +1192,6 @@ static std::vector<std::string> s_Preset_print_options{
|
|||||||
"min_feature_size",
|
"min_feature_size",
|
||||||
"min_bead_width",
|
"min_bead_width",
|
||||||
"post_process",
|
"post_process",
|
||||||
"post_process_plugin",
|
|
||||||
"slicing_pipeline_plugin",
|
"slicing_pipeline_plugin",
|
||||||
"plugins",
|
"plugins",
|
||||||
"process_change_extrusion_role_gcode",
|
"process_change_extrusion_role_gcode",
|
||||||
|
|||||||
@@ -125,8 +125,8 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|
|||||||
"printing_by_object_gcode",
|
"printing_by_object_gcode",
|
||||||
"filament_end_gcode",
|
"filament_end_gcode",
|
||||||
"post_process",
|
"post_process",
|
||||||
"post_process_plugin",
|
// "plugins" is the derived manifest backing the plugin-picker options; on its own it only
|
||||||
// "plugins" is the manifest backing post_process_plugin; like it, it only affects G-code export.
|
// affects G-code export. The specific option (e.g. slicing_pipeline_plugin) drives any re-slice.
|
||||||
"plugins",
|
"plugins",
|
||||||
"extruder_clearance_height_to_rod",
|
"extruder_clearance_height_to_rod",
|
||||||
"extruder_clearance_height_to_lid",
|
"extruder_clearance_height_to_lid",
|
||||||
|
|||||||
@@ -101,7 +101,10 @@ enum PrintObjectStep {
|
|||||||
|
|
||||||
enum class SlicingPipelineStepPlugin {
|
enum class SlicingPipelineStepPlugin {
|
||||||
posSlice, posPerimeters, posEstimateCurledExtrusions, posPrepareInfill, posInfill, posIroning, posContouring,
|
posSlice, posPerimeters, posEstimateCurledExtrusions, posPrepareInfill, posInfill, posIroning, posContouring,
|
||||||
posSupportMaterial, posDetectOverhangsForLift, posSimplifyPath, psWipeTower, psSkirtBrim
|
posSupportMaterial, posDetectOverhangsForLift, posSimplifyPath, psWipeTower, psSkirtBrim,
|
||||||
|
// Fires from the GUI G-code export/post-process seam (PostProcessor.cpp), NOT from Print::process().
|
||||||
|
// At this step the plugin edits the exported G-code file in place; see the binding for the full contract.
|
||||||
|
psGCodePostProcess
|
||||||
};
|
};
|
||||||
|
|
||||||
// A PrintRegion object represents a group of volumes to print
|
// A PrintRegion object represents a group of volumes to print
|
||||||
|
|||||||
@@ -5114,20 +5114,10 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def->mode = comDevelop;
|
def->mode = comDevelop;
|
||||||
def->set_default_value(new ConfigOptionStrings());
|
def->set_default_value(new ConfigOptionStrings());
|
||||||
|
|
||||||
def = this->add("post_process_plugin", coStrings);
|
|
||||||
def->label = L("Post-processing Plugin");
|
|
||||||
def->tooltip = L("Select a Python plugin to process the output G-code. "
|
|
||||||
"Plugins are loaded from the orca_plugins directory in your data folder. "
|
|
||||||
"The plugin will receive the G-code file path and can modify it in place.");
|
|
||||||
def->gui_type = ConfigOptionDef::GUIType::plugin_picker;
|
|
||||||
def->plugin_type = "post-processing";
|
|
||||||
def->full_width = true;
|
|
||||||
def->mode = comAdvanced;
|
|
||||||
def->set_default_value(new ConfigOptionStrings());
|
|
||||||
|
|
||||||
def = this->add("slicing_pipeline_plugin", coStrings);
|
def = this->add("slicing_pipeline_plugin", coStrings);
|
||||||
def->label = L("Slicing Pipeline Plugin");
|
def->label = L("Slicing Pipeline Plugin");
|
||||||
def->tooltip = L("Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data. Research/experimental.");
|
def->tooltip = L("Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, "
|
||||||
|
"including a final G-code post-processing step. Research/experimental.");
|
||||||
def->gui_type = ConfigOptionDef::GUIType::plugin_picker;
|
def->gui_type = ConfigOptionDef::GUIType::plugin_picker;
|
||||||
def->plugin_type = "slicing-pipeline";
|
def->plugin_type = "slicing-pipeline";
|
||||||
def->full_width = true;
|
def->full_width = true;
|
||||||
|
|||||||
@@ -1570,7 +1570,6 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
|
|||||||
((ConfigOptionBool, ooze_prevention))
|
((ConfigOptionBool, ooze_prevention))
|
||||||
((ConfigOptionString, filename_format))
|
((ConfigOptionString, filename_format))
|
||||||
((ConfigOptionStrings, post_process))
|
((ConfigOptionStrings, post_process))
|
||||||
((ConfigOptionStrings, post_process_plugin))
|
|
||||||
((ConfigOptionStrings, slicing_pipeline_plugin))
|
((ConfigOptionStrings, slicing_pipeline_plugin))
|
||||||
((ConfigOptionString, printer_model))
|
((ConfigOptionString, printer_model))
|
||||||
((ConfigOptionFloat, resolution))
|
((ConfigOptionFloat, resolution))
|
||||||
|
|||||||
@@ -618,9 +618,6 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
plugin/PluginAuditManager.hpp
|
plugin/PluginAuditManager.hpp
|
||||||
plugin/PluginResolver.cpp
|
plugin/PluginResolver.cpp
|
||||||
plugin/PluginResolver.hpp
|
plugin/PluginResolver.hpp
|
||||||
plugin/pluginTypes/gcode/GCodePluginCapability.hpp
|
|
||||||
plugin/pluginTypes/gcode/GCodePluginCapability.cpp
|
|
||||||
plugin/pluginTypes/gcode/GCodePluginCapabilityTrampoline.hpp
|
|
||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
|
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
|
||||||
|
|||||||
@@ -3182,7 +3182,7 @@ bool GUI_App::on_init_inner()
|
|||||||
static const char* const kStepNames[] = {
|
static const char* const kStepNames[] = {
|
||||||
"posSlice", "posPerimeters", "posEstimateCurledExtrusions", "posPrepareInfill", "posInfill",
|
"posSlice", "posPerimeters", "posEstimateCurledExtrusions", "posPrepareInfill", "posInfill",
|
||||||
"posIroning", "posContouring", "posSupportMaterial", "posDetectOverhangsForLift",
|
"posIroning", "posContouring", "posSupportMaterial", "posDetectOverhangsForLift",
|
||||||
"posSimplifyPath", "psWipeTower", "psSkirtBrim"
|
"posSimplifyPath", "psWipeTower", "psSkirtBrim", "psGCodePostProcess"
|
||||||
}; // order must match Slic3r::SlicingPipelineStepPlugin
|
}; // order must match Slic3r::SlicingPipelineStepPlugin
|
||||||
const char* step_name = static_cast<size_t>(step) < sizeof(kStepNames) / sizeof(kStepNames[0])
|
const char* step_name = static_cast<size_t>(step) < sizeof(kStepNames) / sizeof(kStepNames[0])
|
||||||
? kStepNames[static_cast<int>(step)] : "Unknown";
|
? kStepNames[static_cast<int>(step)] : "Unknown";
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
// file lives in the GUI layer (libslic3r must not depend on pybind11 / PluginManager).
|
// file lives in the GUI layer (libslic3r must not depend on pybind11 / PluginManager).
|
||||||
#include "libslic3r/Config.hpp"
|
#include "libslic3r/Config.hpp"
|
||||||
#include "slic3r/plugin/PluginManager.hpp"
|
#include "slic3r/plugin/PluginManager.hpp"
|
||||||
#include "slic3r/plugin/pluginTypes/gcode/GCodePluginCapability.hpp"
|
#include "slic3r/plugin/pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||||
#include "slic3r/plugin/PythonInterpreter.hpp"
|
#include "slic3r/plugin/PythonInterpreter.hpp"
|
||||||
|
|
||||||
#include <boost/algorithm/string.hpp>
|
#include <boost/algorithm/string.hpp>
|
||||||
@@ -241,27 +241,39 @@ void gcode_add_line_number(const std::string& path, const DynamicPrintConfig& co
|
|||||||
fs.close();
|
fs.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run the configured post-processing plugins on `gcode_path` in place. Plugins are executed in-process
|
// Run the configured slicing-pipeline plugins on `gcode_path` in place, at their Step.psGCodePostProcess
|
||||||
// through the embedded Python interpreter. Throws Slic3r::RuntimeError on any failure; the caller is
|
// seam. This is the same capability that runs at the geometry seams inside Print::process(); here it is
|
||||||
// responsible for removing the working copy (see run_post_process_scripts' catch block).
|
// dispatched a final time on the exported G-code, so a plugin can edit slices AND the final G-code from
|
||||||
// Entries are bare capability names; the top-level plugins manifest carries the full plugin refs.
|
// one class. Plugins are executed in-process through the embedded Python interpreter. Throws
|
||||||
|
// Slic3r::RuntimeError on any failure; the caller removes the working copy (see run_post_process_scripts'
|
||||||
|
// catch block). Entries are bare capability names; the top-level plugins manifest carries the full refs.
|
||||||
|
// A geometry-only plugin simply returns success here (it filters on ctx.step), so it costs nothing beyond
|
||||||
|
// one no-op call, but note any configured pipeline plugin still engages this post-process path (i.e. the
|
||||||
|
// non-BBL ".pp" working copy) even if it does no G-code work.
|
||||||
static void run_post_process_plugins(const ConfigOptionStrings& capabilities,
|
static void run_post_process_plugins(const ConfigOptionStrings& capabilities,
|
||||||
const ConfigOptionStrings* plugins,
|
const ConfigOptionStrings* plugins,
|
||||||
const std::string& gcode_path,
|
const std::string& gcode_path,
|
||||||
const std::string& host,
|
const std::string& host,
|
||||||
const std::string& output_name)
|
const std::string& output_name,
|
||||||
|
const DynamicPrintConfig& config)
|
||||||
{
|
{
|
||||||
// Let plugins observe the (possibly script-updated) target file name, mirroring the script env.
|
// Let plugins observe the (possibly script-updated) target file name, mirroring the script env.
|
||||||
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
|
||||||
|
|
||||||
const boost::filesystem::path gcode_file(gcode_path);
|
const boost::filesystem::path gcode_file(gcode_path);
|
||||||
|
|
||||||
auto execute_fn = [&](std::shared_ptr<GCodePluginCapability> cap, const PluginCapabilityRef& ref) {
|
auto execute_fn = [&](std::shared_ptr<SlicingPipelinePluginCapability> cap, const PluginCapabilityRef& ref) {
|
||||||
GCodePluginContext ctx;
|
SlicingPipelineContext ctx;
|
||||||
ctx.orca_version = SoftFever_VERSION;
|
ctx.orca_version = SoftFever_VERSION;
|
||||||
|
ctx.step = SlicingPipelineStepPlugin::psGCodePostProcess;
|
||||||
ctx.gcode_path = gcode_path;
|
ctx.gcode_path = gcode_path;
|
||||||
ctx.host = host;
|
ctx.host = host;
|
||||||
ctx.output_name = output_name;
|
ctx.output_name = output_name;
|
||||||
|
ctx.full_config = &config; // no live Print here; config_value() reads this
|
||||||
|
// Hand the plugin its own [tool.orcaslicer.plugin.settings] as ctx.params (same plugin_key the
|
||||||
|
// capability was resolved by), mirroring the in-pipeline dispatcher in GUI_App.cpp.
|
||||||
|
const std::string plugin_key = ref.uuid.empty() ? ref.name : ref.uuid;
|
||||||
|
ctx.params = PluginManager::instance().get_loader().get_plugin_settings(plugin_key);
|
||||||
|
|
||||||
ExecutionResult exec_result;
|
ExecutionResult exec_result;
|
||||||
try {
|
try {
|
||||||
@@ -298,11 +310,12 @@ static void run_post_process_plugins(const ConfigOptionStrings& capabilities,
|
|||||||
BOOST_LOG_TRIVIAL(info) << "Post-processing plugin " << ref.capability_name << " completed successfully";
|
BOOST_LOG_TRIVIAL(info) << "Post-processing plugin " << ref.capability_name << " completed successfully";
|
||||||
};
|
};
|
||||||
|
|
||||||
execute_capabilities_from_refs<GCodePluginCapability>(capabilities, plugins, PluginCapabilityType::PostProcessing, execute_fn);
|
execute_capabilities_from_refs<SlicingPipelinePluginCapability>(capabilities, plugins, PluginCapabilityType::SlicingPipeline, execute_fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run post-processing scripts ("post_process") and/or post-processing plugins ("post_process_plugin")
|
// Run post-processing scripts ("post_process") and/or the slicing-pipeline plugins' psGCodePostProcess
|
||||||
// if defined. Both run on the same working copy of the G-code (the ".pp" temp when make_copy), so a
|
// step ("slicing_pipeline_plugin") if defined. Both run on the same working copy of the G-code (the
|
||||||
|
// ".pp" temp when make_copy), so a
|
||||||
// plugin never opens the original file the G-code viewer keeps memory-mapped (a writable open of the
|
// plugin never opens the original file the G-code viewer keeps memory-mapped (a writable open of the
|
||||||
// mapped file fails on Windows with a sharing violation).
|
// mapped file fails on Windows with a sharing violation).
|
||||||
// Returns true if a script or plugin was executed.
|
// Returns true if a script or plugin was executed.
|
||||||
@@ -317,11 +330,13 @@ static void run_post_process_plugins(const ConfigOptionStrings& capabilities,
|
|||||||
bool run_post_process_scripts(
|
bool run_post_process_scripts(
|
||||||
std::string& src_path, bool make_copy, const std::string& host, std::string& output_name, const DynamicPrintConfig& config)
|
std::string& src_path, bool make_copy, const std::string& host, std::string& output_name, const DynamicPrintConfig& config)
|
||||||
{
|
{
|
||||||
// post_process / post_process_plugin are absent in SLA mode, hence the null checks.
|
// post_process / slicing_pipeline_plugin are absent in SLA mode, hence the null checks. G-code
|
||||||
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
|
// post-processing is now the psGCodePostProcess step of the slicing-pipeline plugin, so the same
|
||||||
const auto* post_process_plugin = config.opt<ConfigOptionStrings>("post_process_plugin");
|
// slicing_pipeline_plugin option drives both the geometry seams and this final G-code seam.
|
||||||
const bool have_scripts = post_process != nullptr && !post_process->values.empty();
|
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
|
||||||
const bool have_plugins = post_process_plugin != nullptr && !post_process_plugin->values.empty();
|
const auto* slicing_pipeline_plugin = config.opt<ConfigOptionStrings>("slicing_pipeline_plugin");
|
||||||
|
const bool have_scripts = post_process != nullptr && !post_process->values.empty();
|
||||||
|
const bool have_plugins = slicing_pipeline_plugin != nullptr && !slicing_pipeline_plugin->values.empty();
|
||||||
if (!have_scripts && !have_plugins)
|
if (!have_scripts && !have_plugins)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@@ -469,7 +484,7 @@ bool run_post_process_scripts(
|
|||||||
// Run plugins after the scripts so they observe any output_name the scripts produced. A thrown
|
// Run plugins after the scripts so they observe any output_name the scripts produced. A thrown
|
||||||
// exception is handled by the catch below, which removes the temp copy.
|
// exception is handled by the catch below, which removes the temp copy.
|
||||||
if (have_plugins) {
|
if (have_plugins) {
|
||||||
run_post_process_plugins(*post_process_plugin, config.opt<ConfigOptionStrings>("plugins"), path, host, output_name);
|
run_post_process_plugins(*slicing_pipeline_plugin, config.opt<ConfigOptionStrings>("plugins"), path, host, output_name, config);
|
||||||
}
|
}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
remove_output_name_file();
|
remove_output_name_file();
|
||||||
|
|||||||
@@ -9,9 +9,10 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
// Run post-processing scripts (the "post_process" option) and/or post-processing plugins (the
|
// Run post-processing scripts (the "post_process" option) and/or the slicing-pipeline plugins'
|
||||||
// "post_process_plugin" option) if defined. Lives in the GUI layer because plugins are executed
|
// Step.psGCodePostProcess seam (the "slicing_pipeline_plugin" option) if defined. Lives in the GUI
|
||||||
// through the embedded-Python PluginManager, which libslic3r must not depend on.
|
// layer because plugins are executed through the embedded-Python PluginManager, which libslic3r must
|
||||||
|
// not depend on.
|
||||||
// Returns true if a script or plugin was executed.
|
// Returns true if a script or plugin was executed.
|
||||||
// Returns false if neither a post-processing script nor plugin was defined.
|
// Returns false if neither a post-processing script nor plugin was defined.
|
||||||
// Throws an exception on error.
|
// Throws an exception on error.
|
||||||
|
|||||||
@@ -1794,7 +1794,7 @@ void Tab::on_value_change(const std::string& opt_key, const boost::any& value)
|
|||||||
// always carries resolved "name;uuid;capability" references that full_config() and save_to_json()
|
// always carries resolved "name;uuid;capability" references that full_config() and save_to_json()
|
||||||
// then pass downstream as-is -- no separate rebuild anywhere else.
|
// then pass downstream as-is -- no separate rebuild anywhere else.
|
||||||
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
if (const ConfigOptionDef* opt_def = m_config->def()->get(opt_key);
|
||||||
opt_def && opt_def->plugin_type != ConfigOptionDef::PluginType::None)
|
opt_def && opt_def->is_plugin_backed())
|
||||||
m_config->update_plugin_manifest();
|
m_config->update_plugin_manifest();
|
||||||
|
|
||||||
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
|
if (opt_key == "gcode_flavor" && m_type == Preset::TYPE_PRINTER) {
|
||||||
@@ -3080,12 +3080,6 @@ void TabPrint::build()
|
|||||||
option.opt.height = 15;
|
option.opt.height = 15;
|
||||||
optgroup->append_single_option_line(option, "others_settings_post_processing_scripts");
|
optgroup->append_single_option_line(option, "others_settings_post_processing_scripts");
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Post-processing Plugin"), L"param_gcode", 0);
|
|
||||||
optgroup->hide_labels();
|
|
||||||
option = optgroup->get_option("post_process_plugin");
|
|
||||||
option.opt.full_width = true;
|
|
||||||
optgroup->append_single_option_line(option, "others_settings_plugin_picker");
|
|
||||||
|
|
||||||
optgroup = page->new_optgroup(L("Slicing Pipeline Plugin"), L"param_gcode", 0);
|
optgroup = page->new_optgroup(L("Slicing Pipeline Plugin"), L"param_gcode", 0);
|
||||||
optgroup->hide_labels();
|
optgroup->hide_labels();
|
||||||
option = optgroup->get_option("slicing_pipeline_plugin");
|
option = optgroup->get_option("slicing_pipeline_plugin");
|
||||||
|
|||||||
@@ -610,7 +610,6 @@ bool PluginLoader::unload_plugin(const std::string& plugin_key, PluginCapability
|
|||||||
if (!torn_down_types.insert(cap_type).second)
|
if (!torn_down_types.insert(cap_type).second)
|
||||||
continue;
|
continue;
|
||||||
switch (cap_type) {
|
switch (cap_type) {
|
||||||
case PluginCapabilityType::PostProcessing: break;
|
|
||||||
case PluginCapabilityType::PrinterConnection: NetworkAgentFactory::deregister_python_plugin(plugin_key); break;
|
case PluginCapabilityType::PrinterConnection: NetworkAgentFactory::deregister_python_plugin(plugin_key); break;
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,6 @@
|
|||||||
#include "PluginHostApi.hpp"
|
#include "PluginHostApi.hpp"
|
||||||
#include "PyPluginPackage.hpp"
|
#include "PyPluginPackage.hpp"
|
||||||
#include "PyPluginTrampoline.hpp"
|
#include "PyPluginTrampoline.hpp"
|
||||||
#include "pluginTypes/gcode/GCodePluginCapability.hpp"
|
|
||||||
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
||||||
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
||||||
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||||
@@ -287,7 +286,6 @@ void bind_python_api(pybind11::module_& m)
|
|||||||
m.doc() = "OrcaSlicer plugin API";
|
m.doc() = "OrcaSlicer plugin API";
|
||||||
|
|
||||||
auto pluginTypes = py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
auto pluginTypes = py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
||||||
.value("PostProcessing", PluginCapabilityType::PostProcessing)
|
|
||||||
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
||||||
.value("Automation", PluginCapabilityType::Automation)
|
.value("Automation", PluginCapabilityType::Automation)
|
||||||
.value("Analysis", PluginCapabilityType::Analysis)
|
.value("Analysis", PluginCapabilityType::Analysis)
|
||||||
@@ -336,7 +334,6 @@ void bind_python_api(pybind11::module_& m)
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
|
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
|
||||||
|
|
||||||
// Make sure you register your bindings here
|
// Make sure you register your bindings here
|
||||||
GCodePluginCapability::RegisterBindings(m, pluginTypes);
|
|
||||||
PrinterAgentPluginCapability::RegisterBindings(m, pluginTypes);
|
PrinterAgentPluginCapability::RegisterBindings(m, pluginTypes);
|
||||||
ScriptPluginCapability::RegisterBindings(m, pluginTypes);
|
ScriptPluginCapability::RegisterBindings(m, pluginTypes);
|
||||||
SlicingPipelinePluginCapability::RegisterBindings(m, pluginTypes);
|
SlicingPipelinePluginCapability::RegisterBindings(m, pluginTypes);
|
||||||
|
|||||||
@@ -10,12 +10,11 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
enum class PluginCapabilityType { PostProcessing = 0, PrinterConnection, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
||||||
|
|
||||||
inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PluginCapabilityType::PostProcessing: return "post-processing";
|
|
||||||
case PluginCapabilityType::PrinterConnection: return "printer-connection";
|
case PluginCapabilityType::PrinterConnection: return "printer-connection";
|
||||||
case PluginCapabilityType::Automation: return "automation";
|
case PluginCapabilityType::Automation: return "automation";
|
||||||
case PluginCapabilityType::Analysis: return "analysis";
|
case PluginCapabilityType::Analysis: return "analysis";
|
||||||
@@ -31,7 +30,6 @@ inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
|||||||
inline std::string plugin_capability_type_display_name(PluginCapabilityType type)
|
inline std::string plugin_capability_type_display_name(PluginCapabilityType type)
|
||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PluginCapabilityType::PostProcessing: return "Post-processing";
|
|
||||||
case PluginCapabilityType::PrinterConnection: return "Printer connection";
|
case PluginCapabilityType::PrinterConnection: return "Printer connection";
|
||||||
case PluginCapabilityType::Automation: return "Automation";
|
case PluginCapabilityType::Automation: return "Automation";
|
||||||
case PluginCapabilityType::Analysis: return "Analysis";
|
case PluginCapabilityType::Analysis: return "Analysis";
|
||||||
@@ -53,8 +51,6 @@ inline PluginCapabilityType plugin_capability_type_from_string(std::string_view
|
|||||||
lowered.push_back(to_lower(ch));
|
lowered.push_back(to_lower(ch));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lowered == "post-processing")
|
|
||||||
return PluginCapabilityType::PostProcessing;
|
|
||||||
if (lowered == "printer-connection")
|
if (lowered == "printer-connection")
|
||||||
return PluginCapabilityType::PrinterConnection;
|
return PluginCapabilityType::PrinterConnection;
|
||||||
if (lowered == "automation")
|
if (lowered == "automation")
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
#include "GCodePluginCapability.hpp"
|
|
||||||
|
|
||||||
#include "GCodePluginCapabilityTrampoline.hpp"
|
|
||||||
|
|
||||||
#include <pybind11/pybind11.h>
|
|
||||||
#include <pybind11/stl.h>
|
|
||||||
|
|
||||||
namespace py = pybind11;
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
void GCodePluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
|
|
||||||
{
|
|
||||||
(void) pluginTypes;
|
|
||||||
|
|
||||||
auto gcode = module.def_submodule("gcode", "G-code API");
|
|
||||||
|
|
||||||
py::class_<GCodePluginContext, PluginContext>(gcode, "GCodePluginContext", "Context shared with G-code plugins")
|
|
||||||
.def(py::init<>())
|
|
||||||
.def_readwrite("gcode_path", &GCodePluginContext::gcode_path)
|
|
||||||
.def_readwrite("host", &GCodePluginContext::host)
|
|
||||||
.def_readwrite("output_name", &GCodePluginContext::output_name);
|
|
||||||
|
|
||||||
py::class_<GCodePluginCapability, PluginCapabilityInterface, PyGCodePluginCapabilityTrampoline, std::shared_ptr<GCodePluginCapability>>(gcode, "GCodePluginCapabilityBase")
|
|
||||||
.def(py::init<>())
|
|
||||||
.def("get_type", &GCodePluginCapability::get_type)
|
|
||||||
.def("execute", &GCodePluginCapability::execute);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
#ifndef slic3r_GCodePluginCapability_hpp_
|
|
||||||
#define slic3r_GCodePluginCapability_hpp_
|
|
||||||
|
|
||||||
#include "../../PythonPluginInterface.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
|
|
||||||
struct GCodePluginContext : public PluginContext {
|
|
||||||
std::string gcode_path;
|
|
||||||
std::string host;
|
|
||||||
std::string output_name;
|
|
||||||
};
|
|
||||||
|
|
||||||
class GCodePluginCapability : public PluginCapabilityInterface
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::PostProcessing; }
|
|
||||||
|
|
||||||
virtual ExecutionResult execute(const GCodePluginContext& ctx) = 0;
|
|
||||||
|
|
||||||
static void RegisterBindings(pybind11::module_ &module,
|
|
||||||
pybind11::enum_<PluginCapabilityType> &pluginTypes);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
#endif /* slic3r_GCodePluginCapability_hpp_ */
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
#ifndef slic3r_GCodePluginCapabilityTrampoline_hpp_
|
|
||||||
#define slic3r_GCodePluginCapabilityTrampoline_hpp_
|
|
||||||
|
|
||||||
#include <filesystem>
|
|
||||||
|
|
||||||
#include "../../PyPluginTrampoline.hpp"
|
|
||||||
#include "../../PluginAuditManager.hpp"
|
|
||||||
#include "GCodePluginCapability.hpp"
|
|
||||||
|
|
||||||
namespace Slic3r {
|
|
||||||
class PyGCodePluginCapabilityTrampoline : public PyPluginCommonTrampoline<GCodePluginCapability>
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
using PyPluginCommonTrampoline<GCodePluginCapability>::PyPluginCommonTrampoline;
|
|
||||||
|
|
||||||
ExecutionResult execute(const GCodePluginContext& ctx) override
|
|
||||||
{
|
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
|
||||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
|
||||||
[&] {
|
|
||||||
// G-code post-processing plugins may also write into the folder holding the
|
|
||||||
// current temp G-code file, in addition to the globally-allowed data_dir().
|
|
||||||
// The setup callback runs AFTER the context is constructed so the scoped root
|
|
||||||
// is not cleared by ScopedPluginAuditContext's constructor.
|
|
||||||
|
|
||||||
if (!ctx.gcode_path.empty())
|
|
||||||
::Slic3r::PluginAuditManager::instance().add_scoped_allowed_root(
|
|
||||||
std::filesystem::path(ctx.gcode_path).parent_path());
|
|
||||||
},
|
|
||||||
PYBIND11_OVERRIDE_PURE, ExecutionResult, GCodePluginCapability, execute, ctx);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
} // namespace Slic3r
|
|
||||||
|
|
||||||
#endif
|
|
||||||
@@ -26,6 +26,13 @@ void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::
|
|||||||
.value("posSimplifyPath", SlicingPipelineStepPlugin::posSimplifyPath) // covers all simplify sub-steps
|
.value("posSimplifyPath", SlicingPipelineStepPlugin::posSimplifyPath) // covers all simplify sub-steps
|
||||||
.value("psWipeTower", SlicingPipelineStepPlugin::psWipeTower)
|
.value("psWipeTower", SlicingPipelineStepPlugin::psWipeTower)
|
||||||
.value("psSkirtBrim", SlicingPipelineStepPlugin::psSkirtBrim)
|
.value("psSkirtBrim", SlicingPipelineStepPlugin::psSkirtBrim)
|
||||||
|
// Post-process seam: fires in the GUI export path AFTER the classic post_process scripts, on the
|
||||||
|
// exported G-code file. Unlike every step above it is NOT fired by Print::process(): ctx.print and
|
||||||
|
// ctx.object are None; instead ctx.gcode_path / ctx.host / ctx.output_name are set and the plugin
|
||||||
|
// edits the file at ctx.gcode_path IN PLACE. May fire more than once per slice (file export and/or
|
||||||
|
// upload each fire once, on separate working copies) and its output is not reflected in the G-code
|
||||||
|
// preview (the viewer maps the pre-post-process file). ctx.config_value()/ctx.params still work.
|
||||||
|
.value("psGCodePostProcess", SlicingPipelineStepPlugin::psGCodePostProcess)
|
||||||
.export_values();
|
.export_values();
|
||||||
|
|
||||||
// The read-graph data model (Surface / ExPolygon / the extrusion tree / LayerRegion /
|
// The read-graph data model (Surface / ExPolygon / the extrusion tree / LayerRegion /
|
||||||
@@ -44,6 +51,14 @@ void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::
|
|||||||
.def_readonly("params", &SlicingPipelineContext::params,
|
.def_readonly("params", &SlicingPipelineContext::params,
|
||||||
"read-only dict of this plugin's [tool.orcaslicer.plugin.settings] values "
|
"read-only dict of this plugin's [tool.orcaslicer.plugin.settings] values "
|
||||||
"(string->string). Parse the values you need, e.g. float(ctx.params['rate']).")
|
"(string->string). Parse the values you need, e.g. float(ctx.params['rate']).")
|
||||||
|
.def_readonly("gcode_path", &SlicingPipelineContext::gcode_path,
|
||||||
|
"Path to the working G-code file, set ONLY at Step.psGCodePostProcess. Edit it in "
|
||||||
|
"place; empty at every other step.")
|
||||||
|
.def_readonly("host", &SlicingPipelineContext::host,
|
||||||
|
"Target host at Step.psGCodePostProcess (\"File\", \"OctoPrint\", ...); empty otherwise.")
|
||||||
|
.def_readonly("output_name", &SlicingPipelineContext::output_name,
|
||||||
|
"Final output G-code name at Step.psGCodePostProcess (mirrors SLIC3R_PP_OUTPUT_NAME); "
|
||||||
|
"empty otherwise.")
|
||||||
.def_property_readonly("print", [](const SlicingPipelineContext& ctx) -> py::object {
|
.def_property_readonly("print", [](const SlicingPipelineContext& ctx) -> py::object {
|
||||||
if (ctx.print == nullptr)
|
if (ctx.print == nullptr)
|
||||||
return py::none();
|
return py::none();
|
||||||
@@ -61,9 +76,13 @@ void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::
|
|||||||
}, "orca.host.PrintObject for object-scoped steps, or None for print-wide steps. "
|
}, "orca.host.PrintObject for object-scoped steps, or None for print-wide steps. "
|
||||||
"Valid only during the execute(ctx) call.")
|
"Valid only during the execute(ctx) call.")
|
||||||
.def("config_value", [](const SlicingPipelineContext& ctx, const std::string& key) -> py::object {
|
.def("config_value", [](const SlicingPipelineContext& ctx, const std::string& key) -> py::object {
|
||||||
if (ctx.print == nullptr)
|
// In-pipeline steps read the live Print's full config; at psGCodePostProcess (print == null)
|
||||||
return py::none();
|
// fall back to the config the export path handed in.
|
||||||
return config_value_or_none(ctx.print->full_print_config(), key);
|
if (ctx.print != nullptr)
|
||||||
|
return config_value_or_none(ctx.print->full_print_config(), key);
|
||||||
|
if (ctx.full_config != nullptr)
|
||||||
|
return config_value_or_none(*ctx.full_config, key);
|
||||||
|
return py::none();
|
||||||
}, py::arg("key"),
|
}, py::arg("key"),
|
||||||
"serialized value of a resolved (full) print config option for this slice, or "
|
"serialized value of a resolved (full) print config option for this slice, or "
|
||||||
"None if absent. Shorthand for ctx.print.config_value(key).")
|
"None if absent. Shorthand for ctx.print.config_value(key).")
|
||||||
|
|||||||
@@ -16,13 +16,23 @@ namespace Slic3r {
|
|||||||
struct SlicingPipelineContext {
|
struct SlicingPipelineContext {
|
||||||
std::string orca_version;
|
std::string orca_version;
|
||||||
SlicingPipelineStepPlugin step { SlicingPipelineStepPlugin::posSlice };
|
SlicingPipelineStepPlugin step { SlicingPipelineStepPlugin::posSlice };
|
||||||
Print* print { nullptr }; // always present when dispatched
|
Print* print { nullptr }; // present for in-pipeline steps; null at psGCodePostProcess
|
||||||
const PrintObject* object { nullptr }; // null for print-wide steps
|
const PrintObject* object { nullptr }; // null for print-wide steps and psGCodePostProcess
|
||||||
// read-only per-plugin settings, populated by the dispatcher from the
|
// read-only per-plugin settings, populated by the dispatcher from the
|
||||||
// plugin's [tool.orcaslicer.plugin.settings] PEP-723 table. Exposed as
|
// plugin's [tool.orcaslicer.plugin.settings] PEP-723 table. Exposed as
|
||||||
// ctx.params (dict of string->string).
|
// ctx.params (dict of string->string).
|
||||||
std::map<std::string, std::string> params;
|
std::map<std::string, std::string> params;
|
||||||
bool cancelled() const; // -> print->canceled()
|
// Populated ONLY at Step.psGCodePostProcess (the GUI G-code export/post-process seam,
|
||||||
|
// PostProcessor.cpp). gcode_path is the working G-code file on disk that the plugin edits
|
||||||
|
// in place; host is the target ("File", "OctoPrint", ...); output_name mirrors
|
||||||
|
// SLIC3R_PP_OUTPUT_NAME. Empty at every other step.
|
||||||
|
std::string gcode_path;
|
||||||
|
std::string host;
|
||||||
|
std::string output_name;
|
||||||
|
// C++-only config fallback for psGCodePostProcess (no live Print graph there): config_value()
|
||||||
|
// reads it when `print` is null. Not exposed to Python directly. Never dereferenced elsewhere.
|
||||||
|
const DynamicPrintConfig* full_config { nullptr };
|
||||||
|
bool cancelled() const; // -> print->canceled() (false when print is null)
|
||||||
};
|
};
|
||||||
|
|
||||||
class SlicingPipelinePluginCapability : public PluginCapabilityInterface {
|
class SlicingPipelinePluginCapability : public PluginCapabilityInterface {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "SlicingPipelinePluginCapability.hpp"
|
#include "SlicingPipelinePluginCapability.hpp"
|
||||||
#include "slic3r/plugin/PyPluginTrampoline.hpp"
|
#include "slic3r/plugin/PyPluginTrampoline.hpp"
|
||||||
|
#include "slic3r/plugin/PluginAuditManager.hpp"
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
class PySlicingPipelinePluginCapabilityTrampoline : public PyPluginCommonTrampoline<SlicingPipelinePluginCapability> {
|
class PySlicingPipelinePluginCapabilityTrampoline : public PyPluginCommonTrampoline<SlicingPipelinePluginCapability> {
|
||||||
@@ -9,7 +11,18 @@ public:
|
|||||||
ExecutionResult execute(SlicingPipelineContext& ctx) override {
|
ExecutionResult execute(SlicingPipelineContext& ctx) override {
|
||||||
ORCA_PY_OVERRIDE_AUDITED(
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
[]{}, PYBIND11_OVERRIDE_PURE,
|
[&]{
|
||||||
|
// At Step.psGCodePostProcess the plugin edits the exported G-code file, which lives
|
||||||
|
// outside data_dir() (a temp/output folder), so writing to it would otherwise be
|
||||||
|
// blocked by the audit sandbox. Grant that folder as a scoped allowed root, mirroring
|
||||||
|
// the former G-code post-processing trampoline. The setup callback runs AFTER the
|
||||||
|
// audit context is constructed, so the scoped root is not cleared by its constructor.
|
||||||
|
// Empty at every other step, so no extra access is granted to the geometry hooks.
|
||||||
|
if (!ctx.gcode_path.empty())
|
||||||
|
::Slic3r::PluginAuditManager::instance().add_scoped_allowed_root(
|
||||||
|
std::filesystem::path(ctx.gcode_path).parent_path());
|
||||||
|
},
|
||||||
|
PYBIND11_OVERRIDE_PURE,
|
||||||
ExecutionResult, SlicingPipelinePluginCapability, execute, ctx);
|
ExecutionResult, SlicingPipelinePluginCapability, execute, ctx);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -44,20 +44,22 @@ TEST_CASE("SlicingPipeline hook fires once per step per object in order", "[slic
|
|||||||
|
|
||||||
using S = Slic3r::SlicingPipelineStepPlugin;
|
using S = Slic3r::SlicingPipelineStepPlugin;
|
||||||
auto count = [&](S s){ return std::count_if(calls.begin(), calls.end(), [&](const Call& c){ return c.step == s; }); };
|
auto count = [&](S s){ return std::count_if(calls.begin(), calls.end(), [&](const Call& c){ return c.step == s; }); };
|
||||||
CHECK(count(S::Slice) == 1);
|
CHECK(count(S::posSlice) == 1);
|
||||||
CHECK(count(S::Perimeters) == 1);
|
CHECK(count(S::posPerimeters) == 1);
|
||||||
CHECK(count(S::PrepareInfill) == 1); // the prepare-infill seam fires once per object
|
CHECK(count(S::posPrepareInfill) == 1); // the prepare-infill seam fires once per object
|
||||||
CHECK(count(S::Infill) == 1);
|
CHECK(count(S::posInfill) == 1);
|
||||||
CHECK(count(S::WipeTower) == 1);
|
CHECK(count(S::psWipeTower) == 1);
|
||||||
CHECK(count(S::SkirtBrim) == 1);
|
CHECK(count(S::psSkirtBrim) == 1);
|
||||||
|
// psGCodePostProcess fires from the GUI export path, never from process():
|
||||||
|
CHECK(count(S::psGCodePostProcess) == 0);
|
||||||
// print-wide steps carry a null object:
|
// print-wide steps carry a null object:
|
||||||
for (const auto& c : calls)
|
for (const auto& c : calls)
|
||||||
if (c.step == S::WipeTower || c.step == S::SkirtBrim) CHECK(c.obj == nullptr);
|
if (c.step == S::psWipeTower || c.step == S::psSkirtBrim) CHECK(c.obj == nullptr);
|
||||||
// Slice must fire before Perimeters for the same object:
|
// Slice must fire before Perimeters for the same object:
|
||||||
auto idx = [&](S s){ for (size_t i=0;i<calls.size();++i) if (calls[i].step==s) return (int)i; return -1; };
|
auto idx = [&](S s){ for (size_t i=0;i<calls.size();++i) if (calls[i].step==s) return (int)i; return -1; };
|
||||||
CHECK(idx(S::Slice) < idx(S::Perimeters));
|
CHECK(idx(S::posSlice) < idx(S::posPerimeters));
|
||||||
CHECK(idx(S::Perimeters) < idx(S::PrepareInfill)); // prepare-infill fires after perimeters...
|
CHECK(idx(S::posPerimeters) < idx(S::posPrepareInfill)); // prepare-infill fires after perimeters...
|
||||||
CHECK(idx(S::PrepareInfill) < idx(S::Infill)); // ...and before the fills are built
|
CHECK(idx(S::posPrepareInfill) < idx(S::posInfill)); // ...and before the fills are built
|
||||||
}
|
}
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
@@ -418,10 +420,10 @@ TEST_CASE("fill_surfaces mutation cascades at PrepareInfill but not at Infill",
|
|||||||
return n;
|
return n;
|
||||||
};
|
};
|
||||||
using S = Slic3r::SlicingPipelineStepPlugin;
|
using S = Slic3r::SlicingPipelineStepPlugin;
|
||||||
const size_t base = fill_paths(false, S::PrepareInfill); // active hook, no mutation
|
const size_t base = fill_paths(false, S::posPrepareInfill); // active hook, no mutation
|
||||||
CHECK(base > 0);
|
CHECK(base > 0);
|
||||||
CHECK(fill_paths(true, S::PrepareInfill) < base); // mutation before make_fills cascades
|
CHECK(fill_paths(true, S::posPrepareInfill) < base); // mutation before make_fills cascades
|
||||||
CHECK(fill_paths(true, S::Infill) == base); // mutation after make_fills is a no-op (v1)
|
CHECK(fill_paths(true, S::posInfill) == base); // mutation after make_fills is a no-op (v1)
|
||||||
}
|
}
|
||||||
|
|
||||||
// lslices (the layer's merged islands) are built once in slice() and never rebuilt by
|
// lslices (the layer's merged islands) are built once in slice() and never rebuilt by
|
||||||
|
|||||||
@@ -410,14 +410,14 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[
|
|||||||
namespace fs = boost::filesystem;
|
namespace fs = boost::filesystem;
|
||||||
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca_plugins_%%%%-%%%%.json");
|
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca_plugins_%%%%-%%%%.json");
|
||||||
const std::vector<std::string> refs = {
|
const std::vector<std::string> refs = {
|
||||||
"local_plugin;;post_process",
|
"local_plugin;;inset",
|
||||||
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;post_process"
|
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;inset"
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unique_ptr<DynamicPrintConfig> config_ptr(
|
std::unique_ptr<DynamicPrintConfig> config_ptr(
|
||||||
DynamicPrintConfig::new_from_defaults_keys({"post_process_plugin"}));
|
DynamicPrintConfig::new_from_defaults_keys({"slicing_pipeline_plugin"}));
|
||||||
DynamicPrintConfig config = std::move(*config_ptr);
|
DynamicPrintConfig config = std::move(*config_ptr);
|
||||||
config.option<ConfigOptionStrings>("post_process_plugin", true)->values = refs;
|
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = refs;
|
||||||
config.save_to_json(tmp.string(), "test_preset", "User", "1.0.0.0");
|
config.save_to_json(tmp.string(), "test_preset", "User", "1.0.0.0");
|
||||||
|
|
||||||
nlohmann::json j;
|
nlohmann::json j;
|
||||||
@@ -425,7 +425,7 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[
|
|||||||
boost::nowide::ifstream ifs(tmp.string());
|
boost::nowide::ifstream ifs(tmp.string());
|
||||||
ifs >> j;
|
ifs >> j;
|
||||||
}
|
}
|
||||||
REQUIRE(j["post_process_plugin"] == nlohmann::json(refs));
|
REQUIRE(j["slicing_pipeline_plugin"] == nlohmann::json(refs));
|
||||||
CHECK_FALSE(j.contains("plugins"));
|
CHECK_FALSE(j.contains("plugins"));
|
||||||
|
|
||||||
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
|
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
|
||||||
@@ -434,7 +434,7 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[
|
|||||||
std::string reason;
|
std::string reason;
|
||||||
REQUIRE(reloaded.load_from_json(tmp.string(), substitutions, true, key_values, reason) == 0);
|
REQUIRE(reloaded.load_from_json(tmp.string(), substitutions, true, key_values, reason) == 0);
|
||||||
CHECK(reason.empty());
|
CHECK(reason.empty());
|
||||||
CHECK(reloaded.option<ConfigOptionStrings>("post_process_plugin")->values == refs);
|
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
|
||||||
|
|
||||||
fs::remove(tmp);
|
fs::remove(tmp);
|
||||||
}
|
}
|
||||||
@@ -446,17 +446,17 @@ TEST_CASE("plugin capability references survive string-map serialization", "[Con
|
|||||||
};
|
};
|
||||||
|
|
||||||
DynamicPrintConfig original = DynamicPrintConfig::full_print_config();
|
DynamicPrintConfig original = DynamicPrintConfig::full_print_config();
|
||||||
original.option<ConfigOptionStrings>("post_process_plugin", true)->values = refs;
|
original.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = refs;
|
||||||
|
|
||||||
std::map<std::string, std::string> serialized{
|
std::map<std::string, std::string> serialized{
|
||||||
{"post_process_plugin", original.option<ConfigOptionStrings>("post_process_plugin")->serialize()}
|
{"slicing_pipeline_plugin", original.option<ConfigOptionStrings>("slicing_pipeline_plugin")->serialize()}
|
||||||
};
|
};
|
||||||
CHECK(serialized["post_process_plugin"].find("\"master_plugin;;header-stamp\"") != std::string::npos);
|
CHECK(serialized["slicing_pipeline_plugin"].find("\"master_plugin;;header-stamp\"") != std::string::npos);
|
||||||
|
|
||||||
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
|
DynamicPrintConfig reloaded = DynamicPrintConfig::full_print_config();
|
||||||
reloaded.load_string_map(serialized, ForwardCompatibilitySubstitutionRule::Disable);
|
reloaded.load_string_map(serialized, ForwardCompatibilitySubstitutionRule::Disable);
|
||||||
|
|
||||||
CHECK(reloaded.option<ConfigOptionStrings>("post_process_plugin")->values == refs);
|
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE("parse_capability_ref parses local and cloud references", "[Config][plugin]") {
|
TEST_CASE("parse_capability_ref parses local and cloud references", "[Config][plugin]") {
|
||||||
@@ -502,14 +502,13 @@ struct PluginResolverFixture {
|
|||||||
TEST_CASE_METHOD(PluginResolverFixture,
|
TEST_CASE_METHOD(PluginResolverFixture,
|
||||||
"update_plugin_manifest derives references generically from plugin-backed options",
|
"update_plugin_manifest derives references generically from plugin-backed options",
|
||||||
"[Config][plugins]") {
|
"[Config][plugins]") {
|
||||||
// Both scalar (printer_agent) and vector (post_process_plugin, slicing_pipeline_plugin) options
|
// Both scalar (printer_agent) and vector (slicing_pipeline_plugin) options opt in via a non-empty
|
||||||
// opt in via a non-empty ConfigOptionDef::plugin_type (is_plugin_backed) and are resolved with it
|
// ConfigOptionDef::plugin_type (is_plugin_backed) and are resolved with it -- there is no hardcoded
|
||||||
// -- there is no hardcoded per-option switch. printer_agent in particular relies on its plugin_type
|
// per-option switch. printer_agent in particular relies on its plugin_type metadata being wired up
|
||||||
// metadata being wired up (it is edited via a dedicated widget, not the plugin_picker).
|
// (it is edited via a dedicated widget, not the plugin_picker).
|
||||||
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
|
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
|
||||||
{"post_process_plugin", "slicing_pipeline_plugin", "printer_agent"}));
|
{"slicing_pipeline_plugin", "printer_agent"}));
|
||||||
DynamicPrintConfig config = std::move(*config_ptr);
|
DynamicPrintConfig config = std::move(*config_ptr);
|
||||||
config.option<ConfigOptionStrings>("post_process_plugin", true)->values = {"pp"};
|
|
||||||
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"sp"};
|
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"sp"};
|
||||||
config.option<ConfigOptionString>("printer_agent", true)->value = "agent";
|
config.option<ConfigOptionString>("printer_agent", true)->value = "agent";
|
||||||
|
|
||||||
@@ -517,23 +516,22 @@ TEST_CASE_METHOD(PluginResolverFixture,
|
|||||||
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
|
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
|
||||||
|
|
||||||
using Catch::Matchers::VectorContains;
|
using Catch::Matchers::VectorContains;
|
||||||
REQUIRE_THAT(manifest, VectorContains(std::string("pp;;post-processing")));
|
|
||||||
REQUIRE_THAT(manifest, VectorContains(std::string("sp;;slicing-pipeline")));
|
REQUIRE_THAT(manifest, VectorContains(std::string("sp;;slicing-pipeline")));
|
||||||
REQUIRE_THAT(manifest, VectorContains(std::string("agent;;printer-connection")));
|
REQUIRE_THAT(manifest, VectorContains(std::string("agent;;printer-connection")));
|
||||||
CHECK(manifest.size() == 3);
|
CHECK(manifest.size() == 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
TEST_CASE_METHOD(PluginResolverFixture,
|
TEST_CASE_METHOD(PluginResolverFixture,
|
||||||
"update_plugin_manifest de-duplicates references and skips unset options",
|
"update_plugin_manifest de-duplicates references and skips unset options",
|
||||||
"[Config][plugins]") {
|
"[Config][plugins]") {
|
||||||
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
|
std::unique_ptr<DynamicPrintConfig> config_ptr(DynamicPrintConfig::new_from_defaults_keys(
|
||||||
{"post_process_plugin", "printer_agent"}));
|
{"slicing_pipeline_plugin", "printer_agent"}));
|
||||||
DynamicPrintConfig config = std::move(*config_ptr);
|
DynamicPrintConfig config = std::move(*config_ptr);
|
||||||
config.option<ConfigOptionStrings>("post_process_plugin", true)->values = {"x", "x"}; // duplicate
|
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"x", "x"}; // duplicate
|
||||||
// printer_agent stays at its default empty value -> contributes nothing to the manifest.
|
// printer_agent stays at its default empty value -> contributes nothing to the manifest.
|
||||||
|
|
||||||
config.update_plugin_manifest();
|
config.update_plugin_manifest();
|
||||||
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
|
const std::vector<std::string> manifest = config.option<ConfigOptionStrings>("plugins")->values;
|
||||||
|
|
||||||
CHECK(manifest == std::vector<std::string>{"x;;post-processing"});
|
CHECK(manifest == std::vector<std::string>{"x;;slicing-pipeline"});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,9 @@ using Slic3r::PluginCapabilityIdentifier;
|
|||||||
using Slic3r::PluginCapabilityType;
|
using Slic3r::PluginCapabilityType;
|
||||||
|
|
||||||
TEST_CASE("PluginCapabilityIdentifier equality includes plugin_key", "[plugin][identifier]") {
|
TEST_CASE("PluginCapabilityIdentifier equality includes plugin_key", "[plugin][identifier]") {
|
||||||
PluginCapabilityIdentifier a{PluginCapabilityType::PostProcessing, "Cleanup", "a.py"};
|
PluginCapabilityIdentifier a{PluginCapabilityType::SlicingPipeline, "Cleanup", "a.py"};
|
||||||
PluginCapabilityIdentifier b{PluginCapabilityType::PostProcessing, "Cleanup", "b.py"};
|
PluginCapabilityIdentifier b{PluginCapabilityType::SlicingPipeline, "Cleanup", "b.py"};
|
||||||
PluginCapabilityIdentifier a2{PluginCapabilityType::PostProcessing, "Cleanup", "a.py"};
|
PluginCapabilityIdentifier a2{PluginCapabilityType::SlicingPipeline, "Cleanup", "a.py"};
|
||||||
|
|
||||||
CHECK(a == a2);
|
CHECK(a == a2);
|
||||||
CHECK_FALSE(a == b); // same (type,name), different plugin_key -> distinct
|
CHECK_FALSE(a == b); // same (type,name), different plugin_key -> distinct
|
||||||
@@ -18,8 +18,8 @@ TEST_CASE("PluginCapabilityIdentifier equality includes plugin_key", "[plugin][i
|
|||||||
|
|
||||||
TEST_CASE("PluginCapabilityIdentifier is usable as a hash-map key", "[plugin][identifier]") {
|
TEST_CASE("PluginCapabilityIdentifier is usable as a hash-map key", "[plugin][identifier]") {
|
||||||
std::unordered_map<PluginCapabilityIdentifier, int> m;
|
std::unordered_map<PluginCapabilityIdentifier, int> m;
|
||||||
m[{PluginCapabilityType::PostProcessing, "Cleanup", "a.py"}] = 1;
|
m[{PluginCapabilityType::SlicingPipeline, "Cleanup", "a.py"}] = 1;
|
||||||
m[{PluginCapabilityType::PostProcessing, "Cleanup", "b.py"}] = 2; // no collision
|
m[{PluginCapabilityType::SlicingPipeline, "Cleanup", "b.py"}] = 2; // no collision
|
||||||
CHECK(m.size() == 2);
|
CHECK(m.size() == 2);
|
||||||
CHECK(m.at({PluginCapabilityType::PostProcessing, "Cleanup", "a.py"}) == 1);
|
CHECK(m.at({PluginCapabilityType::SlicingPipeline, "Cleanup", "a.py"}) == 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,7 +81,8 @@ TEST_CASE("orca.slicing module: Step enum, context, and a Python capability can
|
|||||||
REQUIRE(py::hasattr(orca, "slicing"));
|
REQUIRE(py::hasattr(orca, "slicing"));
|
||||||
py::object slicing = orca.attr("slicing");
|
py::object slicing = orca.attr("slicing");
|
||||||
CHECK(py::hasattr(slicing, "Step"));
|
CHECK(py::hasattr(slicing, "Step"));
|
||||||
CHECK(py::hasattr(slicing.attr("Step"), "Slice"));
|
CHECK(py::hasattr(slicing.attr("Step"), "posSlice"));
|
||||||
|
CHECK(py::hasattr(slicing.attr("Step"), "psGCodePostProcess"));
|
||||||
CHECK(py::hasattr(slicing, "SlicingPipelineContext"));
|
CHECK(py::hasattr(slicing, "SlicingPipelineContext"));
|
||||||
CHECK(py::hasattr(slicing, "SlicingPipelineCapabilityBase"));
|
CHECK(py::hasattr(slicing, "SlicingPipelineCapabilityBase"));
|
||||||
|
|
||||||
@@ -126,6 +127,79 @@ TEST_CASE("orca.slicing is workflow-only: context exposes raw print/object; view
|
|||||||
CHECK(pyctx.attr("object").is_none());
|
CHECK(pyctx.attr("object").is_none());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "libslic3r/PrintConfig.hpp" // DynamicPrintConfig for the psGCodePostProcess context
|
||||||
|
#include <boost/filesystem.hpp>
|
||||||
|
#include <boost/nowide/fstream.hpp>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
// psGCodePostProcess is the merged post-processing seam: no live Print (print/object are None), the
|
||||||
|
// plugin edits the file at ctx.gcode_path in place, and ctx.config_value() falls back to the config
|
||||||
|
// the export path handed in. Exercising the real bindings by calling the Python execute() directly
|
||||||
|
// (not the C++ audit trampoline) keeps this a pure binding-surface test.
|
||||||
|
TEST_CASE("orca.slicing psGCodePostProcess context: file edit in place + config fallback", "[slicing_pipeline]") {
|
||||||
|
namespace fs = boost::filesystem;
|
||||||
|
ensure_python_initialized();
|
||||||
|
import_orca_module();
|
||||||
|
py::gil_scoped_acquire gil;
|
||||||
|
|
||||||
|
const fs::path gpath = fs::temp_directory_path() / fs::unique_path("orca_pp_%%%%-%%%%.gcode");
|
||||||
|
{
|
||||||
|
boost::nowide::ofstream ofs(gpath.string());
|
||||||
|
ofs << "; header\nG1 X0 Y0\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config the plugin reads back through ctx.config_value() (there is no live Print at this step).
|
||||||
|
Slic3r::DynamicPrintConfig config = Slic3r::DynamicPrintConfig::full_print_config();
|
||||||
|
config.set_key_value("layer_height", new Slic3r::ConfigOptionFloat(0.2));
|
||||||
|
|
||||||
|
Slic3r::SlicingPipelineContext ctx;
|
||||||
|
ctx.orca_version = "test";
|
||||||
|
ctx.step = Slic3r::SlicingPipelineStepPlugin::psGCodePostProcess;
|
||||||
|
ctx.gcode_path = gpath.string();
|
||||||
|
ctx.host = "File";
|
||||||
|
ctx.output_name = "final.gcode";
|
||||||
|
ctx.full_config = &config; // print stays null
|
||||||
|
|
||||||
|
py::object pyctx = py::cast(&ctx, py::return_value_policy::reference);
|
||||||
|
CHECK(pyctx.attr("gcode_path").cast<std::string>() == gpath.string());
|
||||||
|
CHECK(pyctx.attr("host").cast<std::string>() == "File");
|
||||||
|
CHECK(pyctx.attr("output_name").cast<std::string>() == "final.gcode");
|
||||||
|
CHECK(pyctx.attr("print").is_none());
|
||||||
|
CHECK(pyctx.attr("object").is_none());
|
||||||
|
CHECK(pyctx.attr("step").cast<Slic3r::SlicingPipelineStepPlugin>()
|
||||||
|
== Slic3r::SlicingPipelineStepPlugin::psGCodePostProcess);
|
||||||
|
CHECK_FALSE(pyctx.attr("cancelled")().cast<bool>()); // null print -> not cancelled
|
||||||
|
// config_value() resolves from full_config when print is null; unknown keys are None.
|
||||||
|
CHECK_FALSE(pyctx.attr("config_value")("layer_height").is_none());
|
||||||
|
CHECK(pyctx.attr("config_value")("this_key_does_not_exist").is_none());
|
||||||
|
|
||||||
|
// A Python capability edits the file in place through ctx.gcode_path. Calling execute() directly
|
||||||
|
// in Python dispatches to the Python method (no C++ trampoline), so this needs no audit context.
|
||||||
|
py::module_ main = py::module_::import("__main__");
|
||||||
|
main.attr("_pp_ctx") = pyctx;
|
||||||
|
py::exec(R"(
|
||||||
|
import orca
|
||||||
|
class Stamp(orca.slicing.SlicingPipelineCapabilityBase):
|
||||||
|
def get_name(self): return "stamp"
|
||||||
|
def execute(self, ctx):
|
||||||
|
assert ctx.step == orca.slicing.Step.psGCodePostProcess
|
||||||
|
assert ctx.print is None and ctx.object is None
|
||||||
|
with open(ctx.gcode_path, "a") as f:
|
||||||
|
f.write("; stamped by " + ctx.host + "\n")
|
||||||
|
return orca.ExecutionResult.success("ok")
|
||||||
|
_pp_result = Stamp().execute(_pp_ctx)
|
||||||
|
)");
|
||||||
|
CHECK(main.attr("_pp_result").attr("message").cast<std::string>() == std::string("ok"));
|
||||||
|
|
||||||
|
std::string contents;
|
||||||
|
{
|
||||||
|
boost::nowide::ifstream ifs(gpath.string());
|
||||||
|
std::stringstream ss; ss << ifs.rdbuf(); contents = ss.str();
|
||||||
|
}
|
||||||
|
CHECK(contents.find("; stamped by File") != std::string::npos);
|
||||||
|
fs::remove(gpath);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Toolpath helpers for the raw-graph tests.
|
// Toolpath helpers for the raw-graph tests.
|
||||||
//
|
//
|
||||||
|
|||||||
Reference in New Issue
Block a user