@pooder/kit 3.0.1 → 3.2.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 +17 -0
- package/dist/index.d.mts +46 -36
- package/dist/index.d.ts +46 -36
- package/dist/index.js +958 -696
- package/dist/index.mjs +962 -700
- package/package.json +2 -2
- package/src/coordinate.ts +57 -0
- package/src/dieline.ts +206 -214
- package/src/geometry.ts +101 -4
- package/src/hole.ts +277 -170
- package/src/image.ts +334 -365
- package/src/ruler.ts +295 -120
package/src/hole.ts
CHANGED
|
@@ -9,7 +9,11 @@ import {
|
|
|
9
9
|
import { Circle, Group, Point } from "fabric";
|
|
10
10
|
import CanvasService from "./CanvasService";
|
|
11
11
|
import { DielineGeometry } from "./dieline";
|
|
12
|
-
import {
|
|
12
|
+
import {
|
|
13
|
+
getNearestPointOnDieline,
|
|
14
|
+
HoleData,
|
|
15
|
+
resolveHolePosition,
|
|
16
|
+
} from "./geometry";
|
|
13
17
|
import { Coordinate } from "./coordinate";
|
|
14
18
|
|
|
15
19
|
export class HoleTool implements Extension {
|
|
@@ -19,10 +23,7 @@ export class HoleTool implements Extension {
|
|
|
19
23
|
name: "HoleTool",
|
|
20
24
|
};
|
|
21
25
|
|
|
22
|
-
private
|
|
23
|
-
private outerRadius: number = 25;
|
|
24
|
-
private style: "solid" | "dashed" = "solid";
|
|
25
|
-
private holes: Array<{ x: number; y: number }> = [];
|
|
26
|
+
private holes: HoleData[] = [];
|
|
26
27
|
private constraintTarget: "original" | "bleed" = "bleed";
|
|
27
28
|
|
|
28
29
|
private canvasService?: CanvasService;
|
|
@@ -39,12 +40,9 @@ export class HoleTool implements Extension {
|
|
|
39
40
|
|
|
40
41
|
constructor(
|
|
41
42
|
options?: Partial<{
|
|
42
|
-
|
|
43
|
-
outerRadius: number;
|
|
44
|
-
style: "solid" | "dashed";
|
|
45
|
-
holes: Array<{ x: number; y: number }>;
|
|
43
|
+
holes: HoleData[];
|
|
46
44
|
constraintTarget: "original" | "bleed";
|
|
47
|
-
}
|
|
45
|
+
}>
|
|
48
46
|
) {
|
|
49
47
|
if (options) {
|
|
50
48
|
Object.assign(this, options);
|
|
@@ -61,64 +59,31 @@ export class HoleTool implements Extension {
|
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
const configService = context.services.get<ConfigurationService>(
|
|
64
|
-
"ConfigurationService"
|
|
62
|
+
"ConfigurationService"
|
|
65
63
|
);
|
|
66
64
|
if (configService) {
|
|
67
65
|
// Load initial config
|
|
68
|
-
this.innerRadius = configService.get(
|
|
69
|
-
"hole.innerRadius",
|
|
70
|
-
this.innerRadius,
|
|
71
|
-
);
|
|
72
|
-
this.outerRadius = configService.get(
|
|
73
|
-
"hole.outerRadius",
|
|
74
|
-
this.outerRadius,
|
|
75
|
-
);
|
|
76
|
-
this.style = configService.get("hole.style", this.style);
|
|
77
66
|
this.constraintTarget = configService.get(
|
|
78
67
|
"hole.constraintTarget",
|
|
79
|
-
this.constraintTarget
|
|
68
|
+
this.constraintTarget
|
|
80
69
|
);
|
|
81
70
|
|
|
82
71
|
// Load holes from dieline.holes (SSOT)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const { width, height } = this.canvasService.canvas;
|
|
86
|
-
this.holes = dielineHoles.map((h: any) => {
|
|
87
|
-
const p = Coordinate.denormalizePoint(h, {
|
|
88
|
-
width: width || 800,
|
|
89
|
-
height: height || 600,
|
|
90
|
-
});
|
|
91
|
-
return { x: p.x, y: p.y };
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
|
|
72
|
+
this.holes = configService.get("dieline.holes", []);
|
|
73
|
+
|
|
95
74
|
// Listen for changes
|
|
96
75
|
configService.onAnyChange((e: { key: string; value: any }) => {
|
|
97
76
|
if (this.isUpdatingConfig) return;
|
|
98
77
|
|
|
99
|
-
if (e.key
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
(this as any)[prop] = e.value;
|
|
103
|
-
this.redraw();
|
|
104
|
-
// Allow syncHolesToDieline to run to update dieline.holes
|
|
105
|
-
this.syncHolesToDieline();
|
|
106
|
-
}
|
|
78
|
+
if (e.key === "hole.constraintTarget") {
|
|
79
|
+
this.constraintTarget = e.value;
|
|
80
|
+
this.enforceConstraints();
|
|
107
81
|
}
|
|
82
|
+
|
|
108
83
|
// Listen for dieline.holes changes (e.g. from undo/redo or other sources)
|
|
109
84
|
if (e.key === "dieline.holes") {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const { width, height } = this.canvasService.canvas;
|
|
113
|
-
this.holes = holes.map((h: any) => {
|
|
114
|
-
const p = Coordinate.denormalizePoint(h, {
|
|
115
|
-
width: width || 800,
|
|
116
|
-
height: height || 600,
|
|
117
|
-
});
|
|
118
|
-
return { x: p.x, y: p.y };
|
|
119
|
-
});
|
|
120
|
-
this.redraw();
|
|
121
|
-
}
|
|
85
|
+
this.holes = e.value || [];
|
|
86
|
+
this.redraw();
|
|
122
87
|
}
|
|
123
88
|
});
|
|
124
89
|
}
|
|
@@ -135,29 +100,6 @@ export class HoleTool implements Extension {
|
|
|
135
100
|
contribute() {
|
|
136
101
|
return {
|
|
137
102
|
[ContributionPointIds.CONFIGURATIONS]: [
|
|
138
|
-
{
|
|
139
|
-
id: "hole.innerRadius",
|
|
140
|
-
type: "number",
|
|
141
|
-
label: "Inner Radius",
|
|
142
|
-
min: 1,
|
|
143
|
-
max: 100,
|
|
144
|
-
default: 15,
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
id: "hole.outerRadius",
|
|
148
|
-
type: "number",
|
|
149
|
-
label: "Outer Radius",
|
|
150
|
-
min: 1,
|
|
151
|
-
max: 100,
|
|
152
|
-
default: 25,
|
|
153
|
-
},
|
|
154
|
-
{
|
|
155
|
-
id: "hole.style",
|
|
156
|
-
type: "select",
|
|
157
|
-
label: "Line Style",
|
|
158
|
-
options: ["solid", "dashed"],
|
|
159
|
-
default: "solid",
|
|
160
|
-
},
|
|
161
103
|
{
|
|
162
104
|
id: "hole.constraintTarget",
|
|
163
105
|
type: "select",
|
|
@@ -183,13 +125,25 @@ export class HoleTool implements Extension {
|
|
|
183
125
|
} as any);
|
|
184
126
|
}
|
|
185
127
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
128
|
+
const { width, height } = this.canvasService.canvas;
|
|
129
|
+
const normalizedHole = Coordinate.normalizePoint(defaultPos, {
|
|
130
|
+
width: width || 800,
|
|
131
|
+
height: height || 600,
|
|
132
|
+
});
|
|
190
133
|
|
|
191
|
-
this.
|
|
192
|
-
|
|
134
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
135
|
+
"ConfigurationService"
|
|
136
|
+
);
|
|
137
|
+
if (configService) {
|
|
138
|
+
configService.update("dieline.holes", [
|
|
139
|
+
{
|
|
140
|
+
x: normalizedHole.x,
|
|
141
|
+
y: normalizedHole.y,
|
|
142
|
+
innerRadius: 15,
|
|
143
|
+
outerRadius: 25,
|
|
144
|
+
},
|
|
145
|
+
]);
|
|
146
|
+
}
|
|
193
147
|
return true;
|
|
194
148
|
},
|
|
195
149
|
},
|
|
@@ -197,10 +151,43 @@ export class HoleTool implements Extension {
|
|
|
197
151
|
command: "addHole",
|
|
198
152
|
title: "Add Hole",
|
|
199
153
|
handler: (x: number, y: number) => {
|
|
200
|
-
if (!this.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
154
|
+
if (!this.canvasService) return false;
|
|
155
|
+
|
|
156
|
+
// Normalize relative to Dieline Geometry if available
|
|
157
|
+
let normalizedX = 0.5;
|
|
158
|
+
let normalizedY = 0.5;
|
|
159
|
+
|
|
160
|
+
if (this.currentGeometry) {
|
|
161
|
+
const { x: gx, y: gy, width: gw, height: gh } = this.currentGeometry;
|
|
162
|
+
const left = gx - gw / 2;
|
|
163
|
+
const top = gy - gh / 2;
|
|
164
|
+
normalizedX = gw > 0 ? (x - left) / gw : 0.5;
|
|
165
|
+
normalizedY = gh > 0 ? (y - top) / gh : 0.5;
|
|
166
|
+
} else {
|
|
167
|
+
const { width, height } = this.canvasService.canvas;
|
|
168
|
+
normalizedX = Coordinate.toNormalized(x, width || 800);
|
|
169
|
+
normalizedY = Coordinate.toNormalized(y, height || 600);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
173
|
+
"ConfigurationService"
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (configService) {
|
|
177
|
+
const currentHoles = configService.get("dieline.holes", []) as HoleData[];
|
|
178
|
+
// Use last hole's radii or default
|
|
179
|
+
const lastHole = currentHoles[currentHoles.length - 1];
|
|
180
|
+
const innerRadius = lastHole?.innerRadius ?? 15;
|
|
181
|
+
const outerRadius = lastHole?.outerRadius ?? 25;
|
|
182
|
+
|
|
183
|
+
const newHole = {
|
|
184
|
+
x: normalizedX,
|
|
185
|
+
y: normalizedY,
|
|
186
|
+
innerRadius,
|
|
187
|
+
outerRadius,
|
|
188
|
+
};
|
|
189
|
+
configService.update("dieline.holes", [...currentHoles, newHole]);
|
|
190
|
+
}
|
|
204
191
|
return true;
|
|
205
192
|
},
|
|
206
193
|
},
|
|
@@ -208,9 +195,12 @@ export class HoleTool implements Extension {
|
|
|
208
195
|
command: "clearHoles",
|
|
209
196
|
title: "Clear Holes",
|
|
210
197
|
handler: () => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
198
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
199
|
+
"ConfigurationService"
|
|
200
|
+
);
|
|
201
|
+
if (configService) {
|
|
202
|
+
configService.update("dieline.holes", []);
|
|
203
|
+
}
|
|
214
204
|
return true;
|
|
215
205
|
},
|
|
216
206
|
},
|
|
@@ -274,6 +264,9 @@ export class HoleTool implements Extension {
|
|
|
274
264
|
|
|
275
265
|
if (!this.currentGeometry) return;
|
|
276
266
|
|
|
267
|
+
const index = target.data?.index ?? -1;
|
|
268
|
+
const holeData = this.holes[index];
|
|
269
|
+
|
|
277
270
|
// Calculate effective geometry based on constraint target
|
|
278
271
|
const effectiveOffset =
|
|
279
272
|
this.constraintTarget === "original"
|
|
@@ -284,13 +277,18 @@ export class HoleTool implements Extension {
|
|
|
284
277
|
width: Math.max(0, this.currentGeometry.width + effectiveOffset * 2),
|
|
285
278
|
height: Math.max(
|
|
286
279
|
0,
|
|
287
|
-
this.currentGeometry.height + effectiveOffset * 2
|
|
280
|
+
this.currentGeometry.height + effectiveOffset * 2
|
|
288
281
|
),
|
|
289
282
|
radius: Math.max(0, this.currentGeometry.radius + effectiveOffset),
|
|
290
283
|
};
|
|
291
284
|
|
|
292
285
|
const p = new Point(target.left, target.top);
|
|
293
|
-
const newPos = this.calculateConstrainedPosition(
|
|
286
|
+
const newPos = this.calculateConstrainedPosition(
|
|
287
|
+
p,
|
|
288
|
+
constraintGeometry,
|
|
289
|
+
holeData?.innerRadius ?? 15,
|
|
290
|
+
holeData?.outerRadius ?? 25
|
|
291
|
+
);
|
|
294
292
|
|
|
295
293
|
target.set({
|
|
296
294
|
left: newPos.x,
|
|
@@ -306,7 +304,14 @@ export class HoleTool implements Extension {
|
|
|
306
304
|
if (!target || target.data?.type !== "hole-marker") return;
|
|
307
305
|
|
|
308
306
|
// Update state when hole is moved
|
|
309
|
-
|
|
307
|
+
// Ensure final position is constrained (handles case where 'modified' reports unconstrained coords)
|
|
308
|
+
const changed = this.enforceConstraints();
|
|
309
|
+
|
|
310
|
+
// If enforceConstraints changed something, it already synced.
|
|
311
|
+
// If not, we sync manually to save the move (which was valid).
|
|
312
|
+
if (!changed) {
|
|
313
|
+
this.syncHolesFromCanvas();
|
|
314
|
+
}
|
|
310
315
|
};
|
|
311
316
|
canvas.on("object:modified", this.handleModified);
|
|
312
317
|
}
|
|
@@ -316,25 +321,6 @@ export class HoleTool implements Extension {
|
|
|
316
321
|
|
|
317
322
|
private initializeHoles() {
|
|
318
323
|
if (!this.canvasService) return;
|
|
319
|
-
// Default hole if none exist
|
|
320
|
-
if (!this.holes || this.holes.length === 0) {
|
|
321
|
-
let defaultPos = { x: this.canvasService.canvas.width! / 2, y: 50 };
|
|
322
|
-
|
|
323
|
-
if (this.currentGeometry) {
|
|
324
|
-
const g = this.currentGeometry;
|
|
325
|
-
// Default to Top-Center of Dieline shape
|
|
326
|
-
const topCenter = { x: g.x, y: g.y - g.height / 2 };
|
|
327
|
-
// Snap to exact shape edge
|
|
328
|
-
const snapped = getNearestPointOnDieline(topCenter, {
|
|
329
|
-
...g,
|
|
330
|
-
holes: [],
|
|
331
|
-
} as any);
|
|
332
|
-
defaultPos = snapped;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
this.holes = [defaultPos];
|
|
336
|
-
}
|
|
337
|
-
|
|
338
324
|
this.redraw();
|
|
339
325
|
this.syncHolesToDieline();
|
|
340
326
|
}
|
|
@@ -383,38 +369,127 @@ export class HoleTool implements Extension {
|
|
|
383
369
|
.getObjects()
|
|
384
370
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
385
371
|
|
|
386
|
-
|
|
387
|
-
|
|
372
|
+
// Sort objects by index
|
|
373
|
+
objects.sort(
|
|
374
|
+
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
// Update holes based on canvas positions
|
|
378
|
+
// We need to preserve original hole properties (radii, anchor)
|
|
379
|
+
// If a hole has an anchor, we update offsetX/Y instead of x/y
|
|
380
|
+
const newHoles = objects.map((obj, i) => {
|
|
381
|
+
const original = this.holes[i];
|
|
382
|
+
const newAbsX = obj.left!;
|
|
383
|
+
const newAbsY = obj.top!;
|
|
384
|
+
|
|
385
|
+
// Get current scale to denormalize offsets
|
|
386
|
+
const scale = this.currentGeometry?.scale || 1;
|
|
387
|
+
const unit = this.currentGeometry?.unit || "mm";
|
|
388
|
+
const unitScale = Coordinate.convertUnit(1, "mm", unit);
|
|
389
|
+
|
|
390
|
+
if (original && original.anchor && this.currentGeometry) {
|
|
391
|
+
// Reverse calculate offset from anchor
|
|
392
|
+
const { x, y, width, height } = this.currentGeometry;
|
|
393
|
+
let bx = x;
|
|
394
|
+
let by = y;
|
|
395
|
+
const left = x - width / 2;
|
|
396
|
+
const right = x + width / 2;
|
|
397
|
+
const top = y - height / 2;
|
|
398
|
+
const bottom = y + height / 2;
|
|
399
|
+
|
|
400
|
+
switch (original.anchor) {
|
|
401
|
+
case "top-left":
|
|
402
|
+
bx = left;
|
|
403
|
+
by = top;
|
|
404
|
+
break;
|
|
405
|
+
case "top-center":
|
|
406
|
+
bx = x;
|
|
407
|
+
by = top;
|
|
408
|
+
break;
|
|
409
|
+
case "top-right":
|
|
410
|
+
bx = right;
|
|
411
|
+
by = top;
|
|
412
|
+
break;
|
|
413
|
+
case "center-left":
|
|
414
|
+
bx = left;
|
|
415
|
+
by = y;
|
|
416
|
+
break;
|
|
417
|
+
case "center":
|
|
418
|
+
bx = x;
|
|
419
|
+
by = y;
|
|
420
|
+
break;
|
|
421
|
+
case "center-right":
|
|
422
|
+
bx = right;
|
|
423
|
+
by = y;
|
|
424
|
+
break;
|
|
425
|
+
case "bottom-left":
|
|
426
|
+
bx = left;
|
|
427
|
+
by = bottom;
|
|
428
|
+
break;
|
|
429
|
+
case "bottom-center":
|
|
430
|
+
bx = x;
|
|
431
|
+
by = bottom;
|
|
432
|
+
break;
|
|
433
|
+
case "bottom-right":
|
|
434
|
+
bx = right;
|
|
435
|
+
by = bottom;
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
return {
|
|
440
|
+
...original,
|
|
441
|
+
// Denormalize offset back to physical units (mm)
|
|
442
|
+
offsetX: (newAbsX - bx) / scale / unitScale,
|
|
443
|
+
offsetY: (newAbsY - by) / scale / unitScale,
|
|
444
|
+
// Clear direct coordinates if we use anchor
|
|
445
|
+
x: undefined,
|
|
446
|
+
y: undefined,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// If no anchor, use normalized coordinates relative to Dieline Geometry
|
|
451
|
+
// normalized = (absolute - (center - width/2)) / width
|
|
452
|
+
let normalizedX = 0.5;
|
|
453
|
+
let normalizedY = 0.5;
|
|
388
454
|
|
|
455
|
+
if (this.currentGeometry) {
|
|
456
|
+
const { x, y, width, height } = this.currentGeometry;
|
|
457
|
+
const left = x - width / 2;
|
|
458
|
+
const top = y - height / 2;
|
|
459
|
+
normalizedX = width > 0 ? (newAbsX - left) / width : 0.5;
|
|
460
|
+
normalizedY = height > 0 ? (newAbsY - top) / height : 0.5;
|
|
461
|
+
} else {
|
|
462
|
+
// Fallback to Canvas normalization if no geometry (should rare)
|
|
463
|
+
const { width, height } = this.canvasService!.canvas;
|
|
464
|
+
normalizedX = Coordinate.toNormalized(newAbsX, width || 800);
|
|
465
|
+
normalizedY = Coordinate.toNormalized(newAbsY, height || 600);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
return {
|
|
469
|
+
...original,
|
|
470
|
+
x: normalizedX,
|
|
471
|
+
y: normalizedY,
|
|
472
|
+
// Ensure radii are preserved
|
|
473
|
+
innerRadius: original?.innerRadius ?? 15,
|
|
474
|
+
outerRadius: original?.outerRadius ?? 25,
|
|
475
|
+
};
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
this.holes = newHoles;
|
|
389
479
|
this.syncHolesToDieline();
|
|
390
480
|
}
|
|
391
481
|
|
|
392
482
|
private syncHolesToDieline() {
|
|
393
483
|
if (!this.context || !this.canvasService) return;
|
|
394
484
|
|
|
395
|
-
const { holes, innerRadius, outerRadius } = this;
|
|
396
|
-
const currentHoles = holes || [];
|
|
397
|
-
const width = this.canvasService.canvas.width || 800;
|
|
398
|
-
const height = this.canvasService.canvas.height || 600;
|
|
399
|
-
|
|
400
485
|
const configService = this.context.services.get<ConfigurationService>(
|
|
401
|
-
"ConfigurationService"
|
|
486
|
+
"ConfigurationService"
|
|
402
487
|
);
|
|
403
488
|
|
|
404
489
|
if (configService) {
|
|
405
490
|
this.isUpdatingConfig = true;
|
|
406
491
|
try {
|
|
407
|
-
|
|
408
|
-
const normalizedHoles = currentHoles.map((h) => {
|
|
409
|
-
const p = Coordinate.normalizePoint(h, { width, height });
|
|
410
|
-
return {
|
|
411
|
-
x: p.x,
|
|
412
|
-
y: p.y,
|
|
413
|
-
innerRadius,
|
|
414
|
-
outerRadius,
|
|
415
|
-
};
|
|
416
|
-
});
|
|
417
|
-
configService.update("dieline.holes", normalizedHoles);
|
|
492
|
+
configService.update("dieline.holes", this.holes);
|
|
418
493
|
} finally {
|
|
419
494
|
this.isUpdatingConfig = false;
|
|
420
495
|
}
|
|
@@ -424,6 +499,7 @@ export class HoleTool implements Extension {
|
|
|
424
499
|
private redraw() {
|
|
425
500
|
if (!this.canvasService) return;
|
|
426
501
|
const canvas = this.canvasService.canvas;
|
|
502
|
+
const { width, height } = canvas;
|
|
427
503
|
|
|
428
504
|
// Remove existing holes
|
|
429
505
|
const existing = canvas
|
|
@@ -431,16 +507,45 @@ export class HoleTool implements Extension {
|
|
|
431
507
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
432
508
|
existing.forEach((obj) => canvas.remove(obj));
|
|
433
509
|
|
|
434
|
-
const
|
|
510
|
+
const holes = this.holes;
|
|
435
511
|
|
|
436
512
|
if (!holes || holes.length === 0) {
|
|
437
513
|
this.canvasService.requestRenderAll();
|
|
438
514
|
return;
|
|
439
515
|
}
|
|
440
516
|
|
|
517
|
+
// Resolve geometry if needed for anchors
|
|
518
|
+
const geometry = this.currentGeometry || {
|
|
519
|
+
x: (width || 800) / 2,
|
|
520
|
+
y: (height || 600) / 2,
|
|
521
|
+
width: width || 800,
|
|
522
|
+
height: height || 600,
|
|
523
|
+
scale: 1, // Default scale if no geometry loaded
|
|
524
|
+
} as any;
|
|
525
|
+
|
|
441
526
|
holes.forEach((hole, index) => {
|
|
527
|
+
// Geometry scale is needed.
|
|
528
|
+
const scale = geometry.scale || 1;
|
|
529
|
+
const unit = geometry.unit || "mm";
|
|
530
|
+
const unitScale = Coordinate.convertUnit(1, 'mm', unit);
|
|
531
|
+
|
|
532
|
+
const visualInnerRadius = hole.innerRadius * unitScale * scale;
|
|
533
|
+
const visualOuterRadius = hole.outerRadius * unitScale * scale;
|
|
534
|
+
|
|
535
|
+
// Resolve position
|
|
536
|
+
// Apply unit conversion and scale to offsets before resolving (mm -> px)
|
|
537
|
+
const pos = resolveHolePosition(
|
|
538
|
+
{
|
|
539
|
+
...hole,
|
|
540
|
+
offsetX: (hole.offsetX || 0) * unitScale * scale,
|
|
541
|
+
offsetY: (hole.offsetY || 0) * unitScale * scale,
|
|
542
|
+
},
|
|
543
|
+
geometry,
|
|
544
|
+
{ width: geometry.width, height: geometry.height } // Use geometry dims instead of canvas
|
|
545
|
+
);
|
|
546
|
+
|
|
442
547
|
const innerCircle = new Circle({
|
|
443
|
-
radius:
|
|
548
|
+
radius: visualInnerRadius,
|
|
444
549
|
fill: "transparent",
|
|
445
550
|
stroke: "red",
|
|
446
551
|
strokeWidth: 2,
|
|
@@ -449,18 +554,18 @@ export class HoleTool implements Extension {
|
|
|
449
554
|
});
|
|
450
555
|
|
|
451
556
|
const outerCircle = new Circle({
|
|
452
|
-
radius:
|
|
557
|
+
radius: visualOuterRadius,
|
|
453
558
|
fill: "transparent",
|
|
454
559
|
stroke: "#666",
|
|
455
560
|
strokeWidth: 1,
|
|
456
|
-
strokeDashArray:
|
|
561
|
+
strokeDashArray: [5, 5],
|
|
457
562
|
originX: "center",
|
|
458
563
|
originY: "center",
|
|
459
564
|
});
|
|
460
565
|
|
|
461
566
|
const holeGroup = new Group([outerCircle, innerCircle], {
|
|
462
|
-
left:
|
|
463
|
-
top:
|
|
567
|
+
left: pos.x,
|
|
568
|
+
top: pos.y,
|
|
464
569
|
originX: "center",
|
|
465
570
|
originY: "center",
|
|
466
571
|
selectable: true,
|
|
@@ -494,12 +599,6 @@ export class HoleTool implements Extension {
|
|
|
494
599
|
});
|
|
495
600
|
|
|
496
601
|
canvas.add(holeGroup);
|
|
497
|
-
|
|
498
|
-
// Ensure hole markers are always on top of Dieline layer
|
|
499
|
-
// Dieline layer uses bringObjectToFront, so we must be aggressive
|
|
500
|
-
// But we can't control when Dieline updates.
|
|
501
|
-
// Ideally, HoleTool should use a dedicated overlay layer above Dieline.
|
|
502
|
-
// For now, let's just bring to front.
|
|
503
602
|
canvas.bringObjectToFront(holeGroup);
|
|
504
603
|
});
|
|
505
604
|
|
|
@@ -513,9 +612,6 @@ export class HoleTool implements Extension {
|
|
|
513
612
|
public enforceConstraints(): boolean {
|
|
514
613
|
const geometry = this.currentGeometry;
|
|
515
614
|
if (!geometry || !this.canvasService) {
|
|
516
|
-
console.log(
|
|
517
|
-
"[HoleTool] Skipping enforceConstraints: No geometry or canvas service",
|
|
518
|
-
);
|
|
519
615
|
return false;
|
|
520
616
|
}
|
|
521
617
|
|
|
@@ -533,29 +629,36 @@ export class HoleTool implements Extension {
|
|
|
533
629
|
.getObjects()
|
|
534
630
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
535
631
|
|
|
536
|
-
console.log(
|
|
537
|
-
`[HoleTool] Enforcing constraints on ${objects.length} markers`,
|
|
538
|
-
);
|
|
539
|
-
|
|
540
632
|
let changed = false;
|
|
541
633
|
// Sort objects by index to maintain order in options.holes
|
|
542
634
|
objects.sort(
|
|
543
|
-
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
635
|
+
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
544
636
|
);
|
|
545
637
|
|
|
546
|
-
const newHoles:
|
|
638
|
+
const newHoles: HoleData[] = [];
|
|
547
639
|
|
|
548
|
-
objects.forEach((obj: any) => {
|
|
640
|
+
objects.forEach((obj: any, i: number) => {
|
|
549
641
|
const currentPos = new Point(obj.left, obj.top);
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
642
|
+
// We need to pass the hole's radii to calculateConstrainedPosition
|
|
643
|
+
const holeData = this.holes[i];
|
|
644
|
+
|
|
645
|
+
// Scale radii for constraint calculation (since geometry is in pixels)
|
|
646
|
+
// Geometry scale is needed.
|
|
647
|
+
const scale = geometry.scale || 1;
|
|
648
|
+
const unit = geometry.unit || "mm";
|
|
649
|
+
const unitScale = Coordinate.convertUnit(1, 'mm', unit);
|
|
650
|
+
|
|
651
|
+
const innerR = (holeData?.innerRadius ?? 15) * unitScale * scale;
|
|
652
|
+
const outerR = (holeData?.outerRadius ?? 25) * unitScale * scale;
|
|
653
|
+
|
|
654
|
+
const newPos = this.calculateConstrainedPosition(
|
|
655
|
+
currentPos,
|
|
656
|
+
constraintGeometry,
|
|
657
|
+
innerR,
|
|
658
|
+
outerR
|
|
659
|
+
);
|
|
554
660
|
|
|
555
661
|
if (currentPos.distanceFrom(newPos) > 0.1) {
|
|
556
|
-
console.log(
|
|
557
|
-
`[HoleTool] Moving hole from (${currentPos.x}, ${currentPos.y}) to (${newPos.x}, ${newPos.y})`,
|
|
558
|
-
);
|
|
559
662
|
obj.set({
|
|
560
663
|
left: newPos.x,
|
|
561
664
|
top: newPos.y,
|
|
@@ -563,23 +666,28 @@ export class HoleTool implements Extension {
|
|
|
563
666
|
obj.setCoords();
|
|
564
667
|
changed = true;
|
|
565
668
|
}
|
|
566
|
-
|
|
669
|
+
|
|
670
|
+
// Update data logic is handled in syncHolesFromCanvas which is called on modified
|
|
671
|
+
// But here we are modifying programmatically.
|
|
672
|
+
// We should probably just let the visual update happen, and then sync?
|
|
673
|
+
// Or just push to newHoles list to verify change?
|
|
567
674
|
});
|
|
568
675
|
|
|
569
676
|
if (changed) {
|
|
570
|
-
|
|
571
|
-
this.
|
|
572
|
-
// We return true instead of syncing directly to avoid recursion
|
|
677
|
+
// If we moved things programmatically, we should update the state
|
|
678
|
+
this.syncHolesFromCanvas();
|
|
573
679
|
return true;
|
|
574
680
|
}
|
|
575
681
|
return false;
|
|
576
682
|
}
|
|
577
683
|
|
|
578
|
-
private calculateConstrainedPosition(
|
|
684
|
+
private calculateConstrainedPosition(
|
|
685
|
+
p: Point,
|
|
686
|
+
g: DielineGeometry,
|
|
687
|
+
innerRadius: number,
|
|
688
|
+
outerRadius: number
|
|
689
|
+
): Point {
|
|
579
690
|
// Use Paper.js to get accurate nearest point
|
|
580
|
-
// This handles ellipses, rects, and rounded rects correctly
|
|
581
|
-
|
|
582
|
-
// Convert to holes format for geometry options
|
|
583
691
|
const options = {
|
|
584
692
|
...g,
|
|
585
693
|
holes: [], // We don't need holes for boundary calculation
|
|
@@ -599,7 +707,6 @@ export class HoleTool implements Extension {
|
|
|
599
707
|
|
|
600
708
|
// Vector from center to nearest point (approximate normal for convex shapes)
|
|
601
709
|
const center = new Point(g.x, g.y);
|
|
602
|
-
const centerToNearest = nearestP.subtract(center);
|
|
603
710
|
|
|
604
711
|
const distToCenter = p.distanceFrom(center);
|
|
605
712
|
const nearestDistToCenter = nearestP.distanceFrom(center);
|
|
@@ -612,9 +719,9 @@ export class HoleTool implements Extension {
|
|
|
612
719
|
// Clamp distance
|
|
613
720
|
let clampedDist = signedDist;
|
|
614
721
|
if (signedDist > 0) {
|
|
615
|
-
clampedDist = Math.min(signedDist,
|
|
722
|
+
clampedDist = Math.min(signedDist, innerRadius);
|
|
616
723
|
} else {
|
|
617
|
-
clampedDist = Math.max(signedDist, -
|
|
724
|
+
clampedDist = Math.max(signedDist, -outerRadius);
|
|
618
725
|
}
|
|
619
726
|
|
|
620
727
|
// Reconstruct point
|