@pooder/kit 3.4.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 +6 -0
- package/dist/index.d.mts +51 -55
- package/dist/index.d.ts +51 -55
- package/dist/index.js +866 -858
- package/dist/index.mjs +865 -857
- 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 -471
- package/src/index.ts +1 -1
- package/src/mirror.ts +128 -128
- package/src/ruler.ts +500 -500
- package/src/tracer.ts +570 -486
- 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, _c, _d, _e;
|
|
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;
|
|
@@ -232,23 +233,37 @@ var ImageTracer = class {
|
|
|
232
233
|
Math.floor(Math.max(width, height) * 0.02)
|
|
233
234
|
);
|
|
234
235
|
const radius = (_b = options.morphologyRadius) != null ? _b : adaptiveRadius;
|
|
235
|
-
|
|
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);
|
|
236
241
|
if (radius > 0) {
|
|
237
|
-
mask = this.
|
|
238
|
-
mask = this.
|
|
239
|
-
|
|
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");
|
|
240
251
|
}
|
|
241
|
-
const allContourPoints = this.traceAllContours(mask,
|
|
252
|
+
const allContourPoints = this.traceAllContours(mask, paddedWidth, paddedHeight);
|
|
242
253
|
if (allContourPoints.length === 0) {
|
|
243
|
-
const w = (
|
|
244
|
-
const h = (
|
|
254
|
+
const w = (_d = options.scaleToWidth) != null ? _d : width;
|
|
255
|
+
const h = (_e = options.scaleToHeight) != null ? _e : height;
|
|
245
256
|
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
|
|
246
257
|
}
|
|
247
258
|
const primaryContour = allContourPoints.sort(
|
|
248
259
|
(a, b) => b.length - a.length
|
|
249
260
|
)[0];
|
|
261
|
+
const unpaddedPoints = primaryContour.map((p) => ({
|
|
262
|
+
x: p.x - padding,
|
|
263
|
+
y: p.y - padding
|
|
264
|
+
}));
|
|
250
265
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
251
|
-
for (const p of
|
|
266
|
+
for (const p of unpaddedPoints) {
|
|
252
267
|
if (p.x < minX) minX = p.x;
|
|
253
268
|
if (p.y < minY) minY = p.y;
|
|
254
269
|
if (p.x > maxX) maxX = p.x;
|
|
@@ -260,95 +275,119 @@ var ImageTracer = class {
|
|
|
260
275
|
width: maxX - minX,
|
|
261
276
|
height: maxY - minY
|
|
262
277
|
};
|
|
263
|
-
let finalPoints =
|
|
278
|
+
let finalPoints = unpaddedPoints;
|
|
264
279
|
if (options.scaleToWidth && options.scaleToHeight) {
|
|
265
280
|
finalPoints = this.scalePoints(
|
|
266
|
-
|
|
281
|
+
unpaddedPoints,
|
|
267
282
|
options.scaleToWidth,
|
|
268
283
|
options.scaleToHeight,
|
|
269
284
|
globalBounds
|
|
270
285
|
);
|
|
271
286
|
}
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
(
|
|
275
|
-
|
|
276
|
-
|
|
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
|
+
}
|
|
277
297
|
}
|
|
278
|
-
static createMask(imageData, threshold) {
|
|
298
|
+
static createMask(imageData, threshold, padding, paddedWidth, paddedHeight) {
|
|
279
299
|
const { width, height, data } = imageData;
|
|
280
|
-
const mask = new Uint8Array(
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const a = data[idx + 3];
|
|
287
|
-
if (a > threshold && !(r > 240 && g > 240 && b > 240)) {
|
|
288
|
-
mask[i] = 1;
|
|
289
|
-
} else {
|
|
290
|
-
mask[i] = 0;
|
|
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;
|
|
291
306
|
}
|
|
292
307
|
}
|
|
293
|
-
return mask;
|
|
294
|
-
}
|
|
295
|
-
/**
|
|
296
|
-
* Fast 1D-separable Dilation
|
|
297
|
-
*/
|
|
298
|
-
static dilate(mask, width, height, radius) {
|
|
299
|
-
const horizontal = new Uint8Array(width * height);
|
|
300
308
|
for (let y = 0; y < height; y++) {
|
|
301
|
-
let
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
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
|
+
}
|
|
317
325
|
}
|
|
318
326
|
}
|
|
319
|
-
return
|
|
327
|
+
return mask;
|
|
320
328
|
}
|
|
321
329
|
/**
|
|
322
|
-
* Fast
|
|
330
|
+
* Fast circular morphology using a distance-transform inspired separable approach.
|
|
331
|
+
* O(N * R) complexity, where R is the radius.
|
|
323
332
|
*/
|
|
324
|
-
static
|
|
325
|
-
const
|
|
326
|
-
|
|
327
|
-
let
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
+
);
|
|
334
349
|
}
|
|
335
350
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
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;
|
|
348
367
|
}
|
|
349
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;
|
|
350
390
|
}
|
|
351
|
-
return vertical;
|
|
352
391
|
}
|
|
353
392
|
/**
|
|
354
393
|
* Fills internal holes in the binary mask using flood fill from edges.
|
|
@@ -548,6 +587,23 @@ var ImageTracer = class {
|
|
|
548
587
|
const tail = points.slice(1);
|
|
549
588
|
return `M ${head.x} ${head.y} ` + tail.map((p) => `L ${p.x} ${p.y}`).join(" ") + " Z";
|
|
550
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
|
+
}
|
|
551
607
|
};
|
|
552
608
|
|
|
553
609
|
// src/coordinate.ts
|
|
@@ -622,96 +678,45 @@ var Coordinate = class {
|
|
|
622
678
|
};
|
|
623
679
|
|
|
624
680
|
// src/geometry.ts
|
|
625
|
-
import
|
|
626
|
-
function
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
const bottom = y + height / 2;
|
|
635
|
-
switch (hole.anchor) {
|
|
636
|
-
case "top-left":
|
|
637
|
-
bx = left;
|
|
638
|
-
by = top;
|
|
639
|
-
break;
|
|
640
|
-
case "top-center":
|
|
641
|
-
bx = x;
|
|
642
|
-
by = top;
|
|
643
|
-
break;
|
|
644
|
-
case "top-right":
|
|
645
|
-
bx = right;
|
|
646
|
-
by = top;
|
|
647
|
-
break;
|
|
648
|
-
case "center-left":
|
|
649
|
-
bx = left;
|
|
650
|
-
by = y;
|
|
651
|
-
break;
|
|
652
|
-
case "center":
|
|
653
|
-
bx = x;
|
|
654
|
-
by = y;
|
|
655
|
-
break;
|
|
656
|
-
case "center-right":
|
|
657
|
-
bx = right;
|
|
658
|
-
by = y;
|
|
659
|
-
break;
|
|
660
|
-
case "bottom-left":
|
|
661
|
-
bx = left;
|
|
662
|
-
by = bottom;
|
|
663
|
-
break;
|
|
664
|
-
case "bottom-center":
|
|
665
|
-
bx = x;
|
|
666
|
-
by = bottom;
|
|
667
|
-
break;
|
|
668
|
-
case "bottom-right":
|
|
669
|
-
bx = right;
|
|
670
|
-
by = bottom;
|
|
671
|
-
break;
|
|
672
|
-
}
|
|
673
|
-
return {
|
|
674
|
-
x: bx + (hole.offsetX || 0),
|
|
675
|
-
y: by + (hole.offsetY || 0)
|
|
676
|
-
};
|
|
677
|
-
} else if (hole.x !== void 0 && hole.y !== void 0) {
|
|
678
|
-
const { x, width, y, height } = geometry;
|
|
679
|
-
return {
|
|
680
|
-
x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
|
|
681
|
-
y: hole.y * height + (y - height / 2) + (hole.offsetY || 0)
|
|
682
|
-
};
|
|
683
|
-
}
|
|
684
|
-
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
|
+
};
|
|
685
690
|
}
|
|
686
691
|
function ensurePaper(width, height) {
|
|
687
|
-
if (!
|
|
688
|
-
|
|
692
|
+
if (!paper2.project) {
|
|
693
|
+
paper2.setup(new paper2.Size(width, height));
|
|
689
694
|
} else {
|
|
690
|
-
|
|
695
|
+
paper2.view.viewSize = new paper2.Size(width, height);
|
|
691
696
|
}
|
|
692
697
|
}
|
|
693
698
|
function createBaseShape(options) {
|
|
694
699
|
const { shape, width, height, radius, x, y, pathData } = options;
|
|
695
|
-
const center = new
|
|
700
|
+
const center = new paper2.Point(x, y);
|
|
696
701
|
if (shape === "rect") {
|
|
697
|
-
return new
|
|
702
|
+
return new paper2.Path.Rectangle({
|
|
698
703
|
point: [x - width / 2, y - height / 2],
|
|
699
704
|
size: [Math.max(0, width), Math.max(0, height)],
|
|
700
705
|
radius: Math.max(0, radius)
|
|
701
706
|
});
|
|
702
707
|
} else if (shape === "circle") {
|
|
703
708
|
const r = Math.min(width, height) / 2;
|
|
704
|
-
return new
|
|
709
|
+
return new paper2.Path.Circle({
|
|
705
710
|
center,
|
|
706
711
|
radius: Math.max(0, r)
|
|
707
712
|
});
|
|
708
713
|
} else if (shape === "ellipse") {
|
|
709
|
-
return new
|
|
714
|
+
return new paper2.Path.Ellipse({
|
|
710
715
|
center,
|
|
711
716
|
radius: [Math.max(0, width / 2), Math.max(0, height / 2)]
|
|
712
717
|
});
|
|
713
718
|
} else if (shape === "custom" && pathData) {
|
|
714
|
-
const path = new
|
|
719
|
+
const path = new paper2.Path();
|
|
715
720
|
path.pathData = pathData;
|
|
716
721
|
path.position = center;
|
|
717
722
|
if (width > 0 && height > 0 && path.bounds.width > 0 && path.bounds.height > 0) {
|
|
@@ -719,89 +724,76 @@ function createBaseShape(options) {
|
|
|
719
724
|
}
|
|
720
725
|
return path;
|
|
721
726
|
} else {
|
|
722
|
-
return new
|
|
727
|
+
return new paper2.Path.Rectangle({
|
|
723
728
|
point: [x - width / 2, y - height / 2],
|
|
724
729
|
size: [Math.max(0, width), Math.max(0, height)]
|
|
725
730
|
});
|
|
726
731
|
}
|
|
727
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
|
+
}
|
|
728
756
|
function getDielineShape(options) {
|
|
729
757
|
let mainShape = createBaseShape(options);
|
|
730
|
-
const {
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
const
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
],
|
|
741
|
-
size: [hole.outerRadius * 2, hole.outerRadius * 2]
|
|
742
|
-
}) : new paper.Path.Circle({
|
|
743
|
-
center,
|
|
744
|
-
radius: hole.outerRadius
|
|
745
|
-
});
|
|
746
|
-
const cut = hole.shape === "square" ? new paper.Path.Rectangle({
|
|
747
|
-
point: [
|
|
748
|
-
center.x - hole.innerRadius,
|
|
749
|
-
center.y - hole.innerRadius
|
|
750
|
-
],
|
|
751
|
-
size: [hole.innerRadius * 2, hole.innerRadius * 2]
|
|
752
|
-
}) : new paper.Path.Circle({
|
|
753
|
-
center,
|
|
754
|
-
radius: hole.innerRadius
|
|
755
|
-
});
|
|
756
|
-
if (!lugsPath) {
|
|
757
|
-
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);
|
|
758
768
|
} else {
|
|
769
|
+
subtracts.push(item);
|
|
770
|
+
}
|
|
771
|
+
});
|
|
772
|
+
if (adds.length > 0) {
|
|
773
|
+
for (const item of adds) {
|
|
759
774
|
try {
|
|
760
|
-
const temp =
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
775
|
+
const temp = mainShape.unite(item);
|
|
776
|
+
mainShape.remove();
|
|
777
|
+
item.remove();
|
|
778
|
+
mainShape = temp;
|
|
764
779
|
} catch (e) {
|
|
765
|
-
console.error("Geometry: Failed to unite
|
|
766
|
-
|
|
780
|
+
console.error("Geometry: Failed to unite feature", e);
|
|
781
|
+
item.remove();
|
|
767
782
|
}
|
|
768
783
|
}
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
784
|
+
}
|
|
785
|
+
if (subtracts.length > 0) {
|
|
786
|
+
for (const item of subtracts) {
|
|
772
787
|
try {
|
|
773
|
-
const temp =
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
788
|
+
const temp = mainShape.subtract(item);
|
|
789
|
+
mainShape.remove();
|
|
790
|
+
item.remove();
|
|
791
|
+
mainShape = temp;
|
|
777
792
|
} catch (e) {
|
|
778
|
-
console.error("Geometry: Failed to
|
|
779
|
-
|
|
793
|
+
console.error("Geometry: Failed to subtract feature", e);
|
|
794
|
+
item.remove();
|
|
780
795
|
}
|
|
781
796
|
}
|
|
782
|
-
});
|
|
783
|
-
if (lugsPath) {
|
|
784
|
-
try {
|
|
785
|
-
const temp = mainShape.unite(lugsPath);
|
|
786
|
-
mainShape.remove();
|
|
787
|
-
lugsPath.remove();
|
|
788
|
-
mainShape = temp;
|
|
789
|
-
} catch (e) {
|
|
790
|
-
console.error("Geometry: Failed to unite lugsPath to mainShape", e);
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
if (cutsPath) {
|
|
794
|
-
try {
|
|
795
|
-
const temp = mainShape.subtract(cutsPath);
|
|
796
|
-
mainShape.remove();
|
|
797
|
-
cutsPath.remove();
|
|
798
|
-
mainShape = temp;
|
|
799
|
-
} catch (e) {
|
|
800
|
-
console.error(
|
|
801
|
-
"Geometry: Failed to subtract cutsPath from mainShape",
|
|
802
|
-
e
|
|
803
|
-
);
|
|
804
|
-
}
|
|
805
797
|
}
|
|
806
798
|
}
|
|
807
799
|
return mainShape;
|
|
@@ -810,7 +802,7 @@ function generateDielinePath(options) {
|
|
|
810
802
|
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
811
803
|
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
812
804
|
ensurePaper(paperWidth, paperHeight);
|
|
813
|
-
|
|
805
|
+
paper2.project.activeLayer.removeChildren();
|
|
814
806
|
const mainShape = getDielineShape(options);
|
|
815
807
|
const pathData = mainShape.pathData;
|
|
816
808
|
mainShape.remove();
|
|
@@ -818,9 +810,9 @@ function generateDielinePath(options) {
|
|
|
818
810
|
}
|
|
819
811
|
function generateMaskPath(options) {
|
|
820
812
|
ensurePaper(options.canvasWidth, options.canvasHeight);
|
|
821
|
-
|
|
813
|
+
paper2.project.activeLayer.removeChildren();
|
|
822
814
|
const { canvasWidth, canvasHeight } = options;
|
|
823
|
-
const maskRect = new
|
|
815
|
+
const maskRect = new paper2.Path.Rectangle({
|
|
824
816
|
point: [0, 0],
|
|
825
817
|
size: [canvasWidth, canvasHeight]
|
|
826
818
|
});
|
|
@@ -832,43 +824,13 @@ function generateMaskPath(options) {
|
|
|
832
824
|
finalMask.remove();
|
|
833
825
|
return pathData;
|
|
834
826
|
}
|
|
835
|
-
function generateBleedZonePath(
|
|
836
|
-
const paperWidth =
|
|
837
|
-
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;
|
|
838
830
|
ensurePaper(paperWidth, paperHeight);
|
|
839
|
-
|
|
840
|
-
const shapeOriginal = getDielineShape(
|
|
841
|
-
|
|
842
|
-
if (options.shape === "custom") {
|
|
843
|
-
const stroker = shapeOriginal.clone();
|
|
844
|
-
stroker.strokeColor = new paper.Color("black");
|
|
845
|
-
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
846
|
-
stroker.strokeJoin = "round";
|
|
847
|
-
stroker.strokeCap = "round";
|
|
848
|
-
let expanded;
|
|
849
|
-
try {
|
|
850
|
-
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
851
|
-
} catch (e) {
|
|
852
|
-
stroker.remove();
|
|
853
|
-
shapeOffset = shapeOriginal.clone();
|
|
854
|
-
return shapeOffset.pathData;
|
|
855
|
-
}
|
|
856
|
-
stroker.remove();
|
|
857
|
-
if (offset > 0) {
|
|
858
|
-
shapeOffset = shapeOriginal.unite(expanded);
|
|
859
|
-
} else {
|
|
860
|
-
shapeOffset = shapeOriginal.subtract(expanded);
|
|
861
|
-
}
|
|
862
|
-
expanded.remove();
|
|
863
|
-
} else {
|
|
864
|
-
const offsetOptions = {
|
|
865
|
-
...options,
|
|
866
|
-
width: Math.max(0, options.width + offset * 2),
|
|
867
|
-
height: Math.max(0, options.height + offset * 2),
|
|
868
|
-
radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset)
|
|
869
|
-
};
|
|
870
|
-
shapeOffset = getDielineShape(offsetOptions);
|
|
871
|
-
}
|
|
831
|
+
paper2.project.activeLayer.removeChildren();
|
|
832
|
+
const shapeOriginal = getDielineShape(originalOptions);
|
|
833
|
+
const shapeOffset = getDielineShape(offsetOptions);
|
|
872
834
|
let bleedZone;
|
|
873
835
|
if (offset > 0) {
|
|
874
836
|
bleedZone = shapeOffset.subtract(shapeOriginal);
|
|
@@ -883,16 +845,16 @@ function generateBleedZonePath(options, offset) {
|
|
|
883
845
|
}
|
|
884
846
|
function getNearestPointOnDieline(point, options) {
|
|
885
847
|
ensurePaper(options.width * 2, options.height * 2);
|
|
886
|
-
|
|
848
|
+
paper2.project.activeLayer.removeChildren();
|
|
887
849
|
const shape = createBaseShape(options);
|
|
888
|
-
const p = new
|
|
850
|
+
const p = new paper2.Point(point.x, point.y);
|
|
889
851
|
const nearest = shape.getNearestPoint(p);
|
|
890
852
|
const result = { x: nearest.x, y: nearest.y };
|
|
891
853
|
shape.remove();
|
|
892
854
|
return result;
|
|
893
855
|
}
|
|
894
856
|
function getPathBounds(pathData) {
|
|
895
|
-
const path = new
|
|
857
|
+
const path = new paper2.Path();
|
|
896
858
|
path.pathData = pathData;
|
|
897
859
|
const bounds = path.bounds;
|
|
898
860
|
path.remove();
|
|
@@ -911,20 +873,41 @@ var DielineTool = class {
|
|
|
911
873
|
this.metadata = {
|
|
912
874
|
name: "DielineTool"
|
|
913
875
|
};
|
|
914
|
-
this.
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
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
|
+
};
|
|
926
901
|
if (options) {
|
|
927
|
-
|
|
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);
|
|
928
911
|
}
|
|
929
912
|
}
|
|
930
913
|
activate(context) {
|
|
@@ -936,38 +919,93 @@ var DielineTool = class {
|
|
|
936
919
|
}
|
|
937
920
|
const configService = context.services.get("ConfigurationService");
|
|
938
921
|
if (configService) {
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
);
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
);
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
);
|
|
959
|
-
|
|
960
|
-
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);
|
|
961
943
|
configService.onAnyChange((e) => {
|
|
962
944
|
if (e.key.startsWith("dieline.")) {
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
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;
|
|
970
1007
|
}
|
|
1008
|
+
this.updateDieline();
|
|
971
1009
|
}
|
|
972
1010
|
});
|
|
973
1011
|
}
|
|
@@ -980,6 +1018,7 @@ var DielineTool = class {
|
|
|
980
1018
|
this.context = void 0;
|
|
981
1019
|
}
|
|
982
1020
|
contribute() {
|
|
1021
|
+
const s = this.state;
|
|
983
1022
|
return {
|
|
984
1023
|
[ContributionPointIds2.CONFIGURATIONS]: [
|
|
985
1024
|
{
|
|
@@ -987,14 +1026,14 @@ var DielineTool = class {
|
|
|
987
1026
|
type: "select",
|
|
988
1027
|
label: "Unit",
|
|
989
1028
|
options: ["px", "mm", "cm", "in"],
|
|
990
|
-
default:
|
|
1029
|
+
default: s.unit
|
|
991
1030
|
},
|
|
992
1031
|
{
|
|
993
1032
|
id: "dieline.shape",
|
|
994
1033
|
type: "select",
|
|
995
1034
|
label: "Shape",
|
|
996
1035
|
options: ["rect", "circle", "ellipse", "custom"],
|
|
997
|
-
default:
|
|
1036
|
+
default: s.shape
|
|
998
1037
|
},
|
|
999
1038
|
{
|
|
1000
1039
|
id: "dieline.width",
|
|
@@ -1002,7 +1041,7 @@ var DielineTool = class {
|
|
|
1002
1041
|
label: "Width",
|
|
1003
1042
|
min: 10,
|
|
1004
1043
|
max: 2e3,
|
|
1005
|
-
default:
|
|
1044
|
+
default: s.width
|
|
1006
1045
|
},
|
|
1007
1046
|
{
|
|
1008
1047
|
id: "dieline.height",
|
|
@@ -1010,7 +1049,7 @@ var DielineTool = class {
|
|
|
1010
1049
|
label: "Height",
|
|
1011
1050
|
min: 10,
|
|
1012
1051
|
max: 2e3,
|
|
1013
|
-
default:
|
|
1052
|
+
default: s.height
|
|
1014
1053
|
},
|
|
1015
1054
|
{
|
|
1016
1055
|
id: "dieline.radius",
|
|
@@ -1018,20 +1057,14 @@ var DielineTool = class {
|
|
|
1018
1057
|
label: "Corner Radius",
|
|
1019
1058
|
min: 0,
|
|
1020
1059
|
max: 500,
|
|
1021
|
-
default:
|
|
1022
|
-
},
|
|
1023
|
-
{
|
|
1024
|
-
id: "dieline.position",
|
|
1025
|
-
type: "json",
|
|
1026
|
-
label: "Position (Normalized)",
|
|
1027
|
-
default: this.radius
|
|
1060
|
+
default: s.radius
|
|
1028
1061
|
},
|
|
1029
1062
|
{
|
|
1030
1063
|
id: "dieline.padding",
|
|
1031
1064
|
type: "select",
|
|
1032
1065
|
label: "View Padding",
|
|
1033
1066
|
options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
|
|
1034
|
-
default:
|
|
1067
|
+
default: s.padding
|
|
1035
1068
|
},
|
|
1036
1069
|
{
|
|
1037
1070
|
id: "dieline.offset",
|
|
@@ -1039,38 +1072,91 @@ var DielineTool = class {
|
|
|
1039
1072
|
label: "Bleed Offset",
|
|
1040
1073
|
min: -100,
|
|
1041
1074
|
max: 100,
|
|
1042
|
-
default:
|
|
1075
|
+
default: s.offset
|
|
1043
1076
|
},
|
|
1044
1077
|
{
|
|
1045
1078
|
id: "dieline.showBleedLines",
|
|
1046
1079
|
type: "boolean",
|
|
1047
1080
|
label: "Show Bleed Lines",
|
|
1048
|
-
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
|
|
1049
1105
|
},
|
|
1050
1106
|
{
|
|
1051
1107
|
id: "dieline.style",
|
|
1052
1108
|
type: "select",
|
|
1053
1109
|
label: "Line Style",
|
|
1054
|
-
options: ["solid", "dashed"],
|
|
1055
|
-
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
|
|
1056
1142
|
},
|
|
1057
1143
|
{
|
|
1058
1144
|
id: "dieline.insideColor",
|
|
1059
1145
|
type: "color",
|
|
1060
1146
|
label: "Inside Color",
|
|
1061
|
-
default:
|
|
1147
|
+
default: s.insideColor
|
|
1062
1148
|
},
|
|
1063
1149
|
{
|
|
1064
1150
|
id: "dieline.outsideColor",
|
|
1065
1151
|
type: "color",
|
|
1066
1152
|
label: "Outside Color",
|
|
1067
|
-
default:
|
|
1153
|
+
default: s.outsideColor
|
|
1068
1154
|
},
|
|
1069
1155
|
{
|
|
1070
|
-
id: "dieline.
|
|
1156
|
+
id: "dieline.features",
|
|
1071
1157
|
type: "json",
|
|
1072
|
-
label: "
|
|
1073
|
-
default:
|
|
1158
|
+
label: "Edge Features",
|
|
1159
|
+
default: s.features
|
|
1074
1160
|
}
|
|
1075
1161
|
],
|
|
1076
1162
|
[ContributionPointIds2.COMMANDS]: [
|
|
@@ -1092,24 +1178,18 @@ var DielineTool = class {
|
|
|
1092
1178
|
command: "detectEdge",
|
|
1093
1179
|
title: "Detect Edge from Image",
|
|
1094
1180
|
handler: async (imageUrl, options) => {
|
|
1095
|
-
var _a;
|
|
1096
1181
|
try {
|
|
1097
1182
|
const pathData = await ImageTracer.trace(imageUrl, options);
|
|
1098
1183
|
const bounds = getPathBounds(pathData);
|
|
1099
|
-
const currentMax = Math.max(
|
|
1184
|
+
const currentMax = Math.max(s.width, s.height);
|
|
1100
1185
|
const scale = currentMax / Math.max(bounds.width, bounds.height);
|
|
1101
1186
|
const newWidth = bounds.width * scale;
|
|
1102
1187
|
const newHeight = bounds.height * scale;
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
configService.update("dieline.height", newHeight);
|
|
1109
|
-
configService.update("dieline.shape", "custom");
|
|
1110
|
-
configService.update("dieline.pathData", pathData);
|
|
1111
|
-
}
|
|
1112
|
-
return pathData;
|
|
1188
|
+
return {
|
|
1189
|
+
pathData,
|
|
1190
|
+
width: newWidth,
|
|
1191
|
+
height: newHeight
|
|
1192
|
+
};
|
|
1113
1193
|
} catch (e) {
|
|
1114
1194
|
console.error("Edge detection failed", e);
|
|
1115
1195
|
throw e;
|
|
@@ -1168,15 +1248,15 @@ var DielineTool = class {
|
|
|
1168
1248
|
return new Pattern({ source: canvas, repetition: "repeat" });
|
|
1169
1249
|
}
|
|
1170
1250
|
resolvePadding(containerWidth, containerHeight) {
|
|
1171
|
-
if (typeof this.padding === "number") {
|
|
1172
|
-
return this.padding;
|
|
1251
|
+
if (typeof this.state.padding === "number") {
|
|
1252
|
+
return this.state.padding;
|
|
1173
1253
|
}
|
|
1174
|
-
if (typeof this.padding === "string") {
|
|
1175
|
-
if (this.padding.endsWith("%")) {
|
|
1176
|
-
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;
|
|
1177
1257
|
return Math.min(containerWidth, containerHeight) * percent;
|
|
1178
1258
|
}
|
|
1179
|
-
return parseFloat(this.padding) || 0;
|
|
1259
|
+
return parseFloat(this.state.padding) || 0;
|
|
1180
1260
|
}
|
|
1181
1261
|
return 0;
|
|
1182
1262
|
}
|
|
@@ -1189,14 +1269,14 @@ var DielineTool = class {
|
|
|
1189
1269
|
shape,
|
|
1190
1270
|
radius,
|
|
1191
1271
|
offset,
|
|
1192
|
-
|
|
1272
|
+
mainLine,
|
|
1273
|
+
offsetLine,
|
|
1193
1274
|
insideColor,
|
|
1194
1275
|
outsideColor,
|
|
1195
|
-
position,
|
|
1196
1276
|
showBleedLines,
|
|
1197
|
-
|
|
1198
|
-
} = this;
|
|
1199
|
-
let { width, height } = this;
|
|
1277
|
+
features
|
|
1278
|
+
} = this.state;
|
|
1279
|
+
let { width, height } = this.state;
|
|
1200
1280
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1201
1281
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1202
1282
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1213,40 +1293,27 @@ var DielineTool = class {
|
|
|
1213
1293
|
const visualRadius = radius * scale;
|
|
1214
1294
|
const visualOffset = offset * scale;
|
|
1215
1295
|
layer.remove(...layer.getObjects());
|
|
1216
|
-
const
|
|
1217
|
-
|
|
1218
|
-
y: cy,
|
|
1219
|
-
width: visualWidth,
|
|
1220
|
-
height: visualHeight
|
|
1221
|
-
// Pass scale/unit context if needed by resolveHolePosition (though currently unused there)
|
|
1222
|
-
};
|
|
1223
|
-
const absoluteHoles = (holes || []).map((h) => {
|
|
1224
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1225
|
-
const offsetScale = unitScale * scale;
|
|
1226
|
-
const hWithPixelOffsets = {
|
|
1227
|
-
...h,
|
|
1228
|
-
offsetX: (h.offsetX || 0) * offsetScale,
|
|
1229
|
-
offsetY: (h.offsetY || 0) * offsetScale
|
|
1230
|
-
};
|
|
1231
|
-
const pos = resolveHolePosition(hWithPixelOffsets, geometryForHoles, {
|
|
1232
|
-
width: canvasW,
|
|
1233
|
-
height: canvasH
|
|
1234
|
-
});
|
|
1296
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
1297
|
+
const featureScale = scale;
|
|
1235
1298
|
return {
|
|
1236
|
-
...
|
|
1237
|
-
x:
|
|
1238
|
-
y:
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
// Store scaled offsets in the result for consistency, though pos is already resolved
|
|
1243
|
-
offsetX: hWithPixelOffsets.offsetX,
|
|
1244
|
-
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
|
|
1245
1305
|
};
|
|
1246
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
|
+
);
|
|
1247
1313
|
const cutW = Math.max(0, visualWidth + visualOffset * 2);
|
|
1248
1314
|
const cutH = Math.max(0, visualHeight + visualOffset * 2);
|
|
1249
1315
|
const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
1316
|
+
const maskFeatures = visualOffset !== 0 ? offsetFeatures : originalFeatures;
|
|
1250
1317
|
const maskPathData = generateMaskPath({
|
|
1251
1318
|
canvasWidth: canvasW,
|
|
1252
1319
|
canvasHeight: canvasH,
|
|
@@ -1256,8 +1323,8 @@ var DielineTool = class {
|
|
|
1256
1323
|
radius: cutR,
|
|
1257
1324
|
x: cx,
|
|
1258
1325
|
y: cy,
|
|
1259
|
-
|
|
1260
|
-
pathData: this.pathData
|
|
1326
|
+
features: maskFeatures,
|
|
1327
|
+
pathData: this.state.pathData
|
|
1261
1328
|
});
|
|
1262
1329
|
const mask = new Path(maskPathData, {
|
|
1263
1330
|
fill: outsideColor,
|
|
@@ -1278,8 +1345,9 @@ var DielineTool = class {
|
|
|
1278
1345
|
radius: cutR,
|
|
1279
1346
|
x: cx,
|
|
1280
1347
|
y: cy,
|
|
1281
|
-
|
|
1282
|
-
|
|
1348
|
+
features: maskFeatures,
|
|
1349
|
+
// Use same features as mask for consistency
|
|
1350
|
+
pathData: this.state.pathData,
|
|
1283
1351
|
canvasWidth: canvasW,
|
|
1284
1352
|
canvasHeight: canvasH
|
|
1285
1353
|
});
|
|
@@ -1303,15 +1371,27 @@ var DielineTool = class {
|
|
|
1303
1371
|
radius: visualRadius,
|
|
1304
1372
|
x: cx,
|
|
1305
1373
|
y: cy,
|
|
1306
|
-
|
|
1307
|
-
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,
|
|
1308
1388
|
canvasWidth: canvasW,
|
|
1309
1389
|
canvasHeight: canvasH
|
|
1310
1390
|
},
|
|
1311
1391
|
visualOffset
|
|
1312
1392
|
);
|
|
1313
1393
|
if (showBleedLines !== false) {
|
|
1314
|
-
const pattern = this.createHatchPattern(
|
|
1394
|
+
const pattern = this.createHatchPattern(mainLine.color);
|
|
1315
1395
|
if (pattern) {
|
|
1316
1396
|
const bleedObj = new Path(bleedPathData, {
|
|
1317
1397
|
fill: pattern,
|
|
@@ -1332,18 +1412,16 @@ var DielineTool = class {
|
|
|
1332
1412
|
radius: cutR,
|
|
1333
1413
|
x: cx,
|
|
1334
1414
|
y: cy,
|
|
1335
|
-
|
|
1336
|
-
pathData: this.pathData,
|
|
1415
|
+
features: offsetFeatures,
|
|
1416
|
+
pathData: this.state.pathData,
|
|
1337
1417
|
canvasWidth: canvasW,
|
|
1338
1418
|
canvasHeight: canvasH
|
|
1339
1419
|
});
|
|
1340
1420
|
const offsetBorderObj = new Path(offsetPathData, {
|
|
1341
1421
|
fill: null,
|
|
1342
|
-
stroke: "
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
strokeDashArray: [4, 4],
|
|
1346
|
-
// Dashed
|
|
1422
|
+
stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
|
|
1423
|
+
strokeWidth: offsetLine.width,
|
|
1424
|
+
strokeDashArray: offsetLine.style === "dashed" ? [offsetLine.dashLength, offsetLine.dashLength] : void 0,
|
|
1347
1425
|
selectable: false,
|
|
1348
1426
|
evented: false,
|
|
1349
1427
|
originX: "left",
|
|
@@ -1358,16 +1436,16 @@ var DielineTool = class {
|
|
|
1358
1436
|
radius: visualRadius,
|
|
1359
1437
|
x: cx,
|
|
1360
1438
|
y: cy,
|
|
1361
|
-
|
|
1362
|
-
pathData: this.pathData,
|
|
1439
|
+
features: originalFeatures,
|
|
1440
|
+
pathData: this.state.pathData,
|
|
1363
1441
|
canvasWidth: canvasW,
|
|
1364
1442
|
canvasHeight: canvasH
|
|
1365
1443
|
});
|
|
1366
1444
|
const borderObj = new Path(borderPathData, {
|
|
1367
1445
|
fill: "transparent",
|
|
1368
|
-
stroke: "
|
|
1369
|
-
strokeWidth:
|
|
1370
|
-
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,
|
|
1371
1449
|
selectable: false,
|
|
1372
1450
|
evented: false,
|
|
1373
1451
|
originX: "left",
|
|
@@ -1399,7 +1477,7 @@ var DielineTool = class {
|
|
|
1399
1477
|
}
|
|
1400
1478
|
getGeometry() {
|
|
1401
1479
|
if (!this.canvasService) return null;
|
|
1402
|
-
const { unit, shape, width, height, radius,
|
|
1480
|
+
const { unit, shape, width, height, radius, offset, mainLine, pathData } = this.state;
|
|
1403
1481
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1404
1482
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1405
1483
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1422,16 +1500,17 @@ var DielineTool = class {
|
|
|
1422
1500
|
height: visualHeight,
|
|
1423
1501
|
radius: radius * scale,
|
|
1424
1502
|
offset: offset * scale,
|
|
1425
|
-
// Pass scale to help other tools (like
|
|
1503
|
+
// Pass scale to help other tools (like FeatureTool) convert units
|
|
1426
1504
|
scale,
|
|
1427
|
-
|
|
1505
|
+
strokeWidth: mainLine.width,
|
|
1506
|
+
pathData
|
|
1428
1507
|
};
|
|
1429
1508
|
}
|
|
1430
1509
|
async exportCutImage() {
|
|
1431
1510
|
if (!this.canvasService) return null;
|
|
1432
1511
|
const userLayer = this.canvasService.getLayer("user");
|
|
1433
1512
|
if (!userLayer) return null;
|
|
1434
|
-
const { shape, width, height, radius,
|
|
1513
|
+
const { shape, width, height, radius, features, unit, pathData } = this.state;
|
|
1435
1514
|
const canvasW = this.canvasService.canvas.width || 800;
|
|
1436
1515
|
const canvasH = this.canvasService.canvas.height || 600;
|
|
1437
1516
|
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
@@ -1446,55 +1525,45 @@ var DielineTool = class {
|
|
|
1446
1525
|
const visualWidth = layout.width;
|
|
1447
1526
|
const visualHeight = layout.height;
|
|
1448
1527
|
const visualRadius = radius * scale;
|
|
1449
|
-
const
|
|
1450
|
-
const
|
|
1451
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1452
|
-
const pos = resolveHolePosition(
|
|
1453
|
-
{
|
|
1454
|
-
...h,
|
|
1455
|
-
offsetX: (h.offsetX || 0) * unitScale * scale,
|
|
1456
|
-
offsetY: (h.offsetY || 0) * unitScale * scale
|
|
1457
|
-
},
|
|
1458
|
-
{ x: cx, y: cy, width: visualWidth, height: visualHeight },
|
|
1459
|
-
{ width: canvasW, height: canvasH }
|
|
1460
|
-
);
|
|
1528
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
1529
|
+
const featureScale = scale;
|
|
1461
1530
|
return {
|
|
1462
|
-
...
|
|
1463
|
-
x:
|
|
1464
|
-
y:
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
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
|
|
1469
1537
|
};
|
|
1470
1538
|
});
|
|
1471
|
-
const
|
|
1539
|
+
const originalFeatures = absoluteFeatures.filter(
|
|
1540
|
+
(f) => !f.target || f.target === "original" || f.target === "both"
|
|
1541
|
+
);
|
|
1542
|
+
const generatedPathData = generateDielinePath({
|
|
1472
1543
|
shape,
|
|
1473
1544
|
width: visualWidth,
|
|
1474
1545
|
height: visualHeight,
|
|
1475
1546
|
radius: visualRadius,
|
|
1476
1547
|
x: cx,
|
|
1477
1548
|
y: cy,
|
|
1478
|
-
|
|
1479
|
-
pathData
|
|
1549
|
+
features: originalFeatures,
|
|
1550
|
+
pathData,
|
|
1480
1551
|
canvasWidth: canvasW,
|
|
1481
1552
|
canvasHeight: canvasH
|
|
1482
1553
|
});
|
|
1483
1554
|
const clonedLayer = await userLayer.clone();
|
|
1484
|
-
const clipPath = new Path(
|
|
1555
|
+
const clipPath = new Path(generatedPathData, {
|
|
1485
1556
|
originX: "left",
|
|
1486
1557
|
originY: "top",
|
|
1487
1558
|
left: 0,
|
|
1488
1559
|
top: 0,
|
|
1489
1560
|
absolutePositioned: true
|
|
1490
|
-
// Important for groups
|
|
1491
1561
|
});
|
|
1492
1562
|
clonedLayer.clipPath = clipPath;
|
|
1493
1563
|
const bounds = clipPath.getBoundingRect();
|
|
1494
1564
|
const dataUrl = clonedLayer.toDataURL({
|
|
1495
1565
|
format: "png",
|
|
1496
1566
|
multiplier: 2,
|
|
1497
|
-
// Better quality
|
|
1498
1567
|
left: bounds.left,
|
|
1499
1568
|
top: bounds.top,
|
|
1500
1569
|
width: bounds.width,
|
|
@@ -1665,24 +1734,22 @@ var FilmTool = class {
|
|
|
1665
1734
|
}
|
|
1666
1735
|
};
|
|
1667
1736
|
|
|
1668
|
-
// src/
|
|
1737
|
+
// src/feature.ts
|
|
1669
1738
|
import {
|
|
1670
1739
|
ContributionPointIds as ContributionPointIds4
|
|
1671
1740
|
} from "@pooder/core";
|
|
1672
1741
|
import { Circle, Group, Point, Rect as Rect2 } from "fabric";
|
|
1673
|
-
var
|
|
1742
|
+
var FeatureTool = class {
|
|
1674
1743
|
constructor(options) {
|
|
1675
|
-
this.id = "pooder.kit.
|
|
1744
|
+
this.id = "pooder.kit.feature";
|
|
1676
1745
|
this.metadata = {
|
|
1677
|
-
name: "
|
|
1746
|
+
name: "FeatureTool"
|
|
1678
1747
|
};
|
|
1679
|
-
this.
|
|
1680
|
-
this.constraintTarget = "bleed";
|
|
1748
|
+
this.features = [];
|
|
1681
1749
|
this.isUpdatingConfig = false;
|
|
1682
1750
|
this.handleMoving = null;
|
|
1683
1751
|
this.handleModified = null;
|
|
1684
1752
|
this.handleDielineChange = null;
|
|
1685
|
-
// Cache geometry to enforce constraints during drag
|
|
1686
1753
|
this.currentGeometry = null;
|
|
1687
1754
|
if (options) {
|
|
1688
1755
|
Object.assign(this, options);
|
|
@@ -1692,26 +1759,18 @@ var HoleTool = class {
|
|
|
1692
1759
|
this.context = context;
|
|
1693
1760
|
this.canvasService = context.services.get("CanvasService");
|
|
1694
1761
|
if (!this.canvasService) {
|
|
1695
|
-
console.warn("CanvasService not found for
|
|
1762
|
+
console.warn("CanvasService not found for FeatureTool");
|
|
1696
1763
|
return;
|
|
1697
1764
|
}
|
|
1698
1765
|
const configService = context.services.get(
|
|
1699
1766
|
"ConfigurationService"
|
|
1700
1767
|
);
|
|
1701
1768
|
if (configService) {
|
|
1702
|
-
this.
|
|
1703
|
-
"hole.constraintTarget",
|
|
1704
|
-
this.constraintTarget
|
|
1705
|
-
);
|
|
1706
|
-
this.holes = configService.get("dieline.holes", []);
|
|
1769
|
+
this.features = configService.get("dieline.features", []);
|
|
1707
1770
|
configService.onAnyChange((e) => {
|
|
1708
1771
|
if (this.isUpdatingConfig) return;
|
|
1709
|
-
if (e.key === "
|
|
1710
|
-
this.
|
|
1711
|
-
this.enforceConstraints();
|
|
1712
|
-
}
|
|
1713
|
-
if (e.key === "dieline.holes") {
|
|
1714
|
-
this.holes = e.value || [];
|
|
1772
|
+
if (e.key === "dieline.features") {
|
|
1773
|
+
this.features = e.value || [];
|
|
1715
1774
|
this.redraw();
|
|
1716
1775
|
}
|
|
1717
1776
|
});
|
|
@@ -1725,102 +1784,38 @@ var HoleTool = class {
|
|
|
1725
1784
|
}
|
|
1726
1785
|
contribute() {
|
|
1727
1786
|
return {
|
|
1728
|
-
[ContributionPointIds4.CONFIGURATIONS]: [
|
|
1729
|
-
{
|
|
1730
|
-
id: "hole.constraintTarget",
|
|
1731
|
-
type: "select",
|
|
1732
|
-
label: "Constraint Target",
|
|
1733
|
-
options: ["original", "bleed"],
|
|
1734
|
-
default: "bleed"
|
|
1735
|
-
}
|
|
1736
|
-
],
|
|
1737
1787
|
[ContributionPointIds4.COMMANDS]: [
|
|
1738
1788
|
{
|
|
1739
|
-
command: "
|
|
1740
|
-
title: "
|
|
1741
|
-
handler: () => {
|
|
1742
|
-
|
|
1743
|
-
if (!this.canvasService) return false;
|
|
1744
|
-
let defaultPos = { x: this.canvasService.canvas.width / 2, y: 50 };
|
|
1745
|
-
if (this.currentGeometry) {
|
|
1746
|
-
const g = this.currentGeometry;
|
|
1747
|
-
const topCenter = { x: g.x, y: g.y - g.height / 2 };
|
|
1748
|
-
defaultPos = getNearestPointOnDieline(topCenter, {
|
|
1749
|
-
...g,
|
|
1750
|
-
holes: []
|
|
1751
|
-
});
|
|
1752
|
-
}
|
|
1753
|
-
const { width, height } = this.canvasService.canvas;
|
|
1754
|
-
const normalizedHole = Coordinate.normalizePoint(defaultPos, {
|
|
1755
|
-
width: width || 800,
|
|
1756
|
-
height: height || 600
|
|
1757
|
-
});
|
|
1758
|
-
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1759
|
-
"ConfigurationService"
|
|
1760
|
-
);
|
|
1761
|
-
if (configService) {
|
|
1762
|
-
configService.update("dieline.holes", [
|
|
1763
|
-
{
|
|
1764
|
-
x: normalizedHole.x,
|
|
1765
|
-
y: normalizedHole.y,
|
|
1766
|
-
innerRadius: 15,
|
|
1767
|
-
outerRadius: 25
|
|
1768
|
-
}
|
|
1769
|
-
]);
|
|
1770
|
-
}
|
|
1771
|
-
return true;
|
|
1789
|
+
command: "addFeature",
|
|
1790
|
+
title: "Add Edge Feature",
|
|
1791
|
+
handler: (type = "subtract") => {
|
|
1792
|
+
return this.addFeature(type);
|
|
1772
1793
|
}
|
|
1773
1794
|
},
|
|
1774
1795
|
{
|
|
1775
1796
|
command: "addHole",
|
|
1776
1797
|
title: "Add Hole",
|
|
1777
|
-
handler: (
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
normalizedX = gw > 0 ? (x - left) / gw : 0.5;
|
|
1787
|
-
normalizedY = gh > 0 ? (y - top) / gh : 0.5;
|
|
1788
|
-
} else {
|
|
1789
|
-
const { width, height } = this.canvasService.canvas;
|
|
1790
|
-
normalizedX = Coordinate.toNormalized(x, width || 800);
|
|
1791
|
-
normalizedY = Coordinate.toNormalized(y, height || 600);
|
|
1792
|
-
}
|
|
1793
|
-
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1794
|
-
"ConfigurationService"
|
|
1795
|
-
);
|
|
1796
|
-
if (configService) {
|
|
1797
|
-
const currentHoles = configService.get("dieline.holes", []);
|
|
1798
|
-
const lastHole = currentHoles[currentHoles.length - 1];
|
|
1799
|
-
const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
|
|
1800
|
-
const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
|
|
1801
|
-
const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
|
|
1802
|
-
const newHole = {
|
|
1803
|
-
x: normalizedX,
|
|
1804
|
-
y: normalizedY,
|
|
1805
|
-
shape,
|
|
1806
|
-
innerRadius,
|
|
1807
|
-
outerRadius
|
|
1808
|
-
};
|
|
1809
|
-
configService.update("dieline.holes", [...currentHoles, newHole]);
|
|
1810
|
-
}
|
|
1811
|
-
return true;
|
|
1798
|
+
handler: () => {
|
|
1799
|
+
return this.addFeature("subtract");
|
|
1800
|
+
}
|
|
1801
|
+
},
|
|
1802
|
+
{
|
|
1803
|
+
command: "addDoubleLayerHole",
|
|
1804
|
+
title: "Add Double Layer Hole",
|
|
1805
|
+
handler: () => {
|
|
1806
|
+
return this.addDoubleLayerHole();
|
|
1812
1807
|
}
|
|
1813
1808
|
},
|
|
1814
1809
|
{
|
|
1815
|
-
command: "
|
|
1816
|
-
title: "Clear
|
|
1810
|
+
command: "clearFeatures",
|
|
1811
|
+
title: "Clear Features",
|
|
1817
1812
|
handler: () => {
|
|
1818
1813
|
var _a;
|
|
1819
1814
|
const configService = (_a = this.context) == null ? void 0 : _a.services.get(
|
|
1820
1815
|
"ConfigurationService"
|
|
1821
1816
|
);
|
|
1822
1817
|
if (configService) {
|
|
1823
|
-
configService.update("dieline.
|
|
1818
|
+
configService.update("dieline.features", []);
|
|
1824
1819
|
}
|
|
1825
1820
|
return true;
|
|
1826
1821
|
}
|
|
@@ -1828,6 +1823,88 @@ var HoleTool = class {
|
|
|
1828
1823
|
]
|
|
1829
1824
|
};
|
|
1830
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
|
+
}
|
|
1831
1908
|
setup() {
|
|
1832
1909
|
if (!this.canvasService || !this.context) return;
|
|
1833
1910
|
const canvas = this.canvasService.canvas;
|
|
@@ -1835,10 +1912,7 @@ var HoleTool = class {
|
|
|
1835
1912
|
this.handleDielineChange = (geometry) => {
|
|
1836
1913
|
this.currentGeometry = geometry;
|
|
1837
1914
|
this.redraw();
|
|
1838
|
-
|
|
1839
|
-
if (changed) {
|
|
1840
|
-
this.syncHolesToDieline();
|
|
1841
|
-
}
|
|
1915
|
+
this.enforceConstraints();
|
|
1842
1916
|
};
|
|
1843
1917
|
this.context.eventBus.on(
|
|
1844
1918
|
"dieline:geometry:change",
|
|
@@ -1848,69 +1922,101 @@ var HoleTool = class {
|
|
|
1848
1922
|
const commandService = this.context.services.get("CommandService");
|
|
1849
1923
|
if (commandService) {
|
|
1850
1924
|
try {
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
Promise.resolve(geometry).then((g) => {
|
|
1925
|
+
Promise.resolve(commandService.executeCommand("getGeometry")).then(
|
|
1926
|
+
(g) => {
|
|
1854
1927
|
if (g) {
|
|
1855
1928
|
this.currentGeometry = g;
|
|
1856
|
-
this.
|
|
1857
|
-
this.initializeHoles();
|
|
1929
|
+
this.redraw();
|
|
1858
1930
|
}
|
|
1859
|
-
}
|
|
1860
|
-
|
|
1931
|
+
}
|
|
1932
|
+
);
|
|
1861
1933
|
} catch (e) {
|
|
1862
1934
|
}
|
|
1863
1935
|
}
|
|
1864
1936
|
if (!this.handleMoving) {
|
|
1865
1937
|
this.handleMoving = (e) => {
|
|
1866
|
-
var _a, _b, _c, _d
|
|
1938
|
+
var _a, _b, _c, _d;
|
|
1867
1939
|
const target = e.target;
|
|
1868
|
-
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "
|
|
1940
|
+
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "feature-marker") return;
|
|
1869
1941
|
if (!this.currentGeometry) return;
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
}
|
|
1882
|
-
const
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
constraintGeometry,
|
|
1886
|
-
(_d = holeData == null ? void 0 : holeData.innerRadius) != null ? _d : 15,
|
|
1887
|
-
(_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
|
|
1888
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);
|
|
1889
1963
|
target.set({
|
|
1890
|
-
left:
|
|
1891
|
-
top:
|
|
1964
|
+
left: snapped.x,
|
|
1965
|
+
top: snapped.y
|
|
1892
1966
|
});
|
|
1893
1967
|
};
|
|
1894
1968
|
canvas.on("object:moving", this.handleMoving);
|
|
1895
1969
|
}
|
|
1896
1970
|
if (!this.handleModified) {
|
|
1897
1971
|
this.handleModified = (e) => {
|
|
1898
|
-
var _a;
|
|
1972
|
+
var _a, _b, _c, _d;
|
|
1899
1973
|
const target = e.target;
|
|
1900
|
-
if (!target || ((_a = target.data) == null ? void 0 : _a.type) !== "
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
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);
|
|
1904
2016
|
}
|
|
1905
2017
|
};
|
|
1906
2018
|
canvas.on("object:modified", this.handleModified);
|
|
1907
2019
|
}
|
|
1908
|
-
this.initializeHoles();
|
|
1909
|
-
}
|
|
1910
|
-
initializeHoles() {
|
|
1911
|
-
if (!this.canvasService) return;
|
|
1912
|
-
this.redraw();
|
|
1913
|
-
this.syncHolesToDieline();
|
|
1914
2020
|
}
|
|
1915
2021
|
teardown() {
|
|
1916
2022
|
if (!this.canvasService) return;
|
|
@@ -1932,357 +2038,259 @@ var HoleTool = class {
|
|
|
1932
2038
|
}
|
|
1933
2039
|
const objects = canvas.getObjects().filter((obj) => {
|
|
1934
2040
|
var _a;
|
|
1935
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2041
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
1936
2042
|
});
|
|
1937
2043
|
objects.forEach((obj) => canvas.remove(obj));
|
|
1938
|
-
if (this.context) {
|
|
1939
|
-
const commandService = this.context.services.get("CommandService");
|
|
1940
|
-
if (commandService) {
|
|
1941
|
-
try {
|
|
1942
|
-
commandService.executeCommand("setHoles", []);
|
|
1943
|
-
} catch (e) {
|
|
1944
|
-
}
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
2044
|
this.canvasService.requestRenderAll();
|
|
1948
2045
|
}
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
var _a;
|
|
1954
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
|
|
1955
|
-
}
|
|
1956
|
-
);
|
|
1957
|
-
if (objects.length === 0 && this.holes.length > 0) {
|
|
1958
|
-
console.warn("HoleTool: No markers found on canvas to sync from");
|
|
1959
|
-
return;
|
|
1960
|
-
}
|
|
1961
|
-
objects.sort(
|
|
1962
|
-
(a, b) => {
|
|
1963
|
-
var _a, _b, _c, _d;
|
|
1964
|
-
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);
|
|
1965
|
-
}
|
|
1966
|
-
);
|
|
1967
|
-
const newHoles = objects.map((obj, i) => {
|
|
1968
|
-
var _a, _b, _c, _d;
|
|
1969
|
-
const original = this.holes[i];
|
|
1970
|
-
const newAbsX = obj.left;
|
|
1971
|
-
const newAbsY = obj.top;
|
|
1972
|
-
if (isNaN(newAbsX) || isNaN(newAbsY)) {
|
|
1973
|
-
console.error("HoleTool: Invalid marker coordinates", {
|
|
1974
|
-
newAbsX,
|
|
1975
|
-
newAbsY
|
|
1976
|
-
});
|
|
1977
|
-
return original;
|
|
1978
|
-
}
|
|
1979
|
-
const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
|
|
1980
|
-
const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
|
|
1981
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
1982
|
-
if (original && original.anchor && this.currentGeometry) {
|
|
1983
|
-
const { x, y, width, height } = this.currentGeometry;
|
|
1984
|
-
let bx = x;
|
|
1985
|
-
let by = y;
|
|
1986
|
-
const left = x - width / 2;
|
|
1987
|
-
const right = x + width / 2;
|
|
1988
|
-
const top = y - height / 2;
|
|
1989
|
-
const bottom = y + height / 2;
|
|
1990
|
-
switch (original.anchor) {
|
|
1991
|
-
case "top-left":
|
|
1992
|
-
bx = left;
|
|
1993
|
-
by = top;
|
|
1994
|
-
break;
|
|
1995
|
-
case "top-center":
|
|
1996
|
-
bx = x;
|
|
1997
|
-
by = top;
|
|
1998
|
-
break;
|
|
1999
|
-
case "top-right":
|
|
2000
|
-
bx = right;
|
|
2001
|
-
by = top;
|
|
2002
|
-
break;
|
|
2003
|
-
case "center-left":
|
|
2004
|
-
bx = left;
|
|
2005
|
-
by = y;
|
|
2006
|
-
break;
|
|
2007
|
-
case "center":
|
|
2008
|
-
bx = x;
|
|
2009
|
-
by = y;
|
|
2010
|
-
break;
|
|
2011
|
-
case "center-right":
|
|
2012
|
-
bx = right;
|
|
2013
|
-
by = y;
|
|
2014
|
-
break;
|
|
2015
|
-
case "bottom-left":
|
|
2016
|
-
bx = left;
|
|
2017
|
-
by = bottom;
|
|
2018
|
-
break;
|
|
2019
|
-
case "bottom-center":
|
|
2020
|
-
bx = x;
|
|
2021
|
-
by = bottom;
|
|
2022
|
-
break;
|
|
2023
|
-
case "bottom-right":
|
|
2024
|
-
bx = right;
|
|
2025
|
-
by = bottom;
|
|
2026
|
-
break;
|
|
2027
|
-
}
|
|
2028
|
-
return {
|
|
2029
|
-
...original,
|
|
2030
|
-
// Denormalize offset back to physical units (mm)
|
|
2031
|
-
offsetX: (newAbsX - bx) / scale / unitScale,
|
|
2032
|
-
offsetY: (newAbsY - by) / scale / unitScale,
|
|
2033
|
-
// Clear direct coordinates if we use anchor
|
|
2034
|
-
x: void 0,
|
|
2035
|
-
y: void 0,
|
|
2036
|
-
// Ensure other properties are preserved
|
|
2037
|
-
innerRadius: original.innerRadius,
|
|
2038
|
-
outerRadius: original.outerRadius,
|
|
2039
|
-
shape: original.shape || "circle"
|
|
2040
|
-
};
|
|
2041
|
-
}
|
|
2042
|
-
let normalizedX = 0.5;
|
|
2043
|
-
let normalizedY = 0.5;
|
|
2044
|
-
if (this.currentGeometry) {
|
|
2045
|
-
const { x, y, width, height } = this.currentGeometry;
|
|
2046
|
-
const left = x - width / 2;
|
|
2047
|
-
const top = y - height / 2;
|
|
2048
|
-
normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
|
|
2049
|
-
normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
|
|
2050
|
-
} else {
|
|
2051
|
-
const { width, height } = this.canvasService.canvas;
|
|
2052
|
-
normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
|
|
2053
|
-
normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
|
|
2054
|
-
}
|
|
2055
|
-
return {
|
|
2056
|
-
...original,
|
|
2057
|
-
x: normalizedX,
|
|
2058
|
-
y: normalizedY,
|
|
2059
|
-
// Clear offsets if we are using direct normalized coordinates
|
|
2060
|
-
offsetX: void 0,
|
|
2061
|
-
offsetY: void 0,
|
|
2062
|
-
// Ensure other properties are preserved
|
|
2063
|
-
innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
|
|
2064
|
-
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
|
|
2065
|
-
shape: (original == null ? void 0 : original.shape) || "circle"
|
|
2066
|
-
};
|
|
2046
|
+
constrainPosition(p, geometry, limit) {
|
|
2047
|
+
const nearest = getNearestPointOnDieline({ x: p.x, y: p.y }, {
|
|
2048
|
+
...geometry,
|
|
2049
|
+
features: []
|
|
2067
2050
|
});
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
+
};
|
|
2070
2062
|
}
|
|
2071
|
-
|
|
2072
|
-
|
|
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;
|
|
2073
2085
|
const configService = this.context.services.get(
|
|
2074
2086
|
"ConfigurationService"
|
|
2075
2087
|
);
|
|
2076
2088
|
if (configService) {
|
|
2077
2089
|
this.isUpdatingConfig = true;
|
|
2078
2090
|
try {
|
|
2079
|
-
configService.update("dieline.
|
|
2091
|
+
configService.update("dieline.features", this.features);
|
|
2080
2092
|
} finally {
|
|
2081
2093
|
this.isUpdatingConfig = false;
|
|
2082
2094
|
}
|
|
2083
2095
|
}
|
|
2084
2096
|
}
|
|
2085
2097
|
redraw() {
|
|
2086
|
-
if (!this.canvasService) return;
|
|
2098
|
+
if (!this.canvasService || !this.currentGeometry) return;
|
|
2087
2099
|
const canvas = this.canvasService.canvas;
|
|
2088
|
-
const
|
|
2100
|
+
const geometry = this.currentGeometry;
|
|
2089
2101
|
const existing = canvas.getObjects().filter((obj) => {
|
|
2090
2102
|
var _a;
|
|
2091
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2103
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
2092
2104
|
});
|
|
2093
2105
|
existing.forEach((obj) => canvas.remove(obj));
|
|
2094
|
-
|
|
2095
|
-
if (!holes || holes.length === 0) {
|
|
2106
|
+
if (!this.features || this.features.length === 0) {
|
|
2096
2107
|
this.canvasService.requestRenderAll();
|
|
2097
2108
|
return;
|
|
2098
2109
|
}
|
|
2099
|
-
const
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
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;
|
|
2106
2162
|
};
|
|
2107
|
-
|
|
2108
|
-
const
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
const visualInnerRadius = hole.innerRadius * unitScale * scale;
|
|
2112
|
-
const visualOuterRadius = hole.outerRadius * unitScale * scale;
|
|
2113
|
-
const pos = resolveHolePosition(
|
|
2114
|
-
{
|
|
2115
|
-
...hole,
|
|
2116
|
-
offsetX: (hole.offsetX || 0) * unitScale * scale,
|
|
2117
|
-
offsetY: (hole.offsetY || 0) * unitScale * scale
|
|
2118
|
-
},
|
|
2119
|
-
geometry,
|
|
2120
|
-
{ width: geometry.width, height: geometry.height }
|
|
2121
|
-
// Use geometry dims instead of canvas
|
|
2163
|
+
singles.forEach(({ feature, index }) => {
|
|
2164
|
+
const geometry2 = this.getGeometryForFeature(
|
|
2165
|
+
this.currentGeometry,
|
|
2166
|
+
feature
|
|
2122
2167
|
);
|
|
2123
|
-
const
|
|
2124
|
-
const
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
fill: "transparent",
|
|
2135
|
-
stroke: "red",
|
|
2136
|
-
strokeWidth: 2,
|
|
2137
|
-
originX: "center",
|
|
2138
|
-
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 }
|
|
2139
2179
|
});
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
stroke: "#666",
|
|
2145
|
-
strokeWidth: 1,
|
|
2146
|
-
strokeDashArray: [5, 5],
|
|
2147
|
-
originX: "center",
|
|
2148
|
-
originY: "center"
|
|
2149
|
-
}) : new Circle({
|
|
2150
|
-
radius: visualOuterRadius,
|
|
2151
|
-
fill: "transparent",
|
|
2152
|
-
stroke: "#666",
|
|
2153
|
-
strokeWidth: 1,
|
|
2154
|
-
strokeDashArray: [5, 5],
|
|
2155
|
-
originX: "center",
|
|
2156
|
-
originY: "center"
|
|
2180
|
+
marker.set("opacity", 0);
|
|
2181
|
+
marker.on("mouseover", () => {
|
|
2182
|
+
marker.set("opacity", 1);
|
|
2183
|
+
canvas.requestRenderAll();
|
|
2157
2184
|
});
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
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, {
|
|
2163
2214
|
selectable: true,
|
|
2164
2215
|
hasControls: false,
|
|
2165
|
-
// Don't allow resizing/rotating
|
|
2166
2216
|
hasBorders: false,
|
|
2167
|
-
subTargetCheck: false,
|
|
2168
|
-
opacity: 0,
|
|
2169
|
-
// Default hidden
|
|
2170
2217
|
hoverCursor: "move",
|
|
2171
|
-
|
|
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
|
+
}
|
|
2172
2232
|
});
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2233
|
+
groupObj.set("opacity", 0);
|
|
2234
|
+
groupObj.on("mouseover", () => {
|
|
2235
|
+
groupObj.set("opacity", 1);
|
|
2176
2236
|
canvas.requestRenderAll();
|
|
2177
2237
|
});
|
|
2178
|
-
|
|
2179
|
-
if (canvas.getActiveObject() !==
|
|
2180
|
-
|
|
2238
|
+
groupObj.on("mouseout", () => {
|
|
2239
|
+
if (canvas.getActiveObject() !== groupObj) {
|
|
2240
|
+
groupObj.set("opacity", 0);
|
|
2181
2241
|
canvas.requestRenderAll();
|
|
2182
2242
|
}
|
|
2183
2243
|
});
|
|
2184
|
-
|
|
2185
|
-
|
|
2244
|
+
groupObj.on("selected", () => {
|
|
2245
|
+
groupObj.set("opacity", 1);
|
|
2186
2246
|
canvas.requestRenderAll();
|
|
2187
2247
|
});
|
|
2188
|
-
|
|
2189
|
-
|
|
2248
|
+
groupObj.on("deselected", () => {
|
|
2249
|
+
groupObj.set("opacity", 0);
|
|
2190
2250
|
canvas.requestRenderAll();
|
|
2191
2251
|
});
|
|
2192
|
-
canvas.add(
|
|
2193
|
-
canvas.bringObjectToFront(
|
|
2252
|
+
canvas.add(groupObj);
|
|
2253
|
+
canvas.bringObjectToFront(groupObj);
|
|
2194
2254
|
});
|
|
2195
|
-
const markers = canvas.getObjects().filter((o) => {
|
|
2196
|
-
var _a;
|
|
2197
|
-
return ((_a = o.data) == null ? void 0 : _a.type) === "hole-marker";
|
|
2198
|
-
});
|
|
2199
|
-
markers.forEach((m) => canvas.bringObjectToFront(m));
|
|
2200
2255
|
this.canvasService.requestRenderAll();
|
|
2201
2256
|
}
|
|
2202
2257
|
enforceConstraints() {
|
|
2203
|
-
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
}
|
|
2207
|
-
const effectiveOffset = this.constraintTarget === "original" ? 0 : geometry.offset;
|
|
2208
|
-
const constraintGeometry = {
|
|
2209
|
-
...geometry,
|
|
2210
|
-
width: Math.max(0, geometry.width + effectiveOffset * 2),
|
|
2211
|
-
height: Math.max(0, geometry.height + effectiveOffset * 2),
|
|
2212
|
-
radius: Math.max(0, geometry.radius + effectiveOffset)
|
|
2213
|
-
};
|
|
2214
|
-
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) => {
|
|
2215
2261
|
var _a;
|
|
2216
|
-
return ((_a = obj.data) == null ? void 0 : _a.type) === "
|
|
2262
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "feature-marker";
|
|
2217
2263
|
});
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
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
|
+
}
|
|
2223
2277
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
var _a, _b;
|
|
2228
|
-
const currentPos = new Point(obj.left, obj.top);
|
|
2229
|
-
const holeData = this.holes[i];
|
|
2230
|
-
const scale = geometry.scale || 1;
|
|
2231
|
-
const unit = geometry.unit || "mm";
|
|
2232
|
-
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
2233
|
-
const innerR = ((_a = holeData == null ? void 0 : holeData.innerRadius) != null ? _a : 15) * unitScale * scale;
|
|
2234
|
-
const outerR = ((_b = holeData == null ? void 0 : holeData.outerRadius) != null ? _b : 25) * unitScale * scale;
|
|
2235
|
-
const newPos = this.calculateConstrainedPosition(
|
|
2236
|
-
currentPos,
|
|
2237
|
-
constraintGeometry,
|
|
2238
|
-
innerR,
|
|
2239
|
-
outerR
|
|
2278
|
+
const geometry = this.getGeometryForFeature(
|
|
2279
|
+
this.currentGeometry,
|
|
2280
|
+
feature
|
|
2240
2281
|
);
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
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();
|
|
2249
2292
|
});
|
|
2250
|
-
|
|
2251
|
-
this.syncHolesFromCanvas();
|
|
2252
|
-
return true;
|
|
2253
|
-
}
|
|
2254
|
-
return false;
|
|
2255
|
-
}
|
|
2256
|
-
calculateConstrainedPosition(p, g, innerRadius, outerRadius) {
|
|
2257
|
-
const options = {
|
|
2258
|
-
...g,
|
|
2259
|
-
holes: []
|
|
2260
|
-
// We don't need holes for boundary calculation
|
|
2261
|
-
};
|
|
2262
|
-
const nearest = getNearestPointOnDieline(
|
|
2263
|
-
{ x: p.x, y: p.y },
|
|
2264
|
-
options
|
|
2265
|
-
);
|
|
2266
|
-
const nearestP = new Point(nearest.x, nearest.y);
|
|
2267
|
-
const dist = p.distanceFrom(nearestP);
|
|
2268
|
-
const v = p.subtract(nearestP);
|
|
2269
|
-
const center = new Point(g.x, g.y);
|
|
2270
|
-
const distToCenter = p.distanceFrom(center);
|
|
2271
|
-
const nearestDistToCenter = nearestP.distanceFrom(center);
|
|
2272
|
-
let signedDist = dist;
|
|
2273
|
-
if (distToCenter < nearestDistToCenter) {
|
|
2274
|
-
signedDist = -dist;
|
|
2275
|
-
}
|
|
2276
|
-
let clampedDist = signedDist;
|
|
2277
|
-
if (signedDist > 0) {
|
|
2278
|
-
clampedDist = Math.min(signedDist, innerRadius);
|
|
2279
|
-
} else {
|
|
2280
|
-
clampedDist = Math.max(signedDist, -outerRadius);
|
|
2281
|
-
}
|
|
2282
|
-
if (dist < 1e-3) return nearestP;
|
|
2283
|
-
const scale = Math.abs(clampedDist) / (dist || 1);
|
|
2284
|
-
const offset = v.scalarMultiply(scale);
|
|
2285
|
-
return nearestP.add(offset);
|
|
2293
|
+
canvas.requestRenderAll();
|
|
2286
2294
|
}
|
|
2287
2295
|
};
|
|
2288
2296
|
|
|
@@ -3495,8 +3503,8 @@ export {
|
|
|
3495
3503
|
BackgroundTool,
|
|
3496
3504
|
CanvasService,
|
|
3497
3505
|
DielineTool,
|
|
3506
|
+
FeatureTool,
|
|
3498
3507
|
FilmTool,
|
|
3499
|
-
HoleTool,
|
|
3500
3508
|
ImageTool,
|
|
3501
3509
|
MirrorTool,
|
|
3502
3510
|
RulerTool,
|