@pixerate/editor 0.117.0 → 0.119.0
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.
- package/THIRD_PARTY_NOTICES.md +3 -0
- package/dist/animation-editor/index.cjs +54 -54
- package/dist/animation-editor/index.d.cts +3 -3
- package/dist/animation-editor/index.d.ts +3 -3
- package/dist/animation-editor/index.js +2 -2
- package/dist/{chunk-2J3LS7JZ.cjs → chunk-4S2AXNQF.cjs} +221 -186
- package/dist/chunk-4S2AXNQF.cjs.map +1 -0
- package/dist/{chunk-7LNZAWSJ.js → chunk-CSHEI3YT.js} +625 -451
- package/dist/chunk-CSHEI3YT.js.map +1 -0
- package/dist/{chunk-M7O4BJRH.cjs → chunk-KGNUFO4T.cjs} +36 -36
- package/dist/{chunk-M7O4BJRH.cjs.map → chunk-KGNUFO4T.cjs.map} +1 -1
- package/dist/{chunk-R2JE6SA7.js → chunk-KZLQY4CD.js} +42 -7
- package/dist/chunk-KZLQY4CD.js.map +1 -0
- package/dist/{chunk-L2T6EJ5S.cjs → chunk-L3PXFMOF.cjs} +38 -25
- package/dist/chunk-L3PXFMOF.cjs.map +1 -0
- package/dist/{chunk-FODKPD44.cjs → chunk-LI3KTY7K.cjs} +724 -550
- package/dist/chunk-LI3KTY7K.cjs.map +1 -0
- package/dist/{chunk-IVSNUXW7.js → chunk-T7IZDNSL.js} +37 -24
- package/dist/chunk-T7IZDNSL.js.map +1 -0
- package/dist/{chunk-T2P5TIZ6.js → chunk-TDQDMBUR.js} +2 -2
- package/dist/{chunk-BYAWOAST.cjs → chunk-TS5IEUW6.cjs} +26 -25
- package/dist/chunk-TS5IEUW6.cjs.map +1 -0
- package/dist/{chunk-VD2UDVF7.js → chunk-UMVW2Q66.js} +21 -5
- package/dist/chunk-UMVW2Q66.js.map +1 -0
- package/dist/{chunk-EBSYZRSW.cjs → chunk-WMRIG4XQ.cjs} +21 -5
- package/dist/chunk-WMRIG4XQ.cjs.map +1 -0
- package/dist/{chunk-JHXMNOMF.js → chunk-YHW26LS2.js} +14 -13
- package/dist/chunk-YHW26LS2.js.map +1 -0
- package/dist/{export-DMOuzgZZ.d.cts → export-B_jO2PrH.d.cts} +18 -4
- package/dist/{export-Dul1SbPv.d.ts → export-Ds8Ccw0K.d.ts} +18 -4
- package/dist/{flow-4-OC-UkQ.d.cts → flow-A2Dj-69w.d.cts} +21 -9
- package/dist/{flow-Cb7lkPm8.d.ts → flow-C95atNoQ.d.ts} +21 -9
- package/dist/image-editor/index.cjs +1104 -979
- package/dist/image-editor/index.cjs.map +1 -1
- package/dist/image-editor/index.d.cts +5 -5
- package/dist/image-editor/index.d.ts +5 -5
- package/dist/image-editor/index.js +157 -32
- package/dist/image-editor/index.js.map +1 -1
- package/dist/paint/index.cjs +5 -3
- package/dist/paint/index.cjs.map +1 -1
- package/dist/paint/index.d.cts +2 -2
- package/dist/paint/index.d.ts +2 -2
- package/dist/paint/index.js +4 -2
- package/dist/paint-editor/index.cjs +147 -147
- package/dist/paint-editor/index.d.cts +4 -4
- package/dist/paint-editor/index.d.ts +4 -4
- package/dist/paint-editor/index.js +4 -4
- package/dist/raster/index.cjs +2 -2
- package/dist/raster/index.js +1 -1
- package/dist/{svg-DlEuhwoX.d.ts → svg-CTxrB5xO.d.ts} +5 -1
- package/dist/{svg-CVdFc80-.d.cts → svg-hK0WQc7-.d.cts} +5 -1
- package/dist/{types-Tfly4Aya.d.cts → types-Dq-8qgoo.d.cts} +45 -3
- package/dist/{types-CDLGhAZp.d.ts → types-TrSSh_RB.d.ts} +45 -3
- package/dist/vector/index.cjs +1535 -1164
- package/dist/vector/index.cjs.map +1 -1
- package/dist/vector/index.d.cts +127 -9
- package/dist/vector/index.d.ts +127 -9
- package/dist/vector/index.js +386 -15
- package/dist/vector/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/pixerate-image-editor/references/photoshop-files.md +9 -7
- package/skills/pixerate-vector/SKILL.md +13 -1
- package/skills/pixerate-vector/references/commands.md +1 -0
- package/dist/chunk-2J3LS7JZ.cjs.map +0 -1
- package/dist/chunk-7LNZAWSJ.js.map +0 -1
- package/dist/chunk-BYAWOAST.cjs.map +0 -1
- package/dist/chunk-EBSYZRSW.cjs.map +0 -1
- package/dist/chunk-FODKPD44.cjs.map +0 -1
- package/dist/chunk-IVSNUXW7.js.map +0 -1
- package/dist/chunk-JHXMNOMF.js.map +0 -1
- package/dist/chunk-L2T6EJ5S.cjs.map +0 -1
- package/dist/chunk-R2JE6SA7.js.map +0 -1
- package/dist/chunk-VD2UDVF7.js.map +0 -1
- /package/dist/{chunk-T2P5TIZ6.js.map → chunk-TDQDMBUR.js.map} +0 -0
package/dist/vector/index.js
CHANGED
|
@@ -40,7 +40,7 @@ import {
|
|
|
40
40
|
reverseMorphContour,
|
|
41
41
|
subpathToMorphContour,
|
|
42
42
|
turnMorphContour
|
|
43
|
-
} from "../chunk-
|
|
43
|
+
} from "../chunk-TDQDMBUR.js";
|
|
44
44
|
import {
|
|
45
45
|
DEFAULT_VECTOR_FONT,
|
|
46
46
|
IDENTITY_MATRIX,
|
|
@@ -83,6 +83,8 @@ import {
|
|
|
83
83
|
VECTOR_STROKE_GRADIENT_MODES,
|
|
84
84
|
VECTOR_STROKE_SETTING_KEYS,
|
|
85
85
|
VECTOR_SYMBOL_MAX_DEPTH,
|
|
86
|
+
VECTOR_SYMMETRY_MODES,
|
|
87
|
+
VECTOR_SYMMETRY_MODE_LABELS,
|
|
86
88
|
VECTOR_TEXT_ALIGNS,
|
|
87
89
|
VECTOR_TOOLS,
|
|
88
90
|
VECTOR_TRANSFORM_HANDLES,
|
|
@@ -200,6 +202,7 @@ import {
|
|
|
200
202
|
isVectorMaskType,
|
|
201
203
|
isVectorMeshGradient,
|
|
202
204
|
isVectorShadedPaint,
|
|
205
|
+
isVectorSymmetryGroup,
|
|
203
206
|
knifePath,
|
|
204
207
|
layoutVectorText,
|
|
205
208
|
lineSubpath,
|
|
@@ -284,6 +287,7 @@ import {
|
|
|
284
287
|
sanitizeVectorShadedPaint,
|
|
285
288
|
sanitizeVectorStrokeSettings,
|
|
286
289
|
sanitizeVectorStrokes,
|
|
290
|
+
sanitizeVectorSymmetry,
|
|
287
291
|
sanitizeVectorTextFields,
|
|
288
292
|
sanitizeVectorTextFrame,
|
|
289
293
|
sanitizeWidthProfile,
|
|
@@ -316,6 +320,7 @@ import {
|
|
|
316
320
|
transformVectorNode,
|
|
317
321
|
transformVectorPaint,
|
|
318
322
|
transformVectorShadedPaint,
|
|
323
|
+
transformVectorSymmetry,
|
|
319
324
|
translateAnchor,
|
|
320
325
|
translateMatrix,
|
|
321
326
|
trimIntervals,
|
|
@@ -429,6 +434,9 @@ import {
|
|
|
429
434
|
vectorStrokes,
|
|
430
435
|
vectorSymbolUses,
|
|
431
436
|
vectorSymbolsUsedIn,
|
|
437
|
+
vectorSymmetryAxes,
|
|
438
|
+
vectorSymmetryDrawn,
|
|
439
|
+
vectorSymmetryMatrices,
|
|
432
440
|
vectorTargetPaint,
|
|
433
441
|
vectorTextBoxOutline,
|
|
434
442
|
vectorTextCaret,
|
|
@@ -467,7 +475,7 @@ import {
|
|
|
467
475
|
withVectorStrokes,
|
|
468
476
|
withVectorTargetPaint,
|
|
469
477
|
zigzagPaths
|
|
470
|
-
} from "../chunk-
|
|
478
|
+
} from "../chunk-CSHEI3YT.js";
|
|
471
479
|
import {
|
|
472
480
|
bezierPathBounds,
|
|
473
481
|
bezierPathToSvg,
|
|
@@ -2167,7 +2175,24 @@ function renderText(node, defs, filters, idPrefix) {
|
|
|
2167
2175
|
spans
|
|
2168
2176
|
};
|
|
2169
2177
|
}
|
|
2178
|
+
var renderCache = /* @__PURE__ */ new WeakMap();
|
|
2170
2179
|
function renderNode(stored, defs, filters, idPrefix) {
|
|
2180
|
+
const gen = vectorTextShaperGeneration();
|
|
2181
|
+
let byPrefix = renderCache.get(stored);
|
|
2182
|
+
const hit = byPrefix?.get(idPrefix);
|
|
2183
|
+
if (hit && hit.gen === gen) {
|
|
2184
|
+
defs.push(...hit.defs);
|
|
2185
|
+
filters.push(...hit.filters);
|
|
2186
|
+
return hit.node;
|
|
2187
|
+
}
|
|
2188
|
+
const d0 = defs.length;
|
|
2189
|
+
const f0 = filters.length;
|
|
2190
|
+
const node = renderNodeFresh(stored, defs, filters, idPrefix);
|
|
2191
|
+
if (!byPrefix) renderCache.set(stored, byPrefix = /* @__PURE__ */ new Map());
|
|
2192
|
+
byPrefix.set(idPrefix, { gen, node, defs: defs.slice(d0), filters: filters.slice(f0) });
|
|
2193
|
+
return node;
|
|
2194
|
+
}
|
|
2195
|
+
function renderNodeFresh(stored, defs, filters, idPrefix) {
|
|
2171
2196
|
if (stored.visible === false) return null;
|
|
2172
2197
|
if (stored.type === "text" && !vectorTextHasOutlines()) return renderText(stored, defs, filters, idPrefix);
|
|
2173
2198
|
const node = expandVectorAppearance(stored);
|
|
@@ -2234,13 +2259,64 @@ function renderMask(group, defs, filters, idPrefix) {
|
|
|
2234
2259
|
const source = renderNode(group.children[0], defs, filters, idPrefix);
|
|
2235
2260
|
return { ...mask, nodes: source ? [source] : [] };
|
|
2236
2261
|
}
|
|
2262
|
+
var collapsedCache = /* @__PURE__ */ new WeakMap();
|
|
2263
|
+
function collapsedNode(node, box, prefix) {
|
|
2264
|
+
let byPrefix = collapsedCache.get(node);
|
|
2265
|
+
if (byPrefix?.has(prefix)) return byPrefix.get(prefix);
|
|
2266
|
+
let fill = null;
|
|
2267
|
+
const look = (n2) => {
|
|
2268
|
+
if (fill || n2.visible === false) return;
|
|
2269
|
+
if (n2.type === "path" || n2.type === "text") {
|
|
2270
|
+
const f = n2.fill;
|
|
2271
|
+
if (typeof f === "string" && f !== "none") fill = f;
|
|
2272
|
+
else if (isVectorGradient(f)) fill = f.stops[0]?.color ?? null;
|
|
2273
|
+
} else if (n2.type === "group") n2.children.forEach(look);
|
|
2274
|
+
else if (n2.type === "instance") (n2.art ?? []).forEach(look);
|
|
2275
|
+
};
|
|
2276
|
+
look(node);
|
|
2277
|
+
const out = fill ? { kind: "path", id: node.id, d: `M${fmt2(box.minX)} ${fmt2(box.minY)}H${fmt2(box.maxX)}V${fmt2(box.maxY)}H${fmt2(box.minX)}Z`, fill, stroke: "none", strokeWidth: 0, fillRule: "nonzero", opacity: node.opacity ?? 1 } : null;
|
|
2278
|
+
if (!byPrefix) collapsedCache.set(node, byPrefix = /* @__PURE__ */ new Map());
|
|
2279
|
+
byPrefix.set(prefix, out);
|
|
2280
|
+
return out;
|
|
2281
|
+
}
|
|
2237
2282
|
function getVectorSceneRenderData(state, options = {}) {
|
|
2238
2283
|
const defs = [];
|
|
2239
2284
|
const filters = [];
|
|
2240
2285
|
const prefix = options.idPrefix ?? "ve-";
|
|
2241
|
-
const nodes = state.document.nodes.map((n2) => renderNode(n2, defs, filters, prefix)).filter((n2) => !!n2);
|
|
2242
2286
|
const v = state.viewport;
|
|
2287
|
+
const all = state.document.nodes;
|
|
2288
|
+
const stats = { nodes: all.length, culled: 0, collapsed: 0 };
|
|
2289
|
+
let nodes;
|
|
2290
|
+
const size = options.viewportSize;
|
|
2291
|
+
if (size && size.width > 0 && size.height > 0 && v.zoom > 0) {
|
|
2292
|
+
const z = v.zoom;
|
|
2293
|
+
const pad = 2 / z;
|
|
2294
|
+
const view = { minX: -v.x / z - pad, minY: -v.y / z - pad, maxX: (size.width - v.x) / z + pad, maxY: (size.height - v.y) / z + pad };
|
|
2295
|
+
const lod = options.lod ?? 2;
|
|
2296
|
+
const near = vectorNodesNear(all, { x: view.minX, y: view.minY, width: view.maxX - view.minX, height: view.maxY - view.minY });
|
|
2297
|
+
stats.culled = all.length - near.length;
|
|
2298
|
+
nodes = [];
|
|
2299
|
+
for (const i of near) {
|
|
2300
|
+
const n2 = all[i];
|
|
2301
|
+
const box = vectorNodePaintBox(n2);
|
|
2302
|
+
if (box && (box.maxX < view.minX || box.minX > view.maxX || box.maxY < view.minY || box.minY > view.maxY)) {
|
|
2303
|
+
stats.culled++;
|
|
2304
|
+
continue;
|
|
2305
|
+
}
|
|
2306
|
+
if (box && lod > 0 && n2.type !== "path" && n2.visible !== false && Math.max(box.maxX - box.minX, box.maxY - box.minY) * z < lod) {
|
|
2307
|
+
const c = collapsedNode(n2, box, prefix);
|
|
2308
|
+
if (c) {
|
|
2309
|
+
stats.collapsed++;
|
|
2310
|
+
nodes.push(c);
|
|
2311
|
+
continue;
|
|
2312
|
+
}
|
|
2313
|
+
}
|
|
2314
|
+
const r = renderNode(n2, defs, filters, prefix);
|
|
2315
|
+
if (r) nodes.push(r);
|
|
2316
|
+
}
|
|
2317
|
+
} else nodes = all.map((n2) => renderNode(n2, defs, filters, prefix)).filter((n2) => !!n2);
|
|
2243
2318
|
return {
|
|
2319
|
+
stats,
|
|
2244
2320
|
transform: `translate(${fmt2(v.x)} ${fmt2(v.y)}) scale(${v.zoom})`,
|
|
2245
2321
|
width: state.document.width,
|
|
2246
2322
|
height: state.document.height,
|
|
@@ -2303,6 +2379,46 @@ function shadingOverlay(state) {
|
|
|
2303
2379
|
}
|
|
2304
2380
|
return out;
|
|
2305
2381
|
}
|
|
2382
|
+
function symmetryOverlay(state) {
|
|
2383
|
+
const ids = /* @__PURE__ */ new Set([...state.selection, ...state.symmetryTarget ? [state.symmetryTarget] : []]);
|
|
2384
|
+
const out = [];
|
|
2385
|
+
for (const id of ids) {
|
|
2386
|
+
const n2 = findVectorNode(state.document.nodes, id);
|
|
2387
|
+
if (!isVectorSymmetryGroup(n2)) continue;
|
|
2388
|
+
const world = vectorWorldMatrix(state.document.nodes, id);
|
|
2389
|
+
if (!world) continue;
|
|
2390
|
+
const inv = invertMatrix(world);
|
|
2391
|
+
const corners2 = [
|
|
2392
|
+
{ x: 0, y: 0 },
|
|
2393
|
+
{ x: state.document.width, y: 0 },
|
|
2394
|
+
{ x: 0, y: state.document.height },
|
|
2395
|
+
{ x: state.document.width, y: state.document.height }
|
|
2396
|
+
].map((p) => applyMatrix(inv, p));
|
|
2397
|
+
const { transform: _t, ...own } = n2;
|
|
2398
|
+
const b = unionBoxes([vectorNodeBounds(own), boundsFromCorners(corners2)]);
|
|
2399
|
+
if (!b) continue;
|
|
2400
|
+
const pad = Math.max(b.width, b.height) * 0.05;
|
|
2401
|
+
const lines = vectorSymmetryAxes(n2.symmetry, { x: b.x - pad, y: b.y - pad, width: b.width + 2 * pad, height: b.height + 2 * pad }).map(([a, c]) => {
|
|
2402
|
+
const p = toScreen(state, applyMatrix(world, a));
|
|
2403
|
+
const q = toScreen(state, applyMatrix(world, c));
|
|
2404
|
+
return { x1: p.x, y1: p.y, x2: q.x, y2: q.y };
|
|
2405
|
+
});
|
|
2406
|
+
out.push({ nodeId: id, drawing: state.symmetryTarget === id, lines });
|
|
2407
|
+
}
|
|
2408
|
+
return out;
|
|
2409
|
+
}
|
|
2410
|
+
function unionBoxes(list) {
|
|
2411
|
+
const ok = list.filter((b) => !!b);
|
|
2412
|
+
if (!ok.length) return null;
|
|
2413
|
+
const x = Math.min(...ok.map((b) => b.x));
|
|
2414
|
+
const y = Math.min(...ok.map((b) => b.y));
|
|
2415
|
+
return { x, y, width: Math.max(...ok.map((b) => b.x + b.width)) - x, height: Math.max(...ok.map((b) => b.y + b.height)) - y };
|
|
2416
|
+
}
|
|
2417
|
+
function boundsFromCorners(pts) {
|
|
2418
|
+
const xs = pts.map((p) => p.x);
|
|
2419
|
+
const ys = pts.map((p) => p.y);
|
|
2420
|
+
return { x: Math.min(...xs), y: Math.min(...ys), width: Math.max(...xs) - Math.min(...xs), height: Math.max(...ys) - Math.min(...ys) };
|
|
2421
|
+
}
|
|
2306
2422
|
function connectorSiteOverlay(state) {
|
|
2307
2423
|
if (state.tool !== "connector" || !state.pointer) return [];
|
|
2308
2424
|
const drawing = state.interaction?.type === "draw" && state.interaction.tool === "connector" ? state.interaction.nodeId : void 0;
|
|
@@ -2575,6 +2691,7 @@ function getVectorOverlayRenderData(state) {
|
|
|
2575
2691
|
frames: frameLabels(state),
|
|
2576
2692
|
connectorSites: connectorSiteOverlay(state),
|
|
2577
2693
|
shading: shadingOverlay(state),
|
|
2694
|
+
symmetry: symmetryOverlay(state),
|
|
2578
2695
|
trackingPoints: (state.trackingPoints ?? []).map((p) => toScreen(state, p)),
|
|
2579
2696
|
typedEntry: state.typedEntry ? { text: state.typedEntry, ...state.pointer ? toScreen(state, state.pointer) : { x: 0, y: 0 } } : null
|
|
2580
2697
|
};
|
|
@@ -3595,9 +3712,9 @@ function nodeGroup(node, notes) {
|
|
|
3595
3712
|
if (node.filters?.length) notes.push(`${label(node)}: filters (shadows, glows, blurs) are not part of Lottie (left out).`);
|
|
3596
3713
|
const m = nodeMatrix(node);
|
|
3597
3714
|
let items = [];
|
|
3598
|
-
const live = (node.effects ?? []).some((e) => e.enabled !== false && (e.type === "wiggle" || e.type === "envelope"));
|
|
3599
|
-
if (node.type === "text" || node.type === "instance" || live || isVectorMaskGroup(node) || isVectorFrame(node)) {
|
|
3600
|
-
if (live) notes.push(`${label(node)}: wiggle and
|
|
3715
|
+
const live = (node.effects ?? []).some((e) => e.enabled !== false && (e.type === "wiggle" || e.type === "envelope" || e.type === "contour"));
|
|
3716
|
+
if (node.type === "text" || node.type === "instance" || live || isVectorMaskGroup(node) || isVectorFrame(node) || isVectorSymmetryGroup(node)) {
|
|
3717
|
+
if (live) notes.push(`${label(node)}: wiggle, envelope and contour effects have no Lottie equivalent (exported as drawn).`);
|
|
3601
3718
|
if (isVectorMaskGroup(node) && node.mask?.type !== "clip") notes.push(`${label(node)}: opacity masks are not exported (the contents are drawn whole).`);
|
|
3602
3719
|
const drawn = isVectorFrame(node) ? vectorFrameDrawn(node) : node;
|
|
3603
3720
|
const shapes = isVectorMaskGroup(drawn) && drawn.mask?.type === "clip" ? vectorAppearanceShapes({ ...drawn, transform: void 0 }) : null;
|
|
@@ -6831,6 +6948,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
6831
6948
|
trackingPoints: this.trackingPoints,
|
|
6832
6949
|
typedEntry: this.typed,
|
|
6833
6950
|
shadingPoint: this.getShadingSelection(),
|
|
6951
|
+
symmetryTarget: this.symmetryTarget && this.doc.nodes.some((n2) => n2.id === this.symmetryTarget) ? this.symmetryTarget : null,
|
|
6834
6952
|
canUndo: h.canUndo,
|
|
6835
6953
|
canRedo: h.canRedo,
|
|
6836
6954
|
undoLabel: h.undoName,
|
|
@@ -9134,6 +9252,155 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
9134
9252
|
return { ok: true, id };
|
|
9135
9253
|
}
|
|
9136
9254
|
// -----------------------------------------------------------------------------------------
|
|
9255
|
+
// Live symmetry groups
|
|
9256
|
+
/** The symmetry group new pen, pencil, shape and text art goes into (see `enterSymmetry`), or null. */
|
|
9257
|
+
symmetryTarget = null;
|
|
9258
|
+
/** The symmetry group drawn art goes into, while it is entered and still a top-level, unturned symmetry group. */
|
|
9259
|
+
drawingTarget() {
|
|
9260
|
+
const id = this.symmetryTarget;
|
|
9261
|
+
if (!id) return null;
|
|
9262
|
+
const n2 = this.doc.nodes.find((x) => x.id === id);
|
|
9263
|
+
if (!isVectorSymmetryGroup(n2) || n2.transform || n2.locked) {
|
|
9264
|
+
this.symmetryTarget = null;
|
|
9265
|
+
return null;
|
|
9266
|
+
}
|
|
9267
|
+
return n2;
|
|
9268
|
+
}
|
|
9269
|
+
/** Adds a node a drawing tool makes: into the entered symmetry group, else on top. */
|
|
9270
|
+
addDrawn(node) {
|
|
9271
|
+
const target = this.tool === "frame" || this.tool === "connector" ? null : this.drawingTarget();
|
|
9272
|
+
if (!target) return this.setDoc([...this.doc.nodes, node]);
|
|
9273
|
+
this.setDoc(this.doc.nodes.map((n2) => n2.id === target.id && n2.type === "group" ? { ...n2, children: [...n2.children, node] } : n2));
|
|
9274
|
+
}
|
|
9275
|
+
/**
|
|
9276
|
+
* Make Symmetry: puts objects that share a parent (default: the selection) into a live symmetry
|
|
9277
|
+
* group that draws them with their mirrored or rotated copies, the axes through `center`
|
|
9278
|
+
* (default: the objects' centre, or the artboard's when there are none, making an empty group to
|
|
9279
|
+
* draw into). One undo step; selects the group (and enters it when empty). Returns its id.
|
|
9280
|
+
*/
|
|
9281
|
+
makeSymmetry(input, options = {}) {
|
|
9282
|
+
const symmetry = sanitizeVectorSymmetry({ ...input, center: input.center ?? { x: 0, y: 0 } });
|
|
9283
|
+
if (!symmetry) return { ok: false, message: `Unknown symmetry "${input.mode}": use ${VECTOR_SYMMETRY_MODES.join(", ")}.` };
|
|
9284
|
+
if (input.copies !== void 0 && !(input.copies >= 2 && input.copies <= 64)) return { ok: false, message: "copies must be from 2 to 64." };
|
|
9285
|
+
const ids = [...new Set(options.ids ?? this.selectedRoots())];
|
|
9286
|
+
for (const id2 of ids) {
|
|
9287
|
+
const n2 = this.getNode(id2);
|
|
9288
|
+
if (!n2) return { ok: false, message: `There is no node "${id2}".` };
|
|
9289
|
+
if (n2.locked) return { ok: false, message: `"${id2}" is locked; unlock it first.` };
|
|
9290
|
+
}
|
|
9291
|
+
const locs = ids.map((id2) => locateVectorNode(this.doc.nodes, id2));
|
|
9292
|
+
const parent = locs[0]?.parent ?? null;
|
|
9293
|
+
if (locs.some((l) => (l.parent?.id ?? null) !== (parent?.id ?? null))) return { ok: false, message: "The objects must share a parent (pick objects at one level)." };
|
|
9294
|
+
const b = unionBounds(ids.map((id2) => vectorNodeBounds(this.getNode(id2))));
|
|
9295
|
+
const center = input.center ?? (b ? { x: b.x + b.width / 2, y: b.y + b.height / 2 } : { x: this.doc.width / 2, y: this.doc.height / 2 });
|
|
9296
|
+
const id = this.newId();
|
|
9297
|
+
const group = { id, type: "group", name: input.name ?? "Symmetry", symmetry: { ...symmetry, center }, children: [] };
|
|
9298
|
+
const order = ids.slice().sort((a, c) => locs[ids.indexOf(a)].index - locs[ids.indexOf(c)].index);
|
|
9299
|
+
const set = new Set(order);
|
|
9300
|
+
this.change("Make Symmetry", () => {
|
|
9301
|
+
if (!order.length) this.setDoc([...this.doc.nodes, group]);
|
|
9302
|
+
else {
|
|
9303
|
+
const regroup = (list) => {
|
|
9304
|
+
const members = order.map((m) => list.find((n2) => n2.id === m));
|
|
9305
|
+
const top = Math.max(...members.map((m) => list.indexOf(m)));
|
|
9306
|
+
const out = [];
|
|
9307
|
+
list.forEach((n2, i) => {
|
|
9308
|
+
if (i === top) out.push({ ...group, children: members });
|
|
9309
|
+
if (!set.has(n2.id)) out.push(n2);
|
|
9310
|
+
});
|
|
9311
|
+
return out;
|
|
9312
|
+
};
|
|
9313
|
+
if (!parent) this.setDoc(regroup(this.doc.nodes));
|
|
9314
|
+
else this.setDoc(mapTree(this.doc.nodes, (n2) => n2.id === parent.id && n2.type === "group" ? { ...n2, children: regroup(n2.children) } : n2));
|
|
9315
|
+
}
|
|
9316
|
+
this.selection = [id];
|
|
9317
|
+
this.anchors = [];
|
|
9318
|
+
this.editingId = null;
|
|
9319
|
+
if (!order.length && !parent) this.symmetryTarget = id;
|
|
9320
|
+
});
|
|
9321
|
+
return { ok: true, id };
|
|
9322
|
+
}
|
|
9323
|
+
/** Symmetry groups among `ids` (default: the selection, or the group holding it). */
|
|
9324
|
+
symmetryTargets(ids) {
|
|
9325
|
+
const out = /* @__PURE__ */ new Map();
|
|
9326
|
+
for (const id of ids ?? this.selection) {
|
|
9327
|
+
const n2 = this.getNode(id);
|
|
9328
|
+
if (isVectorSymmetryGroup(n2)) out.set(n2.id, n2);
|
|
9329
|
+
else if (!ids) {
|
|
9330
|
+
const p = locateVectorNode(this.doc.nodes, id)?.parent;
|
|
9331
|
+
if (isVectorSymmetryGroup(p)) out.set(p.id, p);
|
|
9332
|
+
}
|
|
9333
|
+
}
|
|
9334
|
+
return [...out.values()];
|
|
9335
|
+
}
|
|
9336
|
+
/** Changes symmetry groups (default: the selected one, or the one holding the selection): mode, centre (the group's space), rotation, radial copies and mirroring. One undo step (merged while `merge` is the same key). */
|
|
9337
|
+
setSymmetry(patch, options = {}) {
|
|
9338
|
+
if (options.ids) {
|
|
9339
|
+
for (const id of options.ids) if (!isVectorSymmetryGroup(this.getNode(id))) return { ok: false, message: `"${id}" is not a symmetry group (make one with vector.makeSymmetry).` };
|
|
9340
|
+
}
|
|
9341
|
+
const groups = this.symmetryTargets(options.ids);
|
|
9342
|
+
if (!groups.length) return { ok: false, message: "Select a symmetry group (or something in one)." };
|
|
9343
|
+
if (patch.mode !== void 0 && !VECTOR_SYMMETRY_MODES.includes(patch.mode)) return { ok: false, message: `Unknown symmetry "${patch.mode}": use ${VECTOR_SYMMETRY_MODES.join(", ")}.` };
|
|
9344
|
+
if (patch.copies !== void 0 && !(patch.copies >= 2 && patch.copies <= 64)) return { ok: false, message: "copies must be from 2 to 64." };
|
|
9345
|
+
const next = new Map(groups.map((g) => [g.id, sanitizeVectorSymmetry({ ...g.symmetry, ...patch })]));
|
|
9346
|
+
if ([...next.values()].some((v) => !v)) return { ok: false, message: "center must be { x, y } numbers." };
|
|
9347
|
+
this.change(
|
|
9348
|
+
"Symmetry",
|
|
9349
|
+
() => {
|
|
9350
|
+
this.setDoc(mapTree(this.doc.nodes, (n2) => next.has(n2.id) && n2.type === "group" ? { ...n2, symmetry: next.get(n2.id) } : n2));
|
|
9351
|
+
},
|
|
9352
|
+
options.merge
|
|
9353
|
+
);
|
|
9354
|
+
return { ok: true };
|
|
9355
|
+
}
|
|
9356
|
+
/**
|
|
9357
|
+
* Release Symmetry: the groups stop repeating their children. With `expand`, the copies become
|
|
9358
|
+
* real objects in the group (new ids); otherwise they go. One undo step. Returns the ids of the
|
|
9359
|
+
* objects added by expanding.
|
|
9360
|
+
*/
|
|
9361
|
+
releaseSymmetry(options = {}) {
|
|
9362
|
+
const groups = this.symmetryTargets(options.ids);
|
|
9363
|
+
if (!groups.length) return { ok: false, message: "Select a symmetry group (or something in one)." };
|
|
9364
|
+
const added = [];
|
|
9365
|
+
const next = /* @__PURE__ */ new Map();
|
|
9366
|
+
for (const g of groups) {
|
|
9367
|
+
const { symmetry: _s, ...rest } = g;
|
|
9368
|
+
if (!options.expand) {
|
|
9369
|
+
next.set(g.id, rest);
|
|
9370
|
+
continue;
|
|
9371
|
+
}
|
|
9372
|
+
const drawn = vectorSymmetryDrawn(g);
|
|
9373
|
+
const fresh = (n2) => {
|
|
9374
|
+
const id = this.newId();
|
|
9375
|
+
added.push(id);
|
|
9376
|
+
const { locked: _l, ...plain2 } = n2;
|
|
9377
|
+
return plain2.type === "group" ? { ...plain2, id, children: plain2.children.map(fresh) } : { ...plain2, id };
|
|
9378
|
+
};
|
|
9379
|
+
next.set(g.id, { ...rest, children: [...g.children, ...drawn.children.slice(g.children.length).map(fresh)] });
|
|
9380
|
+
}
|
|
9381
|
+
this.change(options.expand ? "Expand Symmetry" : "Release Symmetry", () => {
|
|
9382
|
+
this.setDoc(mapTree(this.doc.nodes, (n2) => next.get(n2.id) ?? n2));
|
|
9383
|
+
if (this.symmetryTarget && next.has(this.symmetryTarget)) this.symmetryTarget = null;
|
|
9384
|
+
});
|
|
9385
|
+
return { ok: true, ids: added.filter((id) => !!this.getNode(id)) };
|
|
9386
|
+
}
|
|
9387
|
+
/** Enters a top-level symmetry group: new pen, pencil, shape and text art goes into it (mirrored as it is drawn) until `exitSymmetry` or Escape. */
|
|
9388
|
+
enterSymmetry(id) {
|
|
9389
|
+
const n2 = this.doc.nodes.find((x) => x.id === id);
|
|
9390
|
+
if (!isVectorSymmetryGroup(n2)) return { ok: false, message: `"${id}" is not a top-level symmetry group.` };
|
|
9391
|
+
if (n2.transform) return { ok: false, message: "Drawing into a turned symmetry group is not supported; draw beside it and move the art in." };
|
|
9392
|
+
this.symmetryTarget = id;
|
|
9393
|
+
this.selection = [id];
|
|
9394
|
+
this.notify();
|
|
9395
|
+
return { ok: true };
|
|
9396
|
+
}
|
|
9397
|
+
/** Stops drawing into the entered symmetry group. */
|
|
9398
|
+
exitSymmetry() {
|
|
9399
|
+
if (!this.symmetryTarget) return;
|
|
9400
|
+
this.symmetryTarget = null;
|
|
9401
|
+
this.notify();
|
|
9402
|
+
}
|
|
9403
|
+
// -----------------------------------------------------------------------------------------
|
|
9137
9404
|
// PDF
|
|
9138
9405
|
/**
|
|
9139
9406
|
* The document as a vector PDF (see `vectorDocumentToPdf`): one page of the artboard, or a page
|
|
@@ -11605,7 +11872,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11605
11872
|
}
|
|
11606
11873
|
const pt2 = this.snapDrawn(p, null, input, () => this.drawingTargets(null));
|
|
11607
11874
|
const node = { id: this.newId(), type: "path", name: "Path", ...this.styleFor(true), subpaths: [{ anchors: [{ x: pt2.x, y: pt2.y }], closed: false }] };
|
|
11608
|
-
this.
|
|
11875
|
+
this.addDrawn(node);
|
|
11609
11876
|
this.pen = { nodeId: node.id, subpath: 0 };
|
|
11610
11877
|
this.selection = [node.id];
|
|
11611
11878
|
this.editingId = node.id;
|
|
@@ -11626,7 +11893,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11626
11893
|
strokeLinejoin: this.defaults.strokeLinejoin ?? "round",
|
|
11627
11894
|
subpaths: [{ anchors: [{ x: p.x, y: p.y }], closed: false }]
|
|
11628
11895
|
};
|
|
11629
|
-
this.
|
|
11896
|
+
this.addDrawn(node);
|
|
11630
11897
|
const lazy = new LazyMouse(this.editing.pencilSmoothing * VECTOR_PENCIL_STRING_PX / this.viewport.zoom);
|
|
11631
11898
|
lazy.push([p.x, p.y]);
|
|
11632
11899
|
const mode = this.editing.pencilPressure;
|
|
@@ -11644,7 +11911,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
11644
11911
|
const kind = this.tool === "frame" ? "rect" : this.tool;
|
|
11645
11912
|
const node = this.createShapeNode({ kind, x: start.x, y: start.y, width: 0, height: 0 });
|
|
11646
11913
|
if (this.tool === "frame") Object.assign(node, { fill: "#ffffff", stroke: "#a3a3a3", strokeWidth: 1 / this.viewport.zoom });
|
|
11647
|
-
this.
|
|
11914
|
+
this.addDrawn(node);
|
|
11648
11915
|
this.drag = { kind: "shape", tool: this.tool, start, nodeId: node.id, before, targets };
|
|
11649
11916
|
this.interaction = { type: "draw", tool: this.tool, nodeId: node.id, bounds: { x: start.x, y: start.y, width: 0, height: 0 } };
|
|
11650
11917
|
this.notify(true);
|
|
@@ -12787,7 +13054,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
12787
13054
|
this.endTextEdit();
|
|
12788
13055
|
const before = this.snapshot();
|
|
12789
13056
|
if (replace) this.setDoc(mapTree(this.doc.nodes, (n2) => n2.id === replace ? node : n2));
|
|
12790
|
-
else this.
|
|
13057
|
+
else this.addDrawn(node);
|
|
12791
13058
|
this.selection = [node.id];
|
|
12792
13059
|
this.anchors = [];
|
|
12793
13060
|
this.textEdit = { nodeId: node.id, anchor: 0, focus: 0, before, key: `text-edit:${node.id}:${++this.textEditCount}`, created: true };
|
|
@@ -13341,6 +13608,7 @@ var VectorEditorController = class _VectorEditorController {
|
|
|
13341
13608
|
case "Escape":
|
|
13342
13609
|
if (this.drag) this.cancelDrag();
|
|
13343
13610
|
else if (this.trackingPoints.length) this.clearTracking();
|
|
13611
|
+
else if (this.symmetryTarget && !this.pen) this.exitSymmetry();
|
|
13344
13612
|
else if (this.widthEdit) this.closeWidthPointEdit();
|
|
13345
13613
|
else if (this.tool === "width" && this.widthPoints.length) {
|
|
13346
13614
|
this.widthPoints = [];
|
|
@@ -13534,11 +13802,12 @@ function pencilPressureProfile(points, pressures) {
|
|
|
13534
13802
|
}
|
|
13535
13803
|
|
|
13536
13804
|
// src/vector/commands.ts
|
|
13537
|
-
var EFFECT_PARAMS_DESCRIPTION = "Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (\u2212100\u2026100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above. envelope: style (a warp preset: arc|arcLower|arcUpper|arch|bulge|shellLower|shellUpper|flag|wave|fish|rise|fisheye|inflate|squeeze|twist, or mesh), bend, horizontal, vertical (percent, \u2212100\u2026100), orientation horizontal|vertical, rows, columns, mesh { us, vs, points } (node space; prefer vector.envelope).";
|
|
13805
|
+
var EFFECT_PARAMS_DESCRIPTION = "Effect parameters (missing ones keep their value or default). trim: start, end (percent), offset (degrees), mode simultaneous|individual. roundCorners: radius. puckerBloat: amount (\u2212100\u2026100). twist: angle, center {x, y} (offset from the middle). zigzag: size, ridges, smooth. wiggle: size, detail, smooth, speed, correlation, phase, seed, time (seconds). offset: amount, join miter|round|bevel, miterLimit, copies, copyOffset. merge: mode merge|add|subtract|intersect|exclude. repeater: copies (fractions fade the last), offset, position {x, y}, anchor {x, y} (offset from the middle), rotation, scale {x, y} (percent), startOpacity, endOpacity (percent), composite below|above. contour: direction inside|outside|center, steps, offset, fillColor / strokeColor (end colours, null keeps the colour), acceleration (\u2212100\u2026100), join. envelope: style (a warp preset: arc|arcLower|arcUpper|arch|bulge|shellLower|shellUpper|flag|wave|fish|rise|fisheye|inflate|squeeze|twist, or mesh), bend, horizontal, vertical (percent, \u2212100\u2026100), orientation horizontal|vertical, rows, columns, mesh { us, vs, points } (node space; prefer vector.envelope).";
|
|
13538
13806
|
var fail = (message) => {
|
|
13539
13807
|
throw new Error(message);
|
|
13540
13808
|
};
|
|
13541
13809
|
var IDS = { type: "array", items: { type: "string" }, description: "Node ids (default: the selection)." };
|
|
13810
|
+
var PARENT_ID = { type: "string", description: "A group to add into, on top of its children (default: the document). Coordinates are the group's own space (the document's for an unturned top-level group)." };
|
|
13542
13811
|
var ANCHORS = {
|
|
13543
13812
|
type: "array",
|
|
13544
13813
|
items: {
|
|
@@ -13678,6 +13947,11 @@ function createVectorCommands(controller, options = {}) {
|
|
|
13678
13947
|
const prefix = options.prefix ?? "vector.";
|
|
13679
13948
|
const group = options.group ?? "Vector";
|
|
13680
13949
|
const base = { group, ...options.scope ? { scope: options.scope } : {} };
|
|
13950
|
+
const parentOf = (parentId) => {
|
|
13951
|
+
if (parentId === void 0) return {};
|
|
13952
|
+
if (controller.getNode(parentId)?.type !== "group") fail(`"${parentId}" is not a group to add into.`);
|
|
13953
|
+
return { parentId };
|
|
13954
|
+
};
|
|
13681
13955
|
const hasSelection = () => controller.getState().selection.length ? true : "Nothing is selected.";
|
|
13682
13956
|
const ids = (params) => {
|
|
13683
13957
|
const list2 = params?.ids ?? controller.getState().selection;
|
|
@@ -13724,7 +13998,8 @@ function createVectorCommands(controller, options = {}) {
|
|
|
13724
13998
|
...isVectorMaskGroup(n2) ? { mask: vectorMaskSummary(n2) } : {},
|
|
13725
13999
|
...isVectorFrame(n2) ? { frameRect: n2.frame } : {},
|
|
13726
14000
|
...n2.resizing ? { resizing: n2.resizing } : {},
|
|
13727
|
-
...n2.type === "path" && n2.connector ? { connector: n2.connector } : {}
|
|
14001
|
+
...n2.type === "path" && n2.connector ? { connector: n2.connector } : {},
|
|
14002
|
+
...n2.type === "group" && n2.symmetry ? { symmetry: n2.symmetry } : {}
|
|
13728
14003
|
});
|
|
13729
14004
|
if (n2.type === "group") visit(n2.children, depth + 1);
|
|
13730
14005
|
}
|
|
@@ -14605,6 +14880,89 @@ function createVectorCommands(controller, options = {}) {
|
|
|
14605
14880
|
issues: state.constraintIssues
|
|
14606
14881
|
};
|
|
14607
14882
|
};
|
|
14883
|
+
const SYMMETRY_PROPS = {
|
|
14884
|
+
mode: { type: "string", enum: VECTOR_SYMMETRY_MODES, description: "vertical (mirror left / right), horizontal (top / bottom), dual (four quarters), diagonal, or radial (turns round the centre)." },
|
|
14885
|
+
center: { type: "object", properties: { x: { type: "number", description: "Document units (the group's space)." }, y: { type: "number", description: "Document units (the group's space)." } }, required: ["x", "y"], additionalProperties: false, description: "Where the axes cross." },
|
|
14886
|
+
rotation: { type: "number", description: "Turn of the axes, degrees clockwise (default 0)." },
|
|
14887
|
+
copies: { type: "integer", minimum: 2, maximum: 64, description: "Radial: how many turns round the circle (default 6)." },
|
|
14888
|
+
mirror: { type: "boolean", description: "Radial: mirror each turn too, like a kaleidoscope (default false)." }
|
|
14889
|
+
};
|
|
14890
|
+
const symmetryResult = (id) => {
|
|
14891
|
+
const n2 = controller.getNode(id);
|
|
14892
|
+
return n2?.type === "group" && n2.symmetry ? { id, symmetry: n2.symmetry, children: n2.children.map((c) => c.id) } : fail(`"${id}" is not a symmetry group.`);
|
|
14893
|
+
};
|
|
14894
|
+
const symmetryCommands = () => [
|
|
14895
|
+
{
|
|
14896
|
+
...base,
|
|
14897
|
+
id: `${prefix}makeSymmetry`,
|
|
14898
|
+
label: "Make Symmetry\u2026",
|
|
14899
|
+
keywords: ["symmetry", "mirror", "kaleidoscope", "radial", "mandala", "reflect", "live symmetry"],
|
|
14900
|
+
description: "Puts objects sharing a parent (`ids`, default the selection) into a live symmetry group: it draws them with their mirrored or rotated copies, which follow every edit of the originals. center defaults to the objects' centre (the artboard's with no objects: an empty group to add art to with parentId). One undo step. Returns { id, symmetry, children }.",
|
|
14901
|
+
params: { type: "object", properties: { ids: { ...IDS, description: "Objects to repeat (default: the selection; [] for an empty group)." }, ...SYMMETRY_PROPS, name: { type: "string", description: 'Group name (default "Symmetry").' } }, required: ["mode"], additionalProperties: false },
|
|
14902
|
+
undoable: true,
|
|
14903
|
+
run: (_ctx, params) => {
|
|
14904
|
+
const { ids: list2, ...input } = params;
|
|
14905
|
+
const r = controller.makeSymmetry(input, list2 ? { ids: list2 } : {});
|
|
14906
|
+
if (!r.ok) fail(r.message);
|
|
14907
|
+
return symmetryResult(r.id);
|
|
14908
|
+
}
|
|
14909
|
+
},
|
|
14910
|
+
{
|
|
14911
|
+
...base,
|
|
14912
|
+
id: `${prefix}setSymmetry`,
|
|
14913
|
+
label: "Symmetry Options\u2026",
|
|
14914
|
+
keywords: ["symmetry", "mirror", "radial", "axis", "copies"],
|
|
14915
|
+
description: "Changes symmetry groups (`ids`, default the selected one or the one holding the selection): mode, center, rotation, copies, mirror. Pass at least one. One undo step. Returns { groups }.",
|
|
14916
|
+
params: { type: "object", properties: { ids: { ...IDS, description: "Symmetry group ids (default: the selection)." }, ...SYMMETRY_PROPS }, additionalProperties: false },
|
|
14917
|
+
undoable: true,
|
|
14918
|
+
run: (_ctx, params) => {
|
|
14919
|
+
const { ids: list2, ...patch } = params ?? {};
|
|
14920
|
+
if (!Object.keys(patch).length) fail("Pass at least one of mode, center, rotation, copies, mirror.");
|
|
14921
|
+
check(controller.setSymmetry(patch, list2 ? { ids: list2 } : {}));
|
|
14922
|
+
const ids2 = list2 ?? controller.getState().selection.filter((id) => controller.getNode(id)?.type === "group");
|
|
14923
|
+
return { groups: ids2.filter((id) => controller.getNode(id)?.symmetry).map(symmetryResult) };
|
|
14924
|
+
}
|
|
14925
|
+
},
|
|
14926
|
+
{
|
|
14927
|
+
...base,
|
|
14928
|
+
id: `${prefix}releaseSymmetry`,
|
|
14929
|
+
label: "Release Symmetry",
|
|
14930
|
+
keywords: ["symmetry", "release", "expand", "bake"],
|
|
14931
|
+
description: "Stops symmetry groups repeating their children. With expand: true the copies become real, editable objects in the group (new ids); otherwise they go. One undo step. Returns { ids } of the objects expanding added.",
|
|
14932
|
+
params: { type: "object", properties: { ids: { ...IDS, description: "Symmetry group ids (default: the selection)." }, expand: { type: "boolean", description: "Keep the copies as real objects (default false)." } }, additionalProperties: false },
|
|
14933
|
+
undoable: true,
|
|
14934
|
+
enabled: enabledFor,
|
|
14935
|
+
run: (_ctx, params) => {
|
|
14936
|
+
const r = controller.releaseSymmetry({ ...params?.ids ? { ids: params.ids } : {}, ...params?.expand ? { expand: true } : {} });
|
|
14937
|
+
check(r);
|
|
14938
|
+
return { ids: r.ids ?? [] };
|
|
14939
|
+
}
|
|
14940
|
+
},
|
|
14941
|
+
{
|
|
14942
|
+
...base,
|
|
14943
|
+
id: `${prefix}enterSymmetry`,
|
|
14944
|
+
label: "Draw Inside Symmetry",
|
|
14945
|
+
keywords: ["symmetry", "draw inside", "mirror drawing"],
|
|
14946
|
+
description: "Hands the canvas to the user drawing inside a top-level symmetry group: new pen, pencil, shape and text art goes into it, mirrored as it is drawn, until vector.exitSymmetry or Escape. Returns { id }.",
|
|
14947
|
+
params: { type: "object", properties: { id: { type: "string", description: "The symmetry group." } }, required: ["id"], additionalProperties: false },
|
|
14948
|
+
run: (_ctx, params) => {
|
|
14949
|
+
check(controller.enterSymmetry(params.id));
|
|
14950
|
+
return { id: params.id };
|
|
14951
|
+
}
|
|
14952
|
+
},
|
|
14953
|
+
{
|
|
14954
|
+
...base,
|
|
14955
|
+
id: `${prefix}exitSymmetry`,
|
|
14956
|
+
label: "Stop Drawing Inside Symmetry",
|
|
14957
|
+
keywords: ["symmetry", "exit"],
|
|
14958
|
+
description: "New art goes on top of the document again (no symmetry group is entered). Returns { ok: true }.",
|
|
14959
|
+
params: { type: "object", properties: {}, additionalProperties: false },
|
|
14960
|
+
run: () => {
|
|
14961
|
+
controller.exitSymmetry();
|
|
14962
|
+
return { ok: true };
|
|
14963
|
+
}
|
|
14964
|
+
}
|
|
14965
|
+
];
|
|
14608
14966
|
const pdfCommands = () => [
|
|
14609
14967
|
{
|
|
14610
14968
|
...base,
|
|
@@ -15363,12 +15721,16 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15363
15721
|
points: { type: "integer", minimum: 3 },
|
|
15364
15722
|
innerRatio: { type: "number", minimum: 0, maximum: 1 },
|
|
15365
15723
|
name: { type: "string" },
|
|
15724
|
+
parentId: PARENT_ID,
|
|
15366
15725
|
...STYLE_PROPS
|
|
15367
15726
|
},
|
|
15368
15727
|
required: ["kind", "x", "y", "width", "height"]
|
|
15369
15728
|
},
|
|
15370
15729
|
undoable: true,
|
|
15371
|
-
run: (_ctx, params) =>
|
|
15730
|
+
run: (_ctx, params) => {
|
|
15731
|
+
const { parentId, ...input } = params;
|
|
15732
|
+
return { id: controller.addShape(input, parentOf(parentId)) };
|
|
15733
|
+
}
|
|
15372
15734
|
},
|
|
15373
15735
|
{
|
|
15374
15736
|
...base,
|
|
@@ -15376,10 +15738,10 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15376
15738
|
label: "Add Path\u2026",
|
|
15377
15739
|
keywords: ["svg", "path data", "bezier", "curve"],
|
|
15378
15740
|
description: "Adds a path from SVG path data (`d`), in document units.",
|
|
15379
|
-
params: { type: "object", properties: { d: { type: "string" }, name: { type: "string" }, ...STYLE_PROPS }, required: ["d"] },
|
|
15741
|
+
params: { type: "object", properties: { d: { type: "string" }, name: { type: "string" }, parentId: PARENT_ID, ...STYLE_PROPS }, required: ["d"] },
|
|
15380
15742
|
undoable: true,
|
|
15381
15743
|
run: (_ctx, params) => {
|
|
15382
|
-
const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} });
|
|
15744
|
+
const id = controller.addPath(params.d, { ...pickStyle(params), ...params.name ? { name: params.name } : {} }, parentOf(params.parentId));
|
|
15383
15745
|
const node = controller.getNode(id);
|
|
15384
15746
|
if (node?.type === "path" && !node.subpaths.length) {
|
|
15385
15747
|
controller.undo();
|
|
@@ -15436,6 +15798,7 @@ function createVectorCommands(controller, options = {}) {
|
|
|
15436
15798
|
...traceCommands(),
|
|
15437
15799
|
...lottieCommands(),
|
|
15438
15800
|
...pdfCommands(),
|
|
15801
|
+
...symmetryCommands(),
|
|
15439
15802
|
{
|
|
15440
15803
|
...base,
|
|
15441
15804
|
id: `${prefix}setCorners`,
|
|
@@ -16427,6 +16790,8 @@ export {
|
|
|
16427
16790
|
VECTOR_STROKE_SETTING_KEYS,
|
|
16428
16791
|
VECTOR_STYLE_KEYS,
|
|
16429
16792
|
VECTOR_SYMBOL_MAX_DEPTH,
|
|
16793
|
+
VECTOR_SYMMETRY_MODES,
|
|
16794
|
+
VECTOR_SYMMETRY_MODE_LABELS,
|
|
16430
16795
|
VECTOR_TEXT_ALIGNS,
|
|
16431
16796
|
VECTOR_TEXT_ALIGN_LABELS,
|
|
16432
16797
|
VECTOR_TEXT_ALIGN_OPTIONS,
|
|
@@ -16612,6 +16977,7 @@ export {
|
|
|
16612
16977
|
isVectorMaskType,
|
|
16613
16978
|
isVectorMeshGradient,
|
|
16614
16979
|
isVectorShadedPaint,
|
|
16980
|
+
isVectorSymmetryGroup,
|
|
16615
16981
|
isVectorTextTarget,
|
|
16616
16982
|
joinSubpaths,
|
|
16617
16983
|
knifePath,
|
|
@@ -16720,6 +17086,7 @@ export {
|
|
|
16720
17086
|
sanitizeVectorShadedPaint,
|
|
16721
17087
|
sanitizeVectorStrokeSettings,
|
|
16722
17088
|
sanitizeVectorStrokes,
|
|
17089
|
+
sanitizeVectorSymmetry,
|
|
16723
17090
|
sanitizeVectorTextFields,
|
|
16724
17091
|
sanitizeVectorTextFrame,
|
|
16725
17092
|
sanitizeWidthProfile,
|
|
@@ -16763,6 +17130,7 @@ export {
|
|
|
16763
17130
|
transformVectorNode,
|
|
16764
17131
|
transformVectorPaint,
|
|
16765
17132
|
transformVectorShadedPaint,
|
|
17133
|
+
transformVectorSymmetry,
|
|
16766
17134
|
translateAnchor,
|
|
16767
17135
|
translateMatrix,
|
|
16768
17136
|
trimIntervals,
|
|
@@ -16927,6 +17295,9 @@ export {
|
|
|
16927
17295
|
vectorStrokes,
|
|
16928
17296
|
vectorSymbolUses,
|
|
16929
17297
|
vectorSymbolsUsedIn,
|
|
17298
|
+
vectorSymmetryAxes,
|
|
17299
|
+
vectorSymmetryDrawn,
|
|
17300
|
+
vectorSymmetryMatrices,
|
|
16930
17301
|
vectorTargetPaint,
|
|
16931
17302
|
vectorTextBoxOutline,
|
|
16932
17303
|
vectorTextCaret,
|