3D and Materials: Expand as Wireframes takes a compound path member as its compound path

A path inside a compound path given in ids was replaced by a group inside the
compound path, which reads its members as paths, so the edges did not draw. The
compound path is now expanded in its place. The panel's Quick Actions show on
the Object tab of Revolve (Classic), not on its Rotation tab, as for the other
effects with an Object tab.
This commit is contained in:
dotCooCoo
2026-11-12 11:33:34 -08:00
parent 556123e691
commit eb80d45ec5
5 changed files with 40 additions and 5 deletions
+4 -2
View File
@@ -25,7 +25,7 @@ pub fn specs() -> Vec<CommandSpec> {
"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<Value> {
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;
}
+17
View File
@@ -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"));
@@ -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"});
+2 -2
View File
@@ -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")
}
+1 -1
View File
@@ -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.