diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 6c606d11e..dd42fad73 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -20,8 +20,10 @@ pub mod inks; mod inline; pub mod intertwine; pub mod shared; +mod symbol_registration; pub use inline::{SYMBOL_HALF, SYMBOL_SIZES}; pub use shared::Shared; +pub use symbol_registration::{SYMBOL_REGISTRATION, SymbolRegistration}; pub mod links; pub mod live; pub mod marks; @@ -1518,6 +1520,30 @@ impl Document { } (2..).map(|i| format!("{base} {i}")).find(|n| !taken(n)).unwrap_or_else(|| base.to_string()) } + + /// Symbol Options › Registration for `name` (default: centre). + pub fn symbol_registration(&self, name: &str) -> SymbolRegistration { + self.unknown + .get(SYMBOL_REGISTRATION) + .and_then(|m| m.get(name)) + .and_then(|v| v.as_u64()) + .and_then(SymbolRegistration::from_u64) + .unwrap_or_default() + } + + /// Set or clear (when centre) the registration point stored for `name`. + pub fn set_symbol_registration(&mut self, name: &str, reg: SymbolRegistration) { + let m = self.unknown.entry(SYMBOL_REGISTRATION.to_string()).or_insert_with(|| serde_json::json!({})); + let Some(o) = m.as_object_mut() else { return }; + if reg.is_center() { + o.remove(name); + if o.is_empty() { + self.unknown.remove(SYMBOL_REGISTRATION); + } + } else { + o.insert(name.into(), serde_json::json!(reg.0)); + } + } } fn default_graphic_styles() -> Vec { diff --git a/crates/doc/src/symbol_registration.rs b/crates/doc/src/symbol_registration.rs new file mode 100644 index 000000000..2d4dcb000 --- /dev/null +++ b/crates/doc/src/symbol_registration.rs @@ -0,0 +1,36 @@ +//! Symbol Options › Registration: the 9-point grid (0 = top-left … 4 = centre … 8 = bottom-right) +//! that `symbol.place` uses as the point that lands on the click. + +use vectorcraft_geom::Vec2; + +/// Document unknown key: `{ symbolName: 0..8 }` for each symbol's registration point. +pub const SYMBOL_REGISTRATION: &str = "symbolRegistration"; + +/// Index on the 9-point grid (row-major, 4 = centre). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct SymbolRegistration(pub u8); + +impl Default for SymbolRegistration { + fn default() -> Self { + Self(4) + } +} + +impl SymbolRegistration { + pub const CENTER: Self = Self(4); + + pub fn from_u64(v: u64) -> Option { + (v <= 8).then_some(Self(v as u8)) + } + + pub fn is_center(self) -> bool { + self.0 == 4 + } + + /// Offset of this registration point from the symbol's centre, in natural-size units + /// (right +, down +). + pub fn offset_from_center(self, w: f64, h: f64) -> Vec2 { + let (col, row) = (self.0 % 3, self.0 / 3); + Vec2::new((f64::from(col) - 1.0) * (w * 0.5), (f64::from(row) - 1.0) * (h * 0.5)) + } +} diff --git a/crates/engine/src/cmd/brushsym.rs b/crates/engine/src/cmd/brushsym.rs index df6358cfe..a1d3e09b6 100644 --- a/crates/engine/src/cmd/brushsym.rs +++ b/crates/engine/src/cmd/brushsym.rs @@ -11,7 +11,7 @@ use std::sync::Arc; use serde_json::{Value, json}; use vectorcraft_brush::{self as brush, Brush}; use vectorcraft_color::{Color, Paint}; -use vectorcraft_doc::{AppearanceItem, Document, FillLayer, Node, NodeId, NodeKind, Symbol}; +use vectorcraft_doc::{AppearanceItem, Document, FillLayer, Node, NodeId, NodeKind, Symbol, SymbolRegistration}; use vectorcraft_geom::{Affine, Point, Vec2}; use super::edit::selected_roots; @@ -95,13 +95,13 @@ pub fn specs() -> Vec { brush_freehand ), // ----- symbols ----- - cmd!(query "symbol.list", "Symbols", [], None, "{} → {symbols: [{name, instances, size: [w,h]}], current}", has_doc, symbol_list), + cmd!(query "symbol.list", "Symbols", [], None, "{} → {symbols: [{name, instances, size: [w,h], registration: 0..8}], current}", has_doc, symbol_list), cmd!( "symbol.new", "New Symbol…", [], None, - "{name?, ids?|id?} make a symbol from the selection and replace it with an instance → {name, id}", + "{name?, ids?|id?, registration?: 0..8 (9-point grid, 4 = centre; Symbol Options › Registration)} make a symbol from the selection and replace it with an instance → {name, id, registration}", has_selection, symbol_new ), @@ -110,7 +110,7 @@ pub fn specs() -> Vec { "Place Symbol Instance", [], None, - "{name?, x?, y?} place an instance centred at (x, y) (default: artboard centre; name default: current) → {id}", + "{name?, x?, y?} place an instance so its registration point is at (x, y) (default: artboard centre; name default: current) → {id}", has_doc, symbol_place ), @@ -511,11 +511,19 @@ fn set_natural_size(d: &mut Document, name: &str, size: Option<(f64, f64)>) { } } -/// Instance transform for a symbol of natural size `(w, h)` centred at `c`. -fn place_xf(c: Point, (w, h): (f64, f64)) -> Affine { +/// Instance transform for a symbol of natural size `(w, h)` whose registration point is at `at`. +fn place_xf(at: Point, (w, h): (f64, f64), reg: SymbolRegistration) -> Affine { + let c = at - reg.offset_from_center(w, h); Affine::translate(c.to_vec2()) * Affine::scale_non_uniform(w / (2.0 * HALF), h / (2.0 * HALF)) } +fn registration_param(p: &Value, cmd: &str) -> Result { + match p.get("registration") { + None => Ok(SymbolRegistration::CENTER), + Some(v) => v.as_u64().and_then(SymbolRegistration::from_u64).ok_or_else(|| bad(cmd, "registration must be 0..8")), + } +} + /// Normalise art for a symbol definition: returns (art, natural size, centre in the document). fn normalize(mut art: Node) -> Result<(Node, (f64, f64), Point)> { let b = art.geometric_bounds().ok_or_else(|| EngineError::Other("the art has no bounds".into()))?; @@ -553,7 +561,12 @@ fn symbol_list(s: &mut Session, _: &Value) -> Result { .iter() .map(|sym| { let (w, h) = natural_size(d, &sym.name); - json!({"name": sym.name, "instances": instances_of(d, Some(&sym.name)).len(), "size": [w, h]}) + json!({ + "name": sym.name, + "instances": instances_of(d, Some(&sym.name)).len(), + "size": [w, h], + "registration": d.symbol_registration(&sym.name).0, + }) }) .collect(); Ok(json!({ "symbols": list, "current": current_symbol(d).or_else(|| symbol_names(d).into_iter().next()) })) @@ -580,16 +593,20 @@ fn symbol_new(s: &mut Session, p: &Value) -> Result { let art = selection_art(s, p)?.ok_or_else(|| EngineError::Other("nothing selected".into()))?; let (art, size, c) = normalize(art)?; let name = s.doc()?.doc.unique_symbol_name(str_param(p, "name").filter(|n| !n.trim().is_empty()).unwrap_or("New Symbol")); + let reg = registration_param(p, "symbol.new")?; + // Keep the art where it was: the geometric centre stays at `c`, whatever the registration is. + let place = place_xf(c + reg.offset_from_center(size.0, size.1), size, reg); let id = s.edit("New Symbol", |d, sel| { let art = d.reid(&art); d.symbols.push(Symbol { name: name.clone(), art: Arc::new(art) }); set_natural_size(d, &name, Some(size)); + d.set_symbol_registration(&name, reg); d.unknown.insert(CURRENT_SYMBOL.into(), json!(name)); - let id = replace_with_instance(d, &ids, &name, place_xf(c, size))?; + let id = replace_with_instance(d, &ids, &name, place)?; sel.set([id]); Ok(id) })?; - Ok(json!({ "name": name, "id": id.0 })) + Ok(json!({ "name": name, "id": id.0, "registration": reg.0 })) } fn resolve_symbol(d: &Document, p: &Value, cmd: &str) -> Result { @@ -606,12 +623,13 @@ fn symbol_place(s: &mut Session, p: &Value) -> Result { let d = &s.doc()?.doc; let name = resolve_symbol(d, p, "symbol.place")?; let centre = d.artboards.first().map(|a| a.rect.center()).unwrap_or(Point::ZERO); - let c = Point::new(f64_or(p, "x", centre.x), f64_or(p, "y", centre.y)); + let at = Point::new(f64_or(p, "x", centre.x), f64_or(p, "y", centre.y)); let size = natural_size(d, &name); + let reg = d.symbol_registration(&name); let parent = s.doc()?.target_parent()?; let id = s.edit("Place Symbol Instance", |d, sel| { let id = d.alloc_id(); - d.insert(parent, usize::MAX, Node::new(id, NodeKind::SymbolInstance { symbol: name.clone(), xf: place_xf(c, size) }))?; + d.insert(parent, usize::MAX, Node::new(id, NodeKind::SymbolInstance { symbol: name.clone(), xf: place_xf(at, size, reg) }))?; sel.set([id]); Ok(id) })?; @@ -720,7 +738,8 @@ fn symbol_update(s: &mut Session, p: &Value) -> Result { *xf *= fix; } } - let id = replace_with_instance(d, &ids, &name, place_xf(c, size))?; + let reg = d.symbol_registration(&name); + let id = replace_with_instance(d, &ids, &name, place_xf(c + reg.offset_from_center(size.0, size.1), size, reg))?; sel.set([id]); Ok(id) })?; @@ -743,6 +762,7 @@ fn symbol_delete(s: &mut Session, p: &Value) -> Result { } d.symbols.retain(|x| x.name != name); set_natural_size(d, &name, None); + d.set_symbol_registration(&name, SymbolRegistration::CENTER); if d.unknown.get(CURRENT_SYMBOL).and_then(Value::as_str) == Some(name.as_str()) { d.unknown.remove(CURRENT_SYMBOL); } @@ -758,10 +778,12 @@ fn symbol_duplicate(s: &mut Session, p: &Value) -> Result { let sym = d.symbols.iter().find(|x| x.name == name).cloned().ok_or_else(|| bad(C, format!("no symbol named `{name}`")))?; let new = d.unique_symbol_name(str_param(p, "newName").unwrap_or(&format!("{name} copy"))); let size = natural_size(d, &name); + let reg = d.symbol_registration(&name); s.edit("Duplicate Symbol", |d, _| { let art = d.reid(&sym.art); d.symbols.push(Symbol { name: new.clone(), art: Arc::new(art) }); set_natural_size(d, &new, Some(size)); + d.set_symbol_registration(&new, reg); Ok(()) })?; Ok(json!({ "name": new })) @@ -882,13 +904,14 @@ fn symbol_spray(s: &mut Session, p: &Value) -> Result { placed.push(c); } let count = placed.len(); + let reg = s.doc()?.doc.symbol_registration(&name); let id = s.edit("Symbol Sprayer", |d, sel| { let mut nodes = vec![]; for (i, c) in placed.iter().enumerate() { let (_, _, js) = jitter(i + 7, *c); let k = scale * (1.0 + js * 0.1); let id = d.alloc_id(); - nodes.push(Node::new(id, NodeKind::SymbolInstance { symbol: name.clone(), xf: place_xf(*c, size) * Affine::scale(k) })); + nodes.push(Node::new(id, NodeKind::SymbolInstance { symbol: name.clone(), xf: place_xf(*c, size, reg) * Affine::scale(k) })); } let gid = match set { Some(g) => { diff --git a/crates/engine/src/tests_brushsym.rs b/crates/engine/src/tests_brushsym.rs index 818bda51c..b6f3efdb1 100644 --- a/crates/engine/src/tests_brushsym.rs +++ b/crates/engine/src/tests_brushsym.rs @@ -581,6 +581,7 @@ fn brushes_and_symbols_survive_vectorcraft_round_trip() { assert_eq!(vectorcraft_brush::find(&back, "Mine").map(|b| b.kind.type_id()), Some("calligraphic")); assert_eq!(back.symbols, doc.symbols); assert_eq!(back.unknown.get("symbolSizes"), doc.unknown.get("symbolSizes")); + assert_eq!(back.unknown.get(vectorcraft_doc::SYMBOL_REGISTRATION), doc.unknown.get(vectorcraft_doc::SYMBOL_REGISTRATION)); let bl = back.node(l).unwrap(); assert_eq!(bl.appearance.stroke().unwrap().brush.as_deref(), Some("Mine")); // A reloaded document places instances at the natural size. @@ -590,3 +591,19 @@ fn brushes_and_symbols_survive_vectorcraft_round_trip() { let b = s2.doc().unwrap().doc.node(NodeId(r["id"].as_u64().unwrap())).unwrap().geometric_bounds().unwrap(); assert!(close(b, Rect::new(60.0, 80.0, 140.0, 120.0), 1e-9)); } + +/// Symbol Options › Registration: place puts the chosen 9-point grid point on (x, y). +#[test] +fn symbol_registration_places_the_chosen_point_on_xy() { + let mut s = session(); + rect(&mut s, 0.0, 0.0, 80.0, 40.0); + let r = s.execute("symbol.new", &json!({"name": "Corner", "registration": 0})).unwrap(); + assert_eq!(r["registration"], 0); + assert_eq!(s.execute("symbol.list", &json!({})).unwrap()["symbols"][0]["registration"], 0); + // Creating kept the art where it was (top-left still at the origin of the selection). + let inst = NodeId(r["id"].as_u64().unwrap()); + assert!(close(bounds(&s, inst), Rect::new(0.0, 0.0, 80.0, 40.0), 1e-9), "{:?}", bounds(&s, inst)); + let p = NodeId(s.execute("symbol.place", &json!({"name": "Corner", "x": 300, "y": 200})).unwrap()["id"].as_u64().unwrap()); + assert!(close(bounds(&s, p), Rect::new(300.0, 200.0, 380.0, 240.0), 1e-9), "{:?}", bounds(&s, p)); + assert!(s.execute("symbol.new", &json!({"registration": 9})).is_err()); +} diff --git a/crates/ui-egui/src/dialogs/symbol_options.rs b/crates/ui-egui/src/dialogs/symbol_options.rs index c30dc66f0..f5b1f8a08 100644 --- a/crates/ui-egui/src/dialogs/symbol_options.rs +++ b/crates/ui-egui/src/dialogs/symbol_options.rs @@ -1,7 +1,7 @@ -//! Symbol Options: the name of the symbol New Symbol makes from the selection (`symbol.new`), asked -//! before the symbol is made, as the Symbols panel's New Symbol button and menu item do. +//! Symbol Options: the name and registration point of the symbol New Symbol makes from the +//! selection (`symbol.new`), as the Symbols panel's New Symbol button and menu item do. //! -//! Fields: `name`. +//! Fields: `name`, `registration` (0..8, the 9-point grid; 4 = centre). use serde_json::{Value, json}; @@ -9,6 +9,7 @@ use super::swatch_options::{grid, label}; use super::{DialogSpec, form}; use crate::VectorcraftApp; use crate::state::Dialog; +use crate::widgets; /// The dialog kind of Symbol Options. pub const KIND: &str = "symbolOptions"; @@ -22,7 +23,7 @@ pub fn open(app: &mut VectorcraftApp) -> Result { return Err("nothing selected".into()); } let name = st.doc.unique_symbol_name("New Symbol"); - app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name }))); + app.ui.dialog = Some(Dialog::new(KIND, json!({ "name": name, "registration": 4 }))); Ok(Value::Null) } @@ -31,13 +32,25 @@ fn body(_: &mut VectorcraftApp, ui: &mut egui::Ui, d: &mut Dialog) -> bool { label(ui, tl!("Name:")); form::text(ui, d, "name", 190.0); ui.end_row(); + label(ui, tl!("Registration:")); + let cur = d.fields.get("registration").and_then(Value::as_u64).unwrap_or(4) as usize; + if let Some(i) = widgets::reference_point(ui, cur) { + d.fields.insert("registration".into(), json!(i)); + } + ui.end_row(); }); false } -/// OK: make the symbol from the selection with the name typed. +/// OK: make the symbol from the selection with the name and registration chosen. fn confirm(app: &mut VectorcraftApp, d: &Dialog) -> Result { - let r = app.run("symbol.new", json!({ "name": d.str("name") })); + let r = app.run( + "symbol.new", + json!({ + "name": d.str("name"), + "registration": d.fields.get("registration").and_then(Value::as_u64).unwrap_or(4), + }), + ); if r.is_ok() { app.ui.dialog = None; } diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 667b56567..dacc7a6f8 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -4877,6 +4877,22 @@ theme Appearance Външен вид @msg span: "selection" places a gradient: give `gradient` span: "selection" поставя градиент: задайте `gradient` @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` (те вече го поставят в координатите на документа) + Make Image Dynamic Динамично изображение + Import Data Sets… Импортиране на набори от данни… + Export Data Sets… Експортиране на набори от данни… + Make Graph Dynamic Динамична диаграма + Batch… Пакет… + Source Folder: Изходна папка: + Destination Folder: Целева папка: + Overrides Заместване + Uses every data set of the open document. Използва всеки набор от данни на отворения документ. +@msg Batch: {count} processed Пакет: обработени {count} +@msg Batch: {count} processed, {failed} failed Пакет: обработени {count}, неуспешни {failed} +@msg choose a destination folder изберете целева папка +@msg choose a source folder изберете изходна папка +@msg open a document with data sets отворете документ с набори от данни +@msg the action has no steps действието няма стъпки + Registration: Регистрация: # Layers panel filters (Filter Layers Panel Rows) and Color Panel Options By Layer По слой By Object По обект diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index db1e557fd..825492022 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4858,9 +4858,9 @@ mark-style Japanese Japonské Softness Měkkost Ambient Light Okolní světlo Light behind the object Světlo za objektem + Registration: Registrace: # Dimension tool (linked dimension annotations) Display Zobrazení - # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Rozměr Drawing : real size — 1:10 labels 1 unit on the page as 10. Výkres : skutečná velikost — 1:10 označí 1 jednotku na stránce jako 10. diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index fab4e7659..3b624bebd 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4391,7 +4391,7 @@ theme Appearance Erscheinungsbild Softness Weichheit Ambient Light Umgebungslicht Light behind the object Licht hinter dem Objekt - + Registration: Registrierung: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Bemaßung Drawing : real size — 1:10 labels 1 unit on the page as 10. Zeichnung : reale Größe — 1:10 beschriftet 1 Einheit auf der Seite als 10. diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index fe33bd84c..708a720f4 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4799,6 +4799,7 @@ mark-style Japanese Japonesas Softness Suavidad Ambient Light Luz ambiental Light behind the object Luz detrás del objeto + Registration: Registro: # Dimension tool (linked dimension annotations) Create Dimension Crear cota Dimension Tool Herramienta Cota @@ -4808,7 +4809,6 @@ mark-style Japanese Japonesas Precision: Precisión: Update Dimensions Actualizar cotas Display Visualización - # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Cota Drawing : real size — 1:10 labels 1 unit on the page as 10. Dibujo : tamaño real — 1:10 rotula 1 unidad de la página como 10. diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 53e4b0c44..38892433e 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4858,7 +4858,7 @@ theme Appearance Apparence Softness Douceur Ambient Light Lumière ambiante Light behind the object Lumière derrière l'objet - + Registration: Repère d'alignement : # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Cote Drawing : real size — 1:10 labels 1 unit on the page as 10. Dessin : taille réelle — 1:10 étiquette 1 unité de la page comme 10. diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 5b6bc4443..37f949662 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4825,7 +4825,7 @@ imageTrace Outline Körvonal Softness Lágyság Ambient Light Környezeti fény Light behind the object Fény az objektum mögött - + Registration: Regisztráció: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Méret Drawing : real size — 1:10 labels 1 unit on the page as 10. Rajz : valós méret — 1:10 esetén az oldalon 1 egység 10-ként jelenik meg. diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 9e826d931..659a3e03a 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4431,7 +4431,7 @@ lighting Light Luce Softness Morbidezza Ambient Light Luce ambientale Light behind the object Luce dietro l'oggetto - + Registration: Registrazione: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Quota Drawing : real size — 1:10 labels 1 unit on the page as 10. Disegno : dimensione reale — 1:10 indica 1 unità sulla pagina come 10. diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 04864c5b4..7c7cf4088 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4425,6 +4425,7 @@ lighting Light ライト Softness 柔らかさ Ambient Light 環境光 Light behind the object オブジェクトの背後のライト + Registration: 基準点: # Dimension tool (linked dimension annotations) Create Dimension 寸法を作成 Dimension Tool 寸法ツール @@ -4434,7 +4435,6 @@ lighting Light ライト Precision: 精度: Update Dimensions 寸法を更新 Display 表示 - # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension 寸法 Drawing : real size — 1:10 labels 1 unit on the page as 10. 図面 : 実寸 — 1:10 では、ページ上の 1 単位を 10 と表示します。 diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index baf360047..9afafbee1 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4395,7 +4395,7 @@ lighting Light 광원 Softness 부드러움 Ambient Light 주변광 Light behind the object 개체 뒤의 조명 - + Registration: 기준점: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension 치수 Drawing : real size — 1:10 labels 1 unit on the page as 10. 도면 : 실제 크기 — 1:10은 페이지의 1단위를 10으로 표시합니다. diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 4d1d2fdf1..3b130e74a 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4688,6 +4688,7 @@ mark-style Japanese Japońskie Softness Miękkość Ambient Light Światło otoczenia Light behind the object Światło za obiektem + Registration: Punkt rejestracji: Legacy (version 7) Starsza (wersja 7) Transparent Images: Przezroczyste obrazy: Flatten Spłaszcz diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index b8ed4f1cf..288264d57 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3800,6 +3800,7 @@ axis Both Ambos Softness Suavidade Ambient Light Luz ambiente Light behind the object Luz atrás do objeto + Registration: Registro: # Dimension tool (linked dimension annotations) Dimension Tool Ferramenta Cota Create Dimension Criar Cota diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 832607f8e..7c2fde0f0 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4427,7 +4427,7 @@ lighting Light Свет Softness Мягкость Ambient Light Окружающий свет Light behind the object Свет позади объекта - + Registration: Регистрация: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Размер Drawing : real size — 1:10 labels 1 unit on the page as 10. Чертёж : реальный размер — при 1:10 1 единица на странице подписывается как 10. diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 8beba6473..165c2cafc 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4411,7 +4411,7 @@ lighting Light Світло Softness М'якість Ambient Light Навколишнє світло Light behind the object Світло позаду об'єкта - + Registration: Реєстрація: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Розмір Drawing : real size — 1:10 labels 1 unit on the page as 10. Креслення : реальний розмір — при 1:10 1 одиниця на сторінці підписується як 10. diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index e7139df4b..2f5948b58 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4413,6 +4413,7 @@ theme Appearance Giao diện Softness Độ mềm Ambient Light Ánh sáng môi trường Light behind the object Ánh sáng phía sau đối tượng + Registration: Điểm đăng ký: # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension Kích thước Drawing : real size — 1:10 labels 1 unit on the page as 10. Bản vẽ : kích thước thực — 1:10 ghi 1 đơn vị trên trang thành 10. diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 3d28d1e97..414577bfb 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -3999,6 +3999,7 @@ lighting Light 光源 Softness 柔和度 Ambient Light 环境光 Light behind the object 光源位于对象后方 + Registration: 对齐点: # Dimension tool (linked dimension annotations) Create Dimension 创建尺寸标注 Dimension Tool 尺寸标注工具 @@ -4008,7 +4009,6 @@ lighting Light 光源 Precision: 精度: Update Dimensions 更新尺寸标注 Display 显示 - # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension 尺寸 Drawing : real size — 1:10 labels 1 unit on the page as 10. 图纸 : 实际尺寸 — 1:10 会将页面上的 1 个单位标注为 10。 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 8cf5dbd58..7a0d524a9 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -3784,6 +3784,7 @@ lighting Light 光源 Softness 柔和度 Ambient Light 環境光 Light behind the object 光源位於物件後方 + Registration: 對齊點: # Dimension tool (linked dimension annotations) Create Dimension 建立尺寸標註 Dimension Tool 尺寸標註工具 @@ -3793,7 +3794,6 @@ lighting Light 光源 Precision: 精確度: Update Dimensions 更新尺寸標註 Display 顯示 - # Dimension tool and Object › Dimensions (dimension groups, their dialog and messages). Dimension 尺寸 Drawing : real size — 1:10 labels 1 unit on the page as 10. 圖紙 : 實際尺寸 — 1:10 會將頁面上的 1 個單位標示為 10。 diff --git a/crates/ui-egui/src/menus.rs b/crates/ui-egui/src/menus.rs index 8b897e42a..51d4683c6 100644 --- a/crates/ui-egui/src/menus.rs +++ b/crates/ui-egui/src/menus.rs @@ -449,7 +449,7 @@ pub const UI_COMMANDS: &[(&str, &str, &str, &str)] = &[ "ui.symbolOptions", "Symbol Options…", "", - "{} open Symbol Options for a new symbol made from the selection, what the Symbols panel's New Symbol opens (dialog `symbolOptions`, field `name`, default the first free \"New Symbol\"/\"New Symbol N\"); OK runs symbol.new {name}", + "{} open Symbol Options for a new symbol made from the selection, what the Symbols panel's New Symbol opens (dialog `symbolOptions`, fields `name` and `registration` 0..8 on the 9-point grid, default centre; default name the first free \"New Symbol\"/\"New Symbol N\"); OK runs symbol.new {name, registration}", ), ( "tool.options", diff --git a/docs/gaps.md b/docs/gaps.md index 1596cb26b..d8a0487c5 100644 --- a/docs/gaps.md +++ b/docs/gaps.md @@ -25,7 +25,7 @@ stage ([ROADMAP.md](../ROADMAP.md)). | 8 | [G2 Photoshop-style raster effects and the Effect Gallery](#g2-photoshop-style-raster-effects-and-the-effect-gallery) | 23–39 | no | [effects-parity.md](effects-parity.md) | | 9 | [G13 Illustrator 2026 (30.x) additions](#g13-illustrator-2026-30x-additions) | 15–25 | no | [target-app-parity.md](target-app-parity.md) | | 10 | [G14 Pen hardware beyond Windows pressure](#g14-pen-hardware-beyond-windows-pressure) | 8–16 | macOS pressure | [hardware-parity.md](hardware-parity.md) | -| 11 | [G5 Brushes, symbols and libraries](#g5-brushes-symbols-and-libraries) | 17–28 | no | [target-app-parity.md](target-app-parity.md) | +| 11 | [G5 Brushes, symbols and libraries](#g5-brushes-symbols-and-libraries) | 16–27 | no | [target-app-parity.md](target-app-parity.md) | | 12 | [G3 3D and Materials](#g3-3d-and-materials) | 25–45 | no | [effects-parity.md](effects-parity.md) | | 13 | [G7 Views and windows](#g7-views-and-windows) | 15–25 | no | [ui-parity.md](ui-parity.md) | | 14 | [G6 Automation and scripting](#g6-automation-and-scripting) | 8–14 | no | [target-app-parity.md](target-app-parity.md) | @@ -240,8 +240,9 @@ from G14 and the budget run of G15: **~170–270 h** one agent, ~50–80 h wall - **Missing:** the Art brush's Overlap, auto-generated Pattern brush corners, new tile art in Pattern Brush Options, the Key Color eyedropper; original brush, symbol and graphic-style libraries generated in code (never - Adobe's); dynamic symbols; Start Global Edit. -- **Estimate:** 17–28 h. + Adobe's); dynamic symbols; Start Global Edit. Symbol Options › Registration (9-point) is stored and + used by `symbol.place`; export type and 9-slice guides still missing. +- **Estimate:** 16–27 h. ## G3 3D and Materials diff --git a/docs/ui-parity.md b/docs/ui-parity.md index 179f8ed62..d457e24d6 100644 --- a/docs/ui-parity.md +++ b/docs/ui-parity.md @@ -201,7 +201,7 @@ panel-menu commands such as Break Link to Symbol), so they can be given shortcut | Pathfinder shape results keep their source names and SVG IDs | done | #1293; Minus Front keeps the back name, Minus Back the front name; Unite/Intersect/Exclude use the backmost named input; compound output carries it once; geometry and paint unchanged | | Attributes fill-rule controls with a compound member selected | done | Edits the compound's rule; direction still edits only the member; rendered-hole and Undo/Redo regression tests (#1197) | | Make Compound Path on a group (such as outlined type) keeps its backmost path's paint; compound paths joined into one keep their holes | done | #1537 | -| Symbols panel: New Symbol opens Symbol Options (Name) before making the symbol; Alt-click skips it | partial | #1560 (#1566); the dialog has Name only: export type, static or dynamic, registration and 9-slice settings missing | +| Symbols panel: New Symbol opens Symbol Options (Name) before making the symbol; Alt-click skips it | partial | #1560 (#1566); Registration 9-point grid in Symbol Options (`symbol.new` / `symbol.place`); export type, static or dynamic and 9-slice settings still missing | | Pathfinder Trim, Merge and Crop remove strokes; Merge joins same-fill objects whatever their strokes | done | #1570 (#1572) | | Pathfinder panel on one group selected alone; a greyed-out button says what it needs | partial | #1450; the group's members are combined; one plain path selected alone stays disabled (the public documentation doesn't say what the operations do to a single path) | | Pathfinder Divide: an open cutting line splits a filled path | done | #1307; straight, crossed, bent and curved cutters, holes and frontmost appearance regression-tested; native automation: independent piece movement, one Undo/Redo, saved-document round trip and window capture; existing bounded-map fallback remains |