mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-08-02 15:52:14 +00:00
update docs and comments
This commit is contained in:
@@ -27,8 +27,6 @@ See ``plugin_development.md`` for the full reference, and ``host_ui_panel.py`` f
|
|||||||
richer worked example built on the ``orca.host`` read-only API.
|
richer worked example built on the ``orca.host`` read-only API.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
import orca
|
import orca
|
||||||
|
|
||||||
|
|
||||||
@@ -85,11 +83,15 @@ class ExamplePostProcess(orca.gcode.GCodePluginCapabilityBase):
|
|||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
# Capability 3 - a printer-connection (agent) capability
|
# Capability 3 - a printer-connection (agent) capability
|
||||||
# Registers a network printer agent on load. The host serialises each native
|
# Registers a network printer agent on load. Unlike the other capabilities, an
|
||||||
# agent call into a single JSON request envelope
|
# agent is driven through the native printer-agent surface: the host calls
|
||||||
# ({command, request_id, dev_id, payload}); you dispatch on request["command"]
|
# individual operations (connect_printer, start_discovery, start_print, ...)
|
||||||
# and return a JSON response envelope string. Delete this whole class if your
|
# directly on your object. orca.printer_agent.PrinterAgentBase declares ~30
|
||||||
# plugin is not a printer agent.
|
# pure-virtual operations and EVERY one must be overridden - an operation you
|
||||||
|
# leave out raises RuntimeError the moment the host calls it. This skeleton
|
||||||
|
# implements just enough to load and be discovered; see
|
||||||
|
# resources/orca_plugins/BBLPrinterAgentPlugin.py for a complete working agent
|
||||||
|
# and the full method list. Delete this whole class if you are not writing one.
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
class ExamplePrinterAgent(orca.printer_agent.PrinterAgentBase):
|
class ExamplePrinterAgent(orca.printer_agent.PrinterAgentBase):
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
@@ -103,28 +105,54 @@ class ExamplePrinterAgent(orca.printer_agent.PrinterAgentBase):
|
|||||||
description="Skeleton printer agent.",
|
description="Skeleton printer agent.",
|
||||||
)
|
)
|
||||||
|
|
||||||
def send_command(self, request_json):
|
# --- connection ------------------------------------------------------- #
|
||||||
# Parse the request envelope and dispatch on its "command".
|
def connect_printer(self, dev_id, dev_ip, username, password, use_ssl) -> int:
|
||||||
try:
|
# TODO: open your transport (MQTT/HTTP/serial/...). Return 0 on success.
|
||||||
request = json.loads(request_json or "{}")
|
return 0
|
||||||
except json.JSONDecodeError:
|
|
||||||
request = {}
|
|
||||||
command = request.get("command", "")
|
|
||||||
request_id = request.get("request_id", "")
|
|
||||||
|
|
||||||
# TODO: handle the commands your device supports and build a real response.
|
def disconnect_printer(self) -> int:
|
||||||
return json.dumps({
|
# TODO: tear the transport down. Return 0 on success.
|
||||||
"request_id": request_id,
|
return 0
|
||||||
"status": "error",
|
|
||||||
"message": f"unhandled command: {command}",
|
|
||||||
})
|
|
||||||
|
|
||||||
def send_command_with_progress(self, request_json, update_fn, cancel_fn):
|
# --- discovery -------------------------------------------------------- #
|
||||||
# Optional. Override only for long-running commands (uploads, prints).
|
def start_discovery(self, start=True, sending=False) -> bool:
|
||||||
# update_fn(stage, percent, message) - push progress to the host UI.
|
# TODO: start/stop scanning the network for printers. Return True on success.
|
||||||
# cancel_fn() -> bool - poll it; abort if it returns True.
|
return True
|
||||||
# Default behaviour is to run as a plain send_command.
|
|
||||||
return self.send_command(request_json)
|
# --- messaging -------------------------------------------------------- #
|
||||||
|
def send_message(self, dev_id, json_str, qos=0, flag=0) -> int:
|
||||||
|
# TODO: publish a control message to the device. Return 0 on success.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def get_user_selected_machine(self) -> str:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def set_user_selected_machine(self, dev_id) -> int:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# --- printing --------------------------------------------------------- #
|
||||||
|
def start_print(self, params=None, update_fn=None, cancel_fn=None, wait_fn=None) -> int:
|
||||||
|
# params is an orca.printer_agent.PrintParams. The host also passes callbacks:
|
||||||
|
# update_fn(stage, percent, message) - report progress to the host UI
|
||||||
|
# cancel_fn() -> bool - poll it; abort if it returns True
|
||||||
|
# wait_fn(...) - host-provided wait hook
|
||||||
|
# Return 0 on success.
|
||||||
|
return 0
|
||||||
|
|
||||||
|
# --- filament sync ---------------------------------------------------- #
|
||||||
|
def get_filament_sync_mode(self):
|
||||||
|
return orca.printer_agent.FilamentSyncMode.None_
|
||||||
|
|
||||||
|
def fetch_filament_info(self, dev_id) -> bool:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# NOTE: PrinterAgentBase has more pure-virtual operations a real agent must
|
||||||
|
# implement, e.g. send_message_to_printer, bind_detect, bind/unbind, ping_bind,
|
||||||
|
# check_cert/install_device_cert, request_bind_ticket, start_local_print,
|
||||||
|
# start_local_print_with_record, start_sdcard_print, start_send_gcode_to_sdcard,
|
||||||
|
# and the host-callback setters (set_server_callback, set_on_message_fn,
|
||||||
|
# set_on_printer_connected_fn, set_queue_on_main_fn, ...). See
|
||||||
|
# BBLPrinterAgentPlugin.py for the full set and expected signatures.
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------- #
|
# --------------------------------------------------------------------------- #
|
||||||
|
|||||||
@@ -87,13 +87,13 @@ allows everything.
|
|||||||
|
|
||||||
```cpp
|
```cpp
|
||||||
enum class AuditMode {
|
enum class AuditMode {
|
||||||
// Permissive reads, restricted writes. Python must be able to read stdlib
|
// Import/loading phase: allow reads anywhere, only block writes
|
||||||
// modules and the plugin file during import/on-load, so reads are allowed;
|
// outside allowed roots. Python needs to read stdlib modules
|
||||||
// only writes outside the allowed roots are blocked.
|
// during import and those are not inside plugin directories.
|
||||||
Loading,
|
Loading,
|
||||||
|
|
||||||
// Restricted reads AND writes: every file path must resolve inside an
|
// Execution phase: block both reads and writes outside allowed
|
||||||
// allowed root, or it is blocked.
|
// roots, plus subprocess/socket/ctypes.
|
||||||
Enforcing,
|
Enforcing,
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
@@ -318,8 +318,10 @@ This version is deliberately minimal. Do **not** treat it as a hardened sandbox.
|
|||||||
- **Only the `open` event is enforced.** `subprocess.Popen`, `os.system`, `socket.*`,
|
- **Only the `open` event is enforced.** `subprocess.Popen`, `os.system`, `socket.*`,
|
||||||
`ctypes.*` and friends are *not* blocked. (The `Enforcing` enum comment describes an
|
`ctypes.*` and friends are *not* blocked. (The `Enforcing` enum comment describes an
|
||||||
aspiration, not current behavior.)
|
aspiration, not current behavior.)
|
||||||
- **`os.open` slips through.** It raises the `open` event with `mode = None`, so the
|
- **Non‑string paths slip through the `open` check.** The audit callback parses only a
|
||||||
`"s|si"` parse fails and the call is allowed. Low‑level opens are currently unaudited.
|
string path (`"s|si"`); any `open`‑event call whose first argument is bytes or an integer
|
||||||
|
file descriptor — including `os.open`, which additionally passes `mode = None` — fails the
|
||||||
|
parse and is allowed. Low‑level and non‑`str` opens are currently unaudited.
|
||||||
- **`open(path, "x")`** (exclusive create — a write) contains no `w`/`a`/`+`, so it is
|
- **`open(path, "x")`** (exclusive create — a write) contains no `w`/`a`/`+`, so it is
|
||||||
classified as a read and allowed under `Loading`.
|
classified as a read and allowed under `Loading`.
|
||||||
- **Non‑`open` filesystem mutations are unaudited.** `os.remove`, `os.rename`, `os.mkdir`,
|
- **Non‑`open` filesystem mutations are unaudited.** `os.remove`, `os.rename`, `os.mkdir`,
|
||||||
|
|||||||
@@ -103,7 +103,7 @@ fields live at the TOML root. Parsing is implemented in
|
|||||||
| `author` | `[tool.orcaslicer.plugin]` | optional | |
|
| `author` | `[tool.orcaslicer.plugin]` | optional | |
|
||||||
| `version` | `[tool.orcaslicer.plugin]` | recommended | |
|
| `version` | `[tool.orcaslicer.plugin]` | recommended | |
|
||||||
| `dependencies` | TOML root | optional | array of pip requirements (see [Dependencies](#dependencies)) |
|
| `dependencies` | TOML root | optional | array of pip requirements (see [Dependencies](#dependencies)) |
|
||||||
| `requires-python` | TOML root | optional | **stored but not enforced** against the bundled interpreter today |
|
| `requires-python` | TOML root | optional | **read but not stored or enforced** against the bundled interpreter today |
|
||||||
|
|
||||||
> **The metadata block no longer declares a `type`.** A plugin's type(s) are derived from the
|
> **The metadata block no longer declares a `type`.** A plugin's type(s) are derived from the
|
||||||
> capability classes it registers (each capability's `get_type()`), so the same metadata block
|
> capability classes it registers (each capability's `get_type()`), so the same metadata block
|
||||||
@@ -396,14 +396,15 @@ class SamplePlugin(orca.base):
|
|||||||
OrcaSlicer instantiates the package class (it must be callable as `SamplePlugin()` with no
|
OrcaSlicer instantiates the package class (it must be callable as `SamplePlugin()` with no
|
||||||
arguments), calls `register_capabilities()`, then instantiates each registered capability.
|
arguments), calls `register_capabilities()`, then instantiates each registered capability.
|
||||||
|
|
||||||
Rules enforced by `PythonPluginBridge.cpp`:
|
Rules enforced when a plugin loads (most in `PythonPluginBridge.cpp`):
|
||||||
|
|
||||||
- The `@orca.plugin` class **must** subclass `orca.base`, and there must be **exactly one**
|
- The `@orca.plugin` class **must** subclass `orca.base`, and there must be **exactly one**
|
||||||
per file — a second `@orca.plugin` fails the load.
|
per file — a second `@orca.plugin` fails the load.
|
||||||
- Each class passed to `orca.register_capability` must subclass a capability base (ultimately
|
- Each class passed to `orca.register_capability` must subclass a capability base (ultimately
|
||||||
`orca.PythonPluginBase`); otherwise it raises `value_error`.
|
`orca.PythonPluginBase`); otherwise it raises `value_error`.
|
||||||
- Every capability must resolve `get_name()`, and the resulting `(type, name)` pair must be
|
- Every capability must resolve `get_name()` (checked in the bridge); the loader
|
||||||
unique across the plugin — a duplicate is rejected.
|
(`PluginLoader.cpp`) additionally rejects the plugin if the resulting `(type, name)` pair is
|
||||||
|
not unique across it.
|
||||||
- A capability class you never pass to `register_capability` is **invisible** to OrcaSlicer,
|
- A capability class you never pass to `register_capability` is **invisible** to OrcaSlicer,
|
||||||
even if it is defined in the file.
|
even if it is defined in the file.
|
||||||
|
|
||||||
@@ -913,7 +914,7 @@ For your new type, add a call site (or an on‑capability‑load callback) that:
|
|||||||
|
|
||||||
List your new `.hpp` / `.cpp` files in `src/slic3r/CMakeLists.txt`, alongside the existing
|
List your new `.hpp` / `.cpp` files in `src/slic3r/CMakeLists.txt`, alongside the existing
|
||||||
plugin‑type sources (search for `plugin/pluginTypes/gcode/GCodePluginCapability.cpp` — the block is
|
plugin‑type sources (search for `plugin/pluginTypes/gcode/GCodePluginCapability.cpp` — the block is
|
||||||
around lines 613–621):
|
around lines 615–623):
|
||||||
|
|
||||||
```cmake
|
```cmake
|
||||||
plugin/pluginTypes/<type>/<Type>PluginCapability.hpp
|
plugin/pluginTypes/<type>/<Type>PluginCapability.hpp
|
||||||
@@ -1011,5 +1012,5 @@ best covered by the manual steps above.
|
|||||||
| `src/slic3r/plugin/PythonInterpreter.cpp` | interpreter init, audit‑hook install, traceback formatting, `stderr` → log file |
|
| `src/slic3r/plugin/PythonInterpreter.cpp` | interpreter init, audit‑hook install, traceback formatting, `stderr` → log file |
|
||||||
| `src/slic3r/GUI/PluginsDialog.cpp` | Plugins dialog: details/error area, script **Run**, error dialogs |
|
| `src/slic3r/GUI/PluginsDialog.cpp` | Plugins dialog: details/error area, script **Run**, error dialogs |
|
||||||
| `src/slic3r/GUI/PostProcessor.cpp` | resolves the preset's plugin refs and invokes post‑processing (G‑code) capabilities during export |
|
| `src/slic3r/GUI/PostProcessor.cpp` | resolves the preset's plugin refs and invokes post‑processing (G‑code) capabilities during export |
|
||||||
| `src/slic3r/CMakeLists.txt` (~602–610) | build list for plugin sources |
|
| `src/slic3r/CMakeLists.txt` (~609–623) | build list for plugin sources |
|
||||||
| [`plugin_audit_hook.md`](plugin_audit_hook.md) | the audit hook: modes, allow‑list, extending it |
|
| [`plugin_audit_hook.md`](plugin_audit_hook.md) | the audit hook: modes, allow‑list, extending it |
|
||||||
|
|||||||
@@ -255,26 +255,33 @@ Each entry is a single string with three `;`‑separated fields:
|
|||||||
entries drop out.
|
entries drop out.
|
||||||
- Parsing/serialization lives in `Config.cpp` (`parse_capability_ref` →
|
- Parsing/serialization lives in `Config.cpp` (`parse_capability_ref` →
|
||||||
`PluginCapabilityRef{ name, capability_name, uuid }`); the `plugins` option is defined in
|
`PluginCapabilityRef{ name, capability_name, uuid }`); the `plugins` option is defined in
|
||||||
`PrintConfig.cpp` and is a **process/print** preset setting. See
|
`PrintConfig.cpp` and is tracked on **process, printer, and filament** presets. See
|
||||||
`tests/libslic3r/test_config.cpp` and
|
`tests/libslic3r/test_config.cpp` and
|
||||||
`tests/slic3rutils/test_plugin_capability_identifier.cpp`.
|
`tests/slic3rutils/test_plugin_capability_identifier.cpp`.
|
||||||
|
|
||||||
## Restoring missing plugins
|
## Restoring missing plugins
|
||||||
|
|
||||||
When a slice is started (`Plater::reslice`), OrcaSlicer resolves the active preset's `plugins`
|
When you prepare to slice, OrcaSlicer resolves the active process, printer, and filament
|
||||||
array against the loaded catalog. Any reference that is not installed is **missing**, and a
|
presets' `plugins` arrays against the loaded catalog (`Plater::refresh_missing_plugin_block`).
|
||||||
dialog appears before slicing continues. Missing references are split by whether they carry a
|
Any reference it cannot satisfy is surfaced as a **non-closable notification**, and while any
|
||||||
cloud UUID:
|
remain the **Slice button stays blocked** — there is no "slice anyway" path; you resolve the
|
||||||
|
reference (or change the setting that pulls it in). References are sorted into four buckets:
|
||||||
|
|
||||||
- **Missing OrcaCloud plugins** (have a UUID) — the dialog offers **Install plugins**, which
|
- **Missing OrcaCloud plugins** (ref carries a UUID) — notification action **Install Plugins**,
|
||||||
subscribes to, installs, loads, and enables each one so it is usable immediately, or
|
which subscribes to, installs, loads, and enables each one so it becomes usable immediately.
|
||||||
**Continue without plugins**.
|
- **Missing local plugins** (no UUID) — cannot be fetched automatically; the action **Find on
|
||||||
- **Missing local plugins** (no UUID) — these cannot be fetched automatically, so the dialog
|
OrcaCloud** just opens a browser search on the OrcaCloud plugins page. It is a suggestion
|
||||||
offers **Open OrcaCloud** (a browser search for similarly named plugins on the OrcaCloud
|
only: it neither closes the notification nor unblocks slicing.
|
||||||
plugins explore page) or **Continue without plugins**.
|
- **Inactive plugins** — the package is installed locally but the referenced capability is not
|
||||||
|
active (plugin not loaded, or capability disabled). Action **Activate Now** loads/enables it
|
||||||
|
locally, with no download.
|
||||||
|
- **Broken references** — the plugin is installed and loaded but no longer provides the
|
||||||
|
referenced capability (renamed/removed/outdated). Activation cannot fix this, so it is
|
||||||
|
informational, with **Find on OrcaCloud** to look for an update.
|
||||||
|
|
||||||
Choosing *Continue without plugins* proceeds with the slice; the functionality those plugins
|
The bucketing lives in `PluginResolver` (`get_missing_cloud_plugins`, `get_missing_local_plugins`,
|
||||||
would have provided is simply skipped.
|
`get_inactive_plugins`, `get_broken_plugins`); the notifications and the slice block are driven
|
||||||
|
from `Plater.cpp` (`refresh_missing_plugin_block`).
|
||||||
|
|
||||||
## The Plugins dialog
|
## The Plugins dialog
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public:
|
|||||||
|
|
||||||
~PluginManager();
|
~PluginManager();
|
||||||
|
|
||||||
// Initialize plugin system (no longer initializes Python — that happens lazily on first load_plugin)
|
// Initialize the plugin system, eagerly starting the embedded Python interpreter on the main thread.
|
||||||
bool initialize();
|
bool initialize();
|
||||||
|
|
||||||
// Stop discovery and unload Python plugin objects before Python finalizes.
|
// Stop discovery and unload Python plugin objects before Python finalizes.
|
||||||
|
|||||||
@@ -622,8 +622,9 @@ bool PythonInterpreter::initialize()
|
|||||||
BOOST_LOG_TRIVIAL(info) << "Bundled uv executable not found";
|
BOOST_LOG_TRIVIAL(info) << "Bundled uv executable not found";
|
||||||
|
|
||||||
// Install the CPython audit hook for plugin policy enforcement.
|
// Install the CPython audit hook for plugin policy enforcement.
|
||||||
// This is defense-in-depth: it monitors file/subprocess/socket/ctypes
|
// This is defense-in-depth: today it only inspects the `open` audit event
|
||||||
// access from plugin code. It is NOT a full security sandbox.
|
// and blocks writes outside the allowed roots; subprocess/socket/ctypes and
|
||||||
|
// other events are not yet handled. It is NOT a full security sandbox.
|
||||||
PluginAuditManager::instance().install_hook();
|
PluginAuditManager::instance().install_hook();
|
||||||
|
|
||||||
// Persist Python stderr (plugin tracebacks, including uncaught
|
// Persist Python stderr (plugin tracebacks, including uncaught
|
||||||
|
|||||||
Reference in New Issue
Block a user