diff --git a/crates/ui-egui/src/dialogs/revolve.rs b/crates/ui-egui/src/dialogs/revolve.rs index de21c368bc..71b6d80130 100644 --- a/crates/ui-egui/src/dialogs/revolve.rs +++ b/crates/ui-egui/src/dialogs/revolve.rs @@ -260,7 +260,8 @@ fn extrude_controls(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) { }); } -/// Rotation presets, rotation and perspective for Extrude & Bevel, Inflate and Rotate. +/// Rotation presets, rotation and perspective for Extrude & Bevel, Inflate and Rotate, and the +/// classic 3D effects, which have the track cube below the presets. fn view_controls(ui: &mut egui::Ui, d: &mut Dialog) { heading(ui, tl!("Rotation")); let names = [ @@ -294,6 +295,9 @@ fn view_controls(ui: &mut egui::Ui, d: &mut Dialog) { d.fields.extend([("rotationX".into(), json!(x)), ("rotationY".into(), json!(y)), ("rotationZ".into(), json!(z))]); } }); + if vectorcraft_effects::is_classic_three_d(&d.str("__effect")) { + super::three_d_classic::track_cube(ui, d); + } for (key, name) in [("rotationX", tl!("Rotation X")), ("rotationY", tl!("Rotation Y")), ("rotationZ", tl!("Rotation Z"))] { number(ui, d, key, name, (-180.0, 180.0), "°", 2, None); } diff --git a/crates/ui-egui/src/dialogs/revolve/tests.rs b/crates/ui-egui/src/dialogs/revolve/tests.rs index fd802f83f0..1e22be9c76 100644 --- a/crates/ui-egui/src/dialogs/revolve/tests.rs +++ b/crates/ui-egui/src/dialogs/revolve/tests.rs @@ -460,6 +460,88 @@ fn classic_rotate_shows_its_options_and_applies_as_one_step() { assert_eq!(h.app.session.doc().unwrap().doc, before); } +/// The classic effects' track cube: a drag on its face turns the object live and the presets show +/// Custom, Escape during a drag puts the angles back, the ring turns Z alone, the arrow keys turn +/// it with the keyboard focus, and OK keeps it all as one undo step. The newer effects have no cube. +#[test] +fn the_track_cube_turns_the_classic_effects_live_in_one_undo_step() { + use vectorcraft_effects::{CLASSIC_EXTRUDE, CLASSIC_REVOLVE, CLASSIC_ROTATE, EXTRUDE, ROTATE}; + let square = "M100 100 L200 100 L200 200 L100 200 Z"; + for effect in [EXTRUDE, ROTATE] { + let mut h = Harness::with(square, effect, vec2(1400.0, 1000.0)); + h.app.ui.dialog.as_mut().unwrap().fields.insert("__tab".into(), json!("rotation")); + h.settle(); + assert!(!h.texts.iter().any(|(t, _)| t == "Track cube"), "{effect} has no track cube"); + } + for effect in [CLASSIC_EXTRUDE, CLASSIC_REVOLVE] { + let mut h = Harness::with(square, effect, vec2(1400.0, 1000.0)); + h.app.ui.dialog.as_mut().unwrap().fields.insert("__tab".into(), json!("rotation")); + h.settle(); + h.rect("Track cube"); + } + let mut h = Harness::with(square, CLASSIC_ROTATE, vec2(1400.0, 1000.0)); + let before = h.app.session.doc().unwrap().doc.clone(); + h.settle(); + h.rect("Off-Axis Front"); + let start = ["rotationX", "rotationY", "rotationZ"].map(|k| h.value(k)); + let cube = h.response("three-d-track-cube"); + // A face, dragged right: the object turns, the document previews it, and the preset is Custom. + h.drag(cube.center(), vec2(30.0, 0.0)); + let turned = ["rotationX", "rotationY", "rotationZ"].map(|k| h.value(k)); + assert_ne!(turned, start, "the drag turned the object"); + h.rect("Custom"); + let fx = |h: &Harness| { + let state = h.app.session.doc().unwrap(); + state.doc.node(state.selection.objects[0]).unwrap().appearance.effects[0].params.clone() + }; + assert_eq!(fx(&h)["rotationY"].as_f64(), Some(turned[1]), "the preview follows the cube"); + // Escape during a drag puts the angles back and keeps the dialog open. + let p = cube.center(); + h.frame(vec![Event::PointerMoved(p), button(p, true)]); + h.frame(vec![Event::PointerMoved(p + vec2(0.0, 30.0))]); + h.frame(vec![Event::PointerMoved(p + vec2(0.0, 40.0))]); + assert_ne!(h.value("rotationX"), turned[0]); + h.frame(vec![Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }]); + h.frame(vec![button(p + vec2(0.0, 40.0), false)]); + h.settle(); + assert!(h.app.ui.dialog.is_some(), "Escape ends the drag, not the dialog"); + assert_eq!(["rotationX", "rotationY", "rotationZ"].map(|k| h.value(k)), turned); + // The ring turns in the screen plane: Z alone. + let top = pos2(cube.center().x, cube.center().y - 53.5); + h.drag(top, vec2(12.0, 2.0)); + assert_eq!((h.value("rotationX"), h.value("rotationY")), (turned[0], turned[1])); + assert!(h.value("rotationZ") > turned[2], "clockwise: {} -> {}", turned[2], h.value("rotationZ")); + // With the keyboard focus (the drags gave it), Right turns about the screen's vertical axis. + let z = h.value("rotationZ"); + assert!(h.ctx.memory(|m| m.has_focus(Id::unique("three-d-track-cube"))), "the drag gave the cube the keyboard focus"); + h.frame(vec![Event::Key { key: egui::Key::PageUp, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }]); + h.settle(); + assert!((h.value("rotationZ") - (z - 1.0)).abs() < 0.011, "Page Up turns 1° counterclockwise"); + let y = h.value("rotationY"); + h.frame(vec![Event::Key { key: egui::Key::ArrowRight, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }]); + h.settle(); + assert_ne!(h.value("rotationY"), y, "Right turns the cube"); + let last = h.value("rotationZ"); + dialogs::confirm(&mut h.app).unwrap(); + assert_eq!(fx(&h)["rotationZ"].as_f64(), Some(last)); + h.app.run("edit.undo", json!({})).unwrap(); + assert_eq!(h.app.session.doc().unwrap().doc, before); +} + +/// A mixed rotation (the 3D and Materials panel's selected effects differ) leaves the cube off. +#[test] +fn the_track_cube_is_off_while_a_rotation_is_mixed() { + use vectorcraft_effects::CLASSIC_ROTATE; + let mut h = Harness::with("M100 100 L200 100 L200 200 L100 200 Z", CLASSIC_ROTATE, vec2(1400.0, 1000.0)); + h.app.ui.dialog.as_mut().unwrap().fields.insert("rotationX".into(), Value::Null); + h.settle(); + let cube = h.response("three-d-track-cube"); + h.drag(cube.center(), vec2(30.0, 0.0)); + let d = h.app.ui.dialog.as_ref().unwrap(); + assert_eq!(d.fields.get("rotationX"), Some(&Value::Null)); + assert!(!h.ctx.read_response(Id::unique("three-d-track-cube")).unwrap().sense.senses_drag()); +} + #[test] fn extrude_bevel_options_enable_with_a_bevel_shape() { let mut h = Harness::with("M100 100 L200 100 L200 200 L100 200 Z", vectorcraft_effects::EXTRUDE, vec2(1400.0, 1000.0)); diff --git a/crates/ui-egui/src/dialogs/revolve_gizmo.rs b/crates/ui-egui/src/dialogs/revolve_gizmo.rs index 4595447ac2..8b712e556e 100644 --- a/crates/ui-egui/src/dialogs/revolve_gizmo.rs +++ b/crates/ui-egui/src/dialogs/revolve_gizmo.rs @@ -42,8 +42,10 @@ fn drag_id() -> Id { Id::unique("revolve-gizmo-drag") } +/// Is a rotation or light gesture in progress, on the artboard or on the classic 3D effects' track +/// cube? pub(crate) fn dragging(ctx: &egui::Context) -> bool { - ctx.data(|m| m.get_temp::(drag_id())).is_some() + ctx.data(|m| m.get_temp::(drag_id())).is_some() || super::three_d_classic::cube_dragging(ctx) } pub(super) fn clear(ctx: &egui::Context) { diff --git a/crates/ui-egui/src/dialogs/tests_track_cube.rs b/crates/ui-egui/src/dialogs/tests_track_cube.rs new file mode 100644 index 0000000000..3b2de38452 --- /dev/null +++ b/crates/ui-egui/src/dialogs/tests_track_cube.rs @@ -0,0 +1,125 @@ +//! The track cube's rotations, grips and gestures. +use super::*; +use egui::pos2; +use vectorcraft_effects::ROTATION_PRESETS; + +const SAMPLES: [[f64; 3]; 6] = + [[0.0, 0.0, 0.0], [-21.88, -23.4, 9.06], [27.0, -39.0, 53.0], [170.0, 10.0, -95.5], [-120.0, 75.0, 33.0], [45.0, -35.26, 30.0]]; + +fn close(a: [f64; 3], b: [f64; 3]) -> bool { + a.iter().zip(b).all(|(a, b)| tidy(a - b).abs() < 0.011) +} + +fn same_rotation(a: M3, b: M3) -> bool { + a.iter().flatten().zip(b.iter().flatten()).all(|(a, b)| (a - b).abs() < 1e-3) +} + +/// The cube turns as the 3D effects turn the art: each object axis lands where the effects' own +/// view rotation puts it. +#[test] +fn the_cube_rotates_as_the_effects_do() { + for angles in SAMPLES { + let m = rotation(angles); + let view = vectorcraft_three_d::Revolve { rotation_x: angles[0], rotation_y: angles[1], rotation_z: angles[2], ..Default::default() }; + for (axis, p) in view.view_axes().into_iter().enumerate() { + assert!((m[0][axis] - p.x).abs() < 1e-9 && (m[1][axis] - p.y).abs() < 1e-9, "{angles:?} axis {axis}"); + } + } +} + +/// Reading the angles back gives the same angles, presets included, Back's 180° and the gimbal +/// positions Left and Right (Y at ±90°) among them. +#[test] +fn angles_read_back_as_they_were_set() { + for angles in SAMPLES.into_iter().chain(ROTATION_PRESETS.iter().map(|(_, a)| *a)) { + let back = angles_of(rotation(angles), angles); + assert!(close(back, angles), "{angles:?} read back as {back:?}"); + assert!(same_rotation(rotation(back), rotation(angles))); + } + assert_eq!(angles_of(rotation([0.0, 180.0, 0.0]), [0.0, 180.0, 0.0]), [0.0, 180.0, 0.0], "Back stays Back, not -180°"); +} + +#[test] +fn angles_stay_in_the_fields_range_to_a_hundredth() { + assert_eq!(tidy(180.0), 180.0); + assert_eq!(tidy(-180.0), 180.0); + assert_eq!(tidy(190.004), -170.0); + assert_eq!(tidy(-0.001), 0.0); + assert!(tidy(-0.001).is_sign_positive()); +} + +fn drag(grip: Grip, angles: [f64; 3], from: Pos2, to: Pos2, shift: bool) -> [f64; 3] { + let mut g = CubeDrag { grip, matrix: rotation(angles), origin: from, last: from, lock: None, fields: Default::default() }; + // In five steps, as a pointer moves. + let mut near = angles; + for k in 1..=5 { + let p = from + (to - from) * (k as f32 / 5.0); + g.matrix = drag_turn(&mut g, p, pos2(0.0, 0.0), shift); + g.last = p; + near = angles_of(g.matrix, near); + } + near +} + +/// The ring turns in the screen plane, which the effects apply last: only Z changes, by the angle +/// swept, clockwise on the screen. +#[test] +fn the_ring_turns_z_only() { + for angles in SAMPLES { + let after = drag(Grip::Ring, angles, pos2(0.0, -53.0), pos2(53.0, 0.0), false); + assert!(close(after, [angles[0], angles[1], angles[2] + 90.0]), "{angles:?} -> {after:?}"); + } +} + +/// An edge turns about its own object axis. The effects turn X first, so an X edge changes X alone. +#[test] +fn an_x_edge_turns_x_only_and_other_edges_keep_their_axis() { + let start = [-21.88, -23.4, 9.06]; + let after = drag(Grip::Edge(0), start, pos2(0.0, 0.0), pos2(0.0, -40.0), false); + assert!((after[0] - start[0]).abs() > 5.0, "X turned: {after:?}"); + assert!((after[1] - start[1]).abs() < 0.02 && (after[2] - start[2]).abs() < 0.02, "Y and Z kept: {after:?}"); + for axis in 0..3 { + let after = drag(Grip::Edge(axis), start, pos2(0.0, 0.0), pos2(30.0, -30.0), false); + let (m0, m1) = (rotation(start), rotation(after)); + let column = |m: M3| [m[0][axis], m[1][axis], m[2][axis]]; + let (a, b) = (column(m0), column(m1)); + assert!(a.iter().zip(b).all(|(a, b)| (a - b).abs() < 1e-3), "axis {axis} stays put: {a:?} {b:?}"); + assert!(!same_rotation(m0, m1), "axis {axis}: the cube turned"); + } +} + +/// A face drag across the screen turns about the screen's vertical axis, the front toward the +/// drag; Shift keeps a slanted drag to that axis. +#[test] +fn a_face_drag_turns_the_front_toward_the_pointer_and_shift_keeps_one_axis() { + let front = [0.0, 0.0, 0.0]; + let right = drag(Grip::Face, front, pos2(0.0, 0.0), pos2(40.0, 0.0), false); + assert!(close(right, [0.0, 20.0, 0.0]), "{right:?}"); + // The front (+Z) turns toward +X. + assert!(rotation(right)[0][2] > 0.3); + let down = drag(Grip::Face, front, pos2(0.0, 0.0), pos2(0.0, 40.0), false); + assert!(close(down, [-20.0, 0.0, 0.0]), "{down:?}"); + assert!(rotation(down)[1][2] > 0.3, "the front turns down"); + let slanted = drag(Grip::Face, front, pos2(0.0, 0.0), pos2(40.0, 12.0), true); + assert!(close(slanted, [0.0, 20.0, 0.0]), "Shift across: {slanted:?}"); + let steep = drag(Grip::Face, front, pos2(0.0, 0.0), pos2(10.0, -40.0), true); + assert!(close(steep, [20.0, 0.0, 0.0]), "Shift up: {steep:?}"); + let free = drag(Grip::Face, front, pos2(0.0, 0.0), pos2(40.0, 12.0), false); + assert!(free[0].abs() > 1.0, "without Shift a slanted drag also tilts: {free:?}"); +} + +/// The centre is on the front face, the ring's band on the ring, and an edge's middle on that edge. +#[test] +fn grips_follow_what_is_under_the_pointer() { + let m = rotation([-21.88, -23.4, 9.06]); + let center = pos2(100.0, 100.0); + assert_eq!(grip_at(m, center, center), Grip::Face); + assert_eq!(grip_at(m, center, center + vec2(0.0, -(RING_IN + RING_OUT) / 2.0)), Grip::Ring); + for (i, j, axis) in edges().filter(|&(i, _, axis)| edge_visible(m, i, axis)) { + let middle = corner(m, center, i) + (corner(m, center, j) - corner(m, center, i)) * 0.5; + assert_eq!(grip_at(m, center, middle), Grip::Edge(axis), "edge {i}-{j}"); + } + // Facing front: four faces are edge-on and hidden, so only the front's edges show. + let m = rotation([0.0, 0.0, 0.0]); + assert_eq!(edges().filter(|&(i, _, axis)| edge_visible(m, i, axis)).count(), 4); +} diff --git a/crates/ui-egui/src/dialogs/three_d_classic.rs b/crates/ui-egui/src/dialogs/three_d_classic.rs index a3857c914a..32c4fbf764 100644 --- a/crates/ui-egui/src/dialogs/three_d_classic.rs +++ b/crates/ui-egui/src/dialogs/three_d_classic.rs @@ -2,14 +2,16 @@ //! compact 3D dialog: the extrude and bevel options, or the revolve options, on the Object tab //! (Rotate (Classic) has none), and the surface with its lights, highlight, blend steps and shading //! color on the Lighting tab. Position (presets, rotation, perspective) is the Rotation tab every 3D -//! effect shares. +//! effect shares; the classic effects add the track cube to it ([`track_cube`]). +use egui::{Color32, Id, Pos2, Rect, Sense, Stroke, Vec2, vec2}; use serde_json::{Value, json}; use vectorcraft_doc::Unit; use vectorcraft_effects::{CLASSIC_EXTRUDE, CLASSIC_REVOLVE, CLASSIC_ROTATE}; use super::revolve::{c3, choice, heading, label, light_list, light_record, number, reset_dot, selected_light, store_selected, toggle}; use crate::state::Dialog; +use crate::theme::Tokens; /// Depth and cap, then the bevel: its shape, its height and whether it is added around the shape /// or carved into it. @@ -111,3 +113,363 @@ fn shading_custom(ui: &mut egui::Ui, d: &mut Dialog) { } }); } + +/// The settings the track cube turns, as every 3D effect names them. +const ROTATION_KEYS: [&str; 3] = ["rotationX", "rotationY", "rotationZ"]; +/// The track cube's box side, its cube's half edge, and its ring's inner and outer radius (points). +const CUBE_BOX: f32 = 124.0; +const CUBE_HALF: f32 = 25.0; +const RING_IN: f32 = 47.0; +const RING_OUT: f32 = 60.0; +/// How far an edge reaches for the pointer (points). +const EDGE_REACH: f32 = 5.0; +/// Degrees one point of pointer movement turns the cube. +const DEGREES_PER_POINT: f64 = 0.5; + +/// A 3×3 rotation, rows first. +type M3 = [[f64; 3]; 3]; + +/// The rotation that X, Y and Z angles in degrees make, as the 3D effects apply them: X first, then +/// Y, then Z, in view space (x right, y down, z toward the viewer). +fn rotation(angles: [f64; 3]) -> M3 { + let [(sx, cx), (sy, cy), (sz, cz)] = angles.map(|a| a.to_radians().sin_cos()); + [[cz * cy, cz * sy * sx - sz * cx, cz * sy * cx + sz * sx], [sz * cy, sz * sy * sx + cz * cx, sz * sy * cx - cz * sx], [-sy, cy * sx, cy * cx]] +} + +fn multiply(a: M3, b: M3) -> M3 { + std::array::from_fn(|i| std::array::from_fn(|j| (0..3).map(|k| a[i][k] * b[k][j]).sum())) +} + +/// The unit vector along axis `axis` (0 X, 1 Y, 2 Z). +fn unit(axis: usize) -> [f64; 3] { + std::array::from_fn(|i| if i == axis { 1.0 } else { 0.0 }) +} + +/// A turn of `degrees` about the unit vector `axis`. +fn turn(axis: [f64; 3], degrees: f64) -> M3 { + let (s, c) = degrees.to_radians().sin_cos(); + let t = 1.0 - c; + let [x, y, z] = axis; + [ + [t * x * x + c, t * x * y - s * z, t * x * z + s * y], + [t * x * y + s * z, t * y * y + c, t * y * z - s * x], + [t * x * z - s * y, t * y * z + s * x, t * z * z + c], + ] +} + +/// An angle as the rotation fields hold it: above -180° up to 180°, to a hundredth of a degree. +fn tidy(degrees: f64) -> f64 { + let wrapped = 180.0 - (180.0 - degrees).rem_euclid(360.0); + (wrapped * 100.0).round() / 100.0 + 0.0 +} + +/// The X, Y and Z angles of rotation `m`. Of the two sets of angles that make it, the one nearest +/// `near` (so a turn moves the fields as little as it can); with Y at ±90°, where X and Z turn about +/// the same axis, X keeps its value from `near`. +fn angles_of(m: M3, near: [f64; 3]) -> [f64; 3] { + let y = (-m[2][0]).clamp(-1.0, 1.0).asin().to_degrees(); + let candidates = if m[2][1].hypot(m[2][2]) < 1e-9 { + let x = near[0]; + let z = if y > 0.0 { x - m[0][1].atan2(m[1][1]).to_degrees() } else { (-m[0][1]).atan2(m[1][1]).to_degrees() - x }; + [Some([x, y, z]), None] + } else { + let x = m[2][1].atan2(m[2][2]).to_degrees(); + let z = m[1][0].atan2(m[0][0]).to_degrees(); + [Some([x, y, z]), Some([x + 180.0, 180.0 - y, z + 180.0])] + }; + let distance = |a: &[f64; 3]| a.iter().zip(near).map(|(a, b)| tidy(a - b).abs()).sum::(); + candidates.into_iter().flatten().min_by(|a, b| distance(a).total_cmp(&distance(b))).unwrap_or(near).map(tidy) +} + +/// Where corner `i` of the cube (bit 0 for +X, bit 1 for +Y, bit 2 for +Z) is drawn. +fn corner(m: M3, center: Pos2, i: usize) -> Pos2 { + let p: [f64; 3] = std::array::from_fn(|b| if i >> b & 1 == 1 { 1.0 } else { -1.0 }); + let v: [f64; 3] = std::array::from_fn(|r| (0..3).map(|k| m[r][k] * p[k]).sum()); + center + vec2(v[0] as f32, v[1] as f32) * CUBE_HALF +} + +/// The corners of the face square to `axis` on its positive or negative side, in order round it. +fn face_corners(axis: usize, positive: bool) -> [usize; 4] { + let (b, c) = (1 << ((axis + 1) % 3), 1 << ((axis + 2) % 3)); + let side = if positive { 1 << axis } else { 0 }; + [0, b, b | c, c].map(|k| side | k) +} + +/// Does the face square to `axis` on that side face the viewer? +fn face_visible(m: M3, axis: usize, positive: bool) -> bool { + m[2][axis] * if positive { 1.0 } else { -1.0 } > 1e-6 +} + +/// The 12 edges: (from corner, to corner, the object axis it runs along). +fn edges() -> impl Iterator { + (0..3).flat_map(|axis| (0..8).filter(move |i| i >> axis & 1 == 0).map(move |i| (i, i | 1 << axis, axis))) +} + +/// Is the edge from corner `i` along `axis` on a face the viewer sees? +fn edge_visible(m: M3, i: usize, axis: usize) -> bool { + [(axis + 1) % 3, (axis + 2) % 3].into_iter().any(|other| face_visible(m, other, i >> other & 1 == 1)) +} + +/// What a press on the track cube takes hold of. +#[derive(Clone, Copy, Debug, PartialEq)] +enum Grip { + /// A face: a free turn, or with Shift about the screen's horizontal or vertical axis. + Face, + /// An edge: a turn about the object axis the edge runs along. + Edge(usize), + /// The ring round the cube: a turn in the plane of the screen. + Ring, +} + +/// The z of the cross product of two screen vectors: positive when `b` is clockwise from `a`. +fn cross(a: Vec2, b: Vec2) -> f32 { + a.x * b.y - a.y * b.x +} + +fn segment_distance(p: Pos2, a: Pos2, b: Pos2) -> f32 { + let line = b - a; + let t = ((p - a).dot(line) / line.length_sq().max(1e-6)).clamp(0.0, 1.0); + p.distance(a + line * t) +} + +/// What the pointer at `p` takes hold of on a cube drawn at `m` about `center`: the nearest edge +/// within reach, else a face, else the ring, else (in the box's corners) a face's free turn. +fn grip_at(m: M3, center: Pos2, p: Pos2) -> Grip { + let edge = edges() + .filter(|&(i, _, axis)| edge_visible(m, i, axis)) + .map(|(i, j, axis)| (segment_distance(p, corner(m, center, i), corner(m, center, j)), axis)) + .filter(|(distance, _)| *distance <= EDGE_REACH) + .min_by(|a, b| a.0.total_cmp(&b.0)); + if let Some((_, axis)) = edge { + return Grip::Edge(axis); + } + let on_face = (0..3).flat_map(|axis| [(axis, true), (axis, false)]).filter(|&(axis, side)| face_visible(m, axis, side)).any(|(axis, side)| { + let points = face_corners(axis, side).map(|i| corner(m, center, i)); + let sides: [f32; 4] = std::array::from_fn(|k| cross(points[(k + 1) % 4] - points[k], p - points[k])); + sides.iter().all(|s| *s >= 0.0) || sides.iter().all(|s| *s <= 0.0) + }); + let distance = p.distance(center); + if !on_face && (RING_IN - 3.0..=RING_OUT + 3.0).contains(&distance) { Grip::Ring } else { Grip::Face } +} + +/// The turn in degrees, clockwise on the screen, from `from` to `to` about `center`. +fn sweep(from: Pos2, to: Pos2, center: Pos2) -> f64 { + let (a, b) = (from - center, to - center); + if a.length() < 2.0 || b.length() < 2.0 { + return 0.0; + } + f64::from(cross(a, b)).atan2(f64::from(a.dot(b))).to_degrees() +} + +/// A track cube gesture in progress. +#[derive(Clone)] +struct CubeDrag { + grip: Grip, + /// The rotation so far, kept unrounded so a long drag does not drift. + matrix: M3, + origin: Pos2, + last: Pos2, + /// With Shift, the screen axis the drag keeps to (0 X, 1 Y), chosen once it has moved 3 points. + lock: Option, + /// The settings before the gesture, which Escape puts back. + fields: serde_json::Map, +} + +fn cube_drag_id() -> Id { + Id::unique("three-d-track-cube-drag") +} + +/// Is a track cube gesture in progress? +pub(super) fn cube_dragging(ctx: &egui::Context) -> bool { + ctx.data(|m| m.get_temp::(cube_drag_id())).is_some() +} + +/// The rotation after the pointer moves from `drag.last` to `p` on a cube about `center`. +fn drag_turn(drag: &mut CubeDrag, p: Pos2, center: Pos2, shift: bool) -> M3 { + let m = drag.matrix; + let delta = p - drag.last; + let (mut dx, mut dy) = (f64::from(delta.x), f64::from(delta.y)); + match drag.grip { + Grip::Face => { + if shift { + let total = p - drag.origin; + if drag.lock.is_none() && total.length() >= 3.0 { + drag.lock = Some(usize::from(total.x.abs() >= total.y.abs())); + } + match drag.lock { + // Across the screen turns about its vertical (Y) axis, up and down about X. + Some(1) => dy = 0.0, + Some(_) => dx = 0.0, + None => return m, + } + } + let length = dx.hypot(dy); + if length < 1e-9 { + return m; + } + // The front follows the pointer: right turns it toward +X, down toward +Y. + multiply(turn([-dy / length, dx / length, 0.0], length * DEGREES_PER_POINT), m) + } + Grip::Ring => multiply(turn(unit(2), sweep(drag.last, p, center)), m), + Grip::Edge(axis) => { + let w = [m[0][axis], m[1][axis], m[2][axis]]; + let across = w[0].hypot(w[1]); + // An axis pointing at the viewer turns like the ring, on the side it points to. + let degrees = + if w[2].abs() > 0.9 { sweep(drag.last, p, center) * w[2].signum() } else { (dx * w[1] - dy * w[0]) / across * DEGREES_PER_POINT }; + multiply(m, turn(unit(axis), degrees)) + } + } +} + +/// The track cube on the Rotation tab of Extrude & Bevel (Classic), Revolve (Classic) and Rotate +/// (Classic): a cube drawn at the current X, Y and Z rotation (the object's front blue, its top and +/// bottom light gray, its sides mid gray and its back dark gray) inside a ring. +/// +/// Dragging a face turns the object freely; with Shift the turn keeps to the screen's vertical axis +/// (a drag across) or its horizontal axis (a drag up or down), whichever the drag follows first. +/// Dragging an edge turns it about the object axis the edge runs along (X red, Y green, Z blue; the +/// edge takes its axis's color while hovered or dragged), and dragging in the ring turns it in the +/// plane of the screen. Escape during a drag puts the angles back. With the keyboard focus, the +/// arrow keys turn it about the screen's axes and Page Up and Page Down in the screen plane, 1° a +/// press (15° with Shift). The angles go to `rotationX`, `rotationY` and `rotationZ` only when they +/// change, and the dialog's preview applies them; OK records them as one undo step. The cube is off +/// while a rotation setting is mixed (the 3D and Materials panel's selected effects differ). +pub(super) fn track_cube(ui: &mut egui::Ui, d: &mut Dialog) { + let defaults = vectorcraft_effects::default_params(&d.str("__effect")).unwrap_or(json!({})); + let mixed = ROTATION_KEYS.iter().any(|k| d.fields.get(*k).is_some_and(Value::is_null)); + let now = ROTATION_KEYS.map(|k| d.f64(k, defaults.get(k).and_then(Value::as_f64).unwrap_or(0.0))); + let mut next = None; + ui.horizontal(|ui| { + ui.spacing_mut().item_spacing.x = 6.0; + let name = tl!("Track cube"); + label(ui, name); + let (rect, _) = ui.allocate_exact_size(Vec2::splat(CUBE_BOX), Sense::hover()); + let enabled = ui.is_enabled() && !mixed; + let id = Id::unique("three-d-track-cube"); + let resp = ui.interact(rect, id, if enabled { Sense::click_and_drag() } else { Sense::hover() }); + let center = rect.center(); + let value = format!("X {:.2}°, Y {:.2}°, Z {:.2}°", now[0], now[1], now[2]); + resp.widget_info(|| egui::WidgetInfo { + current_text_value: Some(value.clone()), + ..egui::WidgetInfo::labeled(egui::Role::Slider, enabled, name) + }); + if enabled + && resp.drag_started() + && let Some(origin) = ui.input(|i| i.pointer.press_origin()) + { + // A value being typed ends first; the cube then takes the keyboard focus below. + ui.ctx().memory_mut(|m| m.stop_text_input()); + let matrix = rotation(now); + let drag = CubeDrag { grip: grip_at(matrix, center, origin), matrix, origin, last: origin, lock: None, fields: d.fields.clone() }; + ui.data_mut(|m| m.insert_temp(cube_drag_id(), drag)); + } + if enabled && (resp.drag_started() || resp.clicked()) { + resp.request_focus(); + } + let mut drag = ui.data(|m| m.get_temp::(cube_drag_id())); + if let Some(current) = &mut drag { + if ui.input_mut(|i| i.consume_key(i.modifiers, egui::Key::Escape)) { + d.fields = current.fields.clone(); + d.fields.remove(super::form::PREVIEWED); + ui.data_mut(|m| m.remove::(cube_drag_id())); + drag = None; + } else if let Some(p) = resp.interact_pointer_pos() { + let shift = ui.input(|i| i.modifiers.shift); + current.matrix = drag_turn(current, p, center, shift); + current.last = p; + next = Some(angles_of(current.matrix, now)); + ui.data_mut(|m| m.insert_temp(cube_drag_id(), current.clone())); + } + } + if resp.drag_stopped() || !ui.input(|i| i.pointer.primary_down()) { + ui.data_mut(|m| m.remove::(cube_drag_id())); + } + if enabled && resp.has_focus() { + use egui::{Key, Modifiers}; + ui.memory_mut(|m| { + m.set_focus_lock_filter(id, egui::EventFilter { horizontal_arrows: true, vertical_arrows: true, ..Default::default() }) + }); + let mut m = rotation(now); + let mut turned = false; + for (key, axis, sign) in [ + (Key::ArrowRight, 1, 1.0), + (Key::ArrowLeft, 1, -1.0), + (Key::ArrowUp, 0, 1.0), + (Key::ArrowDown, 0, -1.0), + (Key::PageDown, 2, 1.0), + (Key::PageUp, 2, -1.0), + ] { + // Shift first: a key without modifiers also matches the key with Shift. + for (modifiers, step) in [(Modifiers::SHIFT, 15.0), (Modifiers::NONE, 1.0)] { + while ui.input_mut(|i| i.consume_key(modifiers, key)) { + m = multiply(turn(unit(axis), sign * step), m); + turned = true; + } + } + } + if turned { + next = Some(angles_of(m, now)); + } + } + let shown = next.unwrap_or(now); + let hovered = drag.as_ref().map(|g| g.grip).or_else(|| resp.hover_pos().filter(|_| enabled).map(|p| grip_at(rotation(shown), center, p))); + if enabled && (resp.hovered() || drag.is_some()) { + ui.ctx().set_cursor_icon(match (hovered, drag.is_some()) { + (Some(Grip::Edge(_)), _) => egui::CursorIcon::ResizeHorizontal, + (_, true) => egui::CursorIcon::Grabbing, + _ => egui::CursorIcon::Grab, + }); + } + paint_cube(ui, rect, rotation(shown), hovered, enabled, resp.has_focus()); + resp.on_hover_text(tl!("Drag a face, an edge or the ring to rotate. Shift turns about one screen axis.")); + }); + if let Some(angles) = next + && angles.iter().zip(now).any(|(a, b)| (a - b).abs() > 1e-9) + { + for (key, angle) in ROTATION_KEYS.into_iter().zip(angles) { + d.fields.insert(key.into(), json!(angle)); + } + } +} + +/// Draws the track cube at rotation `m` in `rect`, with the grip under the pointer or in hand lit. +fn paint_cube(ui: &egui::Ui, rect: Rect, m: M3, active: Option, enabled: bool, focused: bool) { + let t = Tokens::get(ui.ctx()); + let fade = |c: Color32| if enabled { c } else { c.gamma_multiply(0.4) }; + let painter = ui.painter_at(rect); + let center = rect.center(); + painter.rect_filled(rect, 2.0, t.input); + let ring = if active == Some(Grip::Ring) { 0.6 } else { 0.3 }; + painter.circle_stroke(center, (RING_IN + RING_OUT) / 2.0, Stroke::new(RING_OUT - RING_IN, fade(t.axis_z.gamma_multiply(ring)))); + // Front blue; top and bottom light, sides mid and back dark gray. Only faces turned to the + // viewer are drawn, and on a cube those never overlap. + for (axis, side) in (0..3).flat_map(|axis| [(axis, true), (axis, false)]) { + if !face_visible(m, axis, side) { + continue; + } + let fill = match (axis, side) { + (2, true) => t.axis_z, + (2, false) => Color32::from_gray(90), + (1, _) => Color32::from_gray(205), + _ => Color32::from_gray(155), + }; + let points: Vec = face_corners(axis, side).iter().map(|&i| corner(m, center, i)).collect(); + painter.add(egui::Shape::convex_polygon(points, fade(fill), Stroke::NONE)); + } + let colors = [t.axis_x, t.axis_y, t.axis_z]; + for (i, j, axis) in edges() { + if !edge_visible(m, i, axis) { + continue; + } + let lit = active == Some(Grip::Edge(axis)); + let stroke = if lit { Stroke::new(2.5, fade(colors[axis])) } else { Stroke::new(1.0, fade(t.text_dim)) }; + painter.line_segment([corner(m, center, i), corner(m, center, j)], stroke); + } + let border = if focused { t.accent } else { t.input_border }; + painter.rect_stroke(rect, 2.0, Stroke::new(1.0, border), egui::StrokeKind::Inside); +} + +#[cfg(test)] +#[path = "tests_track_cube.rs"] +mod tests; diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 948b295e17..848a3e3527 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5130,6 +5130,8 @@ effect Soft Меко Step up or down Стъпка нагоре или надолу Drag values or labels. Double-click a value to type. Плъзнете стойности или етикети. Кликнете двукратно върху стойност за въвеждане. Drag rings to rotate. Shift snaps to 15°. Плъзнете пръстените за завъртане. Shift прилепва към 15°. + Track cube Куб за завъртане + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Плъзнете стена, ръб или пръстена за завъртане. Shift завърта около една ос на екрана. Drag the light, or its rings. Shift snaps to 15°. Плъзнете светлината или нейните пръстени. Shift прилепва към 15°. Keep visible surfaces only when expanding Запазване само на видимите повърхности при разширяване Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Изрязване на закритата геометрия при разширяване на оформлението. Живият преглед запазва цялата повърхност. Прозрачните бои и повърхности, надвишаващи границите на видимост, запазват всички лица. diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index f8d6fa263b..b6131c4cff 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -1029,6 +1029,8 @@ Step up or down Korak gore ili dolje Drag values or labels. Double-click a value to type. Prevucite vrijednosti ili oznake. Dvostruki klik na vrijednost za upis. Drag rings to rotate. Shift snaps to 15°. Prevucite prstenove za rotaciju. Shift hvata na 15°. + Track cube Kocka za rotaciju + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Prevucite stranu, ivicu ili prsten za rotaciju. Shift rotira oko jedne ose ekrana. Drag the light, or its rings. Shift snaps to 15°. Prevucite svjetlo ili njegove prstenove. Shift hvata na 15°. lighting Light Svjetlo Keep visible surfaces only when expanding Pri proširivanju zadrži samo vidljive površine diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 3ed3ca8331..31e5a72c78 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -1341,6 +1341,8 @@ antiAlias Sharp Nítid Drag out from a corner, or click two straight lines, then click to place. Arrossega cap enfora des d’una cantonada, o fes clic en dues línies rectes i després fes clic per col·locar-ho. Drag out from a segment, or click two points, then click to place. Arrossega cap enfora des d’un segment, o fes clic en dos punts i després fes clic per col·locar-ho. Drag rings to rotate. Shift snaps to 15°. Arrossega els anells per girar. La tecla Maj ajusta a 15°. + Track cube Cub de rotació + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Arrossega una cara, una aresta o l'anell per girar. La tecla Maj gira al voltant d'un eix de la pantalla. Drag the art to move it on the paper Arrossega la il·lustració per moure-la sobre el paper Drag the light, or its rings. Shift snaps to 15°. Arrossega el llum o els seus anells. La tecla Maj ajusta a 15°. Drag to float as a toolbar Arrossega per surar com a barra d’eines diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 161e235c56..044995624b 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -1051,6 +1051,8 @@ Step up or down O krok nahoru nebo dolů Drag values or labels. Double-click a value to type. Upravte hodnoty přetažením čísel nebo popisků. Dvojím kliknutím na hodnotu ji zadáte. Drag rings to rotate. Shift snaps to 15°. Otáčejte přetažením prstenců. Shift nastaví kroky po 15°. + Track cube Otočná krychle + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Otáčejte přetažením stěny, hrany nebo prstence. Shift otáčí kolem jedné osy obrazovky. Drag the light, or its rings. Shift snaps to 15°. Přetáhněte světlo nebo jeho prstence. Shift nastaví kroky po 15°. lighting Light Světlo Keep visible surfaces only when expanding Při rozložení ponechat pouze viditelné povrchy diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index fd0b7c042e..5ac1a6a428 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -1579,6 +1579,8 @@ Reset all Revolve settings to their defaults. Nulstil alle indstillinger for omdrejning til standardværdierne. Drag values or labels. Double-click a value to type. Træk i værdier eller etiketter. Dobbeltklik på en værdi for at indtaste. Drag rings to rotate. Shift snaps to 15°. Træk i ringe for at rotere. Shift fastgør til 15°. + Track cube Rotationsterning + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Træk i en flade, en kant eller ringen for at rotere. Shift roterer om én skærmakse. Drag the light, or its rings. Shift snaps to 15°. Træk i lyset eller dets ringe. Shift fastgør til 15°. lighting Light Lys Keep visible surfaces only when expanding Behold kun synlige flader ved udvidelse diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 2a5f32a5d9..2bac4955fd 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5427,6 +5427,8 @@ theme Appearance Erscheinungsbild # 3D and Materials › Revolve: its options dialog and gizmos. Axis side Achsenseite Drag rings to rotate. Shift snaps to 15°. Ziehen Sie die Ringe, um zu drehen. Mit Shift in 15°-Schritten. + Track cube Rotationswürfel + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Ziehen Sie eine Fläche, eine Kante oder den Ring, um zu drehen. Mit Shift um eine Bildschirmachse. Drag the light, or its rings. Shift snaps to 15°. Ziehen Sie das Licht oder seine Ringe. Mit Shift in 15°-Schritten. lighting Light Licht Drag values or labels. Double-click a value to type. Ziehen Sie Werte oder Beschriftungen. Doppelklicken Sie auf einen Wert, um ihn einzugeben. diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index b1b7b4d039..0a858147bb 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -5313,6 +5313,8 @@ digits Malayalam Μαλαγιάλαμ Step up or down Ένα βήμα πάνω ή κάτω Drag values or labels. Double-click a value to type. Σύρετε τιμές ή ετικέτες. Κάντε διπλό κλικ σε μια τιμή για πληκτρολόγηση. Drag rings to rotate. Shift snaps to 15°. Σύρετε τους δακτυλίους για περιστροφή. Με Shift γίνεται προσκόλληση ανά 15°. + Track cube Κύβος περιστροφής + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Σύρετε μια έδρα, μια ακμή ή τον δακτύλιο για περιστροφή. Με Shift η περιστροφή γίνεται γύρω από έναν άξονα της οθόνης. Drag the light, or its rings. Shift snaps to 15°. Σύρετε την πηγή φωτός ή τους δακτυλίους της. Με Shift γίνεται προσκόλληση ανά 15°. Keep visible surfaces only when expanding Διατήρηση μόνο ορατών επιφανειών κατά την ανάπτυξη Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Αποκοπή καλυμμένης γεωμετρίας κατά την Ανάπτυξη εμφάνισης. Η ζωντανή προεπισκόπηση διατηρεί ολόκληρη την επιφάνεια. Διαφανή γεμίσματα και επιφάνειες που υπερβαίνουν τα όρια ορατότητας διατηρούν όλες τις έδρες. diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 5b3c675f7b..225ac69850 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -5436,6 +5436,8 @@ axis Both Ambos Step up or down Subir o bajar un paso Drag values or labels. Double-click a value to type. Arrastra los valores o las etiquetas. Haz doble clic en un valor para escribir. Drag rings to rotate. Shift snaps to 15°. Arrastra los anillos para girar. Shift ajusta a pasos de 15°. + Track cube Cubo de rotación + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Arrastra una cara, una arista o el anillo para girar. Shift gira sobre un eje de la pantalla. Drag the light, or its rings. Shift snaps to 15°. Arrastra la luz o sus anillos. Shift ajusta a pasos de 15°. lighting Light Luz Keep visible surfaces only when expanding Conservar solo superficies visibles al expandir diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index e77a617c52..527cc035ca 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -1032,6 +1032,8 @@ Step up or down Askel ylös tai alas Drag values or labels. Double-click a value to type. Vedä arvoja tai nimiöitä. Kirjoita arvo kaksoisnapsauttamalla. Drag rings to rotate. Shift snaps to 15°. Kierrä vetämällä renkaita. Vaihto kohdistaa 15°:n välein. + Track cube Kiertokuutio + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Kierrä vetämällä tahkoa, särmää tai rengasta. Vaihto kiertää yhden näytön akselin ympäri. Drag the light, or its rings. Shift snaps to 15°. Vedä valoa tai sen renkaita. Vaihto kohdistaa 15°:n välein. lighting Light Valo Keep visible surfaces only when expanding Säilytä laajennettaessa vain näkyvät pinnat diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 2762d2a954..be53056710 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -5440,6 +5440,8 @@ effect Soft Doux Step up or down Augmenter ou diminuer d’un cran Drag values or labels. Double-click a value to type. Faites glisser les valeurs ou les libellés. Double-cliquez sur une valeur pour la saisir. Drag rings to rotate. Shift snaps to 15°. Faites glisser les anneaux pour tourner. Maj contraint par pas de 15°. + Track cube Cube de rotation + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Faites glisser une face, une arête ou l'anneau pour tourner. Maj fait tourner autour d'un axe de l'écran. Drag the light, or its rings. Shift snaps to 15°. Faites glisser la lumière ou ses anneaux. Maj contraint par pas de 15°. Keep visible surfaces only when expanding Conserver uniquement les surfaces visibles lors de la décomposition Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Découpe la géométrie masquée lors de la décomposition de l’aspect. L’aperçu conserve toute la surface. Les peintures transparentes et les surfaces dépassant les limites de visibilité conservent toutes les faces. diff --git a/crates/ui-egui/src/i18n/gu.tsv b/crates/ui-egui/src/i18n/gu.tsv index 1c96900b2b..0257008013 100644 --- a/crates/ui-egui/src/i18n/gu.tsv +++ b/crates/ui-egui/src/i18n/gu.tsv @@ -1060,6 +1060,8 @@ Drag art or swatches here, or use + to add the selection's colors and text styles. અહીં આર્ટ અથવા સ્વેચ ખેંચો, અથવા પસંદગીના રંગો અને ટેક્સ્ટ સ્ટાઇલ ઉમેરવા + વાપરો. Drag onto art to apply this appearance આ દેખાવ લાગુ કરવા આર્ટ પર ખેંચો Drag rings to rotate. Shift snaps to 15°. ફેરવવા રિંગ્સ ખેંચો. Shift 15° પર સ્નેપ કરે છે. + Track cube ફેરવવાનો ઘન + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. ફેરવવા ફલક, ધાર અથવા રિંગ ખેંચો. Shift સ્ક્રીનની એક અક્ષની આસપાસ ફેરવે છે. Drag the art to move it on the paper કાગળ પર ખસેડવા આર્ટ ખેંચો Drag the light, or its rings. Shift snaps to 15°. લાઇટ અથવા તેની રિંગ્સ ખેંચો. Shift 15° પર સ્નેપ કરે છે. Drag to float as a toolbar ટૂલબાર તરીકે ફ્લોટ કરવા ખેંચો diff --git a/crates/ui-egui/src/i18n/hi.tsv b/crates/ui-egui/src/i18n/hi.tsv index 548f961f51..5cca42ec81 100644 --- a/crates/ui-egui/src/i18n/hi.tsv +++ b/crates/ui-egui/src/i18n/hi.tsv @@ -1502,6 +1502,8 @@ antiAlias Sharp शार्प Drag out from a corner, or click two straight lines, then click to place. एक कोने से बाहर खींचें, या दो सीधी रेखाओं पर क्लिक करें, फिर जगह पर क्लिक करें। Drag out from a segment, or click two points, then click to place. एक खंड से बाहर खींचें, या दो बिंदुओं पर क्लिक करें, फिर जगह पर क्लिक करें। Drag rings to rotate. Shift snaps to 15°. घुमाने के लिए रिंग ड्रैग करें। Shift से 15° पर स्नैप होता है। + Track cube घुमाने का घन + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. घुमाने के लिए फलक, किनारा या रिंग ड्रैग करें। Shift से स्क्रीन के एक अक्ष के चारों ओर घूमता है। Drag the art to move it on the paper इसे कागज पर स्थानांतरित करने के लिए कला को खींचें Drag the light, or its rings. Shift snaps to 15°. लाइट या उसकी रिंग ड्रैग करें। Shift से 15° पर स्नैप होता है। Drag to float as a toolbar एक टूलबार के रूप में तैरने के लिए खींचें diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 200b45d1b1..68f968f0b7 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -1202,6 +1202,8 @@ font-technology Variable Változó Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Húzz ide grafikát, színeket és szövegstílusokat, hogy más dokumentumokban is használhasd őket. A könyvtárak ezen a gépen tárolódnak, fiók nem kell hozzájuk. Drag onto art to apply this appearance Grafikára húzva alkalmazza ezt a megjelenést Drag rings to rotate. Shift snaps to 15°. Forgatáshoz húzd a gyűrűket. Shift: illesztés 15°-onként. + Track cube Forgatókocka + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Forgatáshoz húzz egy lapot, egy élt vagy a gyűrűt. Shift: forgatás egy képernyőtengely körül. Drag the art to move it on the paper Húzd a grafikát a papíron való áthelyezéshez Drag the light, or its rings. Shift snaps to 15°. Húzd a fényt vagy a gyűrűit. Shift: illesztés 15°-onként. Drag to float as a toolbar Húzd el, hogy önálló eszköztárként lebegjen diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index f3dcca3874..a8086b7bf8 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -970,6 +970,8 @@ Reset all Revolve settings to their defaults. Atur ulang semua pengaturan Revolusi ke defaultnya. Drag values or labels. Double-click a value to type. Seret nilai atau label. Klik dua kali pada nilai untuk mengetik. Drag rings to rotate. Shift snaps to 15°. Seret cincin untuk memutar. Shift mengunci ke kelipatan 15°. + Track cube Kubus putar + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Seret sisi, tepi, atau cincin untuk memutar. Shift memutar pada satu sumbu layar. Drag the light, or its rings. Shift snaps to 15°. Seret cahaya atau cincinnya. Shift mengunci ke kelipatan 15°. lighting Light Cahaya Keep visible surfaces only when expanding Pertahankan hanya permukaan yang terlihat saat memperluas diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 90ec79404c..6ff35214b9 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5488,6 +5488,8 @@ effect Regular Normale Step up or down Aumenta o riduci di un passo Drag values or labels. Double-click a value to type. Trascina i valori o le etichette. Fai doppio clic su un valore per digitarlo. Drag rings to rotate. Shift snaps to 15°. Trascina gli anelli per ruotare. Shift usa incrementi di 15°. + Track cube Cubo di rotazione + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Trascina una faccia, uno spigolo o l'anello per ruotare. Shift ruota attorno a un asse dello schermo. Drag the light, or its rings. Shift snaps to 15°. Trascina la luce o i suoi anelli. Shift usa incrementi di 15°. lighting Light Luce Keep visible surfaces only when expanding Mantieni solo le superfici visibili durante l’espansione diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index d98acfa22d..2b015c035d 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5530,6 +5530,8 @@ effect Vertical 垂直 Step up or down 1 段階ずつ増減 Drag values or labels. Double-click a value to type. 数値やラベルをドラッグして調整。数値をダブルクリックすると入力できます。 Drag rings to rotate. Shift snaps to 15°. リングをドラッグして回転。Shiftで15°刻みに調整。 + Track cube 回転キューブ + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. 面、辺、またはリングをドラッグして回転。Shiftで画面の1つの軸を中心に回転。 Drag the light, or its rings. Shift snaps to 15°. ライトやリングをドラッグ。Shiftで15°刻みに調整。 lighting Light ライト Keep visible surfaces only when expanding アピアランス分割時に見える面だけを保持 diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 794e5454a9..46ed70611d 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -936,6 +936,8 @@ Reset all Revolve settings to their defaults. ბრუნვის ყველა პარამეტრის ნაგულისხმევზე დაბრუნება. Drag values or labels. Double-click a value to type. გადაათრიეთ მნიშვნელობები ან წარწერები. მნიშვნელობის ასაკრეფად ორჯერ დაწკაპეთ. Drag rings to rotate. Shift snaps to 15°. შესაბრუნებლად გადაათრიეთ რგოლები. Shift მიაბამს 15°-ის ჯერად კუთხეებზე. + Track cube შემობრუნების კუბი + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. შესაბრუნებლად გადაათრიეთ წახნაგი, წიბო ან რგოლი. Shift აბრუნებს ეკრანის ერთი ღერძის გარშემო. Drag the light, or its rings. Shift snaps to 15°. გადაათრიეთ სინათლე ან მისი რგოლები. Shift მიაბამს 15°-ის ჯერად კუთხეებზე. lighting Light სინათლე Keep visible surfaces only when expanding გაშლისას მხოლოდ ხილული ზედაპირების შენარჩუნება diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index bb8676df71..ee18f844f0 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5451,6 +5451,8 @@ effect Vertical 세로 Step up or down 한 단계 올리기 또는 내리기 Drag values or labels. Double-click a value to type. 값이나 레이블을 드래그하세요. 값을 더블클릭하면 입력할 수 있습니다. Drag rings to rotate. Shift snaps to 15°. 링을 드래그해 회전하세요. Shift를 누르면 15°씩 스냅됩니다. + Track cube 회전 큐브 + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. 면, 모서리 또는 링을 드래그해 회전하세요. Shift를 누르면 화면의 한 축을 중심으로 회전합니다. Drag the light, or its rings. Shift snaps to 15°. 조명이나 링을 드래그하세요. Shift를 누르면 15°씩 스냅됩니다. lighting Light 광원 Keep visible surfaces only when expanding 확장 시 보이는 표면만 유지 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index a597a03fd0..e0551183d4 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -1488,6 +1488,8 @@ Reset all Revolve settings to their defaults. Stelt alle draai-instellingen opnieuw in op de standaardwaarden. Drag values or labels. Double-click a value to type. Sleep waarden of labels. Dubbelklik op een waarde om te typen. Drag rings to rotate. Shift snaps to 15°. Sleep de ringen om te roteren. Shift lijnt uit op 15°. + Track cube Rotatiekubus + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Sleep een vlak, een rand of de ring om te roteren. Shift roteert om één schermas. Drag the light, or its rings. Shift snaps to 15°. Sleep het licht of de ringen ervan. Shift lijnt uit op 15°. lighting Light Licht Keep visible surfaces only when expanding Alleen zichtbare oppervlakken behouden bij het uitbreiden diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 99ee2080b8..4700f51df3 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -989,6 +989,8 @@ Step up or down Zwiększ lub zmniejsz o krok Drag values or labels. Double-click a value to type. Przeciągaj wartości lub etykiety. Kliknij dwukrotnie wartość, aby ją wpisać. Drag rings to rotate. Shift snaps to 15°. Przeciągaj pierścienie, aby obracać. Shift przyciąga co 15°. + Track cube Kostka obrotu + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Przeciągnij ścianę, krawędź lub pierścień, aby obracać. Shift obraca wokół jednej osi ekranu. Drag the light, or its rings. Shift snaps to 15°. Przeciągaj światło lub jego pierścienie. Shift przyciąga co 15°. lighting Light Światło Keep visible surfaces only when expanding Przy rozszerzaniu zachowaj tylko widoczne powierzchnie diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 677c8e460f..80cdc99314 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3545,6 +3545,8 @@ imageTrace Outline Contorno Step up or down Aumentar ou diminuir um passo Drag values or labels. Double-click a value to type. Arraste os valores ou rótulos. Clique duas vezes em um valor para digitá-lo. Drag rings to rotate. Shift snaps to 15°. Arraste os anéis para girar. Shift ajusta em incrementos de 15°. + Track cube Cubo de rotação + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Arraste uma face, uma aresta ou o anel para girar. Shift gira em torno de um eixo da tela. Drag the light, or its rings. Shift snaps to 15°. Arraste a luz ou seus anéis. Shift ajusta em incrementos de 15°. lighting Light Luz Keep visible surfaces only when expanding Manter apenas as superfícies visíveis ao expandir diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 106343cddd..1ec6a5837d 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -5475,6 +5475,8 @@ axis Both Оба Step up or down Шаг вверх или вниз Drag values or labels. Double-click a value to type. Перетаскивайте значения или подписи. Для ввода дважды щёлкните значение. Drag rings to rotate. Shift snaps to 15°. Перетаскивайте кольца для вращения. Shift задаёт шаг 15°. + Track cube Куб вращения + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Перетащите грань, ребро или кольцо для вращения. Shift вращает вокруг одной оси экрана. Drag the light, or its rings. Shift snaps to 15°. Перетаскивайте источник света или кольца. Shift задаёт шаг 15°. lighting Light Свет Keep visible surfaces only when expanding При разборе сохранять только видимые поверхности diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index cc854aa362..27fc92b2c4 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -5341,6 +5341,8 @@ effect Soft Mjuk Step up or down Ett steg upp eller ned Drag values or labels. Double-click a value to type. Dra i värden eller etiketter. Dubbelklicka på ett värde för att skriva. Drag rings to rotate. Shift snaps to 15°. Dra i ringarna för att rotera. Shift fäster i steg om 15°. + Track cube Rotationskub + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Dra i en sida, en kant eller ringen för att rotera. Shift roterar runt en skärmaxel. Drag the light, or its rings. Shift snaps to 15°. Dra i ljuset eller dess ringar. Shift fäster i steg om 15°. Keep visible surfaces only when expanding Behåll endast synliga ytor vid expandering Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Trimma dold geometri vid Expandera utseende. Live-förhandsgranskningen behåller hela ytan. Transparenta färger och ytor som överskrider synlighetsgränserna behåller alla sidoytor. diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index f2888a30f0..91957420aa 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -5232,6 +5232,8 @@ effect Soft Yumuşak Reset all Revolve settings to their defaults. Tüm Revolve ayarlarını varsayılan değerlerine sıfırlayın. Drag values or labels. Double-click a value to type. Değerleri veya etiketleri sürükleyin. Yazmak için bir değeri çift tıklayın. Drag rings to rotate. Shift snaps to 15°. Döndürmek için halkaları sürükleyin. Kaydırma 15°'ye sabitlenir. + Track cube Döndürme küpü + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Döndürmek için bir yüzü, kenarı veya halkayı sürükleyin. Shift tek bir ekran ekseni etrafında döndürür. Drag the light, or its rings. Shift snaps to 15°. Işığı veya halkalarını sürükleyin. Kaydırma 15°'ye sabitlenir. Keep visible surfaces only when expanding Görünür yüzeyleri yalnızca genişletirken koruyun Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Görünümü Genişlet'te kapsanan geometriyi kırpın. Canlı önizleme tüm yüzeyi korur. Şeffaf boyalar ve görünürlük sınırlarını aşan yüzeyler tüm yüzleri korur. diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 411b2c3baf..74c0b04e31 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5468,6 +5468,8 @@ axis Both Обидві Step up or down Крок угору або вниз Drag values or labels. Double-click a value to type. Перетягуйте значення або підписи. Двічі клацніть значення, щоб увести його. Drag rings to rotate. Shift snaps to 15°. Перетягуйте кільця для обертання. Shift задає крок 15°. + Track cube Куб обертання + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Перетягніть грань, ребро або кільце для обертання. Shift обертає навколо однієї осі екрана. Drag the light, or its rings. Shift snaps to 15°. Перетягуйте джерело світла або його кільця. Shift задає крок 15°. Keep visible surfaces only when expanding Під час розгортання зберігати лише видимі поверхні Trim covered geometry on Expand Appearance. Live preview keeps the full surface. Transparent paints and surfaces exceeding visibility limits retain all faces. Обрізає приховану геометрію під час розгортання оформлення. Попередній перегляд зберігає всю поверхню. Прозорі заливки та поверхні, що перевищують обмеження видимості, зберігають усі грані. diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 1460910197..768660857c 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -1094,6 +1094,8 @@ font-technology Variable Biến thiên Drag art, colors and text styles here to reuse them across documents. Libraries are stored on this machine — no account required. Kéo tác phẩm, màu và kiểu chữ vào đây để tái sử dụng trong các tài liệu. Thư viện được lưu trên máy này — không cần tài khoản. Drag onto art to apply this appearance Kéo lên tác phẩm để áp dụng diện mạo này Drag rings to rotate. Shift snaps to 15°. Kéo vòng để xoay. Shift khớp ở 15°. + Track cube Khối xoay + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. Kéo một mặt, một cạnh hoặc vòng để xoay. Shift xoay quanh một trục màn hình. Drag the art to move it on the paper Kéo tác phẩm để di chuyển trên trang Drag the light, or its rings. Shift snaps to 15°. Kéo đèn hoặc vòng của nó. Shift khớp ở 15°. Drag to float as a toolbar Kéo để thả nổi thanh công cụ diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 3fe2f9e009..a15564480b 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5748,6 +5748,8 @@ effect Vertical 垂直 Step up or down 逐级增减 Drag values or labels. Double-click a value to type. 拖动数值或标签。双击数值即可输入。 Drag rings to rotate. Shift snaps to 15°. 拖动圆环旋转。按住 Shift 以 15° 为步长调整。 + Track cube 旋转立方体 + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. 拖动面、边或圆环进行旋转。按住 Shift 绕一个屏幕轴旋转。 Drag the light, or its rings. Shift snaps to 15°. 拖动光源或圆环。按住 Shift 以 15° 为步长调整。 lighting Light 光源 Keep visible surfaces only when expanding 扩展时仅保留可见表面 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index ab7f9d9dd2..0a06fec655 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5583,6 +5583,8 @@ effect Soft 柔和 Step up or down 逐級增減 Drag values or labels. Double-click a value to type. 拖曳數值或標籤。按兩下數值即可輸入。 Drag rings to rotate. Shift snaps to 15°. 拖曳圓環旋轉。按住 Shift 以 15° 為間距調整。 + Track cube 旋轉立方體 + Drag a face, an edge or the ring to rotate. Shift turns about one screen axis. 拖曳面、邊或圓環進行旋轉。按住 Shift 繞一個螢幕軸旋轉。 Drag the light, or its rings. Shift snaps to 15°. 拖曳光源或圓環。按住 Shift 以 15° 為間距調整。 lighting Light 光源 Keep visible surfaces only when expanding 展開時僅保留可見表面 diff --git a/docs/effects-parity.md b/docs/effects-parity.md index 342a5171b6..444973a787 100644 --- a/docs/effects-parity.md +++ b/docs/effects-parity.md @@ -181,7 +181,7 @@ VectorCraft choices; an original approximation, never compared with another appl | Revolve | initial: live, editable profile, rotation and light gizmos, default-on Cap with continuous curved rims, smooth vector shading and round seam joins, overlapping faces drawn in the order of their depths where they overlap, SVG/PDF output, Expand Appearance ([revolve.md](revolve.md), #846) | | Extrude & Bevel, Inflate, Rotate | initial: live, round seam joins, depth, twist, taper, solid or hollow caps, five original bevel shapes, Inflate volume, both sides, twist, taper and cap, Rotate of every fill and stroke, groups and layers turned about one shared axis (#4506), contours that cross, touch or overlap merged first ([three-d.md](three-d.md)), Position presets, rotation and light gizmos, SVG/PDF output, Expand Appearance ([three-d.md](three-d.md)) | | Materials panel, lighting, ray-traced render | Window › 3D and Materials panel with the object types (Plane, Extrude, Revolve, Inflate) and the Object, Rotation and Lighting settings of the applied effect, and Quick Actions › Expand as Wireframes ([three-d.md](three-d.md#3d-and-materials-panel)); one to ten directional lights with per-light Color (opaque RGB, white by default), Intensity, Rotation, Height, Softness and neutral Ambient Light, Revolve and colored Inflate use smooth vector shading; Extrude & Bevel and Rotate retain planar shading; original Standard, Diffuse, Top Left and Right lighting rigs; hard-edged shadows behind or below the object from the first light ([three-d.md](three-d.md#shadows)); the Materials tab's Graphics, document symbols mapped onto Extrude & Bevel's caps and onto a plane, moved with an artboard handle, and wrapped around Revolve's side ([three-d.md](three-d.md#graphics)); no base materials, graphics on Inflate or on Extrude & Bevel's walls and bevels, soft shadows or ray tracing | -| 3D (Classic): Extrude & Bevel, Revolve, Rotate | Extrude & Bevel (Classic) initial: live, round seam joins, Extrude Depth, Cap, five original bevel shapes with Height and Bevel Extent In or Out, Position presets and rotation gizmo, Perspective, Wireframe, No Shading, Diffuse and Plastic surfaces with one to ten lights, Light Intensity, Ambient Light, Highlight Intensity and Size, Blend Steps, Black, Custom or no Shading Color, Draw Hidden Faces ([create 3D objects](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/create-3d-objects.html)); Revolve (Classic) initial: live, round seam joins, Angle, Cap, Offset from the left or right edge, Position presets and rotation gizmo, Perspective, the same four surfaces, lights, highlight, Blend Steps and Shading Color, groups and layers revolved about one shared axis (same page); Rotate (Classic) initial: live, round seam joins, every fill and stroke turned as one plane, Position presets and rotation gizmo, Perspective, No Shading and Diffuse surfaces with lights, Ambient Light, Blend Steps and Shading Color ([rotate objects in 3D](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/rotate-objects-in-3d.html)); Map Art: none | +| 3D (Classic): Extrude & Bevel, Revolve, Rotate | Extrude & Bevel (Classic) initial: live, round seam joins, Extrude Depth, Cap, five original bevel shapes with Height and Bevel Extent In or Out, Position presets and rotation gizmo, Perspective, Wireframe, No Shading, Diffuse and Plastic surfaces with one to ten lights, Light Intensity, Ambient Light, Highlight Intensity and Size, Blend Steps, Black, Custom or no Shading Color, Draw Hidden Faces ([create 3D objects](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/create-3d-objects.html)); Revolve (Classic) initial: live, round seam joins, Angle, Cap, Offset from the left or right edge, Position presets and rotation gizmo, Perspective, the same four surfaces, lights, highlight, Blend Steps and Shading Color, groups and layers revolved about one shared axis (same page); Rotate (Classic) initial: live, round seam joins, every fill and stroke turned as one plane, Position presets and rotation gizmo, Perspective, No Shading and Diffuse surfaces with lights, Ambient Light, Blend Steps and Shading Color ([rotate objects in 3D](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/rotate-objects-in-3d.html)); all three: the track cube (face, edge, Shift and ring drags, keyboard steps; [three-d.md](three-d.md#track-cube)); Map Art: none | | Turntable (30.0 beta) | none | | Export 3D objects (OBJ, USDA, glTF) | initial: Asset Export's Export 3D Object collects an object with Extrude & Bevel, Revolve, Inflate or Rotate as a 3D asset, and Export writes its solids as a USDA (the default), USDZ, glTF or OBJ mesh with a material in each solid's paint color (`assets.add3D`, `assets.set3DFormat`, `assets.export3D`); no textures, map art or shading ([3D export help](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/export-3d-vector-artwork.html)) | diff --git a/docs/three-d.md b/docs/three-d.md index f12ca4675c..0f06709a21 100644 --- a/docs/three-d.md +++ b/docs/three-d.md @@ -259,6 +259,27 @@ of an isometric view (each visible axis foreshortened equally). These are our ow each view; the dialog shows a preset's name when the angles match it and **Custom** otherwise. The new effects open at Off-Axis Front. +## Track cube + +The Rotation tab of Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic) has a track cube below the +presets, drawn at the current X, Y and Z rotation inside a ring +([set 3D rotation position options](https://web.archive.org/web/20220911143322/https://helpx.adobe.com/pdf/cs6/illustrator_reference.pdf#page=249)): + +- Dragging a face turns the object freely. With Shift, a drag across turns it about the screen's vertical axis and a + drag up or down about its horizontal axis, whichever the drag follows first. +- Dragging an edge turns it about the object axis that edge runs along. The edge under the pointer shows its axis's + color: red for X, green for Y, blue for Z. +- Dragging in the ring turns it in the plane of the screen. +- Escape during a drag puts the angles back. With the keyboard focus, the arrow keys turn it about the screen's + horizontal and vertical axes, and Page Up and Page Down turn it in the screen plane, 1° a press or 15° with Shift. + +The drag changes the effect's `rotationX`, `rotationY` and `rotationZ` and previews live; X, Y and Z and the +presets follow it (**Custom** off a preset). OK keeps the change as one undo step. The angles stay between -180° +and 180°: of the two sets of angles that give the same turn, the cube writes the one nearest the current values. +In the 3D and Materials panel, a drag is one undo step when the pointer is released, and the cube is off while the +selected effects hold different rotations. The cube's colors, its 0.5° per point of drag and its keyboard steps are +VectorCraft's own. + ## Perspective **Perspective** runs 0° to 160°, as the public panel options give it ("how close or far the object appears"), @@ -502,8 +523,8 @@ out, perspective). No Adobe software, assets, presets or bevel shapes were used. - **3D (Classic)**: Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic) are built, with their surface types and blend steps. Revolve (Classic) sweeps the surface Revolve sweeps, at 64 segments for a full turn, and draws every face back to front, so it has no Draw Hidden Faces setting. Rotate (Classic) turns the plane Rotate turns, seen - from either side, with the No Shading and Diffuse surfaces only. The track cube, map art, Turntable and 3D export - are separate, unbuilt items. + from either side, with the No Shading and Diffuse surfaces only. Their Rotation tab has the track cube + ([Track cube](#track-cube)). Map art, Turntable and 3D export are separate, unbuilt items. Release measurements and a repeatable CLI scene are in [3D lighting performance](3d-lighting-performance.md).