mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-13 08:29:14 +00:00
3D and Materials: Expand as Wireframes looks up swatches for plug-ins after a fill's 3D effect
A live-effect plug-in after a fill's or stroke's 3D effect (Extrude & Bevel, Inflate, Rotate or Revolve) that links its pieces to a swatch or pattern was run without the document, so its result was refused and the edges kept the shape before it. The document now goes to those effects, as it does for the effects after an object's 3D effect.
This commit is contained in:
@@ -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<Vec<PathData>, 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<Vec<PathData>, String> {
|
||||
let faces = |path: &PathData, b: Rect, options: Revolve| -> Result<Vec<PathData>, 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<Vec<PathData>, 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)
|
||||
|
||||
@@ -44,14 +44,14 @@ pub fn three_d_wireframe_in(doc: Option<&Document>, n: &Node) -> Result<Vec<Path
|
||||
let n = &*resolve(doc, n, label)?;
|
||||
// The builders' own error says why, when there is one.
|
||||
if crate::has_revolve(n) {
|
||||
crate::revolve::revolve_face_paths(n)?;
|
||||
crate::revolve::revolve_face_paths(doc, n)?;
|
||||
} else {
|
||||
solid_face_paths(n)?;
|
||||
solid_face_paths(doc, n)?;
|
||||
}
|
||||
return Err(format!("{label} draws the source art for this object instead of its 3D surface"));
|
||||
}
|
||||
let n = &*resolve(doc, n, label)?;
|
||||
let faces = if revolve { crate::revolve::revolve_face_paths(n)? } else { solid_face_paths(n)? };
|
||||
let faces = if revolve { crate::revolve::revolve_face_paths(doc, n)? } else { solid_face_paths(doc, n)? };
|
||||
let faces = after_geometry(doc, n, faces);
|
||||
let wire = edges(&faces)?;
|
||||
if wire.is_empty() {
|
||||
@@ -88,8 +88,9 @@ pub(crate) fn reshaped(faces: Vec<PathData>, 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<Vec<PathData>, 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<Vec<PathData>, 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<Vec<PathData>, 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)
|
||||
|
||||
@@ -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"));
|
||||
|
||||
Reference in New Issue
Block a user