@paircode/tool-art 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/index.js +127 -8
  2. package/package.json +1 -2
package/index.js CHANGED
@@ -1257,6 +1257,50 @@ function makeShape(proj, spec) {
1257
1257
  return sh;
1258
1258
  }
1259
1259
 
1260
+ // ── 批量新增图元(art_add 与 art_project create 的 shapes 共用)────
1261
+ // 与 art_edit 的 shape.add 同一套构造/校验(都走 makeShape),差别只在「N 个图元一次调用」:
1262
+ // 省掉手写 N 条 op 的样板。事务性:逐项入列(后一项才看得见前一项占用的 id/z),
1263
+ // 全部通过后由调用方落盘一次;任一项失败直接抛出 → 调用方走不到 saveProject,工程保持原样。
1264
+ var SHAPE_SPEC_FIELDS = ['id', 'name', 'type', 'layer', 'x', 'y', 'w', 'h', 'r', 'rx', 'ry',
1265
+ 'cx', 'cy', 'x1', 'y1', 'x2', 'y2', 'points', 'd', 'text', 'fontSize', 'fontWeight',
1266
+ 'anchor', 'fill', 'stroke', 'strokeWidth', 'opacity'];
1267
+
1268
+ function isPlainObj(v) { return !!v && typeof v === 'object' && !(v instanceof Array); }
1269
+
1270
+ // 单件写法:把同层参数收拢成一个图元描述(与 art_edit op=shape.add 的同层参数一致)
1271
+ function shapeSpecFromArgs(args) {
1272
+ var spec = {};
1273
+ for (var i = 0; i < SHAPE_SPEC_FIELDS.length; i++) {
1274
+ if (args[SHAPE_SPEC_FIELDS[i]] !== undefined) spec[SHAPE_SPEC_FIELDS[i]] = args[SHAPE_SPEC_FIELDS[i]];
1275
+ }
1276
+ return spec;
1277
+ }
1278
+
1279
+ // 批量入列;specs 为 undefined/null 视为「没传」(返回空数组,供 create 复用)
1280
+ function applyShapeSpecs(proj, specs, what) {
1281
+ if (specs === undefined || specs === null) return [];
1282
+ if (!(specs instanceof Array)) throw new Error((what || 'shapes') + ' 必须是数组');
1283
+ if (!specs.length) throw new Error((what || 'shapes') + ' 是空数组(要么不传,要么至少给一个图元)');
1284
+ var added = [];
1285
+ for (var i = 0; i < specs.length; i++) {
1286
+ if (!isPlainObj(specs[i])) throw new Error((what || 'shapes') + '[' + i + '] 必须是对象');
1287
+ var op = { op: 'shape.add' };
1288
+ for (var k in specs[i]) {
1289
+ if (Object.prototype.hasOwnProperty.call(specs[i], k)) op[k] = specs[i][k];
1290
+ }
1291
+ var log;
1292
+ try {
1293
+ log = applyOps(proj, [op]);
1294
+ } catch (e) {
1295
+ throw new Error('第 ' + (i + 1) + ' 个图元' + (specs[i].id ? '(' + specs[i].id + ')' : '') +
1296
+ '新增失败,整批未写入任何改动:' + ((e && e.message) || e));
1297
+ }
1298
+ var sh = proj.shapes[proj.shapes.length - 1];
1299
+ added.push({ id: sh.id, type: sh.type, layer: sh.layer, z: sh.z, detail: log[0] });
1300
+ }
1301
+ return added;
1302
+ }
1303
+
1260
1304
  // ── op 引擎(命令式编辑链,与 UI 操作同源) ────────────────
1261
1305
  function applyOps(proj, ops) {
1262
1306
  var log = [];
@@ -1892,8 +1936,18 @@ function artProject(args, exec, ctx) {
1892
1936
  }
1893
1937
  proj.layers = lys;
1894
1938
  }
1939
+ // ★ 一次调用成型:create 时可带 shapes=[…](每项与 shape.add 同构)——
1940
+ // 省掉「先 create 再逐条 art_edit」的往返;任一项校验失败整批不落盘(不留半成品工程)。
1941
+ var created = applyShapeSpecs(proj, args.shapes, 'shapes');
1895
1942
  saveProject(ctx, path, proj);
