@pooder/kit 3.0.0 → 3.1.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 +13 -0
- package/dist/index.d.mts +7 -12
- package/dist/index.d.ts +7 -12
- package/dist/index.js +266 -210
- package/dist/index.mjs +266 -210
- package/package.json +2 -2
- package/src/dieline.ts +38 -103
- package/src/geometry.ts +90 -2
- package/src/hole.ts +215 -165
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,33 @@ 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
|
+
const { width, height } = this.canvasService.canvas;
|
|
156
|
+
|
|
157
|
+
const normalizedHole = Coordinate.normalizePoint(
|
|
158
|
+
{ x, y },
|
|
159
|
+
{ width: width || 800, height: height || 600 }
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
163
|
+
"ConfigurationService"
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (configService) {
|
|
167
|
+
const currentHoles = configService.get("dieline.holes", []) as HoleData[];
|
|
168
|
+
// Use last hole's radii or default
|
|
169
|
+
const lastHole = currentHoles[currentHoles.length - 1];
|
|
170
|
+
const innerRadius = lastHole?.innerRadius ?? 15;
|
|
171
|
+
const outerRadius = lastHole?.outerRadius ?? 25;
|
|
172
|
+
|
|
173
|
+
const newHole = {
|
|
174
|
+
x: normalizedHole.x,
|
|
175
|
+
y: normalizedHole.y,
|
|
176
|
+
innerRadius,
|
|
177
|
+
outerRadius,
|
|
178
|
+
};
|
|
179
|
+
configService.update("dieline.holes", [...currentHoles, newHole]);
|
|
180
|
+
}
|
|
204
181
|
return true;
|
|
205
182
|
},
|
|
206
183
|
},
|
|
@@ -208,9 +185,12 @@ export class HoleTool implements Extension {
|
|
|
208
185
|
command: "clearHoles",
|
|
209
186
|
title: "Clear Holes",
|
|
210
187
|
handler: () => {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
188
|
+
const configService = this.context?.services.get<ConfigurationService>(
|
|
189
|
+
"ConfigurationService"
|
|
190
|
+
);
|
|
191
|
+
if (configService) {
|
|
192
|
+
configService.update("dieline.holes", []);
|
|
193
|
+
}
|
|
214
194
|
return true;
|
|
215
195
|
},
|
|
216
196
|
},
|
|
@@ -274,6 +254,9 @@ export class HoleTool implements Extension {
|
|
|
274
254
|
|
|
275
255
|
if (!this.currentGeometry) return;
|
|
276
256
|
|
|
257
|
+
const index = target.data?.index ?? -1;
|
|
258
|
+
const holeData = this.holes[index];
|
|
259
|
+
|
|
277
260
|
// Calculate effective geometry based on constraint target
|
|
278
261
|
const effectiveOffset =
|
|
279
262
|
this.constraintTarget === "original"
|
|
@@ -284,13 +267,18 @@ export class HoleTool implements Extension {
|
|
|
284
267
|
width: Math.max(0, this.currentGeometry.width + effectiveOffset * 2),
|
|
285
268
|
height: Math.max(
|
|
286
269
|
0,
|
|
287
|
-
this.currentGeometry.height + effectiveOffset * 2
|
|
270
|
+
this.currentGeometry.height + effectiveOffset * 2
|
|
288
271
|
),
|
|
289
272
|
radius: Math.max(0, this.currentGeometry.radius + effectiveOffset),
|
|
290
273
|
};
|
|
291
274
|
|
|
292
275
|
const p = new Point(target.left, target.top);
|
|
293
|
-
const newPos = this.calculateConstrainedPosition(
|
|
276
|
+
const newPos = this.calculateConstrainedPosition(
|
|
277
|
+
p,
|
|
278
|
+
constraintGeometry,
|
|
279
|
+
holeData?.innerRadius ?? 15,
|
|
280
|
+
holeData?.outerRadius ?? 25
|
|
281
|
+
);
|
|
294
282
|
|
|
295
283
|
target.set({
|
|
296
284
|
left: newPos.x,
|
|
@@ -316,25 +304,6 @@ export class HoleTool implements Extension {
|
|
|
316
304
|
|
|
317
305
|
private initializeHoles() {
|
|
318
306
|
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
307
|
this.redraw();
|
|
339
308
|
this.syncHolesToDieline();
|
|
340
309
|
}
|
|
@@ -383,38 +352,110 @@ export class HoleTool implements Extension {
|
|
|
383
352
|
.getObjects()
|
|
384
353
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
385
354
|
|
|
386
|
-
|
|
387
|
-
|
|
355
|
+
// Sort objects by index
|
|
356
|
+
objects.sort(
|
|
357
|
+
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
// Update holes based on canvas positions
|
|
361
|
+
// We need to preserve original hole properties (radii, anchor)
|
|
362
|
+
// If a hole has an anchor, we update offsetX/Y instead of x/y
|
|
363
|
+
const newHoles = objects.map((obj, i) => {
|
|
364
|
+
const original = this.holes[i];
|
|
365
|
+
const newAbsX = obj.left!;
|
|
366
|
+
const newAbsY = obj.top!;
|
|
367
|
+
|
|
368
|
+
if (original && original.anchor && this.currentGeometry) {
|
|
369
|
+
// Reverse calculate offset from anchor
|
|
370
|
+
const { x, y, width, height } = this.currentGeometry;
|
|
371
|
+
let bx = x;
|
|
372
|
+
let by = y;
|
|
373
|
+
const left = x - width / 2;
|
|
374
|
+
const right = x + width / 2;
|
|
375
|
+
const top = y - height / 2;
|
|
376
|
+
const bottom = y + height / 2;
|
|
377
|
+
|
|
378
|
+
switch (original.anchor) {
|
|
379
|
+
case "top-left":
|
|
380
|
+
bx = left;
|
|
381
|
+
by = top;
|
|
382
|
+
break;
|
|
383
|
+
case "top-center":
|
|
384
|
+
bx = x;
|
|
385
|
+
by = top;
|
|
386
|
+
break;
|
|
387
|
+
case "top-right":
|
|
388
|
+
bx = right;
|
|
389
|
+
by = top;
|
|
390
|
+
break;
|
|
391
|
+
case "center-left":
|
|
392
|
+
bx = left;
|
|
393
|
+
by = y;
|
|
394
|
+
break;
|
|
395
|
+
case "center":
|
|
396
|
+
bx = x;
|
|
397
|
+
by = y;
|
|
398
|
+
break;
|
|
399
|
+
case "center-right":
|
|
400
|
+
bx = right;
|
|
401
|
+
by = y;
|
|
402
|
+
break;
|
|
403
|
+
case "bottom-left":
|
|
404
|
+
bx = left;
|
|
405
|
+
by = bottom;
|
|
406
|
+
break;
|
|
407
|
+
case "bottom-center":
|
|
408
|
+
bx = x;
|
|
409
|
+
by = bottom;
|
|
410
|
+
break;
|
|
411
|
+
case "bottom-right":
|
|
412
|
+
bx = right;
|
|
413
|
+
by = bottom;
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
return {
|
|
418
|
+
...original,
|
|
419
|
+
offsetX: newAbsX - bx,
|
|
420
|
+
offsetY: newAbsY - by,
|
|
421
|
+
// Clear direct coordinates if we use anchor
|
|
422
|
+
x: undefined,
|
|
423
|
+
y: undefined,
|
|
424
|
+
};
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// If no anchor, use normalized coordinates
|
|
428
|
+
const { width, height } = this.canvasService!.canvas;
|
|
429
|
+
const p = Coordinate.normalizePoint(
|
|
430
|
+
{ x: newAbsX, y: newAbsY },
|
|
431
|
+
{ width: width || 800, height: height || 600 }
|
|
432
|
+
);
|
|
433
|
+
|
|
434
|
+
return {
|
|
435
|
+
...original,
|
|
436
|
+
x: p.x,
|
|
437
|
+
y: p.y,
|
|
438
|
+
// Ensure radii are preserved
|
|
439
|
+
innerRadius: original?.innerRadius ?? 15,
|
|
440
|
+
outerRadius: original?.outerRadius ?? 25,
|
|
441
|
+
};
|
|
442
|
+
});
|
|
388
443
|
|
|
444
|
+
this.holes = newHoles;
|
|
389
445
|
this.syncHolesToDieline();
|
|
390
446
|
}
|
|
391
447
|
|
|
392
448
|
private syncHolesToDieline() {
|
|
393
449
|
if (!this.context || !this.canvasService) return;
|
|
394
450
|
|
|
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
451
|
const configService = this.context.services.get<ConfigurationService>(
|
|
401
|
-
"ConfigurationService"
|
|
452
|
+
"ConfigurationService"
|
|
402
453
|
);
|
|
403
454
|
|
|
404
455
|
if (configService) {
|
|
405
456
|
this.isUpdatingConfig = true;
|
|
406
457
|
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);
|
|
458
|
+
configService.update("dieline.holes", this.holes);
|
|
418
459
|
} finally {
|
|
419
460
|
this.isUpdatingConfig = false;
|
|
420
461
|
}
|
|
@@ -424,6 +465,7 @@ export class HoleTool implements Extension {
|
|
|
424
465
|
private redraw() {
|
|
425
466
|
if (!this.canvasService) return;
|
|
426
467
|
const canvas = this.canvasService.canvas;
|
|
468
|
+
const { width, height } = canvas;
|
|
427
469
|
|
|
428
470
|
// Remove existing holes
|
|
429
471
|
const existing = canvas
|
|
@@ -431,16 +473,31 @@ export class HoleTool implements Extension {
|
|
|
431
473
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
432
474
|
existing.forEach((obj) => canvas.remove(obj));
|
|
433
475
|
|
|
434
|
-
const
|
|
476
|
+
const holes = this.holes;
|
|
435
477
|
|
|
436
478
|
if (!holes || holes.length === 0) {
|
|
437
479
|
this.canvasService.requestRenderAll();
|
|
438
480
|
return;
|
|
439
481
|
}
|
|
440
482
|
|
|
483
|
+
// Resolve geometry if needed for anchors
|
|
484
|
+
const geometry = this.currentGeometry || {
|
|
485
|
+
x: (width || 800) / 2,
|
|
486
|
+
y: (height || 600) / 2,
|
|
487
|
+
width: width || 800,
|
|
488
|
+
height: height || 600,
|
|
489
|
+
};
|
|
490
|
+
|
|
441
491
|
holes.forEach((hole, index) => {
|
|
492
|
+
// Resolve position
|
|
493
|
+
const pos = resolveHolePosition(
|
|
494
|
+
hole,
|
|
495
|
+
geometry,
|
|
496
|
+
{ width: width || 800, height: height || 600 }
|
|
497
|
+
);
|
|
498
|
+
|
|
442
499
|
const innerCircle = new Circle({
|
|
443
|
-
radius: innerRadius,
|
|
500
|
+
radius: hole.innerRadius,
|
|
444
501
|
fill: "transparent",
|
|
445
502
|
stroke: "red",
|
|
446
503
|
strokeWidth: 2,
|
|
@@ -449,18 +506,18 @@ export class HoleTool implements Extension {
|
|
|
449
506
|
});
|
|
450
507
|
|
|
451
508
|
const outerCircle = new Circle({
|
|
452
|
-
radius: outerRadius,
|
|
509
|
+
radius: hole.outerRadius,
|
|
453
510
|
fill: "transparent",
|
|
454
511
|
stroke: "#666",
|
|
455
512
|
strokeWidth: 1,
|
|
456
|
-
strokeDashArray:
|
|
513
|
+
strokeDashArray: [5, 5],
|
|
457
514
|
originX: "center",
|
|
458
515
|
originY: "center",
|
|
459
516
|
});
|
|
460
517
|
|
|
461
518
|
const holeGroup = new Group([outerCircle, innerCircle], {
|
|
462
|
-
left:
|
|
463
|
-
top:
|
|
519
|
+
left: pos.x,
|
|
520
|
+
top: pos.y,
|
|
464
521
|
originX: "center",
|
|
465
522
|
originY: "center",
|
|
466
523
|
selectable: true,
|
|
@@ -494,12 +551,6 @@ export class HoleTool implements Extension {
|
|
|
494
551
|
});
|
|
495
552
|
|
|
496
553
|
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
554
|
canvas.bringObjectToFront(holeGroup);
|
|
504
555
|
});
|
|
505
556
|
|
|
@@ -513,9 +564,6 @@ export class HoleTool implements Extension {
|
|
|
513
564
|
public enforceConstraints(): boolean {
|
|
514
565
|
const geometry = this.currentGeometry;
|
|
515
566
|
if (!geometry || !this.canvasService) {
|
|
516
|
-
console.log(
|
|
517
|
-
"[HoleTool] Skipping enforceConstraints: No geometry or canvas service",
|
|
518
|
-
);
|
|
519
567
|
return false;
|
|
520
568
|
}
|
|
521
569
|
|
|
@@ -533,29 +581,27 @@ export class HoleTool implements Extension {
|
|
|
533
581
|
.getObjects()
|
|
534
582
|
.filter((obj: any) => obj.data?.type === "hole-marker");
|
|
535
583
|
|
|
536
|
-
console.log(
|
|
537
|
-
`[HoleTool] Enforcing constraints on ${objects.length} markers`,
|
|
538
|
-
);
|
|
539
|
-
|
|
540
584
|
let changed = false;
|
|
541
585
|
// Sort objects by index to maintain order in options.holes
|
|
542
586
|
objects.sort(
|
|
543
|
-
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
587
|
+
(a: any, b: any) => (a.data?.index ?? 0) - (b.data?.index ?? 0)
|
|
544
588
|
);
|
|
545
589
|
|
|
546
|
-
const newHoles:
|
|
590
|
+
const newHoles: HoleData[] = [];
|
|
547
591
|
|
|
548
|
-
objects.forEach((obj: any) => {
|
|
592
|
+
objects.forEach((obj: any, i: number) => {
|
|
549
593
|
const currentPos = new Point(obj.left, obj.top);
|
|
594
|
+
// We need to pass the hole's radii to calculateConstrainedPosition
|
|
595
|
+
const holeData = this.holes[i];
|
|
596
|
+
|
|
550
597
|
const newPos = this.calculateConstrainedPosition(
|
|
551
598
|
currentPos,
|
|
552
599
|
constraintGeometry,
|
|
600
|
+
holeData?.innerRadius ?? 15,
|
|
601
|
+
holeData?.outerRadius ?? 25
|
|
553
602
|
);
|
|
554
603
|
|
|
555
604
|
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
605
|
obj.set({
|
|
560
606
|
left: newPos.x,
|
|
561
607
|
top: newPos.y,
|
|
@@ -563,23 +609,28 @@ export class HoleTool implements Extension {
|
|
|
563
609
|
obj.setCoords();
|
|
564
610
|
changed = true;
|
|
565
611
|
}
|
|
566
|
-
|
|
612
|
+
|
|
613
|
+
// Update data logic is handled in syncHolesFromCanvas which is called on modified
|
|
614
|
+
// But here we are modifying programmatically.
|
|
615
|
+
// We should probably just let the visual update happen, and then sync?
|
|
616
|
+
// Or just push to newHoles list to verify change?
|
|
567
617
|
});
|
|
568
618
|
|
|
569
619
|
if (changed) {
|
|
570
|
-
|
|
571
|
-
this.
|
|
572
|
-
// We return true instead of syncing directly to avoid recursion
|
|
620
|
+
// If we moved things programmatically, we should update the state
|
|
621
|
+
this.syncHolesFromCanvas();
|
|
573
622
|
return true;
|
|
574
623
|
}
|
|
575
624
|
return false;
|
|
576
625
|
}
|
|
577
626
|
|
|
578
|
-
private calculateConstrainedPosition(
|
|
627
|
+
private calculateConstrainedPosition(
|
|
628
|
+
p: Point,
|
|
629
|
+
g: DielineGeometry,
|
|
630
|
+
innerRadius: number,
|
|
631
|
+
outerRadius: number
|
|
632
|
+
): Point {
|
|
579
633
|
// 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
634
|
const options = {
|
|
584
635
|
...g,
|
|
585
636
|
holes: [], // We don't need holes for boundary calculation
|
|
@@ -599,7 +650,6 @@ export class HoleTool implements Extension {
|
|
|
599
650
|
|
|
600
651
|
// Vector from center to nearest point (approximate normal for convex shapes)
|
|
601
652
|
const center = new Point(g.x, g.y);
|
|
602
|
-
const centerToNearest = nearestP.subtract(center);
|
|
603
653
|
|
|
604
654
|
const distToCenter = p.distanceFrom(center);
|
|
605
655
|
const nearestDistToCenter = nearestP.distanceFrom(center);
|
|
@@ -612,9 +662,9 @@ export class HoleTool implements Extension {
|
|
|
612
662
|
// Clamp distance
|
|
613
663
|
let clampedDist = signedDist;
|
|
614
664
|
if (signedDist > 0) {
|
|
615
|
-
clampedDist = Math.min(signedDist,
|
|
665
|
+
clampedDist = Math.min(signedDist, innerRadius);
|
|
616
666
|
} else {
|
|
617
|
-
clampedDist = Math.max(signedDist, -
|
|
667
|
+
clampedDist = Math.max(signedDist, -outerRadius);
|
|
618
668
|
}
|
|
619
669
|
|
|
620
670
|
// Reconstruct point
|