mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 15:57:49 +00:00
Shapes: addon.shapes commands refuse values outside their ranges
addon.shapes.polygon and addon.shapes.star rounded a fractional side or point count and clamped it to 3..1000, so 2.5, 2 or 5000 drew a shape with a different count than asked for. addon.shapes.configure read sides only as an unsigned integer, ignored a fraction and cut a large value to 32 bits before clamping it, so 4294967299 gave a triangle. The commands now refuse a supplied value outside its range with an error that names the parameter and the range, and draw or change nothing; an omitted or null value still takes its default: - sides and points: a whole number from 3 to 1000 - rounded and randomized: -10 to 10; spokeRatio: 0.01 to 1 - turns: 0.01 to 1024; divergence: 0 to 1000; innerRadius: 0 to 0.999 - width, height, radius and radius1: 0 or more (a negative size was made positive before, and a negative corner radius became 0) - radius2: 0.01 to 1 times radius1 (the ratio was clamped before) - radii: four numbers, each 0 or more (a malformed array was ignored) - arcMode: whole, pie (slice), chord or arc (open); any other value drew a whole shape before addon.shapes.configure checks every value before it changes any of the selected shapes, keeps fitting a corner radius to half each rectangle's shorter side, and records no undo step when no shape changes. Docs: docs/mcp.md (3D Box and shapes add-on), docs/target-app-parity.md.
This commit is contained in:
@@ -9,6 +9,8 @@
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use std::ops::RangeInclusive;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_doc::{LiveShape, NodeId, NodeKind};
|
||||
use vectorcraft_geom::{Affine, FillRule, Point, Rect, shapes};
|
||||
@@ -16,9 +18,25 @@ use vectorcraft_tools::addons::box3d::CustomShape;
|
||||
|
||||
use super::super::create::insert_look;
|
||||
use super::super::{
|
||||
CommandSpec, Result, Session, bool_or, check_numbers, check_switches, cmd, f64_or, f64_req, has_doc, has_selection, str_param, targets,
|
||||
CommandSpec, Result, Session, bad, bool_or, check_numbers, check_switches, cmd, f64_or, f64_req, has_doc, has_selection, number_or, str_param,
|
||||
targets,
|
||||
};
|
||||
|
||||
/// The sides of a polygon and the points of a star: whole numbers in this range.
|
||||
const COUNTS: RangeInclusive<f64> = 3.0..=1000.0;
|
||||
/// `rounded` and `randomized` of a polygon or star.
|
||||
const ROUNDING: RangeInclusive<f64> = -10.0..=10.0;
|
||||
/// A star's inner radius as a fraction of its outer radius.
|
||||
const SPOKE_RATIO: RangeInclusive<f64> = 0.01..=1.0;
|
||||
/// A spiral's `turns`.
|
||||
const TURNS: RangeInclusive<f64> = 0.01..=1024.0;
|
||||
/// A spiral's `divergence`.
|
||||
const DIVERGENCE: RangeInclusive<f64> = 0.0..=1000.0;
|
||||
/// A spiral's `innerRadius`, as a fraction of its radius.
|
||||
const INNER_RADIUS: RangeInclusive<f64> = 0.0..=0.999;
|
||||
/// The `arcMode` names; `slice` and `open` are also read as `pie` and `arc`.
|
||||
const ARC_MODES: [&str; 4] = ["whole", "pie", "chord", "arc"];
|
||||
|
||||
pub fn specs() -> Vec<CommandSpec> {
|
||||
vec![
|
||||
cmd!(
|
||||
@@ -26,7 +44,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Rectangle",
|
||||
[],
|
||||
None,
|
||||
"{x, y, width, height, radius?: pt, radii?: [tl,tr,br,bl], startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top-left corner; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise about the centre (0: upright)} → {id}",
|
||||
"{x, y, width, height: pt, 0 or more, radius?: pt, 0 or more, radii?: [tl,tr,br,bl], each 0 or more, startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top-left corner; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise about the centre (0: upright)} → {id}",
|
||||
has_doc,
|
||||
rectangle
|
||||
),
|
||||
@@ -35,7 +53,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Ellipse",
|
||||
[],
|
||||
None,
|
||||
"{x, y, width, height, startAngle?, endAngle?: degrees (counter-clockwise from 3 o'clock, as object.setLiveShape's pieStart and pieEnd; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise about the centre (0: upright)} → {id}",
|
||||
"{x, y, width, height: pt, 0 or more, startAngle?, endAngle?: degrees (counter-clockwise from 3 o'clock, as object.setLiveShape's pieStart and pieEnd; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise about the centre (0: upright)} → {id}",
|
||||
has_doc,
|
||||
ellipse
|
||||
),
|
||||
@@ -44,7 +62,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Polygon",
|
||||
[],
|
||||
None,
|
||||
"{cx, cy, radius, sides=6, rounded=0, randomized=0, startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top corner; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise (0: a corner straight up)} → {id}",
|
||||
"{cx, cy, radius: pt, 0 or more, sides=6: a whole number 3 to 1000, rounded=0, randomized=0: -10 to 10, startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top corner; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise (0: a corner straight up)} → {id}",
|
||||
has_doc,
|
||||
polygon
|
||||
),
|
||||
@@ -53,7 +71,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Star",
|
||||
[],
|
||||
None,
|
||||
"{cx, cy, radius1, radius2?, points=5, rounded=0, randomized=0, startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top point; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise (0: a point straight up)} → {id}",
|
||||
"{cx, cy, radius1: pt, 0 or more, radius2?: pt, 0.01 to 1 times radius1 (default half of it), points=5: a whole number 3 to 1000, rounded=0, randomized=0: -10 to 10, startAngle?, endAngle?: degrees (the part of the outline kept, clockwise from its first anchor, the top point; each side an equal share of 360; default 0 and 360), arcMode?: whole|pie|chord|arc, rotation?: deg clockwise (0: a point straight up)} → {id}",
|
||||
has_doc,
|
||||
star
|
||||
),
|
||||
@@ -62,7 +80,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Spiral",
|
||||
[],
|
||||
None,
|
||||
"{cx, cy, radius, turns=3, divergence=1, innerRadius=0, clockwise=true} → {id}",
|
||||
"{cx, cy, radius: pt, 0 or more, turns=3: 0.01 to 1024, divergence=1: 0 to 1000, innerRadius=0: 0 to 0.999 of radius, clockwise=true} → {id}",
|
||||
has_doc,
|
||||
spiral
|
||||
),
|
||||
@@ -71,7 +89,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"3D Box",
|
||||
[],
|
||||
None,
|
||||
"{x, y, width, height, angleX=30 (degrees about the level axis: positive tips the top away from you), angleY=30 (degrees about the upright axis: positive turns the right side away from you), angleZ=0 (degrees clockwise in the page), parallelX=false, parallelY=true, parallelZ=false} → {id}",
|
||||
"{x, y, width, height: pt, 0 or more, angleX=30 (degrees about the level axis: positive tips the top away from you), angleY=30 (degrees about the upright axis: positive turns the right side away from you), angleZ=0 (degrees clockwise in the page), parallelX=false, parallelY=true, parallelZ=false} → {id}",
|
||||
has_doc,
|
||||
box3d
|
||||
),
|
||||
@@ -80,33 +98,96 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Configure Shape",
|
||||
[],
|
||||
None,
|
||||
"{ids?, radius?, radii?: [tl,tr,br,bl], startAngle?, endAngle?: degrees (as addon.shapes.rectangle, ellipse, polygon and star take them: an ellipse's counter-clockwise from 3 o'clock, the others' clockwise along the outline), arcMode?: whole|pie|chord|arc, sides?, isStar?, spokeRatio?, rounded?, randomized?, turns?, divergence?, innerRadius?, angleX?, angleY?: degrees (as addon.shapes.box3d: positive tips the top, or turns the right side, away from you), angleZ?: degrees clockwise in the page, parallelX?, parallelY?, parallelZ?, reset?: bool} → {updated}",
|
||||
"{ids?, radius?: pt, 0 or more (fitted to half each rectangle's shorter side), radii?: [tl,tr,br,bl], each 0 or more, startAngle?, endAngle?: degrees (as addon.shapes.rectangle, ellipse, polygon and star take them: an ellipse's counter-clockwise from 3 o'clock, the others' clockwise along the outline), arcMode?: whole|pie|chord|arc, sides?: a whole number 3 to 1000, isStar?, spokeRatio?: 0.01 to 1, rounded?, randomized?: -10 to 10, turns?: 0.01 to 1024, divergence?: 0 to 1000, innerRadius?: 0 to 0.999, angleX?, angleY?: degrees (as addon.shapes.box3d: positive tips the top, or turns the right side, away from you), angleZ?: degrees clockwise in the page, parallelX?, parallelY?, parallelZ?, reset?: bool} → {updated}",
|
||||
has_selection,
|
||||
configure
|
||||
),
|
||||
]
|
||||
}
|
||||
|
||||
fn parse_arc_params(p: &Value) -> ((f64, f64), shapes::EllipseMode) {
|
||||
/// The number `p` gives for `key`, or `default` when `p` leaves it out or gives null. A number
|
||||
/// outside `range` is an error that names `key` and the range.
|
||||
fn ranged(p: &Value, key: &str, default: f64, range: &RangeInclusive<f64>, cmd: &str) -> Result<f64> {
|
||||
let x = number_or(p, key, default, cmd)?;
|
||||
if !range.contains(&x) {
|
||||
let (min, max) = (range.start(), range.end());
|
||||
return Err(bad(cmd, format!("`{key}` must be between {min} and {max} (got {x})")));
|
||||
}
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
/// The number of sides or points `p` gives for `key`, or `default` when `p` leaves it out or gives
|
||||
/// null. A fraction or a number outside [`COUNTS`] is an error.
|
||||
fn count(p: &Value, key: &str, default: u32, cmd: &str) -> Result<u32> {
|
||||
let x = number_or(p, key, f64::from(default), cmd)?;
|
||||
if x.fract() != 0.0 || !COUNTS.contains(&x) {
|
||||
let (min, max) = (COUNTS.start(), COUNTS.end());
|
||||
return Err(bad(cmd, format!("`{key}` must be a whole number between {min} and {max} (got {x})")));
|
||||
}
|
||||
Ok(x as u32)
|
||||
}
|
||||
|
||||
/// The size `p` gives for `key`: a number that is not negative.
|
||||
fn size(p: &Value, key: &str, cmd: &str) -> Result<f64> {
|
||||
not_negative(f64_req(p, key, cmd)?, key, cmd)
|
||||
}
|
||||
|
||||
/// The size `p` gives for `key`, or `default` when `p` leaves it out or gives null.
|
||||
fn size_or(p: &Value, key: &str, default: f64, cmd: &str) -> Result<f64> {
|
||||
not_negative(number_or(p, key, default, cmd)?, key, cmd)
|
||||
}
|
||||
|
||||
fn not_negative(x: f64, key: &str, cmd: &str) -> Result<f64> {
|
||||
if x < 0.0 {
|
||||
return Err(bad(cmd, format!("`{key}` must be a number not below 0")));
|
||||
}
|
||||
Ok(x)
|
||||
}
|
||||
|
||||
/// The four corner radii `p` gives as `radii` ([tl, tr, br, bl]), or `None` when `p` leaves it out
|
||||
/// or gives null. Anything but four numbers that are not negative is an error.
|
||||
fn radii_param(p: &Value, cmd: &str) -> Result<Option<[f64; 4]>> {
|
||||
let key = "radii";
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => {
|
||||
let four = v.as_array().filter(|a| a.len() == 4).and_then(|a| {
|
||||
let mut out = [0.0; 4];
|
||||
for (slot, item) in out.iter_mut().zip(a) {
|
||||
*slot = item.as_f64().filter(|r| *r >= 0.0)?;
|
||||
}
|
||||
Some(out)
|
||||
});
|
||||
four.map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be four numbers not below 0: [tl, tr, br, bl]")))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The mode `p` gives as `arcMode`, or `None` when `p` leaves it out or gives null. Any other
|
||||
/// value than one of [`ARC_MODES`] (or `slice` or `open`) is an error.
|
||||
fn arc_mode(p: &Value, cmd: &str) -> Result<Option<shapes::EllipseMode>> {
|
||||
let key = "arcMode";
|
||||
match p.get(key) {
|
||||
None | Some(Value::Null) => Ok(None),
|
||||
Some(v) => v.as_str().and_then(parse_mode).map(Some).ok_or_else(|| bad(cmd, format!("`{key}` must be one of {}", ARC_MODES.join(", ")))),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_arc_params(p: &Value, cmd: &str) -> Result<((f64, f64), shapes::EllipseMode)> {
|
||||
let start = f64_or(p, "startAngle", 0.0);
|
||||
let end = f64_or(p, "endAngle", 360.0);
|
||||
let mode = match str_param(p, "arcMode") {
|
||||
Some("pie" | "slice") => shapes::EllipseMode::Slice,
|
||||
Some("chord") => shapes::EllipseMode::Chord,
|
||||
Some("arc" | "open") => shapes::EllipseMode::Arc,
|
||||
_ => shapes::EllipseMode::Whole,
|
||||
};
|
||||
let mode = arc_mode(p, cmd)?.unwrap_or(shapes::EllipseMode::Whole);
|
||||
let span = (end - start).rem_euclid(360.0);
|
||||
let pie = if mode == shapes::EllipseMode::Whole && (span < 1e-6 || (span - 360.0).abs() < 1e-6) { (0.0, 360.0) } else { (start, end) };
|
||||
(pie, mode)
|
||||
Ok((pie, mode))
|
||||
}
|
||||
|
||||
fn rect_of(p: &Value, c: &str) -> Result<Rect> {
|
||||
let x = f64_req(p, "x", c)?;
|
||||
let y = f64_req(p, "y", c)?;
|
||||
let w = f64_req(p, "width", c)?;
|
||||
let h = f64_req(p, "height", c)?;
|
||||
Ok(Rect::new(x, y, x + w.abs(), y + h.abs()).abs())
|
||||
let w = size(p, "width", c)?;
|
||||
let h = size(p, "height", c)?;
|
||||
Ok(Rect::new(x, y, x + w, y + h))
|
||||
}
|
||||
|
||||
fn rect_xf(r: Rect, rot: f64) -> Affine {
|
||||
@@ -141,18 +222,9 @@ fn rectangle(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "addon.shapes.rectangle";
|
||||
check_numbers(p, &["radius", "startAngle", "endAngle", "rotation"], c)?;
|
||||
let r = rect_of(p, c)?;
|
||||
let radius = f64_or(p, "radius", 0.0).max(0.0);
|
||||
let mut radii = [radius; 4];
|
||||
if let Some(arr) = p.get("radii").and_then(Value::as_array)
|
||||
&& arr.len() == 4
|
||||
{
|
||||
for (i, item) in arr.iter().enumerate() {
|
||||
if let Some(val) = item.as_f64().filter(|n| n.is_finite()) {
|
||||
radii[i] = val.max(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
let (pie, arc_mode) = parse_arc_params(p);
|
||||
let radius = size_or(p, "radius", 0.0, c)?;
|
||||
let radii = radii_param(p, c)?.unwrap_or([radius; 4]);
|
||||
let (pie, arc_mode) = parse_arc_params(p, c)?;
|
||||
let xf = rect_xf(r, f64_or(p, "rotation", 0.0));
|
||||
let live = LiveShape::Rectangle { w: r.width(), h: r.height(), radii, kinds: Default::default(), radii_relative: false, xf };
|
||||
let cs = CustomShape::Rectangle { w: r.width(), h: r.height(), radii, kinds: Default::default(), radii_relative: false, xf, pie, arc_mode };
|
||||
@@ -164,7 +236,7 @@ fn ellipse(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "addon.shapes.ellipse";
|
||||
check_numbers(p, &["startAngle", "endAngle", "rotation"], c)?;
|
||||
let r = rect_of(p, c)?;
|
||||
let (pie, arc_mode) = parse_arc_params(p);
|
||||
let (pie, arc_mode) = parse_arc_params(p, c)?;
|
||||
let xf = rect_xf(r, f64_or(p, "rotation", 0.0));
|
||||
let live = LiveShape::Ellipse { w: r.width(), h: r.height(), pie, xf };
|
||||
let cs = CustomShape::Ellipse { w: r.width(), h: r.height(), pie, arc_mode, xf };
|
||||
@@ -174,13 +246,13 @@ fn ellipse(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn polygon(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "addon.shapes.polygon";
|
||||
let center = Point::new(f64_req(p, "cx", c)?, f64_req(p, "cy", c)?);
|
||||
let r = f64_req(p, "radius", c)?.abs();
|
||||
let r = size(p, "radius", c)?;
|
||||
check_numbers(p, &["sides", "rotation", "rounded", "randomized", "startAngle", "endAngle"], c)?;
|
||||
let sides = (f64_or(p, "sides", 6.0).round() as i64).clamp(3, 1000) as u32;
|
||||
let sides = count(p, "sides", 6, c)?;
|
||||
let rot = f64_or(p, "rotation", 0.0);
|
||||
let rounded = f64_or(p, "rounded", 0.0).clamp(-10.0, 10.0);
|
||||
let randomized = f64_or(p, "randomized", 0.0).clamp(-10.0, 10.0);
|
||||
let (pie, arc_mode) = parse_arc_params(p);
|
||||
let rounded = ranged(p, "rounded", 0.0, &ROUNDING, c)?;
|
||||
let randomized = ranged(p, "randomized", 0.0, &ROUNDING, c)?;
|
||||
let (pie, arc_mode) = parse_arc_params(p, c)?;
|
||||
let xf = Affine::translate(center.to_vec2()) * vectorcraft_geom::rotate_deg(rot);
|
||||
let live = LiveShape::Polygon { radius: r, sides, xf, radii: vec![], kinds: vec![], radii_relative: false };
|
||||
let cs = CustomShape::Polygon {
|
||||
@@ -202,16 +274,17 @@ fn polygon(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn star(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "addon.shapes.star";
|
||||
let center = Point::new(f64_req(p, "cx", c)?, f64_req(p, "cy", c)?);
|
||||
let r1 = f64_req(p, "radius1", c)?.abs();
|
||||
let r1 = size(p, "radius1", c)?;
|
||||
check_numbers(p, &["radius2", "points", "rotation", "rounded", "randomized", "startAngle", "endAngle"], c)?;
|
||||
let r2 = f64_or(p, "radius2", r1 / 2.0).abs();
|
||||
let points = (f64_or(p, "points", 5.0).round() as i64).clamp(3, 1000) as u32;
|
||||
// The inner radius is a fraction of the outer one, in `SPOKE_RATIO`.
|
||||
let r2 = ranged(p, "radius2", r1 / 2.0, &(r1 * SPOKE_RATIO.start()..=r1 * SPOKE_RATIO.end()), c)?;
|
||||
let points = count(p, "points", 5, c)?;
|
||||
let rot = f64_or(p, "rotation", 0.0);
|
||||
let rounded = f64_or(p, "rounded", 0.0).clamp(-10.0, 10.0);
|
||||
let randomized = f64_or(p, "randomized", 0.0).clamp(-10.0, 10.0);
|
||||
let (pie, arc_mode) = parse_arc_params(p);
|
||||
let rounded = ranged(p, "rounded", 0.0, &ROUNDING, c)?;
|
||||
let randomized = ranged(p, "randomized", 0.0, &ROUNDING, c)?;
|
||||
let (pie, arc_mode) = parse_arc_params(p, c)?;
|
||||
let xf = Affine::translate(center.to_vec2()) * vectorcraft_geom::rotate_deg(rot);
|
||||
let ratio = if r1 > 1e-9 { (r2 / r1).clamp(0.01, 1.0) } else { 0.5 };
|
||||
let ratio = if r1 > 1e-9 { (r2 / r1).clamp(*SPOKE_RATIO.start(), *SPOKE_RATIO.end()) } else { 0.5 };
|
||||
let cs = CustomShape::Polygon {
|
||||
radius: r1,
|
||||
sides: points,
|
||||
@@ -231,12 +304,12 @@ fn star(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
fn spiral(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let c = "addon.shapes.spiral";
|
||||
let center = Point::new(f64_req(p, "cx", c)?, f64_req(p, "cy", c)?);
|
||||
let r = f64_req(p, "radius", c)?.abs();
|
||||
let r = size(p, "radius", c)?;
|
||||
check_numbers(p, &["turns", "divergence", "innerRadius"], c)?;
|
||||
check_switches(p, &["clockwise"], c)?;
|
||||
let turns = f64_or(p, "turns", 3.0).clamp(0.01, 1024.0);
|
||||
let divergence = f64_or(p, "divergence", 1.0).clamp(0.0, 1000.0);
|
||||
let inner_radius = f64_or(p, "innerRadius", 0.0).clamp(0.0, 0.999);
|
||||
let turns = ranged(p, "turns", 3.0, &TURNS, c)?;
|
||||
let divergence = ranged(p, "divergence", 1.0, &DIVERGENCE, c)?;
|
||||
let inner_radius = ranged(p, "innerRadius", 0.0, &INNER_RADIUS, c)?;
|
||||
let clockwise = bool_or(p, "clockwise", true);
|
||||
let cs = CustomShape::Spiral { center, radius: r, turns, divergence, inner_radius, clockwise };
|
||||
insert_custom(s, "Spiral", None, cs)
|
||||
@@ -299,24 +372,54 @@ fn configure(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
c,
|
||||
)?;
|
||||
check_switches(p, &["isStar", "parallelX", "parallelY", "parallelZ", "reset"], c)?;
|
||||
check_configure(p, c)?;
|
||||
let ids = targets(s, p)?;
|
||||
let reset = bool_or(p, "reset", false);
|
||||
let spiral_hint = p.get("turns").is_some() || p.get("divergence").is_some() || p.get("innerRadius").is_some();
|
||||
// The shapes the params change, worked out first so that a call that changes nothing records
|
||||
// no undo step.
|
||||
let changes: Vec<(NodeId, CustomShape)> = {
|
||||
let doc = &s.doc()?.doc;
|
||||
ids.into_iter()
|
||||
.filter_map(|id| {
|
||||
let n = doc.node(id)?;
|
||||
let before = CustomShape::read_for_update(n, spiral_hint)?;
|
||||
let mut cs = before.clone();
|
||||
let stored = CustomShape::read(n).is_some();
|
||||
(apply_shape_params(&mut cs, p, reset) && (cs != before || !stored)).then_some((id, cs))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
if changes.is_empty() {
|
||||
return Ok(json!({ "updated": 0 }));
|
||||
}
|
||||
let updated = s.edit("Live Shape", |d, _| {
|
||||
let mut count = 0usize;
|
||||
for id in ids {
|
||||
let Some(n) = d.node_mut(id) else { continue };
|
||||
let Some(mut cs) = CustomShape::read_for_update(n, spiral_hint) else { continue };
|
||||
if apply_shape_params(&mut cs, p, reset) {
|
||||
cs.write_to_node(n);
|
||||
count += 1;
|
||||
}
|
||||
for (id, cs) in &changes {
|
||||
let Some(n) = d.node_mut(*id) else { continue };
|
||||
cs.write_to_node(n);
|
||||
count += 1;
|
||||
}
|
||||
Ok(count)
|
||||
})?;
|
||||
Ok(json!({ "updated": updated }))
|
||||
}
|
||||
|
||||
/// Error unless every bounded value `p` gives is in its range, before any shape changes.
|
||||
fn check_configure(p: &Value, c: &str) -> Result<()> {
|
||||
size_or(p, "radius", 0.0, c)?;
|
||||
radii_param(p, c)?;
|
||||
arc_mode(p, c)?;
|
||||
count(p, "sides", 6, c)?;
|
||||
ranged(p, "spokeRatio", 0.5, &SPOKE_RATIO, c)?;
|
||||
ranged(p, "rounded", 0.0, &ROUNDING, c)?;
|
||||
ranged(p, "randomized", 0.0, &ROUNDING, c)?;
|
||||
ranged(p, "turns", 3.0, &TURNS, c)?;
|
||||
ranged(p, "divergence", 1.0, &DIVERGENCE, c)?;
|
||||
ranged(p, "innerRadius", 0.0, &INNER_RADIUS, c)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn num_param(p: &Value, k: &str) -> Option<f64> {
|
||||
p.get(k).and_then(Value::as_f64).filter(|v| v.is_finite())
|
||||
}
|
||||
@@ -331,8 +434,8 @@ fn apply_arc_params(p: &Value, pie: &mut (f64, f64), arc_mode: &mut shapes::Elli
|
||||
pie.1 = a1.rem_euclid(360.0);
|
||||
changed = true;
|
||||
}
|
||||
if let Some(m) = str_param(p, "arcMode") {
|
||||
*arc_mode = parse_mode(m);
|
||||
if let Some(m) = str_param(p, "arcMode").and_then(parse_mode) {
|
||||
*arc_mode = m;
|
||||
if *arc_mode == shapes::EllipseMode::Whole {
|
||||
*pie = (0.0, 360.0);
|
||||
} else if (pie.1 - pie.0).abs() < 1e-6 || ((pie.1 - pie.0).abs() - 360.0).abs() < 1e-6 {
|
||||
@@ -351,19 +454,14 @@ fn apply_rect_params(p: &Value, reset: bool, max_r: f64, radii: &mut [f64; 4], p
|
||||
*arc_mode = shapes::EllipseMode::Whole;
|
||||
changed = true;
|
||||
}
|
||||
// A radius larger than half the rectangle's shorter side is fitted to that size.
|
||||
if let Some(r) = num_param(p, "radius") {
|
||||
*radii = [r.clamp(0.0, max_r); 4];
|
||||
changed = true;
|
||||
}
|
||||
if let Some(arr) = p.get("radii").and_then(Value::as_array)
|
||||
&& arr.len() == 4
|
||||
{
|
||||
for (i, v) in arr.iter().enumerate() {
|
||||
if let Some(r) = v.as_f64().filter(|x| x.is_finite()) {
|
||||
radii[i] = r.clamp(0.0, max_r);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if let Ok(Some(four)) = radii_param(p, "addon.shapes.configure") {
|
||||
*radii = four.map(|r| r.clamp(0.0, max_r));
|
||||
changed = true;
|
||||
}
|
||||
changed | apply_arc_params(p, pie, arc_mode)
|
||||
}
|
||||
@@ -378,8 +476,9 @@ fn apply_poly_params(p: &Value, reset: bool, sides: &mut u32, spoke_ratio: &mut
|
||||
}
|
||||
changed = true;
|
||||
}
|
||||
if let Some(n_sides) = p.get("sides").and_then(Value::as_u64) {
|
||||
*sides = (n_sides as u32).clamp(3, 1000);
|
||||
// `check_configure` has refused values outside their ranges.
|
||||
if let Some(n_sides) = num_param(p, "sides") {
|
||||
*sides = n_sides as u32;
|
||||
changed = true;
|
||||
}
|
||||
if let Some(star) = p.get("isStar").and_then(Value::as_bool) {
|
||||
@@ -387,15 +486,15 @@ fn apply_poly_params(p: &Value, reset: bool, sides: &mut u32, spoke_ratio: &mut
|
||||
changed = true;
|
||||
}
|
||||
if let Some(sr) = num_param(p, "spokeRatio") {
|
||||
*spoke_ratio = Some(sr.clamp(0.01, 1.0));
|
||||
*spoke_ratio = Some(sr);
|
||||
changed = true;
|
||||
}
|
||||
if let Some(rnd) = num_param(p, "rounded") {
|
||||
*rounded = rnd.clamp(-10.0, 10.0);
|
||||
*rounded = rnd;
|
||||
changed = true;
|
||||
}
|
||||
if let Some(rand) = num_param(p, "randomized") {
|
||||
*randomized = rand.clamp(-10.0, 10.0);
|
||||
*randomized = rand;
|
||||
changed = true;
|
||||
}
|
||||
changed
|
||||
@@ -429,15 +528,15 @@ fn apply_shape_params(cs: &mut CustomShape, p: &Value, reset: bool) -> bool {
|
||||
changed = true;
|
||||
}
|
||||
if let Some(t) = num_param(p, "turns") {
|
||||
*turns = t.clamp(0.01, 1024.0);
|
||||
*turns = t;
|
||||
changed = true;
|
||||
}
|
||||
if let Some(div) = num_param(p, "divergence") {
|
||||
*divergence = div.clamp(0.0, 1000.0);
|
||||
*divergence = div;
|
||||
changed = true;
|
||||
}
|
||||
if let Some(ir) = num_param(p, "innerRadius") {
|
||||
*inner_radius = ir.clamp(0.0, 0.999);
|
||||
*inner_radius = ir;
|
||||
changed = true;
|
||||
}
|
||||
changed
|
||||
@@ -470,11 +569,12 @@ fn apply_shape_params(cs: &mut CustomShape, p: &Value, reset: bool) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_mode(m: &str) -> shapes::EllipseMode {
|
||||
fn parse_mode(m: &str) -> Option<shapes::EllipseMode> {
|
||||
match m {
|
||||
"pie" | "slice" => shapes::EllipseMode::Slice,
|
||||
"chord" => shapes::EllipseMode::Chord,
|
||||
"arc" | "open" => shapes::EllipseMode::Arc,
|
||||
_ => shapes::EllipseMode::Whole,
|
||||
"whole" => Some(shapes::EllipseMode::Whole),
|
||||
"pie" | "slice" => Some(shapes::EllipseMode::Slice),
|
||||
"chord" => Some(shapes::EllipseMode::Chord),
|
||||
"arc" | "open" => Some(shapes::EllipseMode::Arc),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,3 +135,169 @@ fn addon_shapes_commands_and_format_roundtrip_preserve_paths() {
|
||||
assert_eq!(CustomShape::read(a), CustomShape::read(b));
|
||||
}
|
||||
}
|
||||
|
||||
fn id_of(s: &mut Session, cmd: &str, p: serde_json::Value) -> NodeId {
|
||||
NodeId(s.execute(cmd, &p).unwrap_or_else(|e| panic!("{cmd} {p}: {e}"))["id"].as_u64().unwrap())
|
||||
}
|
||||
|
||||
fn shape_of(s: &Session, id: NodeId) -> CustomShape {
|
||||
CustomShape::read(s.doc().unwrap().doc.node(id).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
/// Runs `cmd` with `p`, which must fail with `msg` and leave the document and its history as they
|
||||
/// were.
|
||||
fn refused(s: &mut Session, cmd: &str, p: serde_json::Value, msg: &str) {
|
||||
let saved = vectorcraft_format::save(&s.doc().unwrap().doc, false);
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
let e = s.execute(cmd, &p).map(|v| v.to_string()).unwrap_err().to_string();
|
||||
assert!(e.contains(msg), "{cmd} {p}: {e}");
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo, "{cmd} {p}");
|
||||
assert_eq!(vectorcraft_format::save(&s.doc().unwrap().doc, false), saved, "{cmd} {p}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polygon_and_star_refuse_counts_that_are_not_whole_numbers_from_3_to_1000() {
|
||||
let mut s = new_session();
|
||||
for n in [json!(2.5), json!(2), json!(0), json!(-6), json!(1000.5), json!(5000), json!(4_294_967_299u64)] {
|
||||
let x = n.as_f64().unwrap();
|
||||
let p = json!({"cx": 0.0, "cy": 0.0, "radius": 10.0, "sides": n});
|
||||
refused(&mut s, "addon.shapes.polygon", p, &format!("`sides` must be a whole number between 3 and 1000 (got {x})"));
|
||||
let p = json!({"cx": 0.0, "cy": 0.0, "radius1": 10.0, "points": n});
|
||||
refused(&mut s, "addon.shapes.star", p, &format!("`points` must be a whole number between 3 and 1000 (got {x})"));
|
||||
}
|
||||
refused(&mut s, "addon.shapes.polygon", json!({"cx": 0.0, "cy": 0.0, "radius": 10.0, "sides": "6"}), "`sides` must be a number");
|
||||
refused(&mut s, "addon.shapes.star", json!({"cx": 0.0, "cy": 0.0, "radius1": 10.0, "points": [5]}), "`points` must be a number");
|
||||
|
||||
// The ends of the range are taken as given, and an omitted or null count is the default.
|
||||
for (n, want) in [(json!(3), 3), (json!(1000), 1000), (json!(7.0), 7), (json!(null), 6)] {
|
||||
let id = id_of(&mut s, "addon.shapes.polygon", json!({"cx": 0.0, "cy": 0.0, "radius": 10.0, "sides": n}));
|
||||
assert!(matches!(shape_of(&s, id), CustomShape::Polygon { sides, spoke_ratio: None, .. } if sides == want), "{n}");
|
||||
}
|
||||
let id = id_of(&mut s, "addon.shapes.polygon", json!({"cx": 0.0, "cy": 0.0, "radius": 10.0}));
|
||||
assert!(matches!(shape_of(&s, id), CustomShape::Polygon { sides: 6, .. }));
|
||||
for (n, want) in [(json!(3), 3), (json!(1000), 1000), (json!(null), 5)] {
|
||||
let id = id_of(&mut s, "addon.shapes.star", json!({"cx": 0.0, "cy": 0.0, "radius1": 10.0, "points": n}));
|
||||
assert!(matches!(shape_of(&s, id), CustomShape::Polygon { sides, spoke_ratio: Some(_), .. } if sides == want), "{n}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shapes_refuse_numbers_outside_their_ranges() {
|
||||
let mut s = new_session();
|
||||
let poly = json!({"cx": 0.0, "cy": 0.0, "radius": 10.0});
|
||||
let star = json!({"cx": 0.0, "cy": 0.0, "radius1": 10.0});
|
||||
let spiral = json!({"cx": 0.0, "cy": 0.0, "radius": 10.0});
|
||||
let with = |base: &serde_json::Value, key: &str, v: f64| {
|
||||
let mut p = base.clone();
|
||||
p[key] = json!(v);
|
||||
p
|
||||
};
|
||||
for (cmd, base, key, bad, good) in [
|
||||
("addon.shapes.polygon", &poly, "rounded", [-10.5, 10.5], [-10.0, 10.0]),
|
||||
("addon.shapes.polygon", &poly, "randomized", [-11.0, 10.01], [-10.0, 10.0]),
|
||||
("addon.shapes.star", &star, "rounded", [-10.5, 10.5], [-10.0, 10.0]),
|
||||
("addon.shapes.star", &star, "randomized", [-11.0, 10.01], [-10.0, 10.0]),
|
||||
("addon.shapes.star", &star, "radius2", [0.05, 11.0], [0.1, 10.0]),
|
||||
("addon.shapes.spiral", &spiral, "turns", [0.0, 1024.5], [0.01, 1024.0]),
|
||||
("addon.shapes.spiral", &spiral, "divergence", [-0.1, 1000.5], [0.0, 1000.0]),
|
||||
("addon.shapes.spiral", &spiral, "innerRadius", [-0.1, 1.0], [0.0, 0.999]),
|
||||
] {
|
||||
let (min, max) = (good[0], good[1]);
|
||||
for x in bad {
|
||||
refused(&mut s, cmd, with(base, key, x), &format!("`{key}` must be between {min} and {max} (got {x})"));
|
||||
}
|
||||
for x in good {
|
||||
s.execute(cmd, &with(base, key, x)).unwrap_or_else(|e| panic!("{cmd} {key} {x}: {e}"));
|
||||
}
|
||||
}
|
||||
|
||||
// Sizes are not negative.
|
||||
for (cmd, p, key) in [
|
||||
("addon.shapes.polygon", json!({"cx": 0.0, "cy": 0.0, "radius": -10.0}), "radius"),
|
||||
("addon.shapes.star", json!({"cx": 0.0, "cy": 0.0, "radius1": -10.0}), "radius1"),
|
||||
("addon.shapes.spiral", json!({"cx": 0.0, "cy": 0.0, "radius": -10.0}), "radius"),
|
||||
("addon.shapes.rectangle", json!({"x": 0.0, "y": 0.0, "width": -10.0, "height": 10.0}), "width"),
|
||||
("addon.shapes.rectangle", json!({"x": 0.0, "y": 0.0, "width": 10.0, "height": 10.0, "radius": -1.0}), "radius"),
|
||||
("addon.shapes.ellipse", json!({"x": 0.0, "y": 0.0, "width": 10.0, "height": -10.0}), "height"),
|
||||
("addon.shapes.box3d", json!({"x": 0.0, "y": 0.0, "width": -10.0, "height": 10.0}), "width"),
|
||||
] {
|
||||
refused(&mut s, cmd, p, &format!("`{key}` must be a number not below 0"));
|
||||
}
|
||||
|
||||
// Corner radii are four numbers that are not negative.
|
||||
for radii in [json!([1.0, 2.0, 3.0]), json!([1.0, 2.0, 3.0, -4.0]), json!([1.0, 2.0, 3.0, "4"]), json!(4.0)] {
|
||||
let p = json!({"x": 0.0, "y": 0.0, "width": 100.0, "height": 80.0, "radii": radii});
|
||||
refused(&mut s, "addon.shapes.rectangle", p, "`radii` must be four numbers not below 0: [tl, tr, br, bl]");
|
||||
}
|
||||
|
||||
// An arc mode is one of the names the commands take.
|
||||
for mode in [json!("slices"), json!(""), json!(3)] {
|
||||
let p = json!({"x": 0.0, "y": 0.0, "width": 100.0, "height": 80.0, "arcMode": mode});
|
||||
refused(&mut s, "addon.shapes.rectangle", p.clone(), "`arcMode` must be one of whole, pie, chord, arc");
|
||||
refused(&mut s, "addon.shapes.ellipse", p, "`arcMode` must be one of whole, pie, chord, arc");
|
||||
}
|
||||
for mode in ["whole", "pie", "slice", "chord", "arc", "open"] {
|
||||
s.execute("addon.shapes.ellipse", &json!({"x": 0.0, "y": 0.0, "width": 100.0, "height": 80.0, "arcMode": mode})).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configure_refuses_values_outside_their_ranges_before_changing_any_shape() {
|
||||
let mut s = new_session();
|
||||
let rect = id_of(&mut s, "addon.shapes.rectangle", json!({"x": 0.0, "y": 0.0, "width": 100.0, "height": 80.0, "radius": 4.0}));
|
||||
let poly = id_of(&mut s, "addon.shapes.polygon", json!({"cx": 50.0, "cy": 50.0, "radius": 40.0, "sides": 5}));
|
||||
let spiral = id_of(&mut s, "addon.shapes.spiral", json!({"cx": 60.0, "cy": 60.0, "radius": 45.0}));
|
||||
let ids = json!([rect.0, poly.0, spiral.0]);
|
||||
let cfg = |key: &str, v: serde_json::Value| json!({"ids": ids.clone(), key: v});
|
||||
let c = "addon.shapes.configure";
|
||||
for n in [json!(2.5), json!(2), json!(-1), json!(5000), json!(4_294_967_299u64)] {
|
||||
let x = n.as_f64().unwrap();
|
||||
refused(&mut s, c, cfg("sides", n), &format!("`sides` must be a whole number between 3 and 1000 (got {x})"));
|
||||
}
|
||||
for (key, bad, min, max) in [
|
||||
("spokeRatio", [0.0, 1.5], 0.01, 1.0),
|
||||
("rounded", [-10.5, 11.0], -10.0, 10.0),
|
||||
("randomized", [-11.0, 10.5], -10.0, 10.0),
|
||||
("turns", [0.0, 2000.0], 0.01, 1024.0),
|
||||
("divergence", [-1.0, 1000.5], 0.0, 1000.0),
|
||||
("innerRadius", [-0.5, 1.0], 0.0, 0.999),
|
||||
] {
|
||||
for x in bad {
|
||||
refused(&mut s, c, cfg(key, json!(x)), &format!("`{key}` must be between {min} and {max} (got {x})"));
|
||||
}
|
||||
}
|
||||
refused(&mut s, c, cfg("radius", json!(-1.0)), "`radius` must be a number not below 0");
|
||||
for radii in [json!([1.0, 2.0]), json!([1.0, 2.0, 3.0, -1.0]), json!("4")] {
|
||||
refused(&mut s, c, cfg("radii", radii), "`radii` must be four numbers not below 0: [tl, tr, br, bl]");
|
||||
}
|
||||
refused(&mut s, c, cfg("arcMode", json!("bogus")), "`arcMode` must be one of whole, pie, chord, arc");
|
||||
// A valid value next to a refused one changes no shape either.
|
||||
refused(&mut s, c, json!({"ids": ids.clone(), "sides": 8, "turns": 0.0}), "`turns` must be between 0.01 and 1024 (got 0)");
|
||||
|
||||
// In range, the values are taken as given; a radius larger than the rectangle takes is fitted to it.
|
||||
s.execute(c, &json!({"ids": [poly.0], "sides": 1000, "rounded": -10.0, "randomized": 10.0})).unwrap();
|
||||
assert!(matches!(shape_of(&s, poly), CustomShape::Polygon { sides: 1000, rounded, randomized, .. } if rounded == -10.0 && randomized == 10.0));
|
||||
s.execute(c, &json!({"ids": [poly.0], "sides": 3})).unwrap();
|
||||
assert!(matches!(shape_of(&s, poly), CustomShape::Polygon { sides: 3, .. }));
|
||||
s.execute(c, &json!({"ids": [spiral.0], "turns": 1024.0, "divergence": 0.0, "innerRadius": 0.999})).unwrap();
|
||||
assert!(
|
||||
matches!(shape_of(&s, spiral), CustomShape::Spiral { turns, divergence, inner_radius, .. } if turns == 1024.0 && divergence == 0.0 && inner_radius == 0.999)
|
||||
);
|
||||
s.execute(c, &json!({"ids": [rect.0], "radius": 1000.0})).unwrap();
|
||||
assert!(matches!(shape_of(&s, rect), CustomShape::Rectangle { radii, .. } if radii == [40.0; 4]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn configure_records_no_undo_step_when_nothing_changes() {
|
||||
let mut s = new_session();
|
||||
let poly = id_of(&mut s, "addon.shapes.polygon", json!({"cx": 50.0, "cy": 50.0, "radius": 40.0, "sides": 5, "rounded": 0.5}));
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
let r = s.execute("addon.shapes.configure", &json!({"ids": [poly.0], "sides": 5, "rounded": 0.5})).unwrap();
|
||||
assert_eq!(r["updated"], 0);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo);
|
||||
|
||||
let r = s.execute("addon.shapes.configure", &json!({"ids": [poly.0], "sides": 6})).unwrap();
|
||||
assert_eq!(r["updated"], 1);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1);
|
||||
assert!(matches!(shape_of(&s, poly), CustomShape::Polygon { sides: 6, .. }));
|
||||
}
|
||||
|
||||
@@ -5263,6 +5263,7 @@ digits Malayalam Malaiàlam
|
||||
@msg `{key}` must be a #rrggbb colour `{key}` ha de ser un color #rrggbb
|
||||
@msg `{key}` must be a number `{key}` ha de ser un nombre
|
||||
@msg `{key}` must be a number not below 0 `{key}` ha de ser un nombre no inferior a 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` han de ser quatre nombres no inferiors a 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be a number, not {v} `{key}` ha de ser un nombre, no {v}
|
||||
@msg `{key}` must be a string `{key}` ha de ser una cadena
|
||||
@msg `{key}` must be a profile name or null `{key}` ha de ser un nom de perfil o null
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4767,6 +4767,7 @@ imageTrace Outline Obrys
|
||||
@msg `select` must be true or false `select` musí být true nebo false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` a `maxAnchors` musí být celá čísla
|
||||
@msg `{key}` must be a number not below 0 `{key}` musí být číslo nejméně 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` musí být čtyři čísla nejméně 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` musí být [x, y, width, height]: čtyři konečná čísla, šířka a výška nesmí být záporné
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` je větší než `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` zadejte alespoň jeden z parametrů `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4381,6 +4381,7 @@ theme Appearance Erscheinungsbild
|
||||
@msg `select` must be true or false `select` muss true oder false sein
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` und `maxAnchors` müssen ganze Zahlen sein
|
||||
@msg `{key}` must be a number not below 0 `{key}` muss eine Zahl nicht kleiner als 0 sein
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` muss aus vier Zahlen nicht kleiner als 0 bestehen: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` muss [x, y, width, height] sein: vier endliche Zahlen, Breite und Höhe nicht negativ
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` ist größer als `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` geben Sie mindestens einen der Parameter `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` an
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2528,6 +2528,7 @@ axis Both Και τα δύο
|
||||
@msg `select` must be true or false Το `select` πρέπει να είναι true ή false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers Τα `minAnchors` και `maxAnchors` πρέπει να είναι ακέραιοι αριθμοί
|
||||
@msg `{key}` must be a number not below 0 Το `{key}` πρέπει να είναι αριθμός όχι μικρότερος από 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] Το `{key}` πρέπει να είναι τέσσερις αριθμοί όχι μικρότεροι από 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative Το `{key}` πρέπει να είναι [x, y, width, height]: τέσσερις πεπερασμένοι αριθμοί, με πλάτος και ύψος όχι αρνητικά
|
||||
@msg `{min_key}` is greater than `{max_key}` Το `{min_key}` είναι μεγαλύτερο από το `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` δώστε τουλάχιστον ένα από τα `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4458,6 +4458,7 @@ axis Both Ambos
|
||||
@msg `select` must be true or false `select` debe ser true o false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` y `maxAnchors` deben ser números enteros
|
||||
@msg `{key}` must be a number not below 0 `{key}` debe ser un número no inferior a 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` debe ser cuatro números no inferiores a 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` debe ser [x, y, width, height]: cuatro números finitos, con anchura y altura no negativas
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` es mayor que `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` indica al menos uno de `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4681,6 +4681,7 @@ imageTrace Outline Ääriviiva
|
||||
@msg `select` must be true or false `select` on oltava true tai false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` ja `maxAnchors` on oltava kokonaislukuja
|
||||
@msg `{key}` must be a number not below 0 `{key}` on oltava luku, joka on vähintään 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` on oltava neljä lukua, jotka ovat vähintään 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` on oltava [x, y, width, height]: neljä äärellistä lukua, joista leveys ja korkeus eivät ole negatiivisia
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` on suurempi kuin `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` anna vähintään yksi parametreista `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4447,6 +4447,7 @@ axis Both Les deux
|
||||
@msg `select` must be true or false `select` doit valoir true ou false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` et `maxAnchors` doivent être des nombres entiers
|
||||
@msg `{key}` must be a number not below 0 `{key}` doit être un nombre supérieur ou égal à 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` doit être quatre nombres supérieurs ou égaux à 0 : [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` doit être [x, y, width, height] : quatre nombres finis, largeur et hauteur non négatives
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` est supérieur à `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` indiquez au moins l’un des paramètres `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5913,6 +5913,7 @@ antiAlias Sharp शार्प
|
||||
@msg `{key}` must be a number `{key}` एक संख्या होना चाहिए
|
||||
@msg `{key}` must be a number {_1}..{_2} `{key}` {_1}..{_2} की एक संख्या होना चाहिए
|
||||
@msg `{key}` must be a number not below 0 `{key}` 0 से कम नहीं होने वाली संख्या होना चाहिए
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` 0 से कम नहीं होने वाली चार संख्याएँ होनी चाहिए: [tl, tr, br, bl]
|
||||
@msg `{key}` must be a number, not {v} `{key}` एक संख्या होना चाहिए, {v} नहीं
|
||||
@msg `{key}` must be a profile name or null `{key}` को प्रोफ़ाइल नाम या null होना चाहिए
|
||||
@msg `{key}` must be a string `{key}` एक स्ट्रिंग होना चाहिए
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4887,6 +4887,7 @@ imageTrace Outline Körvonal
|
||||
@msg `select` must be true or false `select`: true vagy false kell
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` és `maxAnchors`: egész szám kell
|
||||
@msg `{key}` must be a number not below 0 `{key}`: legalább 0 értékű szám kell
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}`: négy legalább 0 értékű szám kell: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}`: [x, y, width, height] kell, négy véges szám, a szélesség és a magasság nem negatív
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` nagyobb, mint `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` adj meg legalább egyet ezek közül: `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4510,6 +4510,7 @@ axis Both Entrambi
|
||||
@msg `select` must be true or false `select` deve essere true o false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` e `maxAnchors` devono essere numeri interi
|
||||
@msg `{key}` must be a number not below 0 `{key}` deve essere un numero non inferiore a 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` deve essere quattro numeri non inferiori a 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` deve essere [x, y, width, height]: quattro numeri finiti, con larghezza e altezza non negative
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` è maggiore di `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` indica almeno uno tra `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4981,6 +4981,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msg `select` must be true or false `select` には true または false を指定してください
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` と `maxAnchors` は整数にしてください
|
||||
@msg `{key}` must be a number not below 0 `{key}` には 0 以上の数値を指定してください
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` には 0 以上の数値を 4 つ指定してください: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` は [x, y, width, height] の形式で指定してください: 有限の数値 4 つで、幅と高さは負の値にできません
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` が `{max_key}` より大きくなっています
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` 次のうち少なくとも 1 つを指定してください: `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4157,6 +4157,7 @@ font-technology Variable ცვლადი
|
||||
@msg `select` must be true or false `select` უნდა იყოს true ან false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` და `maxAnchors` უნდა იყოს მთელი რიცხვები
|
||||
@msg `{key}` must be a number not below 0 `{key}` უნდა იყოს რიცხვი, არანაკლებ 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` უნდა იყოს ოთხი რიცხვი, თითოეული არანაკლებ 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` უნდა იყოს [x, y, width, height]: ოთხი სასრული რიცხვი, სიგანე და სიმაღლე არ უნდა იყოს უარყოფითი
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` მეტია, ვიდრე `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` მიუთითეთ ერთი მაინც: `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4908,6 +4908,7 @@ theme Appearance 외관
|
||||
@msg `select` must be true or false `select`은(는) true 또는 false여야 합니다
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors`와 `maxAnchors`는 정수여야 합니다
|
||||
@msg `{key}` must be a number not below 0 `{key}`은(는) 0 이상의 숫자여야 합니다
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}`은(는) 0 이상의 숫자 네 개여야 합니다: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}`은(는) [x, y, width, height]여야 합니다: 유한한 숫자 네 개이며 너비와 높이는 음수가 아니어야 합니다
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}`이(가) `{max_key}`보다 큽니다
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` 다음 중 하나 이상을 지정하세요: `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -157,6 +157,7 @@
|
||||
@msg `select` must be true or false `select` moet true of false zijn
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` en `maxAnchors` moeten gehele getallen zijn
|
||||
@msg `{key}` must be a number not below 0 `{key}` moet een getal van minstens 0 zijn
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` moet uit vier getallen van minstens 0 bestaan: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` moet [x, y, width, height] zijn: vier eindige getallen, width en height niet negatief
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` is groter dan `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` geef minstens een van `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4591,6 +4591,7 @@ imageTrace Outline Kontur
|
||||
@msg `select` must be true or false `select` musi mieć wartość true lub false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` i `maxAnchors` muszą być liczbami całkowitymi
|
||||
@msg `{key}` must be a number not below 0 `{key}` musi być liczbą nie mniejszą niż 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` musi być czterema liczbami nie mniejszymi niż 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` musi mieć postać [x, y, width, height]: cztery skończone liczby, szerokość i wysokość nieujemne
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` jest większe niż `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` podaj co najmniej jeden z parametrów `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3921,6 +3921,7 @@ axis Both Ambos
|
||||
@msg `select` must be true or false `select` deve ser true ou false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` e `maxAnchors` devem ser números inteiros
|
||||
@msg `{key}` must be a number not below 0 `{key}` deve ser um número não inferior a 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` deve ser quatro números não inferiores a 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` deve ser [x, y, width, height]: quatro números finitos, com largura e altura não negativas
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` é maior que `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` informe pelo menos um de `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -4495,6 +4495,7 @@ axis Both Оба
|
||||
@msg `select` must be true or false `select` должно быть true или false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` и `maxAnchors` должны быть целыми числами
|
||||
@msg `{key}` must be a number not below 0 `{key}` должно быть числом не меньше 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` должно быть четырьмя числами не меньше 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` должно быть [x, y, width, height]: четыре конечных числа, ширина и высота не отрицательные
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` больше, чем `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` укажите хотя бы один из параметров `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4367,6 +4367,7 @@ axis Both Båda
|
||||
@msg `select` must be true or false `select` måste vara true eller false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` och `maxAnchors` måste vara heltal
|
||||
@msg `{key}` must be a number not below 0 `{key}` måste vara ett tal som inte är under 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` måste vara fyra tal som inte är under 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` måste vara [x, y, width, height]: fyra ändliga tal, där bredd och höjd inte är negativa
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` är större än `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` ange minst en av `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4241,6 +4241,7 @@ axis Both Her İkisi
|
||||
@msg `select` must be true or false `select` true veya false olmalıdır
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` ve `maxAnchors` tam sayı olmalıdır
|
||||
@msg `{key}` must be a number not below 0 `{key}` 0'dan küçük olmayan bir sayı olmalıdır
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` 0'dan küçük olmayan dört sayı olmalıdır: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` [x, y, width, height] olmalıdır: dört sonlu sayı, genişlik ve yükseklik negatif olmamalı
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}`, `{max_key}` değerinden büyük
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` şunlardan en az birini verin: `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -4494,6 +4494,7 @@ axis Both Обидві
|
||||
@msg `select` must be true or false `select` має бути true або false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` і `maxAnchors` мають бути цілими числами
|
||||
@msg `{key}` must be a number not below 0 `{key}` має бути числом, не меншим за 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` має бути чотирма числами, не меншими за 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` має бути [x, y, width, height]: чотири скінченні числа, ширина й висота невід’ємні
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` більше за `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` укажіть принаймні один із параметрів `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it is too large.
|
@@ -4547,6 +4547,7 @@ theme Appearance Giao diện
|
||||
@msg `select` must be true or false `select` phải là true hoặc false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` và `maxAnchors` phải là số nguyên
|
||||
@msg `{key}` must be a number not below 0 `{key}` phải là một số không nhỏ hơn 0
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` phải là bốn số không nhỏ hơn 0: [tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` phải là [x, y, width, height]: bốn số hữu hạn, chiều rộng và chiều cao không âm
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` lớn hơn `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` hãy cung cấp ít nhất một trong `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 204 and column 5.
|
@@ -4667,6 +4667,7 @@ imageTrace Outline 轮廓
|
||||
@msg `select` must be true or false `select` 必须是 true 或 false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` 和 `maxAnchors` 必须是整数
|
||||
@msg `{key}` must be a number not below 0 `{key}` 必须是不小于 0 的数字
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` 必须是四个不小于 0 的数字:[tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` 必须是 [x, y, width, height]:四个有限数字,宽度和高度不能为负
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` 大于 `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` 请至少提供以下其中之一:`name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2406,6 +2406,7 @@ file-filter Templates 範本
|
||||
@msg `select` must be true or false `select` 必須是 true 或 false
|
||||
@msg `minAnchors` and `maxAnchors` must be whole numbers `minAnchors` 和 `maxAnchors` 必須是整數
|
||||
@msg `{key}` must be a number not below 0 `{key}` 必須是不小於 0 的數字
|
||||
@msg `{key}` must be four numbers not below 0: [tl, tr, br, bl] `{key}` 必須是四個不小於 0 的數字:[tl, tr, br, bl]
|
||||
@msg `{key}` must be [x, y, width, height]: four finite numbers, width and height not negative `{key}` 必須是 [x, y, width, height]:四個有限數字,寬度和高度不能為負
|
||||
@msg `{min_key}` is greater than `{max_key}` `{min_key}` 大於 `{max_key}`
|
||||
@msg give at least one of `name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors` 請至少提供下列其中之一:`name`, `kind`, `text`, `within`, `intersects`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight`, `minThickness`, `maxThickness`, `minArea`, `maxArea`, `minAnchors`, `maxAnchors`
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
+26
@@ -10872,6 +10872,32 @@ whole points and Escape cancels. A drag keeps a Contour's own corners and miter
|
||||
options `corners` and `miterLimit` (`tool.setOption`). A border with a gap is two Contours of one shape (the gap first, so
|
||||
it ends up in front), baked with `effect.expandAppearance`, then `object.pathfinder.minusFront`.
|
||||
|
||||
## 3D Box and shapes add-on
|
||||
|
||||
The `addon.shapes.*` commands draw rotatable 3D boxes (`addon.shapes.box3d`) and rectangles, ellipses, polygons, stars and
|
||||
spirals that keep their settings (`addon.shapes.rectangle`, `ellipse`, `polygon`, `star`, `spiral`), and
|
||||
`addon.shapes.configure` changes those settings on the selected shapes (or `ids`). An omitted or null value takes its
|
||||
default. A value outside its range is an error that names the parameter and the range, and the command then draws or
|
||||
changes nothing. The ranges are VectorCraft's own:
|
||||
|
||||
| Parameter | Commands | Range (default) |
|
||||
|---|---|---|
|
||||
| `width`, `height` | `rectangle`, `ellipse`, `box3d` | pt, 0 or more |
|
||||
| `radius` | `polygon`, `spiral` | pt, 0 or more |
|
||||
| `radius` (corners) | `rectangle`, `configure` | pt, 0 or more (0); `configure` fits a larger radius to half each rectangle's shorter side |
|
||||
| `radii` | `rectangle`, `configure` | four numbers, 0 or more: `[tl, tr, br, bl]` |
|
||||
| `radius1` | `star` | pt, 0 or more |
|
||||
| `radius2` | `star` | 0.01 to 1 times `radius1` (half of it) |
|
||||
| `sides`, `points` | `polygon`, `star`, `configure` (`sides`) | a whole number from 3 to 1000 (6 sides, 5 points) |
|
||||
| `rounded`, `randomized` | `polygon`, `star`, `configure` | −10 to 10 (0) |
|
||||
| `spokeRatio` | `configure` | 0.01 to 1 |
|
||||
| `turns` | `spiral`, `configure` | 0.01 to 1024 (3) |
|
||||
| `divergence` | `spiral`, `configure` | 0 to 1000 (1) |
|
||||
| `innerRadius` | `spiral`, `configure` | 0 to 0.999 of the radius (0) |
|
||||
| `arcMode` | `rectangle`, `ellipse`, `polygon`, `star`, `configure` | `whole`, `pie` (or `slice`), `chord`, `arc` (or `open`) (`whole`) |
|
||||
|
||||
A `configure` call that changes no shape records no undo step and returns `{updated: 0}`.
|
||||
|
||||
## Cutting Tools add-on
|
||||
|
||||
Cutting Tools is a built-in add-on that prepares art for cutting. Its commands are `addon.cutting.bridges` and
|
||||
|
||||
@@ -2031,6 +2031,15 @@ stored data), `cmd::addons::pathtools::tests` in `vectorcraft-engine` (each comm
|
||||
anchor edits and transforms, the SVG export filter, the tool's gestures), `addons::pathtools::tests` in
|
||||
`vectorcraft-ui-egui` (submenu and panel) and `command_sweep`.
|
||||
|
||||
The 3D Box and shapes add-on's commands (`addon.shapes.*`, 2026-11-13) refuse a value outside its range with an error
|
||||
that names the parameter and the range, and draw or change nothing: polygon sides and star points are whole numbers
|
||||
from 3 to 1,000 ([the help page](https://helpx.adobe.com/illustrator/desktop/draw-shapes-and-paths/modify-live-shapes/live-shape-properties.html)),
|
||||
sizes and corner radii are not negative, a star's inner radius is 0.01 to 1 times its outer radius, and rounding,
|
||||
randomizing, spoke ratio, spiral turns, divergence and inner radius keep VectorCraft's own ranges; `arcMode` takes
|
||||
only the names it lists. `addon.shapes.configure` checks every value before it changes any selected shape and records
|
||||
no undo step when nothing changes. Tests: `cmd::addons::shapes::tests` in `vectorcraft-engine`. Commands:
|
||||
`docs/mcp.md` › 3D Box and shapes add-on.
|
||||
|
||||
The add-on's Corner Tool (2026-10-31) edits the Live Corners every path already keeps (no second corner store): hover
|
||||
shows an anchor that can take a corner, a click cuts one with the tool's radius and kind (round, inverted, chamfer), a
|
||||
drag sizes it (Shift: whole values), a cut corner's marker selects it and loads its values, resizes it, and removes it
|
||||
|
||||
Reference in New Issue
Block a user