Merge pull request #1959 from Messhias/feat/symbol-registration

G5: Symbol Options Registration 9-point grid
This commit is contained in:
BFlat
2026-10-16 05:12:19 +07:00
committed by GitHub
24 changed files with 171 additions and 36 deletions
+26
View File
@@ -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<GraphicStyle> {
+36
View File
@@ -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<Self> {
(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))
}
}
+36 -13
View File
@@ -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<CommandSpec> {
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<CommandSpec> {
"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<SymbolRegistration> {
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<Value> {
.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<Value> {
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<String> {
@@ -606,12 +623,13 @@ fn symbol_place(s: &mut Session, p: &Value) -> Result<Value> {
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<Value> {
*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<Value> {
}
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<Value> {
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<Value> {
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) => {
+17
View File
@@ -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());
}
+19 -6
View File
@@ -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<Value, String> {
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<Value, String> {
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;
}
+16
View File
@@ -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 По обект
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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 と表示します。
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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으로 표시합니다.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1
View File
@@ -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
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 718 and column 40.
+1
View File
@@ -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.
Can't render this file because it contains an unexpected character in line 3726 and column 10.
+1 -1
View File
@@ -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。
Can't render this file because it contains an unexpected character in line 2 and column 73.
+1 -1
View File
@@ -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。
Can't render this file because it contains an unexpected character in line 6 and column 47.
+1 -1
View File
@@ -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",
+4 -3
View File
@@ -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
+1 -1
View File
@@ -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 |