Fix #785: commands refuse an id that isn't an object id instead of using the selection

Commands whose `id` or `ids` the shared check in Session::execute does not
read still parsed them with id_param/ids_param, which drop anything that is
not a non-negative integer, and then acted on the selection or the current
layer. `object.blend.make {"ids": 3}` blended the selection,
`object.envelope.makeWithWarp {"ids": [2, "x"]}` warped the selection,
`graph.setData {"id": "2"}` changed the selected graph and
`layer.newSublayer {"parent": "2"}` added a sublayer to the current layer.

- cmd::named_ids and cmd::named_id read `ids`/`id` as targets does: a value
  that is not an object id, or an unresolved step reference, is an error.
- The symbol and brush, Live Paint, Shape Builder and Image Trace, blend,
  envelope and mesh, perspective, Liquify, Puppet Warp and graph helpers use
  them in place of the lenient readers.
- layer.clippingMask.toggle checks `id`, and layer.newSublayer checks
  `parent`, with checked_id before falling back to the highlighted row or the
  current layer.
This commit is contained in:
dotCooCoo
2026-11-02 14:32:49 +07:00
committed by bflatastic
parent cfb30d8139
commit 6be88a24b4
11 changed files with 116 additions and 29 deletions
+4 -4
View File
@@ -1,16 +1,16 @@
use super::mesh::whole;
use super::{
Arc, BlendEase, BlendEaseType, BlendOrientation, BlendSpacing, Document, EngineError, Node, NodeId, NodeKind, Result, Selection, Session, Value,
bad, check_numbers, has_selection, ids_param, json, selected_roots, str_param, switch_or,
bad, check_numbers, has_selection, json, named_ids, selected_roots, str_param, switch_or,
};
pub(super) fn ids_json(ids: &[NodeId]) -> Value {
json!({ "ids": ids.iter().map(|i| i.0).collect::<Vec<_>>() })
}
/// Explicit `ids` (in paint order) or the selected roots.
/// Explicit `ids`, or `id` as `ids` of one (in paint order), or the selected roots.
pub(super) fn roots_param(s: &Session, p: &Value) -> Result<Vec<NodeId>> {
if let Some(ids) = ids_param(p, "ids") {
if let Some(ids) = named_ids(p)? {
let st = s.doc()?;
for id in &ids {
if st.doc.node(*id).is_none() {
@@ -192,7 +192,7 @@ pub(super) fn starts_param(p: &Value, roots: &[NodeId]) -> Result<Vec<(NodeId, u
const C: &str = "object.blend.make";
let Some(v) = p.get("starts").filter(|v| !v.is_null()) else { return Ok(vec![]) };
let list = v.as_array().ok_or_else(|| bad(C, "`starts` must be an array of anchor indices (or nulls)"))?;
let owners = ids_param(p, "ids").unwrap_or_else(|| roots.to_vec());
let owners = named_ids(p)?.unwrap_or_else(|| roots.to_vec());
let mut out = vec![];
for (id, a) in owners.iter().zip(list) {
if a.is_null() {