@pooder/kit 3.5.0 → 4.0.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 +11 -0
- package/dist/index.d.mts +15 -5
- package/dist/index.d.ts +15 -5
- package/dist/index.js +148 -40
- package/dist/index.mjs +148 -40
- package/package.json +2 -2
- package/src/CanvasService.ts +89 -65
- package/src/dieline.ts +12 -22
- package/src/feature.ts +74 -31
- package/src/geometry.ts +76 -27
- package/src/image.ts +41 -0
package/src/dieline.ts
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
generateMaskPath,
|
|
15
15
|
generateBleedZonePath,
|
|
16
16
|
getPathBounds,
|
|
17
|
-
|
|
17
|
+
DielineFeature,
|
|
18
18
|
} from "./geometry";
|
|
19
19
|
|
|
20
20
|
export interface DielineGeometry {
|
|
@@ -52,7 +52,7 @@ export interface DielineState {
|
|
|
52
52
|
insideColor: string;
|
|
53
53
|
outsideColor: string;
|
|
54
54
|
showBleedLines: boolean;
|
|
55
|
-
features:
|
|
55
|
+
features: DielineFeature[];
|
|
56
56
|
pathData?: string;
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -512,12 +512,8 @@ export class DielineTool implements Extension {
|
|
|
512
512
|
};
|
|
513
513
|
});
|
|
514
514
|
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
);
|
|
518
|
-
const offsetFeatures = absoluteFeatures.filter(
|
|
519
|
-
(f) => f.target === "offset" || f.target === "both",
|
|
520
|
-
);
|
|
515
|
+
// Split features into Cut (Physical) and Visual (All)
|
|
516
|
+
const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
|
|
521
517
|
|
|
522
518
|
// 1. Draw Mask (Outside)
|
|
523
519
|
const cutW = Math.max(0, visualWidth + visualOffset * 2);
|
|
@@ -525,10 +521,6 @@ export class DielineTool implements Extension {
|
|
|
525
521
|
const cutR =
|
|
526
522
|
visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
527
523
|
|
|
528
|
-
// If no bleed offset, mask should match the original dieline (including its features)
|
|
529
|
-
// If bleed offset exists (positive or negative), mask matches bleed line (which only includes offset features)
|
|
530
|
-
const maskFeatures = visualOffset !== 0 ? offsetFeatures : originalFeatures;
|
|
531
|
-
|
|
532
524
|
// Use Paper.js to generate the complex mask path
|
|
533
525
|
const maskPathData = generateMaskPath({
|
|
534
526
|
canvasWidth: canvasW,
|
|
@@ -539,7 +531,7 @@ export class DielineTool implements Extension {
|
|
|
539
531
|
radius: cutR,
|
|
540
532
|
x: cx,
|
|
541
533
|
y: cy,
|
|
542
|
-
features:
|
|
534
|
+
features: cutFeatures,
|
|
543
535
|
pathData: this.state.pathData,
|
|
544
536
|
});
|
|
545
537
|
|
|
@@ -569,7 +561,7 @@ export class DielineTool implements Extension {
|
|
|
569
561
|
radius: cutR,
|
|
570
562
|
x: cx,
|
|
571
563
|
y: cy,
|
|
572
|
-
features:
|
|
564
|
+
features: cutFeatures, // Use same features as mask for consistency
|
|
573
565
|
pathData: this.state.pathData,
|
|
574
566
|
canvasWidth: canvasW,
|
|
575
567
|
canvasHeight: canvasH,
|
|
@@ -596,7 +588,7 @@ export class DielineTool implements Extension {
|
|
|
596
588
|
radius: visualRadius,
|
|
597
589
|
x: cx,
|
|
598
590
|
y: cy,
|
|
599
|
-
features:
|
|
591
|
+
features: cutFeatures,
|
|
600
592
|
pathData: this.state.pathData,
|
|
601
593
|
canvasWidth: canvasW,
|
|
602
594
|
canvasHeight: canvasH,
|
|
@@ -608,7 +600,7 @@ export class DielineTool implements Extension {
|
|
|
608
600
|
radius: cutR,
|
|
609
601
|
x: cx,
|
|
610
602
|
y: cy,
|
|
611
|
-
features:
|
|
603
|
+
features: cutFeatures,
|
|
612
604
|
pathData: this.state.pathData,
|
|
613
605
|
canvasWidth: canvasW,
|
|
614
606
|
canvasHeight: canvasH,
|
|
@@ -641,7 +633,7 @@ export class DielineTool implements Extension {
|
|
|
641
633
|
radius: cutR,
|
|
642
634
|
x: cx,
|
|
643
635
|
y: cy,
|
|
644
|
-
features:
|
|
636
|
+
features: cutFeatures,
|
|
645
637
|
pathData: this.state.pathData,
|
|
646
638
|
canvasWidth: canvasW,
|
|
647
639
|
canvasHeight: canvasH,
|
|
@@ -671,7 +663,7 @@ export class DielineTool implements Extension {
|
|
|
671
663
|
radius: visualRadius,
|
|
672
664
|
x: cx,
|
|
673
665
|
y: cy,
|
|
674
|
-
features:
|
|
666
|
+
features: absoluteFeatures,
|
|
675
667
|
pathData: this.state.pathData,
|
|
676
668
|
canvasWidth: canvasW,
|
|
677
669
|
canvasHeight: canvasH,
|
|
@@ -798,9 +790,7 @@ export class DielineTool implements Extension {
|
|
|
798
790
|
};
|
|
799
791
|
});
|
|
800
792
|
|
|
801
|
-
const
|
|
802
|
-
(f) => !f.target || f.target === "original" || f.target === "both",
|
|
803
|
-
);
|
|
793
|
+
const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
|
|
804
794
|
|
|
805
795
|
const generatedPathData = generateDielinePath({
|
|
806
796
|
shape,
|
|
@@ -809,7 +799,7 @@ export class DielineTool implements Extension {
|
|
|
809
799
|
radius: visualRadius,
|
|
810
800
|
x: cx,
|
|
811
801
|
y: cy,
|
|
812
|
-
features:
|
|
802
|
+
features: cutFeatures,
|
|
813
803
|
pathData,
|
|
814
804
|
canvasWidth: canvasW,
|
|
815
805
|
canvasHeight: canvasH,
|
package/src/feature.ts
CHANGED
|
@@ -11,7 +11,7 @@ import CanvasService from "./CanvasService";
|
|
|
11
11
|
import { DielineGeometry } from "./dieline";
|
|
12
12
|
import {
|
|
13
13
|
getNearestPointOnDieline,
|
|
14
|
-
|
|
14
|
+
DielineFeature,
|
|
15
15
|
resolveFeaturePosition,
|
|
16
16
|
} from "./geometry";
|
|
17
17
|
import { Coordinate } from "./coordinate";
|
|
@@ -23,10 +23,11 @@ export class FeatureTool implements Extension {
|
|
|
23
23
|
name: "FeatureTool",
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
private features:
|
|
26
|
+
private features: DielineFeature[] = [];
|
|
27
27
|
private canvasService?: CanvasService;
|
|
28
28
|
private context?: ExtensionContext;
|
|
29
29
|
private isUpdatingConfig = false;
|
|
30
|
+
private isToolActive = false;
|
|
30
31
|
|
|
31
32
|
private handleMoving: ((e: any) => void) | null = null;
|
|
32
33
|
private handleModified: ((e: any) => void) | null = null;
|
|
@@ -37,7 +38,7 @@ export class FeatureTool implements Extension {
|
|
|
37
38
|
|
|
38
39
|
constructor(
|
|
39
40
|
options?: Partial<{
|
|
40
|
-
features:
|
|
41
|
+
features: DielineFeature[];
|
|
41
42
|
}>,
|
|
42
43
|
) {
|
|
43
44
|
if (options) {
|
|
@@ -70,15 +71,44 @@ export class FeatureTool implements Extension {
|
|
|
70
71
|
});
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
// Listen to tool activation
|
|
75
|
+
context.eventBus.on("tool:activated", this.onToolActivated);
|
|
76
|
+
|
|
73
77
|
this.setup();
|
|
74
78
|
}
|
|
75
79
|
|
|
76
80
|
deactivate(context: ExtensionContext) {
|
|
81
|
+
context.eventBus.off("tool:activated", this.onToolActivated);
|
|
77
82
|
this.teardown();
|
|
78
83
|
this.canvasService = undefined;
|
|
79
84
|
this.context = undefined;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
private onToolActivated = (event: { id: string }) => {
|
|
88
|
+
this.isToolActive = event.id === this.id;
|
|
89
|
+
this.updateVisibility();
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
private updateVisibility() {
|
|
93
|
+
if (!this.canvasService) return;
|
|
94
|
+
const canvas = this.canvasService.canvas;
|
|
95
|
+
const markers = canvas
|
|
96
|
+
.getObjects()
|
|
97
|
+
.filter((obj: any) => obj.data?.type === "feature-marker");
|
|
98
|
+
|
|
99
|
+
markers.forEach((marker: any) => {
|
|
100
|
+
// If tool active, allow selection. If not, disable selection.
|
|
101
|
+
// Also might want to hide them entirely or just disable interaction.
|
|
102
|
+
// Assuming we only want to see/edit holes when tool is active.
|
|
103
|
+
marker.set({
|
|
104
|
+
visible: this.isToolActive, // Or just selectable: false if we want them visible but locked
|
|
105
|
+
selectable: this.isToolActive,
|
|
106
|
+
evented: this.isToolActive
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
canvas.requestRenderAll();
|
|
110
|
+
}
|
|
111
|
+
|
|
82
112
|
contribute() {
|
|
83
113
|
return {
|
|
84
114
|
[ContributionPointIds.COMMANDS]: [
|
|
@@ -131,10 +161,10 @@ export class FeatureTool implements Extension {
|
|
|
131
161
|
const defaultSize = Coordinate.convertUnit(10, "mm", unit);
|
|
132
162
|
|
|
133
163
|
// Default to top edge center
|
|
134
|
-
const newFeature:
|
|
164
|
+
const newFeature: DielineFeature = {
|
|
135
165
|
id: Date.now().toString(),
|
|
136
166
|
operation: type,
|
|
137
|
-
|
|
167
|
+
placement: "edge",
|
|
138
168
|
shape: "rect",
|
|
139
169
|
x: 0.5,
|
|
140
170
|
y: 0, // Top edge
|
|
@@ -147,7 +177,7 @@ export class FeatureTool implements Extension {
|
|
|
147
177
|
const current = configService.get(
|
|
148
178
|
"dieline.features",
|
|
149
179
|
[],
|
|
150
|
-
) as
|
|
180
|
+
) as DielineFeature[];
|
|
151
181
|
configService.update("dieline.features", [...current, newFeature]);
|
|
152
182
|
}
|
|
153
183
|
return true;
|
|
@@ -167,11 +197,12 @@ export class FeatureTool implements Extension {
|
|
|
167
197
|
const timestamp = Date.now();
|
|
168
198
|
|
|
169
199
|
// 1. Lug (Outer) - Add
|
|
170
|
-
const lug:
|
|
200
|
+
const lug: DielineFeature = {
|
|
171
201
|
id: `${timestamp}-lug`,
|
|
172
202
|
groupId,
|
|
173
203
|
operation: "add",
|
|
174
204
|
shape: "circle",
|
|
205
|
+
placement: "edge",
|
|
175
206
|
x: 0.5,
|
|
176
207
|
y: 0,
|
|
177
208
|
radius: lugRadius, // 20mm
|
|
@@ -179,11 +210,12 @@ export class FeatureTool implements Extension {
|
|
|
179
210
|
};
|
|
180
211
|
|
|
181
212
|
// 2. Hole (Inner) - Subtract
|
|
182
|
-
const hole:
|
|
213
|
+
const hole: DielineFeature = {
|
|
183
214
|
id: `${timestamp}-hole`,
|
|
184
215
|
groupId,
|
|
185
216
|
operation: "subtract",
|
|
186
217
|
shape: "circle",
|
|
218
|
+
placement: "edge",
|
|
187
219
|
x: 0.5,
|
|
188
220
|
y: 0,
|
|
189
221
|
radius: holeRadius, // 15mm
|
|
@@ -194,7 +226,7 @@ export class FeatureTool implements Extension {
|
|
|
194
226
|
const current = configService.get(
|
|
195
227
|
"dieline.features",
|
|
196
228
|
[],
|
|
197
|
-
) as
|
|
229
|
+
) as DielineFeature[];
|
|
198
230
|
configService.update("dieline.features", [...current, lug, hole]);
|
|
199
231
|
}
|
|
200
232
|
return true;
|
|
@@ -202,19 +234,10 @@ export class FeatureTool implements Extension {
|
|
|
202
234
|
|
|
203
235
|
private getGeometryForFeature(
|
|
204
236
|
geometry: DielineGeometry,
|
|
205
|
-
feature?:
|
|
237
|
+
feature?: DielineFeature,
|
|
206
238
|
): DielineGeometry {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
...geometry,
|
|
210
|
-
width: geometry.width + geometry.offset * 2,
|
|
211
|
-
height: geometry.height + geometry.offset * 2,
|
|
212
|
-
radius:
|
|
213
|
-
geometry.radius === 0
|
|
214
|
-
? 0
|
|
215
|
-
: Math.max(0, geometry.radius + geometry.offset),
|
|
216
|
-
};
|
|
217
|
-
}
|
|
239
|
+
// Legacy support or specialized scaling can go here if needed
|
|
240
|
+
// Currently all features operate on the base geometry (or scaled version of it)
|
|
218
241
|
return geometry;
|
|
219
242
|
}
|
|
220
243
|
|
|
@@ -258,7 +281,7 @@ export class FeatureTool implements Extension {
|
|
|
258
281
|
if (!this.currentGeometry) return;
|
|
259
282
|
|
|
260
283
|
// Determine feature to use for snapping context
|
|
261
|
-
let feature:
|
|
284
|
+
let feature: DielineFeature | undefined;
|
|
262
285
|
if (target.data?.isGroup) {
|
|
263
286
|
const indices = target.data?.indices as number[];
|
|
264
287
|
if (indices && indices.length > 0) {
|
|
@@ -288,7 +311,7 @@ export class FeatureTool implements Extension {
|
|
|
288
311
|
const minDim = Math.min(target.getScaledWidth(), target.getScaledHeight());
|
|
289
312
|
const limit = Math.max(0, minDim / 2 - markerStrokeWidth);
|
|
290
313
|
|
|
291
|
-
const snapped = this.constrainPosition(p, geometry, limit);
|
|
314
|
+
const snapped = this.constrainPosition(p, geometry, limit, feature);
|
|
292
315
|
|
|
293
316
|
target.set({
|
|
294
317
|
left: snapped.x,
|
|
@@ -409,8 +432,23 @@ export class FeatureTool implements Extension {
|
|
|
409
432
|
private constrainPosition(
|
|
410
433
|
p: Point,
|
|
411
434
|
geometry: DielineGeometry,
|
|
412
|
-
limit: number
|
|
435
|
+
limit: number,
|
|
436
|
+
feature?: DielineFeature
|
|
413
437
|
): { x: number; y: number } {
|
|
438
|
+
if (feature && feature.placement === "internal") {
|
|
439
|
+
// Constrain to bounds
|
|
440
|
+
// geometry.x/y is center
|
|
441
|
+
const minX = geometry.x - geometry.width / 2;
|
|
442
|
+
const maxX = geometry.x + geometry.width / 2;
|
|
443
|
+
const minY = geometry.y - geometry.height / 2;
|
|
444
|
+
const maxY = geometry.y + geometry.height / 2;
|
|
445
|
+
|
|
446
|
+
return {
|
|
447
|
+
x: Math.max(minX, Math.min(maxX, p.x)),
|
|
448
|
+
y: Math.max(minY, Math.min(maxY, p.y))
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
414
452
|
// Use geometry helper to find nearest point on Base Shape
|
|
415
453
|
// geometry object matches GeometryOptions structure required by getNearestPointOnDieline
|
|
416
454
|
// except for 'features' which we don't need for base shape snapping
|
|
@@ -502,9 +540,9 @@ export class FeatureTool implements Extension {
|
|
|
502
540
|
const finalScale = scale;
|
|
503
541
|
|
|
504
542
|
// Group features by groupId
|
|
505
|
-
const groups: { [key: string]: { feature:
|
|
543
|
+
const groups: { [key: string]: { feature: DielineFeature; index: number }[] } =
|
|
506
544
|
{};
|
|
507
|
-
const singles: { feature:
|
|
545
|
+
const singles: { feature: DielineFeature; index: number }[] = [];
|
|
508
546
|
|
|
509
547
|
this.features.forEach((f, i) => {
|
|
510
548
|
if (f.groupId) {
|
|
@@ -517,7 +555,7 @@ export class FeatureTool implements Extension {
|
|
|
517
555
|
|
|
518
556
|
// Helper to create marker shape
|
|
519
557
|
const createMarkerShape = (
|
|
520
|
-
feature:
|
|
558
|
+
feature: DielineFeature,
|
|
521
559
|
pos: { x: number; y: number },
|
|
522
560
|
) => {
|
|
523
561
|
// Features are in the same unit as geometry.unit
|
|
@@ -578,7 +616,9 @@ export class FeatureTool implements Extension {
|
|
|
578
616
|
const marker = createMarkerShape(feature, pos);
|
|
579
617
|
|
|
580
618
|
marker.set({
|
|
581
|
-
|
|
619
|
+
visible: this.isToolActive,
|
|
620
|
+
selectable: this.isToolActive,
|
|
621
|
+
evented: this.isToolActive,
|
|
582
622
|
hasControls: false,
|
|
583
623
|
hasBorders: false,
|
|
584
624
|
hoverCursor: "move",
|
|
@@ -633,7 +673,9 @@ export class FeatureTool implements Extension {
|
|
|
633
673
|
});
|
|
634
674
|
|
|
635
675
|
const groupObj = new Group(shapes, {
|
|
636
|
-
|
|
676
|
+
visible: this.isToolActive,
|
|
677
|
+
selectable: this.isToolActive,
|
|
678
|
+
evented: this.isToolActive,
|
|
637
679
|
hasControls: false,
|
|
638
680
|
hasBorders: false,
|
|
639
681
|
hoverCursor: "move",
|
|
@@ -689,7 +731,7 @@ export class FeatureTool implements Extension {
|
|
|
689
731
|
|
|
690
732
|
markers.forEach((marker: any) => {
|
|
691
733
|
// Find associated feature
|
|
692
|
-
let feature:
|
|
734
|
+
let feature: DielineFeature | undefined;
|
|
693
735
|
if (marker.data?.isGroup) {
|
|
694
736
|
const indices = marker.data?.indices as number[];
|
|
695
737
|
if (indices && indices.length > 0) {
|
|
@@ -714,7 +756,8 @@ export class FeatureTool implements Extension {
|
|
|
714
756
|
const snapped = this.constrainPosition(
|
|
715
757
|
new Point(marker.left, marker.top),
|
|
716
758
|
geometry,
|
|
717
|
-
limit
|
|
759
|
+
limit,
|
|
760
|
+
feature
|
|
718
761
|
);
|
|
719
762
|
marker.set({ left: snapped.x, top: snapped.y });
|
|
720
763
|
marker.setCoords();
|
package/src/geometry.ts
CHANGED
|
@@ -3,20 +3,21 @@ import paper from "paper";
|
|
|
3
3
|
export type FeatureOperation = "add" | "subtract";
|
|
4
4
|
export type FeatureShape = "rect" | "circle";
|
|
5
5
|
|
|
6
|
-
export interface
|
|
6
|
+
export interface DielineFeature {
|
|
7
7
|
id: string;
|
|
8
|
-
groupId?: string;
|
|
8
|
+
groupId?: string;
|
|
9
9
|
operation: FeatureOperation;
|
|
10
10
|
shape: FeatureShape;
|
|
11
|
-
x: number;
|
|
12
|
-
y: number;
|
|
13
|
-
width?: number;
|
|
14
|
-
height?: number;
|
|
15
|
-
radius?: number;
|
|
16
|
-
rotation?: number;
|
|
17
|
-
|
|
18
|
-
color?: string;
|
|
19
|
-
strokeDash?: number[];
|
|
11
|
+
x: number;
|
|
12
|
+
y: number;
|
|
13
|
+
width?: number;
|
|
14
|
+
height?: number;
|
|
15
|
+
radius?: number;
|
|
16
|
+
rotation?: number;
|
|
17
|
+
placement?: "edge" | "internal";
|
|
18
|
+
color?: string;
|
|
19
|
+
strokeDash?: number[];
|
|
20
|
+
skipCut?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
22
23
|
export interface GeometryOptions {
|
|
@@ -26,7 +27,7 @@ export interface GeometryOptions {
|
|
|
26
27
|
radius: number;
|
|
27
28
|
x: number;
|
|
28
29
|
y: number;
|
|
29
|
-
features: Array<
|
|
30
|
+
features: Array<DielineFeature>;
|
|
30
31
|
pathData?: string;
|
|
31
32
|
canvasWidth?: number;
|
|
32
33
|
canvasHeight?: number;
|
|
@@ -41,7 +42,7 @@ export interface MaskGeometryOptions extends GeometryOptions {
|
|
|
41
42
|
* Resolves the absolute position of a feature based on normalized coordinates.
|
|
42
43
|
*/
|
|
43
44
|
export function resolveFeaturePosition(
|
|
44
|
-
feature:
|
|
45
|
+
feature: DielineFeature,
|
|
45
46
|
geometry: { x: number; y: number; width: number; height: number },
|
|
46
47
|
): { x: number; y: number } {
|
|
47
48
|
const { x, y, width, height } = geometry;
|
|
@@ -116,7 +117,7 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
116
117
|
* Creates a Paper.js Item for a single feature.
|
|
117
118
|
*/
|
|
118
119
|
function createFeatureItem(
|
|
119
|
-
feature:
|
|
120
|
+
feature: DielineFeature,
|
|
120
121
|
center: paper.Point,
|
|
121
122
|
): paper.PathItem {
|
|
122
123
|
let item: paper.PathItem;
|
|
@@ -147,20 +148,24 @@ function createFeatureItem(
|
|
|
147
148
|
}
|
|
148
149
|
|
|
149
150
|
/**
|
|
150
|
-
* Internal helper to generate the
|
|
151
|
-
* Logic: (Base U Adds) - Subtracts
|
|
151
|
+
* Internal helper to generate the Perimeter Shape (Base + Edge Features).
|
|
152
152
|
*/
|
|
153
|
-
function
|
|
153
|
+
function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
154
154
|
// 1. Create Base Shape
|
|
155
155
|
let mainShape = createBaseShape(options);
|
|
156
156
|
|
|
157
157
|
const { features } = options;
|
|
158
158
|
|
|
159
159
|
if (features && features.length > 0) {
|
|
160
|
+
// Filter for Edge Features (Default or explicit 'edge')
|
|
161
|
+
const edgeFeatures = features.filter(
|
|
162
|
+
(f) => !f.placement || f.placement === "edge",
|
|
163
|
+
);
|
|
164
|
+
|
|
160
165
|
const adds: paper.PathItem[] = [];
|
|
161
166
|
const subtracts: paper.PathItem[] = [];
|
|
162
167
|
|
|
163
|
-
|
|
168
|
+
edgeFeatures.forEach((f) => {
|
|
164
169
|
const pos = resolveFeaturePosition(f, options);
|
|
165
170
|
const center = new paper.Point(pos.x, pos.y);
|
|
166
171
|
const item = createFeatureItem(f, center);
|
|
@@ -174,9 +179,6 @@ function getDielineShape(options: GeometryOptions): paper.PathItem {
|
|
|
174
179
|
|
|
175
180
|
// 2. Process Additions (Union)
|
|
176
181
|
if (adds.length > 0) {
|
|
177
|
-
// Unite all additions first to avoid artifacts?
|
|
178
|
-
// Or unite one by one to mainShape?
|
|
179
|
-
// Unite one by one is safer for simple logic.
|
|
180
182
|
for (const item of adds) {
|
|
181
183
|
try {
|
|
182
184
|
const temp = mainShape.unite(item);
|
|
@@ -209,6 +211,49 @@ function getDielineShape(options: GeometryOptions): paper.PathItem {
|
|
|
209
211
|
return mainShape;
|
|
210
212
|
}
|
|
211
213
|
|
|
214
|
+
/**
|
|
215
|
+
* Applies Internal/Surface features to a shape.
|
|
216
|
+
*/
|
|
217
|
+
function applySurfaceFeatures(
|
|
218
|
+
shape: paper.PathItem,
|
|
219
|
+
features: DielineFeature[],
|
|
220
|
+
options: GeometryOptions,
|
|
221
|
+
): paper.PathItem {
|
|
222
|
+
const internalFeatures = features.filter((f) => f.placement === "internal");
|
|
223
|
+
|
|
224
|
+
if (internalFeatures.length === 0) return shape;
|
|
225
|
+
|
|
226
|
+
let result = shape;
|
|
227
|
+
|
|
228
|
+
// Internal features are usually subtractive (holes)
|
|
229
|
+
// But we support 'add' too (islands? maybe just unite)
|
|
230
|
+
|
|
231
|
+
for (const f of internalFeatures) {
|
|
232
|
+
const pos = resolveFeaturePosition(f, options);
|
|
233
|
+
const center = new paper.Point(pos.x, pos.y);
|
|
234
|
+
const item = createFeatureItem(f, center);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
if (f.operation === "add") {
|
|
238
|
+
const temp = result.unite(item);
|
|
239
|
+
result.remove();
|
|
240
|
+
item.remove();
|
|
241
|
+
result = temp;
|
|
242
|
+
} else {
|
|
243
|
+
const temp = result.subtract(item);
|
|
244
|
+
result.remove();
|
|
245
|
+
item.remove();
|
|
246
|
+
result = temp;
|
|
247
|
+
}
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error("Geometry: Failed to apply surface feature", e);
|
|
250
|
+
item.remove();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
|
|
212
257
|
/**
|
|
213
258
|
* Generates the path data for the Dieline (Product Shape).
|
|
214
259
|
*/
|
|
@@ -218,10 +263,11 @@ export function generateDielinePath(options: GeometryOptions): string {
|
|
|
218
263
|
ensurePaper(paperWidth, paperHeight);
|
|
219
264
|
paper.project.activeLayer.removeChildren();
|
|
220
265
|
|
|
221
|
-
const
|
|
266
|
+
const perimeter = getPerimeterShape(options);
|
|
267
|
+
const finalShape = applySurfaceFeatures(perimeter, options.features, options);
|
|
222
268
|
|
|
223
|
-
const pathData =
|
|
224
|
-
|
|
269
|
+
const pathData = finalShape.pathData;
|
|
270
|
+
finalShape.remove();
|
|
225
271
|
|
|
226
272
|
return pathData;
|
|
227
273
|
}
|
|
@@ -241,7 +287,8 @@ export function generateMaskPath(options: MaskGeometryOptions): string {
|
|
|
241
287
|
size: [canvasWidth, canvasHeight],
|
|
242
288
|
});
|
|
243
289
|
|
|
244
|
-
const
|
|
290
|
+
const perimeter = getPerimeterShape(options);
|
|
291
|
+
const mainShape = applySurfaceFeatures(perimeter, options.features, options);
|
|
245
292
|
|
|
246
293
|
const finalMask = maskRect.subtract(mainShape);
|
|
247
294
|
|
|
@@ -270,10 +317,12 @@ export function generateBleedZonePath(
|
|
|
270
317
|
paper.project.activeLayer.removeChildren();
|
|
271
318
|
|
|
272
319
|
// 1. Generate Original Shape
|
|
273
|
-
const
|
|
320
|
+
const pOriginal = getPerimeterShape(originalOptions);
|
|
321
|
+
const shapeOriginal = applySurfaceFeatures(pOriginal, originalOptions.features, originalOptions);
|
|
274
322
|
|
|
275
323
|
// 2. Generate Offset Shape
|
|
276
|
-
const
|
|
324
|
+
const pOffset = getPerimeterShape(offsetOptions);
|
|
325
|
+
const shapeOffset = applySurfaceFeatures(pOffset, offsetOptions.features, offsetOptions);
|
|
277
326
|
|
|
278
327
|
// 3. Calculate Difference
|
|
279
328
|
let bleedZone: paper.PathItem;
|
package/src/image.ts
CHANGED
|
@@ -33,6 +33,7 @@ export class ImageTool implements Extension {
|
|
|
33
33
|
private canvasService?: CanvasService;
|
|
34
34
|
private context?: ExtensionContext;
|
|
35
35
|
private isUpdatingConfig = false;
|
|
36
|
+
private isToolActive = false;
|
|
36
37
|
|
|
37
38
|
activate(context: ExtensionContext) {
|
|
38
39
|
this.context = context;
|
|
@@ -42,6 +43,9 @@ export class ImageTool implements Extension {
|
|
|
42
43
|
return;
|
|
43
44
|
}
|
|
44
45
|
|
|
46
|
+
// Listen to tool activation
|
|
47
|
+
context.eventBus.on("tool:activated", this.onToolActivated);
|
|
48
|
+
|
|
45
49
|
const configService = context.services.get<ConfigurationService>(
|
|
46
50
|
"ConfigurationService",
|
|
47
51
|
);
|
|
@@ -65,6 +69,8 @@ export class ImageTool implements Extension {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
deactivate(context: ExtensionContext) {
|
|
72
|
+
context.eventBus.off("tool:activated", this.onToolActivated);
|
|
73
|
+
|
|
68
74
|
if (this.canvasService) {
|
|
69
75
|
const layer = this.canvasService.getLayer("user");
|
|
70
76
|
if (layer) {
|
|
@@ -79,6 +85,23 @@ export class ImageTool implements Extension {
|
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
87
|
|
|
88
|
+
private onToolActivated = (event: { id: string }) => {
|
|
89
|
+
this.isToolActive = event.id === this.id;
|
|
90
|
+
this.updateInteractivity();
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
private updateInteractivity() {
|
|
94
|
+
this.objectMap.forEach((obj) => {
|
|
95
|
+
obj.set({
|
|
96
|
+
selectable: this.isToolActive,
|
|
97
|
+
evented: this.isToolActive,
|
|
98
|
+
hasControls: this.isToolActive,
|
|
99
|
+
hasBorders: this.isToolActive,
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
this.canvasService?.requestRenderAll();
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
contribute() {
|
|
83
106
|
return {
|
|
84
107
|
[ContributionPointIds.CONFIGURATIONS]: [
|
|
@@ -291,6 +314,20 @@ export class ImageTool implements Extension {
|
|
|
291
314
|
this.items.forEach((item, index) => {
|
|
292
315
|
let obj = this.objectMap.get(item.id);
|
|
293
316
|
|
|
317
|
+
// Check if URL changed, if so remove object to force reload
|
|
318
|
+
// We assume Fabric object has getSrc() or we check data.url if we stored it
|
|
319
|
+
// Since we don't store url on object easily accessible without casting,
|
|
320
|
+
// let's rely on checking if we need to reload.
|
|
321
|
+
// Actually, standard Fabric Image doesn't expose src easily on type without casting to any.
|
|
322
|
+
if (obj && (obj as any).getSrc) {
|
|
323
|
+
const currentSrc = (obj as any).getSrc();
|
|
324
|
+
if (currentSrc !== item.url) {
|
|
325
|
+
layer.remove(obj);
|
|
326
|
+
this.objectMap.delete(item.id);
|
|
327
|
+
obj = undefined;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
294
331
|
if (!obj) {
|
|
295
332
|
// New object, load it
|
|
296
333
|
this.loadImage(item, layer, layout);
|
|
@@ -375,6 +412,10 @@ export class ImageTool implements Extension {
|
|
|
375
412
|
data: { id: item.id },
|
|
376
413
|
uniformScaling: true,
|
|
377
414
|
lockScalingFlip: true,
|
|
415
|
+
selectable: this.isToolActive,
|
|
416
|
+
evented: this.isToolActive,
|
|
417
|
+
hasControls: this.isToolActive,
|
|
418
|
+
hasBorders: this.isToolActive,
|
|
378
419
|
});
|
|
379
420
|
|
|
380
421
|
image.setControlsVisibility({
|