mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-26 02:37:01 -06:00
Merge pull request #2555 from dotCooCoo/feat/simplify-slider
Simplify: Object › Path › Simplify opens a slider with Auto-Simplify and More Options
This commit is contained in:
@@ -130,7 +130,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Simplify…",
|
||||
["Object", "Path"],
|
||||
None,
|
||||
"{tolerance?: pt (1), cornerAngle?: deg (30), straightLines?: bool} → {ids, before, after} anchor counts",
|
||||
"{tolerance?: pt (1), level?: 0..100 (the Simplify slider, in place of tolerance: 0 fewest anchor points, 50 is 1 pt, every 10 levels halves or doubles it), auto?: bool (Auto-Simplify: the lowest level whose tolerance is at most 1/400 of the selection's longer side, or 100 when that side is under 12.5 pt), cornerAngle?: deg (30), straightLines?: bool} → {ids, before, after} anchor counts, and the level used with level or auto",
|
||||
has_selection,
|
||||
simplify
|
||||
),
|
||||
@@ -800,13 +800,29 @@ fn edit_paths(s: &mut Session, label: &str, f: impl Fn(&PathData) -> PathData) -
|
||||
}
|
||||
|
||||
fn simplify(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// With auto, the slider's level is Auto-Simplify's for the selection's size; otherwise it is the level given.
|
||||
let level = if bool_or(p, "auto", false) {
|
||||
let roots = selected_roots(s)?;
|
||||
let size = s.doc()?.doc.bounds_of(&roots, false).map_or(0.0, |b| b.width().max(b.height()));
|
||||
Some(po::auto_simplify_level(size))
|
||||
} else {
|
||||
num_param(p, "level").map(|l| if l.is_finite() { l.round().clamp(0.0, 100.0) } else { po::SIMPLIFY_LEVEL })
|
||||
};
|
||||
let tolerance = match level {
|
||||
Some(l) => po::simplify_level_tolerance(l),
|
||||
None => len_param(p, "tolerance").unwrap_or(1.0),
|
||||
};
|
||||
let opts = po::SimplifyOptions {
|
||||
tolerance: len_param(p, "tolerance").unwrap_or(1.0).clamp(1e-4, 1e4),
|
||||
tolerance: tolerance.clamp(1e-4, 1e4),
|
||||
corner_angle_deg: num_param(p, "cornerAngle").unwrap_or(30.0).clamp(0.0, 180.0),
|
||||
straight_lines: bool_or(p, "straightLines", false),
|
||||
};
|
||||
let (ids, before, after) = edit_paths(s, "Simplify", |path| po::simplify_with(path, &opts))?;
|
||||
Ok(json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "before": before, "after": after }))
|
||||
let mut out = json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>(), "before": before, "after": after });
|
||||
if let Some(l) = level {
|
||||
out["level"] = json!(l);
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn add_anchor_points(s: &mut Session, _: &Value) -> Result<Value> {
|
||||
|
||||
@@ -36,6 +36,7 @@ pub use journal::Journal;
|
||||
pub use tooling::{UiRequest, ViewInfo};
|
||||
pub use vectorcraft_doc as doc;
|
||||
pub use vectorcraft_doc::cancel;
|
||||
pub use vectorcraft_pathops::{SIMPLIFY_LEVEL, simplify_level_tolerance};
|
||||
pub use vectorcraft_tools as tools;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
||||
@@ -448,6 +448,58 @@ fn simplify_dense_polyline() {
|
||||
assert!(close(bounds(&s, id), Rect::new(0.0, 60.0, 300.0, 140.0), 2.0));
|
||||
}
|
||||
|
||||
/// A sine wave with an anchor every point over `width` pt, `amplitude` pt high and a wavelength of
|
||||
/// `2π × period` pt, its anchors `jag` pt above and below it in turn, selected: (the session, the
|
||||
/// path's id).
|
||||
fn dense_wave(width: u32, amplitude: f64, period: f64, jag: f64) -> (Session, NodeId) {
|
||||
let mut s = session();
|
||||
let y = |i: u32| 100.0 + amplitude * (f64::from(i) / period).sin() + if i.is_multiple_of(2) { jag } else { -jag };
|
||||
let anchors: Vec<Value> = (0..=width).map(|i| json!({"x": f64::from(i), "y": y(i)})).collect();
|
||||
let r = s.execute("path.create", &json!({"anchors": anchors})).unwrap();
|
||||
(s, NodeId(r["id"].as_u64().unwrap()))
|
||||
}
|
||||
|
||||
/// The Simplify slider: moving its level from 0 to 100 never lowers the anchor count, level 0
|
||||
/// leaves fewer anchors than level 100, and level 50 gives the 1 pt result.
|
||||
#[test]
|
||||
fn simplify_level_moves_the_anchor_count_monotonically() {
|
||||
let (mut s, id) = dense_wave(300, 40.0, 25.0, 0.0);
|
||||
let mut counts = vec![];
|
||||
for level in (0..=100).step_by(10) {
|
||||
let out = s.execute("object.path.simplify", &json!({ "level": level })).unwrap();
|
||||
assert_eq!(out["level"], json!(f64::from(level)));
|
||||
counts.push(path_of(&s, id).0.anchor_count());
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
}
|
||||
assert!(counts.windows(2).all(|w| w[0] <= w[1]), "{counts:?}");
|
||||
assert!(counts[0] < counts[10], "{counts:?}");
|
||||
s.execute("object.path.simplify", &json!({"tolerance": 1})).unwrap();
|
||||
assert_eq!(path_of(&s, id).0.anchor_count(), counts[5], "level 50 is 1 pt");
|
||||
}
|
||||
|
||||
/// Auto-Simplify picks the lowest level whose tolerance is at most 1/400 of the selection's longer
|
||||
/// side, and 11 sampled points on each segment of the original path stay that close to the result.
|
||||
#[test]
|
||||
fn auto_simplify_keeps_the_shape_within_a_400th_of_its_size() {
|
||||
let (mut s, id) = dense_wave(100, 15.0, 8.0, 0.2);
|
||||
let original = path_of(&s, id).0.clone();
|
||||
let out = s.execute("object.path.simplify", &json!({"auto": true})).unwrap();
|
||||
// 100 pt across: 0.25 pt at most, level 70 (0.25 pt).
|
||||
assert_eq!(out["level"], json!(70.0));
|
||||
let result = path_of(&s, id).0.clone();
|
||||
assert!(result.anchor_count() < original.anchor_count() / 2, "{}", result.anchor_count());
|
||||
// How far each point along the original lies from the nearest point of the result.
|
||||
use vectorcraft_geom::ParamCurve;
|
||||
use vectorcraft_geom::kurbo::ParamCurveNearest;
|
||||
let fitted = &result.subpaths[0];
|
||||
let off = |p: vectorcraft_geom::Point| {
|
||||
(0..fitted.segment_count()).map(|i| fitted.segment(i).nearest(p, 1e-9).distance_sq.sqrt()).fold(f64::INFINITY, f64::min)
|
||||
};
|
||||
let sp = &original.subpaths[0];
|
||||
let worst = (0..sp.segment_count()).flat_map(|i| (0..=10).map(move |k| sp.segment(i).eval(f64::from(k) / 10.0))).map(off).fold(0.0, f64::max);
|
||||
assert!(worst <= 100.0 / 400.0, "{worst}");
|
||||
}
|
||||
|
||||
/// How many steps there are to undo and to redo, and whether the document has unsaved changes.
|
||||
fn history(s: &Session) -> (usize, usize, bool) {
|
||||
let st = s.doc().unwrap();
|
||||
|
||||
@@ -355,6 +355,9 @@ fn structured_junk() {
|
||||
("object.path.offsetPath", json!({"offset": 1e308})),
|
||||
("object.path.offsetPath", json!({"offset": -1e308})),
|
||||
("object.path.simplify", json!({"tolerance": -1, "cornerAngle": 1e308})),
|
||||
("object.path.simplify", json!({"level": 1e308, "tolerance": "x"})),
|
||||
("object.path.simplify", json!({"level": -1e308, "auto": "yes"})),
|
||||
("object.path.simplify", json!({"auto": true, "level": [1]})),
|
||||
(
|
||||
"shape.rectangularGrid",
|
||||
json!({"x": 0, "y": 0, "width": 1e308, "height": -1e308, "rows": 999, "columns": u64::MAX, "rowSkew": 1e308, "columnSkew": -1e308, "frame": false, "fill": true}),
|
||||
|
||||
@@ -29,6 +29,30 @@ pub fn simplify(path: &PathData, tolerance: f64) -> PathData {
|
||||
simplify_with(path, &SimplifyOptions { tolerance, ..Default::default() })
|
||||
}
|
||||
|
||||
/// The Simplify slider's level for a tolerance of 1 pt, where the slider opens.
|
||||
pub const SIMPLIFY_LEVEL: f64 = 50.0;
|
||||
|
||||
/// The tolerance (points) of Simplify slider level `level`, from 0 (the fewest anchor points) to
|
||||
/// 100 (the closest to the original): 1 pt at [`SIMPLIFY_LEVEL`], doubled for every 10 levels below
|
||||
/// it and halved for every 10 above, so level 0 is 32 pt and level 100 is 1/32 pt. A level outside
|
||||
/// 0..=100 counts as the nearer end, and one that is not a number counts as [`SIMPLIFY_LEVEL`].
|
||||
pub fn simplify_level_tolerance(level: f64) -> f64 {
|
||||
let level = if level.is_finite() { level.clamp(0.0, 100.0) } else { SIMPLIFY_LEVEL };
|
||||
((SIMPLIFY_LEVEL - level) / 10.0).exp2()
|
||||
}
|
||||
|
||||
/// Auto-Simplify's level for paths whose bounds are `size` points on their longer side: the lowest
|
||||
/// whole level whose tolerance ([`simplify_level_tolerance`]) is at most 1/400 of `size`. It returns
|
||||
/// level 100 for a size of 0 or one that is not a number, and for a size under 12.5 pt, where no
|
||||
/// level is that close.
|
||||
pub fn auto_simplify_level(size: f64) -> f64 {
|
||||
let target = size / 400.0;
|
||||
if !(target.is_finite() && target > 0.0) {
|
||||
return 100.0;
|
||||
}
|
||||
(SIMPLIFY_LEVEL - 10.0 * target.log2()).ceil().clamp(0.0, 100.0)
|
||||
}
|
||||
|
||||
/// Simplify never adds anchors: keep whichever subpath is smaller.
|
||||
fn no_worse(original: &SubPath, fitted: SubPath) -> SubPath {
|
||||
if fitted.anchors.len() > original.anchors.len() { original.clone() } else { fitted }
|
||||
|
||||
@@ -15,8 +15,8 @@ mod planar;
|
||||
|
||||
pub use boolean::{BoolOp, DEFAULT_PRECISION, area, boolean, boolean_n, normalize, try_boolean, try_normalize, unite_all};
|
||||
pub use edit::{
|
||||
AverageAxis, SimplifyOptions, add_anchor_points, average, join, remove_anchor, remove_redundant_points, simplify, simplify_with, smooth,
|
||||
split_into_grid,
|
||||
AverageAxis, SIMPLIFY_LEVEL, SimplifyOptions, add_anchor_points, auto_simplify_level, average, join, remove_anchor, remove_redundant_points,
|
||||
simplify, simplify_level_tolerance, simplify_with, smooth, split_into_grid,
|
||||
};
|
||||
pub use offset::{Cap, Join, offset_path, outline_stroke, stroke_region};
|
||||
pub use pathfinder::{FaceMerger, PathfinderOp, Region, Shape, merge_regions, pathfinder, region_at, regions};
|
||||
|
||||
@@ -343,6 +343,20 @@ fn simplify_straight_lines_option() {
|
||||
assert!(max_deviation(&c, &s) <= 1.0 + 1e-6);
|
||||
}
|
||||
|
||||
/// The Simplify slider's levels: 1 pt at 50, doubled for every 10 levels down and halved for every
|
||||
/// 10 up; Auto-Simplify takes the lowest level whose tolerance is at most 1/400 of the size, and
|
||||
/// level 100 for a size under 12.5 pt.
|
||||
#[test]
|
||||
fn simplify_levels_and_auto_simplify() {
|
||||
assert_eq!(simplify_level_tolerance(SIMPLIFY_LEVEL), 1.0);
|
||||
assert_eq!((simplify_level_tolerance(0.0), simplify_level_tolerance(100.0)), (32.0, 1.0 / 32.0));
|
||||
assert_eq!((simplify_level_tolerance(-5.0), simplify_level_tolerance(500.0), simplify_level_tolerance(f64::NAN)), (32.0, 1.0 / 32.0, 1.0));
|
||||
assert_eq!((auto_simplify_level(400.0), auto_simplify_level(100.0), auto_simplify_level(300.0)), (50.0, 70.0, 55.0));
|
||||
assert!(simplify_level_tolerance(auto_simplify_level(300.0)) <= 300.0 / 400.0);
|
||||
assert_eq!((auto_simplify_level(12.5), auto_simplify_level(10.0)), (100.0, 100.0));
|
||||
assert_eq!((auto_simplify_level(0.0), auto_simplify_level(f64::INFINITY), auto_simplify_level(1e9)), (100.0, 100.0, 0.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smooth_rounds_corners() {
|
||||
let s = smooth(&rect(0.0, 0.0, 10.0, 10.0), 1.0);
|
||||
|
||||
@@ -552,7 +552,8 @@ pub fn show(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
}
|
||||
rulers(ui, full, &xf, app.hover_doc, app.session.general_unit(), origin_preview.unwrap_or_else(|| app.ruler_origin()), &t);
|
||||
}
|
||||
if app.ui.task_bar && !revolve_edit && !app.session.tool_busy() && app.ui.screen_mode < 3 {
|
||||
// The task bar is not drawn while the Simplify slider shows.
|
||||
if app.ui.task_bar && !revolve_edit && !app.session.tool_busy() && app.ui.screen_mode < 3 && !crate::dialogs::simplify_slider::showing(app) {
|
||||
task_bar(app, ui, &xf);
|
||||
}
|
||||
if app.ui.screen_mode < 3 {
|
||||
|
||||
@@ -611,14 +611,21 @@ pub(super) fn preview_result(
|
||||
}
|
||||
return None;
|
||||
}
|
||||
preview_now(app, d, label, cmd, params)
|
||||
}
|
||||
|
||||
/// The preview of [`preview_result`] without its checkbox: `params` run again on the interaction's
|
||||
/// snapshot when they differ from the last preview. Returns the command's result only when a new
|
||||
/// preview succeeds.
|
||||
pub(super) fn preview_now(app: &mut crate::VectorcraftApp, d: &mut Dialog, label: &str, cmd: &str, params: Value) -> Option<Value> {
|
||||
let mut result = None;
|
||||
if d.fields.get(LAST) != Some(¶ms) || !app.session.in_interaction() {
|
||||
if d.fields.get(PREVIEWED) != Some(¶ms) || !app.session.in_interaction() {
|
||||
let _ = app.session.begin_interaction(label);
|
||||
match app.session.preview(cmd, ¶ms) {
|
||||
Ok(r) => result = Some(r),
|
||||
Err(e) => app.status(e.to_string()),
|
||||
}
|
||||
d.fields.insert(LAST.into(), params);
|
||||
d.fields.insert(PREVIEWED.into(), params);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
@@ -89,6 +89,7 @@ pub mod save_swatch_library;
|
||||
pub mod script_check;
|
||||
mod shapes;
|
||||
pub mod shortcut_import;
|
||||
pub(crate) mod simplify_slider;
|
||||
pub mod slices;
|
||||
pub mod spot_colors;
|
||||
pub(crate) mod svg_options;
|
||||
@@ -325,6 +326,7 @@ registry! {
|
||||
Plugin: [plugin::KIND] => plugin::SPEC,
|
||||
ScriptCheck: [script_check::KIND] => script_check::SPEC,
|
||||
VectorHalftone: [halftone::KIND] => halftone::SPEC,
|
||||
SimplifySlider: [simplify_slider::KIND] => simplify_slider::SPEC,
|
||||
PerspectiveGrid: [perspective_grid::KIND] => perspective_grid::SPEC,
|
||||
Envelope: [envelope::WARP, envelope::MESH, envelope::OPTIONS] => envelope::SPEC,
|
||||
LiquifyOptions: [liquify::KIND] => liquify::SPEC,
|
||||
|
||||
@@ -101,9 +101,18 @@ fn split_grid(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) {
|
||||
form::check(ui, d, "addGuides", tl!("Add Guides"));
|
||||
}
|
||||
|
||||
/// The fields the Simplify dialog opens with at `tolerance` (points): the slider's tolerance when
|
||||
/// its More Options opens the dialog.
|
||||
pub(crate) fn simplify_dialog(tolerance: f64) -> Value {
|
||||
json!({"tolerance": tolerance, "angleThreshold": ANGLE_THRESHOLD, "straightLines": false, "showOriginal": false, "retain": false, "preview": true})
|
||||
}
|
||||
|
||||
/// The fields the Simplify dialog keeps when OK confirms it with `retain` ticked.
|
||||
const SIMPLIFY_KEPT: [&str; 6] = ["tolerance", "angleThreshold", "straightLines", "showOriginal", "retain", "preview"];
|
||||
|
||||
/// Simplify's fields: Tolerance, Corner Point Angle Threshold (its label above a slider from 0° on
|
||||
/// the left, the smoothest, to 180°, the sharpest), Convert to Straight Lines and Show Original
|
||||
/// Path.
|
||||
/// the left, the smoothest, to 180°, the sharpest), Convert to Straight Lines, Show Original Path
|
||||
/// and Open this dialog with these settings next time.
|
||||
fn simplify_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) {
|
||||
let rail = Tokens::get(ui.ctx()).input_border;
|
||||
egui::Grid::new("dlg").num_columns(2).spacing([10.0, 8.0]).show(ui, |ui| {
|
||||
@@ -115,6 +124,7 @@ fn simplify_fields(ui: &mut egui::Ui, d: &mut Dialog, unit: Unit) {
|
||||
ui.add_space(8.0);
|
||||
form::check(ui, d, "straightLines", tl!("Convert to Straight Lines"));
|
||||
form::check(ui, d, "showOriginal", tl!("Show Original Path"));
|
||||
form::check(ui, d, "retain", tl!("Open this dialog with these settings next time"));
|
||||
}
|
||||
|
||||
/// Simplify's Show Original Path: while the dialog previews with it on, every path the preview ran
|
||||
@@ -175,6 +185,12 @@ fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result<Value, String> {
|
||||
return run_and_close(app, id, params);
|
||||
}
|
||||
let r = form::commit_preview(app, id, params)?;
|
||||
// Simplify: with retain ticked, the menu item opens this dialog on these fields next time; with it
|
||||
// unticked, the menu item opens the slider.
|
||||
if d.kind == "simplify" {
|
||||
let kept = SIMPLIFY_KEPT.into_iter().filter_map(|k| Some((k.to_string(), d.fields.get(k)?.clone())));
|
||||
app.ui.simplify = if d.bool("retain") { Value::Object(kept.collect()) } else { Value::Null };
|
||||
}
|
||||
// The fields as the dialog held them, so they show as typed when it opens again.
|
||||
if d.kind == "offsetPath" {
|
||||
let kept = ["offset", "joins", "miterLimit"].into_iter().filter_map(|k| Some((k.to_string(), d.fields.get(k)?.clone())));
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
//! Object › Path › Simplify's slider: a strip under the selection with Auto-Simplify, a slider from
|
||||
//! the fewest anchor points (left) to the closest fit to the original (right), and More Options,
|
||||
//! which opens the Simplify dialog at the slider's tolerance. While the strip is open, the canvas
|
||||
//! previews `object.path.simplify {level}`; Enter or a click outside the strip keeps the result as
|
||||
//! one undo step, and Escape puts the paths back. The slider opens at level 50, a tolerance of
|
||||
//! 1 pt (`vectorcraft_pathops::simplify_level_tolerance` maps levels to tolerances).
|
||||
//!
|
||||
//! Fields: `level` (0..=100, the command's parameter).
|
||||
|
||||
use egui::{Color32, CornerRadius, Order, Pos2, Sense, Stroke, vec2};
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_engine::{SIMPLIFY_LEVEL, simplify_level_tolerance};
|
||||
use vectorcraft_geom::Rect;
|
||||
|
||||
use super::{DialogResult, DialogSpec, form, modal};
|
||||
use crate::state::Dialog;
|
||||
use crate::theme::Tokens;
|
||||
use crate::{VectorcraftApp, widgets};
|
||||
|
||||
/// The dialog kind of the Simplify slider.
|
||||
pub(crate) const KIND: &str = "simplifySlider";
|
||||
|
||||
pub(super) const SPEC: DialogSpec = DialogSpec { window: Some(show), confirm, preview: true, ..DialogSpec::FORM };
|
||||
|
||||
/// The command the slider previews, and its undo label.
|
||||
const CMD: &str = "object.path.simplify";
|
||||
const LABEL: &str = "Simplify";
|
||||
|
||||
/// The fields the slider opens with.
|
||||
pub(crate) fn fields() -> Value {
|
||||
json!({ "level": SIMPLIFY_LEVEL })
|
||||
}
|
||||
|
||||
/// Whether the Simplify slider is open.
|
||||
pub(crate) fn showing(app: &VectorcraftApp) -> bool {
|
||||
app.ui.dialog.as_ref().is_some_and(|d| d.kind == KIND)
|
||||
}
|
||||
|
||||
/// The slider's level, a whole number from 0 to 100.
|
||||
fn level(d: &Dialog) -> f64 {
|
||||
let l = d.f64("level", SIMPLIFY_LEVEL);
|
||||
if l.is_finite() { l.round().clamp(0.0, 100.0) } else { SIMPLIFY_LEVEL }
|
||||
}
|
||||
|
||||
fn params(d: &Dialog) -> Value {
|
||||
json!({ "level": level(d) })
|
||||
}
|
||||
|
||||
fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> DialogResult {
|
||||
form::commit_preview(app, CMD, params(d))
|
||||
}
|
||||
|
||||
/// The open slider, or an error when it isn't open.
|
||||
fn open_slider(app: &VectorcraftApp) -> Result<Dialog, String> {
|
||||
app.ui.dialog.clone().filter(|d| d.kind == KIND).ok_or_else(|| "the Simplify slider is not open".to_string())
|
||||
}
|
||||
|
||||
/// Auto-Simplify (`ui.simplifyAuto`): the open slider moves to the level `object.path.simplify
|
||||
/// {auto: true}` picks for the selection, and the canvas previews it → {level}.
|
||||
pub(crate) fn auto(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let mut d = open_slider(app)?;
|
||||
let _ = app.session.begin_interaction(LABEL);
|
||||
let r = app.session.preview(CMD, &json!({ "auto": true })).map_err(|e| e.to_string())?;
|
||||
let level = r["level"].as_f64().unwrap_or(SIMPLIFY_LEVEL);
|
||||
d.fields.insert("level".into(), json!(level));
|
||||
d.fields.insert(form::PREVIEWED.into(), params(&d));
|
||||
app.ui.dialog = Some(d);
|
||||
Ok(json!({ "level": level }))
|
||||
}
|
||||
|
||||
/// More Options (`ui.simplifyMoreOptions`): the Simplify dialog takes the slider's place, at the
|
||||
/// slider's tolerance → {tolerance}.
|
||||
pub(crate) fn more_options(app: &mut VectorcraftApp) -> Result<Value, String> {
|
||||
let d = open_slider(app)?;
|
||||
let tolerance = simplify_level_tolerance(level(&d));
|
||||
app.ui.dialog = Some(Dialog::new("simplify", super::path_ops::simplify_dialog(tolerance)));
|
||||
Ok(json!({ "tolerance": tolerance }))
|
||||
}
|
||||
|
||||
/// The private field that holds the selection's bounds before the preview, `[x0, y0, x1, y1]`.
|
||||
const BOUNDS: &str = "__bounds";
|
||||
|
||||
/// The selection's bounds before the preview: the interaction's snapshot when a preview runs, read
|
||||
/// once and kept in `d`, so the strip stays where it opened while the preview changes the paths.
|
||||
fn bounds(app: &mut VectorcraftApp, d: &mut Dialog) -> Option<Rect> {
|
||||
if let Some([x0, y0, x1, y1]) = d.fields.get(BOUNDS).and_then(|v| serde_json::from_value::<[f64; 4]>(v.clone()).ok()) {
|
||||
return Some(Rect::new(x0, y0, x1, y1));
|
||||
}
|
||||
let b = match app.session.active().and_then(|st| st.interaction.as_ref()) {
|
||||
Some(it) => it.doc.bounds_of(&it.selection.objects, true),
|
||||
None => app.selection_bounds(),
|
||||
}?;
|
||||
d.fields.insert(BOUNDS.into(), json!([b.x0, b.y0, b.x1, b.y1]));
|
||||
Some(b)
|
||||
}
|
||||
|
||||
/// Where the strip goes: under the lowest corner on screen of `bounds` (the selection's, before the
|
||||
/// preview), centered on them, kept on the canvas.
|
||||
fn place(app: &mut VectorcraftApp, ctx: &egui::Context, id: egui::Id, bounds: Option<Rect>) -> Pos2 {
|
||||
let size = egui::AreaState::load(ctx, id).and_then(|s| s.size).unwrap_or(vec2(270.0, 42.0));
|
||||
let screen = ctx.content_rect();
|
||||
let Some((rect, view)) = app.canvas_rect.zip(app.view().copied()) else { return screen.center() - size / 2.0 };
|
||||
let xf = crate::canvas::Xf::new(rect, &view);
|
||||
let under = match bounds {
|
||||
Some(b) => {
|
||||
let lowest = xf.quad(b).iter().map(|c| c.y).fold(f32::NEG_INFINITY, f32::max);
|
||||
Pos2::new(xf.to_screen(b.center()).x - size.x / 2.0, lowest + 28.0)
|
||||
}
|
||||
None => rect.center() - size / 2.0,
|
||||
};
|
||||
let bounds = rect.shrink(8.0);
|
||||
under.clamp(bounds.min, (bounds.max - size).max(bounds.min))
|
||||
}
|
||||
|
||||
/// What a button of the strip asked for.
|
||||
enum Action {
|
||||
Auto,
|
||||
MoreOptions,
|
||||
}
|
||||
|
||||
fn show(app: &mut VectorcraftApp, ctx: &egui::Context) {
|
||||
let Some(mut d) = app.ui.dialog.clone() else { return };
|
||||
let t = Tokens::get(ctx);
|
||||
let id = egui::Id::new(("dialog", KIND));
|
||||
let b = bounds(app, &mut d);
|
||||
let pos = place(app, ctx, id, b);
|
||||
// A click anywhere else keeps the result, and the rest of the app doesn't take it.
|
||||
let dim = modal::backdrop();
|
||||
let outside =
|
||||
egui::Area::new(dim.id).order(dim.order).fixed_pos(Pos2::ZERO).show(ctx, |ui| ui.allocate_rect(ctx.content_rect(), Sense::click())).inner;
|
||||
let mut action = None;
|
||||
egui::Area::new(id).order(Order::Foreground).fixed_pos(pos).show(ctx, |ui| {
|
||||
egui::Frame::NONE
|
||||
.fill(t.panel)
|
||||
.stroke(Stroke::new(1.0, t.tool_active))
|
||||
.corner_radius(CornerRadius::same(5))
|
||||
.inner_margin(egui::Margin::symmetric(8, 6))
|
||||
.shadow(egui::epaint::Shadow { offset: [0, 3], blur: 10, spread: 0, color: Color32::from_black_alpha(70) })
|
||||
.show(ui, |ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.spacing_mut().item_spacing.x = 6.0;
|
||||
if widgets::icon_button(ui, "wand-sparkles", tl!("Auto-Simplify"), false, 28.0).clicked() {
|
||||
action = Some(Action::Auto);
|
||||
}
|
||||
let rail = t.input_border;
|
||||
form::slider_rail_named(ui, &mut d, "level", tl!("Simplify"), 0.0..=100.0, &|_| rail);
|
||||
if widgets::icon_button(ui, "ellipsis", tl!("More Options"), false, 28.0).clicked() {
|
||||
action = Some(Action::MoreOptions);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
let p = params(&d);
|
||||
let _ = form::preview_now(app, &mut d, LABEL, CMD, p);
|
||||
app.ui.dialog = Some(d);
|
||||
let r = match action {
|
||||
Some(Action::Auto) => auto(app),
|
||||
Some(Action::MoreOptions) => more_options(app),
|
||||
None if outside.clicked() || ctx.input(|i| i.key_pressed(egui::Key::Enter)) => super::confirm(app),
|
||||
None => Ok(Value::Null),
|
||||
};
|
||||
if let Err(e) = r {
|
||||
app.status(e);
|
||||
}
|
||||
}
|
||||
@@ -474,6 +474,10 @@ fn menu_parameter_dialogs_are_compact() {
|
||||
app.run("shape.rectangle", json!({"x": 10, "y": 10, "width": 50, "height": 50})).unwrap();
|
||||
app.run("select.all", json!({})).unwrap();
|
||||
crate::menus::invoke(&mut app, id, json!({}));
|
||||
// Simplify opens its slider; More Options opens the dialog.
|
||||
if id == "object.path.simplify" {
|
||||
app.run("ui.simplifyMoreOptions", json!({})).unwrap();
|
||||
}
|
||||
assert!(app.ui.dialog.is_some(), "{id} opened no dialog");
|
||||
let wide = dialog_rect(&mut app, 1600.0);
|
||||
eprintln!("{id}: {:.0} × {:.0} in a 1600-point window", wide.width(), wide.height());
|
||||
|
||||
@@ -292,13 +292,181 @@ fn app_with_dense_path() -> VectorcraftApp {
|
||||
app
|
||||
}
|
||||
|
||||
/// Object › Path › Simplify, then the slider's More Options: the Simplify dialog.
|
||||
fn open_simplify_dialog(app: &mut VectorcraftApp) {
|
||||
crate::menus::invoke(app, "object.path.simplify", json!({}));
|
||||
app.run("ui.simplifyMoreOptions", json!({})).unwrap();
|
||||
}
|
||||
|
||||
/// The anchors of the paths in `app`'s document.
|
||||
fn anchor_count(app: &VectorcraftApp) -> usize {
|
||||
let mut n = 0;
|
||||
app.session.doc().unwrap().doc.walk(|node| {
|
||||
if let vectorcraft_doc::NodeKind::Path { path, .. } = &node.kind {
|
||||
n += path.anchor_count();
|
||||
}
|
||||
});
|
||||
n
|
||||
}
|
||||
|
||||
/// The parameters of the Simplify preview on the canvas.
|
||||
fn simplify_preview(app: &VectorcraftApp) -> serde_json::Value {
|
||||
app.session.doc().unwrap().interaction.as_ref().and_then(|it| it.preview.clone()).expect("a preview").1
|
||||
}
|
||||
|
||||
/// The Simplify slider's strip, and the centers of its Auto-Simplify button, its rail and its More
|
||||
/// Options button.
|
||||
fn slider_strip(ctx: &egui::Context) -> (Rect, Pos2, Pos2, Pos2) {
|
||||
let r = rect_of(ctx, egui::Id::new(("dialog", simplify_slider::KIND)));
|
||||
let y = r.center().y;
|
||||
(r, Pos2::new(r.left() + 8.0 + 14.0, y), Pos2::new(r.left() + 8.0 + 28.0 + 6.0 + 90.0, y), Pos2::new(r.right() - 8.0 - 14.0, y))
|
||||
}
|
||||
|
||||
/// Object › Path › Simplify opens a slider under the selection that previews the 1 pt result.
|
||||
/// Dragging it to the right previews more anchor points and to the left fewer, and Enter keeps the
|
||||
/// result as one undo step.
|
||||
#[test]
|
||||
fn simplify_opens_a_slider_whose_drag_previews_fewer_or_more_points() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(simplify_slider::KIND));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 50.0}));
|
||||
assert_eq!(anchor_count(&app), 2, "the 1 pt result");
|
||||
let (_, _, rail, _) = slider_strip(&ctx);
|
||||
drag(&ctx, &mut app, rail, vec2(100.0, 0.0));
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 100.0}));
|
||||
let most = anchor_count(&app);
|
||||
assert!(most > 2, "{most}");
|
||||
let (_, _, rail, _) = slider_strip(&ctx);
|
||||
drag(&ctx, &mut app, rail + vec2(90.0, 0.0), vec2(-190.0, 0.0));
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 0.0}));
|
||||
assert_eq!(anchor_count(&app), 2);
|
||||
frame(
|
||||
&ctx,
|
||||
&mut app,
|
||||
vec![Event::Key { key: egui::Key::Enter, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() }],
|
||||
);
|
||||
assert!(app.ui.dialog.is_none() && !app.session.in_interaction());
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo_len + 1);
|
||||
assert_eq!(anchor_count(&app), 2);
|
||||
}
|
||||
|
||||
/// Escape, taken by the app's key handling before the dialog layer draws, closes the Simplify
|
||||
/// slider and puts the paths back as they were, with no undo step.
|
||||
#[test]
|
||||
fn simplify_slider_escape_puts_the_paths_back() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
settle(&ctx, &mut app);
|
||||
let (_, _, rail, _) = slider_strip(&ctx);
|
||||
drag(&ctx, &mut app, rail, vec2(100.0, 0.0));
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 100.0}));
|
||||
assert_ne!(app.session.doc().unwrap().doc, before, "the preview changes the path");
|
||||
let escape = Event::Key { key: egui::Key::Escape, physical_key: None, pressed: true, repeat: false, modifiers: Default::default() };
|
||||
let input = egui::RawInput { screen_rect: Some(Rect::from_min_size(Pos2::ZERO, SCREEN)), events: vec![escape], ..Default::default() };
|
||||
let mut out = ctx.run_ui(input, |ui| {
|
||||
crate::shortcuts::handle(&mut app, ui.ctx());
|
||||
show(&mut app, ui.ctx());
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
assert!(app.ui.dialog.is_none() && !app.session.in_interaction());
|
||||
assert_eq!(app.session.doc().unwrap().doc, before);
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo_len);
|
||||
assert_eq!(anchor_count(&app), 20);
|
||||
}
|
||||
|
||||
/// The Simplify slider stays where it opened, under the paths as they were, while the preview
|
||||
/// changes their bounds, so a drag doesn't move the rail under the pointer.
|
||||
#[test]
|
||||
fn simplify_slider_stays_where_it_opened_while_the_preview_changes_the_bounds() {
|
||||
let ctx = ctx();
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 400, "height": 300})).unwrap();
|
||||
app.canvas_rect = Some(Rect::from_min_size(Pos2::ZERO, SCREEN));
|
||||
// A hump 20 pt high, which level 0 (32 pt) fits with a lower curve, seen at 400%.
|
||||
let anchors: Vec<_> = (0..20).map(|i| json!({"x": 30 + i * 15, "y": 100.0 + 20.0 * (std::f64::consts::PI * i as f64 / 19.0).sin()})).collect();
|
||||
app.run("path.create", json!({"anchors": anchors})).unwrap();
|
||||
app.run("view.setZoom", json!({"zoom": 400})).unwrap();
|
||||
app.view_mut().unwrap().center = vectorcraft_geom::Point::new(172.5, 60.0);
|
||||
let before = app.selection_bounds().unwrap();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
settle(&ctx, &mut app);
|
||||
let opened = rect_of(&ctx, egui::Id::new(("dialog", simplify_slider::KIND)));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("level".into(), json!(0));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 0.0}));
|
||||
let after = app.selection_bounds().unwrap();
|
||||
assert!(after.height() < before.height() - 0.5, "the preview changes the bounds: {before:?} → {after:?}");
|
||||
assert_eq!(rect_of(&ctx, egui::Id::new(("dialog", simplify_slider::KIND))), opened);
|
||||
}
|
||||
|
||||
/// Auto-Simplify moves the slider to the level that keeps the path within 1/400 of its size, and a
|
||||
/// click outside the strip keeps the result as one undo step.
|
||||
#[test]
|
||||
fn simplify_slider_auto_simplify_and_a_click_outside_keep_the_result() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
settle(&ctx, &mut app);
|
||||
let (_, auto, ..) = slider_strip(&ctx);
|
||||
click(&ctx, &mut app, auto);
|
||||
// The path is 285 pt wide: 0.7125 pt at most, level 55.
|
||||
assert_eq!(app.ui.dialog.as_ref().unwrap().f64("level", 0.0), 55.0);
|
||||
assert_eq!(app.session.doc().unwrap().interaction.as_ref().and_then(|it| it.preview.clone()).unwrap().1, json!({"auto": true}));
|
||||
frame(&ctx, &mut app, vec![]);
|
||||
assert_eq!(anchor_count(&app), 2);
|
||||
assert!(app.session.in_interaction(), "the result waits for a click outside");
|
||||
click(&ctx, &mut app, Pos2::new(20.0, 20.0));
|
||||
assert!(app.ui.dialog.is_none() && !app.session.in_interaction());
|
||||
assert_eq!(app.session.doc().unwrap().history.undo.len(), undo_len + 1);
|
||||
assert_ne!(app.session.doc().unwrap().doc, before);
|
||||
}
|
||||
|
||||
/// More Options opens the Simplify dialog at the slider's tolerance. With "Open this dialog with
|
||||
/// these settings next time" ticked, OK makes the menu item open the dialog on its settings; with
|
||||
/// it unticked, the slider again.
|
||||
#[test]
|
||||
fn simplify_slider_more_options_and_the_dialog_that_opens_directly() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("level".into(), json!(60));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app), json!({"level": 60.0}));
|
||||
let (_, _, _, more) = slider_strip(&ctx);
|
||||
click(&ctx, &mut app, more);
|
||||
let d = app.ui.dialog.as_ref().unwrap();
|
||||
assert_eq!((d.kind.as_str(), d.f64("tolerance", 0.0), d.bool("retain")), ("simplify", 0.5, false));
|
||||
settle(&ctx, &mut app);
|
||||
assert_eq!(simplify_preview(&app)["tolerance"], json!(0.5));
|
||||
let texts = frame(&ctx, &mut app, vec![]);
|
||||
let retain = texts.iter().find(|(t, _)| t == "Open this dialog with these settings next time").map(|(_, r)| *r).expect("the checkbox");
|
||||
click(&ctx, &mut app, retain.center());
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("straightLines".into(), json!(true));
|
||||
confirm(&mut app).unwrap();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
let d = app.ui.dialog.clone().unwrap();
|
||||
assert_eq!((d.kind.as_str(), d.f64("tolerance", 0.0), d.bool("straightLines"), d.bool("retain")), ("simplify", 0.5, true, true));
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("retain".into(), json!(false));
|
||||
confirm(&mut app).unwrap();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
assert_eq!(app.ui.dialog.as_ref().map(|d| d.kind.as_str()), Some(simplify_slider::KIND));
|
||||
assert!(app.run("ui.simplifyAuto", json!({})).is_ok());
|
||||
cancel(&mut app);
|
||||
assert!(app.run("ui.simplifyMoreOptions", json!({})).is_err(), "the slider is not open");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simplify_dialog_preview_counts_and_cancel_restore_the_original() {
|
||||
let (ctx, mut app) = (ctx(), app_with_dense_path());
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let selection = app.session.doc().unwrap().selection.clone();
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
open_simplify_dialog(&mut app);
|
||||
settle(&ctx, &mut app);
|
||||
let texts = frame(&ctx, &mut app, vec![]);
|
||||
let preview = texts.iter().find(|(t, _)| t == "Preview").map(|(_, r)| *r).expect("a Preview checkbox");
|
||||
@@ -337,7 +505,7 @@ fn simplify_dialog_ok_keeps_exactly_one_undo_with_or_without_preview() {
|
||||
let before = app.session.doc().unwrap().doc.clone();
|
||||
let selection = app.session.doc().unwrap().selection.clone();
|
||||
let undo_len = app.session.doc().unwrap().history.undo.len();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
open_simplify_dialog(&mut app);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("preview".into(), json!(on));
|
||||
settle(&ctx, &mut app);
|
||||
if on {
|
||||
@@ -381,7 +549,7 @@ fn only_straight_segments(app: &VectorcraftApp) -> bool {
|
||||
fn simplify_dialog_sends_corner_angle_and_straight_lines() {
|
||||
let (ctx, mut app) = (ctx(), app_with_selection());
|
||||
app.run("shape.ellipse", json!({"x": 100, "y": 200, "width": 160, "height": 90})).unwrap();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
open_simplify_dialog(&mut app);
|
||||
settle(&ctx, &mut app);
|
||||
let texts = frame(&ctx, &mut app, vec![]);
|
||||
assert!(texts.iter().any(|(t, _)| t == "Corner Point Angle Threshold:"), "{texts:?}");
|
||||
@@ -407,7 +575,7 @@ fn simplify_dialog_corner_point_angle_threshold_keeps_or_smooths_corners() {
|
||||
let (ctx, mut app) = (ctx(), app_with_selection());
|
||||
app.run("edit.clear", json!({})).unwrap();
|
||||
app.run("shape.polygon", json!({"cx": 200, "cy": 200, "radius": 100, "sides": 8})).unwrap();
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
open_simplify_dialog(&mut app);
|
||||
app.ui.dialog.as_mut().unwrap().fields.insert("tolerance".into(), json!("10 pt"));
|
||||
settle(&ctx, &mut app);
|
||||
assert!(only_straight_segments(&app), "the corners stay and the sides stay straight");
|
||||
@@ -426,7 +594,7 @@ fn simplify_dialog_show_original_path_outlines_the_paths_before_the_preview() {
|
||||
Some((id, vectorcraft_doc::NodeKind::Path { path, .. })) => (id, path.clone()),
|
||||
_ => panic!("a path is selected"),
|
||||
};
|
||||
crate::menus::invoke(&mut app, "object.path.simplify", json!({}));
|
||||
open_simplify_dialog(&mut app);
|
||||
settle(&ctx, &mut app);
|
||||
let texts = frame(&ctx, &mut app, vec![]);
|
||||
assert!(super::path_ops::original_paths(&app).is_empty(), "Show Original Path is off as the dialog opens");
|
||||
|
||||
@@ -2868,6 +2868,8 @@
|
||||
Show Note Показване на бележка
|
||||
Show Options Показване на опциите
|
||||
Show Original Path Показване на оригиналния път
|
||||
Open this dialog with these settings next time Отваряне на този прозорец с тези настройки следващия път
|
||||
Auto-Simplify Автоматично опростяване
|
||||
Show Package Показване на пакет
|
||||
Show Panel Показване на панел
|
||||
Show Pattern Swatches Показване на образци за мостра
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1712,6 +1712,8 @@ lighting Light Světlo
|
||||
Corner Point Angle Threshold: Prahový úhel rohových bodů:
|
||||
Convert to Straight Lines Převést na přímé čáry
|
||||
Show Original Path Zobrazit původní cestu
|
||||
Open this dialog with these settings next time Příště otevřít toto dialogové okno s tímto nastavením
|
||||
Auto-Simplify Automaticky zjednodušit
|
||||
Transparency Options Možnosti průhlednosti
|
||||
Color Mixer (Shift-click the chip) Míchání barev (Shift+kliknutí na políčko)
|
||||
Fill: click for swatches, Shift-click for the color mixer Výplň: kliknutím otevřete vzorník, Shift+kliknutím míchání barev
|
||||
@@ -4173,6 +4175,7 @@ imageTrace Outline Obrys
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" pozadí {other}: transparent, white, black nebo barva jako "#ff8800"
|
||||
@msg bad color {v} chybná barva {v}
|
||||
@msg the Color Picker is not open Výběr barvy není otevřený
|
||||
@msg the Simplify slider is not open Posuvník Zjednodušit není otevřený
|
||||
@msg the screen color sampler is already open nástroj pro nabírání barvy z obrazovky už je otevřený
|
||||
@msg this system has no screen color sampler tento systém nemá nástroj pro nabírání barvy z obrazovky
|
||||
@msg bad colour `{hex}` (#rrggbb) chybná barva `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5052,6 +5052,8 @@ swatch Bright Violet Kraftig violet
|
||||
Outline Object Objekt til kontur
|
||||
Update Selection Opdater markering
|
||||
Show Original Path Vis oprindelig kurve
|
||||
Open this dialog with these settings next time Åbn denne dialogboks med disse indstillinger næste gang
|
||||
Auto-Simplify Forenkl automatisk
|
||||
@msg check a layer to export markér et lag, der skal eksporteres
|
||||
@msg select the art to export first markér først grafikken, der skal eksporteres
|
||||
@msg no saved selection recalled ingen gemt markering hentet
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2907,6 +2907,8 @@ mark-style Japanese Japanisch
|
||||
Show Note Notiz einblenden
|
||||
Show Options Optionen einblenden
|
||||
Show Original Path Originalpfad einblenden
|
||||
Open this dialog with these settings next time Dieses Dialogfeld nächstes Mal mit diesen Einstellungen öffnen
|
||||
Auto-Simplify Automatisch vereinfachen
|
||||
Color Panel Options Optionen für das Farbbedienfeld
|
||||
Show Package Paket anzeigen
|
||||
Show Panel Bedienfeld einblenden
|
||||
@@ -3808,6 +3810,7 @@ theme Appearance Erscheinungsbild
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" background {other}: transparent, white, black oder eine Farbe wie "#ff8800"
|
||||
@msg bad color {v} ungültige Farbe {v}
|
||||
@msg the Color Picker is not open der Farbwähler ist nicht geöffnet
|
||||
@msg the Simplify slider is not open der Regler „Vereinfachen“ ist nicht geöffnet
|
||||
@msg the screen color sampler is already open die Farbaufnahme vom Bildschirm ist bereits geöffnet
|
||||
@msg this system has no screen color sampler dieses System hat keine Farbaufnahme vom Bildschirm
|
||||
@msg bad colour `{hex}` (#rrggbb) ungültige Farbe `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3325,6 +3325,8 @@ theme Appearance Εμφάνιση
|
||||
Show Note Εμφάνιση σημείωσης
|
||||
Show Options Εμφάνιση επιλογών
|
||||
Show Original Path Εμφάνιση αρχικής διαδρομής
|
||||
Open this dialog with these settings next time Άνοιγμα αυτού του παραθύρου με αυτές τις ρυθμίσεις την επόμενη φορά
|
||||
Auto-Simplify Αυτόματη απλοποίηση
|
||||
Show Package Εμφάνιση συσκευασίας
|
||||
Show Panel Εμφάνιση πίνακα
|
||||
Show Pattern Swatches Εμφάνιση δειγμάτων μοτίβου
|
||||
@@ -5113,6 +5115,7 @@ effect Points Σημεία
|
||||
@msg span: "selection" places a linear or radial gradient, not a freeform one span: το "selection" τοποθετεί γραμμικό ή ακτινικό ντεγκραντέ, όχι ελεύθερης μορφής
|
||||
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: το "selection" τοποθετεί το ίδιο το ντεγκραντέ: παραλείψτε τα `start` και `end` (το τοποθετούν ήδη σε συντεταγμένες εγγράφου)
|
||||
@msg the Color Picker is not open ο επιλογέας χρώματος δεν είναι ανοιχτός
|
||||
@msg the Simplify slider is not open ο ρυθμιστής Απλοποίησης δεν είναι ανοιχτός
|
||||
@msg the action has no steps η ενέργεια δεν έχει βήματα
|
||||
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer τα επίπεδα του αρχείου δεν διαβάστηκαν ({_1}): εισάγεται ως η σελίδα του, σε ένα επίπεδο
|
||||
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part τα επίπεδα του αρχείου δεν διαβάστηκαν από τα δεδομένα επεξεργασίας του ({_1}): προέρχονται από το τμήμα PDF του
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2016 and column 19.
|
@@ -2925,6 +2925,8 @@
|
||||
Show Note Mostrar nota
|
||||
Show Options Mostrar opciones
|
||||
Show Original Path Mostrar trazado original
|
||||
Open this dialog with these settings next time Abrir este cuadro de diálogo con estos ajustes la próxima vez
|
||||
Auto-Simplify Simplificar automáticamente
|
||||
Color Panel Options Opciones del panel Color
|
||||
Show Package Mostrar paquete
|
||||
Show Panel Mostrar panel
|
||||
@@ -3871,6 +3873,7 @@ axis Both Ambos
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" fondo {other}: transparent, white, black o un color como "#ff8800"
|
||||
@msg bad color {v} color no válido {v}
|
||||
@msg the Color Picker is not open el Selector de color no está abierto
|
||||
@msg the Simplify slider is not open el regulador de Simplificar no está abierto
|
||||
@msg the screen color sampler is already open el muestreador de color de la pantalla ya está abierto
|
||||
@msg this system has no screen color sampler este sistema no tiene muestreador de color de la pantalla
|
||||
@msg bad colour `{hex}` (#rrggbb) color no válido `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1675,6 +1675,8 @@ lighting Light Valo
|
||||
Corner Point Angle Threshold: Kulmapisteiden kulmakynnys:
|
||||
Convert to Straight Lines Muunna suoriksi viivoiksi
|
||||
Show Original Path Näytä alkuperäinen polku
|
||||
Open this dialog with these settings next time Avaa tämä valintaikkuna näillä asetuksilla ensi kerralla
|
||||
Auto-Simplify Yksinkertaista automaattisesti
|
||||
Transparency Options Läpinäkyvyysasetukset
|
||||
Color Mixer (Shift-click the chip) Värisekoitin (Vaihto-napsauta värilaattaa)
|
||||
Fill: click for swatches, Shift-click for the color mixer Täyttö: napsauta avataksesi värimallit, Vaihto-napsauta avataksesi värisekoittimen
|
||||
@@ -5125,6 +5127,7 @@ mark-style Japanese Japanilainen
|
||||
@msg span: "selection" places a linear or radial gradient, not a freeform one span: "selection" sijoittaa lineaarisen tai säteittäisen liukuvärin, ei vapaamuotoista
|
||||
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: "selection" sijoittaa itse liukuvärin: jätä pois `start` ja `end` (ne sijoittavat sen jo asiakirjan koordinaatteihin)
|
||||
@msg the Color Picker is not open värinvalitsin ei ole auki
|
||||
@msg the Simplify slider is not open Yksinkertaista-liukusäädin ei ole auki
|
||||
@msg the action has no steps toiminnossa ei ole vaiheita
|
||||
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer tiedoston tasoja ei luettu ({_1}): se tuodaan sivunaan yhdelle tasolle
|
||||
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part tiedoston tasoja ei luettu sen muokkaustiedoista ({_1}): ne tulevat sen PDF-osasta
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2922,6 +2922,8 @@
|
||||
Show Note Afficher la note
|
||||
Show Options Afficher les options
|
||||
Show Original Path Afficher le tracé d’origine
|
||||
Open this dialog with these settings next time Ouvrir cette boîte de dialogue avec ces réglages la prochaine fois
|
||||
Auto-Simplify Simplifier automatiquement
|
||||
Color Panel Options Options du panneau Couleur
|
||||
Show Package Afficher le dossier d’assemblage
|
||||
Show Panel Afficher le panneau
|
||||
@@ -3868,6 +3870,7 @@ axis Both Les deux
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" arrière-plan {other} : transparent, white, black ou une couleur comme "#ff8800"
|
||||
@msg bad color {v} couleur non valide {v}
|
||||
@msg the Color Picker is not open le Sélecteur de couleurs n’est pas ouvert
|
||||
@msg the Simplify slider is not open le curseur Simplifier n’est pas ouvert
|
||||
@msg the screen color sampler is already open l’outil de prélèvement de couleur à l’écran est déjà ouvert
|
||||
@msg this system has no screen color sampler ce système n’a pas d’outil de prélèvement de couleur à l’écran
|
||||
@msg bad colour `{hex}` (#rrggbb) couleur non valide `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -3543,6 +3543,8 @@ mark-style Japanese Japán
|
||||
Show Note Megjegyzés megjelenítése
|
||||
Show Options Beállítások megjelenítése
|
||||
Show Original Path Eredeti útvonal megjelenítése
|
||||
Open this dialog with these settings next time Legközelebb ez a párbeszédablak nyíljon meg ezekkel a beállításokkal
|
||||
Auto-Simplify Automatikus egyszerűsítés
|
||||
Color Panel Options Szín panel beállításai
|
||||
Show Package Csomag megjelenítése
|
||||
Show Panel Panel megjelenítése
|
||||
@@ -4414,6 +4416,7 @@ imageTrace Outline Körvonal
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" background {other}: transparent, white, black vagy egy szín, például "#ff8800"
|
||||
@msg bad color {v} hibás szín: {v}
|
||||
@msg the Color Picker is not open a Színválasztó nincs megnyitva
|
||||
@msg the Simplify slider is not open az Egyszerűsítés csúszka nincs megnyitva
|
||||
@msg the screen color sampler is already open a képernyős színmintavevő már nyitva van
|
||||
@msg this system has no screen color sampler ezen a rendszeren nincs képernyős színmintavevő
|
||||
@msg bad colour `{hex}` (#rrggbb) hibás szín: `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5052,6 +5052,8 @@ swatch Bright Violet Violet cerah
|
||||
Outline Object Kerangka objek
|
||||
Update Selection Perbarui seleksi
|
||||
Show Original Path Tampilkan jalur asli
|
||||
Open this dialog with these settings next time Buka dialog ini dengan pengaturan ini lain kali
|
||||
Auto-Simplify Sederhanakan otomatis
|
||||
@msg check a layer to export centang lapisan yang akan diekspor
|
||||
@msg select the art to export first pilih dulu karya yang akan diekspor
|
||||
@msg no saved selection recalled tidak ada seleksi tersimpan yang dipanggil
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3771 and column 19.
|
@@ -2958,6 +2958,8 @@ mark-style Japanese Giapponese
|
||||
Show Note Mostra nota
|
||||
Show Options Mostra opzioni
|
||||
Show Original Path Mostra tracciato originale
|
||||
Open this dialog with these settings next time Apri questa finestra di dialogo con queste impostazioni la prossima volta
|
||||
Auto-Simplify Semplifica automaticamente
|
||||
Color Panel Options Opzioni pannello Colore
|
||||
Show Package Mostra pacchetto
|
||||
Show Panel Mostra pannello
|
||||
@@ -3910,6 +3912,7 @@ axis Both Entrambi
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" sfondo {other}: transparent, white, black o un colore come "#ff8800"
|
||||
@msg bad color {v} colore non valido {v}
|
||||
@msg the Color Picker is not open il Selettore colore non è aperto
|
||||
@msg the Simplify slider is not open il cursore Semplifica non è aperto
|
||||
@msg the screen color sampler is already open il campionatore di colore dello schermo è già aperto
|
||||
@msg this system has no screen color sampler questo sistema non ha un campionatore di colore dello schermo
|
||||
@msg bad colour `{hex}` (#rrggbb) colore non valido `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2766,6 +2766,8 @@ mark-style Japanese 日本式
|
||||
Show Note メモを表示
|
||||
Show Options オプションを表示
|
||||
Show Original Path 元のパスを表示
|
||||
Open this dialog with these settings next time 次回はこの設定でこのダイアログを開く
|
||||
Auto-Simplify 自動単純化
|
||||
Color Panel Options カラーパネルオプション
|
||||
Show Package パッケージを表示
|
||||
Show Panel パネルを表示
|
||||
@@ -4225,6 +4227,7 @@ swatch Bright Violet ビビッドバイオレット
|
||||
@msg no blend options ブレンドオプションがありません
|
||||
@msg bad color {v} カラー {v} が正しくありません
|
||||
@msg the Color Picker is not open カラーピッカーが開いていません
|
||||
@msg the Simplify slider is not open 単純化スライダーが開いていません
|
||||
@msg the screen color sampler is already open 画面のカラーサンプラーはすでに開いています
|
||||
@msg this system has no screen color sampler このシステムには画面のカラーサンプラーがありません
|
||||
@msg Corners edits the corners of a path 「コーナー」で編集できるのはパスの角だけです
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -4756,6 +4756,7 @@ imageTrace Outline კონტური
|
||||
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer ფაილის შრეები ვერ წაიკითხა ({_1}): ის გაიხსნება როგორც მისი გვერდი, ერთ შრეში
|
||||
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part ფაილის შრეები რედაქტირების მონაცემებიდან ვერ წაიკითხა ({_1}): ისინი მისი PDF ნაწილიდან აიღება
|
||||
@msg the Color Picker is not open ფერის ამრჩევი გახსნილი არ არის
|
||||
@msg the Simplify slider is not open გამარტივების სლაიდერი გახსნილი არ არის
|
||||
@msg the screen color sampler is already open ეკრანიდან ფერის აღება უკვე გახსნილია
|
||||
@msg this system has no screen color sampler ამ სისტემას ეკრანიდან ფერის აღების საშუალება არ აქვს
|
||||
# Dimension tool and Object › Dimensions. A drafting dimension is ზომა (ზომის დასმა), like a dimension on a technical drawing.
|
||||
@@ -4899,6 +4900,8 @@ imageTrace Outline კონტური
|
||||
Rotate View to Selection ხედის შემობრუნება მონიშნულის მიხედვით
|
||||
Update Selection მონიშნულის განახლება
|
||||
Show Original Path საწყისი ტრაექტორიის ჩვენება
|
||||
Open this dialog with these settings next time შემდეგ ჯერზე ამ დიალოგის ამ პარამეტრებით გახსნა
|
||||
Auto-Simplify ავტომატური გამარტივება
|
||||
Texture Image ტექსტურის გამოსახულება
|
||||
@msg check a layer to export მონიშნეთ საექსპორტო შრე
|
||||
@msg select the art to export first ჯერ მონიშნეთ საექსპორტო ნახატი
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2760,6 +2760,8 @@ mark-style Japanese 일본식
|
||||
Show Note 메모 표시
|
||||
Show Options 옵션 표시
|
||||
Show Original Path 원래 패스 표시
|
||||
Open this dialog with these settings next time 다음에 이 설정으로 이 대화상자 열기
|
||||
Auto-Simplify 자동 단순화
|
||||
Color Panel Options 색상 패널 옵션
|
||||
Show Package 패키지 표시
|
||||
Show Panel 패널 표시
|
||||
@@ -4157,6 +4159,7 @@ theme Appearance 외관
|
||||
@msg no blend options 혼합 옵션이 없습니다
|
||||
@msg bad color {v} 잘못된 색상 {v}
|
||||
@msg the Color Picker is not open 색상 피커가 열려 있지 않습니다
|
||||
@msg the Simplify slider is not open 단순화 슬라이더가 열려 있지 않습니다
|
||||
@msg the screen color sampler is already open 화면 색상 추출기가 이미 열려 있습니다
|
||||
@msg this system has no screen color sampler 이 시스템에는 화면 색상 추출기가 없습니다
|
||||
@msg Corners edits the corners of a path 모퉁이는 패스의 모퉁이를 편집합니다
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -5064,6 +5064,8 @@ swatch Bright Violet Felviolet
|
||||
Outline Object Object omtrekken
|
||||
Update Selection Selectie bijwerken
|
||||
Show Original Path Oorspronkelijk pad tonen
|
||||
Open this dialog with these settings next time Dit dialoogvenster de volgende keer met deze instellingen openen
|
||||
Auto-Simplify Automatisch vereenvoudigen
|
||||
@msg check a layer to export vink een laag aan om te exporteren
|
||||
@msg select the art to export first selecteer eerst de illustratie die je wilt exporteren
|
||||
@msg no saved selection recalled geen opgeslagen selectie opgeroepen
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1632,6 +1632,8 @@ lighting Light Światło
|
||||
Corner Point Angle Threshold: Próg kąta punktów narożnych:
|
||||
Convert to Straight Lines Konwertuj na linie proste
|
||||
Show Original Path Pokaż oryginalną ścieżkę
|
||||
Open this dialog with these settings next time Następnym razem otwórz to okno dialogowe z tymi ustawieniami
|
||||
Auto-Simplify Uprość automatycznie
|
||||
Transparency Options Opcje przezroczystości
|
||||
Color Mixer (Shift-click the chip) Mikser kolorów (kliknij próbkę z Shift)
|
||||
Fill: click for swatches, Shift-click for the color mixer Wypełnienie: kliknij, aby wybrać próbkę, z Shift — mikser kolorów
|
||||
@@ -3995,6 +3997,7 @@ imageTrace Outline Kontur
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" tło {other}: transparent, white, black lub kolor, np. "#ff8800"
|
||||
@msg bad color {v} nieprawidłowy kolor {v}
|
||||
@msg the Color Picker is not open Próbnik kolorów nie jest otwarty
|
||||
@msg the Simplify slider is not open Suwak Uprość nie jest otwarty
|
||||
@msg the screen color sampler is already open narzędzie do pobierania koloru z ekranu jest już otwarte
|
||||
@msg this system has no screen color sampler ten system nie ma narzędzia do pobierania koloru z ekranu
|
||||
@msg bad colour `{hex}` (#rrggbb) nieprawidłowy kolor `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2430,6 +2430,8 @@
|
||||
Show Note Mostrar Nota
|
||||
Show Options Mostrar Opções
|
||||
Show Original Path Mostrar Caminho Original
|
||||
Open this dialog with these settings next time Abrir este diálogo com estas configurações na próxima vez
|
||||
Auto-Simplify Simplificar Automaticamente
|
||||
Show Package Mostrar Pacote
|
||||
Show Panel Mostrar Painel
|
||||
Show Perspective Grid Mostrar Grade de Perspectiva
|
||||
@@ -5068,6 +5070,7 @@ mark-style Japanese Japonesas
|
||||
@msg Unknown dimension mode Modo de cota desconhecido
|
||||
@msg Unknown dimension option {k} Opção de cota desconhecida {k}
|
||||
@msg the Color Picker is not open o Seletor de Cores não está aberto
|
||||
@msg the Simplify slider is not open o controle deslizante Simplificar não está aberto
|
||||
@msg the screen color sampler is already open o coletor de cores da tela já está aberto
|
||||
@msg this system has no screen color sampler este sistema não tem coletor de cores da tela
|
||||
Darkness Escuridão
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2954,6 +2954,8 @@ mark-style Japanese Японский
|
||||
Show Note Показать примечание
|
||||
Show Options Показать параметры
|
||||
Show Original Path Показать исходный контур
|
||||
Open this dialog with these settings next time Открывать это диалоговое окно с этими параметрами в следующий раз
|
||||
Auto-Simplify Автоматическое упрощение
|
||||
Color Panel Options Параметры панели «Цвет»
|
||||
Show Package Показать пакет
|
||||
Show Panel Показать панель
|
||||
@@ -3906,6 +3908,7 @@ axis Both Оба
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" фон {other}: transparent, white, black или цвет, например "#ff8800"
|
||||
@msg bad color {v} недопустимый цвет {v}
|
||||
@msg the Color Picker is not open палитра цветов не открыта
|
||||
@msg the Simplify slider is not open ползунок «Упростить» не открыт
|
||||
@msg the screen color sampler is already open средство выбора цвета с экрана уже открыто
|
||||
@msg this system has no screen color sampler в этой системе нет средства выбора цвета с экрана
|
||||
@msg bad colour `{hex}` (#rrggbb) недопустимый цвет `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -2868,6 +2868,8 @@
|
||||
Show Note Visa anteckning
|
||||
Show Options Visa alternativ
|
||||
Show Original Path Visa ursprunglig bana
|
||||
Open this dialog with these settings next time Öppna den här dialogrutan med de här inställningarna nästa gång
|
||||
Auto-Simplify Förenkla automatiskt
|
||||
Show Package Visa paket
|
||||
Show Panel Visa panel
|
||||
Show Pattern Swatches Visa mönsterprover
|
||||
@@ -5117,6 +5119,7 @@ theme Appearance Utseende
|
||||
@msg span: "selection" places a linear or radial gradient, not a freeform one span: "selection" placerar en linjär eller radiell övertoning, inte en fri
|
||||
@msg span: "selection" places the gradient itself: leave out `start` and `end` (they already place it in document coordinates) span: "selection" placerar själva övertoningen: utelämna `start` och `end` (de placerar den redan i dokumentkoordinater)
|
||||
@msg the Color Picker is not open färgväljaren är inte öppen
|
||||
@msg the Simplify slider is not open Förenkla-reglaget är inte öppet
|
||||
@msg the action has no steps åtgärden har inga steg
|
||||
@msg the file's layers weren't read ({_1}): it comes in as its page, in one layer filens lager lästes inte ({_1}): den hämtas in som en sida, i ett enda lager
|
||||
@msg the file's layers weren't read from its editing data ({_1}): they come from its PDF part filens lager lästes inte från dess redigeringsdata ({_1}): de kommer från dess PDF-del
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 5 and column 25.
|
@@ -2950,6 +2950,8 @@ mark-style Japanese Японський
|
||||
Show Note Показати примітку
|
||||
Show Options Показати параметри
|
||||
Show Original Path Показати початковий контур
|
||||
Open this dialog with these settings next time Наступного разу відкривати це діалогове вікно з цими параметрами
|
||||
Auto-Simplify Автоматичне спрощення
|
||||
Color Panel Options Параметри панелі «Колір»
|
||||
Show Package Показати пакет
|
||||
Show Panel Показати панель
|
||||
@@ -3894,6 +3896,7 @@ axis Both Обидві
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" фон {other}: transparent, white, black або колір, наприклад "#ff8800"
|
||||
@msg bad color {v} неприпустимий колір {v}
|
||||
@msg the Color Picker is not open палітра кольорів не відкрита
|
||||
@msg the Simplify slider is not open повзунок «Спростити» не відкрито
|
||||
@msg the screen color sampler is already open засіб вибору кольору з екрана вже відкритий
|
||||
@msg this system has no screen color sampler у цій системі немає засобу вибору кольору з екрана
|
||||
@msg bad colour `{hex}` (#rrggbb) неприпустимий колір `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 725 and column 40.
|
@@ -3283,6 +3283,8 @@ mark-style Japanese Kiểu Nhật
|
||||
Show Note Hiện ghi chú
|
||||
Show Options Hiện tùy chọn
|
||||
Show Original Path Hiện đường dẫn gốc
|
||||
Open this dialog with these settings next time Lần sau mở hộp thoại này với các cài đặt này
|
||||
Auto-Simplify Tự động đơn giản hóa
|
||||
Color Panel Options Tùy chọn bảng màu
|
||||
Show Package Hiện gói
|
||||
Show Panel Hiện bảng
|
||||
@@ -4062,6 +4064,7 @@ theme Appearance Giao diện
|
||||
@msg background {other}: transparent, white, black or a colour such as "#ff8800" background {other}: transparent, white, black hoặc màu như "#ff8800"
|
||||
@msg bad color {v} màu không hợp lệ {v}
|
||||
@msg the Color Picker is not open Bộ chọn màu chưa được mở
|
||||
@msg the Simplify slider is not open Thanh trượt Đơn giản hóa chưa được mở
|
||||
@msg the screen color sampler is already open công cụ lấy mẫu màu trên màn hình đã được mở
|
||||
@msg this system has no screen color sampler hệ thống này không có công cụ lấy mẫu màu trên màn hình
|
||||
@msg bad colour `{hex}` (#rrggbb) màu không hợp lệ `{hex}` (#rrggbb)
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 3961 and column 10.
|
@@ -2809,6 +2809,8 @@ font-script Japanese 汉字
|
||||
Show Note 显示注解
|
||||
Show Options 显示选项
|
||||
Show Original Path 显示原始路径
|
||||
Open this dialog with these settings next time 下次以这些设置打开此对话框
|
||||
Auto-Simplify 自动简化
|
||||
Color Panel Options 颜色面板选项
|
||||
Show Package 显示打包
|
||||
Show Panel 显示面板
|
||||
@@ -3971,6 +3973,7 @@ imageTrace Outline 轮廓
|
||||
@msg no blend options 没有混合选项
|
||||
@msg bad color {v} 颜色 {v} 无效
|
||||
@msg the Color Picker is not open 拾色器未打开
|
||||
@msg the Simplify slider is not open 简化滑块未打开
|
||||
@msg the screen color sampler is already open 屏幕取色器已打开
|
||||
@msg this system has no screen color sampler 此系统没有屏幕取色器
|
||||
@msg Corners edits the corners of a path 转角用于编辑路径的转角
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 2 and column 73.
|
@@ -1979,6 +1979,7 @@ file-filter Templates 範本
|
||||
@msg no blend options 沒有漸變選項
|
||||
@msg bad color {v} 顏色 {v} 無效
|
||||
@msg the Color Picker is not open 「顏色選擇器」未開啟
|
||||
@msg the Simplify slider is not open 「簡化」滑桿未開啟
|
||||
@msg the screen color sampler is already open 螢幕取色器已開啟
|
||||
@msg this system has no screen color sampler 此系統沒有螢幕取色器
|
||||
@msg Corners edits the corners of a path 「轉角」用於編輯路徑的轉角
|
||||
@@ -3676,6 +3677,8 @@ font-script Japanese 漢字
|
||||
Show Note 顯示註解
|
||||
Show Options 顯示選項
|
||||
Show Original Path 顯示原始路徑
|
||||
Open this dialog with these settings next time 下次以這些設定開啟此對話方塊
|
||||
Auto-Simplify 自動簡化
|
||||
Color Panel Options 顏色面板選項
|
||||
Show Package 顯示封裝
|
||||
Show Panel 顯示面板
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -666,6 +666,18 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[
|
||||
"",
|
||||
"{id?, corners?: [i…]} open Corners for path `id` (default: the selected one) and its corners (anchor indices of the path with its corners uncut, as object.setLiveShape takes them: a rectangle's 0 top-left, 1 top-right, 2 bottom-right, 3 bottom-left; default: the Direct-Selected corners, else every corner) (dialog `corners`: kind (round|invertedRound|chamfer), radius (pt); double-clicking a corner widget opens it too): OK runs object.setLiveShape",
|
||||
),
|
||||
(
|
||||
"ui.simplifyAuto",
|
||||
"Auto-Simplify",
|
||||
"",
|
||||
"{} the open Simplify slider (Object › Path › Simplify opens it: dialog `simplifySlider` with field `level` 0..100, object.path.simplify's level, 50 as it opens, previewed on the canvas until ui.dialog.confirm keeps it as one undo step; after OK in the Simplify dialog with `retain` ticked, the menu item opens the dialog `simplify` with the fields of that OK instead) moves to the level object.path.simplify {auto: true} picks for the selection and previews it → {level}; an error when the slider isn't open",
|
||||
),
|
||||
(
|
||||
"ui.simplifyMoreOptions",
|
||||
"More Options",
|
||||
"",
|
||||
"{} the Simplify dialog (dialog `simplify`: tolerance (pt), angleThreshold, straightLines, showOriginal, retain: ticked, OK makes Object › Path › Simplify open this dialog with these fields next time) takes the open Simplify slider's place, at the slider's tolerance → {tolerance}; an error when the slider isn't open",
|
||||
),
|
||||
(
|
||||
"ui.justification",
|
||||
"Justification…",
|
||||
@@ -1583,6 +1595,8 @@ pub fn run_ui_command(app: &mut VectorcraftApp, id: &str, p: &Value) -> Option<R
|
||||
None => Err("no such user library".into()),
|
||||
},
|
||||
"ui.expandDialog" => crate::dialogs::expand::open(app),
|
||||
"ui.simplifyAuto" => crate::dialogs::simplify_slider::auto(app),
|
||||
"ui.simplifyMoreOptions" => crate::dialogs::simplify_slider::more_options(app),
|
||||
"attributes.openUrl" => crate::panels::attributes::open_url(app, p),
|
||||
"ui.javascriptFilesDialog" => crate::panels::svg_interactivity::files_dialog(app, p),
|
||||
"ui.spotColors" => crate::dialogs::spot_colors::open(app),
|
||||
@@ -2292,6 +2306,7 @@ pub fn enabled(app: &VectorcraftApp, id: &str) -> bool {
|
||||
"ui.expandDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
|
||||
"ui.spotColors" => app.session.active().is_some(),
|
||||
"ui.menuDialog" => app.session.active().is_some_and(|d| !d.selection.is_empty()),
|
||||
"ui.simplifyAuto" | "ui.simplifyMoreOptions" => crate::dialogs::simplify_slider::showing(app),
|
||||
"ui.dxfOptionsDialog" => app.session.active().is_some(),
|
||||
"links.editOriginal" | "links.reveal" => selected_image(app, |im| im.link.is_some()) || selected_placed(app),
|
||||
"ui.placementOptionsDialog" => selected_image(app, |_| true) || selected_placed(app),
|
||||
@@ -3369,10 +3384,10 @@ fn menu_dialog(app: &VectorcraftApp, id: &str) -> Option<(&'static str, Value)>
|
||||
// Offset Path opens on the values it was last confirmed with.
|
||||
"object.path.offsetPath" if app.ui.offset_path.is_object() => ("offsetPath", app.ui.offset_path.clone()),
|
||||
"object.path.offsetPath" => ("offsetPath", json!({"offset": "10 pt", "joins": "miter", "miterLimit": 4})),
|
||||
"object.path.simplify" => (
|
||||
"simplify",
|
||||
json!({"tolerance": "1 pt", "angleThreshold": crate::dialogs::path_ops::ANGLE_THRESHOLD, "straightLines": false, "showOriginal": false, "preview": true}),
|
||||
),
|
||||
// Simplify opens its slider, or the dialog on the fields it was last confirmed with when
|
||||
// "Open this dialog with these settings next time" was ticked.
|
||||
"object.path.simplify" if app.ui.simplify.is_object() => ("simplify", app.ui.simplify.clone()),
|
||||
"object.path.simplify" => (crate::dialogs::simplify_slider::KIND, crate::dialogs::simplify_slider::fields()),
|
||||
"object.path.splitIntoGrid" => {
|
||||
// The grid starts at the size of the selection.
|
||||
let size =
|
||||
|
||||
@@ -488,6 +488,11 @@ pub struct UiState {
|
||||
/// dialog opens on them (#1152).
|
||||
#[serde(default)]
|
||||
pub offset_path: Value,
|
||||
/// The Simplify dialog's fields confirmed last with "Open this dialog with these settings next
|
||||
/// time" ticked (null: not ticked): Object › Path › Simplify opens the dialog on them instead
|
||||
/// of the Simplify slider.
|
||||
#[serde(default)]
|
||||
pub simplify: Value,
|
||||
/// The Move dialog's checkboxes as it was last confirmed with (copy, objects, preview; null:
|
||||
/// never used): it opens with them.
|
||||
#[serde(default)]
|
||||
@@ -823,6 +828,7 @@ impl Default for UiState {
|
||||
dxf_options: Value::Null,
|
||||
eps_options: Value::Null,
|
||||
offset_path: Value::Null,
|
||||
simplify: Value::Null,
|
||||
move_options: Value::Null,
|
||||
dxf_import: Value::Null,
|
||||
home: None,
|
||||
|
||||
+17
@@ -2024,6 +2024,23 @@ the path it had begun while that stays selected. Its reply's `tool` is the tool
|
||||
{"name":"run_command","arguments":{"command":"path.moveAnchors","params":{"dx":0,"dy":40}}}
|
||||
```
|
||||
|
||||
## Simplify
|
||||
|
||||
`object.path.simplify` takes the amount as `tolerance` (points, 1 by default) or as the Simplify slider's `level`, a whole
|
||||
number from 0 (the fewest anchor points) to 100 (the closest fit). Level 50 is a tolerance of 1 pt, and every 10 levels
|
||||
halve or double it, so level 0 is 32 pt and level 100 is 1/32 pt. `auto: true` (Auto-Simplify) uses the lowest level
|
||||
whose tolerance is at most 1/400 of the selection's longer side, or level 100 when that side is under 12.5 pt. With
|
||||
`level` or `auto`, the reply also has the `level` used:
|
||||
|
||||
```json
|
||||
{"name":"run_command","arguments":{"command":"object.path.simplify","params":{"auto":true}}}
|
||||
```
|
||||
|
||||
In the desktop app, Object › Path › Simplify opens the slider (`ui.menuDialog {command: "object.path.simplify"}`,
|
||||
dialog `simplifySlider` with field `level`) and previews the result until `ui.dialog.confirm`. `ui.simplifyAuto` moves
|
||||
the open slider to the Auto-Simplify level, and `ui.simplifyMoreOptions` opens the Simplify dialog at the slider's
|
||||
tolerance. When OK confirms the dialog with `retain` ticked, the menu item opens the dialog on those settings next time.
|
||||
|
||||
## Editing a path with the Curvature tool
|
||||
|
||||
The Curvature tool edits any selected path, whatever drew it, keeping its shape except where edited (#798). Each edit
|
||||
|
||||
File diff suppressed because one or more lines are too long
+1
-1
@@ -65,7 +65,7 @@ tools, never checked side by side) isn't captured row by row.
|
||||
| Join tool scrubbing, Average, Join | done | Join merges the direct-selected anchors lying on one another along a path, as after Average, into one (#1533) |
|
||||
| Average and Join in one step, with a corner or smooth choice for the joined anchor | partial | `path.join` accepts `to: corner|smooth` on the merged anchor; Average and Join are still separate commands; [gap 1](gaps.md) |
|
||||
| Smooth tool smooths only the stretch of path under the drag | done | #1512 |
|
||||
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; slider, auto-simplify and Retain my latest settings remain missing |
|
||||
| Simplify (slider, auto, corner angle threshold) | partial | Tolerance, default-on Preview and original/new point counts (#1272); Corner Point Angle Threshold, Convert to Straight Lines and Show Original Path; the slider under the selection with Auto-Simplify and More Options, and the dialog's option to open it directly with its last settings; the slider opens at 1 pt rather than at the Auto-Simplify level, and simplifying only a Direct Selection part of a path remains missing |
|
||||
|
||||
## Direct Selection, anchors and handles
|
||||
|
||||
|
||||
Reference in New Issue
Block a user