@pooder/kit 3.2.0 → 3.4.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 +3 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +387 -158
- package/dist/index.mjs +388 -159
- package/package.json +1 -1
- package/src/dieline.ts +10 -0
- package/src/geometry.ts +37 -11
- package/src/hole.ts +79 -29
- package/src/image.ts +122 -124
- package/src/tracer.ts +278 -164
package/dist/index.js
CHANGED
|
@@ -255,7 +255,7 @@ var ImageTracer = class {
|
|
|
255
255
|
* @param options Configuration options.
|
|
256
256
|
*/
|
|
257
257
|
static async trace(imageUrl, options = {}) {
|
|
258
|
-
var _a, _b;
|
|
258
|
+
var _a, _b, _c, _d, _e;
|
|
259
259
|
const img = await this.loadImage(imageUrl);
|
|
260
260
|
const width = img.width;
|
|
261
261
|
const height = img.height;
|
|
@@ -266,21 +266,213 @@ var ImageTracer = class {
|
|
|
266
266
|
if (!ctx) throw new Error("Could not get 2D context");
|
|
267
267
|
ctx.drawImage(img, 0, 0);
|
|
268
268
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
269
|
-
const
|
|
270
|
-
|
|
271
|
-
|
|
269
|
+
const threshold = (_a = options.threshold) != null ? _a : 10;
|
|
270
|
+
const adaptiveRadius = Math.max(
|
|
271
|
+
5,
|
|
272
|
+
Math.floor(Math.max(width, height) * 0.02)
|
|
273
|
+
);
|
|
274
|
+
const radius = (_b = options.morphologyRadius) != null ? _b : adaptiveRadius;
|
|
275
|
+
let mask = this.createMask(imageData, threshold);
|
|
276
|
+
if (radius > 0) {
|
|
277
|
+
mask = this.dilate(mask, width, height, radius);
|
|
278
|
+
mask = this.erode(mask, width, height, radius);
|
|
279
|
+
mask = this.fillHoles(mask, width, height);
|
|
280
|
+
}
|
|
281
|
+
const allContourPoints = this.traceAllContours(mask, width, height);
|
|
282
|
+
if (allContourPoints.length === 0) {
|
|
283
|
+
const w = (_c = options.scaleToWidth) != null ? _c : width;
|
|
284
|
+
const h = (_d = options.scaleToHeight) != null ? _d : height;
|
|
285
|
+
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
|
|
286
|
+
}
|
|
287
|
+
const primaryContour = allContourPoints.sort(
|
|
288
|
+
(a, b) => b.length - a.length
|
|
289
|
+
)[0];
|
|
290
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
291
|
+
for (const p of primaryContour) {
|
|
292
|
+
if (p.x < minX) minX = p.x;
|
|
293
|
+
if (p.y < minY) minY = p.y;
|
|
294
|
+
if (p.x > maxX) maxX = p.x;
|
|
295
|
+
if (p.y > maxY) maxY = p.y;
|
|
296
|
+
}
|
|
297
|
+
const globalBounds = {
|
|
298
|
+
minX,
|
|
299
|
+
minY,
|
|
300
|
+
width: maxX - minX,
|
|
301
|
+
height: maxY - minY
|
|
302
|
+
};
|
|
303
|
+
let finalPoints = primaryContour;
|
|
304
|
+
if (options.scaleToWidth && options.scaleToHeight) {
|
|
272
305
|
finalPoints = this.scalePoints(
|
|
273
|
-
|
|
306
|
+
primaryContour,
|
|
274
307
|
options.scaleToWidth,
|
|
275
|
-
options.scaleToHeight
|
|
308
|
+
options.scaleToHeight,
|
|
309
|
+
globalBounds
|
|
276
310
|
);
|
|
277
311
|
}
|
|
278
312
|
const simplifiedPoints = this.douglasPeucker(
|
|
279
313
|
finalPoints,
|
|
280
|
-
(
|
|
314
|
+
(_e = options.simplifyTolerance) != null ? _e : 2
|
|
281
315
|
);
|
|
282
316
|
return this.pointsToSVG(simplifiedPoints);
|
|
283
317
|
}
|
|
318
|
+
static createMask(imageData, threshold) {
|
|
319
|
+
const { width, height, data } = imageData;
|
|
320
|
+
const mask = new Uint8Array(width * height);
|
|
321
|
+
for (let i = 0; i < width * height; i++) {
|
|
322
|
+
const idx = i * 4;
|
|
323
|
+
const r = data[idx];
|
|
324
|
+
const g = data[idx + 1];
|
|
325
|
+
const b = data[idx + 2];
|
|
326
|
+
const a = data[idx + 3];
|
|
327
|
+
if (a > threshold && !(r > 240 && g > 240 && b > 240)) {
|
|
328
|
+
mask[i] = 1;
|
|
329
|
+
} else {
|
|
330
|
+
mask[i] = 0;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return mask;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Fast 1D-separable Dilation
|
|
337
|
+
*/
|
|
338
|
+
static dilate(mask, width, height, radius) {
|
|
339
|
+
const horizontal = new Uint8Array(width * height);
|
|
340
|
+
for (let y = 0; y < height; y++) {
|
|
341
|
+
let count = 0;
|
|
342
|
+
for (let x = -radius; x < width; x++) {
|
|
343
|
+
if (x + radius < width && mask[y * width + x + radius]) count++;
|
|
344
|
+
if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
|
|
345
|
+
if (x >= 0) horizontal[y * width + x] = count > 0 ? 1 : 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const vertical = new Uint8Array(width * height);
|
|
349
|
+
for (let x = 0; x < width; x++) {
|
|
350
|
+
let count = 0;
|
|
351
|
+
for (let y = -radius; y < height; y++) {
|
|
352
|
+
if (y + radius < height && horizontal[(y + radius) * width + x])
|
|
353
|
+
count++;
|
|
354
|
+
if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
|
|
355
|
+
count--;
|
|
356
|
+
if (y >= 0) vertical[y * width + x] = count > 0 ? 1 : 0;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return vertical;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Fast 1D-separable Erosion
|
|
363
|
+
*/
|
|
364
|
+
static erode(mask, width, height, radius) {
|
|
365
|
+
const horizontal = new Uint8Array(width * height);
|
|
366
|
+
for (let y = 0; y < height; y++) {
|
|
367
|
+
let count = 0;
|
|
368
|
+
for (let x = -radius; x < width; x++) {
|
|
369
|
+
if (x + radius < width && mask[y * width + x + radius]) count++;
|
|
370
|
+
if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
|
|
371
|
+
if (x >= 0) {
|
|
372
|
+
const winWidth = Math.min(x + radius, width - 1) - Math.max(x - radius, 0) + 1;
|
|
373
|
+
horizontal[y * width + x] = count === winWidth ? 1 : 0;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
const vertical = new Uint8Array(width * height);
|
|
378
|
+
for (let x = 0; x < width; x++) {
|
|
379
|
+
let count = 0;
|
|
380
|
+
for (let y = -radius; y < height; y++) {
|
|
381
|
+
if (y + radius < height && horizontal[(y + radius) * width + x])
|
|
382
|
+
count++;
|
|
383
|
+
if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
|
|
384
|
+
count--;
|
|
385
|
+
if (y >= 0) {
|
|
386
|
+
const winHeight = Math.min(y + radius, height - 1) - Math.max(y - radius, 0) + 1;
|
|
387
|
+
vertical[y * width + x] = count === winHeight ? 1 : 0;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
return vertical;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Fills internal holes in the binary mask using flood fill from edges.
|
|
395
|
+
*/
|
|
396
|
+
static fillHoles(mask, width, height) {
|
|
397
|
+
const background = new Uint8Array(width * height);
|
|
398
|
+
const queue = [];
|
|
399
|
+
for (let x = 0; x < width; x++) {
|
|
400
|
+
if (mask[x] === 0) {
|
|
401
|
+
background[x] = 1;
|
|
402
|
+
queue.push([x, 0]);
|
|
403
|
+
}
|
|
404
|
+
const lastRow = (height - 1) * width + x;
|
|
405
|
+
if (mask[lastRow] === 0) {
|
|
406
|
+
background[lastRow] = 1;
|
|
407
|
+
queue.push([x, height - 1]);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
for (let y = 1; y < height - 1; y++) {
|
|
411
|
+
if (mask[y * width] === 0) {
|
|
412
|
+
background[y * width] = 1;
|
|
413
|
+
queue.push([0, y]);
|
|
414
|
+
}
|
|
415
|
+
if (mask[y * width + width - 1] === 0) {
|
|
416
|
+
background[y * width + width - 1] = 1;
|
|
417
|
+
queue.push([width - 1, y]);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
const dirs = [
|
|
421
|
+
[0, 1],
|
|
422
|
+
[0, -1],
|
|
423
|
+
[1, 0],
|
|
424
|
+
[-1, 0]
|
|
425
|
+
];
|
|
426
|
+
let head = 0;
|
|
427
|
+
while (head < queue.length) {
|
|
428
|
+
const [cx, cy] = queue[head++];
|
|
429
|
+
for (const [dx, dy] of dirs) {
|
|
430
|
+
const nx = cx + dx;
|
|
431
|
+
const ny = cy + dy;
|
|
432
|
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
433
|
+
const nidx = ny * width + nx;
|
|
434
|
+
if (mask[nidx] === 0 && background[nidx] === 0) {
|
|
435
|
+
background[nidx] = 1;
|
|
436
|
+
queue.push([nx, ny]);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
const filledMask = new Uint8Array(width * height);
|
|
442
|
+
for (let i = 0; i < width * height; i++) {
|
|
443
|
+
filledMask[i] = background[i] === 0 ? 1 : 0;
|
|
444
|
+
}
|
|
445
|
+
return filledMask;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Traces all contours in the mask with optimized start-point detection
|
|
449
|
+
*/
|
|
450
|
+
static traceAllContours(mask, width, height) {
|
|
451
|
+
const visited = new Uint8Array(width * height);
|
|
452
|
+
const allContours = [];
|
|
453
|
+
for (let y = 0; y < height; y++) {
|
|
454
|
+
for (let x = 0; x < width; x++) {
|
|
455
|
+
const idx = y * width + x;
|
|
456
|
+
if (mask[idx] && !visited[idx]) {
|
|
457
|
+
const isLeftEdge = x === 0 || mask[idx - 1] === 0;
|
|
458
|
+
if (isLeftEdge) {
|
|
459
|
+
const contour = this.marchingSquares(
|
|
460
|
+
mask,
|
|
461
|
+
visited,
|
|
462
|
+
x,
|
|
463
|
+
y,
|
|
464
|
+
width,
|
|
465
|
+
height
|
|
466
|
+
);
|
|
467
|
+
if (contour.length > 2) {
|
|
468
|
+
allContours.push(contour);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
return allContours;
|
|
475
|
+
}
|
|
284
476
|
static loadImage(url) {
|
|
285
477
|
return new Promise((resolve, reject) => {
|
|
286
478
|
const img = new Image();
|
|
@@ -294,33 +486,11 @@ var ImageTracer = class {
|
|
|
294
486
|
* Moore-Neighbor Tracing Algorithm
|
|
295
487
|
* More robust for irregular shapes than simple Marching Squares walker.
|
|
296
488
|
*/
|
|
297
|
-
static marchingSquares(
|
|
298
|
-
const width = imageData.width;
|
|
299
|
-
const height = imageData.height;
|
|
300
|
-
const data = imageData.data;
|
|
489
|
+
static marchingSquares(mask, visited, startX, startY, width, height) {
|
|
301
490
|
const isSolid = (x, y) => {
|
|
302
491
|
if (x < 0 || x >= width || y < 0 || y >= height) return false;
|
|
303
|
-
|
|
304
|
-
const r = data[index];
|
|
305
|
-
const g = data[index + 1];
|
|
306
|
-
const b = data[index + 2];
|
|
307
|
-
const a = data[index + 3];
|
|
308
|
-
if (a <= alphaThreshold) return false;
|
|
309
|
-
if (r > 240 && g > 240 && b > 240) return false;
|
|
310
|
-
return true;
|
|
492
|
+
return mask[y * width + x] === 1;
|
|
311
493
|
};
|
|
312
|
-
let startX = -1;
|
|
313
|
-
let startY = -1;
|
|
314
|
-
searchLoop: for (let y = 0; y < height; y++) {
|
|
315
|
-
for (let x = 0; x < width; x++) {
|
|
316
|
-
if (isSolid(x, y)) {
|
|
317
|
-
startX = x;
|
|
318
|
-
startY = y;
|
|
319
|
-
break searchLoop;
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
if (startX === -1) return [];
|
|
324
494
|
const points = [];
|
|
325
495
|
let cx = startX;
|
|
326
496
|
let cy = startY;
|
|
@@ -339,6 +509,7 @@ var ImageTracer = class {
|
|
|
339
509
|
let steps = 0;
|
|
340
510
|
do {
|
|
341
511
|
points.push({ x: cx, y: cy });
|
|
512
|
+
visited[cy * width + cx] = 1;
|
|
342
513
|
let found = false;
|
|
343
514
|
for (let i = 0; i < 8; i++) {
|
|
344
515
|
const idx = (backtrack + 1 + i) % 8;
|
|
@@ -347,16 +518,12 @@ var ImageTracer = class {
|
|
|
347
518
|
if (isSolid(nx, ny)) {
|
|
348
519
|
cx = nx;
|
|
349
520
|
cy = ny;
|
|
350
|
-
backtrack = (idx + 4) % 8;
|
|
351
|
-
backtrack = (idx + 4 + 1) % 8;
|
|
352
521
|
backtrack = (idx + 4 + 1) % 8;
|
|
353
522
|
found = true;
|
|
354
523
|
break;
|
|
355
524
|
}
|
|
356
525
|
}
|
|
357
|
-
if (!found)
|
|
358
|
-
break;
|
|
359
|
-
}
|
|
526
|
+
if (!found) break;
|
|
360
527
|
steps++;
|
|
361
528
|
} while ((cx !== startX || cy !== startY) && steps < maxSteps);
|
|
362
529
|
return points;
|
|
@@ -405,23 +572,14 @@ var ImageTracer = class {
|
|
|
405
572
|
dy = p.y - y;
|
|
406
573
|
return dx * dx + dy * dy;
|
|
407
574
|
}
|
|
408
|
-
static scalePoints(points, targetWidth, targetHeight) {
|
|
575
|
+
static scalePoints(points, targetWidth, targetHeight, bounds) {
|
|
409
576
|
if (points.length === 0) return points;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (p.y < minY) minY = p.y;
|
|
414
|
-
if (p.x > maxX) maxX = p.x;
|
|
415
|
-
if (p.y > maxY) maxY = p.y;
|
|
416
|
-
}
|
|
417
|
-
const srcW = maxX - minX;
|
|
418
|
-
const srcH = maxY - minY;
|
|
419
|
-
if (srcW === 0 || srcH === 0) return points;
|
|
420
|
-
const scaleX = targetWidth / srcW;
|
|
421
|
-
const scaleY = targetHeight / srcH;
|
|
577
|
+
if (bounds.width === 0 || bounds.height === 0) return points;
|
|
578
|
+
const scaleX = targetWidth / bounds.width;
|
|
579
|
+
const scaleY = targetHeight / bounds.height;
|
|
422
580
|
return points.map((p) => ({
|
|
423
|
-
x: (p.x - minX) * scaleX,
|
|
424
|
-
y: (p.y - minY) * scaleY
|
|
581
|
+
x: (p.x - bounds.minX) * scaleX,
|
|
582
|
+
y: (p.y - bounds.minY) * scaleY
|
|
425
583
|
}));
|
|
426
584
|
}
|
|
427
585
|
static pointsToSVG(points) {
|
|
@@ -614,12 +772,25 @@ function getDielineShape(options) {
|
|
|
614
772
|
let lugsPath = null;
|
|
615
773
|
let cutsPath = null;
|
|
616
774
|
holes.forEach((hole) => {
|
|
617
|
-
const
|
|
618
|
-
|
|
775
|
+
const center = new import_paper.default.Point(hole.x, hole.y);
|
|
776
|
+
const lug = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
|
|
777
|
+
point: [
|
|
778
|
+
center.x - hole.outerRadius,
|
|
779
|
+
center.y - hole.outerRadius
|
|
780
|
+
],
|
|
781
|
+
size: [hole.outerRadius * 2, hole.outerRadius * 2]
|
|
782
|
+
}) : new import_paper.default.Path.Circle({
|
|
783
|
+
center,
|
|
619
784
|
radius: hole.outerRadius
|
|
620
785
|
});
|
|
621
|
-
const cut = new import_paper.default.Path.
|
|
622
|
-
|
|
786
|
+
const cut = hole.shape === "square" ? new import_paper.default.Path.Rectangle({
|
|
787
|
+
point: [
|
|
788
|
+
center.x - hole.innerRadius,
|
|
789
|
+
center.y - hole.innerRadius
|
|
790
|
+
],
|
|
791
|
+
size: [hole.innerRadius * 2, hole.innerRadius * 2]
|
|
792
|
+
}) : new import_paper.default.Path.Circle({
|
|
793
|
+
center,
|
|
623
794
|
radius: hole.innerRadius
|
|
624
795
|
});
|
|
625
796
|
if (!lugsPath) {
|
|
@@ -676,7 +847,9 @@ function getDielineShape(options) {
|
|
|
676
847
|
return mainShape;
|
|
677
848
|
}
|
|
678
849
|
function generateDielinePath(options) {
|
|
679
|
-
|
|
850
|
+
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
851
|
+
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
852
|
+
ensurePaper(paperWidth, paperHeight);
|
|
680
853
|
import_paper.default.project.activeLayer.removeChildren();
|
|
681
854
|
const mainShape = getDielineShape(options);
|
|
682
855
|
const pathData = mainShape.pathData;
|
|
@@ -700,8 +873,9 @@ function generateMaskPath(options) {
|
|
|
700
873
|
return pathData;
|
|
701
874
|
}
|
|
702
875
|
function generateBleedZonePath(options, offset) {
|
|
703
|
-
const
|
|
704
|
-
|
|
876
|
+
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
877
|
+
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
878
|
+
ensurePaper(paperWidth, paperHeight);
|
|
705
879
|
import_paper.default.project.activeLayer.removeChildren();
|
|
706
880
|
const shapeOriginal = getDielineShape(options);
|
|
707
881
|
let shapeOffset;
|
|
@@ -1145,7 +1319,9 @@ var DielineTool = class {
|
|
|
1145
1319
|
x: cx,
|
|
1146
1320
|
y: cy,
|
|
1147
1321
|
holes: absoluteHoles,
|
|
1148
|
-
pathData: this.pathData
|
|
1322
|
+
pathData: this.pathData,
|
|
1323
|
+
canvasWidth: canvasW,
|
|
1324
|
+
canvasHeight: canvasH
|
|
1149
1325
|
});
|
|
1150
1326
|
const insideObj = new import_fabric2.Path(productPathData, {
|
|
1151
1327
|
fill: insideColor,
|
|
@@ -1168,7 +1344,9 @@ var DielineTool = class {
|
|
|
1168
1344
|
x: cx,
|
|
1169
1345
|
y: cy,
|
|
1170
1346
|
holes: absoluteHoles,
|
|
1171
|
-
pathData: this.pathData
|
|
1347
|
+
pathData: this.pathData,
|
|
1348
|
+
canvasWidth: canvasW,
|
|
1349
|
+
canvasHeight: canvasH
|
|
1172
1350
|
},
|
|
1173
1351
|
visualOffset
|
|
1174
1352
|
);
|
|
@@ -1195,7 +1373,9 @@ var DielineTool = class {
|
|
|
1195
1373
|
x: cx,
|
|
1196
1374
|
y: cy,
|
|
1197
1375
|
holes: absoluteHoles,
|
|
1198
|
-
pathData: this.pathData
|
|
1376
|
+
pathData: this.pathData,
|
|
1377
|
+
canvasWidth: canvasW,
|
|
1378
|
+
canvasHeight: canvasH
|
|
1199
1379
|
});
|
|
1200
1380
|
const offsetBorderObj = new import_fabric2.Path(offsetPathData, {
|
|
1201
1381
|
fill: null,
|
|
@@ -1219,7 +1399,9 @@ var DielineTool = class {
|
|
|
1219
1399
|
x: cx,
|
|
1220
1400
|
y: cy,
|
|
1221
1401
|
holes: absoluteHoles,
|
|
1222
|
-
pathData: this.pathData
|
|
1402
|
+
pathData: this.pathData,
|
|
1403
|
+
canvasWidth: canvasW,
|
|
1404
|
+
canvasHeight: canvasH
|
|
1223
1405
|
});
|
|
1224
1406
|
const borderObj = new import_fabric2.Path(borderPathData, {
|
|
1225
1407
|
fill: "transparent",
|
|
@@ -1334,7 +1516,9 @@ var DielineTool = class {
|
|
|
1334
1516
|
x: cx,
|
|
1335
1517
|
y: cy,
|
|
1336
1518
|
holes: absoluteHoles,
|
|
1337
|
-
pathData: this.pathData
|
|
1519
|
+
pathData: this.pathData,
|
|
1520
|
+
canvasWidth: canvasW,
|
|
1521
|
+
canvasHeight: canvasH
|
|
1338
1522
|
});
|
|
1339
1523
|
const clonedLayer = await userLayer.clone();
|
|
1340
1524
|
const clipPath = new import_fabric2.Path(pathData, {
|
|
@@ -1627,7 +1811,7 @@ var HoleTool = class {
|
|
|
1627
1811
|
command: "addHole",
|
|
1628
1812
|
title: "Add Hole",
|
|
1629
1813
|
handler: (x, y) => {
|
|
1630
|
-
var _a, _b, _c;
|
|
1814
|
+
var _a, _b, _c, _d;
|
|
1631
1815
|
if (!this.canvasService) return false;
|
|
1632
1816
|
let normalizedX = 0.5;
|
|
1633
1817
|
let normalizedY = 0.5;
|
|
@@ -1650,9 +1834,11 @@ var HoleTool = class {
|
|
|
1650
1834
|
const lastHole = currentHoles[currentHoles.length - 1];
|
|
1651
1835
|
const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
|
|
1652
1836
|
const outerRadius = (_c = lastHole == null ? void 0 : lastHole.outerRadius) != null ? _c : 25;
|
|
1837
|
+
const shape = (_d = lastHole == null ? void 0 : lastHole.shape) != null ? _d : "circle";
|
|
1653
1838
|
const newHole = {
|
|
1654
1839
|
x: normalizedX,
|
|
1655
1840
|
y: normalizedY,
|
|
1841
|
+
shape,
|
|
1656
1842
|
innerRadius,
|
|
1657
1843
|
outerRadius
|
|
1658
1844
|
};
|
|
@@ -1684,6 +1870,7 @@ var HoleTool = class {
|
|
|
1684
1870
|
if (!this.handleDielineChange) {
|
|
1685
1871
|
this.handleDielineChange = (geometry) => {
|
|
1686
1872
|
this.currentGeometry = geometry;
|
|
1873
|
+
this.redraw();
|
|
1687
1874
|
const changed = this.enforceConstraints();
|
|
1688
1875
|
if (changed) {
|
|
1689
1876
|
this.syncHolesToDieline();
|
|
@@ -1797,10 +1984,16 @@ var HoleTool = class {
|
|
|
1797
1984
|
}
|
|
1798
1985
|
syncHolesFromCanvas() {
|
|
1799
1986
|
if (!this.canvasService) return;
|
|
1800
|
-
const objects = this.canvasService.canvas.getObjects().filter(
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1987
|
+
const objects = this.canvasService.canvas.getObjects().filter(
|
|
1988
|
+
(obj) => {
|
|
1989
|
+
var _a;
|
|
1990
|
+
return ((_a = obj.data) == null ? void 0 : _a.type) === "hole-marker" || obj.name === "hole-marker";
|
|
1991
|
+
}
|
|
1992
|
+
);
|
|
1993
|
+
if (objects.length === 0 && this.holes.length > 0) {
|
|
1994
|
+
console.warn("HoleTool: No markers found on canvas to sync from");
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1804
1997
|
objects.sort(
|
|
1805
1998
|
(a, b) => {
|
|
1806
1999
|
var _a, _b, _c, _d;
|
|
@@ -1812,6 +2005,13 @@ var HoleTool = class {
|
|
|
1812
2005
|
const original = this.holes[i];
|
|
1813
2006
|
const newAbsX = obj.left;
|
|
1814
2007
|
const newAbsY = obj.top;
|
|
2008
|
+
if (isNaN(newAbsX) || isNaN(newAbsY)) {
|
|
2009
|
+
console.error("HoleTool: Invalid marker coordinates", {
|
|
2010
|
+
newAbsX,
|
|
2011
|
+
newAbsY
|
|
2012
|
+
});
|
|
2013
|
+
return original;
|
|
2014
|
+
}
|
|
1815
2015
|
const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
|
|
1816
2016
|
const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
|
|
1817
2017
|
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
@@ -1868,7 +2068,11 @@ var HoleTool = class {
|
|
|
1868
2068
|
offsetY: (newAbsY - by) / scale / unitScale,
|
|
1869
2069
|
// Clear direct coordinates if we use anchor
|
|
1870
2070
|
x: void 0,
|
|
1871
|
-
y: void 0
|
|
2071
|
+
y: void 0,
|
|
2072
|
+
// Ensure other properties are preserved
|
|
2073
|
+
innerRadius: original.innerRadius,
|
|
2074
|
+
outerRadius: original.outerRadius,
|
|
2075
|
+
shape: original.shape || "circle"
|
|
1872
2076
|
};
|
|
1873
2077
|
}
|
|
1874
2078
|
let normalizedX = 0.5;
|
|
@@ -1888,9 +2092,13 @@ var HoleTool = class {
|
|
|
1888
2092
|
...original,
|
|
1889
2093
|
x: normalizedX,
|
|
1890
2094
|
y: normalizedY,
|
|
1891
|
-
//
|
|
2095
|
+
// Clear offsets if we are using direct normalized coordinates
|
|
2096
|
+
offsetX: void 0,
|
|
2097
|
+
offsetY: void 0,
|
|
2098
|
+
// Ensure other properties are preserved
|
|
1892
2099
|
innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
|
|
1893
|
-
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25
|
|
2100
|
+
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
|
|
2101
|
+
shape: (original == null ? void 0 : original.shape) || "circle"
|
|
1894
2102
|
};
|
|
1895
2103
|
});
|
|
1896
2104
|
this.holes = newHoles;
|
|
@@ -1948,7 +2156,16 @@ var HoleTool = class {
|
|
|
1948
2156
|
{ width: geometry.width, height: geometry.height }
|
|
1949
2157
|
// Use geometry dims instead of canvas
|
|
1950
2158
|
);
|
|
1951
|
-
const
|
|
2159
|
+
const isSquare = hole.shape === "square";
|
|
2160
|
+
const innerMarker = isSquare ? new import_fabric4.Rect({
|
|
2161
|
+
width: visualInnerRadius * 2,
|
|
2162
|
+
height: visualInnerRadius * 2,
|
|
2163
|
+
fill: "transparent",
|
|
2164
|
+
stroke: "red",
|
|
2165
|
+
strokeWidth: 2,
|
|
2166
|
+
originX: "center",
|
|
2167
|
+
originY: "center"
|
|
2168
|
+
}) : new import_fabric4.Circle({
|
|
1952
2169
|
radius: visualInnerRadius,
|
|
1953
2170
|
fill: "transparent",
|
|
1954
2171
|
stroke: "red",
|
|
@@ -1956,7 +2173,16 @@ var HoleTool = class {
|
|
|
1956
2173
|
originX: "center",
|
|
1957
2174
|
originY: "center"
|
|
1958
2175
|
});
|
|
1959
|
-
const
|
|
2176
|
+
const outerMarker = isSquare ? new import_fabric4.Rect({
|
|
2177
|
+
width: visualOuterRadius * 2,
|
|
2178
|
+
height: visualOuterRadius * 2,
|
|
2179
|
+
fill: "transparent",
|
|
2180
|
+
stroke: "#666",
|
|
2181
|
+
strokeWidth: 1,
|
|
2182
|
+
strokeDashArray: [5, 5],
|
|
2183
|
+
originX: "center",
|
|
2184
|
+
originY: "center"
|
|
2185
|
+
}) : new import_fabric4.Circle({
|
|
1960
2186
|
radius: visualOuterRadius,
|
|
1961
2187
|
fill: "transparent",
|
|
1962
2188
|
stroke: "#666",
|
|
@@ -1965,7 +2191,7 @@ var HoleTool = class {
|
|
|
1965
2191
|
originX: "center",
|
|
1966
2192
|
originY: "center"
|
|
1967
2193
|
});
|
|
1968
|
-
const holeGroup = new import_fabric4.Group([
|
|
2194
|
+
const holeGroup = new import_fabric4.Group([outerMarker, innerMarker], {
|
|
1969
2195
|
left: pos.x,
|
|
1970
2196
|
top: pos.y,
|
|
1971
2197
|
originX: "center",
|
|
@@ -2107,6 +2333,7 @@ var ImageTool = class {
|
|
|
2107
2333
|
};
|
|
2108
2334
|
this.items = [];
|
|
2109
2335
|
this.objectMap = /* @__PURE__ */ new Map();
|
|
2336
|
+
this.loadResolvers = /* @__PURE__ */ new Map();
|
|
2110
2337
|
this.isUpdatingConfig = false;
|
|
2111
2338
|
}
|
|
2112
2339
|
activate(context) {
|
|
@@ -2116,19 +2343,15 @@ var ImageTool = class {
|
|
|
2116
2343
|
console.warn("CanvasService not found for ImageTool");
|
|
2117
2344
|
return;
|
|
2118
2345
|
}
|
|
2119
|
-
const configService = context.services.get(
|
|
2346
|
+
const configService = context.services.get(
|
|
2347
|
+
"ConfigurationService"
|
|
2348
|
+
);
|
|
2120
2349
|
if (configService) {
|
|
2121
2350
|
this.items = configService.get("image.items", []) || [];
|
|
2122
2351
|
configService.onAnyChange((e) => {
|
|
2123
2352
|
if (this.isUpdatingConfig) return;
|
|
2124
|
-
let shouldUpdate = false;
|
|
2125
2353
|
if (e.key === "image.items") {
|
|
2126
2354
|
this.items = e.value || [];
|
|
2127
|
-
shouldUpdate = true;
|
|
2128
|
-
} else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
|
|
2129
|
-
shouldUpdate = true;
|
|
2130
|
-
}
|
|
2131
|
-
if (shouldUpdate) {
|
|
2132
2355
|
this.updateImages();
|
|
2133
2356
|
}
|
|
2134
2357
|
});
|
|
@@ -2164,15 +2387,39 @@ var ImageTool = class {
|
|
|
2164
2387
|
{
|
|
2165
2388
|
command: "addImage",
|
|
2166
2389
|
title: "Add Image",
|
|
2167
|
-
handler: (url, options) => {
|
|
2390
|
+
handler: async (url, options) => {
|
|
2391
|
+
const id = this.generateId();
|
|
2168
2392
|
const newItem = {
|
|
2169
|
-
id
|
|
2393
|
+
id,
|
|
2170
2394
|
url,
|
|
2171
2395
|
opacity: 1,
|
|
2172
2396
|
...options
|
|
2173
2397
|
};
|
|
2398
|
+
const promise = new Promise((resolve) => {
|
|
2399
|
+
this.loadResolvers.set(id, () => resolve(id));
|
|
2400
|
+
});
|
|
2174
2401
|
this.updateConfig([...this.items, newItem]);
|
|
2175
|
-
return
|
|
2402
|
+
return promise;
|
|
2403
|
+
}
|
|
2404
|
+
},
|
|
2405
|
+
{
|
|
2406
|
+
command: "fitImageToArea",
|
|
2407
|
+
title: "Fit Image to Area",
|
|
2408
|
+
handler: (id, area) => {
|
|
2409
|
+
var _a, _b;
|
|
2410
|
+
const item = this.items.find((i) => i.id === id);
|
|
2411
|
+
const obj = this.objectMap.get(id);
|
|
2412
|
+
if (item && obj && obj.width && obj.height) {
|
|
2413
|
+
const scale = Math.max(
|
|
2414
|
+
area.width / obj.width,
|
|
2415
|
+
area.height / obj.height
|
|
2416
|
+
);
|
|
2417
|
+
this.updateImageInConfig(id, {
|
|
2418
|
+
scale,
|
|
2419
|
+
left: (_a = area.left) != null ? _a : 0.5,
|
|
2420
|
+
top: (_b = area.top) != null ? _b : 0.5
|
|
2421
|
+
});
|
|
2422
|
+
}
|
|
2176
2423
|
}
|
|
2177
2424
|
},
|
|
2178
2425
|
{
|
|
@@ -2240,7 +2487,9 @@ var ImageTool = class {
|
|
|
2240
2487
|
if (!this.context) return;
|
|
2241
2488
|
this.isUpdatingConfig = true;
|
|
2242
2489
|
this.items = newItems;
|
|
2243
|
-
const configService = this.context.services.get(
|
|
2490
|
+
const configService = this.context.services.get(
|
|
2491
|
+
"ConfigurationService"
|
|
2492
|
+
);
|
|
2244
2493
|
if (configService) {
|
|
2245
2494
|
configService.update("image.items", newItems);
|
|
2246
2495
|
}
|
|
@@ -2286,39 +2535,12 @@ var ImageTool = class {
|
|
|
2286
2535
|
var _a, _b;
|
|
2287
2536
|
const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
|
|
2288
2537
|
const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
|
|
2289
|
-
let layoutScale = 1;
|
|
2290
|
-
let layoutOffsetX = 0;
|
|
2291
|
-
let layoutOffsetY = 0;
|
|
2292
|
-
let visualWidth = canvasW;
|
|
2293
|
-
let visualHeight = canvasH;
|
|
2294
|
-
let dielinePhysicalWidth = 500;
|
|
2295
|
-
let dielinePhysicalHeight = 500;
|
|
2296
|
-
if (this.context) {
|
|
2297
|
-
const configService = this.context.services.get("ConfigurationService");
|
|
2298
|
-
if (configService) {
|
|
2299
|
-
dielinePhysicalWidth = configService.get("dieline.width") || 500;
|
|
2300
|
-
dielinePhysicalHeight = configService.get("dieline.height") || 500;
|
|
2301
|
-
const padding = configService.get("dieline.padding") || 40;
|
|
2302
|
-
const layout = Coordinate.calculateLayout(
|
|
2303
|
-
{ width: canvasW, height: canvasH },
|
|
2304
|
-
{ width: dielinePhysicalWidth, height: dielinePhysicalHeight },
|
|
2305
|
-
padding
|
|
2306
|
-
);
|
|
2307
|
-
layoutScale = layout.scale;
|
|
2308
|
-
layoutOffsetX = layout.offsetX;
|
|
2309
|
-
layoutOffsetY = layout.offsetY;
|
|
2310
|
-
visualWidth = layout.width;
|
|
2311
|
-
visualHeight = layout.height;
|
|
2312
|
-
}
|
|
2313
|
-
}
|
|
2314
2538
|
return {
|
|
2315
|
-
layoutScale,
|
|
2316
|
-
layoutOffsetX,
|
|
2317
|
-
layoutOffsetY,
|
|
2318
|
-
visualWidth,
|
|
2319
|
-
visualHeight
|
|
2320
|
-
dielinePhysicalWidth,
|
|
2321
|
-
dielinePhysicalHeight
|
|
2539
|
+
layoutScale: 1,
|
|
2540
|
+
layoutOffsetX: 0,
|
|
2541
|
+
layoutOffsetY: 0,
|
|
2542
|
+
visualWidth: canvasW,
|
|
2543
|
+
visualHeight: canvasH
|
|
2322
2544
|
};
|
|
2323
2545
|
}
|
|
2324
2546
|
updateImages() {
|
|
@@ -2341,8 +2563,8 @@ var ImageTool = class {
|
|
|
2341
2563
|
if (!obj) {
|
|
2342
2564
|
this.loadImage(item, layer, layout);
|
|
2343
2565
|
} else {
|
|
2344
|
-
this.updateObjectProperties(obj, item, layout);
|
|
2345
2566
|
layer.remove(obj);
|
|
2567
|
+
this.updateObjectProperties(obj, item, layout);
|
|
2346
2568
|
layer.add(obj);
|
|
2347
2569
|
}
|
|
2348
2570
|
});
|
|
@@ -2350,10 +2572,17 @@ var ImageTool = class {
|
|
|
2350
2572
|
this.canvasService.requestRenderAll();
|
|
2351
2573
|
}
|
|
2352
2574
|
updateObjectProperties(obj, item, layout) {
|
|
2353
|
-
const {
|
|
2575
|
+
const {
|
|
2576
|
+
layoutScale,
|
|
2577
|
+
layoutOffsetX,
|
|
2578
|
+
layoutOffsetY,
|
|
2579
|
+
visualWidth,
|
|
2580
|
+
visualHeight
|
|
2581
|
+
} = layout;
|
|
2354
2582
|
const updates = {};
|
|
2355
2583
|
if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
|
|
2356
|
-
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2584
|
+
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2585
|
+
updates.angle = item.angle;
|
|
2357
2586
|
if (item.left !== void 0) {
|
|
2358
2587
|
const globalLeft = layoutOffsetX + item.left * visualWidth;
|
|
2359
2588
|
if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
|
|
@@ -2362,13 +2591,12 @@ var ImageTool = class {
|
|
|
2362
2591
|
const globalTop = layoutOffsetY + item.top * visualHeight;
|
|
2363
2592
|
if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
|
|
2364
2593
|
}
|
|
2365
|
-
if (item.
|
|
2366
|
-
const
|
|
2367
|
-
if (Math.abs(obj.scaleX -
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
|
|
2594
|
+
if (item.scale !== void 0) {
|
|
2595
|
+
const targetScale = item.scale * layoutScale;
|
|
2596
|
+
if (Math.abs(obj.scaleX - targetScale) > 1e-3) {
|
|
2597
|
+
updates.scaleX = targetScale;
|
|
2598
|
+
updates.scaleY = targetScale;
|
|
2599
|
+
}
|
|
2372
2600
|
}
|
|
2373
2601
|
if (obj.originX !== "center") {
|
|
2374
2602
|
updates.originX = "center";
|
|
@@ -2376,6 +2604,7 @@ var ImageTool = class {
|
|
|
2376
2604
|
}
|
|
2377
2605
|
if (Object.keys(updates).length > 0) {
|
|
2378
2606
|
obj.set(updates);
|
|
2607
|
+
obj.setCoords();
|
|
2379
2608
|
}
|
|
2380
2609
|
}
|
|
2381
2610
|
loadImage(item, layer, layout) {
|
|
@@ -2385,24 +2614,20 @@ var ImageTool = class {
|
|
|
2385
2614
|
image.set({
|
|
2386
2615
|
originX: "center",
|
|
2387
2616
|
originY: "center",
|
|
2388
|
-
data: { id: item.id }
|
|
2617
|
+
data: { id: item.id },
|
|
2618
|
+
uniformScaling: true,
|
|
2619
|
+
lockScalingFlip: true
|
|
2389
2620
|
});
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
const h = dielinePhysicalHeight;
|
|
2401
|
-
height = h;
|
|
2402
|
-
width = h * imgAspect;
|
|
2403
|
-
}
|
|
2404
|
-
item.width = width;
|
|
2405
|
-
item.height = height;
|
|
2621
|
+
image.setControlsVisibility({
|
|
2622
|
+
mt: false,
|
|
2623
|
+
mb: false,
|
|
2624
|
+
ml: false,
|
|
2625
|
+
mr: false
|
|
2626
|
+
});
|
|
2627
|
+
let { scale, left, top } = item;
|
|
2628
|
+
if (scale === void 0) {
|
|
2629
|
+
scale = 1;
|
|
2630
|
+
item.scale = scale;
|
|
2406
2631
|
}
|
|
2407
2632
|
if (left === void 0 && top === void 0) {
|
|
2408
2633
|
left = 0.5;
|
|
@@ -2413,13 +2638,18 @@ var ImageTool = class {
|
|
|
2413
2638
|
this.updateObjectProperties(image, item, layout);
|
|
2414
2639
|
layer.add(image);
|
|
2415
2640
|
this.objectMap.set(item.id, image);
|
|
2641
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
2642
|
+
if (resolver) {
|
|
2643
|
+
resolver();
|
|
2644
|
+
this.loadResolvers.delete(item.id);
|
|
2645
|
+
}
|
|
2416
2646
|
image.on("modified", (e) => {
|
|
2417
2647
|
this.handleObjectModified(item.id, image);
|
|
2418
2648
|
});
|
|
2419
2649
|
layer.dirty = true;
|
|
2420
2650
|
(_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
|
|
2421
|
-
if (item.
|
|
2422
|
-
this.updateImageInConfig(item.id, {
|
|
2651
|
+
if (item.scale !== scale || item.left !== left || item.top !== top) {
|
|
2652
|
+
this.updateImageInConfig(item.id, { scale, left, top }, true);
|
|
2423
2653
|
}
|
|
2424
2654
|
}).catch((err) => {
|
|
2425
2655
|
console.error("Failed to load image", item.url, err);
|
|
@@ -2427,29 +2657,28 @@ var ImageTool = class {
|
|
|
2427
2657
|
}
|
|
2428
2658
|
handleObjectModified(id, image) {
|
|
2429
2659
|
const layout = this.getLayoutInfo();
|
|
2430
|
-
const {
|
|
2660
|
+
const {
|
|
2661
|
+
layoutScale,
|
|
2662
|
+
layoutOffsetX,
|
|
2663
|
+
layoutOffsetY,
|
|
2664
|
+
visualWidth,
|
|
2665
|
+
visualHeight
|
|
2666
|
+
} = layout;
|
|
2431
2667
|
const matrix = image.calcTransformMatrix();
|
|
2432
2668
|
const globalPoint = import_fabric5.util.transformPoint(new import_fabric5.Point(0, 0), matrix);
|
|
2433
2669
|
const updates = {};
|
|
2434
2670
|
updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
|
|
2435
2671
|
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
2436
2672
|
updates.angle = image.angle;
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
updates.width = pixelWidth / layoutScale;
|
|
2440
|
-
}
|
|
2441
|
-
if (image.height) {
|
|
2442
|
-
const pixelHeight = image.height * image.scaleY;
|
|
2443
|
-
updates.height = pixelHeight / layoutScale;
|
|
2444
|
-
}
|
|
2445
|
-
this.updateImageInConfig(id, updates);
|
|
2673
|
+
updates.scale = image.scaleX / layoutScale;
|
|
2674
|
+
this.updateImageInConfig(id, updates, true);
|
|
2446
2675
|
}
|
|
2447
|
-
updateImageInConfig(id, updates) {
|
|
2676
|
+
updateImageInConfig(id, updates, skipCanvasUpdate = false) {
|
|
2448
2677
|
const index = this.items.findIndex((i) => i.id === id);
|
|
2449
2678
|
if (index !== -1) {
|
|
2450
2679
|
const newItems = [...this.items];
|
|
2451
2680
|
newItems[index] = { ...newItems[index], ...updates };
|
|
2452
|
-
this.updateConfig(newItems,
|
|
2681
|
+
this.updateConfig(newItems, skipCanvasUpdate);
|
|
2453
2682
|
}
|
|
2454
2683
|
}
|
|
2455
2684
|
};
|