1896
- return '✅ 已创建画板工程: ' + path + '\n\n' + projectSummary(proj, path);
1943
+ var resNew = {
1944
+ ok: true, action: 'created', path: path,
1945
+ layers: proj.layers.length, shapes: proj.shapes.length,
1946
+ summary: projectSummary(proj, path),
1947
+ next: 'art_add 批量加图元 / art_edit 调整(align/distribute/set)/ art_export 出 SVG / art_verify 校验',
1948
+ };
1949
+ if (created.length) resNew.added = created;
1950
+ return JSON.stringify(resNew, null, 2);
1897
1951
  }
1898
1952
  if (mode === 'show') {
1899
1953
  var p1 = loadProject(ctx, path);
@@ -1933,10 +1987,50 @@ function artEdit(args, exec, ctx) {
1933
1987
  }];
1934
1988
  }
1935
1989
  if (!ops || !ops.length) throw new Error('需要 ops 数组(或 op + 同层参数)');
1936
- var log = applyOps(proj, ops);
1990
+ // 逐条应用 = 一次 load / 一次 save:批量语义不变(同一内存工程按序改),
1991
+ // 但能精确报出是哪一条失败,并保证「任一 op 失败整批不落盘」(saveProject 在循环之后)。
1992
+ var log = [];
1993
+ for (var i = 0; i < ops.length; i++) {
1994
+ var one;
1995
+ try {
1996
+ one = applyOps(proj, [ops[i]]);
1997
+ } catch (e) {
1998
+ throw new Error('第 ' + (i + 1) + ' 条 op(' + ((ops[i] && ops[i].op) || '?') + ')失败,整批未写入任何改动:' +
1999
+ ((e && e.message) || e));
2000
+ }
2001
+ log = log.concat(one);
2002
+ }
1937
2003
  saveProject(ctx, path, proj);
1938
- return '✅ 已应用 ' + ops.length + ' 条编辑命令到 ' + path + '\n\n' + log.join('\n') +
1939
- '\n\n' + projectSummary(proj, path);
2004
+ // 结构化返回(与 tool-model / tool-rig 同一口径):ok/applied/log 供程序化消费,
2005
+ // summary 保留人类可读摘要 —— 信息量不比旧的纯文本少。
2006
+ return JSON.stringify({
2007
+ ok: true, action: 'edited', path: path, applied: ops.length, log: log,
2008
+ shapes: proj.shapes.length, layers: proj.layers.length,
2009
+ summary: projectSummary(proj, path),
2010
+ next: 'art_add 批量加图元 / art_edit 继续调整 / art_export 出 SVG / art_verify 校验',
2011
+ }, null, 2);
2012
+ }
2013
+
2014
+ // ── art_add:批量新增图元(一次调用 N 个,事务式)────────────────
2015
+ // 与 tool-model 的 model_add(parts=[…])同一形态:单件写法兼容 + 数组批量 + 整批不落盘。
2016
+ function artAdd(args, exec, ctx) {
2017
+ var path = argStr(args, 'path', 'art.project.json');
2018
+ var proj = loadProject(ctx, path);
2019
+ var specs;
2020
+ if (args.shapes !== undefined) {
2021
+ specs = args.shapes; // 批量写法:以 shapes 为准(忽略同层单件字段)
2022
+ } else {
2023
+ if (!args.type) throw new Error('art_add 需要 shapes 数组(批量);或给 type 等几何参数(单件)');
2024
+ specs = [shapeSpecFromArgs(args)]; // 单件写法:同层参数收拢成一个图元
2025
+ }
2026
+ var added = applyShapeSpecs(proj, specs, 'shapes');
2027
+ saveProject(ctx, path, proj);
2028
+ var res = {
2029
+ ok: true, action: 'added', path: path, added: added, shapes: proj.shapes.length,
2030
+ next: 'art_edit 调整(align/distribute/set/move)/ art_export 出 SVG / art_verify 校验',
2031
+ };
2032
+ if (added.length === 1) { res.id = added[0].id; res.shape = added[0]; } // 单件写法兼容旧返回体
2033
+ return JSON.stringify(res, null, 2);
1940
2034
  }
1941
2035
 
