diff --git a/crates/effects/src/revolve.rs b/crates/effects/src/revolve.rs index 8d05ab38ed..aae131dfdc 100644 --- a/crates/effects/src/revolve.rs +++ b/crates/effects/src/revolve.rs @@ -511,8 +511,9 @@ pub(crate) fn revolve_meshes(n: &Node, total: &mut usize, limit: usize) -> Resul /// instances, blends and clipping groups already resolved), in document coordinates: with the /// effect on the object, the surface in its top visible fill (else its stroke); with it on fills and strokes, one surface for each of /// them, reshaped by the geometry effects after each one's Revolve; on a group or layer, one for -/// each member, about one shared axis. -pub(crate) fn revolve_face_paths(n: &Node) -> Result, String> { +/// each member, about one shared axis. The swatches and patterns named by a live-effect plug-in +/// after a fill's or stroke's Revolve are looked up in `doc`. +pub(crate) fn revolve_face_paths(doc: Option<&Document>, n: &Node) -> Result, String> { let faces = |path: &PathData, b: Rect, options: Revolve| -> Result, String> { Ok(vectorcraft_three_d::revolve(path, b, options)?.iter().map(vectorcraft_three_d::Face::path).collect()) }; @@ -554,7 +555,7 @@ pub(crate) fn revolve_face_paths(n: &Node) -> Result, String> { let p = geometry(&path, item.effects().get(..index).unwrap_or_default(), &ctx.item(item)); let b = p.bounds().ok_or("Revolve requires a shape with at least two anchors")?; let surface = faces(&p, b, fx_options(fx))?; - out.extend(crate::solids::wireframe::reshaped(surface, item.effects().get(index + 1..).unwrap_or_default(), &ctx)); + out.extend(crate::solids::wireframe::reshaped(surface, item.effects().get(index + 1..).unwrap_or_default(), &GeomContext { doc, ..ctx })); } } Ok(out) diff --git a/crates/effects/src/solids/wireframe.rs b/crates/effects/src/solids/wireframe.rs index 5f2275d8eb..33f7f7f018 100644 --- a/crates/effects/src/solids/wireframe.rs +++ b/crates/effects/src/solids/wireframe.rs @@ -44,14 +44,14 @@ pub fn three_d_wireframe_in(doc: Option<&Document>, n: &Node) -> Result, later: &[Effect], ctx: &GeomContext /// The outline of every face [`super::solid_art`] draws for `n` (already resolved): with the effect /// on the object, its solids; with it on fills and strokes, the solid of each; on a group or layer, -/// the solids of its members, about one shared axis. -fn solid_face_paths(n: &Node) -> Result, String> { +/// the solids of its members, about one shared axis. The swatches and patterns named by a live-effect +/// plug-in after a fill's or stroke's 3D effect are looked up in `doc`. +fn solid_face_paths(doc: Option<&Document>, n: &Node) -> Result, String> { if is_container(n) { let (index, fx) = solid(&n.appearance.effects).ok_or("a 3D effect on a group's fill or stroke draws nothing")?; let mut shapes = members(n, label_of(&fx.id))?; @@ -110,7 +111,7 @@ fn solid_face_paths(n: &Node) -> Result, String> { if let Some((index, fx)) = solid(item.effects()) { let p = geometry(&path, item.effects().get(..index).unwrap_or_default(), &ctx.item(item)); let faces = facet_paths(fx, &[Profile { path: p, rule, items: vec![item.clone()], opacity: 1.0, scopes: vec![] }])?; - out.extend(reshaped(faces, item.effects().get(index + 1..).unwrap_or_default(), &ctx)); + out.extend(reshaped(faces, item.effects().get(index + 1..).unwrap_or_default(), &GeomContext { doc, ..ctx })); } } Ok(out) diff --git a/crates/engine/src/cmd/wireframe/tests.rs b/crates/engine/src/cmd/wireframe/tests.rs index ab51c81282..268fbcd6db 100644 --- a/crates/engine/src/cmd/wireframe/tests.rs +++ b/crates/engine/src/cmd/wireframe/tests.rs @@ -161,6 +161,34 @@ fn objects_without_a_3d_effect_are_left_and_none_at_all_is_an_error() { assert_eq!(s.doc().unwrap().doc.node(plain).unwrap(), &kept); } +/// A live-effect plug-in after a fill's 3D effect that links its pieces to one of the document's +/// swatches reshapes the edges, as it reshapes the drawn surface. +#[test] +fn a_painted_effect_after_a_fill_s_3d_effect_finds_the_document_s_swatches() { + use vectorcraft_geom::{PathData, Point, SubPath}; + let mut leaf = serde_json::to_value(vectorcraft_color::Paint::solid(vectorcraft_color::Color::from_hex("#00ff00").unwrap())).unwrap(); + leaf["swatch"] = json!("Leaf"); + let corners = [Point::new(300.0, 300.0), Point::new(320.0, 300.0), Point::new(320.0, 320.0), Point::new(300.0, 320.0)]; + let output = json!({"objects": [{"path": PathData::single(SubPath::polyline(&corners, true)), "fills": [leaf]}]}).to_string(); + let manifest = r#"{"id":"test.engine.wireleaf","name":"Test wireleaf","version":"1","kind":"effect","api":2,"paintOutput":true,"params":{}}"#; + let wasm = wat::parse_str(vectorcraft_plugins::wat::returning(manifest, &output)).unwrap(); + for effect in ["threeD.extrude", "threeD.revolve"] { + let (mut s, id) = setup(None); + s.execute("plugin.install", &json!({"dataBase64": vectorcraft_format::base64_encode(&wasm), "replace": true})).unwrap(); + s.execute("paint.setStroke", &json!({"none": true})).unwrap(); + s.execute("swatch.new", &json!({"name": "Leaf", "color": "#00ff00", "global": true})).unwrap(); + let items = &s.doc().unwrap().doc.node(id).unwrap().appearance.items; + let fill = items.iter().position(|i| matches!(i, AppearanceItem::Fill(_))).unwrap(); + for e in [effect, "plugin.test.engine.wireleaf"] { + s.execute("effect.apply", &json!({"ids": [id.0], "effect": e, "item": fill, "params": {}})).unwrap(); + } + run(&mut s, json!({"ids": [id.0]})).unwrap_or_else(|e| panic!("{effect}: {e}")); + let b = s.doc().unwrap().doc.node(id).unwrap().geometric_bounds().unwrap(); + assert_eq!((b.x0, b.y0, b.x1, b.y1), (300.0, 300.0, 320.0, 320.0), "{effect}: the plug-in's square"); + assert_eq!(segments(&s, id), 4, "{effect}"); + } +} + #[test] fn bad_ids_are_errors() { let (mut s, _) = setup(Some("threeD.extrude"));