Effects: a fill's 3D Revolve tells the plug-in effect before it which fill it sits on

The per-item Revolve loop passed a copy of each fill or stroke to the
geometry context, which finds the item by identity in the object's
appearance, so a plug-in effect before the Revolve was told it sat on
the object. Each copy now keeps its original item beside it.
This commit is contained in:
dotCooCoo
2026-11-12 15:48:59 -08:00
parent b4505a76de
commit 3570efa0a9
2 changed files with 29 additions and 4 deletions
+6 -4
View File
@@ -620,15 +620,17 @@ fn art(doc: Option<&Document>, n: &Node, expand: bool) -> Option<Node> {
let path = geometry(&path, &n.appearance.effects, &ctx);
remaining = crate::without_geometry(&remaining);
// The object's Transform effects, applied to the path above, transform the items' raster
// effects too.
let mut items: Vec<AppearanceItem> = n.appearance.items.iter().filter(|i| crate::paints(i)).cloned().collect();
// effects too. Each copy keeps its item beside it, which plug-in effects are told they sit
// on.
let originals: Vec<&AppearanceItem> = n.appearance.items.iter().filter(|i| crate::paints(i)).collect();
let mut items: Vec<AppearanceItem> = originals.iter().map(|i| (*i).clone()).collect();
crate::raster::follow_paints(&mut items, &n.appearance.effects);
// Every item's shadow goes behind every item's art.
let mut shadows = vec![];
for item in &items {
for (original, item) in originals.into_iter().zip(&items) {
if let Some(index) = item.effects().iter().position(|e| e.visible && is_revolve(&e.id)) {
let fx = item.effects().get(index)?;
let p = geometry(&path, item.effects().get(..index)?, &ctx.item(item));
let p = geometry(&path, item.effects().get(..index)?, &ctx.item(original));
let expand = expand && !item.effects().iter().skip(index + 1).any(|e| e.visible && is_geometry(&e.id));
let mut art = object_surface(n, &p, fx, item, expand, &mut copies)?;
art.appearance.effects = item.effects().iter().skip(index + 1).filter(|e| !is_revolve(&e.id)).cloned().collect();
+23
View File
@@ -329,6 +329,29 @@ fn expanding_a_fill_gives_its_plug_in_effect_the_fill_it_sits_on() {
assert_eq!(clip.path_data().unwrap().bounds(), triangle().bounds(), "the triangle the canvas fills");
}
/// A plug-in effect on a fill, before the fill's own 3D Revolve, is given the fill it sits on
/// (`_context.item`), so the fill revolves the shape the plug-in returns for it.
#[test]
fn a_fills_revolve_gives_the_plug_in_effect_before_it_the_fill_it_sits_on() {
let mut s = session();
let id = rect(&mut s, 10.0, "#ff0000");
let tri = json!({"objects": [{"path": triangle()}]}).to_string();
let on_fill = tpl::when_given(&tpl::manifest("test.engine.revolvefill", "effect", "{}"), tpl::Given::Params, r#""item":{"fill":0}"#, &tri);
install_wat(&mut s, &on_fill);
let fill = node(&s, id).appearance.items.iter().position(|i| i.is_fill()).unwrap();
run(&mut s, "effect.apply", json!({"ids": [id.0], "effect": "plugin.test.engine.revolvefill", "item": fill}));
run(&mut s, "effect.apply", json!({"ids": [id.0], "effect": "threeD.revolve", "item": fill}));
let n = node(&s, id);
// The same object with the triangle as its path and only the Revolve on its fill.
let mut expected = n.clone();
if let vectorcraft_doc::NodeKind::Path { path, .. } = &mut expected.kind {
*path = triangle();
}
expected.appearance.items[fill].effects_mut().retain(|e| e.id == "threeD.revolve");
let art = vectorcraft_render::effects::revolve_art(&n).unwrap();
assert_eq!(art, vectorcraft_render::effects::revolve_art(&expected).unwrap(), "the fill revolves the triangle");
}
/// The manifest of a test live effect `id` that declares `paintOutput` (at `api` 2), or that
/// doesn't (`paints` off).
fn painting(id: &str, paints: bool) -> String {