feat(plugin): add the slicing-pipeline plugin capability

Introduces a plugin capability that runs Python at the seams of Print::process(),
letting a plugin read and rewrite slicing state as it is computed.

- New slicing_pipeline_plugin config option; selected plugin refs are serialized
  into the print manifest.
- Print gains an injectable hook fired at each pipeline step (posSlice,
  posPerimeters, posInfill, ...). It is a no-op when unset, fires only on genuine
  (re)computation, and never on the use-cache path.
- orca.slicing submodule: SlicingPipelineCapabilityBase plus a trampoline and a
  Step enum. Capabilities read the live graph through zero-copy int64 numpy views
  (contour/holes geometry with unscaled coordinates, flattened toolpath data) and
  edit it through 2D-geometry mutators with cache-invariant refresh.
- GUI dispatcher runs capabilities during slicing under the GIL, turns plugin
  errors into slicing errors, honors cancellation, and adds the plugin picker.
- Ships the InsetEverySlice sample plugin and binding/hook tests.
This commit is contained in:
SoftFever
2026-07-04 04:33:20 +08:00
parent 43bc76d9a9
commit b0bacdd00b
23 changed files with 1622 additions and 38 deletions

View File

@@ -102,10 +102,14 @@ void execute_capabilities_from_refs(const ConfigOptionStrings& capabilities,
{
PluginManager& plugin_mgr = PluginManager::instance();
// Log prefix derived from the capability type so each capability family (Post-processing,
// Slicing Pipeline, ...) tags its dispatch diagnostics with its own display name.
const std::string tag = plugin_capability_type_display_name(type);
const bool has_any = std::any_of(capabilities.values.begin(), capabilities.values.end(),
[](const std::string& s) { return !s.empty(); });
if (has_any && !plugin_mgr.get_loader().wait_for_all_plugin_loads(std::chrono::seconds(10))) {
BOOST_LOG_TRIVIAL(warning) << "Post-process: timed out waiting for plugin loads; unresolved capabilities will be skipped";
BOOST_LOG_TRIVIAL(warning) << tag << ": timed out waiting for plugin loads; unresolved capabilities will be skipped";
}
for (const std::string& capability : capabilities.values) {
@@ -127,7 +131,7 @@ void execute_capabilities_from_refs(const ConfigOptionStrings& capabilities,
}
if (!ref) {
BOOST_LOG_TRIVIAL(warning) << "Post-processing: no plugin reference found for capability '" << capability << "'; skipping";
BOOST_LOG_TRIVIAL(warning) << tag << ": no plugin reference found for capability '" << capability << "'; skipping";
continue;
}
@@ -136,19 +140,19 @@ void execute_capabilities_from_refs(const ConfigOptionStrings& capabilities,
cap = plugin_mgr.get_loader().get_plugin_capability_by_name(plugin_key, type, cap_name);
if (!cap) {
BOOST_LOG_TRIVIAL(warning) << "Post-processing: no loaded capability '" << cap_name
BOOST_LOG_TRIVIAL(warning) << tag << ": no loaded capability '" << cap_name
<< "' for plugin '" << plugin_key << "'; skipping";
continue;
}
if (!cap->enabled) {
BOOST_LOG_TRIVIAL(warning) << "Post-processing: capability '" << cap_name
BOOST_LOG_TRIVIAL(warning) << tag << ": capability '" << cap_name
<< "' for plugin '" << plugin_key << "' is disabled; skipping";
continue;
}
auto plugin_capability = std::dynamic_pointer_cast<T>(cap->instance);
if (!plugin_capability) {
BOOST_LOG_TRIVIAL(warning) << "Post-processing: capability '" << cap_name
BOOST_LOG_TRIVIAL(warning) << tag << ": capability '" << cap_name
<< "' (plugin_key=" << cap->plugin_key
<< ") is not a " << plugin_capability_type_to_string(type) << "; skipping";
continue;