@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.mjs
CHANGED
|
@@ -215,7 +215,7 @@ var ImageTracer = class {
|
|
|
215
215
|
* @param options Configuration options.
|
|
216
216
|
*/
|
|
217
217
|
static async trace(imageUrl, options = {}) {
|
|
218
|
-
var _a, _b;
|
|
218
|
+
var _a, _b, _c, _d, _e;
|
|
219
219
|
const img = await this.loadImage(imageUrl);
|
|
220
220
|
const width = img.width;
|
|
221
221
|
const height = img.height;
|
|
@@ -226,21 +226,213 @@ var ImageTracer = class {
|
|
|
226
226
|
if (!ctx) throw new Error("Could not get 2D context");
|
|
227
227
|
ctx.drawImage(img, 0, 0);
|
|
228
228
|
const imageData = ctx.getImageData(0, 0, width, height);
|
|
229
|
-
const
|
|
230
|
-
|
|
231
|
-
|
|
229
|
+
const threshold = (_a = options.threshold) != null ? _a : 10;
|
|
230
|
+
const adaptiveRadius = Math.max(
|
|
231
|
+
5,
|
|
232
|
+
Math.floor(Math.max(width, height) * 0.02)
|
|
233
|
+
);
|
|
234
|
+
const radius = (_b = options.morphologyRadius) != null ? _b : adaptiveRadius;
|
|
235
|
+
let mask = this.createMask(imageData, threshold);
|
|
236
|
+
if (radius > 0) {
|
|
237
|
+
mask = this.dilate(mask, width, height, radius);
|
|
238
|
+
mask = this.erode(mask, width, height, radius);
|
|
239
|
+
mask = this.fillHoles(mask, width, height);
|
|
240
|
+
}
|
|
241
|
+
const allContourPoints = this.traceAllContours(mask, width, height);
|
|
242
|
+
if (allContourPoints.length === 0) {
|
|
243
|
+
const w = (_c = options.scaleToWidth) != null ? _c : width;
|
|
244
|
+
const h = (_d = options.scaleToHeight) != null ? _d : height;
|
|
245
|
+
return `M 0 0 L ${w} 0 L ${w} ${h} L 0 ${h} Z`;
|
|
246
|
+
}
|
|
247
|
+
const primaryContour = allContourPoints.sort(
|
|
248
|
+
(a, b) => b.length - a.length
|
|
249
|
+
)[0];
|
|
250
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
251
|
+
for (const p of primaryContour) {
|
|
252
|
+
if (p.x < minX) minX = p.x;
|
|
253
|
+
if (p.y < minY) minY = p.y;
|
|
254
|
+
if (p.x > maxX) maxX = p.x;
|
|
255
|
+
if (p.y > maxY) maxY = p.y;
|
|
256
|
+
}
|
|
257
|
+
const globalBounds = {
|
|
258
|
+
minX,
|
|
259
|
+
minY,
|
|
260
|
+
width: maxX - minX,
|
|
261
|
+
height: maxY - minY
|
|
262
|
+
};
|
|
263
|
+
let finalPoints = primaryContour;
|
|
264
|
+
if (options.scaleToWidth && options.scaleToHeight) {
|
|
232
265
|
finalPoints = this.scalePoints(
|
|
233
|
-
|
|
266
|
+
primaryContour,
|
|
234
267
|
options.scaleToWidth,
|
|
235
|
-
options.scaleToHeight
|
|
268
|
+
options.scaleToHeight,
|
|
269
|
+
globalBounds
|
|
236
270
|
);
|
|
237
271
|
}
|
|
238
272
|
const simplifiedPoints = this.douglasPeucker(
|
|
239
273
|
finalPoints,
|
|
240
|
-
(
|
|
274
|
+
(_e = options.simplifyTolerance) != null ? _e : 2
|
|
241
275
|
);
|
|
242
276
|
return this.pointsToSVG(simplifiedPoints);
|
|
243
277
|
}
|
|
278
|
+
static createMask(imageData, threshold) {
|
|
279
|
+
const { width, height, data } = imageData;
|
|
280
|
+
const mask = new Uint8Array(width * height);
|
|
281
|
+
for (let i = 0; i < width * height; i++) {
|
|
282
|
+
const idx = i * 4;
|
|
283
|
+
const r = data[idx];
|
|
284
|
+
const g = data[idx + 1];
|
|
285
|
+
const b = data[idx + 2];
|
|
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;
|
|
291
|
+
}
|
|
292
|
+
}
|
|
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
|
+
for (let y = 0; y < height; y++) {
|
|
301
|
+
let count = 0;
|
|
302
|
+
for (let x = -radius; x < width; x++) {
|
|
303
|
+
if (x + radius < width && mask[y * width + x + radius]) count++;
|
|
304
|
+
if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
|
|
305
|
+
if (x >= 0) horizontal[y * width + x] = count > 0 ? 1 : 0;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const vertical = new Uint8Array(width * height);
|
|
309
|
+
for (let x = 0; x < width; x++) {
|
|
310
|
+
let count = 0;
|
|
311
|
+
for (let y = -radius; y < height; y++) {
|
|
312
|
+
if (y + radius < height && horizontal[(y + radius) * width + x])
|
|
313
|
+
count++;
|
|
314
|
+
if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
|
|
315
|
+
count--;
|
|
316
|
+
if (y >= 0) vertical[y * width + x] = count > 0 ? 1 : 0;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return vertical;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Fast 1D-separable Erosion
|
|
323
|
+
*/
|
|
324
|
+
static erode(mask, width, height, radius) {
|
|
325
|
+
const horizontal = new Uint8Array(width * height);
|
|
326
|
+
for (let y = 0; y < height; y++) {
|
|
327
|
+
let count = 0;
|
|
328
|
+
for (let x = -radius; x < width; x++) {
|
|
329
|
+
if (x + radius < width && mask[y * width + x + radius]) count++;
|
|
330
|
+
if (x - radius - 1 >= 0 && mask[y * width + x - radius - 1]) count--;
|
|
331
|
+
if (x >= 0) {
|
|
332
|
+
const winWidth = Math.min(x + radius, width - 1) - Math.max(x - radius, 0) + 1;
|
|
333
|
+
horizontal[y * width + x] = count === winWidth ? 1 : 0;
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
const vertical = new Uint8Array(width * height);
|
|
338
|
+
for (let x = 0; x < width; x++) {
|
|
339
|
+
let count = 0;
|
|
340
|
+
for (let y = -radius; y < height; y++) {
|
|
341
|
+
if (y + radius < height && horizontal[(y + radius) * width + x])
|
|
342
|
+
count++;
|
|
343
|
+
if (y - radius - 1 >= 0 && horizontal[(y - radius - 1) * width + x])
|
|
344
|
+
count--;
|
|
345
|
+
if (y >= 0) {
|
|
346
|
+
const winHeight = Math.min(y + radius, height - 1) - Math.max(y - radius, 0) + 1;
|
|
347
|
+
vertical[y * width + x] = count === winHeight ? 1 : 0;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
return vertical;
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Fills internal holes in the binary mask using flood fill from edges.
|
|
355
|
+
*/
|
|
356
|
+
static fillHoles(mask, width, height) {
|
|
357
|
+
const background = new Uint8Array(width * height);
|
|
358
|
+
const queue = [];
|
|
359
|
+
for (let x = 0; x < width; x++) {
|
|
360
|
+
if (mask[x] === 0) {
|
|
361
|
+
background[x] = 1;
|
|
362
|
+
queue.push([x, 0]);
|
|
363
|
+
}
|
|
364
|
+
const lastRow = (height - 1) * width + x;
|
|
365
|
+
if (mask[lastRow] === 0) {
|
|
366
|
+
background[lastRow] = 1;
|
|
367
|
+
queue.push([x, height - 1]);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
for (let y = 1; y < height - 1; y++) {
|
|
371
|
+
if (mask[y * width] === 0) {
|
|
372
|
+
background[y * width] = 1;
|
|
373
|
+
queue.push([0, y]);
|
|
374
|
+
}
|
|
375
|
+
if (mask[y * width + width - 1] === 0) {
|
|
376
|
+
background[y * width + width - 1] = 1;
|
|
377
|
+
queue.push([width - 1, y]);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
const dirs = [
|
|
381
|
+
[0, 1],
|
|
382
|
+
[0, -1],
|
|
383
|
+
[1, 0],
|
|
384
|
+
[-1, 0]
|
|
385
|
+
];
|
|
386
|
+
let head = 0;
|
|
387
|
+
while (head < queue.length) {
|
|
388
|
+
const [cx, cy] = queue[head++];
|
|
389
|
+
for (const [dx, dy] of dirs) {
|
|
390
|
+
const nx = cx + dx;
|
|
391
|
+
const ny = cy + dy;
|
|
392
|
+
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
|
|
393
|
+
const nidx = ny * width + nx;
|
|
394
|
+
if (mask[nidx] === 0 && background[nidx] === 0) {
|
|
395
|
+
background[nidx] = 1;
|
|
396
|
+
queue.push([nx, ny]);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
const filledMask = new Uint8Array(width * height);
|
|
402
|
+
for (let i = 0; i < width * height; i++) {
|
|
403
|
+
filledMask[i] = background[i] === 0 ? 1 : 0;
|
|
404
|
+
}
|
|
405
|
+
return filledMask;
|
|
406
|
+
}
|
|
407
|
+
/**
|
|
408
|
+
* Traces all contours in the mask with optimized start-point detection
|
|
409
|
+
*/
|
|
410
|
+
static traceAllContours(mask, width, height) {
|
|
411
|
+
const visited = new Uint8Array(width * height);
|
|
412
|
+
const allContours = [];
|
|
413
|
+
for (let y = 0; y < height; y++) {
|
|
414
|
+
for (let x = 0; x < width; x++) {
|
|
415
|
+
const idx = y * width + x;
|
|
416
|
+
if (mask[idx] && !visited[idx]) {
|
|
417
|
+
const isLeftEdge = x === 0 || mask[idx - 1] === 0;
|
|
418
|
+
if (isLeftEdge) {
|
|
419
|
+
const contour = this.marchingSquares(
|
|
420
|
+
mask,
|
|
421
|
+
visited,
|
|
422
|
+
x,
|
|
423
|
+
y,
|
|
424
|
+
width,
|
|
425
|
+
height
|
|
426
|
+
);
|
|
427
|
+
if (contour.length > 2) {
|
|
428
|
+
allContours.push(contour);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return allContours;
|
|
435
|
+
}
|
|
244
436
|
static loadImage(url) {
|
|
245
437
|
return new Promise((resolve, reject) => {
|
|
246
438
|
const img = new Image();
|
|
@@ -254,33 +446,11 @@ var ImageTracer = class {
|
|
|
254
446
|
* Moore-Neighbor Tracing Algorithm
|
|
255
447
|
* More robust for irregular shapes than simple Marching Squares walker.
|
|
256
448
|
*/
|
|
257
|
-
static marchingSquares(
|
|
258
|
-
const width = imageData.width;
|
|
259
|
-
const height = imageData.height;
|
|
260
|
-
const data = imageData.data;
|
|
449
|
+
static marchingSquares(mask, visited, startX, startY, width, height) {
|
|
261
450
|
const isSolid = (x, y) => {
|
|
262
451
|
if (x < 0 || x >= width || y < 0 || y >= height) return false;
|
|
263
|
-
|
|
264
|
-
const r = data[index];
|
|
265
|
-
const g = data[index + 1];
|
|
266
|
-
const b = data[index + 2];
|
|
267
|
-
const a = data[index + 3];
|
|
268
|
-
if (a <= alphaThreshold) return false;
|
|
269
|
-
if (r > 240 && g > 240 && b > 240) return false;
|
|
270
|
-
return true;
|
|
452
|
+
return mask[y * width + x] === 1;
|
|
271
453
|
};
|
|
272
|
-
let startX = -1;
|
|
273
|
-
let startY = -1;
|
|
274
|
-
searchLoop: for (let y = 0; y < height; y++) {
|
|
275
|
-
for (let x = 0; x < width; x++) {
|
|
276
|
-
if (isSolid(x, y)) {
|
|
277
|
-
startX = x;
|
|
278
|
-
startY = y;
|
|
279
|
-
break searchLoop;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
if (startX === -1) return [];
|
|
284
454
|
const points = [];
|
|
285
455
|
let cx = startX;
|
|
286
456
|
let cy = startY;
|
|
@@ -299,6 +469,7 @@ var ImageTracer = class {
|
|
|
299
469
|
let steps = 0;
|
|
300
470
|
do {
|
|
301
471
|
points.push({ x: cx, y: cy });
|
|
472
|
+
visited[cy * width + cx] = 1;
|
|
302
473
|
let found = false;
|
|
303
474
|
for (let i = 0; i < 8; i++) {
|
|
304
475
|
const idx = (backtrack + 1 + i) % 8;
|
|
@@ -307,16 +478,12 @@ var ImageTracer = class {
|
|
|
307
478
|
if (isSolid(nx, ny)) {
|
|
308
479
|
cx = nx;
|
|
309
480
|
cy = ny;
|
|
310
|
-
backtrack = (idx + 4) % 8;
|
|
311
|
-
backtrack = (idx + 4 + 1) % 8;
|
|
312
481
|
backtrack = (idx + 4 + 1) % 8;
|
|
313
482
|
found = true;
|
|
314
483
|
break;
|
|
315
484
|
}
|
|
316
485
|
}
|
|
317
|
-
if (!found)
|
|
318
|
-
break;
|
|
319
|
-
}
|
|
486
|
+
if (!found) break;
|
|
320
487
|
steps++;
|
|
321
488
|
} while ((cx !== startX || cy !== startY) && steps < maxSteps);
|
|
322
489
|
return points;
|
|
@@ -365,23 +532,14 @@ var ImageTracer = class {
|
|
|
365
532
|
dy = p.y - y;
|
|
366
533
|
return dx * dx + dy * dy;
|
|
367
534
|
}
|
|
368
|
-
static scalePoints(points, targetWidth, targetHeight) {
|
|
535
|
+
static scalePoints(points, targetWidth, targetHeight, bounds) {
|
|
369
536
|
if (points.length === 0) return points;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
if (p.y < minY) minY = p.y;
|
|
374
|
-
if (p.x > maxX) maxX = p.x;
|
|
375
|
-
if (p.y > maxY) maxY = p.y;
|
|
376
|
-
}
|
|
377
|
-
const srcW = maxX - minX;
|
|
378
|
-
const srcH = maxY - minY;
|
|
379
|
-
if (srcW === 0 || srcH === 0) return points;
|
|
380
|
-
const scaleX = targetWidth / srcW;
|
|
381
|
-
const scaleY = targetHeight / srcH;
|
|
537
|
+
if (bounds.width === 0 || bounds.height === 0) return points;
|
|
538
|
+
const scaleX = targetWidth / bounds.width;
|
|
539
|
+
const scaleY = targetHeight / bounds.height;
|
|
382
540
|
return points.map((p) => ({
|
|
383
|
-
x: (p.x - minX) * scaleX,
|
|
384
|
-
y: (p.y - minY) * scaleY
|
|
541
|
+
x: (p.x - bounds.minX) * scaleX,
|
|
542
|
+
y: (p.y - bounds.minY) * scaleY
|
|
385
543
|
}));
|
|
386
544
|
}
|
|
387
545
|
static pointsToSVG(points) {
|
|
@@ -574,12 +732,25 @@ function getDielineShape(options) {
|
|
|
574
732
|
let lugsPath = null;
|
|
575
733
|
let cutsPath = null;
|
|
576
734
|
holes.forEach((hole) => {
|
|
577
|
-
const
|
|
578
|
-
|
|
735
|
+
const center = new paper.Point(hole.x, hole.y);
|
|
736
|
+
const lug = hole.shape === "square" ? new paper.Path.Rectangle({
|
|
737
|
+
point: [
|
|
738
|
+
center.x - hole.outerRadius,
|
|
739
|
+
center.y - hole.outerRadius
|
|
740
|
+
],
|
|
741
|
+
size: [hole.outerRadius * 2, hole.outerRadius * 2]
|
|
742
|
+
}) : new paper.Path.Circle({
|
|
743
|
+
center,
|
|
579
744
|
radius: hole.outerRadius
|
|
580
745
|
});
|
|
581
|
-
const cut = new paper.Path.
|
|
582
|
-
|
|
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,
|
|
583
754
|
radius: hole.innerRadius
|
|
584
755
|
});
|
|
585
756
|
if (!lugsPath) {
|
|
@@ -636,7 +807,9 @@ function getDielineShape(options) {
|
|
|
636
807
|
return mainShape;
|
|
637
808
|
}
|
|
638
809
|
function generateDielinePath(options) {
|
|
639
|
-
|
|
810
|
+
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
811
|
+
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
812
|
+
ensurePaper(paperWidth, paperHeight);
|
|
640
813
|
paper.project.activeLayer.removeChildren();
|
|
641
814
|
const mainShape = getDielineShape(options);
|
|
642
815
|
const pathData = mainShape.pathData;
|
|
@@ -660,8 +833,9 @@ function generateMaskPath(options) {
|
|
|
660
833
|
return pathData;
|
|
661
834
|
}
|
|
662
835
|
function generateBleedZonePath(options, offset) {
|
|
663
|
-
const
|
|
664
|
-
|
|
836
|
+
const paperWidth = options.canvasWidth || options.width * 2 || 2e3;
|
|
837
|
+
const paperHeight = options.canvasHeight || options.height * 2 || 2e3;
|
|
838
|
+
ensurePaper(paperWidth, paperHeight);
|
|
665
839
|
paper.project.activeLayer.removeChildren();
|
|
666
840
|
const shapeOriginal = getDielineShape(options);
|
|
667
841
|
let shapeOffset;
|
|
@@ -1105,7 +1279,9 @@ var DielineTool = class {
|
|
|
1105
1279
|
x: cx,
|
|
1106
1280
|
y: cy,
|
|
1107
1281
|
holes: absoluteHoles,
|
|
1108
|
-
pathData: this.pathData
|
|
1282
|
+
pathData: this.pathData,
|
|
1283
|
+
canvasWidth: canvasW,
|
|
1284
|
+
canvasHeight: canvasH
|
|
1109
1285
|
});
|
|
1110
1286
|
const insideObj = new Path(productPathData, {
|
|
1111
1287
|
fill: insideColor,
|
|
@@ -1128,7 +1304,9 @@ var DielineTool = class {
|
|
|
1128
1304
|
x: cx,
|
|
1129
1305
|
y: cy,
|
|
1130
1306
|
holes: absoluteHoles,
|
|
1131
|
-
pathData: this.pathData
|
|
1307
|
+
pathData: this.pathData,
|
|
1308
|
+
canvasWidth: canvasW,
|
|
1309
|
+
canvasHeight: canvasH
|
|
1132
1310
|
},
|
|
1133
1311
|
visualOffset
|
|
1134
1312
|
);
|
|
@@ -1155,7 +1333,9 @@ var DielineTool = class {
|
|
|
1155
1333
|
x: cx,
|
|
1156
1334
|
y: cy,
|
|
1157
1335
|
holes: absoluteHoles,
|
|
1158
|
-
pathData: this.pathData
|
|
1336
|
+
pathData: this.pathData,
|
|
1337
|
+
canvasWidth: canvasW,
|
|
1338
|
+
canvasHeight: canvasH
|
|
1159
1339
|
});
|
|
1160
1340
|
const offsetBorderObj = new Path(offsetPathData, {
|
|
1161
1341
|
fill: null,
|
|
@@ -1179,7 +1359,9 @@ var DielineTool = class {
|
|
|
1179
1359
|
x: cx,
|
|
1180
1360
|
y: cy,
|
|
1181
1361
|
holes: absoluteHoles,
|
|
1182
|
-
pathData: this.pathData
|
|
1362
|
+
pathData: this.pathData,
|
|
1363
|
+
canvasWidth: canvasW,
|
|
1364
|
+
canvasHeight: canvasH
|
|
1183
1365
|
});
|
|
1184
1366
|
const borderObj = new Path(borderPathData, {
|
|
1185
1367
|
fill: "transparent",
|
|
@@ -1294,7 +1476,9 @@ var DielineTool = class {
|
|
|
1294
1476
|
x: cx,
|
|
1295
1477
|
y: cy,
|
|
1296
1478
|
holes: absoluteHoles,
|
|
1297
|
-
pathData: this.pathData
|
|
1479
|
+
pathData: this.pathData,
|
|
1480
|
+
canvasWidth: canvasW,
|
|
1481
|
+
canvasHeight: canvasH
|
|
1298
1482
|
});
|
|
1299
1483
|
const clonedLayer = await userLayer.clone();
|
|
1300
1484
|
const clipPath = new Path(pathData, {
|
|
@@ -1485,7 +1669,7 @@ var FilmTool = class {
|
|
|
1485
1669
|
import {
|
|
1486
1670
|
ContributionPointIds as ContributionPointIds4
|
|
1487
1671
|
} from "@pooder/core";
|
|
1488
|
-
import { Circle, Group, Point } from "fabric";
|
|
1672
|
+
import { Circle, Group, Point, Rect as Rect2 } from "fabric";
|
|
1489
1673
|
var HoleTool = class {
|
|
1490
1674
|
constructor(options) {
|
|
1491
1675
|
this.id = "pooder.kit.hole";
|
|
@@ -1591,7 +1775,7 @@ var HoleTool = class {
|
|
|
1591
1775
|
command: "addHole",
|
|
1592
1776
|
title: "Add Hole",
|
|
1593
1777
|
handler: (x, y) => {
|
|
1594
|
-
var _a, _b, _c;
|
|
1778
|
+
var _a, _b, _c, _d;
|
|
1595
1779
|
if (!this.canvasService) return false;
|
|
1596
1780
|
let normalizedX = 0.5;
|
|
1597
1781
|
let normalizedY = 0.5;
|
|
@@ -1614,9 +1798,11 @@ var HoleTool = class {
|
|
|
1614
1798
|
const lastHole = currentHoles[currentHoles.length - 1];
|
|
1615
1799
|
const innerRadius = (_b = lastHole == null ? void 0 : lastHole.innerRadius) != null ? _b : 15;
|
|
1616
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";
|
|
1617
1802
|
const newHole = {
|
|
1618
1803
|
x: normalizedX,
|
|
1619
1804
|
y: normalizedY,
|
|
1805
|
+
shape,
|
|
1620
1806
|
innerRadius,
|
|
1621
1807
|
outerRadius
|
|
1622
1808
|
};
|
|
@@ -1648,6 +1834,7 @@ var HoleTool = class {
|
|
|
1648
1834
|
if (!this.handleDielineChange) {
|
|
1649
1835
|
this.handleDielineChange = (geometry) => {
|
|
1650
1836
|
this.currentGeometry = geometry;
|
|
1837
|
+
this.redraw();
|
|
1651
1838
|
const changed = this.enforceConstraints();
|
|
1652
1839
|
if (changed) {
|
|
1653
1840
|
this.syncHolesToDieline();
|
|
@@ -1761,10 +1948,16 @@ var HoleTool = class {
|
|
|
1761
1948
|
}
|
|
1762
1949
|
syncHolesFromCanvas() {
|
|
1763
1950
|
if (!this.canvasService) return;
|
|
1764
|
-
const objects = this.canvasService.canvas.getObjects().filter(
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1951
|
+
const objects = this.canvasService.canvas.getObjects().filter(
|
|
1952
|
+
(obj) => {
|
|
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
|
+
}
|
|
1768
1961
|
objects.sort(
|
|
1769
1962
|
(a, b) => {
|
|
1770
1963
|
var _a, _b, _c, _d;
|
|
@@ -1776,6 +1969,13 @@ var HoleTool = class {
|
|
|
1776
1969
|
const original = this.holes[i];
|
|
1777
1970
|
const newAbsX = obj.left;
|
|
1778
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
|
+
}
|
|
1779
1979
|
const scale = ((_a = this.currentGeometry) == null ? void 0 : _a.scale) || 1;
|
|
1780
1980
|
const unit = ((_b = this.currentGeometry) == null ? void 0 : _b.unit) || "mm";
|
|
1781
1981
|
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
@@ -1832,7 +2032,11 @@ var HoleTool = class {
|
|
|
1832
2032
|
offsetY: (newAbsY - by) / scale / unitScale,
|
|
1833
2033
|
// Clear direct coordinates if we use anchor
|
|
1834
2034
|
x: void 0,
|
|
1835
|
-
y: 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"
|
|
1836
2040
|
};
|
|
1837
2041
|
}
|
|
1838
2042
|
let normalizedX = 0.5;
|
|
@@ -1852,9 +2056,13 @@ var HoleTool = class {
|
|
|
1852
2056
|
...original,
|
|
1853
2057
|
x: normalizedX,
|
|
1854
2058
|
y: normalizedY,
|
|
1855
|
-
//
|
|
2059
|
+
// Clear offsets if we are using direct normalized coordinates
|
|
2060
|
+
offsetX: void 0,
|
|
2061
|
+
offsetY: void 0,
|
|
2062
|
+
// Ensure other properties are preserved
|
|
1856
2063
|
innerRadius: (_c = original == null ? void 0 : original.innerRadius) != null ? _c : 15,
|
|
1857
|
-
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25
|
|
2064
|
+
outerRadius: (_d = original == null ? void 0 : original.outerRadius) != null ? _d : 25,
|
|
2065
|
+
shape: (original == null ? void 0 : original.shape) || "circle"
|
|
1858
2066
|
};
|
|
1859
2067
|
});
|
|
1860
2068
|
this.holes = newHoles;
|
|
@@ -1912,7 +2120,16 @@ var HoleTool = class {
|
|
|
1912
2120
|
{ width: geometry.width, height: geometry.height }
|
|
1913
2121
|
// Use geometry dims instead of canvas
|
|
1914
2122
|
);
|
|
1915
|
-
const
|
|
2123
|
+
const isSquare = hole.shape === "square";
|
|
2124
|
+
const innerMarker = isSquare ? new Rect2({
|
|
2125
|
+
width: visualInnerRadius * 2,
|
|
2126
|
+
height: visualInnerRadius * 2,
|
|
2127
|
+
fill: "transparent",
|
|
2128
|
+
stroke: "red",
|
|
2129
|
+
strokeWidth: 2,
|
|
2130
|
+
originX: "center",
|
|
2131
|
+
originY: "center"
|
|
2132
|
+
}) : new Circle({
|
|
1916
2133
|
radius: visualInnerRadius,
|
|
1917
2134
|
fill: "transparent",
|
|
1918
2135
|
stroke: "red",
|
|
@@ -1920,7 +2137,16 @@ var HoleTool = class {
|
|
|
1920
2137
|
originX: "center",
|
|
1921
2138
|
originY: "center"
|
|
1922
2139
|
});
|
|
1923
|
-
const
|
|
2140
|
+
const outerMarker = isSquare ? new Rect2({
|
|
2141
|
+
width: visualOuterRadius * 2,
|
|
2142
|
+
height: visualOuterRadius * 2,
|
|
2143
|
+
fill: "transparent",
|
|
2144
|
+
stroke: "#666",
|
|
2145
|
+
strokeWidth: 1,
|
|
2146
|
+
strokeDashArray: [5, 5],
|
|
2147
|
+
originX: "center",
|
|
2148
|
+
originY: "center"
|
|
2149
|
+
}) : new Circle({
|
|
1924
2150
|
radius: visualOuterRadius,
|
|
1925
2151
|
fill: "transparent",
|
|
1926
2152
|
stroke: "#666",
|
|
@@ -1929,7 +2155,7 @@ var HoleTool = class {
|
|
|
1929
2155
|
originX: "center",
|
|
1930
2156
|
originY: "center"
|
|
1931
2157
|
});
|
|
1932
|
-
const holeGroup = new Group([
|
|
2158
|
+
const holeGroup = new Group([outerMarker, innerMarker], {
|
|
1933
2159
|
left: pos.x,
|
|
1934
2160
|
top: pos.y,
|
|
1935
2161
|
originX: "center",
|
|
@@ -2073,6 +2299,7 @@ var ImageTool = class {
|
|
|
2073
2299
|
};
|
|
2074
2300
|
this.items = [];
|
|
2075
2301
|
this.objectMap = /* @__PURE__ */ new Map();
|
|
2302
|
+
this.loadResolvers = /* @__PURE__ */ new Map();
|
|
2076
2303
|
this.isUpdatingConfig = false;
|
|
2077
2304
|
}
|
|
2078
2305
|
activate(context) {
|
|
@@ -2082,19 +2309,15 @@ var ImageTool = class {
|
|
|
2082
2309
|
console.warn("CanvasService not found for ImageTool");
|
|
2083
2310
|
return;
|
|
2084
2311
|
}
|
|
2085
|
-
const configService = context.services.get(
|
|
2312
|
+
const configService = context.services.get(
|
|
2313
|
+
"ConfigurationService"
|
|
2314
|
+
);
|
|
2086
2315
|
if (configService) {
|
|
2087
2316
|
this.items = configService.get("image.items", []) || [];
|
|
2088
2317
|
configService.onAnyChange((e) => {
|
|
2089
2318
|
if (this.isUpdatingConfig) return;
|
|
2090
|
-
let shouldUpdate = false;
|
|
2091
2319
|
if (e.key === "image.items") {
|
|
2092
2320
|
this.items = e.value || [];
|
|
2093
|
-
shouldUpdate = true;
|
|
2094
|
-
} else if (e.key.startsWith("dieline.") && e.key !== "dieline.holes") {
|
|
2095
|
-
shouldUpdate = true;
|
|
2096
|
-
}
|
|
2097
|
-
if (shouldUpdate) {
|
|
2098
2321
|
this.updateImages();
|
|
2099
2322
|
}
|
|
2100
2323
|
});
|
|
@@ -2130,15 +2353,39 @@ var ImageTool = class {
|
|
|
2130
2353
|
{
|
|
2131
2354
|
command: "addImage",
|
|
2132
2355
|
title: "Add Image",
|
|
2133
|
-
handler: (url, options) => {
|
|
2356
|
+
handler: async (url, options) => {
|
|
2357
|
+
const id = this.generateId();
|
|
2134
2358
|
const newItem = {
|
|
2135
|
-
id
|
|
2359
|
+
id,
|
|
2136
2360
|
url,
|
|
2137
2361
|
opacity: 1,
|
|
2138
2362
|
...options
|
|
2139
2363
|
};
|
|
2364
|
+
const promise = new Promise((resolve) => {
|
|
2365
|
+
this.loadResolvers.set(id, () => resolve(id));
|
|
2366
|
+
});
|
|
2140
2367
|
this.updateConfig([...this.items, newItem]);
|
|
2141
|
-
return
|
|
2368
|
+
return promise;
|
|
2369
|
+
}
|
|
2370
|
+
},
|
|
2371
|
+
{
|
|
2372
|
+
command: "fitImageToArea",
|
|
2373
|
+
title: "Fit Image to Area",
|
|
2374
|
+
handler: (id, area) => {
|
|
2375
|
+
var _a, _b;
|
|
2376
|
+
const item = this.items.find((i) => i.id === id);
|
|
2377
|
+
const obj = this.objectMap.get(id);
|
|
2378
|
+
if (item && obj && obj.width && obj.height) {
|
|
2379
|
+
const scale = Math.max(
|
|
2380
|
+
area.width / obj.width,
|
|
2381
|
+
area.height / obj.height
|
|
2382
|
+
);
|
|
2383
|
+
this.updateImageInConfig(id, {
|
|
2384
|
+
scale,
|
|
2385
|
+
left: (_a = area.left) != null ? _a : 0.5,
|
|
2386
|
+
top: (_b = area.top) != null ? _b : 0.5
|
|
2387
|
+
});
|
|
2388
|
+
}
|
|
2142
2389
|
}
|
|
2143
2390
|
},
|
|
2144
2391
|
{
|
|
@@ -2206,7 +2453,9 @@ var ImageTool = class {
|
|
|
2206
2453
|
if (!this.context) return;
|
|
2207
2454
|
this.isUpdatingConfig = true;
|
|
2208
2455
|
this.items = newItems;
|
|
2209
|
-
const configService = this.context.services.get(
|
|
2456
|
+
const configService = this.context.services.get(
|
|
2457
|
+
"ConfigurationService"
|
|
2458
|
+
);
|
|
2210
2459
|
if (configService) {
|
|
2211
2460
|
configService.update("image.items", newItems);
|
|
2212
2461
|
}
|
|
@@ -2252,39 +2501,12 @@ var ImageTool = class {
|
|
|
2252
2501
|
var _a, _b;
|
|
2253
2502
|
const canvasW = ((_a = this.canvasService) == null ? void 0 : _a.canvas.width) || 800;
|
|
2254
2503
|
const canvasH = ((_b = this.canvasService) == null ? void 0 : _b.canvas.height) || 600;
|
|
2255
|
-
let layoutScale = 1;
|
|
2256
|
-
let layoutOffsetX = 0;
|
|
2257
|
-
let layoutOffsetY = 0;
|
|
2258
|
-
let visualWidth = canvasW;
|
|
2259
|
-
let visualHeight = canvasH;
|
|
2260
|
-
let dielinePhysicalWidth = 500;
|
|
2261
|
-
let dielinePhysicalHeight = 500;
|
|
2262
|
-
if (this.context) {
|
|
2263
|
-
const configService = this.context.services.get("ConfigurationService");
|
|
2264
|
-
if (configService) {
|
|
2265
|
-
dielinePhysicalWidth = configService.get("dieline.width") || 500;
|
|
2266
|
-
dielinePhysicalHeight = configService.get("dieline.height") || 500;
|
|
2267
|
-
const padding = configService.get("dieline.padding") || 40;
|
|
2268
|
-
const layout = Coordinate.calculateLayout(
|
|
2269
|
-
{ width: canvasW, height: canvasH },
|
|
2270
|
-
{ width: dielinePhysicalWidth, height: dielinePhysicalHeight },
|
|
2271
|
-
padding
|
|
2272
|
-
);
|
|
2273
|
-
layoutScale = layout.scale;
|
|
2274
|
-
layoutOffsetX = layout.offsetX;
|
|
2275
|
-
layoutOffsetY = layout.offsetY;
|
|
2276
|
-
visualWidth = layout.width;
|
|
2277
|
-
visualHeight = layout.height;
|
|
2278
|
-
}
|
|
2279
|
-
}
|
|
2280
2504
|
return {
|
|
2281
|
-
layoutScale,
|
|
2282
|
-
layoutOffsetX,
|
|
2283
|
-
layoutOffsetY,
|
|
2284
|
-
visualWidth,
|
|
2285
|
-
visualHeight
|
|
2286
|
-
dielinePhysicalWidth,
|
|
2287
|
-
dielinePhysicalHeight
|
|
2505
|
+
layoutScale: 1,
|
|
2506
|
+
layoutOffsetX: 0,
|
|
2507
|
+
layoutOffsetY: 0,
|
|
2508
|
+
visualWidth: canvasW,
|
|
2509
|
+
visualHeight: canvasH
|
|
2288
2510
|
};
|
|
2289
2511
|
}
|
|
2290
2512
|
updateImages() {
|
|
@@ -2307,8 +2529,8 @@ var ImageTool = class {
|
|
|
2307
2529
|
if (!obj) {
|
|
2308
2530
|
this.loadImage(item, layer, layout);
|
|
2309
2531
|
} else {
|
|
2310
|
-
this.updateObjectProperties(obj, item, layout);
|
|
2311
2532
|
layer.remove(obj);
|
|
2533
|
+
this.updateObjectProperties(obj, item, layout);
|
|
2312
2534
|
layer.add(obj);
|
|
2313
2535
|
}
|
|
2314
2536
|
});
|
|
@@ -2316,10 +2538,17 @@ var ImageTool = class {
|
|
|
2316
2538
|
this.canvasService.requestRenderAll();
|
|
2317
2539
|
}
|
|
2318
2540
|
updateObjectProperties(obj, item, layout) {
|
|
2319
|
-
const {
|
|
2541
|
+
const {
|
|
2542
|
+
layoutScale,
|
|
2543
|
+
layoutOffsetX,
|
|
2544
|
+
layoutOffsetY,
|
|
2545
|
+
visualWidth,
|
|
2546
|
+
visualHeight
|
|
2547
|
+
} = layout;
|
|
2320
2548
|
const updates = {};
|
|
2321
2549
|
if (obj.opacity !== item.opacity) updates.opacity = item.opacity;
|
|
2322
|
-
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2550
|
+
if (item.angle !== void 0 && obj.angle !== item.angle)
|
|
2551
|
+
updates.angle = item.angle;
|
|
2323
2552
|
if (item.left !== void 0) {
|
|
2324
2553
|
const globalLeft = layoutOffsetX + item.left * visualWidth;
|
|
2325
2554
|
if (Math.abs(obj.left - globalLeft) > 1) updates.left = globalLeft;
|
|
@@ -2328,13 +2557,12 @@ var ImageTool = class {
|
|
|
2328
2557
|
const globalTop = layoutOffsetY + item.top * visualHeight;
|
|
2329
2558
|
if (Math.abs(obj.top - globalTop) > 1) updates.top = globalTop;
|
|
2330
2559
|
}
|
|
2331
|
-
if (item.
|
|
2332
|
-
const
|
|
2333
|
-
if (Math.abs(obj.scaleX -
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
if (Math.abs(obj.scaleY - targetScaleY) > 1e-3) updates.scaleY = targetScaleY;
|
|
2560
|
+
if (item.scale !== void 0) {
|
|
2561
|
+
const targetScale = item.scale * layoutScale;
|
|
2562
|
+
if (Math.abs(obj.scaleX - targetScale) > 1e-3) {
|
|
2563
|
+
updates.scaleX = targetScale;
|
|
2564
|
+
updates.scaleY = targetScale;
|
|
2565
|
+
}
|
|
2338
2566
|
}
|
|
2339
2567
|
if (obj.originX !== "center") {
|
|
2340
2568
|
updates.originX = "center";
|
|
@@ -2342,6 +2570,7 @@ var ImageTool = class {
|
|
|
2342
2570
|
}
|
|
2343
2571
|
if (Object.keys(updates).length > 0) {
|
|
2344
2572
|
obj.set(updates);
|
|
2573
|
+
obj.setCoords();
|
|
2345
2574
|
}
|
|
2346
2575
|
}
|
|
2347
2576
|
loadImage(item, layer, layout) {
|
|
@@ -2351,24 +2580,20 @@ var ImageTool = class {
|
|
|
2351
2580
|
image.set({
|
|
2352
2581
|
originX: "center",
|
|
2353
2582
|
originY: "center",
|
|
2354
|
-
data: { id: item.id }
|
|
2583
|
+
data: { id: item.id },
|
|
2584
|
+
uniformScaling: true,
|
|
2585
|
+
lockScalingFlip: true
|
|
2355
2586
|
});
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
const h = dielinePhysicalHeight;
|
|
2367
|
-
height = h;
|
|
2368
|
-
width = h * imgAspect;
|
|
2369
|
-
}
|
|
2370
|
-
item.width = width;
|
|
2371
|
-
item.height = height;
|
|
2587
|
+
image.setControlsVisibility({
|
|
2588
|
+
mt: false,
|
|
2589
|
+
mb: false,
|
|
2590
|
+
ml: false,
|
|
2591
|
+
mr: false
|
|
2592
|
+
});
|
|
2593
|
+
let { scale, left, top } = item;
|
|
2594
|
+
if (scale === void 0) {
|
|
2595
|
+
scale = 1;
|
|
2596
|
+
item.scale = scale;
|
|
2372
2597
|
}
|
|
2373
2598
|
if (left === void 0 && top === void 0) {
|
|
2374
2599
|
left = 0.5;
|
|
@@ -2379,13 +2604,18 @@ var ImageTool = class {
|
|
|
2379
2604
|
this.updateObjectProperties(image, item, layout);
|
|
2380
2605
|
layer.add(image);
|
|
2381
2606
|
this.objectMap.set(item.id, image);
|
|
2607
|
+
const resolver = this.loadResolvers.get(item.id);
|
|
2608
|
+
if (resolver) {
|
|
2609
|
+
resolver();
|
|
2610
|
+
this.loadResolvers.delete(item.id);
|
|
2611
|
+
}
|
|
2382
2612
|
image.on("modified", (e) => {
|
|
2383
2613
|
this.handleObjectModified(item.id, image);
|
|
2384
2614
|
});
|
|
2385
2615
|
layer.dirty = true;
|
|
2386
2616
|
(_a = this.canvasService) == null ? void 0 : _a.requestRenderAll();
|
|
2387
|
-
if (item.
|
|
2388
|
-
this.updateImageInConfig(item.id, {
|
|
2617
|
+
if (item.scale !== scale || item.left !== left || item.top !== top) {
|
|
2618
|
+
this.updateImageInConfig(item.id, { scale, left, top }, true);
|
|
2389
2619
|
}
|
|
2390
2620
|
}).catch((err) => {
|
|
2391
2621
|
console.error("Failed to load image", item.url, err);
|
|
@@ -2393,29 +2623,28 @@ var ImageTool = class {
|
|
|
2393
2623
|
}
|
|
2394
2624
|
handleObjectModified(id, image) {
|
|
2395
2625
|
const layout = this.getLayoutInfo();
|
|
2396
|
-
const {
|
|
2626
|
+
const {
|
|
2627
|
+
layoutScale,
|
|
2628
|
+
layoutOffsetX,
|
|
2629
|
+
layoutOffsetY,
|
|
2630
|
+
visualWidth,
|
|
2631
|
+
visualHeight
|
|
2632
|
+
} = layout;
|
|
2397
2633
|
const matrix = image.calcTransformMatrix();
|
|
2398
2634
|
const globalPoint = util.transformPoint(new Point2(0, 0), matrix);
|
|
2399
2635
|
const updates = {};
|
|
2400
2636
|
updates.left = (globalPoint.x - layoutOffsetX) / visualWidth;
|
|
2401
2637
|
updates.top = (globalPoint.y - layoutOffsetY) / visualHeight;
|
|
2402
2638
|
updates.angle = image.angle;
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
updates.width = pixelWidth / layoutScale;
|
|
2406
|
-
}
|
|
2407
|
-
if (image.height) {
|
|
2408
|
-
const pixelHeight = image.height * image.scaleY;
|
|
2409
|
-
updates.height = pixelHeight / layoutScale;
|
|
2410
|
-
}
|
|
2411
|
-
this.updateImageInConfig(id, updates);
|
|
2639
|
+
updates.scale = image.scaleX / layoutScale;
|
|
2640
|
+
this.updateImageInConfig(id, updates, true);
|
|
2412
2641
|
}
|
|
2413
|
-
updateImageInConfig(id, updates) {
|
|
2642
|
+
updateImageInConfig(id, updates, skipCanvasUpdate = false) {
|
|
2414
2643
|
const index = this.items.findIndex((i) => i.id === id);
|
|
2415
2644
|
if (index !== -1) {
|
|
2416
2645
|
const newItems = [...this.items];
|
|
2417
2646
|
newItems[index] = { ...newItems[index], ...updates };
|
|
2418
|
-
this.updateConfig(newItems,
|
|
2647
|
+
this.updateConfig(newItems, skipCanvasUpdate);
|
|
2419
2648
|
}
|
|
2420
2649
|
}
|
|
2421
2650
|
};
|