1942
2036
  function artImport(args, exec, ctx) {
@@ -2003,8 +2097,8 @@ function artVerify(args, exec, ctx) {
2003
2097
  var TOOL_DEFS = [
2004
2098
  {
2005
2099
  name: 'art_project',
2006
- description: '矢量画板工程管理(文本真相源 art.project.json):创建/查看/更新画布尺寸、背景色、色板、图层。图元几何用绝对坐标 + 可选 2D 仿射矩阵 transform;SVG 是唯一文本产物(零依赖生成、可回读)。PNG 光栅化不在沙箱内(见 art_export)。',
2007
- usageGuide: '创作第一步:mode=create 建画板(width/height/background,可带 layers=[{id,name}])→ art_edit 加图元(shape.add)与调整(move/resize/set/align/distribute/z)→ art_export 出 SVG → art_verify 校验。mode=show 只看摘要,mode=update 改画布属性。已有 SVG 素材请用 art_import 导入。',
2100
+ description: '矢量画板工程管理(文本真相源 art.project.json):创建/查看/更新画布尺寸、背景色、色板、图层,并可在创建时一次带入整批图元(shapes=[…])。图元几何用绝对坐标 + 可选 2D 仿射矩阵 transform;SVG 是唯一文本产物(零依赖生成、可回读)。PNG 光栅化不在沙箱内(见 art_export)。',
2101
+ usageGuide: '创作第一步:mode=create 建画板(width/height/background,可带 layers=[{id,name}],也可带 shapes=[{type:"rect",…},{type:"text",…}] 一次把画面画好)→ art_add 批量加图元 / art_edit 调整(move/resize/set/align/distribute/z)→ art_export 出 SVG → art_verify 校验。mode=show 只看摘要,mode=update 改画布属性。已有 SVG 素材请用 art_import 导入。',
2008
2102
  category: '创作',
2009
2103
  parameters: {
2010
2104
  type: 'object',
@@ -2017,14 +2111,38 @@ var TOOL_DEFS = [
2017
2111
  background: { type: 'string', description: '可选:背景色(#RRGGBB / rgb() / 命名色 / none)' },
2018
2112
  palette: { type: 'array', description: '可选:色板(颜色字符串数组,默认 Tailwind 标准 10 色)' },
2019
2113
  layers: { type: 'array', description: '可选(仅 create):图层定义 [{id,name,visible}]' },
2114
+ shapes: { type: 'array', description: '可选(仅 create):一次带入的图元数组(每项与 art_add 的 shapes 项 / art_edit 的 shape.add 同构);任一项非法则整批不落盘' },
2020
2115
  overwrite: { type: 'boolean', description: '可选(仅 create):已存在时是否重建(默认 false)' },
2021
2116
  },
2022
2117
  },
2023
2118
  },
2119
+ {
2120
+ name: 'art_add',
2121
+ description: '批量新增图元到画板工程(一次调用任意多个,事务式)。shapes=[{type:"rect",x:…,y:…,w:…,h:…,fill:…},{type:"text",x:…,y:…,text:…}] —— 每项与 art_edit 的 shape.add 同构(rect/circle/ellipse/line/polyline/polygon/path/text;字段 x/y/w/h/rx/cx/cy/r/points/d/text/fontSize/fontWeight/anchor/fill/stroke/strokeWidth/opacity/layer/id/name)。也支持单件写法(type + 同层参数)。全部校验通过才落盘一次,任一项失败整批不写入(工程保持原样)。',
2122
+ usageGuide: '一次把画面画出来:art_add shapes=[{"type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8},{"type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"}]。id 不传自动分配(s1、s2…),z 按图层内顺序自动排。新增后要调位置/样式用 art_edit(shape.move/resize/set/align/distribute),出图用 art_export,交付前 art_verify。',
2123
+ category: '创作',
2124
+ parameters: {
2125
+ type: 'object',
2126
+ properties: {
2127
+ path: { type: 'string', description: '可选:工程路径(默认 <主项目根>/art.project.json;相对主项目根解析)' },
2128
+ shapes: { type: 'array', description: '图元描述数组(每项与 art_edit 的 shape.add 同构;批量写法)' },
2129
+ type: { type: 'string', description: '可选(单件写法):图元类型 rect/circle/ellipse/line/polyline/polygon/path/text' },
2130
+ id: { type: 'string', description: '可选(单件写法):图元 id(默认自动分配)' },
2131
+ layer: { type: 'string', description: '可选(单件写法):目标图层 id(默认第一个图层)' },
2132
+ x: { type: 'number', description: '可选(单件写法):x(rect/text 用;其余几何字段建议走 shapes)' },
2133
+ y: { type: 'number', description: '可选(单件写法):y' },
2134
+ w: { type: 'number', description: '可选(单件写法):宽' },
2135
+ h: { type: 'number', description: '可选(单件写法):高' },
2136
+ text: { type: 'string', description: '可选(单件写法):文本内容(type=text 必填)' },
2137
+ fill: { type: 'string', description: '可选(单件写法):填充色(none 表示不填充)' },
2138
+ stroke: { type: 'string', description: '可选(单件写法):描边色' },
2139
+ },
2140
+ },
2141
+ },
2024
2142
  {
2025
2143
  name: 'art_edit',
2026
- description: '向画板工程追加编辑命令(命令式 op,与面板操作同源)。支持:图元(shape.add/remove/duplicate/move/resize/set/text/z)、排版(shape.align 六向对齐 + shape.distribute 等距分布)、图层(layer.add/remove/rename/visible/reorder)、画布与色板(set.canvas/set.palette)、标题(project.rename)。图元类型:rect/circle/ellipse/line/polyline/polygon/path/text。目标选择支持 ids / id / type / layer / name / all 过滤(不指定目标直接报错,避免误改全部)。',
2027
- usageGuide: '示例:{"op":"shape.add","type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8} / {"op":"shape.add","type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"} / {"op":"shape.align","type":"rect","to":"hcenter"} / {"op":"shape.distribute","ids":["s1","s2","s3"],"axis":"h"} / move:{"op":"shape.move","ids":["s1"],"to":{"x":100,"y":60}} 或 dx/dy 相对偏移。改完用 art_verify 校验、art_export 出 SVG。',
2144
+ description: '向画板工程追加编辑命令(命令式 op,与面板操作同源)。支持:图元(shape.add/remove/duplicate/move/resize/set/text/z)、排版(shape.align 六向对齐 + shape.distribute 等距分布)、图层(layer.add/remove/rename/visible/reorder)、画布与色板(set.canvas/set.palette)、标题(project.rename)。图元类型:rect/circle/ellipse/line/polyline/polygon/path/text。目标选择支持 ids / id / type / layer / name / all 过滤(不指定目标直接报错,避免误改全部)。纯新增图元用 art_add(shapes 数组一次成型)更省。返回结构化结果(ok/applied/log/summary)。',
2145
+ usageGuide: '示例:{"op":"shape.add","type":"rect","x":40,"y":40,"w":320,"h":180,"fill":"#2563EB","rx":8} / {"op":"shape.add","type":"text","x":40,"y":80,"text":"标题","fontSize":24,"fontWeight":"600","fill":"#111827"} / {"op":"shape.align","type":"rect","to":"hcenter"} / {"op":"shape.distribute","ids":["s1","s2","s3"],"axis":"h"} / move:{"op":"shape.move","ids":["s1"],"to":{"x":100,"y":60}} 或 dx/dy 相对偏移。改完用 art_verify 校验、art_export 出 SVG。 ★ 批量:一次调用可传任意多条 op(按顺序应用;任一 op 失败整批不落盘)—— 图元多时一次调用成型(如 [{op:"shape.add",...},{op:"shape.add",...},{op:"shape.align",...}]),别一条一条调。',
2028
2146
  category: '创作',
2029
2147
  parameters: {
2030
2148
  type: 'object',
@@ -2088,6 +2206,7 @@ var TOOL_DEFS = [
2088
2206
 
2089
2207
  var IMPLS = {
2090
2208
  art_project: artProject,
2209
+ art_add: artAdd,
2091
2210
  art_edit: artEdit,
2092
2211
  art_import: artImport,
2093
2212
  art_export: artExport,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paircode/tool-art",
3
- "version": "0.3.1",
3
+ "version": "0.3.2",
4
4
  "scope": "global",
5
5
  "type": "plugin",
6
6
  "main": "index.js",
@@ -37,7 +37,6 @@
37
37
  "client.js",
38
38
  "assets",
39
39
  "bin",
40
- "lib",
41
40
  "package.json"
42
41
  ]
43
42
  }