diff --git a/crates/doc/src/variables.rs b/crates/doc/src/variables.rs index 8656e216c..5393abd0d 100644 --- a/crates/doc/src/variables.rs +++ b/crates/doc/src/variables.rs @@ -6,7 +6,7 @@ use std::collections::{BTreeMap, BTreeSet}; use serde::{Deserialize, Serialize}; -use super::{Document, NodeId, NodeKind, TextKind}; +use super::{Document, LockedArtboards, NodeId, NodeKind, TextKind}; /// What a variable drives. #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)] @@ -190,17 +190,32 @@ impl Document { } /// Whether a variable may be bound to the object `id`: it is in the document, neither it nor - /// anything above it is locked, and nothing above it is hidden. Its own visibility doesn't - /// count, as that is what a visibility variable drives. + /// anything above it is locked, nothing above it is hidden, and it is not on a locked artboard + /// ([`Document::on_locked_artboard`]). Its own visibility doesn't count, as that is what a + /// visibility variable drives. pub fn variable_target(&self, id: NodeId) -> bool { - self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| !n.locked && (n.visible || *i == id)))) + self.variable_target_with(&self.locked_artboards(), id) } - /// Whether a data set reads and rewrites the bound object `id`: it is in the document and - /// neither it nor anything above it is locked. Hidden objects count, and so do objects in a - /// hidden layer or group. + /// [`Document::variable_target`] with the locked artboards `locks` of this document, measured + /// once for many ids. + pub fn variable_target_with(&self, locks: &LockedArtboards, id: NodeId) -> bool { + self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| !n.locked && (n.visible || *i == id)))) + && !locks.holds_id(id) + } + + /// Whether a data set reads and rewrites the bound object `id`: it is in the document, neither + /// it nor anything above it is locked, and it is not on a locked artboard + /// ([`Document::on_locked_artboard`]). Hidden objects count, and so do objects in a hidden + /// layer or group. pub fn dataset_target(&self, id: NodeId) -> bool { - self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| !n.locked))) + self.dataset_target_with(&self.locked_artboards(), id) + } + + /// [`Document::dataset_target`] with the locked artboards `locks` of this document, measured + /// once for many ids. + pub fn dataset_target_with(&self, locks: &LockedArtboards, id: NodeId) -> bool { + self.ancestry(id).is_some_and(|a| a.iter().all(|i| self.node(*i).is_some_and(|n| !n.locked))) && !locks.holds_id(id) } /// The type objects of the story that the type object `id` belongs to, in thread order: the @@ -228,21 +243,32 @@ impl Document { /// Whether a data set reads and rewrites the text of the type object `id`: every frame of its /// story ([`Document::story_of`]) is a [`Document::dataset_target`]. pub fn story_target(&self, id: NodeId) -> bool { - self.story_of(id).into_iter().all(|f| self.dataset_target(f)) + self.story_target_with(&self.locked_artboards(), id) + } + + /// [`Document::story_target`] with the locked artboards `locks` of this document, measured + /// once for many ids. + pub fn story_target_with(&self, locks: &LockedArtboards, id: NodeId) -> bool { + self.story_of(id).into_iter().all(|f| self.dataset_target_with(locks, f)) } /// What the object `id` holds for the variable of `kind`: its characters (the whole story /// for a frame of threaded text), whether it shows, or a linked image's path. `None` when the - /// object is gone, is not of that kind, has no link path yet (an embedded image), or is locked - /// or in a locked layer or group, or for text when a frame of its story is (the document's - /// value is whatever it was left at). + /// object is gone, is not of that kind, has no link path yet (an embedded image), or is locked, + /// in a locked layer or group, or on a locked artboard, or for text when a frame of its story + /// is (the document's value is whatever it was left at). pub fn variable_value(&self, id: NodeId, kind: VariableKind) -> Option { - if !self.dataset_target(id) { + self.variable_value_with(&self.locked_artboards(), id, kind) + } + + /// [`Document::variable_value`] with the locked artboards `locks` of this document. + fn variable_value_with(&self, locks: &LockedArtboards, id: NodeId, kind: VariableKind) -> Option { + if !self.dataset_target_with(locks, id) { return None; } let node = self.node(id)?; match (kind, &node.kind) { - (VariableKind::Text, NodeKind::Text(_)) if self.story_target(id) => Some(DataValue::Text(self.story_text(id))), + (VariableKind::Text, NodeKind::Text(_)) if self.story_target_with(locks, id) => Some(DataValue::Text(self.story_text(id))), (VariableKind::Visibility, _) => Some(DataValue::Visible(node.visible)), (VariableKind::Image, NodeKind::Image(im)) => im.link.as_ref().map(|l| DataValue::Image(l.path.clone())), (VariableKind::Graph, _) => node.extras().graph.as_ref().map(|g| DataValue::Graph(g.to_csv())), @@ -257,10 +283,11 @@ impl Document { /// still matches what it says. pub fn captured_values(&self) -> BTreeMap { let mut out = BTreeMap::new(); + let locks = self.locked_artboards(); for v in &self.variables.variables { // An object driven by several variables of the same kind reads from the first one // bound, so a capture is one value per variable and applies back the same way. - let value = self.variables.objects_of(&v.name).into_iter().find_map(|id| self.variable_value(id, v.kind)); + let value = self.variables.objects_of(&v.name).into_iter().find_map(|id| self.variable_value_with(&locks, id, v.kind)); if let Some(value) = value { out.insert(v.name.clone(), value); } diff --git a/crates/engine/src/cmd/variablecmds.rs b/crates/engine/src/cmd/variablecmds.rs index c3e706f7b..596848ac9 100644 --- a/crates/engine/src/cmd/variablecmds.rs +++ b/crates/engine/src/cmd/variablecmds.rs @@ -138,13 +138,15 @@ fn names_param(p: &Value, cmd: &str, fallback: Option, nothing: &str) -> } } -/// Refuse what a variable of `kind` can't be bound to: art that is locked or on a hidden or locked -/// layer; a text variable only drives type; an image variable only drives an image object. +/// Refuse what a variable of `kind` can't be bound to: art that is locked, on a hidden or locked +/// layer, or on a locked artboard; a text variable only drives type; an image variable only drives +/// an image object. fn check_bindable(doc: &vectorcraft_doc::Document, ids: &[NodeId], kind: VariableKind, cmd: &str) -> Result<()> { + let locks = doc.locked_artboards(); for id in ids { match doc.node(*id) { None => return Err(EngineError::NoNode(*id)), - Some(_) if !doc.variable_target(*id) => return Err(bad(cmd, not_a_target(doc, *id))), + Some(_) if !doc.variable_target_with(&locks, *id) => return Err(bad(cmd, not_a_target(doc, *id))), Some(n) if kind == VariableKind::Text && !matches!(n.kind, NodeKind::Text(_)) => { return Err(bad(cmd, format!("node {} is not type", id.0))); } @@ -161,7 +163,7 @@ fn check_bindable(doc: &vectorcraft_doc::Document, ids: &[NodeId], kind: Variabl } /// Why `variable_target` refuses `id`: the object is locked, a group or layer that holds it is -/// locked, or one that holds it is hidden, checked in that order. +/// locked, one that holds it is hidden, or it is on a locked artboard, checked in that order. fn not_a_target(doc: &vectorcraft_doc::Document, id: NodeId) -> String { let holders: Vec<&vectorcraft_doc::Node> = doc.ancestry(id).unwrap_or_default().into_iter().filter(|i| *i != id).filter_map(|i| doc.node(i)).collect(); @@ -169,8 +171,10 @@ fn not_a_target(doc: &vectorcraft_doc::Document, id: NodeId) -> String { format!("node {} is locked", id.0) } else if holders.iter().any(|n| n.locked) { format!("node {} is in a locked group or layer", id.0) - } else { + } else if holders.iter().any(|n| !n.visible) { format!("node {} is in a hidden group or layer", id.0) + } else { + format!("node {} is on a locked artboard", id.0) } } @@ -483,7 +487,7 @@ fn dataset_capture(s: &mut Session, p: &Value) -> Result { /// Update Data Set: write what the bound objects hold now into the row that is active, for each /// variable the row holds. A variable whose objects report no value (locked, inside a locked -/// layer or group, or gone) keeps the row's value. With `all`, every other variable the +/// layer or group, on a locked artboard, or gone) keeps the row's value. With `all`, every other variable the /// objects report a value for is added to the row. Without an active row there is nothing to /// update, so say so rather than picking one. With every bound object locked, no value is /// written; an update that writes no value adds no undo step. @@ -557,6 +561,20 @@ fn apply_dataset(s: &mut Session, name: &str) -> Result { for v in &d.variables.variables { pairs.extend(d.variables.objects_of(&v.name).into_iter().map(|id| (id, v.name.clone()))); } + // Which bound objects the row may rewrite, measured once before any of them changes, so + // text that grows onto a locked artboard part way through is still rewritten. Each id is + // keyed by whether a text value is meant for it, which checks every frame of its story. + let targets: BTreeSet<(NodeId, bool)> = { + let locks = d.locked_artboards(); + let mut out = BTreeSet::new(); + for (id, var) in &pairs { + let text = d.variables.variable(var).is_some_and(|v| v.kind == VariableKind::Text); + if if text { d.story_target_with(&locks, *id) } else { d.dataset_target_with(&locks, *id) } { + out.insert((*id, text)); + } + } + out + }; // Whether an object is bound to a variable that this row has a value for (the reply's `warning`). let bound = pairs.iter().any(|(_, var)| ds.values.contains_key(var)); let mut applied = 0usize; @@ -574,12 +592,12 @@ fn apply_dataset(s: &mut Session, name: &str) -> Result { continue; } }; - // Locked art, art in a locked layer or group, and text whose object is gone, can't be - // rewritten: skipped and counted, like the rest. Hidden art is rewritten, in a hidden - // layer or group too, and stays hidden unless a visibility value shows it. A text value - // rewrites the whole story of threaded text, so it skips when any frame of it is locked. - let target = if kind == VariableKind::Text { d.story_target(id) } else { d.dataset_target(id) }; - if !target { + // Locked art, art in a locked layer or group or on a locked artboard, and text whose + // object is gone, can't be rewritten: skipped and counted, like the rest. Hidden art + // is rewritten, in a hidden layer or group too, and stays hidden unless a visibility + // value shows it. A text value rewrites the whole story of threaded text, so it skips + // when any frame of it is locked or on a locked artboard. + if !targets.contains(&(id, kind == VariableKind::Text)) { skipped += 1; continue; } @@ -772,7 +790,7 @@ pub fn specs() -> Vec { "Update Data Set", ["Window", "Variables"], None, - "{all?: bool} write what the bound objects hold now into the active dataset, for each variable the dataset holds; a variable whose objects report no value (locked, inside a locked layer or group, or gone) keeps the dataset's value. `all: true` also adds every other variable the objects report a value for; `left` names the variables it didn't write → {values, left}", + "{all?: bool} write what the bound objects hold now into the active dataset, for each variable the dataset holds; a variable whose objects report no value (locked, inside a locked layer or group, on a locked artboard, or gone) keeps the dataset's value. `all: true` also adds every other variable the objects report a value for; `left` names the variables it didn't write → {values, left}", has_doc, dataset_update ), diff --git a/crates/engine/src/tests/variables.rs b/crates/engine/src/tests/variables.rs index 8dd5a55e3..493871de2 100644 --- a/crates/engine/src/tests/variables.rs +++ b/crates/engine/src/tests/variables.rs @@ -438,6 +438,68 @@ fn locked_art_is_neither_bound_nor_rewritten() { assert!(!s.doc().unwrap().doc.node(r).unwrap().visible, "the unlocked rectangle still applied"); } +/// The art on a locked artboard acts as locked art: a data set skips it, Update Data Set reads +/// nothing from it, and it takes no new binding. Unlocking the artboard lets the data set rewrite it. +#[test] +fn art_on_a_locked_artboard_is_neither_bound_nor_rewritten() { + let mut s = session(); + let t = NodeId(s.execute("text.create", &json!({"x": 2050, "y": 50, "text": "cat"})).unwrap()["id"].as_u64().unwrap()); + let fresh = NodeId(s.execute("text.create", &json!({"x": 2100, "y": 100, "text": "new"})).unwrap()["id"].as_u64().unwrap()); + let r = rect(&mut s); + s.execute("artboard.new", &json!({"x": 2000, "y": 0, "width": 400, "height": 400})).unwrap(); + s.execute("variable.define", &json!({"name": "Name", "kind": "text"})).unwrap(); + s.execute("variable.define", &json!({"name": "Show", "kind": "visibility"})).unwrap(); + s.execute("variable.define", &json!({"name": "Box", "kind": "visibility"})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Name", "ids": [t.0]})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Show", "ids": [t.0]})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Box", "ids": [r.0]})).unwrap(); + s.execute("dataset.capture", &json!({"name": "A"})).unwrap(); + s.execute("dataset.new", &json!({"name": "B", "values": {"Name": "Bob", "Show": false, "Box": false}})).unwrap(); + s.execute("select.none", &json!({})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 1, "locked": true})).unwrap(); + + let e = s.execute("variable.bind", &json!({"variable": "Name", "ids": [fresh.0]})).unwrap_err().to_string(); + assert!(e.contains(&format!("node {} is on a locked artboard", fresh.0)), "{e}"); + let v = s.execute("dataset.select", &json!({"name": "B"})).unwrap(); + assert_eq!((v["applied"].as_u64(), v["skipped"].as_u64()), (Some(1), Some(2)), "{v}"); + assert_eq!(plain(&s, t), "cat", "the text on the locked artboard was left alone"); + assert!(s.doc().unwrap().doc.node(t).unwrap().visible, "and still shows"); + assert!(!s.doc().unwrap().doc.node(r).unwrap().visible, "the rectangle off it applied"); + // Update Data Set keeps the row's values for the art it can't read. + let v = s.execute("dataset.update", &json!({})).unwrap(); + assert_eq!(v["values"], 1, "{v}"); + let row = s.doc().unwrap().doc.variables.dataset("B").unwrap().values.clone(); + assert_eq!(row.get("Name"), Some(&vectorcraft_doc::DataValue::Text("Bob".into()))); + assert_eq!(row.get("Show"), Some(&vectorcraft_doc::DataValue::Visible(false))); + + s.execute("artboard.setProps", &json!({"index": 1, "locked": false})).unwrap(); + let v = s.execute("dataset.select", &json!({"name": "B"})).unwrap(); + assert_eq!((v["applied"].as_u64(), v["skipped"].as_u64()), (Some(3), Some(0)), "{v}"); + assert_eq!(plain(&s, t), "Bob"); +} + +/// A locked artboard elsewhere in the document does not stop threaded type on an unlocked artboard +/// from having its whole story rewritten, and type on the locked artboard is still left alone. +#[test] +fn threaded_type_on_an_unlocked_artboard_is_rewritten_beside_a_locked_artboard() { + let (mut s, a, b, story) = threaded(200); + let far = NodeId(s.execute("text.create", &json!({"x": 2050, "y": 50, "text": "cat"})).unwrap()["id"].as_u64().unwrap()); + s.execute("artboard.new", &json!({"x": 2000, "y": 0, "width": 400, "height": 400})).unwrap(); + s.execute("variable.define", &json!({"name": "Name", "kind": "text"})).unwrap(); + s.execute("variable.define", &json!({"name": "Tail", "kind": "text"})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Name", "ids": [a.0]})).unwrap(); + s.execute("variable.bind", &json!({"variable": "Tail", "ids": [far.0]})).unwrap(); + s.execute("dataset.new", &json!({"name": "B", "values": {"Name": "Bob", "Tail": "dog"}})).unwrap(); + s.execute("select.none", &json!({})).unwrap(); + s.execute("artboard.setProps", &json!({"index": 1, "locked": true})).unwrap(); + + let v = s.execute("dataset.select", &json!({"name": "B"})).unwrap(); + assert_eq!((v["applied"].as_u64(), v["skipped"].as_u64()), (Some(1), Some(1)), "{v}"); + assert_eq!((plain(&s, a), plain(&s, b)), ("Bob".to_string(), String::new()), "the whole story was rewritten"); + assert_eq!(plain(&s, far), "cat", "the text on the locked artboard was left alone"); + assert!(!story.is_empty()); +} + /// Binding fails for locked art and for art in a hidden or locked group or layer, and the /// error names which of the three applies. #[test] diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index f2158a683..c72b11a97 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -5275,6 +5275,7 @@ digits Malayalam Malaiàlam @msg no user library folder here aquí no hi ha cap carpeta de biblioteca d’usuari @msg no user workspace `{from}` no hi ha l’espai de treball d’usuari `{from}` @msg node {_1} is not text el node {_1} no és text +@msg node {_1} is on a locked artboard el node {_1} és en un tauler bloquejat @msg Non-finite dimension Cota no finita @msg not a blend no és una fusió @msg not a DXF drawing no és un dibuix DXF diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 19b8c43c9..9054417bf 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -4609,6 +4609,7 @@ imageTrace Outline Obrys @msg no user library folder here zde není složka uživatelských knihoven @msg no user workspace `{from}` uživatelský pracovní prostor `{from}` neexistuje @msg node {_1} is not text uzel {_1} není text +@msg node {_1} is on a locked artboard uzel {_1} je na zamčené pracovní ploše @msg not a DXF drawing nejde o výkres DXF @msg not a VectorCraft file: {0} nejde o soubor VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) nejde o sadu klávesových zkratek VectorCraft (chybí `overrides`) diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 0fec9a048..a0f272211 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -4218,6 +4218,7 @@ theme Appearance Erscheinungsbild @msg no user library folder here hier gibt es keinen Ordner für Benutzerbibliotheken @msg no user workspace `{from}` kein Benutzerarbeitsbereich `{from}` @msg node {_1} is not text Knoten {_1} ist kein Text +@msg node {_1} is on a locked artboard Knoten {_1} liegt auf einer gesperrten Zeichenfläche @msg not a DXF drawing keine DXF-Zeichnung @msg not a VectorCraft file: {0} keine VectorCraft-Datei: {0} @msg not a VectorCraft shortcut set (missing `overrides`) kein VectorCraft-Tastaturbefehlssatz (`overrides` fehlt) diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 9c4ba881b..5260c7dc9 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -4257,6 +4257,7 @@ digits Malayalam Μαλαγιάλαμ @msg no user library folder here δεν υπάρχει φάκελος βιβλιοθήκης χρήστη εδώ @msg no user workspace `{from}` δεν υπάρχει χώρος εργασίας χρήστη `{from}` @msg node {_1} is not text ο κόμβος {_1} δεν είναι κείμενο +@msg node {_1} is on a locked artboard ο κόμβος {_1} βρίσκεται σε κλειδωμένο πίνακα σχεδίασης @msg not a DXF drawing δεν είναι σχέδιο DXF @msg not a VectorCraft file: {0} δεν είναι αρχείο VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) δεν είναι σύνολο συντομεύσεων VectorCraft (λείπει το `overrides`) diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 90273208d..2ac467dab 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -4302,6 +4302,7 @@ axis Both Ambos @msg no user library folder here aquí no hay ninguna carpeta de bibliotecas de usuario @msg no user workspace `{from}` no existe el espacio de trabajo de usuario `{from}` @msg node {_1} is not text el nodo {_1} no es texto +@msg node {_1} is on a locked artboard el nodo {_1} está en una mesa de trabajo bloqueada @msg not a DXF drawing no es un dibujo DXF @msg not a VectorCraft file: {0} no es un archivo de VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) no es un conjunto de atajos de VectorCraft (falta `overrides`) diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index b56dc7e73..284735fbe 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -4517,6 +4517,7 @@ imageTrace Outline Ääriviiva @msg no user library folder here täällä ei ole käyttäjäkirjastokansiota @msg no user workspace `{from}` ei käyttäjän työtilaa `{from}` @msg node {_1} is not text solmu {_1} ei ole tekstiä +@msg node {_1} is on a locked artboard solmu {_1} on lukitulla piirtoalustalla @msg not a DXF drawing ei DXF-piirustus @msg not a VectorCraft file: {0} ei VectorCraft-tiedosto: {0} @msg not a VectorCraft shortcut set (missing `overrides`) ei VectorCraftin pikanäppäinjoukko (`overrides` puuttuu) diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 3f1374cf8..0078b6566 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -4292,6 +4292,7 @@ axis Both Les deux @msg no user library folder here aucun dossier de bibliothèques utilisateur ici @msg no user workspace `{from}` aucun espace de travail utilisateur `{from}` @msg node {_1} is not text le nœud {_1} n’est pas du texte +@msg node {_1} is on a locked artboard le nœud {_1} est sur un plan de travail verrouillé @msg not a DXF drawing ce n’est pas un dessin DXF @msg not a VectorCraft file: {0} ce n’est pas un fichier VectorCraft : {0} @msg not a VectorCraft shortcut set (missing `overrides`) ce n’est pas un jeu de raccourcis VectorCraft (`overrides` manquant) diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 5c0099845..3018d5845 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -4976,6 +4976,7 @@ imageTrace Outline Körvonal @msg no user library folder here itt nincs felhasználói könyvtármappa @msg no user workspace `{from}` nincs ilyen felhasználói munkaterület: `{from}` @msg node {_1} is not text a csomópont nem szöveg: {_1} +@msg node {_1} is on a locked artboard a csomópont zárolt rajztáblán van: {_1} @msg not a DXF drawing nem DXF-rajz @msg not a file nem fájl @msg not a font file nem betűtípusfájl diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 3185cf883..eb066f48e 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -4344,6 +4344,7 @@ axis Both Entrambi @msg no user library folder here nessuna cartella di librerie utente qui @msg no user workspace `{from}` nessuna area di lavoro utente `{from}` @msg node {_1} is not text il nodo {_1} non è testo +@msg node {_1} is on a locked artboard il nodo {_1} è su una tavola da disegno bloccata @msg not a DXF drawing non è un disegno DXF @msg not a VectorCraft file: {0} non è un file VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) non è un set di scelte rapide VectorCraft (manca `overrides`) diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 928fd7055..71336831a 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -4306,6 +4306,7 @@ swatch Bright Violet ビビッドバイオレット @msg a key object sits on that point そのポイントにはキーオブジェクトがあります @msg no graphic style `{name}` グラフィックスタイル `{name}` が見つかりません @msg node {_1} is not text ノード {_1} はテキストではありません +@msg node {_1} is on a locked artboard ノード {_1} はロックされたアートボード上にあります @msg Text Wrap: select a wrap object テキストの回り込み: 回り込みオブジェクトを選択してください @msg layers can't be wrap objects レイヤーは回り込みオブジェクトにできません @msg line {line}: value for `{var}` must be true or false {line} 行目: `{var}` の値には true または false を指定してください diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 8a42692a8..dbcd8bfb2 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -4059,6 +4059,7 @@ font-technology Variable ცვლადი @msg no user library folder here აქ მომხმარებლის ბიბლიოთეკის საქაღალდე არ არის @msg no user workspace `{from}` მომხმარებლის სამუშაო სივრცე `{from}` არ არის @msg node {_1} is not text კვანძი {_1} ტექსტი არ არის +@msg node {_1} is on a locked artboard კვანძი {_1} ჩაკეტილ სამუშაო დაფაზეა @msg not a DXF drawing ეს არ არის DXF ნახაზი @msg not a VectorCraft file: {0} ეს არ არის VectorCraft-ის ფაილი: {0} @msg not a VectorCraft shortcut set (missing `overrides`) ეს არ არის VectorCraft-ის მალსახმობების ნაკრები (აკლია `overrides`) diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 495c8b230..a98074101 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -4233,6 +4233,7 @@ theme Appearance 외관 @msg a key object sits on that point 그 포인트에 키 개체가 있습니다 @msg no graphic style `{name}` `{name}` 그래픽 스타일이 없습니다 @msg node {_1} is not text 노드 {_1}은(는) 텍스트가 아닙니다 +@msg node {_1} is on a locked artboard 노드 {_1}이(가) 잠긴 아트보드에 있습니다 @msg Text Wrap: select a wrap object 텍스트 감싸기: 감싸기 개체를 선택하세요 @msg layers can't be wrap objects 레이어는 감싸기 개체가 될 수 없습니다 @msg line {line}: value for `{var}` must be true or false {line}행: `{var}`의 값은 true 또는 false여야 합니다 diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 711bf649e..208610868 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -4432,6 +4432,7 @@ imageTrace Outline Kontur @msg no user library folder here brak folderu bibliotek użytkownika @msg no user workspace `{from}` brak obszaru roboczego użytkownika `{from}` @msg node {_1} is not text węzeł {_1} nie jest tekstem +@msg node {_1} is on a locked artboard węzeł {_1} jest na zablokowanym obszarze kompozycji @msg not a DXF drawing to nie jest rysunek DXF @msg not a VectorCraft file: {0} to nie jest plik VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) to nie jest zestaw skrótów VectorCraft (brak `overrides`) diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 5e77e3e63..6a0b875ca 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -3887,6 +3887,7 @@ axis Both Ambos @msg no user library folder here nenhuma pasta de biblioteca do usuário aqui @msg no user workspace `{from}` nenhum espaço de trabalho do usuário `{from}` @msg node {_1} is not text o nó {_1} não é texto +@msg node {_1} is on a locked artboard o nó {_1} está em uma prancheta bloqueada @msg not a DXF drawing não é um desenho DXF @msg not a VectorCraft file: {0} não é um arquivo do VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) não é um conjunto de atalhos do VectorCraft (falta `overrides`) diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index 3a52211b4..3dd370fa6 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -4339,6 +4339,7 @@ axis Both Оба @msg no user library folder here здесь нет папки пользовательских библиотек @msg no user workspace `{from}` нет пользовательской рабочей среды `{from}` @msg node {_1} is not text узел {_1} не является текстом +@msg node {_1} is on a locked artboard узел {_1} находится на заблокированной монтажной области @msg not a DXF drawing не является чертежом DXF @msg not a VectorCraft file: {0} не является файлом VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) не является набором сочетаний клавиш VectorCraft (отсутствует `overrides`) diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 1e2eace01..cf78d8f59 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -4207,6 +4207,7 @@ axis Both Båda @msg no user library folder here ingen mapp för användarbibliotek här @msg no user workspace `{from}` ingen användararbetslayout `{from}` @msg node {_1} is not text nod {_1} är inte text +@msg node {_1} is on a locked artboard nod {_1} ligger på en låst rityta @msg not a DXF drawing inte en DXF-ritning @msg not a VectorCraft file: {0} inte en VectorCraft-fil: {0} @msg not a VectorCraft shortcut set (missing `overrides`) inte en kortkommandouppsättning för VectorCraft (`overrides` saknas) diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index a9bf7050e..9a31ba088 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -4062,6 +4062,7 @@ axis Both Her İkisi @msg no user library folder here burada kullanıcı kitaplığı klasörü yok @msg no user workspace `{from}` kullanıcı çalışma alanı yok `{from}` @msg node {_1} is not text {_1} düğümü metin değil +@msg node {_1} is on a locked artboard {_1} düğümü kilitli bir çalışma yüzeyinde @msg not a DXF drawing DXF çizimi değil @msg not a VectorCraft file: {0} bir VectorCraft dosyası değil: {0} @msg not a VectorCraft shortcut set (missing `overrides`) VectorCraft kısayol seti değil ("geçersiz kılmalar" eksik) diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index 03f9ee088..87ba2b473 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -4328,6 +4328,7 @@ axis Both Обидві @msg no user library folder here тут немає папки користувацьких бібліотек @msg no user workspace `{from}` немає користувача робочого середовища `{from}` @msg node {_1} is not text вузол {_1} не є текстом +@msg node {_1} is on a locked artboard вузол {_1} розташований на заблокованій монтажній області @msg not a DXF drawing не є кресленням DXF @msg not a VectorCraft file: {0} не є файлом VectorCraft: {0} @msg not a VectorCraft shortcut set (missing `overrides`) не є набором поєднань клавіш VectorCraft (відсутнє `overrides`) diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 1db2648f6..3f95c4a3c 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -4628,6 +4628,7 @@ theme Appearance Giao diện @msg no user library folder here không có thư mục thư viện người dùng ở đây @msg no user workspace `{from}` không có không gian làm việc người dùng `{from}` @msg node {_1} is not text nút {_1} không phải là văn bản +@msg node {_1} is on a locked artboard nút {_1} nằm trên bảng vẽ bị khóa @msg not a blend không phải đối tượng hòa trộn @msg not a DXF drawing không phải bản vẽ DXF @msg not a file không phải tệp diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 28846a8c1..ff7bf05e1 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -4031,6 +4031,7 @@ imageTrace Outline 轮廓 @msg a graphic style named `{to}` already exists 名为 `{to}` 的图形样式已存在 @msg no graphic style `{name}` 没有图形样式 `{name}` @msg node {_1} is not text 节点 {_1} 不是文字 +@msg node {_1} is on a locked artboard 节点 {_1} 位于已锁定的画板上 @msg Text Wrap: select a wrap object 文本绕排:请选择一个绕排对象 @msg layers can't be wrap objects 图层不能作为绕排对象 @msg line {line}: value for `{var}` must be true or false 第 {line} 行:`{var}` 的值必须是 true 或 false diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 6f2903978..883a36e87 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -1938,6 +1938,7 @@ file-filter Templates 範本 @msg a graphic style named `{to}` already exists 名為 `{to}` 的繪圖樣式已存在 @msg no graphic style `{name}` 沒有繪圖樣式 `{name}` @msg node {_1} is not text 節點 {_1} 不是文字 +@msg node {_1} is on a locked artboard 節點 {_1} 位於已鎖定的工作區域上 @msg Text Wrap: select a wrap object 文字繞圖:請選取繞圖物件 @msg layers can't be wrap objects 圖層不能作為繞圖物件 @msg line {line}: value for `{var}` must be true or false 第 {line} 行:`{var}` 的值必須是 true 或 false diff --git a/docs/mcp.md b/docs/mcp.md index 19b072579..296e74e91 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -1813,7 +1813,7 @@ document stays unmodified. `dataset.select`, `dataset.next` and `dataset.prev` reply `{name, applied, skipped}`. They add `overflow: [id…]` when area type or type on a path whose text they rewrote doesn't fit, as `text.setText` does (see [Fitting area type](#fitting-area-type-auto-size-shrink-text-to-fit-overflow)). They skip an object that is -locked or inside a locked layer or group, and count it in `skipped`. They rewrite hidden objects and objects inside +locked, inside a locked layer or group, or on a locked artboard, and count it in `skipped`. They rewrite hidden objects and objects inside a hidden layer or group, which stay hidden unless one of the data set's visibility values shows them. When such a layer is shown again, its type holds the data set's text. They apply the values in the order the variables are defined. Of two variables of the same kind on one object, the one defined last wins. `dataset.list`, @@ -1848,7 +1848,7 @@ or no, or a graph value longer than 262,144 bytes. `dataset.update {all?}` writes what the bound objects hold now into the active data set, for each variable that the data set holds. A variable whose objects report no value, because they are locked, inside a locked layer or group, -or deleted, keeps the data set's value. With `all: true` it also adds every other variable that the +on a locked artboard, or deleted, keeps the data set's value. With `all: true` it also adds every other variable that the objects report a value for. The reply gives `values`, the number of values it wrote, and `left`, the variables it did not write. When every bound object is locked, it writes no value and adds no undo step, and `dataset.capture` returns the error "no bound object reports a value (locked objects and embedded images report none)".