feat(plugin,node): bridge parametric params into the node/inspector path

- ValueType::Parametric; the node input carries the whole curve set as
  NodeValue::Text(JSON) so undo and project serialization come for free
- translation pass builds the input with the default-curve JSON and the
  dimension/range/ui-colour properties
- edits flow both ways: node input (UI) -> curves_from_json ->
  set_ofx(Parametric) on the instance; plugin-side Set/Add/Delete ->
  notify_instance_changed -> JSON written back to the input (undoable)
- screenshot example: gate the macOS-only offscreen capture items so
  the workspace tests build on Linux/Windows
This commit is contained in:
2026-08-21 04:20:14 +08:00
parent 980c41acec
commit a43302d9b7
7 changed files with 676 additions and 35 deletions
+41 -1
View File
@@ -206,7 +206,7 @@ pub fn value_to_string(declared: ValueType, value: &NodeValue, key_track: bool)
}
}
ValueType::Float => format!("{}", value.to_double()),
ValueType::Text | ValueType::StrCombo => match value {
ValueType::Text | ValueType::StrCombo | ValueType::Parametric => match value {
NodeValue::Text(s) => s.clone(),
NodeValue::StrCombo(s) => s.clone(),
_ => String::new(),
@@ -249,6 +249,7 @@ pub fn string_to_value(declared: ValueType, text: &str) -> NodeValue {
ValueType::Float => NodeValue::Float(text.trim().parse().unwrap_or(0.0)),
ValueType::Text => NodeValue::Text(text.to_string()),
ValueType::StrCombo => NodeValue::StrCombo(text.to_string()),
ValueType::Parametric => NodeValue::Text(text.to_string()),
_ => NodeValue::None,
}
}
@@ -1082,3 +1083,42 @@ fn resolve_folder_children(
fn lock<T>(m: &Mutex<T>) -> std::sync::MutexGuard<'_, T> {
m.lock().unwrap_or_else(|e| e.into_inner())
}
#[cfg(test)]
mod tests {
use super::*;
/// Value codec 对 Parametric 输入的往返(save_immediate /
/// load_immediate 对 standard 值走的纯函数路径:split →
/// value_to_string → string_to_value → combineXML 桥由
/// tests/serializer_test.rs 覆盖)。
#[test]
fn parametric_input_value_codec_roundtrip() {
let declared = ValueType::Parametric;
let json = r#"{"curves":[[{"key":0,"value":0,"slope":1},{"key":0.5,"value":0.6,"slope":1}]]}"#;
let value = NodeValue::Text(json.to_string());
// save 侧:standard 值按声明类型拆轨(Text → 单轨整值)。
let tracks = value.split_into_tracks(declared);
assert_eq!(tracks.len(), 1);
let text = value_to_string(declared, &tracks[0], true);
assert_eq!(text, json);
// load 侧:track 文本还原 + 合并回整值。
let restored = string_to_value(declared, &text);
assert_eq!(restored, value);
let combined = NodeValue::combine_tracks(&[restored], declared);
assert_eq!(combined, value);
// key_track=false 形态(整值轨道)同样往返。
assert_eq!(value_to_string(declared, &value, false), json);
// 载荷不是 Text(防御)→ 空串;声明类型 Parametric 无键帧
// 插值、无 C++ 判别值。
assert_eq!(value_to_string(declared, &NodeValue::None, true), "");
assert!(!declared.can_interpolate());
assert_eq!(declared.to_cpp_discriminant(), 0);
assert!(declared.is_string());
assert_eq!(declared.to_oak(), crate::value::oak::STRING);
}
}
+15 -5
View File
@@ -103,6 +103,11 @@ pub enum ValueType {
NodeRef,
/// Push button (no payload).
PushButton,
/// Parametric curve (OpenFX parametric param; the value payload is a
/// [`NodeValue::Text`] JSON document, see
/// `oakplugin::param_curve::curves_to_json` — string-carried like
/// [`ValueType::Text`]).
Parametric,
}
/// A node value. `Texture` stores an oakrender handle; dropping the
@@ -310,8 +315,9 @@ impl ValueType {
ValueType::Vec3 => oak::VEC3,
ValueType::Vec4 => oak::VEC4,
ValueType::Combo => oak::COMBO,
// String-carried types (k_file/k_text/k_font/k_str_combo).
ValueType::Text | ValueType::StrCombo => oak::STRING,
// String-carried types (k_file/k_text/k_font/k_str_combo;
// parametric carries a JSON text).
ValueType::Text | ValueType::StrCombo | ValueType::Parametric => oak::STRING,
_ => oak::NONE,
}
}
@@ -320,7 +326,10 @@ impl ValueType {
/// the dedicated string getters/setters, `// CPP-PARITY: valueconvert.h`
/// `value_type_is_string`).
pub fn is_string(self) -> bool {
matches!(self, ValueType::Text | ValueType::StrCombo)
matches!(
self,
ValueType::Text | ValueType::StrCombo | ValueType::Parametric
)
}
/// Number of keyframe tracks the type splits into (C++
@@ -363,8 +372,9 @@ impl ValueType {
// k_subtitle_params = 20 has no Rust type counterpart.
ValueType::Binary => 21,
ValueType::PushButton => 22,
// No C++ counterpart (k_none).
ValueType::NodeRef => 0,
// No C++ counterpart (k_none); the parametric curve payload
// lives in the JSON text (k_bezier = 15 is a different model).
ValueType::NodeRef | ValueType::Parametric => 0,
}
}