diff --git a/crates/effects/src/revolve.rs b/crates/effects/src/revolve.rs index 0006e5362f..6845199b29 100644 --- a/crates/effects/src/revolve.rs +++ b/crates/effects/src/revolve.rs @@ -620,15 +620,17 @@ fn art(doc: Option<&Document>, n: &Node, expand: bool) -> Option { 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 = 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 = 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(); diff --git a/crates/engine/src/tests/plugins.rs b/crates/engine/src/tests/plugins.rs index 75580fb6bc..0224d59b38 100644 --- a/crates/engine/src/tests/plugins.rs +++ b/crates/engine/src/tests/plugins.rs @@ -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 {