mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-12 12:24:54 +00:00
3D (Classic): Rotate (Classic) turns flat art in 3D with the No Shading and Diffuse surfaces
Effect › 3D and Materials › 3D (Classic) › Rotate (Classic)… turns every fill and stroke as one flat plane, as Rotate does, and shades it with the classic No Shading or Diffuse surface. - `effect.apply` with `threeD.classicRotate`: the Position rotation and Perspective, `surface` (`none` or `diffuse`), the classic lights, Ambient Light, Blend Steps and Shading Color. It has no highlight, no Draw Hidden Faces and no shadow. - The dialog shows Position on the Rotation tab and the surface and lights on the Lighting tab, with no Object tab. - Groups and layers turn about one shared center, and the effect also goes on a fill or stroke, as Rotate's does. - `effect.lightingPreset` refuses it, as it refuses the other classic 3D effects. - Unknown parameters, numbers out of range, bad flags, a surface other than No Shading or Diffuse and bad shading colors are refused with an error.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
use super::{
|
||||
CLASSIC_EXTRUDE, CLASSIC_REVOLVE, CROP_MARKS, EXTRUDE, INFLATE, PATHFINDER_EFFECTS, RASTERIZE, REVOLVE, ROTATE, TRAP, Value, json, trap,
|
||||
CLASSIC_EXTRUDE, CLASSIC_REVOLVE, CLASSIC_ROTATE, CROP_MARKS, EXTRUDE, INFLATE, PATHFINDER_EFFECTS, RASTERIZE, REVOLVE, ROTATE, TRAP, Value,
|
||||
json, trap,
|
||||
};
|
||||
|
||||
/// Catalogue entry for one effect.
|
||||
@@ -217,6 +218,13 @@ pub fn effect_catalog() -> Vec<EffectInfo> {
|
||||
"{angle: degrees (0..360, 360; how far the profile sweeps round the axis), cap: bool (true: solid, the open ends closed; false: hollow), offset: pt (0..1000, 0; the axis's distance from the profile, a ring past 0), edge: left|right (left; the axis is at the left or the right edge of the profile's bounds), rotationX: degrees (-180..180, -21.88), rotationY: degrees (-180..180, -23.4), rotationZ: degrees (-180..180, 9.06; the defaults are the Off-Axis Front position), perspectiveAngle: degrees (0..160, 0; 0 is none, wider brings the eye nearer), surface: wireframe|none|diffuse|plastic (plastic; wireframe draws the edges of every face and fills none; none keeps the paint unlit; diffuse lights the faces softly; plastic adds a glossy highlight), lights: null or array of 1..10 objects (intensity: % 0..100, rotation: degrees -180..180, height: degrees 0..90, behind: bool; overrides the single-light fields; the lights add up; color and softness are not used), lightRotation: degrees (-180..180, 125.26; round the object on the page, 0 on its right, counterclockwise), lightHeight: degrees (0..90, 30; 0 beside the object, 90 straight in front), lightBehind: bool (false; true lights it from behind), lightIntensity: % (0..100, 100), ambient: % (0..100, 50; Ambient Light, every face whichever way it faces), highlightIntensity: % (0..100, 60; plastic only), highlightSize: % (0..100, 90; 100 broad, 0 a small spot; plastic only), blendSteps: integer (1..256, 25; how many levels the shading and the highlight are rounded to), shadingColor: black|custom|none (black; the color the faces darken toward; none keeps the paint and adds only the highlight), shadingCustom: #rrggbb (#000000; the color shadingColor custom darkens toward)}. The profile sweeps round a vertical axis, as Revolve's does, painted with the top fill (else the top stroke); every face is drawn, back to front. The defaults are VectorCraft's own.",
|
||||
json!({"angle":360.0,"cap":true,"offset":0.0,"edge":"left","rotationX":-21.88,"rotationY":-23.4,"rotationZ":9.06,"perspectiveAngle":0.0,"surface":"plastic","lights":null,"lightRotation":125.26438968275465,"lightHeight":30.0,"lightBehind":false,"lightIntensity":100.0,"ambient":50.0,"highlightIntensity":60.0,"highlightSize":90.0,"blendSteps":25,"shadingColor":"black","shadingCustom":"#000000"}),
|
||||
),
|
||||
g(
|
||||
CLASSIC_ROTATE,
|
||||
"Rotate (Classic)…",
|
||||
&["Effect", "3D and Materials", "3D (Classic)"],
|
||||
"{rotationX: degrees (-180..180, -21.88), rotationY: degrees (-180..180, -23.4), rotationZ: degrees (-180..180, 9.06; the defaults are the Off-Axis Front position), perspectiveAngle: degrees (0..160, 0; 0 is none, wider brings the eye nearer), surface: none|diffuse (diffuse; none keeps the paint unlit; diffuse lights the art softly), lights: null or array of 1..10 objects (intensity: % 0..100, rotation: degrees -180..180, height: degrees 0..90, behind: bool; overrides the single-light fields; the lights add up; color and softness are not used), lightRotation: degrees (-180..180, 125.26; round the object on the page, 0 on its right, counterclockwise), lightHeight: degrees (0..90, 30; 0 beside the object, 90 straight in front), lightBehind: bool (false; true lights it from behind), lightIntensity: % (0..100, 100), ambient: % (0..100, 50; Ambient Light, the art whichever way it faces), blendSteps: integer (1..256, 25; how many levels the shading is rounded to), shadingColor: black|custom|none (black; the color the art darkens toward; none keeps the paint), shadingCustom: #rrggbb (#000000; the color shadingColor custom darkens toward)}. Every fill and stroke turns as one flat plane about the center of the object's bounds, as Rotate's do, each in its own paint, and is seen from either side. The defaults are VectorCraft's own.",
|
||||
json!({"rotationX":-21.88,"rotationY":-23.4,"rotationZ":9.06,"perspectiveAngle":0.0,"surface":"diffuse","lights":null,"lightRotation":125.26438968275465,"lightHeight":30.0,"lightBehind":false,"lightIntensity":100.0,"ambient":50.0,"blendSteps":25,"shadingColor":"black","shadingCustom":"#000000"}),
|
||||
),
|
||||
g(
|
||||
"convertToShape.rectangle",
|
||||
"Rectangle…",
|
||||
|
||||
@@ -87,10 +87,10 @@ pub use revolve::{
|
||||
CLASSIC_REVOLVE, REVOLVE, has_revolve, is_revolve, revolve_art, revolve_art_in, revolve_options, validate_revolve, validate_revolve_in,
|
||||
};
|
||||
pub use solids::{
|
||||
CLASSIC_EXTRUDE, EXTRUDE, INFLATE, ROTATE, ROTATION_PRESETS, SHADING_COLORS, SOLIDS, SolidMesh, THREE_D_TYPES, classic_extrude_options,
|
||||
classic_shading, extrude_options, has_solid, inflate_options, is_three_d, solid_art, solid_art_in, three_d_meshes, three_d_meshes_in,
|
||||
three_d_meshes_of, three_d_meshes_of_in, three_d_params_as, validate_solid, validate_solid_in, validate_three_d, validate_three_d_at,
|
||||
validate_three_d_at_in, validate_three_d_in, validate_three_d_params, view_options,
|
||||
CLASSIC_EXTRUDE, CLASSIC_ROTATE, EXTRUDE, INFLATE, ROTATE, ROTATE_SURFACES, ROTATION_PRESETS, SHADING_COLORS, SOLIDS, SolidMesh, THREE_D_TYPES,
|
||||
classic_extrude_options, classic_shading, extrude_options, has_solid, inflate_options, is_classic_three_d, is_three_d, solid_art, solid_art_in,
|
||||
three_d_meshes, three_d_meshes_in, three_d_meshes_of, three_d_meshes_of_in, three_d_params_as, validate_solid, validate_solid_in,
|
||||
validate_three_d, validate_three_d_at, validate_three_d_at_in, validate_three_d_in, validate_three_d_params, view_options,
|
||||
};
|
||||
pub use stylize::{ScribbleSetting, scribble_setting_id, scribble_settings};
|
||||
pub use svg_filter::SVG_FILTER;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Extrude & Bevel, Inflate, Rotate and Extrude & Bevel (Classic) as live, shaded vector art,
|
||||
//! Extrude & Bevel, Inflate, Rotate, Extrude & Bevel (Classic) and Rotate (Classic) as live, shaded vector art,
|
||||
//! shared by the canvas and every exporter. Like Revolve, the source path stays editable and the effect draws its 3D faces.
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -15,7 +15,7 @@ use crate::util::{flag, num, text};
|
||||
use crate::{GeomContext, apply_geometry_with, is_geometry};
|
||||
|
||||
pub(crate) mod classic;
|
||||
pub use classic::{CLASSIC_EXTRUDE, SHADING_COLORS, classic_extrude_options, classic_shading};
|
||||
pub use classic::{CLASSIC_EXTRUDE, CLASSIC_ROTATE, ROTATE_SURFACES, SHADING_COLORS, classic_extrude_options, classic_shading};
|
||||
|
||||
mod source;
|
||||
pub(crate) use source::{resolve, resolve_for};
|
||||
@@ -26,13 +26,24 @@ pub const EXTRUDE: &str = "threeD.extrude";
|
||||
pub const INFLATE: &str = "threeD.inflate";
|
||||
pub const ROTATE: &str = "threeD.rotate";
|
||||
/// The 3D effects this module draws (Revolve has its own).
|
||||
pub const SOLIDS: [&str; 4] = [EXTRUDE, INFLATE, ROTATE, CLASSIC_EXTRUDE];
|
||||
pub const SOLIDS: [&str; 5] = [EXTRUDE, INFLATE, ROTATE, CLASSIC_EXTRUDE, CLASSIC_ROTATE];
|
||||
|
||||
/// Is `id` one of the 3D and Materials effects (Revolve and Revolve (Classic) included)?
|
||||
pub fn is_three_d(id: &str) -> bool {
|
||||
crate::revolve::is_revolve(id) || SOLIDS.contains(&id)
|
||||
}
|
||||
|
||||
/// Is `id` one of the 3D (Classic) effects: Extrude & Bevel (Classic), Revolve (Classic) or Rotate
|
||||
/// (Classic)?
|
||||
pub fn is_classic_three_d(id: &str) -> bool {
|
||||
id == CLASSIC_EXTRUDE || id == crate::CLASSIC_REVOLVE || id == CLASSIC_ROTATE
|
||||
}
|
||||
|
||||
/// Does 3D effect `id` turn the art as a flat plane: Rotate or Rotate (Classic)?
|
||||
fn is_plane(id: &str) -> bool {
|
||||
id == ROTATE || id == CLASSIC_ROTATE
|
||||
}
|
||||
|
||||
/// The 3D and Materials panel's object types and the effect that draws each: (type, effect id).
|
||||
/// Plane keeps the art flat, which is what Rotate draws.
|
||||
pub const THREE_D_TYPES: [(&str, &str); 4] = [("plane", ROTATE), ("extrude", EXTRUDE), ("revolve", crate::REVOLVE), ("inflate", INFLATE)];
|
||||
@@ -193,6 +204,9 @@ fn validate_solid_params(id: &str, p: &Value) -> Result<(), String> {
|
||||
if id == CLASSIC_EXTRUDE {
|
||||
classic::validate(p)?;
|
||||
}
|
||||
if id == CLASSIC_ROTATE {
|
||||
classic::validate_rotate(p)?;
|
||||
}
|
||||
crate::lighting::validate(p)?;
|
||||
shadow::validate(p)?;
|
||||
if let Some(v) = p.get("bevel")
|
||||
@@ -225,7 +239,7 @@ pub fn validate_solid_in(doc: Option<&Document>, n: &Node, id: &str, p: &Value)
|
||||
fn validate_solid_for(doc: Option<&Document>, n: &Node, id: &str, p: &Value, item: Option<usize>) -> Result<(), String> {
|
||||
validate_solid_params(id, p)?;
|
||||
let label = label_of(id);
|
||||
let n = &*source::resolve_for(doc, n, label, id == ROTATE)?;
|
||||
let n = &*source::resolve_for(doc, n, label, is_plane(id))?;
|
||||
if is_container(n) {
|
||||
let shapes = members(n, label)?;
|
||||
let b = union_bounds(&shapes).ok_or_else(|| format!("{label} requires a group holding a shape with at least two anchors"))?;
|
||||
@@ -708,12 +722,12 @@ fn art_for(n: &Node, fx: &Effect, shapes: &[Profile]) -> Option<ShadowAndSurface
|
||||
/// What the shadow of the solids [`surfaces_for`] builds for effect `id` with parameters `p` on
|
||||
/// `shapes` is cast from, on one plane: the faces of every solid, the shadow's settings and the
|
||||
/// faces of each solid of visible art with its member's scopes and ink. `None` with the shadow
|
||||
/// off, for Extrude & Bevel (Classic) or with no shapes. A stroke's solid that is not built (past
|
||||
/// off, for Extrude & Bevel (Classic) and Rotate (Classic) or with no shapes. A stroke's solid that is not built (past
|
||||
/// the limits) casts nothing, as it draws nothing; any other solid that is not built is an error.
|
||||
/// When a member's opacity mask or blending mode applies to its solids ([`masked`]), it applies to
|
||||
/// the part of the shadow they cast too ([`shadow::scoped_art`]).
|
||||
fn cast_shadow<'a>(id: &str, p: &Value, shapes: &'a [Profile]) -> Result<Option<Cast<'a>>, String> {
|
||||
if id == CLASSIC_EXTRUDE {
|
||||
if id == CLASSIC_EXTRUDE || id == CLASSIC_ROTATE {
|
||||
return Ok(None);
|
||||
}
|
||||
let (Some(settings), Some(b)) = (shadow::options(p), union_bounds(shapes)) else { return Ok(None) };
|
||||
@@ -775,12 +789,12 @@ type Cast<'a> = (vectorcraft_three_d::ShadowCaster, vectorcraft_three_d::Shadow,
|
||||
fn surfaces_for(n: &Node, fx: &Effect, shapes: &[Profile]) -> Option<Vec<Arc<Node>>> {
|
||||
let b = union_bounds(shapes)?;
|
||||
let p = crate::merged_params(&fx.id, &fx.params);
|
||||
let classic = fx.id == CLASSIC_EXTRUDE;
|
||||
let classic = fx.id == CLASSIC_EXTRUDE || fx.id == CLASSIC_ROTATE;
|
||||
let colored = !classic && view_options(&p).has_colored_lights();
|
||||
let masks = has_masks(shapes);
|
||||
// The paint copies of every solid together: past the allowance the source path is drawn.
|
||||
let mut paint_bytes = 0usize;
|
||||
if fx.id == ROTATE {
|
||||
if is_plane(&fx.id) {
|
||||
// Every part turns about the centre of the object's own bounds (a group's members about
|
||||
// the centre of theirs), so they stay together.
|
||||
let mut out = vec![];
|
||||
@@ -792,7 +806,8 @@ fn surfaces_for(n: &Node, fx: &Effect, shapes: &[Profile]) -> Option<Vec<Arc<Nod
|
||||
if !crate::revolve::paint_copies_add(&mut paint_bytes, facet_copies(&f, &paint, colored), &paint) {
|
||||
return None;
|
||||
}
|
||||
out.push((s.scopes.as_slice(), Arc::new(surface(n, f, &paint, opacity, blend, colored))));
|
||||
let art = if classic { classic::surface(n, f, &paint, opacity, blend, &p) } else { surface(n, f, &paint, opacity, blend, colored) };
|
||||
out.push((s.scopes.as_slice(), Arc::new(art)));
|
||||
}
|
||||
}
|
||||
return Some(if masks { masked(n.id, out) } else { out.into_iter().map(|(_, s)| s).collect() });
|
||||
@@ -1001,7 +1016,7 @@ fn meshes_for(fx: &Effect, shapes: &[Profile], total: &mut usize, limit: usize)
|
||||
let view = view_options(&p);
|
||||
let mut out = vec![];
|
||||
let mut keep = |out: &mut Vec<SolidMesh>, mesh: Mesh, color: [f32; 4]| keep_mesh(out, total, SolidMesh { mesh, color }, limit);
|
||||
if fx.id == ROTATE {
|
||||
if is_plane(&fx.id) {
|
||||
for s in shapes {
|
||||
for item in &s.items {
|
||||
let (paint, opacity, _) = paint_of(item, s.opacity);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Extrude & Bevel (Classic): the solid Extrude & Bevel builds from a path, drawn with the classic
|
||||
//! surfaces (Wireframe, No Shading, Diffuse and Plastic), their highlight, blend steps and shading
|
||||
//! color.
|
||||
//! color. Rotate (Classic): the plane Rotate turns, drawn with the No Shading and Diffuse surfaces.
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::Value;
|
||||
@@ -12,6 +12,10 @@ use vectorcraft_three_d::{Bevel, BevelShape, ClassicShading, ClassicSurface, Ext
|
||||
use crate::util::{flag, num, text};
|
||||
|
||||
pub const CLASSIC_EXTRUDE: &str = "threeD.classicExtrude";
|
||||
pub const CLASSIC_ROTATE: &str = "threeD.classicRotate";
|
||||
|
||||
/// The surfaces Rotate (Classic) takes: No Shading and Diffuse.
|
||||
pub const ROTATE_SURFACES: [&str; 2] = ["none", "diffuse"];
|
||||
|
||||
/// The width of a Wireframe edge, in points.
|
||||
const WIRE: f64 = 0.5;
|
||||
@@ -107,6 +111,29 @@ pub fn validate(p: &Value) -> Result<(), String> {
|
||||
validate_classic(CLASSIC_EXTRUDE, p, &RANGES, &FLAGS)
|
||||
}
|
||||
|
||||
/// Rotate (Classic)'s numeric parameters: (key, lowest, highest, whole numbers only).
|
||||
const ROTATE_RANGES: [(&str, f64, f64, bool); 9] = [
|
||||
("rotationX", -180.0, 180.0, false),
|
||||
("rotationY", -180.0, 180.0, false),
|
||||
("rotationZ", -180.0, 180.0, false),
|
||||
("perspectiveAngle", 0.0, 160.0, false),
|
||||
("lightRotation", -180.0, 180.0, false),
|
||||
("lightHeight", 0.0, 90.0, false),
|
||||
("lightIntensity", 0.0, 100.0, false),
|
||||
("ambient", 0.0, 100.0, false),
|
||||
("blendSteps", 1.0, 256.0, true),
|
||||
];
|
||||
|
||||
/// Check Rotate (Classic)'s parameters `p` (its defaults filled in) as [`validate`] checks Extrude &
|
||||
/// Bevel (Classic)'s, with `surface` No Shading or Diffuse only.
|
||||
pub fn validate_rotate(p: &Value) -> Result<(), String> {
|
||||
validate_classic(CLASSIC_ROTATE, p, &ROTATE_RANGES, &["lightBehind"])?;
|
||||
if p.get("surface").is_some_and(|v| !v.as_str().is_some_and(|s| ROTATE_SURFACES.contains(&s))) {
|
||||
return Err(format!("`surface` must be one of {}", ROTATE_SURFACES.join(", ")));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Check the parameters `p` of classic 3D effect `id` (its defaults filled in): every key is one of
|
||||
/// its parameters, the numbers `ranges` lists, as (key, lowest, highest, whole numbers only), are in
|
||||
/// range, the `flags` are true or false, and `surface`, `shadingColor` and `shadingCustom` name a
|
||||
|
||||
@@ -55,7 +55,7 @@ fn needs(n: &Node) -> bool {
|
||||
/// a clipping path too complex to cut the art with. `label` is the effect's name. The art is cut
|
||||
/// for the solid effect `n` carries ([`resolve_for`]).
|
||||
pub(crate) fn resolve<'a>(doc: Option<&Document>, n: &'a Node, label: &str) -> Result<Cow<'a, Node>, String> {
|
||||
let every_fill = super::solid(&n.appearance.effects).is_some_and(|(_, fx)| fx.id == super::ROTATE);
|
||||
let every_fill = super::solid(&n.appearance.effects).is_some_and(|(_, fx)| super::is_plane(&fx.id));
|
||||
resolve_for(doc, n, label, every_fill)
|
||||
}
|
||||
|
||||
|
||||
@@ -889,3 +889,80 @@ fn classic_parameters_are_checked() {
|
||||
assert!(err.starts_with(says), "{p}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
/// The red square, stroked black, with Rotate (Classic) and `params`: its surfaces, the fill's
|
||||
/// plane first, then the stroke's outline.
|
||||
fn classic_rotate(params: Value) -> Vec<Node> {
|
||||
let mut n = square_node();
|
||||
n.appearance.effects.push(fx(CLASSIC_ROTATE, params));
|
||||
solid_art(&n).unwrap().children().unwrap().iter().map(|s| (**s).clone()).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_rotate_turns_every_fill_and_stroke_as_one_plane() {
|
||||
let front = json!({"rotationX": 0, "rotationY": 0, "rotationZ": 0});
|
||||
let surfaces = classic_rotate(front.clone());
|
||||
assert_eq!(surfaces.len(), 2, "the fill's plane and the stroke's outline");
|
||||
assert!(surfaces.iter().all(|s| s.children().unwrap().len() == 1), "each is one flat face");
|
||||
let fill = surfaces[0].visual_bounds().unwrap();
|
||||
assert!((fill.width() - 100.0).abs() < 1.0 && (fill.height() - 100.0).abs() < 1.0, "{fill:?}");
|
||||
// Turned 60° about the vertical axis, the square shows half its width and all its height,
|
||||
// about the center of its bounds.
|
||||
let turned = classic_rotate(json!({"rotationX": 0, "rotationY": 60, "rotationZ": 0}))[0].visual_bounds().unwrap();
|
||||
assert!((turned.width() - 50.0).abs() < 1.0 && (turned.height() - 100.0).abs() < 1.0, "{turned:?}");
|
||||
assert!((turned.center().x - 150.0).abs() < 1.0, "{turned:?}");
|
||||
// Turned past 90°, the back of the plane is drawn.
|
||||
let back = classic_rotate(json!({"rotationX": 0, "rotationY": 150, "rotationZ": 0}));
|
||||
assert!(back[0].visual_bounds().is_some_and(|b| (b.width() - 100.0 * 30f64.to_radians().cos()).abs() < 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_rotate_shades_with_no_shading_or_diffuse() {
|
||||
let rgb = |p: Value| fill_rgb(&classic_rotate(p)[0].children().unwrap()[0]);
|
||||
// No Shading keeps the paint, the color itself kept.
|
||||
let mut n = square_node();
|
||||
n.appearance.effects.push(fx(CLASSIC_ROTATE, json!({"surface": "none"})));
|
||||
let art = solid_art(&n).unwrap();
|
||||
let AppearanceItem::Fill(f) = &faces(&art)[0].appearance.items[0] else { panic!("a fill first") };
|
||||
assert_eq!(f.paint, Paint::solid(RED));
|
||||
// Diffuse: straight on with the light in front, the face is fully lit; turned away from the
|
||||
// light, it is the red darkened toward black.
|
||||
let lit = json!({"rotationX": 0, "rotationY": 0, "rotationZ": 0, "lightHeight": 90});
|
||||
assert!(close3(rgb(lit), RED.to_rgb()));
|
||||
let [r, g, b] = rgb(json!({"rotationX": 0, "rotationY": 70, "rotationZ": 0, "lightHeight": 90}));
|
||||
assert!(r < 0.8 - 0.05 && (g * 4.0 - r).abs() < 1e-4 && (b * 8.0 - r).abs() < 1e-4, "{r} {g} {b}");
|
||||
// No light: the ambient share of the red. A custom shading color darkens toward that color;
|
||||
// None keeps the paint.
|
||||
let dark = rgb(json!({"lightIntensity": 0, "ambient": 20, "blendSteps": 256}));
|
||||
assert!((dark[0] - 0.8 * 0.2).abs() < 0.01, "{dark:?}");
|
||||
let blue = rgb(json!({"lightIntensity": 0, "ambient": 20, "shadingColor": "custom", "shadingCustom": "#0000ff"}));
|
||||
assert!(blue[2] > blue[0] / 8.0 + 0.01, "{blue:?}");
|
||||
assert!(close3(rgb(json!({"lightIntensity": 0, "shadingColor": "none"})), RED.to_rgb()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn classic_rotate_parameters_are_checked() {
|
||||
let n = square_node();
|
||||
let check = |p: Value| validate_solid(&n, CLASSIC_ROTATE, &crate::merged_params(CLASSIC_ROTATE, &p));
|
||||
assert!(check(json!({})).is_ok());
|
||||
assert!(check(json!({"surface": "none", "shadingColor": "custom", "shadingCustom": "#12aBcd", "blendSteps": 1})).is_ok());
|
||||
for (p, says) in [
|
||||
(json!({"surface": "plastic"}), "`surface` must be one of none, diffuse"),
|
||||
(json!({"surface": "wireframe"}), "`surface` must be one of none, diffuse"),
|
||||
(json!({"surface": 2}), "`surface` must be one of"),
|
||||
(json!({"shadingColor": "red"}), "`shadingColor` must be one of black, custom, none"),
|
||||
(json!({"shadingCustom": "#fff"}), "`shadingCustom` must be a #rrggbb color"),
|
||||
(json!({"rotationX": 181}), "`rotationX` must be between -180 and 180 (got 181)"),
|
||||
(json!({"perspectiveAngle": 161}), "`perspectiveAngle` must be between 0 and 160 (got 161)"),
|
||||
(json!({"lightIntensity": 101}), "`lightIntensity` must be between 0 and 100 (got 101)"),
|
||||
(json!({"blendSteps": 0}), "`blendSteps` must be a whole number between 1 and 256 (got 0)"),
|
||||
(json!({"lightBehind": 1}), "`lightBehind` must be true or false"),
|
||||
(json!({"highlightSize": 50}), "unknown parameter `highlightSize`"),
|
||||
(json!({"drawHiddenFaces": true}), "unknown parameter `drawHiddenFaces`"),
|
||||
(json!({"shadow": true}), "unknown parameter `shadow`"),
|
||||
(json!({"lights": []}), "lights must contain one to ten lights"),
|
||||
] {
|
||||
let err = check(p.clone()).unwrap_err();
|
||||
assert!(err.starts_with(says), "{p}: {err}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"3D Lighting Preset",
|
||||
[],
|
||||
None,
|
||||
"{index: int, preset: standard|diffuse|topLeft|right, item?: appearance item index|null (omitted: the active item, else the object's), ids?|id?: [..], target?: object|contents, aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection)} replace an applied 3D effect's light rig and ambient with an original lighting preset; preserve shading, geometry and view; all targets must have a 3D effect at index other than Extrude & Bevel (Classic) or Revolve (Classic), which have no lighting presets; one undo step → {ids}",
|
||||
"{index: int, preset: standard|diffuse|topLeft|right, item?: appearance item index|null (omitted: the active item, else the object's), ids?|id?: [..], target?: object|contents, aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection)} replace an applied 3D effect's light rig and ambient with an original lighting preset; preserve shading, geometry and view; all targets must have a 3D effect at index other than Extrude & Bevel (Classic), Revolve (Classic) or Rotate (Classic), which have no lighting presets; one undo step → {ids}",
|
||||
has_doc,
|
||||
lighting_preset
|
||||
),
|
||||
@@ -657,7 +657,7 @@ fn lighting_preset(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
return Err(bad(C, format!("object {} needs a 3D effect at index {index}", id.0)));
|
||||
}
|
||||
// The classic 3D effects have no lighting presets; their lights are set in their own options.
|
||||
if let Some(e) = effect.filter(|e| e.id == effects::CLASSIC_EXTRUDE || e.id == effects::CLASSIC_REVOLVE) {
|
||||
if let Some(e) = effect.filter(|e| effects::is_classic_three_d(&e.id)) {
|
||||
let name = effects::effect_info(&e.id).map_or("This effect", |info| info.label.trim_end_matches('…'));
|
||||
return Err(bad(C, format!("object {}: {name} at index {index} has no lighting presets; set its lights with effect.setParams", id.0)));
|
||||
}
|
||||
|
||||
@@ -130,3 +130,17 @@ fn lighting_presets_refuse_revolve_classic() {
|
||||
assert_eq!(s.doc().unwrap().doc, before);
|
||||
assert!(!s.doc().unwrap().is_dirty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lighting_presets_refuse_rotate_classic() {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({})).unwrap();
|
||||
let id = s.execute("shape.rectangle", &json!({"x":100,"y":100,"width":80,"height":60})).unwrap()["id"].as_u64().unwrap();
|
||||
s.execute("effect.apply", &json!({"effect":"threeD.classicRotate","item":null,"params":{"lightRotation":40}})).unwrap();
|
||||
s.doc_mut().unwrap().mark_saved();
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
let err = s.execute("effect.lightingPreset", &json!({"ids":[id],"index":0,"item":null,"preset":"diffuse"})).unwrap_err().to_string();
|
||||
assert!(err.contains(&format!("object {id}: Rotate (Classic) at index 0 has no lighting presets")), "{err}");
|
||||
assert_eq!(s.doc().unwrap().doc, before);
|
||||
assert!(!s.doc().unwrap().is_dirty());
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use vectorcraft_doc::{NodeId, NodeKind};
|
||||
|
||||
use crate::Session;
|
||||
|
||||
const TYPES: [&str; 5] = ["threeD.extrude", "threeD.classicExtrude", "threeD.inflate", "threeD.rotate", "threeD.revolve"];
|
||||
const TYPES: [&str; 6] = ["threeD.extrude", "threeD.classicExtrude", "threeD.inflate", "threeD.rotate", "threeD.classicRotate", "threeD.revolve"];
|
||||
|
||||
fn id_of(v: &Value) -> NodeId {
|
||||
NodeId(v["id"].as_u64().unwrap())
|
||||
|
||||
@@ -400,7 +400,8 @@ pub(super) fn own_choices(effect: &str, key: &str, cmyk: bool) -> Option<Vec<(&'
|
||||
(vectorcraft_effects::CLASSIC_EXTRUDE | vectorcraft_effects::CLASSIC_REVOLVE, "surface") => {
|
||||
vec![(tl!("Wireframe"), "wireframe"), (tl!("No Shading"), "none"), (tl!("Diffuse"), "diffuse"), (tl!("Plastic"), "plastic")]
|
||||
}
|
||||
(vectorcraft_effects::CLASSIC_EXTRUDE | vectorcraft_effects::CLASSIC_REVOLVE, "shadingColor") => {
|
||||
(vectorcraft_effects::CLASSIC_ROTATE, "surface") => vec![(tl!("No Shading"), "none"), (tl!("Diffuse"), "diffuse")],
|
||||
(vectorcraft_effects::CLASSIC_EXTRUDE | vectorcraft_effects::CLASSIC_REVOLVE | vectorcraft_effects::CLASSIC_ROTATE, "shadingColor") => {
|
||||
vec![(tl!("Black"), "black"), (tl!("Custom"), "custom"), (tl!("None"), "none")]
|
||||
}
|
||||
(vectorcraft_effects::EXTRUDE | vectorcraft_effects::CLASSIC_EXTRUDE, "bevel") => vec![
|
||||
@@ -970,6 +971,8 @@ mod tests {
|
||||
(vectorcraft_effects::CLASSIC_REVOLVE, "edge"),
|
||||
(vectorcraft_effects::CLASSIC_REVOLVE, "surface"),
|
||||
(vectorcraft_effects::CLASSIC_REVOLVE, "shadingColor"),
|
||||
(vectorcraft_effects::CLASSIC_ROTATE, "surface"),
|
||||
(vectorcraft_effects::CLASSIC_ROTATE, "shadingColor"),
|
||||
("warp.arc", "orientation"),
|
||||
("warp.twist", "orientation"),
|
||||
(vectorcraft_effects::RASTERIZE, "background"),
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Compact controls for the 3D and Materials effects: Extrude & Bevel, Revolve, Inflate and Rotate,
|
||||
//! and Extrude & Bevel (Classic) and Revolve (Classic), whose own controls are in
|
||||
//! and Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic), whose own controls are in
|
||||
//! `three_d_classic`. The object and the rotation and light gizmos live on the artboard. All document changes run through the effect
|
||||
//! dialog's existing preview/commit commands.
|
||||
|
||||
@@ -146,11 +146,11 @@ pub(super) fn c3(s: &'static str) -> &'static str {
|
||||
}
|
||||
|
||||
/// Extrude & Bevel, Inflate, Extrude & Bevel (Classic) and Revolve (Classic): Object, Rotation and
|
||||
/// Lighting tabs (Rotate has no Object tab). The rotation gizmo shows on the first two, the light
|
||||
/// gizmo on Lighting.
|
||||
/// Lighting tabs (Rotate and Rotate (Classic) have no Object tab). The rotation gizmo shows on the
|
||||
/// first two, the light gizmo on Lighting.
|
||||
fn solid_controls(app: &VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog, id: &str) {
|
||||
let classic = id == CLASSIC_EXTRUDE || id == CLASSIC_REVOLVE;
|
||||
let object = id == EXTRUDE || id == INFLATE || classic;
|
||||
let classic = vectorcraft_effects::is_classic_three_d(id);
|
||||
let object = id == EXTRUDE || id == INFLATE || id == CLASSIC_EXTRUDE || id == CLASSIC_REVOLVE;
|
||||
let mut tabs = vec![];
|
||||
if object {
|
||||
tabs.push(tl!("Object"));
|
||||
|
||||
@@ -422,6 +422,44 @@ fn classic_revolve_shows_its_options_and_applies_as_one_step() {
|
||||
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
||||
}
|
||||
|
||||
/// Rotate (Classic): Position on the Rotation tab, with no Object tab, and No Shading or Diffuse
|
||||
/// on the Lighting tab, without the highlight or Draw Hidden Faces; OK keeps the edit as one undo
|
||||
/// step.
|
||||
#[test]
|
||||
fn classic_rotate_shows_its_options_and_applies_as_one_step() {
|
||||
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));
|
||||
let before = h.app.session.doc().unwrap().doc.clone();
|
||||
h.settle();
|
||||
for text in ["Rotation", "Presets", "Rotation X", "Perspective", "Off-Axis Front"] {
|
||||
h.rect(text);
|
||||
}
|
||||
assert!(!h.texts.iter().any(|(t, _)| t == "Object" || t == "Depth" || t == "Angle"), "no Object tab");
|
||||
h.click(h.rect("Lighting").center());
|
||||
h.settle();
|
||||
for text in ["Surface", "Diffuse", "Light Intensity", "Ambient Light", "Blend Steps", "Shading Color"] {
|
||||
h.rect(text);
|
||||
}
|
||||
assert!(
|
||||
!h.texts.iter().any(|(t, _)| t == "Highlight Intensity" || t == "Highlight Size" || t == "Draw Hidden Faces"),
|
||||
"no highlight and no hidden faces"
|
||||
);
|
||||
assert_eq!(dialogs::revolve_gizmo::hint(h.app.ui.dialog.as_ref().unwrap()), "Drag the light, or its rings. Shift snaps to 15°.");
|
||||
let d = h.app.ui.dialog.as_mut().unwrap();
|
||||
d.fields.insert("surface".into(), json!("none"));
|
||||
d.fields.insert("rotationY".into(), json!(40.0));
|
||||
h.settle();
|
||||
assert_eq!(dialogs::revolve_gizmo::hint(h.app.ui.dialog.as_ref().unwrap()), "Drag values or labels. Double-click a value to type.");
|
||||
dialogs::confirm(&mut h.app).unwrap();
|
||||
let state = h.app.session.doc().unwrap();
|
||||
let fx = &state.doc.node(state.selection.objects[0]).unwrap().appearance.effects[0];
|
||||
assert_eq!(fx.id, CLASSIC_ROTATE);
|
||||
assert_eq!((fx.params["surface"].clone(), fx.params["rotationY"].as_f64()), (json!("none"), Some(40.0)));
|
||||
assert!(fx.params.get("highlightSize").is_none() && fx.params.get("drawHiddenFaces").is_none());
|
||||
h.app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(h.app.session.doc().unwrap().doc, before);
|
||||
}
|
||||
|
||||
#[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));
|
||||
|
||||
@@ -24,11 +24,7 @@ pub(crate) fn lighting(d: &Dialog) -> bool {
|
||||
/// surface?
|
||||
fn lit(d: &Dialog) -> bool {
|
||||
let id = d.str("__effect");
|
||||
if id == vectorcraft_effects::CLASSIC_EXTRUDE || id == CLASSIC_REVOLVE {
|
||||
matches!(d.str("surface").as_str(), "diffuse" | "plastic")
|
||||
} else {
|
||||
d.bool("shade")
|
||||
}
|
||||
if vectorcraft_effects::is_classic_three_d(&id) { matches!(d.str("surface").as_str(), "diffuse" | "plastic") } else { d.bool("shade") }
|
||||
}
|
||||
|
||||
pub(crate) fn hint(d: &Dialog) -> &'static str {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
//! The own controls of Extrude & Bevel (Classic) and Revolve (Classic) in the compact 3D dialog: the
|
||||
//! extrude and bevel options, or the revolve options, on the Object tab, 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.
|
||||
//! The own controls of Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic) in the
|
||||
//! 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.
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::Unit;
|
||||
use vectorcraft_effects::{CLASSIC_EXTRUDE, CLASSIC_REVOLVE};
|
||||
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;
|
||||
@@ -40,10 +41,13 @@ pub(super) fn revolve_controls(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) {
|
||||
}
|
||||
|
||||
/// The surface, then for Diffuse and Plastic the lights, Ambient Light, the highlight (Plastic
|
||||
/// only), Blend Steps and Shading Color; Draw Hidden Faces last, for Extrude & Bevel (Classic)
|
||||
/// (Revolve (Classic) draws every face).
|
||||
/// only, and not for Rotate (Classic), which has no Plastic surface), Blend Steps and Shading Color;
|
||||
/// Draw Hidden Faces last, for Extrude & Bevel (Classic) (Revolve (Classic) draws every face, and
|
||||
/// Rotate (Classic)'s plane is seen from either side).
|
||||
pub(super) fn surface_controls(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
choice(ui, d, "surface", tl!("Surface"), &options("surface"));
|
||||
let id = d.str("__effect");
|
||||
let effect = if id == CLASSIC_ROTATE { CLASSIC_ROTATE } else { CLASSIC_EXTRUDE };
|
||||
choice(ui, d, "surface", tl!("Surface"), &options_of(effect, "surface"));
|
||||
let surface = d.str("surface");
|
||||
ui.add_enabled_ui(surface == "diffuse" || surface == "plastic", |ui| {
|
||||
light_list(ui, d);
|
||||
@@ -57,24 +61,31 @@ pub(super) fn surface_controls(ui: &mut egui::Ui, d: &mut Dialog) {
|
||||
store_selected(d, &selected);
|
||||
}
|
||||
number(ui, d, "ambient", tl!("Ambient Light"), (0.0, 100.0), "%", 1, None);
|
||||
ui.add_enabled_ui(surface == "plastic", |ui| {
|
||||
number(ui, d, "highlightIntensity", tl!("Highlight Intensity"), (0.0, 100.0), "%", 1, None);
|
||||
number(ui, d, "highlightSize", tl!("Highlight Size"), (0.0, 100.0), "%", 1, None);
|
||||
});
|
||||
if id != CLASSIC_ROTATE {
|
||||
ui.add_enabled_ui(surface == "plastic", |ui| {
|
||||
number(ui, d, "highlightIntensity", tl!("Highlight Intensity"), (0.0, 100.0), "%", 1, None);
|
||||
number(ui, d, "highlightSize", tl!("Highlight Size"), (0.0, 100.0), "%", 1, None);
|
||||
});
|
||||
}
|
||||
number(ui, d, "blendSteps", tl!("Blend Steps"), (1.0, 256.0), "", 0, None);
|
||||
choice(ui, d, "shadingColor", tl!("Shading Color"), &options("shadingColor"));
|
||||
choice(ui, d, "shadingColor", tl!("Shading Color"), &options_of(effect, "shadingColor"));
|
||||
if d.str("shadingColor") == "custom" {
|
||||
shading_custom(ui, d);
|
||||
}
|
||||
});
|
||||
if d.str("__effect") == CLASSIC_EXTRUDE {
|
||||
if id == CLASSIC_EXTRUDE {
|
||||
toggle(ui, d, "drawHiddenFaces", tl!("Draw Hidden Faces"), false);
|
||||
}
|
||||
}
|
||||
|
||||
/// The values of Extrude & Bevel (Classic)'s `key` as the dropdowns list them, (value, label).
|
||||
fn options(key: &str) -> Vec<(Value, &'static str)> {
|
||||
super::effect::own_choices(CLASSIC_EXTRUDE, key, false).unwrap_or_default().into_iter().map(|(label, value)| (json!(value), label)).collect()
|
||||
options_of(CLASSIC_EXTRUDE, key)
|
||||
}
|
||||
|
||||
/// The values of classic 3D effect `effect`'s `key` as the dropdowns list them, (value, label).
|
||||
fn options_of(effect: &str, key: &str) -> Vec<(Value, &'static str)> {
|
||||
super::effect::own_choices(effect, key, false).unwrap_or_default().into_iter().map(|(label, value)| (json!(value), label)).collect()
|
||||
}
|
||||
|
||||
/// The custom shading color: a color button, its hex value and its reset dot.
|
||||
|
||||
@@ -1120,6 +1120,7 @@ font-technology Variable Променливи
|
||||
3D (Classic) 3D (класически)
|
||||
Extrude & Bevel (Classic)… Избутване и фасетка (класически)…
|
||||
Revolve (Classic)… Завъртане (класическо)…
|
||||
Rotate (Classic)… Ротация (класическа)…
|
||||
Surface Повърхност
|
||||
Wireframe Телена рамка
|
||||
No Shading Без засенчване
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -484,6 +484,7 @@
|
||||
3D (Classic) 3D (klasično)
|
||||
Extrude & Bevel (Classic)… Istiskivanje i zakošenje (klasično)…
|
||||
Revolve (Classic)… Rotaciono tijelo (klasično)…
|
||||
Rotate (Classic)… Rotiraj (klasično)…
|
||||
Surface Površina
|
||||
Wireframe Žičani model
|
||||
No Shading Bez sjenčenja
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1574,6 +1574,7 @@ antiAlias Sharp Nítid
|
||||
3D (Classic) 3D (clàssic)
|
||||
Extrude & Bevel (Classic)… Extrudeix i bisella (clàssic)…
|
||||
Revolve (Classic)… Revoluciona (clàssic)…
|
||||
Rotate (Classic)… Gira (clàssic)…
|
||||
Surface Superfície
|
||||
Wireframe Filferro
|
||||
No Shading Sense ombra
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -493,6 +493,7 @@
|
||||
3D (Classic) 3D (klasické)
|
||||
Extrude & Bevel (Classic)… Vysunutí a zkosení (klasické)…
|
||||
Revolve (Classic)… Rotační těleso (klasické)…
|
||||
Rotate (Classic)… Otočit (klasické)…
|
||||
Surface Povrch
|
||||
Wireframe Drátový model
|
||||
No Shading Bez stínování
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -451,6 +451,7 @@
|
||||
3D (Classic) 3D (klassisk)
|
||||
Extrude & Bevel (Classic)… Ekstrudér og facet (klassisk)…
|
||||
Revolve (Classic)… Omdrej (klassisk)…
|
||||
Rotate (Classic)… Rotér (klassisk)…
|
||||
Surface Overflade
|
||||
Wireframe Trådmodel
|
||||
No Shading Ingen skyggelægning
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1125,6 +1125,7 @@ font-technology Variable Variabel
|
||||
3D (Classic) 3D (klassisch)
|
||||
Extrude & Bevel (Classic)… Extrudieren und abgeflachte Kante (klassisch)…
|
||||
Revolve (Classic)… Kreiseln (klassisch)…
|
||||
Rotate (Classic)… Drehen (klassisch)…
|
||||
Surface Oberfläche
|
||||
Wireframe Drahtmodell
|
||||
No Shading Keine Schattierung
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1098,6 +1098,7 @@ font-technology Variable Μεταβλητές
|
||||
3D (Classic) 3D (κλασικό)
|
||||
Extrude & Bevel (Classic)… Εξώθηση και λοξότμηση (κλασικό)…
|
||||
Revolve (Classic)… Περιστροφή γύρω από άξονα (κλασικό)…
|
||||
Rotate (Classic)… Περιστροφή (κλασικό)…
|
||||
Surface Επιφάνεια
|
||||
Wireframe Συρματόπλεγμα
|
||||
No Shading Χωρίς σκίαση
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1154,6 +1154,7 @@ font-technology Variable Variables
|
||||
3D (Classic) 3D (clásico)
|
||||
Extrude & Bevel (Classic)… Extrusión y biselado (clásico)…
|
||||
Revolve (Classic)… Revolución (clásico)…
|
||||
Rotate (Classic)… Rotar (clásico)…
|
||||
Surface Superficie
|
||||
Wireframe Malla
|
||||
No Shading Sin sombreado
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -490,6 +490,7 @@
|
||||
3D (Classic) 3D (perinteinen)
|
||||
Extrude & Bevel (Classic)… Pursota ja viistä (perinteinen)…
|
||||
Revolve (Classic)… Pyöräytä (perinteinen)…
|
||||
Rotate (Classic)… Kierrä (perinteinen)…
|
||||
Surface Pinta
|
||||
Wireframe Rautalanka
|
||||
No Shading Ei varjostusta
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1149,6 +1149,7 @@ font-technology Variable Variables
|
||||
3D (Classic) 3D (classique)
|
||||
Extrude & Bevel (Classic)… Extrusion et biseautage (classique)…
|
||||
Revolve (Classic)… Révolution (classique)…
|
||||
Rotate (Classic)… Rotation (classique)…
|
||||
Surface Surface
|
||||
Wireframe Fil de fer
|
||||
No Shading Sans ombrage
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1746,6 +1746,7 @@ antiAlias Sharp शार्प
|
||||
3D (Classic) 3D (क्लासिक)
|
||||
Extrude & Bevel (Classic)… एक्सट्रूडेड और बेवेल (क्लासिक)…
|
||||
Revolve (Classic)… रिवॉल्व (क्लासिक)…
|
||||
Rotate (Classic)… घुमाव (क्लासिक)…
|
||||
Surface सतह
|
||||
Wireframe वायरफ़्रेम
|
||||
No Shading कोई शेडिंग नहीं
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1445,6 +1445,7 @@ font-technology Variable Változó
|
||||
3D (Classic) 3D (klasszikus)
|
||||
Extrude & Bevel (Classic)… Kihúzás és élletörés (klasszikus)…
|
||||
Revolve (Classic)… Forgatás tengely körül (klasszikus)…
|
||||
Rotate (Classic)… Elforgatás (klasszikus)…
|
||||
Surface Felület
|
||||
Wireframe Drótváz
|
||||
No Shading Nincs árnyékolás
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -451,6 +451,7 @@
|
||||
3D (Classic) 3D (klasik)
|
||||
Extrude & Bevel (Classic)… Ekstrusi & bevel (klasik)…
|
||||
Revolve (Classic)… Revolusi (klasik)…
|
||||
Rotate (Classic)… Putar (klasik)…
|
||||
Surface Permukaan
|
||||
Wireframe Kerangka kawat
|
||||
No Shading Tanpa pengarsiran
|
||||
|
||||
|
@@ -1155,6 +1155,7 @@ font-technology Variable Variabili
|
||||
3D (Classic) 3D (classico)
|
||||
Extrude & Bevel (Classic)… Estrusione e smusso (classico)…
|
||||
Revolve (Classic)… Rivoluzione (classico)…
|
||||
Rotate (Classic)… Ruota (classico)…
|
||||
Surface Superficie
|
||||
Wireframe Reticolo
|
||||
No Shading Nessuna ombreggiatura
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -528,6 +528,7 @@ digits Malayalam マラヤーラム数字
|
||||
3D (Classic) 3D(クラシック)
|
||||
Extrude & Bevel (Classic)… 押し出しとベベル(クラシック)…
|
||||
Revolve (Classic)… 回転体(クラシック)…
|
||||
Rotate (Classic)… 回転(クラシック)…
|
||||
Surface 表面
|
||||
Wireframe ワイヤーフレーム
|
||||
No Shading 陰影なし
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -419,6 +419,7 @@
|
||||
3D (Classic) 3D (კლასიკური)
|
||||
Extrude & Bevel (Classic)… ამოწევა და წაკვეთა (კლასიკური)…
|
||||
Revolve (Classic)… ბრუნვა (კლასიკური)…
|
||||
Rotate (Classic)… შემობრუნება (კლასიკური)…
|
||||
Surface ზედაპირი
|
||||
Wireframe მავთულის ჩარჩო
|
||||
No Shading დაჩრდილვის გარეშე
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -521,6 +521,7 @@ digits Malayalam 말라얄람어
|
||||
3D (Classic) 3D(클래식)
|
||||
Extrude & Bevel (Classic)… 돌출 및 경사(클래식)…
|
||||
Revolve (Classic)… 회전체(클래식)…
|
||||
Rotate (Classic)… 회전(클래식)…
|
||||
Surface 표면
|
||||
Wireframe 와이어프레임
|
||||
No Shading 셰이딩 없음
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -465,6 +465,7 @@
|
||||
3D (Classic) 3D (klassiek)
|
||||
Extrude & Bevel (Classic)… Extruderen en afschuinen (klassiek)…
|
||||
Revolve (Classic)… Draaien (klassiek)…
|
||||
Rotate (Classic)… Roteren (klassiek)…
|
||||
Surface Oppervlak
|
||||
Wireframe Draadmodel
|
||||
No Shading Geen arcering
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -452,6 +452,7 @@
|
||||
3D (Classic) 3D (klasyczne)
|
||||
Extrude & Bevel (Classic)… Wyciągnięcie i fazowanie (klasyczne)…
|
||||
Revolve (Classic)… Obrót (klasyczny)…
|
||||
Rotate (Classic)… Obróć (klasyczny)…
|
||||
Surface Powierzchnia
|
||||
Wireframe Szkielet
|
||||
No Shading Bez cieniowania
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1047,6 +1047,7 @@ antiAlias Sharp Nítido
|
||||
3D (Classic) 3D (Clássico)
|
||||
Extrude & Bevel (Classic)… Extrusão e Bisel (Clássico)…
|
||||
Revolve (Classic)… Revolver (Clássico)…
|
||||
Rotate (Classic)… Girar (Clássico)…
|
||||
Surface Superfície
|
||||
Wireframe Aramado
|
||||
No Shading Sem Sombreamento
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -1152,6 +1152,7 @@ font-technology Variable Вариативные
|
||||
3D (Classic) 3D (классические)
|
||||
Extrude & Bevel (Classic)… Выдавливание и фаска (классическое)…
|
||||
Revolve (Classic)… Вращение (классическое)…
|
||||
Rotate (Classic)… Поворот (классический)…
|
||||
Surface Поверхность
|
||||
Wireframe Каркас
|
||||
No Shading Без затенения
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -1133,6 +1133,7 @@ font-technology Variable Variabla
|
||||
3D (Classic) 3D (klassisk)
|
||||
Extrude & Bevel (Classic)… Extrudera och fasa (klassisk)…
|
||||
Revolve (Classic)… Rotera runt axel (klassisk)…
|
||||
Rotate (Classic)… Rotera (klassisk)…
|
||||
Surface Yta
|
||||
Wireframe Trådram
|
||||
No Shading Ingen skuggning
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1125,6 +1125,7 @@ font-technology Variable Değişken
|
||||
3D (Classic) 3D (Klasik)
|
||||
Extrude & Bevel (Classic)… Ekstrüzyon ve Eğim (Klasik)…
|
||||
Revolve (Classic)… Döndür (Klasik)…
|
||||
Rotate (Classic)… Çevir (Klasik)…
|
||||
Surface Yüzey
|
||||
Wireframe Tel Kafes
|
||||
No Shading Gölgeleme Yok
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -1149,6 +1149,7 @@ font-technology Variable Варіативні
|
||||
3D (Classic) 3D (класичні)
|
||||
Extrude & Bevel (Classic)… Видавлювання та фаска (класичне)…
|
||||
Revolve (Classic)… Обертання (класичне)…
|
||||
Rotate (Classic)… Поворот (класичний)…
|
||||
Surface Поверхня
|
||||
Wireframe Каркас
|
||||
No Shading Без затінення
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 182 and column 5.
|
@@ -1322,6 +1322,7 @@ font-technology Variable Biến thiên
|
||||
3D (Classic) 3D (cổ điển)
|
||||
Extrude & Bevel (Classic)… Kéo dài & vát (cổ điển)…
|
||||
Revolve (Classic)… Xoay tròn (cổ điển)…
|
||||
Rotate (Classic)… Xoay (cổ điển)…
|
||||
Surface Bề mặt
|
||||
Wireframe Khung dây
|
||||
No Shading Không tô bóng
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -1095,6 +1095,7 @@ antiAlias Sharp 锐利
|
||||
3D (Classic) 3D (经典)
|
||||
Extrude & Bevel (Classic)… 突出与斜角 (经典)…
|
||||
Revolve (Classic)… 回转 (经典)…
|
||||
Rotate (Classic)… 旋转 (经典)…
|
||||
Surface 表面
|
||||
Wireframe 线框
|
||||
No Shading 无底纹
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1119,6 +1119,7 @@ font-technology Variable 可變
|
||||
3D (Classic) 3D (經典)
|
||||
Extrude & Bevel (Classic)… 突出與斜角 (經典)…
|
||||
Revolve (Classic)… 迴轉 (經典)…
|
||||
Rotate (Classic)… 旋轉 (經典)…
|
||||
Surface 表面
|
||||
Wireframe 線框
|
||||
No Shading 無網底
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -540,6 +540,22 @@ fn classic_revolve_is_in_the_3d_classic_submenu() {
|
||||
assert!(at("Extrude & Bevel (Classic)…").unwrap() < at("Revolve (Classic)…").unwrap());
|
||||
}
|
||||
|
||||
/// Effect › 3D and Materials › 3D (Classic) › Rotate (Classic)… opens its effect dialog, after
|
||||
/// Revolve (Classic)…, and nowhere else.
|
||||
#[test]
|
||||
fn classic_rotate_is_in_the_3d_classic_submenu() {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), crate::Services::default());
|
||||
app.run("file.new", json!({})).unwrap();
|
||||
let entries = menu_entries(&app);
|
||||
let classic: Vec<_> = entries.iter().filter(|e| e.params.get("effect").and_then(Value::as_str) == Some("threeD.classicRotate")).collect();
|
||||
assert_eq!(classic.len(), 1, "listed once");
|
||||
let e = classic[0];
|
||||
assert_eq!(e.path, ["Effect", "3D and Materials", "3D (Classic)"]);
|
||||
assert_eq!((e.label.as_str(), e.command.as_deref()), ("Rotate (Classic)…", Some("effect.dialog")));
|
||||
let at = |label: &str| entries.iter().position(|x| x.label == label && x.path.get(1).is_some_and(|p| p == "3D and Materials"));
|
||||
assert!(at("Revolve (Classic)…").unwrap() < at("Rotate (Classic)…").unwrap());
|
||||
}
|
||||
|
||||
/// A document's 25 views are all listed at the bottom of the View menu, by name, and the last
|
||||
/// one brings back its zoom, centre and rotation after the view changed.
|
||||
#[test]
|
||||
|
||||
@@ -181,13 +181,13 @@ 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, SVG/PDF output, Expand Appearance ([revolve.md](revolve.md), #846) |
|
||||
| Extrude & Bevel, Inflate, Rotate | initial: live, 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 ([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)); no materials, soft shadows or ray tracing |
|
||||
| 3D (Classic): Extrude & Bevel, Revolve, Rotate | Extrude & Bevel (Classic) initial: live, 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, 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) and Map Art: none |
|
||||
| 3D (Classic): Extrude & Bevel, Revolve, Rotate | Extrude & Bevel (Classic) initial: live, 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, 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, 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 |
|
||||
| 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)) |
|
||||
|
||||
The `three-d` crate (depends on `geom` and `pathops`, with no UI or GPU dependencies) builds, rotates, projects, shades and depth-sorts the surfaces;
|
||||
the effects crate turns them into art. What's left (materials and shadows, ray-traced rendering,
|
||||
angular cut caps, exact visibility, the rest of 3D Classic (Rotate, Map Art), Turntable) is 25–45 h.
|
||||
angular cut caps, exact visibility, the rest of 3D Classic (Map Art), Turntable) is 25–45 h.
|
||||
|
||||
## Revision history
|
||||
|
||||
|
||||
+1
-1
@@ -663,7 +663,7 @@ macOS cursor delivery (#2292) is narrowed to a native host adapter using the exi
|
||||
Extrude & Bevel, Inflate and Rotate with Position presets ([three-d.md](three-d.md), #1407). Colored Inflate has automatic smooth vector lighting from shared membrane normals. White-light Inflate keeps its existing appearance. All four take one to ten colored directional lights, with neutral ambient applied once, a light list and independent previewing controls. Standard, Diffuse, Top Left and Right select original rigs, shared with commands; manual settings show Custom.
|
||||
- **Missing:** materials (base, Substance, graphics), shadows,
|
||||
ray-traced rendering, angular cut caps, exact visibility for intersecting and concave surfaces, the rest of 3D
|
||||
Classic (Rotate, Map Art), Turntable, textures, map art and shading in 3D export.
|
||||
Classic (Map Art), Turntable, textures, map art and shading in 3D export.
|
||||
- **Impact:** specialist; Materials is the one menu stub left.
|
||||
- **Estimate:** 25–45 h; plan in `plan/` before coding.
|
||||
|
||||
|
||||
+26
-3
@@ -3078,8 +3078,8 @@ See [Revolve](revolve.md) for the scope and limitations.
|
||||
replaces the lighting rig and ambient of an existing Revolve, Extrude & Bevel, Inflate or Rotate
|
||||
effect. `item: null` addresses object-level effects; an item index addresses a fill/stroke's
|
||||
effects. All targets must have a 3D effect at `index`, otherwise nothing changes. Extrude & Bevel
|
||||
(Classic) and Revolve (Classic) have no lighting presets: a target with either at `index` is an error, nothing
|
||||
changes, and `effect.setParams` sets its lights. The operation is one undo step and preserves Shading, geometry, source paint and the view.
|
||||
(Classic), Revolve (Classic) and Rotate (Classic) have no lighting presets: a target with any of them at `index` is an
|
||||
error, nothing changes, and `effect.setParams` sets its lights. The operation is one undo step and preserves Shading, geometry, source paint and the view.
|
||||
|
||||
The four original rigs use one white front light at 80% intensity. Standard keeps the existing
|
||||
default; Diffuse uses 100% Softness and 35% Ambient Light, Top Left uses Rotation 135 and Height
|
||||
@@ -3111,7 +3111,7 @@ art", and a loaded file draws the source path, as it does for a solid past its l
|
||||
documentation names the controls and their ranges
|
||||
([3D and Materials panel options](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/3d-materials-panel-options.html)).
|
||||
The settings are saved in the native file; SVG and PDF exports draw the shadow, and `effect.expandAppearance` keeps
|
||||
it as a path. Extrude & Bevel (Classic) and Revolve (Classic) have no shadow. See [three-d.md](three-d.md#shadows).
|
||||
it as a path. Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic) have no shadow. See [three-d.md](three-d.md#shadows).
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"effect.setParams","params":{"index":0,"item":null,"params":{"shadow":true,"shadowPosition":"below","shadowDistance":10,"shadowBounds":150}}}}
|
||||
@@ -3209,6 +3209,29 @@ changed.
|
||||
{"name":"run_command","arguments":{"command":"effect.apply","params":{"effect":"threeD.classicRevolve","item":null,"params":{"angle":270,"offset":10,"surface":"diffuse","blendSteps":64}}}}
|
||||
```
|
||||
|
||||
## Rotate (Classic)
|
||||
|
||||
`effect.apply {effect: "threeD.classicRotate", params: {...}, ids?, item?}` is Effect › 3D and Materials ›
|
||||
3D (Classic) › Rotate (Classic)…. It turns every fill and stroke as one flat plane about the center of the object's
|
||||
bounds, as `threeD.rotate` does, each in its own paint, and draws the plane from either side. Parameters:
|
||||
`rotationX/Y/Z` and `perspectiveAngle` as above; `surface` (`diffuse`, the default, lights the art softly, and
|
||||
`none` keeps the paint unlit); and the lights, `ambient`, `blendSteps`, `shadingColor` and `shadingCustom` as Extrude &
|
||||
Bevel (Classic) takes them. It has no highlight, no `drawHiddenFaces` and no shadow. The effect also goes on a group
|
||||
or layer, whose members turn about one shared center, or on a fill or stroke. The public documentation describes the
|
||||
dialog's Position and Surface settings
|
||||
([Rotate objects in 3D](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/create-3d-graphics/rotate-objects-in-3d.html)),
|
||||
the Position presets, rotations from -180 to 180 and Perspective from 0 to 160
|
||||
([Set 3D rotation position options](https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=270)),
|
||||
and No Shading and Diffuse Shading as the only surfaces for an object that uses only this effect
|
||||
([Surface shading options](https://help.adobe.com/archive/en_US/illustrator/cs5/illustrator_cs5_help.pdf#page=271)).
|
||||
The defaults and the shading are VectorCraft's own. An unknown parameter (`highlightSize` and `drawHiddenFaces`
|
||||
included), a number out of range, a flag that is not true or false, a surface other than `none` or `diffuse`, or
|
||||
another shading color is refused with an error and the document is not changed.
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"effect.apply","params":{"effect":"threeD.classicRotate","item":null,"params":{"rotationX":0,"rotationY":40,"rotationZ":0,"surface":"diffuse","blendSteps":64}}}}
|
||||
```
|
||||
|
||||
|
||||
## Edit Colors and Recolor Artwork
|
||||
|
||||
|
||||
+6
-5
@@ -141,7 +141,7 @@ them all. The members stay editable: moving or editing one reshapes the solids.
|
||||
|
||||
## Clipping groups, blends and symbol instances
|
||||
|
||||
Every 3D effect (Extrude & Bevel, Extrude & Bevel (Classic), Inflate, Rotate, Revolve and Revolve (Classic)) also applies to a clipping
|
||||
Every 3D effect (Extrude & Bevel, Extrude & Bevel (Classic), Inflate, Rotate, Rotate (Classic), Revolve and Revolve (Classic)) also applies to a clipping
|
||||
group, a blend or a symbol instance, alone or inside a group or layer. The help page says that effects apply to
|
||||
objects, groups or layers ([Apply and modify effects](https://helpx.adobe.com/illustrator/desktop/special-effects-styles/apply-filter-effects/apply-and-modify-effects.html)),
|
||||
that a clipping mask hides everything outside its shape ([Clipping masks](https://helpx.adobe.com/illustrator/using/clipping-masks.html)),
|
||||
@@ -279,7 +279,7 @@ Our reading:
|
||||
command is refused with "The 3D shadow is too complex; turn Shadows off or simplify the art", and a loaded
|
||||
file draws the source path.
|
||||
- Canvas, SVG and PDF exports draw the shadow, the art's bounds take it in, and Expand Appearance keeps it as a
|
||||
path below the faces. Extrude & Bevel (Classic) and Revolve (Classic) have no shadow.
|
||||
path below the faces. Extrude & Bevel (Classic), Revolve (Classic) and Rotate (Classic) have no shadow.
|
||||
|
||||
## Parameters (`effect.apply`, `effect.setParams`)
|
||||
|
||||
@@ -336,9 +336,10 @@ out, perspective). No Adobe software, assets, presets or bevel shapes were used.
|
||||
outline crosses. An outline of tens of thousands of points that crosses nearly every cell (a comb of 4,000 teeth
|
||||
across the shape) is refused because the 3D surface is too complex; a closed path of 65,536 points round a
|
||||
circle builds.
|
||||
- **3D (Classic)**: Extrude & Bevel (Classic) and Revolve (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), the track cube, map art, Turntable and 3D export
|
||||
- **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.
|
||||
|
||||
Release measurements and a repeatable CLI scene are in [3D lighting performance](3d-lighting-performance.md).
|
||||
|
||||
Reference in New Issue
Block a user