#3087: give a placeholder a match id that is one path segment

A pseudo effect's match name (Pseudo/0.25) was its group's match id, so a
match path to its parameters (keyframe paste, pick whip) was cut apart at
'/' and '.' and found nothing. The effect id stays the match name.
This commit is contained in:
arnav-hiwarkar committed 2026-11-12 13:06:51 +05:30
1 parent f6e2bdf6a0
commit 0da7691d9d
3 files changed
+32 -8

No files matched your search

+15 -3
View File
@@ -40,7 +40,9 @@ pub(crate) fn placeholder(
refs: &mut Vec<PendingLayerRef>,
col: &mut Collector,
) -> PropGroup {
let mut group = ids.group(fx.match_name(), shown);
// The effect id is the match name; the group's match id is one segment of a property path
// (a pseudo effect's `Pseudo/0.25` would be cut apart), so match paths find its parameters.
let mut group = ids.group(&segment(fx.match_name()), shown);
group.kind = GroupKind::Effect { effect: fx.match_name().to_string() };
let here = format!("{}: '{}' effect '{shown}' ({})", cx.comp, cx.layer, fx.match_name());
for p in params.iter().filter(|p| !carries_nothing(p)) {
@@ -126,8 +128,18 @@ fn popup_len(p: &EffectParam<'_, '_>) -> Option<usize> {
/// third-party effect's `CC Cylinder-0003`), else `param<index>` (a pseudo effect's
/// `Pseudo/0.25-0001`: `/` and `.` separate path segments). Expressions also find it by its name.
fn param_id(p: &EffectParam<'_, '_>) -> String {
let plain = !p.match_name.is_empty() && p.match_name.chars().all(|c| c.is_alphanumeric() || matches!(c, ' ' | '-' | '_'));
if plain { p.match_name.clone() } else { format!("param{}", p.index) }
if !p.match_name.is_empty() && p.match_name.chars().all(plain) { p.match_name.clone() } else { format!("param{}", p.index) }
}
/// Whether `c` can stand in one segment of a property path (`/`, `.`, `#` and `@` mean more).
fn plain(c: char) -> bool {
c.is_alphanumeric() || matches!(c, ' ' | '-' | '_')
}
/// `name` as one segment of a property path: other characters become `_` (`Pseudo/0.25` is
/// `Pseudo_0_25`).
fn segment(name: &str) -> String {
name.chars().map(|c| if plain(c) { c } else { '_' }).collect()
}
/// Sets `prop` from `p`: its expression, then its keys or its value (the record, else the `pard`
+10 -5
View File
@@ -10,7 +10,7 @@
use effectcraft_aep::import::{ImportOptions, ImportResult, LossCategory, import};
use effectcraft_aep::{Body, Chunk, ParamKind, ParamValue, Project as AepProject, parse, write};
use effectcraft_keyframe::Value;
use effectcraft_project::{Project, PropGroup};
use effectcraft_project::{GroupKind, Project, PropGroup};
const SYNTHETIC: &[u8] = include_bytes!("../../fixtures/synthetic.aep");
@@ -274,10 +274,15 @@ pub fn import_bytes(bytes: &[u8], effect: &str) -> (Option<PropGroup>, ImportRes
let mut project = Project::default();
let none = |_: &str| false;
let r = import(&mut project, bytes, &ImportOptions { allow_losses: true, ..ImportOptions::new(&none) }).unwrap();
let g = project
.comps()
.flat_map(|(_, c)| c.layers.iter())
.find_map(|l| l.props.sub("effects").and_then(|fx| fx.children.iter().filter_map(|n| n.as_group()).find(|g| g.match_id == effect).cloned()));
let g = project.comps().flat_map(|(_, c)| c.layers.iter()).find_map(|l| {
l.props.sub("effects").and_then(|fx| {
fx.children
.iter()
.filter_map(|n| n.as_group())
.find(|g| g.match_id == effect || matches!(&g.kind, GroupKind::Effect { effect: e } if e == effect))
.cloned()
})
});
(g, r)
}
@@ -100,6 +100,13 @@ fn a_pseudo_effect_is_kept_with_its_parameter_names() {
assert_eq!(g.prop("param1").map(|p| (p.name.as_str(), p.value.clone())), Some(("Speed", Value::Scalar(4.0))));
assert_eq!(g.prop("param2").map(|p| (p.name.as_str(), p.value.clone())), Some(("Mode", Value::Enum(1))));
assert_eq!(losses(&r, LossCategory::Effects, "Pseudo/0.25").len(), 1);
// The effect id stays the match name (Find Missing Effects, expressions); the group's match id
// is one path segment, so a match path (keyframe paste, pick whip) finds its parameters again.
assert_eq!(g.kind, GroupKind::Effect { effect: "Pseudo/0.25".into() });
let speed = g.prop("param1").map(|p| p.uid);
let layer = PropGroup::new(0, "layer", "Layer").with(PropGroup::new(0, "effects", "Effects").with(g));
let path = speed.and_then(|u| layer.match_path_of(u));
assert_eq!(path.as_deref().and_then(|p| layer.prop(p)).map(|p| p.name.as_str()), Some("Speed"), "{path:?}");
}
/// A popup whose definition is borrowed (the project copy) and has no value record: its default