mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-27 03:06:57 -06:00
Plug-ins: exporters write file formats of their own (G17)
A plug-in could filter objects or reshape them as a live effect, but
nothing it made could leave the app: pen plotters, cutting machines and
laser cutters read formats VectorCraft doesn't write. An exporter is a
third kind of plug-in ("kind": "exporter", with the file "extensions"
it writes), in the same sandbox and with the same ABI v1 exports.
- It adds a write-only format, plugin.<id>. document.formats lists it
with its plug-in id, and document.export writes it by that id, or by
a path ending in one of its extensions that no built-in format has;
"params" carries its parameters, checked against its manifest as a
filter's are.
- File › Export › Export As lists it after the built-in formats, with
Use Artboards greyed out, since the plug-in writes the whole
document. One with parameters asks for them in a dialog generated
from its schema (pluginExport) once the file is picked.
- The plug-in is given the native file's JSON (format::json_part:
images of 8 KiB or more without their data) and _context {mode:
"export", extension, name}. It returns {"dataBase64", "warnings"?}
or {"error"}. The host writes the file, and nothing when the plug-in
declines or returns bad data.
- An export gets 10 G instructions plus 4,000 per byte and 300 s.
Background Export's Cancel stops it between two slices of that
budget (Error::Cancelled), and a cancelled live-effect run is not
cached as a failure.
- plugin.run and Object › Plug-ins say where an exporter is instead of
calling it a live effect.
engine/tests/plugin_export.rs reads back what an exporter is handed
with wat::echo_exporter, an exporter whose file is its input or its
parameters (its base64 encoder is written in WAT). The document it is
given is document.save's JSON, without the large image's data, and its
parameters arrive defaulted, clamped and with the file's name and
extension. It also covers listing and removal, built-in extensions
winning, selectedOnly, declines, bad base64, missing data, warnings,
and Cancel stopping a plug-in that never returns. It is a test binary
of its own: installed plug-ins are process-wide, and the format-list
unit tests check exact lists. dialogs/tests_plugin_export.rs drives
Export As headlessly; plugins/tests/abi.rs covers the budget, the
parameters and the echo exporter; format/tests/native_v4.rs covers
json_part.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HhvnnnD5fTFupK8WWq35un
This commit is contained in:
committed by
bflatastic
co-authored by
Claude Opus 5.5
parent
1dda8d9e67
commit
24936cd006
@@ -5,12 +5,13 @@
|
||||
//! preference, and listed with `plugin.list` / `plugin.info`. Object filters run
|
||||
//! with `plugin.run` on the selected paths and compound paths (one undo step, transactional: a
|
||||
//! plug-in that fails leaves the document untouched); live-effect plug-ins are applied like any
|
||||
//! effect (`effect.apply {effect: "plugin.<id>"}`) and drawn by `vectorcraft-effects`.
|
||||
//! effect (`effect.apply {effect: "plugin.<id>"}`) and drawn by `vectorcraft-effects`; exporters
|
||||
//! add a file format that `document.export` writes (`fileio::plugin_formats`).
|
||||
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use serde_json::{Map, Value, json};
|
||||
use vectorcraft_plugins::{Kind, Plugin, effect, objects, registry};
|
||||
use vectorcraft_plugins::{Kind, Plugin, effect, export, objects, registry};
|
||||
|
||||
use super::*;
|
||||
|
||||
@@ -27,8 +28,13 @@ pub fn describe(p: &Plugin) -> Value {
|
||||
"params": m.params_doc(), "paramsSchema": serde_json::to_value(m).ok().and_then(|v| v.get("params").cloned()),
|
||||
"size": p.size(), "source": p.source(),
|
||||
});
|
||||
if m.kind == Kind::Effect {
|
||||
v["effect"] = json!(effect::effect_id(&m.id));
|
||||
match m.kind {
|
||||
Kind::Effect => v["effect"] = json!(effect::effect_id(&m.id)),
|
||||
Kind::Exporter => {
|
||||
v["format"] = json!(export::format_id(&m.id));
|
||||
v["extensions"] = json!(m.extensions);
|
||||
}
|
||||
Kind::Filter => {}
|
||||
}
|
||||
v
|
||||
}
|
||||
@@ -56,8 +62,14 @@ fn run(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
const C: &str = "plugin.run";
|
||||
let plugin = plugin_by_id(C, p)?;
|
||||
let m = plugin.manifest();
|
||||
if m.kind != Kind::Filter {
|
||||
return Err(bad(C, format!("`{}` is a live effect: apply it with effect.apply {{\"effect\": \"{}\"}}", m.id, effect::effect_id(&m.id))));
|
||||
match m.kind {
|
||||
Kind::Filter => {}
|
||||
Kind::Effect => {
|
||||
return Err(bad(C, format!("`{}` is a live effect: apply it with effect.apply {{\"effect\": \"{}\"}}", m.id, effect::effect_id(&m.id))));
|
||||
}
|
||||
Kind::Exporter => {
|
||||
return Err(bad(C, format!("`{}` is an exporter: export with document.export {{\"format\": \"{}\"}}", m.id, export::format_id(&m.id))));
|
||||
}
|
||||
}
|
||||
// Bad parameters fail before anything runs.
|
||||
let mut params = Value::Object(m.resolve_params(&plugin_params(p)).map_err(|e| bad(C, e.to_string()))?);
|
||||
@@ -206,7 +218,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"List Plug-ins",
|
||||
[],
|
||||
None,
|
||||
"{} → {plugins: [{id, name, version, kind: \"filter\"|\"effect\", author, description, params (doc), paramsSchema, effect? (the effect id of a live effect: plugin.<id>), size, source}], folder: the Additional Plug-ins Folder load report {folder, loaded, failed: [{file, error}]} | null}",
|
||||
"{} → {plugins: [{id, name, version, kind: \"filter\"|\"effect\"|\"exporter\", author, description, params (doc), paramsSchema, effect? (the effect id of a live effect: plugin.<id>), format? and extensions? (an exporter's format id, plugin.<id>, for document.export, and its file extensions), size, source}], folder: the Additional Plug-ins Folder load report {folder, loaded, failed: [{file, error}]} | null}",
|
||||
always,
|
||||
list
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user