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:
SoftFever
2026-07-10 19:57:35 +08:00
parent 21ed68963f
commit 19352215da
25 changed files with 288 additions and 200 deletions

View File

@@ -410,14 +410,14 @@ TEST_CASE("save_to_json round-trips plugin capability references as strings", "[
namespace fs = boost::filesystem;
const fs::path tmp = fs::temp_directory_path() / fs::unique_path("orca_plugins_%%%%-%%%%.json");
const std::vector<std::string> refs = {
"local_plugin;;post_process",
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;post_process"
"local_plugin;;inset",
"cloud_plugin;550e8400-e29b-41d4-a716-446655440000;inset"
};
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);
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");
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());
ifs >> j;
}
REQUIRE(j["post_process_plugin"] == nlohmann::json(refs));
REQUIRE(j["slicing_pipeline_plugin"] == nlohmann::json(refs));
CHECK_FALSE(j.contains("plugins"));
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;
REQUIRE(reloaded.load_from_json(tmp.string(), substitutions, true, key_values, reason) == 0);
CHECK(reason.empty());
CHECK(reloaded.option<ConfigOptionStrings>("post_process_plugin")->values == refs);
CHECK(reloaded.option<ConfigOptionStrings>("slicing_pipeline_plugin")->values == refs);
fs::remove(tmp);
}
@@ -446,17 +446,17 @@ TEST_CASE("plugin capability references survive string-map serialization", "[Con
};
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{
{"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();
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]") {
@@ -502,14 +502,13 @@ struct PluginResolverFixture {
TEST_CASE_METHOD(PluginResolverFixture,
"update_plugin_manifest derives references generically from plugin-backed options",
"[Config][plugins]") {
// Both scalar (printer_agent) and vector (post_process_plugin, slicing_pipeline_plugin) options
// opt in via a non-empty ConfigOptionDef::plugin_type (is_plugin_backed) and are resolved with it
// -- there is no hardcoded per-option switch. printer_agent in particular relies on its plugin_type
// metadata being wired up (it is edited via a dedicated widget, not the plugin_picker).
// Both scalar (printer_agent) and vector (slicing_pipeline_plugin) options opt in via a non-empty
// ConfigOptionDef::plugin_type (is_plugin_backed) and are resolved with it -- there is no hardcoded
// per-option switch. printer_agent in particular relies on its plugin_type metadata being wired up
// (it is edited via a dedicated widget, not the plugin_picker).
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);
config.option<ConfigOptionStrings>("post_process_plugin", true)->values = {"pp"};
config.option<ConfigOptionStrings>("slicing_pipeline_plugin", true)->values = {"sp"};
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;
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("agent;;printer-connection")));
CHECK(manifest.size() == 3);
CHECK(manifest.size() == 2);
}
TEST_CASE_METHOD(PluginResolverFixture,
"update_plugin_manifest de-duplicates references and skips unset options",
"[Config][plugins]") {
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);
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.
config.update_plugin_manifest();
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"});
}