@pooder/kit 3.3.0 → 3.5.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/CHANGELOG.md +12 -0
- package/dist/index.d.mts +53 -57
- package/dist/index.d.ts +53 -57
- package/dist/index.js +1081 -930
- package/dist/index.mjs +1080 -929
- package/package.json +1 -1
- package/src/CanvasService.ts +65 -65
- package/src/background.ts +230 -230
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +282 -218
- package/src/feature.ts +724 -0
- package/src/film.ts +194 -194
- package/src/geometry.ts +118 -370
- package/src/image.ts +471 -496
- package/src/index.ts +1 -1
- package/src/mirror.ts +128 -128
- package/src/ruler.ts +500 -500
- package/src/tracer.ts +570 -372
- package/src/white-ink.ts +373 -373
- package/src/hole.ts +0 -786
package/dist/index.mjs
CHANGED
|
@@ -208,6 +208,7 @@ import {
|
|
|
208
208
|
import { Path, Pattern } from "fabric";
|
|
209
209
|
|
|
210
210
|
// src/tracer.ts
|
|
211
|
+
import paper from "paper";
|
|
211
212
|
var ImageTracer = class {
|
|
212
213
|
/**
|
|
213
214
|
* Main entry point: Traces an image URL to an SVG path string.
|
|
@@ -215,7 +216,7 @@ var ImageTracer = class {
|
|
|
215
216
|
* @param options Configuration options.
|
|
216
217
|
*/
|
|
217
218
|
static async trace(imageUrl, options = {}) {
|
|
218
|
-
var _a, _b;
|
|
219
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
219
220
|
const img = await this.loadImage(imageUrl);
|
|
220
221
|
const width = img.width;
|
|
221
222
|
const height = img.height;
|
|
@@ -226,20 +227,250 @@ var ImageTracer = class {
|
|
|
226
227
|
if (!ctx) throw new Error("Could not get 2D context");
|
|
227
228
|
ctx.drawImage(img, 0, 0);
|
|
228
229
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
230
|
+
const threshold = (_a = options.threshold) != null ? _a : 10;
|
|
231
|
+
const adaptiveRadius = Math.max(
|
|
232
|
+
5,
|
|
233
|
+
Math.floor(Math.max(width, height) * 0.02)
|
|
234
|
+
);
|
|
235
|
+
const radius = (_b = options.morphologyRadius) != null ? _b : adaptiveRadius;
|
|
236
|
+
const expand = (_c = options.expand) != null ? _c : 0;
|
|
237
|
+
const padding = radius + expand + 2;
|
|
238
|
+
const paddedWidth = width + padding * 2;
|
|
239
|
+
const paddedHeight = height + padding * 2;
|
|
240
|
+
let mask = this.createMask(imageData, threshold, padding, paddedWidth, paddedHeight);
|
|
241
|
+
if (radius > 0) {
|
|
242
|
+
mask = this.circularMorphology(mask, paddedWidth, paddedHeight, radius, "closing");
|
|
243
|
+
mask = this.fillHoles(mask, paddedWidth, paddedHeight);
|
|
244
|
+
const smoothRadius = Math.max(2, Math.floor(radius * 0.3));
|
|
245
|
+
mask = this.circularMorphology(mask, paddedWidth, paddedHeight, smoothRadius, "closing");
|
|
246
|
+
} else {
|
|
247
|
+
mask = this.fillHoles(mask, paddedWidth, paddedHeight);
|
|
248
|
+
}
|
|
249
|
+
if (expand > 0) {
|
|
250
|
+
mask = this.circularMorphology(mask, paddedWidth, paddedHeight, expand, "dilate");
|
|
251
|
+
}
|
|
252
|
+
const allContourPoints = this.traceAllContours(mask, paddedWidth, paddedHeight);
|
|
253
|
+
if (allContourPoints.length === 0) {
|
|
254
|
+
const w = (_d = options.scaleToWidth) != null ? _d : width;
|
|
255
|
+
const h = (_e = options.scaleToHeight) != null ? _e : height;
|
|
256
|
+
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
|
|
257
|
+
}
|
|
258
|
+
const primaryContour = allContourPoints.sort(
|
|
259
|
+
(a, b) => b.length - a.length
|
|
260
|
+
)[0];
|
|
261
|
+
const unpaddedPoints = primaryContour.map((p) => ({
|
|
262
|
+
x: p.x - padding,
|
|
263
|
+
y: p.y - padding
|
|
264
|
+
}));
|
|
265
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
266
|
+
for (const p of unpaddedPoints) {
|
|
267
|
+
if (p.x < minX) minX = p.x;
|
|
268
|
+
if (p.y < minY) minY = p.y;
|
|
269
|
+
if (p.x > maxX) maxX = p.x;
|
|
270
|
+
if (p.y > maxY) maxY = p.y;
|
|
271
|
+
}
|
|
272
|
+
const globalBounds = {
|
|
273
|
+
minX,
|
|
274
|
+
minY,
|
|
275
|
+
width: maxX - minX,
|
|
276
|
+
height: maxY - minY
|
|
277
|
+
};
|
|
278
|
+
let finalPoints = unpaddedPoints;
|
|
279
|
+
if (options.scaleToWidth && options.scaleToHeight) {
|
|
232
280
|
finalPoints = this.scalePoints(
|
|
233
|
-
|
|
281
|
+
unpaddedPoints,
|
|
234
282
|
options.scaleToWidth,
|
|
235
|
-
options.scaleToHeight
|
|
283
|
+
options.scaleToHeight,
|
|
284
|
+
globalBounds
|
|
236
285
|
);
|
|
237
286
|
}
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
(
|
|
241
|
-
|
|
242
|
-
|
|
287
|
+
const useSmoothing = options.smoothing !== false;
|
|
288
|
+
if (useSmoothing) {
|
|
289
|
+
return this.pointsToSVGPaper(finalPoints, (_f = options.simplifyTolerance) != null ? _f : 2.5);
|
|
290
|
+
} else {
|
|
291
|
+
const simplifiedPoints = this.douglasPeucker(
|
|
292
|
+
finalPoints,
|
|
293
|
+
(_g = options.simplifyTolerance) != null ? _g : 2
|
|
294
|
+
);
|
|
295
|
+
return this.pointsToSVG(simplifiedPoints);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
static createMask(imageData, threshold, padding, paddedWidth, paddedHeight) {
|
|
299
|
+
const { width, height, data } = imageData;
|
|
300
|
+
const mask = new Uint8Array(paddedWidth * paddedHeight);
|
|
301
|
+
let hasTransparency = false;
|
|
302
|
+
for (let i = 3; i < data.length; i += 4) {
|
|
303
|
+
if (data[i] < 255) {
|
|
304
|
+
hasTransparency = true;
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
for (let y = 0; y < height; y++) {
|
|
309
|
+
for (let x = 0; x < width; x++) {
|
|
310
|
+
const srcIdx = (y * width + x) * 4;
|
|
311
|
+
const r = data[srcIdx];
|
|
312
|
+
const g = data[srcIdx + 1];
|
|
313
|
+
const b = data[srcIdx + 2];
|
|
314
|
+
const a = data[srcIdx + 3];
|
|
315
|
+
const destIdx = (y + padding) * paddedWidth + (x + padding);
|
|
316
|
+
if (hasTransparency) {
|
|
317
|
+
if (a > threshold) {
|
|
318
|
+
mask[destIdx] = 1;
|
|
319
|
+
}
|
|
320
|
+
} else {
|
|
321
|
+
if (!(r > 240 && g > 240 && b > 240)) {
|
|
322
|
+
mask[destIdx] = 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return mask;
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Fast circular morphology using a distance-transform inspired separable approach.
|
|
331
|
+
* O(N * R) complexity, where R is the radius.
|
|
332
|
+
*/
|
|
333
|
+
static circularMorphology(mask, width, height, radius, op) {
|
|
334
|
+
const dilate = (m, r) => {
|
|
335
|
+
const horizontalDist = new Int32Array(width * height);
|
|
336
|
+
for (let y = 0; y < height; y++) {
|
|
337
|
+
let lastSolid = -r * 2;
|
|
338
|
+
for (let x = 0; x < width; x++) {
|
|
339
|
+
if (m[y * width + x]) lastSolid = x;
|
|
340
|
+
horizontalDist[y * width + x] = x - lastSolid;
|
|
341
|
+
}
|
|
342
|
+
lastSolid = width + r * 2;
|
|
343
|
+
for (let x = width - 1; x >= 0; x--) {
|
|
344
|
+
if (m[y * width + x]) lastSolid = x;
|
|
345
|
+
horizontalDist[y * width + x] = Math.min(
|
|
346
|
+
horizontalDist[y * width + x],
|
|
347
|
+
lastSolid - x
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
const result = new Uint8Array(width * height);
|
|
352
|
+
const r2 = r * r;
|
|
353
|
+
for (let x = 0; x < width; x++) {
|
|
354
|
+
for (let y = 0; y < height; y++) {
|
|
355
|
+
let found = false;
|
|
356
|
+
const minY = Math.max(0, y - r);
|
|
357
|
+
const maxY = Math.min(height - 1, y + r);
|
|
358
|
+
for (let dy = minY; dy <= maxY; dy++) {
|
|
359
|
+
const dY = dy - y;
|
|
360
|
+
const hDist = horizontalDist[dy * width + x];
|
|
361
|
+
if (hDist * hDist + dY * dY <= r2) {
|
|
362
|
+
found = true;
|
|
363
|
+
break;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
if (found) result[y * width + x] = 1;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
return result;
|
|
370
|
+
};
|
|
371
|
+
const erode = (m, r) => {
|
|
372
|
+
const inverted = new Uint8Array(m.length);
|
|
373
|
+
for (let i = 0; i < m.length; i++) inverted[i] = m[i] ? 0 : 1;
|
|
374
|
+
const dilatedInverted = dilate(inverted, r);
|
|
375
|
+
const result = new Uint8Array(m.length);
|
|
376
|
+
for (let i = 0; i < m.length; i++) result[i] = dilatedInverted[i] ? 0 : 1;
|
|
377
|
+
return result;
|
|
378
|
+
};
|
|
379
|
+
switch (op) {
|
|
380
|
+
case "dilate":
|
|
381
|
+
return dilate(mask, radius);
|
|
382
|
+
case "erode":
|
|
383
|
+
return erode(mask, radius);
|
|
384
|
+
case "closing":
|
|
385
|
+
return erode(dilate(mask, radius), radius);
|
|
386
|
+
case "opening":
|
|
387
|
+
return dilate(erode(mask, radius), radius);
|
|
388
|
+
default:
|
|
389
|
+
return mask;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Fills internal holes in the binary mask using flood fill from edges.
|
|
394
|
+
*/
|
|
395
|
+
static fillHoles(mask, width, height) {
|
|
396
|
+
const background = new Uint8Array(width * height);
|
|
397
|
+
const queue = [];
|
|
398
|
+
for (let x = 0; x < width; x++) {
|
|
399
|
+
if (mask[x] === 0) {
|
|
400
|
+
background[x] = 1;
|
|
401
|
+
queue.push([x, 0]);
|
|
402
|
+
}
|
|
403
|
+
const lastRow = (height - 1) * width + x;
|
|
404
|
+
if (mask[lastRow] === 0) {
|
|
405
|
+
background[lastRow] = 1;
|
|
406
|
+
queue.push([x, height - 1]);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
for (let y = 1; y < height - 1; y++) {
|
|
410
|
+
if (mask[y * width] === 0) {
|
|
411
|
+
background[y * width] = 1;
|
|
412
|
+
queue.push([0, y]);
|
|
413
|
+
}
|
|
414
|
+
if (mask[y * width + width - 1] === 0) {
|
|
415
|
+
background[y * width + width - 1] = 1;
|
|
416
|
+
queue.push([width - 1, y]);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
const dirs = [
|
|
420
|
+
[0, 1],
|
|
421
|
+
[0, -1],
|
|
422
|
+
[1, 0],
|
|
423
|
+
[-1, 0]
|
|
424
|
+
];
|
|
425
|
+
let head = 0;
|
|
426
|
+
while (head < queue.length) {
|
|
427
|
+
const [cx, cy] = queue[head++];
|
|
428
|
+
for (const [dx, dy] of dirs) {
|
|
429
|
+
const nx = cx + dx;
|
|
430
|
+
const ny = cy + dy;
|
|
431
|
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
432
|
+
const nidx = ny * width + nx;
|
|
433
|
+
if (mask[nidx] === 0 && background[nidx] === 0) {
|
|
434
|
+
background[nidx] = 1;
|
|
435
|
+
queue.push([nx, ny]);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
const filledMask = new Uint8Array(width * height);
|
|
441
|
+
for (let i = 0; i < width * height; i++) {
|
|
442
|
+
filledMask[i] = background[i] === 0 ? 1 : 0;
|
|
443
|
+
}
|
|
444
|
+
return filledMask;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Traces all contours in the mask with optimized start-point detection
|
|
448
|
+
*/
|
|
449
|
+
static traceAllContours(mask, width, height) {
|
|
450
|
+
const visited = new Uint8Array(width * height);
|
|
451
|
+
const allContours = [];
|
|
452
|
+
for (let y = 0; y < height; y++) {
|
|
453
|
+
for (let x = 0; x < width; x++) {
|
|
454
|
+
const idx = y * width + x;
|
|
455
|
+
if (mask[idx] && !visited[idx]) {
|
|
456
|
+
const isLeftEdge = x === 0 || mask[idx - 1] === 0;
|
|
457
|
+
if (isLeftEdge) {
|
|
458
|
+
const contour = this.marchingSquares(
|
|
459
|
+
mask,
|
|
460
|
+
visited,
|
|
461
|
+
x,
|
|
462
|
+
y,
|
|
463
|
+
width,
|
|
464
|
+
height
|
|
465
|
+
);
|
|
466
|
+
if (contour.length > 2) {
|
|
467
|
+
allContours.push(contour);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return allContours;
|
|
243
474
|
}
|
|
244
475
|
static loadImage(url) {
|
|
245
476
|
return new Promise((resolve, reject) => {
|
|
@@ -254,33 +485,11 @@ var ImageTracer = class {
|
|
|
254
485
|
* Moore-Neighbor Tracing Algorithm
|
|
255
486
|
* More robust for irregular shapes than simple Marching Squares walker.
|
|
256
487
|
*/
|
|
257
|
-
static marchingSquares(
|
|
258
|
-
const width = imageData.width;
|
|
259
|
-
const height = imageData.height;
|
|
260
|
-
const data = imageData.data;
|
|
488
|
+
static marchingSquares(mask, visited, startX, startY, width, height) {
|
|
261
489
|
const isSolid = (x, y) => {
|
|
262
490
|
if (x < 0 || x >= width || y < 0 || y >= height) return false;
|
|
263
|
-
|
|
264
|
-
const r = data[index];
|
|
265
|
-
const g = data[index + 1];
|
|
266
|
-
const b = data[index + 2];
|
|
267
|
-
const a = data[index + 3];
|
|
268
|
-
if (a <= alphaThreshold) return false;
|
|
269
|
-
if (r > 240 && g > 240 && b > 240) return false;
|
|
270
|
-
return true;
|
|
491
|
+
return mask[y * width + x] === 1;
|
|
271
492
|
};
|
|
272
|
-
let startX = -1;
|
|
273
|
-
let startY = -1;
|
|
274
|
-
searchLoop: for (let y = 0; y < height; y++) {
|
|
275
|
-
for (let x = 0; x < width; x++) {
|
|
276
|
-
if (isSolid(x, y)) {
|
|
277
|
-
startX = x;
|
|
278
|
-
startY = y;
|
|
279
|
-
break searchLoop;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (startX === -1) return [];
|
|
284
493
|
const points = [];
|
|
285
494
|
let cx = startX;
|
|
286
495
|
let cy = startY;
|
|
@@ -299,6 +508,7 @@ var ImageTracer = class {
|
|
|
299
508
|
let steps = 0;
|
|
300
509
|
do {
|
|
301
510
|
points.push({ x: cx, y: cy });
|
|
511
|
+
visited[cy * width + cx] = 1;
|
|
302
512
|
let found = false;
|
|
303
513
|
for (let i = 0; i < 8; i++) {
|
|
304
514
|
const idx = (backtrack + 1 + i) % 8;
|
|
@@ -307,16 +517,12 @@ var ImageTracer = class {
|
|
|
307
517
|
if (isSolid(nx, ny)) {
|
|
308
518
|
cx = nx;
|
|
309
519
|
cy = ny;
|
|
310
|
-
backtrack = (idx + 4) % 8;
|
|
311
|
-
backtrack = (idx + 4 + 1) % 8;
|
|
312
520
|
backtrack = (idx + 4 + 1) % 8;
|
|
313
521
|
found = true;
|
|
314
522
|
break;
|
|
315
523
|
}
|
|
316
524
|
}
|
|
317
|
-
if (!found)
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
525
|
+
if (!found) break;
|
|
320
526
|
steps++;
|
|
321
527
|
} while ((cx !== startX || cy !== startY) && steps < maxSteps);
|
|
322
528
|
return points;
|
|
@@ -365,23 +571,14 @@ var ImageTracer = class {
|
|
|
365
571
|
dy = p.y - y;
|
|
366
572
|
return dx * dx + dy * dy;
|
|
367
573
|
}
|
|
368
|
-
static scalePoints(points, targetWidth, targetHeight) {
|
|
574
|
+
static scalePoints(points, targetWidth, targetHeight, bounds) {
|
|
369
575
|
if (points.length === 0) return points;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (p.y < minY) minY = p.y;
|
|
374
|
-
if (p.x > maxX) maxX = p.x;
|
|
375
|
-
if (p.y > maxY) maxY = p.y;
|
|
376
|
-
}
|
|
377
|
-
const srcW = maxX - minX;
|
|
378
|
-
const srcH = maxY - minY;
|
|
379
|
-
if (srcW === 0 || srcH === 0) return points;
|
|
380
|
-
const scaleX = targetWidth / srcW;
|
|
381
|
-
const scaleY = targetHeight / srcH;
|
|
576
|
+
if (bounds.width === 0 || bounds.height === 0) return points;
|
|
577
|
+
const scaleX = targetWidth / bounds.width;
|
|
578
|
+
const scaleY = targetHeight / bounds.height;
|
|
382
579
|
return points.map((p) => ({
|
|
383
|
-
x: (p.x - minX) * scaleX,
|
|
384
|
-
y: (p.y - minY) * scaleY
|
|
580
|
+
x: (p.x - bounds.minX) * scaleX,
|
|
581
|
+
y: (p.y - bounds.minY) * scaleY
|
|
385
582
|
}));
|
|
386
583
|
}
|
|
387
584
|
static pointsToSVG(points) {
|
|
@@ -390,6 +587,23 @@ var ImageTracer = class {
|
|
|
390
587
|
const tail = points.slice(1);
|
|
391
588
|
return `M ${head.x} ${head.y} ` + tail.map((p) => `L ${p.x} ${p.y}`).join(" ") + " Z";
|
|
392
589
|
}
|
|
590
|
+
static ensurePaper() {
|
|
591
|
+
if (!paper.project) {
|
|
592
|
+
paper.setup(new paper.Size(100, 100));
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
static pointsToSVGPaper(points, tolerance) {
|
|
596
|
+
if (points.length < 3) return this.pointsToSVG(points);
|
|
597
|
+
this.ensurePaper();
|
|
598
|
+
const path = new paper.Path({
|
|
599
|
+
segments: points.map((p) => [p.x, p.y]),
|
|
600
|
+
closed: true
|
|
601
|
+
});
|
|
602
|
+
path.simplify(tolerance);
|
|
603
|
+
const data = path.pathData;
|
|
604
|
+
path.remove();
|
|
605
|
+
return data;
|
|
606
|
+
}
|
|
393
607
|
};
|
|
394
608
|
|
|
395
609
|
// src/coordinate.ts
|
|
@@ -464,96 +678,45 @@ var Coordinate = class {
|
|
|
464
678
|
};
|
|
465
679
|
|
|
466
680
|
// src/geometry.ts
|
|
467
|
-
import
|
|
468
|
-
function
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
const bottom = y + height / 2;
|
|
477
|
-
switch (hole.anchor) {
|
|
478
|
-
case "top-left":
|
|
479
|
-
bx = left;
|
|
480
|
-
by = top;
|
|
481
|
-
break;
|
|
482
|
-
case "top-center":
|
|
483
|
-
bx = x;
|
|
484
|
-
by = top;
|
|
485
|
-
break;
|
|
486
|
-
case "top-right":
|
|
487
|
-
bx = right;
|
|
488
|
-
by = top;
|
|
489
|
-
break;
|
|
490
|
-
case "center-left":
|
|
491
|
-
bx = left;
|
|
492
|
-
by = y;
|
|
493
|
-
break;
|
|
494
|
-
case "center":
|
|
495
|
-
bx = x;
|
|
496
|
-
by = y;
|
|
497
|
-
break;
|
|
498
|
-
case "center-right":
|
|
499
|
-
bx = right;
|
|
500
|
-
by = y;
|
|
501
|
-
break;
|
|
502
|
-
case "bottom-left":
|
|
503
|
-
bx = left;
|
|
504
|
-
by = bottom;
|
|
505
|
-
break;
|
|
506
|
-
case "bottom-center":
|
|
507
|
-
bx = x;
|
|
508
|
-
by = bottom;
|
|
509
|
-
break;
|
|
510
|
-
case "bottom-right":
|
|
511
|
-
bx = right;
|
|
512
|
-
by = bottom;
|
|
513
|
-
break;
|
|
514
|
-
}
|
|
515
|
-
return {
|
|
516
|
-
x: bx + (hole.offsetX || 0),
|
|
517
|
-
y: by + (hole.offsetY || 0)
|
|
518
|
-
};
|
|
519
|
-
} else if (hole.x !== void 0 && hole.y !== void 0) {
|
|
520
|
-
const { x, width, y, height } = geometry;
|
|
521
|
-
return {
|
|
522
|
-
x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
|
|
523
|
-
y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
|
|
524
|
-
};
|
|
525
|
-
}
|
|
526
|
-
return { x: 0, y: 0 };
|
|
681
|
+
import paper2 from "paper";
|
|
682
|
+
function resolveFeaturePosition(feature, geometry) {
|
|
683
|
+
const { x, y, width, height } = geometry;
|
|
684
|
+
const left = x - width / 2;
|
|
685
|
+
const top = y - height / 2;
|
|
686
|
+
return {
|
|
687
|
+
x: left + feature.x * width,
|
|
688
|
+
y: top + feature.y * height
|
|
689
|
+
};
|
|
527
690
|
}
|
|
528
691
|
function ensurePaper(width, height) {
|
|
529
|
-
if (!
|
|
530
|
-
|
|
692
|
+
if (!paper2.project) {
|
|
693
|
+
paper2.setup(new paper2.Size(width, height));
|
|
531
694
|
} else {
|
|
532
|
-
|
|
695
|
+
paper2.view.viewSize = new paper2.Size(width, height);
|
|
533
696
|
}
|
|
534
697
|
}
|
|
535
698
|
function createBaseShape(options) {
|
|
536
699
|
const { shape, width, height, radius, x, y, pathData } = options;
|
|
537
|
-
const center = new
|
|
700
|
+
const center = new paper2.Point(x, y);
|
|
538
701
|
if (shape === "rect") {
|
|
539
|
-
return new
|
|
702
|
+
return new paper2.Path.Rectangle({
|
|
540
703
|
point: [x - width / 2, y - height / 2],
|
|
541
704
|
size: [Math.max(0, width), Math.max(0, height)],
|
|
542
705
|
radius: Math.max(0, radius)
|
|
543
706
|
});
|
|
544
707
|
} else if (shape === "circle") {
|
|
545
708
|
const r = Math.min(width, height) / 2;
|
|
546
|
-
return new
|
|
709
|
+
return new paper2.Path.Circle({
|
|
547
710
|
center,
|
|
548
711
|
radius: Math.max(0, r)
|
|
549
712
|
});
|
|
550
713
|
} else if (shape === "ellipse") {
|
|
551
|
-
return new
|
|
714
|
+
return new paper2.Path.Ellipse({
|
|
552
715
|
center,
|
|
553
716
|
radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
|
|
554
717
|
});
|
|
555
718
|
} else if (shape === "custom" && pathData) {
|
|
556
|
-
const path = new
|
|
719
|
+
const path = new paper2.Path();
|
|
557
720
|
path.pathData = pathData;
|
|
558
721
|
path.position = center;
|
|
559
722
|
if (width > 0 && height > 0 && path.bounds.width > 0 && path.bounds.height > 0) {
|
|
@@ -561,89 +724,76 @@ function createBaseShape(options) {
|
|
|
561
724
|
}
|
|
562
725
|
return path;
|
|
563
726
|
} else {
|
|
564
|
-
return new
|
|
727
|
+
return new paper2.Path.Rectangle({
|
|
565
728
|
point: [x - width / 2, y - height / 2],
|
|
566
729
|
size: [Math.max(0, width), Math.max(0, height)]
|
|
567
730
|
});
|
|
568
731
|
}
|
|
569
732
|
}
|
|
733
|
+
function createFeatureItem(feature, center) {
|
|
734
|
+
let item;
|
|
735
|
+
if (feature.shape === "rect") {
|
|
736
|
+
const w = feature.width || 10;
|
|
737
|
+
const h = feature.height || 10;
|
|
738
|
+
const r = feature.radius || 0;
|
|
739
|
+
item = new paper2.Path.Rectangle({
|
|
740
|
+
point: [center.x - w / 2, center.y - h / 2],
|
|
741
|
+
size: [w, h],
|
|
742
|
+
radius: r
|
|
743
|
+
});
|
|
744
|
+
} else {
|
|
745
|
+
const r = feature.radius || 5;
|
|
746
|
+
item = new paper2.Path.Circle({
|
|
747
|
+
center,
|
|
748
|
+
radius: r
|
|
749
|
+
});
|
|
750
|
+
}
|
|
751
|
+
if (feature.rotation) {
|
|
752
|
+
item.rotate(feature.rotation, center);
|
|
753
|
+
}
|
|
754
|
+
return item;
|
|
755
|
+
}
|
|
570
756
|
function getDielineShape(options) {
|
|
571
757
|
let mainShape = createBaseShape(options);
|
|
572
|
-
const {
|
|
573
|
-
if (
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
const
|
|
578
|
-
const
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
],
|
|
583
|
-
size: [hole.outerRadius * 2, hole.outerRadius * 2]
|
|
584
|
-
}) : new paper.Path.Circle({
|
|
585
|
-
center,
|
|
586
|
-
radius: hole.outerRadius
|
|
587
|
-
});
|
|
588
|
-
const cut = hole.shape === "square" ? new paper.Path.Rectangle({
|
|
589
|
-
point: [
|
|
590
|
-
center.x - hole.innerRadius,
|
|
591
|
-
center.y - hole.innerRadius
|
|
592
|
-
],
|
|
593
|
-
size: [hole.innerRadius * 2, hole.innerRadius * 2]
|
|
594
|
-
}) : new paper.Path.Circle({
|
|
595
|
-
center,
|
|
596
|
-
radius: hole.innerRadius
|
|
597
|
-
});
|
|
598
|
-
if (!lugsPath) {
|
|
599
|
-
lugsPath = lug;
|
|
758
|
+
const { features } = options;
|
|
759
|
+
if (features && features.length > 0) {
|
|
760
|
+
const adds = [];
|
|
761
|
+
const subtracts = [];
|
|
762
|
+
features.forEach((f) => {
|
|
763
|
+
const pos = resolveFeaturePosition(f, options);
|
|
764
|
+
const center = new paper2.Point(pos.x, pos.y);
|
|
765
|
+
const item = createFeatureItem(f, center);
|
|
766
|
+
if (f.operation === "add") {
|
|
767
|
+
adds.push(item);
|
|
600
768
|
} else {
|
|
769
|
+
subtracts.push(item);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
if (adds.length > 0) {
|
|
773
|
+
for (const item of adds) {
|
|
601
774
|
try {
|
|
602
|
-
const temp =
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
775
|
+
const temp = mainShape.unite(item);
|
|
776
|
+
mainShape.remove();
|
|
777
|
+
item.remove();
|
|
778
|
+
mainShape = temp;
|
|
606
779
|
} catch (e) {
|
|
607
|
-
console.error("Geometry: Failed to unite
|
|
608
|
-
|
|
780
|
+
console.error("Geometry: Failed to unite feature", e);
|
|
781
|
+
item.remove();
|
|
609
782
|
}
|
|
610
783
|
}
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
784
|
+
}
|
|
785
|
+
if (subtracts.length > 0) {
|
|
786
|
+
for (const item of subtracts) {
|
|
614
787
|
try {
|
|
615
|
-
const temp =
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
788
|
+
const temp = mainShape.subtract(item);
|
|
789
|
+
mainShape.remove();
|
|
790
|
+
item.remove();
|
|
791
|
+
mainShape = temp;
|
|
619
792
|
} catch (e) {
|
|
620
|
-
console.error("Geometry: Failed to
|
|
621
|
-
|
|
793
|
+
console.error("Geometry: Failed to subtract feature", e);
|
|
794
|
+
item.remove();
|
|
622
795
|
}
|
|
623
796
|
}
|
|
624
|
-
});
|
|
625
|
-
if (lugsPath) {
|
|
626
|
-
try {
|
|
627
|
-
const temp = mainShape.unite(lugsPath);
|
|
628
|
-
mainShape.remove();
|
|
629
|
-
lugsPath.remove();
|
|
630
|
-
mainShape = temp;
|
|
631
|
-
} catch (e) {
|
|
632
|
-
console.error("Geometry: Failed to unite lugsPath to mainShape", e);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
if (cutsPath) {
|
|
636
|
-
try {
|
|
637
|
-
const temp = mainShape.subtract(cutsPath);
|
|
638
|
-
mainShape.remove();
|
|
639
|
-
cutsPath.remove();
|
|
640
|
-
mainShape = temp;
|
|
641
|
-
} catch (e) {
|
|
642
|
-
console.error(
|
|
643
|
-
"Geometry: Failed to subtract cutsPath from mainShape",
|
|
644
|
-
e
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
797
|
}
|
|
648
798
|
}
|
|
649
799
|
return mainShape;
|
|
@@ -652,7 +802,7 @@ function generateDielinePath(options) {
|
|
|
652
802
|
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
653
803
|
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
654
804
|
ensurePaper(paperWidth, paperHeight);
|
|
655
|
-
|
|
805
|
+
paper2.project.activeLayer.removeChildren();
|
|
656
806
|
const mainShape = getDielineShape(options);
|
|
657
807
|
const pathData = mainShape.pathData;
|
|
658
808
|
mainShape.remove();
|
|
@@ -660,9 +810,9 @@ function generateDielinePath(options) {
|
|
|
660
810
|
}
|
|
661
811
|
function generateMaskPath(options) {
|
|
662
812
|
ensurePaper(options.canvasWidth, options.canvasHeight);
|
|
663
|
-
|
|
813
|
+
paper2.project.activeLayer.removeChildren();
|
|
664
814
|
const { canvasWidth, canvasHeight } = options;
|
|
665
|
-
const maskRect = new
|
|
815
|
+
const maskRect = new paper2.Path.Rectangle({
|
|
666
816
|
point: [0, 0],
|
|
667
817
|
size: [canvasWidth, canvasHeight]
|
|
668
818
|
});
|
|
@@ -674,43 +824,13 @@ function generateMaskPath(options) {
|
|
|
674
824
|
finalMask.remove();
|
|
675
825
|
return pathData;
|
|
676
826
|
}
|
|
677
|
-
function generateBleedZonePath(
|
|
678
|
-
const paperWidth =
|
|
679
|
-
const paperHeight =
|
|
827
|
+
function generateBleedZonePath(originalOptions, offsetOptions, offset) {
|
|
828
|
+
const paperWidth = originalOptions.canvasWidth || originalOptions.width * 2 || 2e3;
|
|
829
|
+
const paperHeight = originalOptions.canvasHeight || originalOptions.height * 2 || 2e3;
|
|
680
830
|
ensurePaper(paperWidth, paperHeight);
|
|
681
|
-
|
|
682
|
-
const shapeOriginal = getDielineShape(
|
|
683
|
-
|
|
684
|
-
if (options.shape === "custom") {
|
|
685
|
-
const stroker = shapeOriginal.clone();
|
|
686
|
-
stroker.strokeColor = new paper.Color("black");
|
|
687
|
-
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
688
|
-
stroker.strokeJoin = "round";
|
|
689
|
-
stroker.strokeCap = "round";
|
|
690
|
-
let expanded;
|
|
691
|
-
try {
|
|
692
|
-
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
693
|
-
} catch (e) {
|
|
694
|
-
stroker.remove();
|
|
695
|
-
shapeOffset = shapeOriginal.clone();
|
|
696
|
-
return shapeOffset.pathData;
|
|
697
|
-
}
|
|
698
|
-
stroker.remove();
|
|
699
|
-
if (offset > 0) {
|
|
700
|
-
shapeOffset = shapeOriginal.unite(expanded);
|
|
701
|
-
} else {
|
|
702
|
-
shapeOffset = shapeOriginal.subtract(expanded);
|
|
703
|
-
}
|
|
704
|
-
expanded.remove();
|
|
705
|
-
} else {
|
|
706
|
-
const offsetOptions = {
|
|
707
|
-
...options,
|
|
708
|
-
width: Math.max(0, options.width + offset * 2),
|
|
709
|
-
height: Math.max(0, options.height + offset * 2),
|
|
710
|
-
radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset)
|
|
711
|
-
};
|
|
712
|
-
shapeOffset = getDielineShape(offsetOptions);
|
|
713
|
-
}
|
|
831
|
+
paper2.project.activeLayer.removeChildren();
|
|
832
|
+
const shapeOriginal = getDielineShape(originalOptions);
|
|
833
|
+
const shapeOffset = getDielineShape(offsetOptions);
|
|
714
834
|
let bleedZone;
|
|
715
835
|
if (offset > 0) {
|
|
716
836
|
bleedZone = shapeOffset.subtract(shapeOriginal);
|
|
@@ -725,16 +845,16 @@ function generateBleedZonePath(options, offset) {
|
|
|
725
845
|
}
|
|
726
846
|
function getNearestPointOnDieline(point, options) {
|
|
727
847
|
ensurePaper(options.width * 2, options.height * 2);
|
|
728
|
-
|
|
848
|
+
paper2.project.activeLayer.removeChildren();
|
|
729
849
|
const shape = createBaseShape(options);
|
|
730
|
-
const p = new
|
|
850
|
+
const p = new paper2.Point(point.x, point.y);
|
|
731
851
|
const nearest = shape.getNearestPoint(p);
|
|
732
852
|
const result = { x: nearest.x, y: nearest.y };
|
|
733
853
|
shape.remove();
|
|
734
854
|
return result;
|
|
735
855
|
}
|
|
736
856
|
function getPathBounds(pathData) {
|
|
737
|
-
const path = new
|
|
857
|
+
const path = new paper2.Path();
|
|
738
858
|
path.pathData = pathData;
|
|
739
859
|
const bounds = path.bounds;
|
|
740
860
|
path.remove();
|
|
@@ -753,20 +873,41 @@ var DielineTool = class {
|
|
|
753
873
|
this.metadata = {
|
|
754
874
|
name: "DielineTool"
|
|
755
875
|
};
|
|
756
|
-
this.
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
876
|
+
this.state = {
|
|
877
|
+
unit: "mm",
|
|
878
|
+
shape: "rect",
|
|
879
|
+
width: 500,
|
|
880
|
+
height: 500,
|
|
881
|
+
radius: 0,
|
|
882
|
+
offset: 0,
|
|
883
|
+
padding: 140,
|
|
884
|
+
mainLine: {
|
|
885
|
+
width: 2.7,
|
|
886
|
+
color: "#FF0000",
|
|
887
|
+
dashLength: 5,
|
|
888
|
+
style: "solid"
|
|
889
|
+
},
|
|
890
|
+
offsetLine: {
|
|
891
|
+
width: 2.7,
|
|
892
|
+
color: "#FF0000",
|
|
893
|
+
dashLength: 5,
|
|
894
|
+
style: "solid"
|
|
895
|
+
},
|
|
896
|
+
insideColor: "rgba(0,0,0,0)",
|
|
897
|
+
outsideColor: "#ffffff",
|
|
898
|
+
showBleedLines: true,
|
|
899
|
+
features: []
|
|
900
|
+
};
|
|
768
901
|
if (options) {
|
|
769
|
-
|
|
902
|
+
if (options.mainLine) {
|
|
903
|
+
Object.assign(this.state.mainLine, options.mainLine);
|
|
904
|
+
delete options.mainLine;
|
|
905
|
+
}
|
|
906
|
+
if (options.offsetLine) {
|
|
907
|
+
Object.assign(this.state.offsetLine, options.offsetLine);
|
|
908
|
+
delete options.offsetLine;
|
|
909
|
+
}
|
|
910
|
+
Object.assign(this.state, options);
|
|
770
911
|
}
|
|
771
912
|
}
|
|
772
913
|
activate(context) {
|
|
@@ -778,38 +919,93 @@ var DielineTool = class {
|
|
|
778
919
|
}
|
|
779
920
|
const configService = context.services.get("ConfigurationService");
|
|
780
921
|
if (configService) {
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
);
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
);
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
);
|
|
801
|
-
|
|
802
|
-
this.pathData = configService.get("dieline.pathData", this.pathData);
|
|
922
|
+
const s = this.state;
|
|
923
|
+
s.unit = configService.get("dieline.unit", s.unit);
|
|
924
|
+
s.shape = configService.get("dieline.shape", s.shape);
|
|
925
|
+
s.width = configService.get("dieline.width", s.width);
|
|
926
|
+
s.height = configService.get("dieline.height", s.height);
|
|
927
|
+
s.radius = configService.get("dieline.radius", s.radius);
|
|
928
|
+
s.padding = configService.get("dieline.padding", s.padding);
|
|
929
|
+
s.offset = configService.get("dieline.offset", s.offset);
|
|
930
|
+
s.mainLine.width = configService.get("dieline.strokeWidth", s.mainLine.width);
|
|
931
|
+
s.mainLine.color = configService.get("dieline.strokeColor", s.mainLine.color);
|
|
932
|
+
s.mainLine.dashLength = configService.get("dieline.dashLength", s.mainLine.dashLength);
|
|
933
|
+
s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
|
|
934
|
+
s.offsetLine.width = configService.get("dieline.offsetStrokeWidth", s.offsetLine.width);
|
|
935
|
+
s.offsetLine.color = configService.get("dieline.offsetStrokeColor", s.offsetLine.color);
|
|
936
|
+
s.offsetLine.dashLength = configService.get("dieline.offsetDashLength", s.offsetLine.dashLength);
|
|
937
|
+
s.offsetLine.style = configService.get("dieline.offsetStyle", s.offsetLine.style);
|
|
938
|
+
s.insideColor = configService.get("dieline.insideColor", s.insideColor);
|
|
939
|
+
s.outsideColor = configService.get("dieline.outsideColor", s.outsideColor);
|
|
940
|
+
s.showBleedLines = configService.get("dieline.showBleedLines", s.showBleedLines);
|
|
941
|
+
s.features = configService.get("dieline.features", s.features);
|
|
942
|
+
s.pathData = configService.get("dieline.pathData", s.pathData);
|
|
803
943
|
configService.onAnyChange((e) => {
|
|
804
944
|
if (e.key.startsWith("dieline.")) {
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
945
|
+
console.log(`[DielineTool] Config change detected: ${e.key} -> ${e.value}`);
|
|
946
|
+
switch (e.key) {
|
|
947
|
+
case "dieline.unit":
|
|
948
|
+
s.unit = e.value;
|
|
949
|
+
break;
|
|
950
|
+
case "dieline.shape":
|
|
951
|
+
s.shape = e.value;
|
|
952
|
+
break;
|
|
953
|
+
case "dieline.width":
|
|
954
|
+
s.width = e.value;
|
|
955
|
+
break;
|
|
956
|
+
case "dieline.height":
|
|
957
|
+
s.height = e.value;
|
|
958
|
+
break;
|
|
959
|
+
case "dieline.radius":
|
|
960
|
+
s.radius = e.value;
|
|
961
|
+
break;
|
|
962
|
+
case "dieline.padding":
|
|
963
|
+
s.padding = e.value;
|
|
964
|
+
break;
|
|
965
|
+
case "dieline.offset":
|
|
966
|
+
s.offset = e.value;
|
|
967
|
+
break;
|
|
968
|
+
case "dieline.strokeWidth":
|
|
969
|
+
s.mainLine.width = e.value;
|
|
970
|
+
break;
|
|
971
|
+
case "dieline.strokeColor":
|
|
972
|
+
s.mainLine.color = e.value;
|
|
973
|
+
break;
|
|
974
|
+
case "dieline.dashLength":
|
|
975
|
+
s.mainLine.dashLength = e.value;
|
|
976
|
+
break;
|
|
977
|
+
case "dieline.style":
|
|
978
|
+
s.mainLine.style = e.value;
|
|
979
|
+
break;
|
|
980
|
+
case "dieline.offsetStrokeWidth":
|
|
981
|
+
s.offsetLine.width = e.value;
|
|
982
|
+
break;
|
|
983
|
+
case "dieline.offsetStrokeColor":
|
|
984
|
+
s.offsetLine.color = e.value;
|
|
985
|
+
break;
|
|
986
|
+
case "dieline.offsetDashLength":
|
|
987
|
+
s.offsetLine.dashLength = e.value;
|
|
988
|
+
break;
|
|
989
|
+
case "dieline.offsetStyle":
|
|
990
|
+
s.offsetLine.style = e.value;
|
|
991
|
+
break;
|
|
992
|
+
case "dieline.insideColor":
|
|
993
|
+
s.insideColor = e.value;
|
|
994
|
+
break;
|
|
995
|
+
case "dieline.outsideColor":
|
|
996
|
+
s.outsideColor = e.value;
|
|
997
|
+
break;
|
|
998
|
+
case "dieline.showBleedLines":
|
|
999
|
+
s.showBleedLines = e.value;
|
|
1000
|
+
break;
|
|
1001
|
+
case "dieline.features":
|
|
1002
|
+
s.features = e.value;
|
|
1003
|
+
break;
|
|
1004
|
+
case "dieline.pathData":
|
|
1005
|
+
s.pathData = e.value;
|
|
1006
|
+
break;
|
|
812
1007
|
}
|
|
1008
|
+
this.updateDieline();
|
|
813
1009
|
}
|
|
814
1010
|
});
|
|
815
1011
|
}
|
|
@@ -822,6 +1018,7 @@ var DielineTool = class {
|
|
|
822
1018
|
this.context = void 0;
|
|
823
1019
|
}
|
|
824
1020
|
contribute() {
|
|
1021
|
+
const s = this.state;
|
|
825
1022
|
return {
|
|
826
1023
|
[ContributionPointIds2.CONFIGURATIONS]: [
|
|
827
1024
|
{
|
|
@@ -829,14 +1026,14 @@ var DielineTool = class {
|
|
|
829
1026
|
type: "select",
|
|
830
1027
|
label: "Unit",
|
|
831
1028
|
options: ["px", "mm", "cm", "in"],
|
|
832
|
-
default:
|
|
1029
|
+
default: s.unit
|
|
833
1030
|
},
|
|
834
1031
|
{
|
|
835
1032
|
id: "dieline.shape",
|
|
836
1033
|
type: "select",
|
|
837
1034
|
label: "Shape",
|
|
838
1035
|
options: ["rect", "circle", "ellipse", "custom"],
|
|
839
|
-
default:
|
|
1036
|
+
default: s.shape
|
|
840
1037
|
},
|
|
841
1038
|
{
|
|
842
1039
|
id: "dieline.width",
|
|
@@ -844,7 +1041,7 @@ var DielineTool = class {
|
|
|
844
1041
|
label: "Width",
|
|
845
1042
|
min: 10,
|
|
846
1043
|
max: 2e3,
|
|
847
|
-
default:
|
|
1044
|
+
default: s.width
|
|
848
1045
|
},
|
|
849
1046
|
{
|
|
850
1047
|
id: "dieline.height",
|
|
@@ -852,7 +1049,7 @@ var DielineTool = class {
|
|
|
852
1049
|
label: "Height",
|
|
853
1050
|
min: 10,
|
|
854
1051
|
max: 2e3,
|
|
855
|
-
default:
|
|
1052
|
+
default: s.height
|
|
856
1053
|
},
|
|
857
1054
|
{
|
|
858
1055
|
id: "dieline.radius",
|
|
@@ -860,20 +1057,14 @@ var DielineTool = class {
|
|
|
860
1057
|
label: "Corner Radius",
|
|
861
1058
|
min: 0,
|
|
862
1059
|
max: 500,
|
|
863
|
-
default:
|
|
864
|
-
},
|
|
865
|
-
{
|
|
866
|
-
id: "dieline.position",
|
|
867
|
-
type: "json",
|
|
868
|
-
label: "Position (Normalized)",
|
|
869
|
-
default: this.radius
|
|
1060
|
+
default: s.radius
|
|
870
1061
|
},
|
|
871
1062
|
{
|
|
872
1063
|
id: "dieline.padding",
|
|
873
1064
|
type: "select",
|
|
874
1065
|
label: "View Padding",
|
|
875
1066
|
options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
|
|
876
|
-
default:
|
|
1067
|
+
default: s.padding
|
|
877
1068
|
},
|
|
878
1069
|
{
|
|
879
1070
|
id: "dieline.offset",
|
|
@@ -881,38 +1072,91 @@ var DielineTool = class {
|
|
|
881
1072
|
label: "Bleed Offset",
|
|
882
1073
|
min: -100,
|
|
883
1074
|
max: 100,
|
|
884
|
-
default:
|
|
1075
|
+
default: s.offset
|
|
885
1076
|
},
|
|
886
1077
|
{
|
|
887
1078
|
id: "dieline.showBleedLines",
|
|
888
1079
|
type: "boolean",
|
|
889
1080
|
label: "Show Bleed Lines",
|
|
890
|
-
default:
|
|
1081
|
+
default: s.showBleedLines
|
|
1082
|
+
},
|
|
1083
|
+
{
|
|
1084
|
+
id: "dieline.strokeWidth",
|
|
1085
|
+
type: "number",
|
|
1086
|
+
label: "Line Width",
|
|
1087
|
+
min: 0.1,
|
|
1088
|
+
max: 10,
|
|
1089
|
+
step: 0.1,
|
|
1090
|
+
default: s.mainLine.width
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
id: "dieline.strokeColor",
|
|
1094
|
+
type: "color",
|
|
1095
|
+
label: "Line Color",
|
|
1096
|
+
default: s.mainLine.color
|
|
1097
|
+
},
|
|
1098
|
+
{
|
|
1099
|
+
id: "dieline.dashLength",
|
|
1100
|
+
type: "number",
|
|
1101
|
+
label: "Dash Length",
|
|
1102
|
+
min: 1,
|
|
1103
|
+
max: 50,
|
|
1104
|
+
default: s.mainLine.dashLength
|
|
891
1105
|
},
|
|
892
1106
|
{
|
|
893
1107
|
id: "dieline.style",
|
|
894
1108
|
type: "select",
|
|
895
1109
|
label: "Line Style",
|
|
896
|
-
options: ["solid", "dashed"],
|
|
897
|
-
default:
|
|
1110
|
+
options: ["solid", "dashed", "hidden"],
|
|
1111
|
+
default: s.mainLine.style
|
|
1112
|
+
},
|
|
1113
|
+
{
|
|
1114
|
+
id: "dieline.offsetStrokeWidth",
|
|
1115
|
+
type: "number",
|
|
1116
|
+
label: "Offset Line Width",
|
|
1117
|
+
min: 0.1,
|
|
1118
|
+
max: 10,
|
|
1119
|
+
step: 0.1,
|
|
1120
|
+
default: s.offsetLine.width
|
|
1121
|
+
},
|
|
1122
|
+
{
|
|
1123
|
+
id: "dieline.offsetStrokeColor",
|
|
1124
|
+
type: "color",
|
|
1125
|
+
label: "Offset Line Color",
|
|
1126
|
+
default: s.offsetLine.color
|
|
1127
|
+
},
|
|
1128
|
+
{
|
|
1129
|
+
id: "dieline.offsetDashLength",
|
|
1130
|
+
type: "number",
|
|
1131
|
+
label: "Offset Dash Length",
|
|
1132
|
+
min: 1,
|
|
1133
|
+
max: 50,
|
|
1134
|
+
default: s.offsetLine.dashLength
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: "dieline.offsetStyle",
|
|
1138
|
+
type: "select",
|
|
1139
|
+
label: "Offset Line Style",
|
|
1140
|
+
options: ["solid", "dashed", "hidden"],
|
|
1141
|
+
default: s.offsetLine.style
|
|
898
1142
|
},
|
|
899
1143
|
{
|
|
900
1144
|
id: "dieline.insideColor",
|
|
901
1145
|
type: "color",
|
|
902
1146
|
label: "Inside Color",
|
|
903
|
-
default:
|
|
1147
|
+
default: s.insideColor
|
|
904
1148
|
},
|
|
905
1149
|
{
|
|
906
1150
|
id: "dieline.outsideColor",
|
|
907
1151
|
type: "color",
|
|
908
1152
|
label: "Outside Color",
|
|
909
|
-
default:
|
|
1153
|
+
default: s.outsideColor
|
|
910
1154
|
},
|
|
911
1155
|
{
|
|
912
|
-
id: "dieline.
|
|
1156
|
+
id: "dieline.features",
|
|
913
1157
|
type: "json",
|
|
914
|
-
label: "
|
|
915
|
-
default:
|
|
1158
|
+
label: "Edge Features",
|
|
1159
|
+
default: s.features
|
|
916
1160
|
}
|
|
917
1161
|
],
|
|
918
1162
|
[ContributionPointIds2.COMMANDS]: [
|
|
@@ -934,24 +1178,18 @@ var DielineTool = class {
|
|
|
934
1178
|
command: "detectEdge",
|
|
935
1179
|
title: "Detect Edge from Image",
|
|
936
1180
|
handler: async (imageUrl, options) => {
|
|
937
|
-
var _a;
|
|
938
1181
|
try {
|
|
939
1182
|
const pathData = await ImageTracer.trace(imageUrl, options);
|
|
940
1183
|
const bounds = getPathBounds(pathData);
|
|
941
|
-
const currentMax = Math.max(
|
|
1184
|
+
const currentMax = Math.max(s.width, s.height);
|
|
942
1185
|
const scale = currentMax / Math.max(bounds.width, bounds.height);
|
|
943
1186
|
const newWidth = bounds.width * scale;
|
|
944
1187
|
const newHeight = bounds.height * scale;
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
configService.update("dieline.height", newHeight);
|
|
951
|
-
configService.update("dieline.shape", "custom");
|
|
952
|
-
configService.update("dieline.pathData", pathData);
|
|
953
|
-
}
|
|
954
|
-
return pathData;
|
|
1188
|
+
return {
|
|
1189
|
+
pathData,
|
|
1190
|
+
width: newWidth,
|
|
1191
|
+
height: newHeight
|
|
1192
|
+
};
|
|
955
1193
|
} catch (e) {
|
|
956
1194
|
console.error("Edge detection failed", e);
|
|
957
1195
|
throw e;
|
|
@@ -1010,15 +1248,15 @@ var DielineTool = class {
|
|
|
1010
1248
|
return new Pattern({ source: canvas, repetition: "repeat" });
|
|
1011
1249
|
}
|
|
1012
1250
|
resolvePadding(containerWidth, containerHeight) {
|
|
1013
|
-
if (typeof this.padding === "number") {
|
|
1014
|
-
return this.padding;
|
|
1251
|
+
if (typeof this.state.padding === "number") {
|
|
1252
|
+
return this.state.padding;
|
|
1015
1253
|
}
|
|
1016
|
-
if (typeof this.padding === "string") {
|
|
1017
|
-
if (this.padding.endsWith("%")) {
|
|
1018
|
-
const percent = parseFloat(this.padding) / 100;
|
|
1254
|
+
if (typeof this.state.padding === "string") {
|
|
1255
|
+
if (this.state.padding.endsWith("%")) {
|
|
1256
|
+
const percent = parseFloat(this.state.padding) / 100;
|
|
1019
1257
|
return Math.min(containerWidth, containerHeight) * percent;
|
|
1020
1258
|
}
|
|
1021
|
-
return parseFloat(this.padding) || 0;
|
|
1259
|
+
return parseFloat(this.state.padding) || 0;
|
|
1022
1260
|
}
|
|
1023
1261
|
return 0;
|
|
1024
1262
|
}
|
|
@@ -1031,14 +1269,14 @@ var DielineTool = class {
|
|
|
1031
1269
|
shape,
|
|
1032
1270
|
radius,
|
|
1033
1271
|
offset,
|
|
1034
|
-
|
|
1272
|
+
mainLine,
|
|
1273
|
+
offsetLine,
|
|
1035
1274
|
insideColor,
|
|
1036
1275
|
outsideColor,
|
|
1037
|
-
position,
|
|
1038
1276
|
showBleedLines,
|
|
1039
|
-
|
|
1040
|
-
} = this;
|
|
1041
|
-
let { width, height } = this;
|
|
1277
|
+
features
|
|
1278
|
+
} = this.state;
|
|
1279
|
+
let { width, height } = this.state;
|
|
1042
1280
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1043
1281
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1044
1282
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1055,40 +1293,27 @@ var DielineTool = class {
|
|
|
1055
1293
|
const visualRadius = radius * scale;
|
|
1056
1294
|
const visualOffset = offset * scale;
|
|
1057
1295
|
layer.remove(...layer.getObjects());
|
|
1058
|
-
const
|
|
1059
|
-
|
|
1060
|
-
y: cy,
|
|
1061
|
-
width: visualWidth,
|
|
1062
|
-
height: visualHeight
|
|
1063
|
-
// Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
|
|
1064
|
-
};
|
|
1065
|
-
const absoluteHoles = (holes || []).map((h) => {
|
|
1066
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1067
|
-
const offsetScale = unitScale * scale;
|
|
1068
|
-
const hWithPixelOffsets = {
|
|
1069
|
-
...h,
|
|
1070
|
-
offsetX: (h.offsetX || 0) * offsetScale,
|
|
1071
|
-
offsetY: (h.offsetY || 0) * offsetScale
|
|
1072
|
-
};
|
|
1073
|
-
const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
|
|
1074
|
-
width: canvasW,
|
|
1075
|
-
height: canvasH
|
|
1076
|
-
});
|
|
1296
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
1297
|
+
const featureScale = scale;
|
|
1077
1298
|
return {
|
|
1078
|
-
...
|
|
1079
|
-
x:
|
|
1080
|
-
y:
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
// Store scaled offsets in the result for consistency, though pos is already resolved
|
|
1085
|
-
offsetX: hWithPixelOffsets.offsetX,
|
|
1086
|
-
offsetY: hWithPixelOffsets.offsetY
|
|
1299
|
+
...f,
|
|
1300
|
+
x: f.x,
|
|
1301
|
+
y: f.y,
|
|
1302
|
+
width: (f.width || 0) * featureScale,
|
|
1303
|
+
height: (f.height || 0) * featureScale,
|
|
1304
|
+
radius: (f.radius || 0) * featureScale
|
|
1087
1305
|
};
|
|
1088
1306
|
});
|
|
1307
|
+
const originalFeatures = absoluteFeatures.filter(
|
|
1308
|
+
(f) => !f.target || f.target === "original" || f.target === "both"
|
|
1309
|
+
);
|
|
1310
|
+
const offsetFeatures = absoluteFeatures.filter(
|
|
1311
|
+
(f) => f.target === "offset" || f.target === "both"
|
|
1312
|
+
);
|
|
1089
1313
|
const cutW = Math.max(0, visualWidth + visualOffset * 2);
|
|
1090
1314
|
const cutH = Math.max(0, visualHeight + visualOffset * 2);
|
|
1091
1315
|
const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
1316
|
+
const maskFeatures = visualOffset !== 0 ? offsetFeatures : originalFeatures;
|
|
1092
1317
|
const maskPathData = generateMaskPath({
|
|
1093
1318
|
canvasWidth: canvasW,
|
|
1094
1319
|
canvasHeight: canvasH,
|
|
@@ -1098,8 +1323,8 @@ var DielineTool = class {
|
|
|
1098
1323
|
radius: cutR,
|
|
1099
1324
|
x: cx,
|
|
1100
1325
|
y: cy,
|
|
1101
|
-
|
|
1102
|
-
pathData: this.pathData
|
|
1326
|
+
features: maskFeatures,
|
|
1327
|
+
pathData: this.state.pathData
|
|
1103
1328
|
});
|
|
1104
1329
|
const mask = new Path(maskPathData, {
|
|
1105
1330
|
fill: outsideColor,
|
|
@@ -1120,8 +1345,9 @@ var DielineTool = class {
|
|
|
1120
1345
|
radius: cutR,
|
|
1121
1346
|
x: cx,
|
|
1122
1347
|
y: cy,
|
|
1123
|
-
|
|
1124
|
-
|
|
1348
|
+
features: maskFeatures,
|
|
1349
|
+
// Use same features as mask for consistency
|
|
1350
|
+
pathData: this.state.pathData,
|
|
1125
1351
|
canvasWidth: canvasW,
|
|
1126
1352
|
canvasHeight: canvasH
|
|
1127
1353
|
});
|
|
@@ -1145,15 +1371,27 @@ var DielineTool = class {
|
|
|
1145
1371
|
radius: visualRadius,
|
|
1146
1372
|
x: cx,
|
|
1147
1373
|
y: cy,
|
|
1148
|
-
|
|
1149
|
-
pathData: this.pathData,
|
|
1374
|
+
features: originalFeatures,
|
|
1375
|
+
pathData: this.state.pathData,
|
|
1376
|
+
canvasWidth: canvasW,
|
|
1377
|
+
canvasHeight: canvasH
|
|
1378
|
+
},
|
|
1379
|
+
{
|
|
1380
|
+
shape,
|
|
1381
|
+
width: cutW,
|
|
1382
|
+
height: cutH,
|
|
1383
|
+
radius: cutR,
|
|
1384
|
+
x: cx,
|
|
1385
|
+
y: cy,
|
|
1386
|
+
features: offsetFeatures,
|
|
1387
|
+
pathData: this.state.pathData,
|
|
1150
1388
|
canvasWidth: canvasW,
|
|
1151
1389
|
canvasHeight: canvasH
|
|
1152
1390
|
},
|
|
1153
1391
|
visualOffset
|
|
1154
1392
|
);
|
|
1155
1393
|
if (showBleedLines !== false) {
|
|
1156
|
-
const pattern = this.createHatchPattern(
|
|
1394
|
+
const pattern = this.createHatchPattern(mainLine.color);
|
|
1157
1395
|
if (pattern) {
|
|
1158
1396
|
const bleedObj = new Path(bleedPathData, {
|
|
1159
1397
|
fill: pattern,
|
|
@@ -1174,18 +1412,16 @@ var DielineTool = class {
|
|
|
1174
1412
|
radius: cutR,
|
|
1175
1413
|
x: cx,
|
|
1176
1414
|
y: cy,
|
|
1177
|
-
|
|
1178
|
-
pathData: this.pathData,
|
|
1415
|
+
features: offsetFeatures,
|
|
1416
|
+
pathData: this.state.pathData,
|
|
1179
1417
|
canvasWidth: canvasW,
|
|
1180
1418
|
canvasHeight: canvasH
|
|
1181
1419
|
});
|
|
1182
1420
|
const offsetBorderObj = new Path(offsetPathData, {
|
|
1183
1421
|
fill: null,
|
|
1184
|
-
stroke: "
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
strokeDashArray: [4, 4],
|
|
1188
|
-
// Dashed
|
|
1422
|
+
stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
|
|
1423
|
+
strokeWidth: offsetLine.width,
|
|
1424
|
+
strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
|
|
1189
1425
|
selectable: false,
|
|
1190
1426
|
evented: false,
|
|
1191
1427
|
originX: "left",
|
|
@@ -1200,16 +1436,16 @@ var DielineTool = class {
|
|
|
1200
1436
|
radius: visualRadius,
|
|
1201
1437
|
x: cx,
|
|
1202
1438
|
y: cy,
|
|
1203
|
-
|
|
1204
|
-
pathData: this.pathData,
|
|
1439
|
+
features: originalFeatures,
|
|
1440
|
+
pathData: this.state.pathData,
|
|
1205
1441
|
canvasWidth: canvasW,
|
|
1206
1442
|
canvasHeight: canvasH
|
|
1207
1443
|
});
|
|
1208
1444
|
const borderObj = new Path(borderPathData, {
|
|
1209
1445
|
fill: "transparent",
|
|
1210
|
-
stroke: "
|
|
1211
|
-
strokeWidth:
|
|
1212
|
-
strokeDashArray: style === "dashed" ? [
|
|
1446
|
+
stroke: mainLine.style === "hidden" ? null : mainLine.color,
|
|
1447
|
+
strokeWidth: mainLine.width,
|
|
1448
|
+
strokeDashArray: mainLine.style === "dashed" ? [mainLine.dashLength, mainLine.dashLength] : void 0,
|
|
1213
1449
|
selectable: false,
|
|
1214
1450
|
evented: false,
|
|
1215
1451
|
originX: "left",
|
|
@@ -1241,7 +1477,7 @@ var DielineTool = class {
|
|
|
1241
1477
|
}
|
|
1242
1478
|
getGeometry() {
|
|
1243
1479
|
if (!this.canvasService) return null;
|
|
1244
|
-
const { unit, shape, width, height, radius,
|
|
1480
|
+
const { unit, shape, width, height, radius, offset, mainLine, pathData } = this.state;
|
|
1245
1481
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1246
1482
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1247
1483
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1264,16 +1500,17 @@ var DielineTool = class {
|
|
|
1264
1500
|
height: visualHeight,
|
|
1265
1501
|
radius: radius * scale,
|
|
1266
1502
|
offset: offset * scale,
|
|
1267
|
-
// Pass scale to help other tools (like
|
|
1503
|
+
// Pass scale to help other tools (like FeatureTool) convert units
|
|
1268
1504
|
scale,
|
|
1269
|
-
|
|
1505
|
+
strokeWidth: mainLine.width,
|
|
1506
|
+
pathData
|
|
1270
1507
|
};
|
|
1271
1508
|
}
|
|
1272
1509
|
async exportCutImage() {
|
|
1273
1510
|
if (!this.canvasService) return null;
|
|
1274
1511
|
const userLayer = this.canvasService.getLayer("user");
|
|
1275
1512
|
if (!userLayer) return null;
|
|
1276
|
-
const { shape, width, height, radius,
|
|
1513
|
+
const { shape, width, height, radius, features, unit, pathData } = this.state;
|
|
1277
1514
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1278
1515
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1279
1516
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1288,55 +1525,45 @@ var DielineTool = class {
|
|
|
1288
1525
|
const visualWidth = layout.width;
|
|
1289
1526
|
const visualHeight = layout.height;
|
|
1290
1527
|
const visualRadius = radius * scale;
|
|
1291
|
-
const
|
|
1292
|
-
const
|
|
1293
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1294
|
-
const pos = resolveHolePosition(
|
|
1295
|
-
{
|
|
1296
|
-
...h,
|
|
1297
|
-
offsetX: (h.offsetX || 0) * unitScale * scale,
|
|
1298
|
-
offsetY: (h.offsetY || 0) * unitScale * scale
|
|
1299
|
-
},
|
|
1300
|
-
{ x: cx, y: cy, width: visualWidth, height: visualHeight },
|
|
1301
|
-
{ width: canvasW, height: canvasH }
|
|
1302
|
-
);
|
|
1528
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
1529
|
+
const featureScale = scale;
|
|
1303
1530
|
return {
|
|
1304
|
-
...
|
|
1305
|
-
x:
|
|
1306
|
-
y:
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
offsetY: (h.offsetY || 0) * unitScale * scale
|
|
1531
|
+
...f,
|
|
1532
|
+
x: f.x,
|
|
1533
|
+
y: f.y,
|
|
1534
|
+
width: (f.width || 0) * featureScale,
|
|
1535
|
+
height: (f.height || 0) * featureScale,
|
|
1536
|
+
radius: (f.radius || 0) * featureScale
|
|
1311
1537
|
};
|
|
1312
1538
|
});
|
|
1313
|
-
const
|
|
1539
|
+
const originalFeatures = absoluteFeatures.filter(
|
|
1540
|
+
(f) => !f.target || f.target === "original" || f.target === "both"
|
|
1541
|
+
);
|
|
1542
|
+
const generatedPathData = generateDielinePath({
|
|
1314
1543
|
shape,
|
|
1315
1544
|
width: visualWidth,
|
|
1316
1545
|
height: visualHeight,
|
|
1317
1546
|
radius: visualRadius,
|
|
1318
1547
|
x: cx,
|
|
1319
1548
|
y: cy,
|
|
1320
|
-
|
|
1321
|
-
pathData
|
|
1549
|
+
features: originalFeatures,
|
|
1550
|
+
pathData,
|
|
1322
1551
|
canvasWidth: canvasW,
|
|
1323
1552
|
canvasHeight: canvasH
|
|
1324
1553
|
});
|
|
1325
1554
|
const clonedLayer = await userLayer.clone();
|
|
1326
|
-
const clipPath = new Path(
|
|
1555
|
+
const clipPath = new Path(generatedPathData, {
|
|
1327
1556
|
originX: "left",
|
|
1328
1557
|
originY: "top",
|
|
1329
1558
|
left: 0,
|
|
1330
1559
|
top: 0,
|
|
1331
1560
|
absolutePositioned: true
|
|
1332
|
-
// Important for groups
|
|
1333
1561
|
});
|
|
1334
1562
|
clonedLayer.clipPath = clipPath;
|
|
1335
1563
|
const bounds = clipPath.getBoundingRect();
|
|
1336
1564
|
const dataUrl = clonedLayer.toDataURL({
|
|
1337
1565
|
format: "png",
|
|
1338
1566
|
multiplier: 2,
|
|
1339
|
-
// Better quality
|
|
1340
1567
|
left: bounds.left,
|
|
1341
1568
|
top: bounds.top,
|
|
1342
1569
|
width: bounds.width,
|
|
@@ -1507,24 +1734,22 @@ var FilmTool = class {
|
|
|
1507
1734
|
}
|
|
1508
1735
|
};
|
|
1509
1736
|
|
|
1510
|
-
// src/
|
|
1737
|
+
// src/feature.ts
|
|
1511
1738
|
import {
|
|
1512
1739
|
ContributionPointIds as ContributionPointIds4
|
|
1513
1740
|
} from "@pooder/core";
|
|
1514
1741
|
import { Circle, Group, Point, Rect as Rect2 } from "fabric";
|
|
1515
|
-
var
|
|
1742
|
+
var FeatureTool = class {
|
|
1516
1743
|
constructor(options) {
|
|
1517
|
-
this.id = "pooder.kit.
|
|
1744
|
+
this.id = "pooder.kit.feature";
|
|
1518
1745
|
this.metadata = {
|
|
1519
|
-
name: "
|
|
1746
|
+
name: "FeatureTool"
|
|
1520
1747
|
};
|
|
1521
|
-
this.
|
|
1522
|
-
this.constraintTarget = "bleed";
|
|
1748
|
+
this.features = [];
|
|
1523
1749
|
this.isUpdatingConfig = false;
|
|
1524
1750
|
this.handleMoving = null;
|
|
1525
1751
|
this.handleModified = null;
|
|
1526
1752
|
this.handleDielineChange = null;
|
|
1527
|
-
// Cache geometry to enforce constraints during drag
|
|
1528
1753
|
this.currentGeometry = null;
|
|
1529
1754
|
if (options) {
|
|
1530
1755
|
Object.assign(this, options);
|
|
@@ -1534,26 +1759,18 @@ var HoleTool = class {
|
|
|
1534
1759
|
this.context = context;
|
|
1535
1760
|
this.canvasService = context.services.get("CanvasService");
|
|
1536
1761
|
if (!this.canvasService) {
|
|
1537
|
-
console.warn("CanvasService not found for
|
|
1762
|
+
console.warn("CanvasService not found for FeatureTool");
|
|
1538
1763
|
return;
|
|
1539
1764
|
}
|
|
1540
1765
|
const configService = context.services.get(
|
|
1541
1766
|
"ConfigurationService"
|
|
1542
1767
|
);
|
|
1543
1768
|
if (configService) {
|
|
1544
|
-
this.
|
|
1545
|
-
"hole.constraintTarget",
|
|
1546
|
-
this.constraintTarget
|
|
1547
|
-
);
|
|
1548
|
-
this.holes = configService.get("dieline.holes", []);
|
|
1769
|
+
this.features = configService.get("dieline.features", []);
|
|
1549
1770
|
configService.onAnyChange((e) => {
|
|
1550
1771
|
if (this.isUpdatingConfig) return;
|
|
1551
|
-
if (e.key === "
|
|
1552
|
-
this.
|
|
1553
|
-
this.enforceConstraints();
|
|
1554
|
-
}
|
|
1555
|
-
if (e.key === "dieline.holes") {
|
|
1556
|
-
this.holes = e.value || [];
|
|
1772
|
+
if (e.key === "dieline.features") {
|
|
1773
|
+
this.features = e.value || [];
|
|
1557
1774
|
this.redraw();
|
|
1558
1775
|
}
|
|
1559
1776
|
});
|
|
@@ -1567,102 +1784,38 @@ var HoleTool = class {
|
|
|
1567
1784
|
}
|
|
1568
1785
|
contribute() {
|
|
1569
1786
|
return {
|
|
1570
|
-
[ContributionPointIds4.CONFIGURATIONS]: [
|
|
1571
|
-
{
|
|
1572
|
-
id: "hole.constraintTarget",
|
|
1573
|
-
type: "select",
|
|
1574
|
-
label: "Constraint Target",
|
|
1575
|
-
options: ["original", "bleed"],
|
|
1576
|
-
default: "bleed"
|
|
1577
|
-
}
|
|
1578
|
-
],
|
|
1579
1787
|
[ContributionPointIds4.COMMANDS]: [
|
|
1580
1788
|
{
|
|
1581
|
-
command: "
|
|
1582
|
-
title: "
|
|
1583
|
-
handler: () => {
|
|
1584
|
-
|
|
1585
|
-
if (!this.canvasService) return false;
|
|
1586
|
-
let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
|
|
1587
|
-
if (this.currentGeometry) {
|
|
1588
|
-
const g = this.currentGeometry;
|
|
1589
|
-
const topCenter = { x: g.x, y: g.y - g.height / 2 };
|
|
1590
|
-
defaultPos = getNearestPointOnDieline(topCenter, {
|
|
1591
|
-
...g,
|
|
1592
|
-
holes: []
|
|
1593
|
-
});
|
|
1594
|
-
}
|
|
1595
|
-
const { width, height } = this.canvasService.canvas;
|
|
1596
|
-
const normalizedHole = Coordinate.normalizePoint(defaultPos, {
|
|
1597
|
-
width: width || 800,
|
|
1598
|
-
height: height || 600
|
|
1599
|
-
});
|
|
1600
|
-
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1601
|
-
"ConfigurationService"
|
|
1602
|
-
);
|
|
1603
|
-
if (configService) {
|
|
1604
|
-
configService.update("dieline.holes", [
|
|
1605
|
-
{
|
|
1606
|
-
x: normalizedHole.x,
|
|
1607
|
-
y: normalizedHole.y,
|
|
1608
|
-
innerRadius: 15,
|
|
1609
|
-
outerRadius: 25
|
|
1610
|
-
}
|
|
1611
|
-
]);
|
|
1612
|
-
}
|
|
1613
|
-
return true;
|
|
1789
|
+
command: "addFeature",
|
|
1790
|
+
title: "Add Edge Feature",
|
|
1791
|
+
handler: (type = "subtract") => {
|
|
1792
|
+
return this.addFeature(type);
|
|
1614
1793
|
}
|
|
1615
1794
|
},
|
|
1616
1795
|
{
|
|
1617
1796
|
command: "addHole",
|
|
1618
1797
|
title: "Add Hole",
|
|
1619
|
-
handler: (
|
|
1620
|
-
|
|
1621
|
-
if (!this.canvasService) return false;
|
|
1622
|
-
let normalizedX = 0.5;
|
|
1623
|
-
let normalizedY = 0.5;
|
|
1624
|
-
if (this.currentGeometry) {
|
|
1625
|
-
const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
|
|
1626
|
-
const left = gx - gw / 2;
|
|
1627
|
-
const top = gy - gh / 2;
|
|
1628
|
-
normalizedX = gw > 0 ? (x - left) / gw : 0.5;
|
|
1629
|
-
normalizedY = gh > 0 ? (y - top) / gh : 0.5;
|
|
1630
|
-
} else {
|
|
1631
|
-
const { width, height } = this.canvasService.canvas;
|
|
1632
|
-
normalizedX = Coordinate.toNormalized(x, width || 800);
|
|
1633
|
-
normalizedY = Coordinate.toNormalized(y, height || 600);
|
|
1634
|
-
}
|
|
1635
|
-
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1636
|
-
"ConfigurationService"
|
|
1637
|
-
);
|
|
1638
|
-
if (configService) {
|
|
1639
|
-
const currentHoles = configService.get("dieline.holes", []);
|
|
1640
|
-
const lastHole = currentHoles[currentHoles.length - 1];
|
|
1641
|
-
const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
|
|
1642
|
-
const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
|
|
1643
|
-
const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
|
|
1644
|
-
const newHole = {
|
|
1645
|
-
x: normalizedX,
|
|
1646
|
-
y: normalizedY,
|
|
1647
|
-
shape,
|
|
1648
|
-
innerRadius,
|
|
1649
|
-
outerRadius
|
|
1650
|
-
};
|
|
1651
|
-
configService.update("dieline.holes", [...currentHoles, newHole]);
|
|
1652
|
-
}
|
|
1653
|
-
return true;
|
|
1798
|
+
handler: () => {
|
|
1799
|
+
return this.addFeature("subtract");
|
|
1654
1800
|
}
|
|
1655
1801
|
},
|
|
1656
1802
|
{
|
|
1657
|
-
command: "
|
|
1658
|
-
title: "
|
|
1803
|
+
command: "addDoubleLayerHole",
|
|
1804
|
+
title: "Add Double Layer Hole",
|
|
1805
|
+
handler: () => {
|
|
1806
|
+
return this.addDoubleLayerHole();
|
|
1807
|
+
}
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
command: "clearFeatures",
|
|
1811
|
+
title: "Clear Features",
|
|
1659
1812
|
handler: () => {
|
|
1660
1813
|
var _a;
|
|
1661
1814
|
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1662
1815
|
"ConfigurationService"
|
|
1663
1816
|
);
|
|
1664
1817
|
if (configService) {
|
|
1665
|
-
configService.update("dieline.
|
|
1818
|
+
configService.update("dieline.features", []);
|
|
1666
1819
|
}
|
|
1667
1820
|
return true;
|
|
1668
1821
|
}
|
|
@@ -1670,6 +1823,88 @@ var HoleTool = class {
|
|
|
1670
1823
|
]
|
|
1671
1824
|
};
|
|
1672
1825
|
}
|
|
1826
|
+
addFeature(type) {
|
|
1827
|
+
var _a;
|
|
1828
|
+
if (!this.canvasService) return false;
|
|
1829
|
+
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1830
|
+
"ConfigurationService"
|
|
1831
|
+
);
|
|
1832
|
+
const unit = (configService == null ? void 0 : configService.get("dieline.unit", "mm")) || "mm";
|
|
1833
|
+
const defaultSize = Coordinate.convertUnit(10, "mm", unit);
|
|
1834
|
+
const newFeature = {
|
|
1835
|
+
id: Date.now().toString(),
|
|
1836
|
+
operation: type,
|
|
1837
|
+
target: "original",
|
|
1838
|
+
shape: "rect",
|
|
1839
|
+
x: 0.5,
|
|
1840
|
+
y: 0,
|
|
1841
|
+
// Top edge
|
|
1842
|
+
width: defaultSize,
|
|
1843
|
+
height: defaultSize,
|
|
1844
|
+
rotation: 0
|
|
1845
|
+
};
|
|
1846
|
+
if (configService) {
|
|
1847
|
+
const current = configService.get(
|
|
1848
|
+
"dieline.features",
|
|
1849
|
+
[]
|
|
1850
|
+
);
|
|
1851
|
+
configService.update("dieline.features", [...current, newFeature]);
|
|
1852
|
+
}
|
|
1853
|
+
return true;
|
|
1854
|
+
}
|
|
1855
|
+
addDoubleLayerHole() {
|
|
1856
|
+
var _a;
|
|
1857
|
+
if (!this.canvasService) return false;
|
|
1858
|
+
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1859
|
+
"ConfigurationService"
|
|
1860
|
+
);
|
|
1861
|
+
const unit = (configService == null ? void 0 : configService.get("dieline.unit", "mm")) || "mm";
|
|
1862
|
+
const lugRadius = Coordinate.convertUnit(20, "mm", unit);
|
|
1863
|
+
const holeRadius = Coordinate.convertUnit(15, "mm", unit);
|
|
1864
|
+
const groupId = Date.now().toString();
|
|
1865
|
+
const timestamp = Date.now();
|
|
1866
|
+
const lug = {
|
|
1867
|
+
id: `${timestamp}-lug`,
|
|
1868
|
+
groupId,
|
|
1869
|
+
operation: "add",
|
|
1870
|
+
shape: "circle",
|
|
1871
|
+
x: 0.5,
|
|
1872
|
+
y: 0,
|
|
1873
|
+
radius: lugRadius,
|
|
1874
|
+
// 20mm
|
|
1875
|
+
rotation: 0
|
|
1876
|
+
};
|
|
1877
|
+
const hole = {
|
|
1878
|
+
id: `${timestamp}-hole`,
|
|
1879
|
+
groupId,
|
|
1880
|
+
operation: "subtract",
|
|
1881
|
+
shape: "circle",
|
|
1882
|
+
x: 0.5,
|
|
1883
|
+
y: 0,
|
|
1884
|
+
radius: holeRadius,
|
|
1885
|
+
// 15mm
|
|
1886
|
+
rotation: 0
|
|
1887
|
+
};
|
|
1888
|
+
if (configService) {
|
|
1889
|
+
const current = configService.get(
|
|
1890
|
+
"dieline.features",
|
|
1891
|
+
[]
|
|
1892
|
+
);
|
|
1893
|
+
configService.update("dieline.features", [...current, lug, hole]);
|
|
1894
|
+
}
|
|
1895
|
+
return true;
|
|
1896
|
+
}
|
|
1897
|
+
getGeometryForFeature(geometry, feature) {
|
|
1898
|
+
if ((feature == null ? void 0 : feature.target) === "offset" && geometry.offset !== 0) {
|
|
1899
|
+
return {
|
|
1900
|
+
...geometry,
|
|
1901
|
+
width: geometry.width + geometry.offset * 2,
|
|
1902
|
+
height: geometry.height + geometry.offset * 2,
|
|
1903
|
+
radius: geometry.radius === 0 ? 0 : Math.max(0, geometry.radius + geometry.offset)
|
|
1904
|
+
};
|
|
1905
|
+
}
|
|
1906
|
+
return geometry;
|
|
1907
|
+
}
|
|
1673
1908
|
setup() {
|
|
1674
1909
|
if (!this.canvasService || !this.context) return;
|
|
1675
1910
|
const canvas = this.canvasService.canvas;
|
|
@@ -1677,10 +1912,7 @@ var HoleTool = class {
|
|
|
1677
1912
|
this.handleDielineChange = (geometry) => {
|
|
1678
1913
|
this.currentGeometry = geometry;
|
|
1679
1914
|
this.redraw();
|
|
1680
|
-
|
|
1681
|
-
if (changed) {
|
|
1682
|
-
this.syncHolesToDieline();
|
|
1683
|
-
}
|
|
1915
|
+
this.enforceConstraints();
|
|
1684
1916
|
};
|
|
1685
1917
|
this.context.eventBus.on(
|
|
1686
1918
|
"dieline:geometry:change",
|
|
@@ -1690,69 +1922,101 @@ var HoleTool = class {
|
|
|
1690
1922
|
const commandService = this.context.services.get("CommandService");
|
|
1691
1923
|
if (commandService) {
|
|
1692
1924
|
try {
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
Promise.resolve(geometry).then((g) => {
|
|
1925
|
+
Promise.resolve(commandService.executeCommand("getGeometry")).then(
|
|
1926
|
+
(g) => {
|
|
1696
1927
|
if (g) {
|
|
1697
1928
|
this.currentGeometry = g;
|
|
1698
|
-
this.
|
|
1699
|
-
this.initializeHoles();
|
|
1929
|
+
this.redraw();
|
|
1700
1930
|
}
|
|
1701
|
-
}
|
|
1702
|
-
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1703
1933
|
} catch (e) {
|
|
1704
1934
|
}
|
|
1705
1935
|
}
|
|
1706
1936
|
if (!this.handleMoving) {
|
|
1707
1937
|
this.handleMoving = (e) => {
|
|
1708
|
-
var _a, _b, _c, _d
|
|
1938
|
+
var _a, _b, _c, _d;
|
|
1709
1939
|
const target = e.target;
|
|
1710
|
-
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "
|
|
1940
|
+
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "feature-marker") return;
|
|
1711
1941
|
if (!this.currentGeometry) return;
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
}
|
|
1724
|
-
const
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
constraintGeometry,
|
|
1728
|
-
(_d = holeData == null ? void 0 : holeData.innerRadius) != null ? _d : 15,
|
|
1729
|
-
(_e = holeData == null ? void 0 : holeData.outerRadius) != null ? _e : 25
|
|
1942
|
+
let feature;
|
|
1943
|
+
if ((_b = target.data) == null ? void 0 : _b.isGroup) {
|
|
1944
|
+
const indices = (_c = target.data) == null ? void 0 : _c.indices;
|
|
1945
|
+
if (indices && indices.length > 0) {
|
|
1946
|
+
feature = this.features[indices[0]];
|
|
1947
|
+
}
|
|
1948
|
+
} else {
|
|
1949
|
+
const index = (_d = target.data) == null ? void 0 : _d.index;
|
|
1950
|
+
if (index !== void 0) {
|
|
1951
|
+
feature = this.features[index];
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
const geometry = this.getGeometryForFeature(
|
|
1955
|
+
this.currentGeometry,
|
|
1956
|
+
feature
|
|
1730
1957
|
);
|
|
1958
|
+
const p = new Point(target.left, target.top);
|
|
1959
|
+
const markerStrokeWidth = (target.strokeWidth || 2) * (target.scaleX || 1);
|
|
1960
|
+
const minDim = Math.min(target.getScaledWidth(), target.getScaledHeight());
|
|
1961
|
+
const limit = Math.max(0, minDim / 2 - markerStrokeWidth);
|
|
1962
|
+
const snapped = this.constrainPosition(p, geometry, limit);
|
|
1731
1963
|
target.set({
|
|
1732
|
-
left:
|
|
1733
|
-
top:
|
|
1964
|
+
left: snapped.x,
|
|
1965
|
+
top: snapped.y
|
|
1734
1966
|
});
|
|
1735
1967
|
};
|
|
1736
1968
|
canvas.on("object:moving", this.handleMoving);
|
|
1737
1969
|
}
|
|
1738
1970
|
if (!this.handleModified) {
|
|
1739
1971
|
this.handleModified = (e) => {
|
|
1740
|
-
var _a;
|
|
1972
|
+
var _a, _b, _c, _d;
|
|
1741
1973
|
const target = e.target;
|
|
1742
|
-
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1974
|
+
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "feature-marker") return;
|
|
1975
|
+
if ((_b = target.data) == null ? void 0 : _b.isGroup) {
|
|
1976
|
+
const groupObj = target;
|
|
1977
|
+
const indices = (_c = groupObj.data) == null ? void 0 : _c.indices;
|
|
1978
|
+
if (!indices) return;
|
|
1979
|
+
const groupCenter = new Point(groupObj.left, groupObj.top);
|
|
1980
|
+
const newFeatures = [...this.features];
|
|
1981
|
+
const { x, y } = this.currentGeometry;
|
|
1982
|
+
groupObj.getObjects().forEach((child, i) => {
|
|
1983
|
+
const originalIndex = indices[i];
|
|
1984
|
+
const feature = this.features[originalIndex];
|
|
1985
|
+
const geometry = this.getGeometryForFeature(
|
|
1986
|
+
this.currentGeometry,
|
|
1987
|
+
feature
|
|
1988
|
+
);
|
|
1989
|
+
const { width, height } = geometry;
|
|
1990
|
+
const layoutLeft = x - width / 2;
|
|
1991
|
+
const layoutTop = y - height / 2;
|
|
1992
|
+
const absX = groupCenter.x + (child.left || 0);
|
|
1993
|
+
const absY = groupCenter.y + (child.top || 0);
|
|
1994
|
+
const normalizedX = width > 0 ? (absX - layoutLeft) / width : 0.5;
|
|
1995
|
+
const normalizedY = height > 0 ? (absY - layoutTop) / height : 0.5;
|
|
1996
|
+
newFeatures[originalIndex] = {
|
|
1997
|
+
...newFeatures[originalIndex],
|
|
1998
|
+
x: normalizedX,
|
|
1999
|
+
y: normalizedY
|
|
2000
|
+
};
|
|
2001
|
+
});
|
|
2002
|
+
this.features = newFeatures;
|
|
2003
|
+
const configService = (_d = this.context) == null ? void 0 : _d.services.get(
|
|
2004
|
+
"ConfigurationService"
|
|
2005
|
+
);
|
|
2006
|
+
if (configService) {
|
|
2007
|
+
this.isUpdatingConfig = true;
|
|
2008
|
+
try {
|
|
2009
|
+
configService.update("dieline.features", this.features);
|
|
2010
|
+
} finally {
|
|
2011
|
+
this.isUpdatingConfig = false;
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
} else {
|
|
2015
|
+
this.syncFeatureFromCanvas(target);
|
|
1746
2016
|
}
|
|
1747
2017
|
};
|
|
1748
2018
|
canvas.on("object:modified", this.handleModified);
|
|
1749
2019
|
}
|
|
1750
|
-
this.initializeHoles();
|
|
1751
|
-
}
|
|
1752
|
-
initializeHoles() {
|
|
1753
|
-
if (!this.canvasService) return;
|
|
1754
|
-
this.redraw();
|
|
1755
|
-
this.syncHolesToDieline();
|
|
1756
2020
|
}
|
|
1757
2021
|
teardown() {
|
|
1758
2022
|
if (!this.canvasService) return;
|
|
@@ -1774,357 +2038,259 @@ var HoleTool = class {
|
|
|
1774
2038
|
}
|
|
1775
2039
|
const objects = canvas.getObjects().filter((obj) => {
|
|
1776
2040
|
var _a;
|
|
1777
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2041
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
1778
2042
|
});
|
|
1779
2043
|
objects.forEach((obj) => canvas.remove(obj));
|
|
1780
|
-
if (this.context) {
|
|
1781
|
-
const commandService = this.context.services.get("CommandService");
|
|
1782
|
-
if (commandService) {
|
|
1783
|
-
try {
|
|
1784
|
-
commandService.executeCommand("setHoles", []);
|
|
1785
|
-
} catch (e) {
|
|
1786
|
-
}
|
|
1787
|
-
}
|
|
1788
|
-
}
|
|
1789
2044
|
this.canvasService.requestRenderAll();
|
|
1790
2045
|
}
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
var _a;
|
|
1796
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
|
|
1797
|
-
}
|
|
1798
|
-
);
|
|
1799
|
-
if (objects.length === 0 && this.holes.length > 0) {
|
|
1800
|
-
console.warn("HoleTool: No markers found on canvas to sync from");
|
|
1801
|
-
return;
|
|
1802
|
-
}
|
|
1803
|
-
objects.sort(
|
|
1804
|
-
(a, b) => {
|
|
1805
|
-
var _a, _b, _c, _d;
|
|
1806
|
-
return ((_b = (_a = a.data) == null ? void 0 : _a.index) != null ? _b : 0) - ((_d = (_c = b.data) == null ? void 0 : _c.index) != null ? _d : 0);
|
|
1807
|
-
}
|
|
1808
|
-
);
|
|
1809
|
-
const newHoles = objects.map((obj, i) => {
|
|
1810
|
-
var _a, _b, _c, _d;
|
|
1811
|
-
const original = this.holes[i];
|
|
1812
|
-
const newAbsX = obj.left;
|
|
1813
|
-
const newAbsY = obj.top;
|
|
1814
|
-
if (isNaN(newAbsX) || isNaN(newAbsY)) {
|
|
1815
|
-
console.error("HoleTool: Invalid marker coordinates", {
|
|
1816
|
-
newAbsX,
|
|
1817
|
-
newAbsY
|
|
1818
|
-
});
|
|
1819
|
-
return original;
|
|
1820
|
-
}
|
|
1821
|
-
const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
|
|
1822
|
-
const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
|
|
1823
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1824
|
-
if (original && original.anchor && this.currentGeometry) {
|
|
1825
|
-
const { x, y, width, height } = this.currentGeometry;
|
|
1826
|
-
let bx = x;
|
|
1827
|
-
let by = y;
|
|
1828
|
-
const left = x - width / 2;
|
|
1829
|
-
const right = x + width / 2;
|
|
1830
|
-
const top = y - height / 2;
|
|
1831
|
-
const bottom = y + height / 2;
|
|
1832
|
-
switch (original.anchor) {
|
|
1833
|
-
case "top-left":
|
|
1834
|
-
bx = left;
|
|
1835
|
-
by = top;
|
|
1836
|
-
break;
|
|
1837
|
-
case "top-center":
|
|
1838
|
-
bx = x;
|
|
1839
|
-
by = top;
|
|
1840
|
-
break;
|
|
1841
|
-
case "top-right":
|
|
1842
|
-
bx = right;
|
|
1843
|
-
by = top;
|
|
1844
|
-
break;
|
|
1845
|
-
case "center-left":
|
|
1846
|
-
bx = left;
|
|
1847
|
-
by = y;
|
|
1848
|
-
break;
|
|
1849
|
-
case "center":
|
|
1850
|
-
bx = x;
|
|
1851
|
-
by = y;
|
|
1852
|
-
break;
|
|
1853
|
-
case "center-right":
|
|
1854
|
-
bx = right;
|
|
1855
|
-
by = y;
|
|
1856
|
-
break;
|
|
1857
|
-
case "bottom-left":
|
|
1858
|
-
bx = left;
|
|
1859
|
-
by = bottom;
|
|
1860
|
-
break;
|
|
1861
|
-
case "bottom-center":
|
|
1862
|
-
bx = x;
|
|
1863
|
-
by = bottom;
|
|
1864
|
-
break;
|
|
1865
|
-
case "bottom-right":
|
|
1866
|
-
bx = right;
|
|
1867
|
-
by = bottom;
|
|
1868
|
-
break;
|
|
1869
|
-
}
|
|
1870
|
-
return {
|
|
1871
|
-
...original,
|
|
1872
|
-
// Denormalize offset back to physical units (mm)
|
|
1873
|
-
offsetX: (newAbsX - bx) / scale / unitScale,
|
|
1874
|
-
offsetY: (newAbsY - by) / scale / unitScale,
|
|
1875
|
-
// Clear direct coordinates if we use anchor
|
|
1876
|
-
x: void 0,
|
|
1877
|
-
y: void 0,
|
|
1878
|
-
// Ensure other properties are preserved
|
|
1879
|
-
innerRadius: original.innerRadius,
|
|
1880
|
-
outerRadius: original.outerRadius,
|
|
1881
|
-
shape: original.shape || "circle"
|
|
1882
|
-
};
|
|
1883
|
-
}
|
|
1884
|
-
let normalizedX = 0.5;
|
|
1885
|
-
let normalizedY = 0.5;
|
|
1886
|
-
if (this.currentGeometry) {
|
|
1887
|
-
const { x, y, width, height } = this.currentGeometry;
|
|
1888
|
-
const left = x - width / 2;
|
|
1889
|
-
const top = y - height / 2;
|
|
1890
|
-
normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
|
|
1891
|
-
normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
|
|
1892
|
-
} else {
|
|
1893
|
-
const { width, height } = this.canvasService.canvas;
|
|
1894
|
-
normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
|
|
1895
|
-
normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
|
|
1896
|
-
}
|
|
1897
|
-
return {
|
|
1898
|
-
...original,
|
|
1899
|
-
x: normalizedX,
|
|
1900
|
-
y: normalizedY,
|
|
1901
|
-
// Clear offsets if we are using direct normalized coordinates
|
|
1902
|
-
offsetX: void 0,
|
|
1903
|
-
offsetY: void 0,
|
|
1904
|
-
// Ensure other properties are preserved
|
|
1905
|
-
innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
|
|
1906
|
-
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
|
|
1907
|
-
shape: (original == null ? void 0 : original.shape) || "circle"
|
|
1908
|
-
};
|
|
2046
|
+
constrainPosition(p, geometry, limit) {
|
|
2047
|
+
const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, {
|
|
2048
|
+
...geometry,
|
|
2049
|
+
features: []
|
|
1909
2050
|
});
|
|
1910
|
-
|
|
1911
|
-
|
|
2051
|
+
const dx = p.x - nearest.x;
|
|
2052
|
+
const dy = p.y - nearest.y;
|
|
2053
|
+
const dist = Math.sqrt(dx * dx + dy * dy);
|
|
2054
|
+
if (dist <= limit) {
|
|
2055
|
+
return { x: p.x, y: p.y };
|
|
2056
|
+
}
|
|
2057
|
+
const scale = limit / dist;
|
|
2058
|
+
return {
|
|
2059
|
+
x: nearest.x + dx * scale,
|
|
2060
|
+
y: nearest.y + dy * scale
|
|
2061
|
+
};
|
|
1912
2062
|
}
|
|
1913
|
-
|
|
1914
|
-
|
|
2063
|
+
syncFeatureFromCanvas(target) {
|
|
2064
|
+
var _a;
|
|
2065
|
+
if (!this.currentGeometry || !this.context) return;
|
|
2066
|
+
const index = (_a = target.data) == null ? void 0 : _a.index;
|
|
2067
|
+
if (index === void 0 || index < 0 || index >= this.features.length)
|
|
2068
|
+
return;
|
|
2069
|
+
const feature = this.features[index];
|
|
2070
|
+
const geometry = this.getGeometryForFeature(this.currentGeometry, feature);
|
|
2071
|
+
const { width, height, x, y } = geometry;
|
|
2072
|
+
const left = x - width / 2;
|
|
2073
|
+
const top = y - height / 2;
|
|
2074
|
+
const normalizedX = width > 0 ? (target.left - left) / width : 0.5;
|
|
2075
|
+
const normalizedY = height > 0 ? (target.top - top) / height : 0.5;
|
|
2076
|
+
const updatedFeature = {
|
|
2077
|
+
...feature,
|
|
2078
|
+
x: normalizedX,
|
|
2079
|
+
y: normalizedY
|
|
2080
|
+
// Could also update rotation if we allowed rotating markers
|
|
2081
|
+
};
|
|
2082
|
+
const newFeatures = [...this.features];
|
|
2083
|
+
newFeatures[index] = updatedFeature;
|
|
2084
|
+
this.features = newFeatures;
|
|
1915
2085
|
const configService = this.context.services.get(
|
|
1916
2086
|
"ConfigurationService"
|
|
1917
2087
|
);
|
|
1918
2088
|
if (configService) {
|
|
1919
2089
|
this.isUpdatingConfig = true;
|
|
1920
2090
|
try {
|
|
1921
|
-
configService.update("dieline.
|
|
2091
|
+
configService.update("dieline.features", this.features);
|
|
1922
2092
|
} finally {
|
|
1923
2093
|
this.isUpdatingConfig = false;
|
|
1924
2094
|
}
|
|
1925
2095
|
}
|
|
1926
2096
|
}
|
|
1927
2097
|
redraw() {
|
|
1928
|
-
if (!this.canvasService) return;
|
|
2098
|
+
if (!this.canvasService || !this.currentGeometry) return;
|
|
1929
2099
|
const canvas = this.canvasService.canvas;
|
|
1930
|
-
const
|
|
2100
|
+
const geometry = this.currentGeometry;
|
|
1931
2101
|
const existing = canvas.getObjects().filter((obj) => {
|
|
1932
2102
|
var _a;
|
|
1933
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2103
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
1934
2104
|
});
|
|
1935
2105
|
existing.forEach((obj) => canvas.remove(obj));
|
|
1936
|
-
|
|
1937
|
-
if (!holes || holes.length === 0) {
|
|
2106
|
+
if (!this.features || this.features.length === 0) {
|
|
1938
2107
|
this.canvasService.requestRenderAll();
|
|
1939
2108
|
return;
|
|
1940
2109
|
}
|
|
1941
|
-
const
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
2110
|
+
const scale = geometry.scale || 1;
|
|
2111
|
+
const finalScale = scale;
|
|
2112
|
+
const groups = {};
|
|
2113
|
+
const singles = [];
|
|
2114
|
+
this.features.forEach((f, i) => {
|
|
2115
|
+
if (f.groupId) {
|
|
2116
|
+
if (!groups[f.groupId]) groups[f.groupId] = [];
|
|
2117
|
+
groups[f.groupId].push({ feature: f, index: i });
|
|
2118
|
+
} else {
|
|
2119
|
+
singles.push({ feature: f, index: i });
|
|
2120
|
+
}
|
|
2121
|
+
});
|
|
2122
|
+
const createMarkerShape = (feature, pos) => {
|
|
2123
|
+
const featureScale = scale;
|
|
2124
|
+
const visualWidth = (feature.width || 10) * featureScale;
|
|
2125
|
+
const visualHeight = (feature.height || 10) * featureScale;
|
|
2126
|
+
const visualRadius = (feature.radius || 0) * featureScale;
|
|
2127
|
+
const color = feature.color || (feature.operation === "add" ? "#00FF00" : "#FF0000");
|
|
2128
|
+
const strokeDash = feature.strokeDash || (feature.operation === "subtract" ? [4, 4] : void 0);
|
|
2129
|
+
let shape;
|
|
2130
|
+
if (feature.shape === "rect") {
|
|
2131
|
+
shape = new Rect2({
|
|
2132
|
+
width: visualWidth,
|
|
2133
|
+
height: visualHeight,
|
|
2134
|
+
rx: visualRadius,
|
|
2135
|
+
ry: visualRadius,
|
|
2136
|
+
fill: "transparent",
|
|
2137
|
+
stroke: color,
|
|
2138
|
+
strokeWidth: 2,
|
|
2139
|
+
strokeDashArray: strokeDash,
|
|
2140
|
+
originX: "center",
|
|
2141
|
+
originY: "center",
|
|
2142
|
+
left: pos.x,
|
|
2143
|
+
top: pos.y
|
|
2144
|
+
});
|
|
2145
|
+
} else {
|
|
2146
|
+
shape = new Circle({
|
|
2147
|
+
radius: visualRadius || 5 * finalScale,
|
|
2148
|
+
fill: "transparent",
|
|
2149
|
+
stroke: color,
|
|
2150
|
+
strokeWidth: 2,
|
|
2151
|
+
strokeDashArray: strokeDash,
|
|
2152
|
+
originX: "center",
|
|
2153
|
+
originY: "center",
|
|
2154
|
+
left: pos.x,
|
|
2155
|
+
top: pos.y
|
|
2156
|
+
});
|
|
2157
|
+
}
|
|
2158
|
+
if (feature.rotation) {
|
|
2159
|
+
shape.rotate(feature.rotation);
|
|
2160
|
+
}
|
|
2161
|
+
return shape;
|
|
1948
2162
|
};
|
|
1949
|
-
|
|
1950
|
-
const
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
const visualInnerRadius = hole.innerRadius * unitScale * scale;
|
|
1954
|
-
const visualOuterRadius = hole.outerRadius * unitScale * scale;
|
|
1955
|
-
const pos = resolveHolePosition(
|
|
1956
|
-
{
|
|
1957
|
-
...hole,
|
|
1958
|
-
offsetX: (hole.offsetX || 0) * unitScale * scale,
|
|
1959
|
-
offsetY: (hole.offsetY || 0) * unitScale * scale
|
|
1960
|
-
},
|
|
1961
|
-
geometry,
|
|
1962
|
-
{ width: geometry.width, height: geometry.height }
|
|
1963
|
-
// Use geometry dims instead of canvas
|
|
2163
|
+
singles.forEach(({ feature, index }) => {
|
|
2164
|
+
const geometry2 = this.getGeometryForFeature(
|
|
2165
|
+
this.currentGeometry,
|
|
2166
|
+
feature
|
|
1964
2167
|
);
|
|
1965
|
-
const
|
|
1966
|
-
const
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
fill: "transparent",
|
|
1977
|
-
stroke: "red",
|
|
1978
|
-
strokeWidth: 2,
|
|
1979
|
-
originX: "center",
|
|
1980
|
-
originY: "center"
|
|
2168
|
+
const pos = resolveFeaturePosition(feature, geometry2);
|
|
2169
|
+
const marker = createMarkerShape(feature, pos);
|
|
2170
|
+
marker.set({
|
|
2171
|
+
selectable: true,
|
|
2172
|
+
hasControls: false,
|
|
2173
|
+
hasBorders: false,
|
|
2174
|
+
hoverCursor: "move",
|
|
2175
|
+
lockRotation: true,
|
|
2176
|
+
lockScalingX: true,
|
|
2177
|
+
lockScalingY: true,
|
|
2178
|
+
data: { type: "feature-marker", index, isGroup: false }
|
|
1981
2179
|
});
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
stroke: "#666",
|
|
1987
|
-
strokeWidth: 1,
|
|
1988
|
-
strokeDashArray: [5, 5],
|
|
1989
|
-
originX: "center",
|
|
1990
|
-
originY: "center"
|
|
1991
|
-
}) : new Circle({
|
|
1992
|
-
radius: visualOuterRadius,
|
|
1993
|
-
fill: "transparent",
|
|
1994
|
-
stroke: "#666",
|
|
1995
|
-
strokeWidth: 1,
|
|
1996
|
-
strokeDashArray: [5, 5],
|
|
1997
|
-
originX: "center",
|
|
1998
|
-
originY: "center"
|
|
2180
|
+
marker.set("opacity", 0);
|
|
2181
|
+
marker.on("mouseover", () => {
|
|
2182
|
+
marker.set("opacity", 1);
|
|
2183
|
+
canvas.requestRenderAll();
|
|
1999
2184
|
});
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2185
|
+
marker.on("mouseout", () => {
|
|
2186
|
+
if (canvas.getActiveObject() !== marker) {
|
|
2187
|
+
marker.set("opacity", 0);
|
|
2188
|
+
canvas.requestRenderAll();
|
|
2189
|
+
}
|
|
2190
|
+
});
|
|
2191
|
+
marker.on("selected", () => {
|
|
2192
|
+
marker.set("opacity", 1);
|
|
2193
|
+
canvas.requestRenderAll();
|
|
2194
|
+
});
|
|
2195
|
+
marker.on("deselected", () => {
|
|
2196
|
+
marker.set("opacity", 0);
|
|
2197
|
+
canvas.requestRenderAll();
|
|
2198
|
+
});
|
|
2199
|
+
canvas.add(marker);
|
|
2200
|
+
canvas.bringObjectToFront(marker);
|
|
2201
|
+
});
|
|
2202
|
+
Object.keys(groups).forEach((groupId) => {
|
|
2203
|
+
const members = groups[groupId];
|
|
2204
|
+
if (members.length === 0) return;
|
|
2205
|
+
const shapes = members.map(({ feature }) => {
|
|
2206
|
+
const geometry2 = this.getGeometryForFeature(
|
|
2207
|
+
this.currentGeometry,
|
|
2208
|
+
feature
|
|
2209
|
+
);
|
|
2210
|
+
const pos = resolveFeaturePosition(feature, geometry2);
|
|
2211
|
+
return createMarkerShape(feature, pos);
|
|
2212
|
+
});
|
|
2213
|
+
const groupObj = new Group(shapes, {
|
|
2005
2214
|
selectable: true,
|
|
2006
2215
|
hasControls: false,
|
|
2007
|
-
// Don't allow resizing/rotating
|
|
2008
2216
|
hasBorders: false,
|
|
2009
|
-
subTargetCheck: false,
|
|
2010
|
-
opacity: 0,
|
|
2011
|
-
// Default hidden
|
|
2012
2217
|
hoverCursor: "move",
|
|
2013
|
-
|
|
2218
|
+
lockRotation: true,
|
|
2219
|
+
lockScalingX: true,
|
|
2220
|
+
lockScalingY: true,
|
|
2221
|
+
subTargetCheck: true,
|
|
2222
|
+
// Allow events to pass through if needed, but we treat as one
|
|
2223
|
+
interactive: false,
|
|
2224
|
+
// Children not interactive
|
|
2225
|
+
// @ts-ignore
|
|
2226
|
+
data: {
|
|
2227
|
+
type: "feature-marker",
|
|
2228
|
+
isGroup: true,
|
|
2229
|
+
groupId,
|
|
2230
|
+
indices: members.map((m) => m.index)
|
|
2231
|
+
}
|
|
2014
2232
|
});
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2233
|
+
groupObj.set("opacity", 0);
|
|
2234
|
+
groupObj.on("mouseover", () => {
|
|
2235
|
+
groupObj.set("opacity", 1);
|
|
2018
2236
|
canvas.requestRenderAll();
|
|
2019
2237
|
});
|
|
2020
|
-
|
|
2021
|
-
if (canvas.getActiveObject() !==
|
|
2022
|
-
|
|
2238
|
+
groupObj.on("mouseout", () => {
|
|
2239
|
+
if (canvas.getActiveObject() !== groupObj) {
|
|
2240
|
+
groupObj.set("opacity", 0);
|
|
2023
2241
|
canvas.requestRenderAll();
|
|
2024
2242
|
}
|
|
2025
2243
|
});
|
|
2026
|
-
|
|
2027
|
-
|
|
2244
|
+
groupObj.on("selected", () => {
|
|
2245
|
+
groupObj.set("opacity", 1);
|
|
2028
2246
|
canvas.requestRenderAll();
|
|
2029
2247
|
});
|
|
2030
|
-
|
|
2031
|
-
|
|
2248
|
+
groupObj.on("deselected", () => {
|
|
2249
|
+
groupObj.set("opacity", 0);
|
|
2032
2250
|
canvas.requestRenderAll();
|
|
2033
2251
|
});
|
|
2034
|
-
canvas.add(
|
|
2035
|
-
canvas.bringObjectToFront(
|
|
2036
|
-
});
|
|
2037
|
-
const markers = canvas.getObjects().filter((o) => {
|
|
2038
|
-
var _a;
|
|
2039
|
-
return ((_a = o.data) == null ? void 0 : _a.type) === "hole-marker";
|
|
2252
|
+
canvas.add(groupObj);
|
|
2253
|
+
canvas.bringObjectToFront(groupObj);
|
|
2040
2254
|
});
|
|
2041
|
-
markers.forEach((m) => canvas.bringObjectToFront(m));
|
|
2042
2255
|
this.canvasService.requestRenderAll();
|
|
2043
2256
|
}
|
|
2044
2257
|
enforceConstraints() {
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
}
|
|
2049
|
-
const effectiveOffset = this.constraintTarget === "original" ? 0 : geometry.offset;
|
|
2050
|
-
const constraintGeometry = {
|
|
2051
|
-
...geometry,
|
|
2052
|
-
width: Math.max(0, geometry.width + effectiveOffset * 2),
|
|
2053
|
-
height: Math.max(0, geometry.height + effectiveOffset * 2),
|
|
2054
|
-
radius: Math.max(0, geometry.radius + effectiveOffset)
|
|
2055
|
-
};
|
|
2056
|
-
const objects = this.canvasService.canvas.getObjects().filter((obj) => {
|
|
2258
|
+
if (!this.canvasService || !this.currentGeometry) return;
|
|
2259
|
+
const canvas = this.canvasService.canvas;
|
|
2260
|
+
const markers = canvas.getObjects().filter((obj) => {
|
|
2057
2261
|
var _a;
|
|
2058
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2262
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
2059
2263
|
});
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2264
|
+
markers.forEach((marker) => {
|
|
2265
|
+
var _a, _b, _c;
|
|
2266
|
+
let feature;
|
|
2267
|
+
if ((_a = marker.data) == null ? void 0 : _a.isGroup) {
|
|
2268
|
+
const indices = (_b = marker.data) == null ? void 0 : _b.indices;
|
|
2269
|
+
if (indices && indices.length > 0) {
|
|
2270
|
+
feature = this.features[indices[0]];
|
|
2271
|
+
}
|
|
2272
|
+
} else {
|
|
2273
|
+
const index = (_c = marker.data) == null ? void 0 : _c.index;
|
|
2274
|
+
if (index !== void 0) {
|
|
2275
|
+
feature = this.features[index];
|
|
2276
|
+
}
|
|
2065
2277
|
}
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
var _a, _b;
|
|
2070
|
-
const currentPos = new Point(obj.left, obj.top);
|
|
2071
|
-
const holeData = this.holes[i];
|
|
2072
|
-
const scale = geometry.scale || 1;
|
|
2073
|
-
const unit = geometry.unit || "mm";
|
|
2074
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
2075
|
-
const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
|
|
2076
|
-
const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
|
|
2077
|
-
const newPos = this.calculateConstrainedPosition(
|
|
2078
|
-
currentPos,
|
|
2079
|
-
constraintGeometry,
|
|
2080
|
-
innerR,
|
|
2081
|
-
outerR
|
|
2278
|
+
const geometry = this.getGeometryForFeature(
|
|
2279
|
+
this.currentGeometry,
|
|
2280
|
+
feature
|
|
2082
2281
|
);
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2282
|
+
const markerStrokeWidth = (marker.strokeWidth || 2) * (marker.scaleX || 1);
|
|
2283
|
+
const minDim = Math.min(marker.getScaledWidth(), marker.getScaledHeight());
|
|
2284
|
+
const limit = Math.max(0, minDim / 2 - markerStrokeWidth);
|
|
2285
|
+
const snapped = this.constrainPosition(
|
|
2286
|
+
new Point(marker.left, marker.top),
|
|
2287
|
+
geometry,
|
|
2288
|
+
limit
|
|
2289
|
+
);
|
|
2290
|
+
marker.set({ left: snapped.x, top: snapped.y });
|
|
2291
|
+
marker.setCoords();
|
|
2091
2292
|
});
|
|
2092
|
-
|
|
2093
|
-
this.syncHolesFromCanvas();
|
|
2094
|
-
return true;
|
|
2095
|
-
}
|
|
2096
|
-
return false;
|
|
2097
|
-
}
|
|
2098
|
-
calculateConstrainedPosition(p, g, innerRadius, outerRadius) {
|
|
2099
|
-
const options = {
|
|
2100
|
-
...g,
|
|
2101
|
-
holes: []
|
|
2102
|
-
// We don't need holes for boundary calculation
|
|
2103
|
-
};
|
|
2104
|
-
const nearest = getNearestPointOnDieline(
|
|
2105
|
-
{ x: p.x, y: p.y },
|
|
2106
|
-
options
|
|
2107
|
-
);
|
|
2108
|
-
const nearestP = new Point(nearest.x, nearest.y);
|
|
2109
|
-
const dist = p.distanceFrom(nearestP);
|
|
2110
|
-
const v = p.subtract(nearestP);
|
|
2111
|
-
const center = new Point(g.x, g.y);
|
|
2112
|
-
const distToCenter = p.distanceFrom(center);
|
|
2113
|
-
const nearestDistToCenter = nearestP.distanceFrom(center);
|
|
2114
|
-
let signedDist = dist;
|
|
2115
|
-
if (distToCenter < nearestDistToCenter) {
|
|
2116
|
-
signedDist = -dist;
|
|
2117
|
-
}
|
|
2118
|
-
let clampedDist = signedDist;
|
|
2119
|
-
if (signedDist > 0) {
|
|
2120
|
-
clampedDist = Math.min(signedDist, innerRadius);
|
|
2121
|
-
} else {
|
|
2122
|
-
clampedDist = Math.max(signedDist, -outerRadius);
|
|
2123
|
-
}
|
|
2124
|
-
if (dist < 1e-3) return nearestP;
|
|
2125
|
-
const scale = Math.abs(clampedDist) / (dist || 1);
|
|
2126
|
-
const offset = v.scalarMultiply(scale);
|
|
2127
|
-
return nearestP.add(offset);
|
|
2293
|
+
canvas.requestRenderAll();
|
|
2128
2294
|
}
|
|
2129
2295
|
};
|
|
2130
2296
|
|
|
@@ -2141,6 +2307,7 @@ var ImageTool = class {
|
|
|
2141
2307
|
};
|
|
2142
2308
|
this.items = [];
|
|
2143
2309
|
this.objectMap = /* @__PURE__ */ new Map();
|
|
2310
|
+
this.loadResolvers = /* @__PURE__ */ new Map();
|
|
2144
2311
|
this.isUpdatingConfig = false;
|
|
2145
2312
|
}
|
|
2146
2313
|
activate(context) {
|
|
@@ -2150,19 +2317,15 @@ var ImageTool = class {
|
|
|
2150
2317
|
console.warn("CanvasService not found for ImageTool");
|
|
2151
2318
|
return;
|
|
2152
2319
|
}
|
|
2153
|
-
const configService = context.services.get(
|
|
2320
|
+
const configService = context.services.get(
|
|
2321
|
+
"ConfigurationService"
|
|
2322
|
+
);
|
|
2154
2323
|
if (configService) {
|
|
2155
2324
|
this.items = configService.get("image.items", []) || [];
|
|
2156
2325
|
configService.onAnyChange((e) => {
|
|
2157
2326
|
if (this.isUpdatingConfig) return;
|
|
2158
|
-
let shouldUpdate = false;
|
|
2159
2327
|
if (e.key === "image.items") {
|
|
2160
2328
|
this.items = e.value || [];
|
|
2161
|
-
shouldUpdate = true;
|
|
2162
|
-
} else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
|
|
2163
|
-
shouldUpdate = true;
|
|
2164
|
-
}
|
|
2165
|
-
if (shouldUpdate) {
|
|
2166
2329
|
this.updateImages();
|
|
2167
2330
|
}
|
|
2168
2331
|
});
|
|
@@ -2198,15 +2361,39 @@ var ImageTool = class {
|
|
|
2198
2361
|
{
|
|
2199
2362
|
command: "addImage",
|
|
2200
2363
|
title: "Add Image",
|
|
2201
|
-
handler: (url, options) => {
|
|
2364
|
+
handler: async (url, options) => {
|
|
2365
|
+
const id = this.generateId();
|
|
2202
2366
|
const newItem = {
|
|
2203
|
-
id
|
|
2367
|
+
id,
|
|
2204
2368
|
url,
|
|
2205
2369
|
opacity: 1,
|
|
2206
2370
|
...options
|
|
2207
2371
|
};
|
|
2372
|
+
const promise = new Promise((resolve) => {
|
|
2373
|
+
this.loadResolvers.set(id, () => resolve(id));
|
|
2374
|
+
});
|
|
2208
2375
|
this.updateConfig([...this.items, newItem]);
|
|
2209
|
-
return
|
|
2376
|
+
return promise;
|
|
2377
|
+
}
|
|
2378
|
+
},
|
|
2379
|
+
{
|
|
2380
|
+
command: "fitImageToArea",
|
|
2381
|
+
title: "Fit Image to Area",
|
|
2382
|
+
handler: (id, area) => {
|
|
2383
|
+
var _a, _b;
|
|
2384
|
+
const item = this.items.find((i) => i.id === id);
|
|
2385
|
+
const obj = this.objectMap.get(id);
|
|
2386
|
+
if (item && obj && obj.width && obj.height) {
|
|
2387
|
+
const scale = Math.max(
|
|
2388
|
+
area.width / obj.width,
|
|
2389
|
+
area.height / obj.height
|
|
2390
|
+
);
|
|
2391
|
+
this.updateImageInConfig(id, {
|
|
2392
|
+
scale,
|
|
2393
|
+
left: (_a = area.left) != null ? _a : 0.5,
|
|
2394
|
+
top: (_b = area.top) != null ? _b : 0.5
|
|
2395
|
+
});
|
|
2396
|
+
}
|
|
2210
2397
|
}
|
|
2211
2398
|
},
|
|
2212
2399
|
{
|
|
@@ -2274,7 +2461,9 @@ var ImageTool = class {
|
|
|
2274
2461
|
if (!this.context) return;
|
|
2275
2462
|
this.isUpdatingConfig = true;
|
|
2276
2463
|
this.items = newItems;
|
|
2277
|
-
const configService = this.context.services.get(
|
|
2464
|
+
const configService = this.context.services.get(
|
|
2465
|
+
"ConfigurationService"
|
|
2466
|
+
);
|
|
2278
2467
|
if (configService) {
|
|
2279
2468
|
configService.update("image.items", newItems);
|
|
2280
2469
|
}
|
|
@@ -2320,53 +2509,12 @@ var ImageTool = class {
|
|
|
2320
2509
|
var _a, _b;
|
|
2321
2510
|
const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
|
|
2322
2511
|
const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
|
|
2323
|
-
let layoutScale = 1;
|
|
2324
|
-
let layoutOffsetX = 0;
|
|
2325
|
-
let layoutOffsetY = 0;
|
|
2326
|
-
let visualWidth = canvasW;
|
|
2327
|
-
let visualHeight = canvasH;
|
|
2328
|
-
let dielinePhysicalWidth = 500;
|
|
2329
|
-
let dielinePhysicalHeight = 500;
|
|
2330
|
-
let bleedOffset = 0;
|
|
2331
|
-
if (this.context) {
|
|
2332
|
-
const configService = this.context.services.get("ConfigurationService");
|
|
2333
|
-
if (configService) {
|
|
2334
|
-
dielinePhysicalWidth = configService.get("dieline.width") || 500;
|
|
2335
|
-
dielinePhysicalHeight = configService.get("dieline.height") || 500;
|
|
2336
|
-
bleedOffset = configService.get("dieline.offset") || 0;
|
|
2337
|
-
const paddingValue = configService.get("dieline.padding") || 40;
|
|
2338
|
-
let padding = 0;
|
|
2339
|
-
if (typeof paddingValue === "number") {
|
|
2340
|
-
padding = paddingValue;
|
|
2341
|
-
} else if (typeof paddingValue === "string") {
|
|
2342
|
-
if (paddingValue.endsWith("%")) {
|
|
2343
|
-
const percent = parseFloat(paddingValue) / 100;
|
|
2344
|
-
padding = Math.min(canvasW, canvasH) * percent;
|
|
2345
|
-
} else {
|
|
2346
|
-
padding = parseFloat(paddingValue) || 0;
|
|
2347
|
-
}
|
|
2348
|
-
}
|
|
2349
|
-
const layout = Coordinate.calculateLayout(
|
|
2350
|
-
{ width: canvasW, height: canvasH },
|
|
2351
|
-
{ width: dielinePhysicalWidth, height: dielinePhysicalHeight },
|
|
2352
|
-
padding
|
|
2353
|
-
);
|
|
2354
|
-
layoutScale = layout.scale;
|
|
2355
|
-
layoutOffsetX = layout.offsetX;
|
|
2356
|
-
layoutOffsetY = layout.offsetY;
|
|
2357
|
-
visualWidth = layout.width;
|
|
2358
|
-
visualHeight = layout.height;
|
|
2359
|
-
}
|
|
2360
|
-
}
|
|
2361
2512
|
return {
|
|
2362
|
-
layoutScale,
|
|
2363
|
-
layoutOffsetX,
|
|
2364
|
-
layoutOffsetY,
|
|
2365
|
-
visualWidth,
|
|
2366
|
-
visualHeight
|
|
2367
|
-
dielinePhysicalWidth,
|
|
2368
|
-
dielinePhysicalHeight,
|
|
2369
|
-
bleedOffset
|
|
2513
|
+
layoutScale: 1,
|
|
2514
|
+
layoutOffsetX: 0,
|
|
2515
|
+
layoutOffsetY: 0,
|
|
2516
|
+
visualWidth: canvasW,
|
|
2517
|
+
visualHeight: canvasH
|
|
2370
2518
|
};
|
|
2371
2519
|
}
|
|
2372
2520
|
updateImages() {
|
|
@@ -2389,8 +2537,8 @@ var ImageTool = class {
|
|
|
2389
2537
|
if (!obj) {
|
|
2390
2538
|
this.loadImage(item, layer, layout);
|
|
2391
2539
|
} else {
|
|
2392
|
-
this.updateObjectProperties(obj, item, layout);
|
|
2393
2540
|
layer.remove(obj);
|
|
2541
|
+
this.updateObjectProperties(obj, item, layout);
|
|
2394
2542
|
layer.add(obj);
|
|
2395
2543
|
}
|
|
2396
2544
|
});
|
|
@@ -2398,10 +2546,17 @@ var ImageTool = class {
|
|
|
2398
2546
|
this.canvasService.requestRenderAll();
|
|
2399
2547
|
}
|
|
2400
2548
|
updateObjectProperties(obj, item, layout) {
|
|
2401
|
-
const {
|
|
2549
|
+
const {
|
|
2550
|
+
layoutScale,
|
|
2551
|
+
layoutOffsetX,
|
|
2552
|
+
layoutOffsetY,
|
|
2553
|
+
visualWidth,
|
|
2554
|
+
visualHeight
|
|
2555
|
+
} = layout;
|
|
2402
2556
|
const updates = {};
|
|
2403
2557
|
if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
|
|
2404
|
-
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2558
|
+
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2559
|
+
updates.angle = item.angle;
|
|
2405
2560
|
if (item.left !== void 0) {
|
|
2406
2561
|
const globalLeft = layoutOffsetX + item.left * visualWidth;
|
|
2407
2562
|
if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
|
|
@@ -2410,13 +2565,12 @@ var ImageTool = class {
|
|
|
2410
2565
|
const globalTop = layoutOffsetY + item.top * visualHeight;
|
|
2411
2566
|
if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
|
|
2412
2567
|
}
|
|
2413
|
-
if (item.
|
|
2414
|
-
const
|
|
2415
|
-
if (Math.abs(obj.scaleX -
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
|
|
2568
|
+
if (item.scale !== void 0) {
|
|
2569
|
+
const targetScale = item.scale * layoutScale;
|
|
2570
|
+
if (Math.abs(obj.scaleX - targetScale) > 1e-3) {
|
|
2571
|
+
updates.scaleX = targetScale;
|
|
2572
|
+
updates.scaleY = targetScale;
|
|
2573
|
+
}
|
|
2420
2574
|
}
|
|
2421
2575
|
if (obj.originX !== "center") {
|
|
2422
2576
|
updates.originX = "center";
|
|
@@ -2424,6 +2578,7 @@ var ImageTool = class {
|
|
|
2424
2578
|
}
|
|
2425
2579
|
if (Object.keys(updates).length > 0) {
|
|
2426
2580
|
obj.set(updates);
|
|
2581
|
+
obj.setCoords();
|
|
2427
2582
|
}
|
|
2428
2583
|
}
|
|
2429
2584
|
loadImage(item, layer, layout) {
|
|
@@ -2443,18 +2598,10 @@ var ImageTool = class {
|
|
|
2443
2598
|
ml: false,
|
|
2444
2599
|
mr: false
|
|
2445
2600
|
});
|
|
2446
|
-
let {
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
const targetHeight = dielinePhysicalHeight + 2 * bleedOffset;
|
|
2451
|
-
const targetMax = Math.max(targetWidth, targetHeight);
|
|
2452
|
-
const imageMax = Math.max(image.width || 1, image.height || 1);
|
|
2453
|
-
const scale = targetMax / imageMax;
|
|
2454
|
-
width = (image.width || 1) * scale;
|
|
2455
|
-
height = (image.height || 1) * scale;
|
|
2456
|
-
item.width = width;
|
|
2457
|
-
item.height = height;
|
|
2601
|
+
let { scale, left, top } = item;
|
|
2602
|
+
if (scale === void 0) {
|
|
2603
|
+
scale = 1;
|
|
2604
|
+
item.scale = scale;
|
|
2458
2605
|
}
|
|
2459
2606
|
if (left === void 0 && top === void 0) {
|
|
2460
2607
|
left = 0.5;
|
|
@@ -2465,13 +2612,18 @@ var ImageTool = class {
|
|
|
2465
2612
|
this.updateObjectProperties(image, item, layout);
|
|
2466
2613
|
layer.add(image);
|
|
2467
2614
|
this.objectMap.set(item.id, image);
|
|
2615
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
2616
|
+
if (resolver) {
|
|
2617
|
+
resolver();
|
|
2618
|
+
this.loadResolvers.delete(item.id);
|
|
2619
|
+
}
|
|
2468
2620
|
image.on("modified", (e) => {
|
|
2469
2621
|
this.handleObjectModified(item.id, image);
|
|
2470
2622
|
});
|
|
2471
2623
|
layer.dirty = true;
|
|
2472
2624
|
(_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
|
|
2473
|
-
if (item.
|
|
2474
|
-
this.updateImageInConfig(item.id, {
|
|
2625
|
+
if (item.scale !== scale || item.left !== left || item.top !== top) {
|
|
2626
|
+
this.updateImageInConfig(item.id, { scale, left, top }, true);
|
|
2475
2627
|
}
|
|
2476
2628
|
}).catch((err) => {
|
|
2477
2629
|
console.error("Failed to load image", item.url, err);
|
|
@@ -2479,29 +2631,28 @@ var ImageTool = class {
|
|
|
2479
2631
|
}
|
|
2480
2632
|
handleObjectModified(id, image) {
|
|
2481
2633
|
const layout = this.getLayoutInfo();
|
|
2482
|
-
const {
|
|
2634
|
+
const {
|
|
2635
|
+
layoutScale,
|
|
2636
|
+
layoutOffsetX,
|
|
2637
|
+
layoutOffsetY,
|
|
2638
|
+
visualWidth,
|
|
2639
|
+
visualHeight
|
|
2640
|
+
} = layout;
|
|
2483
2641
|
const matrix = image.calcTransformMatrix();
|
|
2484
2642
|
const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
|
|
2485
2643
|
const updates = {};
|
|
2486
2644
|
updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
|
|
2487
2645
|
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
2488
2646
|
updates.angle = image.angle;
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
updates.width = pixelWidth / layoutScale;
|
|
2492
|
-
}
|
|
2493
|
-
if (image.height) {
|
|
2494
|
-
const pixelHeight = image.height * image.scaleY;
|
|
2495
|
-
updates.height = pixelHeight / layoutScale;
|
|
2496
|
-
}
|
|
2497
|
-
this.updateImageInConfig(id, updates);
|
|
2647
|
+
updates.scale = image.scaleX / layoutScale;
|
|
2648
|
+
this.updateImageInConfig(id, updates, true);
|
|
2498
2649
|
}
|
|
2499
|
-
updateImageInConfig(id, updates) {
|
|
2650
|
+
updateImageInConfig(id, updates, skipCanvasUpdate = false) {
|
|
2500
2651
|
const index = this.items.findIndex((i) => i.id === id);
|
|
2501
2652
|
if (index !== -1) {
|
|
2502
2653
|
const newItems = [...this.items];
|
|
2503
2654
|
newItems[index] = { ...newItems[index], ...updates };
|
|
2504
|
-
this.updateConfig(newItems,
|
|
2655
|
+
this.updateConfig(newItems, skipCanvasUpdate);
|
|
2505
2656
|
}
|
|
2506
2657
|
}
|
|
2507
2658
|
};
|
|
@@ -3352,8 +3503,8 @@ export {
|
|
|
3352
3503
|
BackgroundTool,
|
|
3353
3504
|
CanvasService,
|
|
3354
3505
|
DielineTool,
|
|
3506
|
+
FeatureTool,
|
|
3355
3507
|
FilmTool,
|
|
3356
|
-
HoleTool,
|
|
3357
3508
|
ImageTool,
|
|
3358
3509
|
MirrorTool,
|
|
3359
3510
|
RulerTool,
|