diff --git a/crates/engine/src/cmd/wireframe.rs b/crates/engine/src/cmd/wireframe.rs index 595b837aa5..eeee004b07 100644 --- a/crates/engine/src/cmd/wireframe.rs +++ b/crates/engine/src/cmd/wireframe.rs @@ -25,7 +25,7 @@ pub fn specs() -> Vec { "Expand as Wireframes", [], None, - "{ids?: [..] (default: the selected objects)} replace each object that has a visible 3D effect (Extrude & Bevel, Revolve, Inflate, Rotate or one of their 3D (Classic) forms, on the object or on its fills and strokes) with a group of paths: the edges of the faces the effect draws, as the object is turned and projected now, each edge once (edges that meet end to end make one path, closed where it comes back to its start; curved edges stay curves; geometry effects after the 3D effect, on the object or on a fill or stroke, reshape them too), stroked 0.5 pt black with round joins and no fill (VectorCraft's own); the group takes the object's place, id, name, opacity, blending mode, opacity mask, attributes, slice and text wrap and is selected (a layer stays a layer holding the edges, which are selected, and its ruler guides and paths made into guides); objects without a visible 3D effect are left as they are; one undo step. An error, with nothing changed, when no object is selected or given, when none has a visible 3D effect, when an object's solid can't be built or the canvas draws its source art in its place, or when one object's wireframe has more than 1,000,000 edges → {ids}", + "{ids?: [..] (default: the selected objects)} replace each object that has a visible 3D effect (Extrude & Bevel, Revolve, Inflate, Rotate or one of their 3D (Classic) forms, on the object or on its fills and strokes) with a group of paths: the edges of the faces the effect draws, as the object is turned and projected now, each edge once (edges that meet end to end make one path, closed where it comes back to its start; curved edges stay curves; geometry effects after the 3D effect, on the object or on a fill or stroke, reshape them too), stroked 0.5 pt black with round joins and no fill (VectorCraft's own); the group takes the object's place, id, name, opacity, blending mode, opacity mask, attributes, slice and text wrap and is selected (a layer stays a layer holding the edges, which are selected, and its ruler guides and paths made into guides; a path inside a compound path in ids stands for the compound path); objects without a visible 3D effect are left as they are; one undo step. An error, with nothing changed, when no object is selected or given, when none has a visible 3D effect, when an object's solid can't be built or the canvas draws its source art in its place, or when one object's wireframe has more than 1,000,000 edges → {ids}", has_doc, expand_as_wireframes )] @@ -68,7 +68,9 @@ fn expand_as_wireframes(s: &mut Session, p: &Value) -> Result { let mut wires = vec![]; let mut seen = std::collections::HashSet::new(); for id in roots { - // An id given twice is expanded once. + // A path inside a compound path stands for the compound path, which draws it. An id given + // twice is expanded once. + let id = super::path::compound_or_self(d, id); if !seen.insert(id) { continue; } diff --git a/crates/engine/src/cmd/wireframe/tests.rs b/crates/engine/src/cmd/wireframe/tests.rs index 33a30975d9..7d9265bdbc 100644 --- a/crates/engine/src/cmd/wireframe/tests.rs +++ b/crates/engine/src/cmd/wireframe/tests.rs @@ -212,6 +212,23 @@ fn a_painted_effect_after_a_fill_s_3d_effect_finds_the_document_s_swatches() { } } +/// A path inside a compound path in `ids` stands for the compound path: the compound path becomes +/// the group, and no member is replaced by a group inside it. +#[test] +fn a_compound_path_member_in_ids_expands_the_compound_path() { + let (mut s, a) = setup(None); + let b = NodeId(s.execute("path.create", &json!({"d":"M250 100L350 100L350 200L250 200Z"})).unwrap()["id"].as_u64().unwrap()); + s.execute("select.set", &json!({"ids":[a.0, b.0]})).unwrap(); + let c = NodeId(s.execute("object.compoundPath.make", &json!({})).unwrap()["id"].as_u64().unwrap()); + s.execute("effect.apply", &json!({"ids":[c.0],"effect":"threeD.extrude","item":null,"params":{}})).unwrap(); + let member = s.doc().unwrap().doc.node(c).unwrap().children().unwrap()[0].id; + let out = run(&mut s, json!({"ids":[member.0]})).unwrap(); + assert_eq!(out["ids"], json!([c.0])); + let st = s.doc().unwrap(); + let NodeKind::Group { children, clip: false } = &st.doc.node(c).unwrap().kind else { panic!("the compound path is a group") }; + assert!(!children.is_empty() && children.iter().all(|ch| ch.path_data().is_some())); +} + #[test] fn bad_ids_are_errors() { let (mut s, _) = setup(Some("threeD.extrude")); diff --git a/crates/ui-egui/src/panels/tests_three_d.rs b/crates/ui-egui/src/panels/tests_three_d.rs index e8bfede3fd..29751dd5d7 100644 --- a/crates/ui-egui/src/panels/tests_three_d.rs +++ b/crates/ui-egui/src/panels/tests_three_d.rs @@ -427,6 +427,22 @@ fn expand_as_wireframes_replaces_the_object_with_its_edges() { assert_eq!(undo_len(&app), history); } +/// The Quick Actions show on the Object tab of the effects that have one (Revolve (Classic) +/// included) and not on their Rotation tab; Revolve and Rotate show them on their first tab. +#[test] +fn quick_actions_show_on_the_object_tab_only() { + use crate::state::Dialog; + use vectorcraft_effects::{CLASSIC_EXTRUDE, CLASSIC_REVOLVE, EXTRUDE, INFLATE, REVOLVE}; + for effect in [EXTRUDE, INFLATE, CLASSIC_EXTRUDE, CLASSIC_REVOLVE] { + for (tab, shown) in [("object", true), ("rotation", false)] { + let d = Dialog::new("x", json!({"__effect": effect, "__tab": tab, "__gizmo": "rotation"})); + assert_eq!(super::quick_actions_shown(&d), shown, "{effect} {tab}"); + } + } + let d = Dialog::new("x", json!({"__effect": REVOLVE, "__tab": "rotation", "__gizmo": "rotation"})); + assert!(super::quick_actions_shown(&d)); +} + #[test] fn changed_settings_lists_only_new_values() { let before = json!({"depth": 50.0, "cap": true, "bevel": "none"}); diff --git a/crates/ui-egui/src/panels/three_d.rs b/crates/ui-egui/src/panels/three_d.rs index 5d4763090e..c9f2f2205e 100644 --- a/crates/ui-egui/src/panels/three_d.rs +++ b/crates/ui-egui/src/panels/three_d.rs @@ -7,7 +7,7 @@ use egui::Ui; use serde_json::{Map, Value, json}; use vectorcraft_doc::{Effect, Node}; -use vectorcraft_effects::{CLASSIC_EXTRUDE, EXTRUDE, INFLATE, THREE_D_TYPES, is_three_d}; +use vectorcraft_effects::{CLASSIC_EXTRUDE, CLASSIC_REVOLVE, EXTRUDE, INFLATE, THREE_D_TYPES, is_three_d}; use super::{live_run, pstate, set_pstate}; use crate::VectorcraftApp; @@ -261,7 +261,7 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) { /// the first tab of the others (Revolve, Rotate), never on Lighting. fn quick_actions_shown(d: &Dialog) -> bool { let effect = d.str("__effect"); - let object_tab = [EXTRUDE, INFLATE, CLASSIC_EXTRUDE].contains(&effect.as_str()); + let object_tab = [EXTRUDE, INFLATE, CLASSIC_EXTRUDE, CLASSIC_REVOLVE].contains(&effect.as_str()); !crate::dialogs::revolve_gizmo::lighting(d) && (!object_tab || d.str("__tab") != "rotation") } diff --git a/docs/mcp.md b/docs/mcp.md index 7d344068bb..95de49525c 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -3237,7 +3237,7 @@ on the object or on a fill or stroke, reshape its edges as they reshape the draw black stroke with round joins and no fill (VectorCraft's own choice). The group keeps the object's place, id, name, visibility, lock, opacity, blending mode, knockout, opacity mask, attributes, slice and text wrap, and the groups are selected. -A layer in `ids` stays a layer: its members are replaced by the edges, which are selected, and its ruler guides and paths made into guides stay. Objects without a visible 3D effect do not change. It is one undo step and +A layer in `ids` stays a layer: its members are replaced by the edges, which are selected, and its ruler guides and paths made into guides stay. A path inside a compound path in `ids` stands for the compound path. Objects without a visible 3D effect do not change. It is one undo step and returns `{ids}`, the groups. It is an error, and nothing changes, when nothing is selected or given, when no object has a visible 3D effect, when an object's solid can't be built or the canvas draws its source art in its place, or when one object's wireframe has more than 1,000,000 edges.