@pooder/kit 4.1.0 → 4.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/.test-dist/src/CanvasService.js +83 -0
- package/.test-dist/src/ViewportSystem.js +75 -0
- package/.test-dist/src/background.js +203 -0
- package/.test-dist/src/constraints.js +153 -0
- package/.test-dist/src/coordinate.js +74 -0
- package/.test-dist/src/dieline.js +758 -0
- package/.test-dist/src/feature.js +687 -0
- package/.test-dist/src/featureComplete.js +31 -0
- package/.test-dist/src/featureDraft.js +31 -0
- package/.test-dist/src/film.js +167 -0
- package/.test-dist/src/geometry.js +292 -0
- package/.test-dist/src/image.js +421 -0
- package/.test-dist/src/index.js +31 -0
- package/.test-dist/src/mirror.js +104 -0
- package/.test-dist/src/ruler.js +383 -0
- package/.test-dist/src/tracer.js +448 -0
- package/.test-dist/src/units.js +30 -0
- package/.test-dist/src/white-ink.js +310 -0
- package/.test-dist/tests/run.js +60 -0
- package/CHANGELOG.md +6 -0
- package/dist/index.d.mts +50 -5
- package/dist/index.d.ts +50 -5
- package/dist/index.js +544 -297
- package/dist/index.mjs +541 -296
- package/package.json +3 -2
- package/src/CanvasService.ts +7 -0
- package/src/ViewportSystem.ts +92 -0
- package/src/constraints.ts +53 -4
- package/src/dieline.ts +169 -85
- package/src/feature.ts +217 -150
- package/src/featureComplete.ts +45 -0
- package/src/index.ts +1 -0
- package/src/ruler.ts +26 -18
- package/src/units.ts +27 -0
- package/tests/run.ts +81 -0
- package/tsconfig.test.json +15 -0
|
@@ -0,0 +1,758 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DielineTool = void 0;
|
|
4
|
+
const core_1 = require("@pooder/core");
|
|
5
|
+
const fabric_1 = require("fabric");
|
|
6
|
+
const tracer_1 = require("./tracer");
|
|
7
|
+
const units_1 = require("./units");
|
|
8
|
+
const geometry_1 = require("./geometry");
|
|
9
|
+
class DielineTool {
|
|
10
|
+
constructor(options) {
|
|
11
|
+
this.id = "pooder.kit.dieline";
|
|
12
|
+
this.metadata = {
|
|
13
|
+
name: "DielineTool",
|
|
14
|
+
};
|
|
15
|
+
this.state = {
|
|
16
|
+
displayUnit: "mm",
|
|
17
|
+
shape: "rect",
|
|
18
|
+
width: 500,
|
|
19
|
+
height: 500,
|
|
20
|
+
radius: 0,
|
|
21
|
+
offset: 0,
|
|
22
|
+
padding: 140,
|
|
23
|
+
mainLine: {
|
|
24
|
+
width: 2.7,
|
|
25
|
+
color: "#FF0000",
|
|
26
|
+
dashLength: 5,
|
|
27
|
+
style: "solid",
|
|
28
|
+
},
|
|
29
|
+
offsetLine: {
|
|
30
|
+
width: 2.7,
|
|
31
|
+
color: "#FF0000",
|
|
32
|
+
dashLength: 5,
|
|
33
|
+
style: "solid",
|
|
34
|
+
},
|
|
35
|
+
insideColor: "rgba(0,0,0,0)",
|
|
36
|
+
outsideColor: "#ffffff",
|
|
37
|
+
showBleedLines: true,
|
|
38
|
+
features: [],
|
|
39
|
+
};
|
|
40
|
+
if (options) {
|
|
41
|
+
// Deep merge for styles to avoid overwriting defaults with partial objects
|
|
42
|
+
if (options.mainLine) {
|
|
43
|
+
Object.assign(this.state.mainLine, options.mainLine);
|
|
44
|
+
delete options.mainLine;
|
|
45
|
+
}
|
|
46
|
+
if (options.offsetLine) {
|
|
47
|
+
Object.assign(this.state.offsetLine, options.offsetLine);
|
|
48
|
+
delete options.offsetLine;
|
|
49
|
+
}
|
|
50
|
+
Object.assign(this.state, options);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
activate(context) {
|
|
54
|
+
this.context = context;
|
|
55
|
+
this.canvasService = context.services.get("CanvasService");
|
|
56
|
+
if (!this.canvasService) {
|
|
57
|
+
console.warn("CanvasService not found for DielineTool");
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const configService = context.services.get("ConfigurationService");
|
|
61
|
+
if (configService) {
|
|
62
|
+
// Load initial config
|
|
63
|
+
const s = this.state;
|
|
64
|
+
s.displayUnit = configService.get("dieline.displayUnit", s.displayUnit);
|
|
65
|
+
s.shape = configService.get("dieline.shape", s.shape);
|
|
66
|
+
s.width = (0, units_1.parseLengthToMm)(configService.get("dieline.width", s.width), "mm");
|
|
67
|
+
s.height = (0, units_1.parseLengthToMm)(configService.get("dieline.height", s.height), "mm");
|
|
68
|
+
s.radius = (0, units_1.parseLengthToMm)(configService.get("dieline.radius", s.radius), "mm");
|
|
69
|
+
s.padding = configService.get("dieline.padding", s.padding);
|
|
70
|
+
s.offset = (0, units_1.parseLengthToMm)(configService.get("dieline.offset", s.offset), "mm");
|
|
71
|
+
// Main Line
|
|
72
|
+
s.mainLine.width = configService.get("dieline.strokeWidth", s.mainLine.width);
|
|
73
|
+
s.mainLine.color = configService.get("dieline.strokeColor", s.mainLine.color);
|
|
74
|
+
s.mainLine.dashLength = configService.get("dieline.dashLength", s.mainLine.dashLength);
|
|
75
|
+
s.mainLine.style = configService.get("dieline.style", s.mainLine.style);
|
|
76
|
+
// Offset Line
|
|
77
|
+
s.offsetLine.width = configService.get("dieline.offsetStrokeWidth", s.offsetLine.width);
|
|
78
|
+
s.offsetLine.color = configService.get("dieline.offsetStrokeColor", s.offsetLine.color);
|
|
79
|
+
s.offsetLine.dashLength = configService.get("dieline.offsetDashLength", s.offsetLine.dashLength);
|
|
80
|
+
s.offsetLine.style = configService.get("dieline.offsetStyle", s.offsetLine.style);
|
|
81
|
+
s.insideColor = configService.get("dieline.insideColor", s.insideColor);
|
|
82
|
+
s.outsideColor = configService.get("dieline.outsideColor", s.outsideColor);
|
|
83
|
+
s.showBleedLines = configService.get("dieline.showBleedLines", s.showBleedLines);
|
|
84
|
+
s.features = configService.get("dieline.features", s.features);
|
|
85
|
+
s.pathData = configService.get("dieline.pathData", s.pathData);
|
|
86
|
+
// Listen for changes
|
|
87
|
+
configService.onAnyChange((e) => {
|
|
88
|
+
if (e.key.startsWith("dieline.")) {
|
|
89
|
+
switch (e.key) {
|
|
90
|
+
case "dieline.displayUnit":
|
|
91
|
+
s.displayUnit = e.value;
|
|
92
|
+
break;
|
|
93
|
+
case "dieline.shape":
|
|
94
|
+
s.shape = e.value;
|
|
95
|
+
break;
|
|
96
|
+
case "dieline.width":
|
|
97
|
+
s.width = (0, units_1.parseLengthToMm)(e.value, "mm");
|
|
98
|
+
break;
|
|
99
|
+
case "dieline.height":
|
|
100
|
+
s.height = (0, units_1.parseLengthToMm)(e.value, "mm");
|
|
101
|
+
break;
|
|
102
|
+
case "dieline.radius":
|
|
103
|
+
s.radius = (0, units_1.parseLengthToMm)(e.value, "mm");
|
|
104
|
+
break;
|
|
105
|
+
case "dieline.padding":
|
|
106
|
+
s.padding = e.value;
|
|
107
|
+
break;
|
|
108
|
+
case "dieline.offset":
|
|
109
|
+
s.offset = (0, units_1.parseLengthToMm)(e.value, "mm");
|
|
110
|
+
break;
|
|
111
|
+
case "dieline.strokeWidth":
|
|
112
|
+
s.mainLine.width = e.value;
|
|
113
|
+
break;
|
|
114
|
+
case "dieline.strokeColor":
|
|
115
|
+
s.mainLine.color = e.value;
|
|
116
|
+
break;
|
|
117
|
+
case "dieline.dashLength":
|
|
118
|
+
s.mainLine.dashLength = e.value;
|
|
119
|
+
break;
|
|
120
|
+
case "dieline.style":
|
|
121
|
+
s.mainLine.style = e.value;
|
|
122
|
+
break;
|
|
123
|
+
case "dieline.offsetStrokeWidth":
|
|
124
|
+
s.offsetLine.width = e.value;
|
|
125
|
+
break;
|
|
126
|
+
case "dieline.offsetStrokeColor":
|
|
127
|
+
s.offsetLine.color = e.value;
|
|
128
|
+
break;
|
|
129
|
+
case "dieline.offsetDashLength":
|
|
130
|
+
s.offsetLine.dashLength = e.value;
|
|
131
|
+
break;
|
|
132
|
+
case "dieline.offsetStyle":
|
|
133
|
+
s.offsetLine.style = e.value;
|
|
134
|
+
break;
|
|
135
|
+
case "dieline.insideColor":
|
|
136
|
+
s.insideColor = e.value;
|
|
137
|
+
break;
|
|
138
|
+
case "dieline.outsideColor":
|
|
139
|
+
s.outsideColor = e.value;
|
|
140
|
+
break;
|
|
141
|
+
case "dieline.showBleedLines":
|
|
142
|
+
s.showBleedLines = e.value;
|
|
143
|
+
break;
|
|
144
|
+
case "dieline.features":
|
|
145
|
+
s.features = e.value;
|
|
146
|
+
break;
|
|
147
|
+
case "dieline.pathData":
|
|
148
|
+
s.pathData = e.value;
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
this.updateDieline();
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
this.createLayer();
|
|
156
|
+
this.updateDieline();
|
|
157
|
+
}
|
|
158
|
+
deactivate(context) {
|
|
159
|
+
this.destroyLayer();
|
|
160
|
+
this.canvasService = undefined;
|
|
161
|
+
this.context = undefined;
|
|
162
|
+
}
|
|
163
|
+
contribute() {
|
|
164
|
+
const s = this.state;
|
|
165
|
+
return {
|
|
166
|
+
[core_1.ContributionPointIds.CONFIGURATIONS]: [
|
|
167
|
+
{
|
|
168
|
+
id: "dieline.displayUnit",
|
|
169
|
+
type: "select",
|
|
170
|
+
label: "Display Unit",
|
|
171
|
+
options: ["mm", "cm", "in"],
|
|
172
|
+
default: s.displayUnit,
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: "dieline.shape",
|
|
176
|
+
type: "select",
|
|
177
|
+
label: "Shape",
|
|
178
|
+
options: ["rect", "circle", "ellipse", "custom"],
|
|
179
|
+
default: s.shape,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
id: "dieline.width",
|
|
183
|
+
type: "number",
|
|
184
|
+
label: "Width (mm)",
|
|
185
|
+
min: 10,
|
|
186
|
+
max: 2000,
|
|
187
|
+
default: s.width,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "dieline.height",
|
|
191
|
+
type: "number",
|
|
192
|
+
label: "Height (mm)",
|
|
193
|
+
min: 10,
|
|
194
|
+
max: 2000,
|
|
195
|
+
default: s.height,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
id: "dieline.radius",
|
|
199
|
+
type: "number",
|
|
200
|
+
label: "Corner Radius (mm)",
|
|
201
|
+
min: 0,
|
|
202
|
+
max: 500,
|
|
203
|
+
default: s.radius,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
id: "dieline.padding",
|
|
207
|
+
type: "select",
|
|
208
|
+
label: "View Padding",
|
|
209
|
+
options: [0, 10, 20, 40, 60, 100, "2%", "5%", "10%", "15%", "20%"],
|
|
210
|
+
default: s.padding,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
id: "dieline.offset",
|
|
214
|
+
type: "number",
|
|
215
|
+
label: "Bleed Offset (mm)",
|
|
216
|
+
min: -100,
|
|
217
|
+
max: 100,
|
|
218
|
+
default: s.offset,
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
id: "dieline.showBleedLines",
|
|
222
|
+
type: "boolean",
|
|
223
|
+
label: "Show Bleed Lines",
|
|
224
|
+
default: s.showBleedLines,
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "dieline.strokeWidth",
|
|
228
|
+
type: "number",
|
|
229
|
+
label: "Line Width",
|
|
230
|
+
min: 0.1,
|
|
231
|
+
max: 10,
|
|
232
|
+
step: 0.1,
|
|
233
|
+
default: s.mainLine.width,
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "dieline.strokeColor",
|
|
237
|
+
type: "color",
|
|
238
|
+
label: "Line Color",
|
|
239
|
+
default: s.mainLine.color,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: "dieline.dashLength",
|
|
243
|
+
type: "number",
|
|
244
|
+
label: "Dash Length",
|
|
245
|
+
min: 1,
|
|
246
|
+
max: 50,
|
|
247
|
+
default: s.mainLine.dashLength,
|
|
248
|
+
},
|
|
249
|
+
{
|
|
250
|
+
id: "dieline.style",
|
|
251
|
+
type: "select",
|
|
252
|
+
label: "Line Style",
|
|
253
|
+
options: ["solid", "dashed", "hidden"],
|
|
254
|
+
default: s.mainLine.style,
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: "dieline.offsetStrokeWidth",
|
|
258
|
+
type: "number",
|
|
259
|
+
label: "Offset Line Width",
|
|
260
|
+
min: 0.1,
|
|
261
|
+
max: 10,
|
|
262
|
+
step: 0.1,
|
|
263
|
+
default: s.offsetLine.width,
|
|
264
|
+
},
|
|
265
|
+
{
|
|
266
|
+
id: "dieline.offsetStrokeColor",
|
|
267
|
+
type: "color",
|
|
268
|
+
label: "Offset Line Color",
|
|
269
|
+
default: s.offsetLine.color,
|
|
270
|
+
},
|
|
271
|
+
{
|
|
272
|
+
id: "dieline.offsetDashLength",
|
|
273
|
+
type: "number",
|
|
274
|
+
label: "Offset Dash Length",
|
|
275
|
+
min: 1,
|
|
276
|
+
max: 50,
|
|
277
|
+
default: s.offsetLine.dashLength,
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
id: "dieline.offsetStyle",
|
|
281
|
+
type: "select",
|
|
282
|
+
label: "Offset Line Style",
|
|
283
|
+
options: ["solid", "dashed", "hidden"],
|
|
284
|
+
default: s.offsetLine.style,
|
|
285
|
+
},
|
|
286
|
+
{
|
|
287
|
+
id: "dieline.insideColor",
|
|
288
|
+
type: "color",
|
|
289
|
+
label: "Inside Color",
|
|
290
|
+
default: s.insideColor,
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: "dieline.outsideColor",
|
|
294
|
+
type: "color",
|
|
295
|
+
label: "Outside Color",
|
|
296
|
+
default: s.outsideColor,
|
|
297
|
+
},
|
|
298
|
+
{
|
|
299
|
+
id: "dieline.features",
|
|
300
|
+
type: "json",
|
|
301
|
+
label: "Edge Features",
|
|
302
|
+
default: s.features,
|
|
303
|
+
},
|
|
304
|
+
],
|
|
305
|
+
[core_1.ContributionPointIds.COMMANDS]: [
|
|
306
|
+
{
|
|
307
|
+
command: "updateFeaturePosition",
|
|
308
|
+
title: "Update Feature Position",
|
|
309
|
+
handler: (groupId, x, y) => {
|
|
310
|
+
const configService = this.context?.services.get("ConfigurationService");
|
|
311
|
+
if (!configService)
|
|
312
|
+
return;
|
|
313
|
+
const features = configService.get("dieline.features") || [];
|
|
314
|
+
let changed = false;
|
|
315
|
+
const newFeatures = features.map((f) => {
|
|
316
|
+
if (f.groupId === groupId) {
|
|
317
|
+
if (f.x !== x || f.y !== y) {
|
|
318
|
+
changed = true;
|
|
319
|
+
return { ...f, x, y };
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
return f;
|
|
323
|
+
});
|
|
324
|
+
if (changed) {
|
|
325
|
+
configService.update("dieline.features", newFeatures);
|
|
326
|
+
}
|
|
327
|
+
},
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
command: "getGeometry",
|
|
331
|
+
title: "Get Geometry",
|
|
332
|
+
handler: () => {
|
|
333
|
+
return this.getGeometry();
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
command: "exportCutImage",
|
|
338
|
+
title: "Export Cut Image",
|
|
339
|
+
handler: () => {
|
|
340
|
+
return this.exportCutImage();
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
{
|
|
344
|
+
command: "detectEdge",
|
|
345
|
+
title: "Detect Edge from Image",
|
|
346
|
+
handler: async (imageUrl, options) => {
|
|
347
|
+
try {
|
|
348
|
+
const pathData = await tracer_1.ImageTracer.trace(imageUrl, options);
|
|
349
|
+
const bounds = (0, geometry_1.getPathBounds)(pathData);
|
|
350
|
+
const currentMax = Math.max(s.width, s.height);
|
|
351
|
+
const scale = currentMax / Math.max(bounds.width, bounds.height);
|
|
352
|
+
const newWidth = bounds.width * scale;
|
|
353
|
+
const newHeight = bounds.height * scale;
|
|
354
|
+
return {
|
|
355
|
+
pathData,
|
|
356
|
+
width: newWidth,
|
|
357
|
+
height: newHeight,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
catch (e) {
|
|
361
|
+
console.error("Edge detection failed", e);
|
|
362
|
+
throw e;
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
getLayer() {
|
|
370
|
+
return this.canvasService?.getLayer("dieline-overlay");
|
|
371
|
+
}
|
|
372
|
+
createLayer() {
|
|
373
|
+
if (!this.canvasService)
|
|
374
|
+
return;
|
|
375
|
+
const width = this.canvasService.canvas.width || 800;
|
|
376
|
+
const height = this.canvasService.canvas.height || 600;
|
|
377
|
+
const layer = this.canvasService.createLayer("dieline-overlay", {
|
|
378
|
+
width,
|
|
379
|
+
height,
|
|
380
|
+
selectable: false,
|
|
381
|
+
evented: false,
|
|
382
|
+
});
|
|
383
|
+
this.canvasService.canvas.bringObjectToFront(layer);
|
|
384
|
+
// Ensure above user layer
|
|
385
|
+
const userLayer = this.canvasService.getLayer("user");
|
|
386
|
+
if (userLayer) {
|
|
387
|
+
const userIndex = this.canvasService.canvas
|
|
388
|
+
.getObjects()
|
|
389
|
+
.indexOf(userLayer);
|
|
390
|
+
this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
destroyLayer() {
|
|
394
|
+
if (!this.canvasService)
|
|
395
|
+
return;
|
|
396
|
+
const layer = this.getLayer();
|
|
397
|
+
if (layer) {
|
|
398
|
+
this.canvasService.canvas.remove(layer);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
createHatchPattern(color = "rgba(0, 0, 0, 0.3)") {
|
|
402
|
+
if (typeof document === "undefined") {
|
|
403
|
+
return undefined;
|
|
404
|
+
}
|
|
405
|
+
const size = 20;
|
|
406
|
+
const canvas = document.createElement("canvas");
|
|
407
|
+
canvas.width = size;
|
|
408
|
+
canvas.height = size;
|
|
409
|
+
const ctx = canvas.getContext("2d");
|
|
410
|
+
if (ctx) {
|
|
411
|
+
// Transparent background
|
|
412
|
+
ctx.clearRect(0, 0, size, size);
|
|
413
|
+
// Draw diagonal /
|
|
414
|
+
ctx.strokeStyle = color;
|
|
415
|
+
ctx.lineWidth = 1;
|
|
416
|
+
ctx.beginPath();
|
|
417
|
+
ctx.moveTo(0, size);
|
|
418
|
+
ctx.lineTo(size, 0);
|
|
419
|
+
ctx.stroke();
|
|
420
|
+
}
|
|
421
|
+
// @ts-ignore
|
|
422
|
+
return new fabric_1.Pattern({ source: canvas, repetition: "repeat" });
|
|
423
|
+
}
|
|
424
|
+
resolvePadding(containerWidth, containerHeight) {
|
|
425
|
+
if (typeof this.state.padding === "number") {
|
|
426
|
+
return this.state.padding;
|
|
427
|
+
}
|
|
428
|
+
if (typeof this.state.padding === "string") {
|
|
429
|
+
if (this.state.padding.endsWith("%")) {
|
|
430
|
+
const percent = parseFloat(this.state.padding) / 100;
|
|
431
|
+
return Math.min(containerWidth, containerHeight) * percent;
|
|
432
|
+
}
|
|
433
|
+
return parseFloat(this.state.padding) || 0;
|
|
434
|
+
}
|
|
435
|
+
return 0;
|
|
436
|
+
}
|
|
437
|
+
updateDieline(emitEvent = true) {
|
|
438
|
+
if (!this.canvasService)
|
|
439
|
+
return;
|
|
440
|
+
const layer = this.getLayer();
|
|
441
|
+
if (!layer)
|
|
442
|
+
return;
|
|
443
|
+
const { displayUnit, shape, radius, offset, mainLine, offsetLine, insideColor, outsideColor, showBleedLines, features, } = this.state;
|
|
444
|
+
const { width, height } = this.state;
|
|
445
|
+
const canvasW = this.canvasService.canvas.width || 800;
|
|
446
|
+
const canvasH = this.canvasService.canvas.height || 600;
|
|
447
|
+
// Calculate Layout based on Physical Dimensions and Canvas Size
|
|
448
|
+
// Add padding to avoid edge hugging
|
|
449
|
+
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
450
|
+
// Update Viewport System
|
|
451
|
+
this.canvasService.viewport.setPadding(paddingPx);
|
|
452
|
+
this.canvasService.viewport.updatePhysical(width, height);
|
|
453
|
+
const layout = this.canvasService.viewport.layout;
|
|
454
|
+
const scale = layout.scale;
|
|
455
|
+
const cx = layout.offsetX + layout.width / 2;
|
|
456
|
+
const cy = layout.offsetY + layout.height / 2;
|
|
457
|
+
// Scaled dimensions for rendering (Pixels)
|
|
458
|
+
const visualWidth = layout.width;
|
|
459
|
+
const visualHeight = layout.height;
|
|
460
|
+
const visualRadius = radius * scale;
|
|
461
|
+
const visualOffset = offset * scale;
|
|
462
|
+
// Clear existing objects
|
|
463
|
+
layer.remove(...layer.getObjects());
|
|
464
|
+
// Scale Features for Geometry Generation
|
|
465
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
466
|
+
const featureScale = scale;
|
|
467
|
+
return {
|
|
468
|
+
...f,
|
|
469
|
+
x: f.x,
|
|
470
|
+
y: f.y,
|
|
471
|
+
width: (f.width || 0) * featureScale,
|
|
472
|
+
height: (f.height || 0) * featureScale,
|
|
473
|
+
radius: (f.radius || 0) * featureScale,
|
|
474
|
+
};
|
|
475
|
+
});
|
|
476
|
+
// Split features into Cut (Physical) and Visual (All)
|
|
477
|
+
const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
|
|
478
|
+
// 1. Draw Mask (Outside)
|
|
479
|
+
const cutW = Math.max(0, visualWidth + visualOffset * 2);
|
|
480
|
+
const cutH = Math.max(0, visualHeight + visualOffset * 2);
|
|
481
|
+
const cutR = visualRadius === 0 ? 0 : Math.max(0, visualRadius + visualOffset);
|
|
482
|
+
// Use Paper.js to generate the complex mask path
|
|
483
|
+
const maskPathData = (0, geometry_1.generateMaskPath)({
|
|
484
|
+
canvasWidth: canvasW,
|
|
485
|
+
canvasHeight: canvasH,
|
|
486
|
+
shape,
|
|
487
|
+
width: cutW,
|
|
488
|
+
height: cutH,
|
|
489
|
+
radius: cutR,
|
|
490
|
+
x: cx,
|
|
491
|
+
y: cy,
|
|
492
|
+
features: cutFeatures,
|
|
493
|
+
pathData: this.state.pathData,
|
|
494
|
+
});
|
|
495
|
+
const mask = new fabric_1.Path(maskPathData, {
|
|
496
|
+
fill: outsideColor,
|
|
497
|
+
stroke: null,
|
|
498
|
+
selectable: false,
|
|
499
|
+
evented: false,
|
|
500
|
+
originX: "left",
|
|
501
|
+
originY: "top",
|
|
502
|
+
left: 0,
|
|
503
|
+
top: 0,
|
|
504
|
+
});
|
|
505
|
+
layer.add(mask);
|
|
506
|
+
// 2. Draw Inside Fill (Dieline Shape itself, merged with features if needed)
|
|
507
|
+
if (insideColor &&
|
|
508
|
+
insideColor !== "transparent" &&
|
|
509
|
+
insideColor !== "rgba(0,0,0,0)") {
|
|
510
|
+
// Generate path for the product shape (Paper) = Dieline +/- Features
|
|
511
|
+
const productPathData = (0, geometry_1.generateDielinePath)({
|
|
512
|
+
shape,
|
|
513
|
+
width: cutW,
|
|
514
|
+
height: cutH,
|
|
515
|
+
radius: cutR,
|
|
516
|
+
x: cx,
|
|
517
|
+
y: cy,
|
|
518
|
+
features: cutFeatures, // Use same features as mask for consistency
|
|
519
|
+
pathData: this.state.pathData,
|
|
520
|
+
canvasWidth: canvasW,
|
|
521
|
+
canvasHeight: canvasH,
|
|
522
|
+
});
|
|
523
|
+
const insideObj = new fabric_1.Path(productPathData, {
|
|
524
|
+
fill: insideColor,
|
|
525
|
+
stroke: null,
|
|
526
|
+
selectable: false,
|
|
527
|
+
evented: false,
|
|
528
|
+
originX: "left", // paper.js paths are absolute
|
|
529
|
+
originY: "top",
|
|
530
|
+
});
|
|
531
|
+
layer.add(insideObj);
|
|
532
|
+
}
|
|
533
|
+
// 3. Draw Bleed Zone (Hatch Fill) and Offset Border
|
|
534
|
+
if (offset !== 0) {
|
|
535
|
+
const bleedPathData = (0, geometry_1.generateBleedZonePath)({
|
|
536
|
+
shape,
|
|
537
|
+
width: visualWidth,
|
|
538
|
+
height: visualHeight,
|
|
539
|
+
radius: visualRadius,
|
|
540
|
+
x: cx,
|
|
541
|
+
y: cy,
|
|
542
|
+
features: cutFeatures,
|
|
543
|
+
pathData: this.state.pathData,
|
|
544
|
+
canvasWidth: canvasW,
|
|
545
|
+
canvasHeight: canvasH,
|
|
546
|
+
}, {
|
|
547
|
+
shape,
|
|
548
|
+
width: cutW,
|
|
549
|
+
height: cutH,
|
|
550
|
+
radius: cutR,
|
|
551
|
+
x: cx,
|
|
552
|
+
y: cy,
|
|
553
|
+
features: cutFeatures,
|
|
554
|
+
pathData: this.state.pathData,
|
|
555
|
+
canvasWidth: canvasW,
|
|
556
|
+
canvasHeight: canvasH,
|
|
557
|
+
}, visualOffset);
|
|
558
|
+
// Use solid red for hatch lines to match dieline, background is transparent
|
|
559
|
+
if (showBleedLines !== false) {
|
|
560
|
+
const pattern = this.createHatchPattern(mainLine.color);
|
|
561
|
+
if (pattern) {
|
|
562
|
+
const bleedObj = new fabric_1.Path(bleedPathData, {
|
|
563
|
+
fill: pattern,
|
|
564
|
+
stroke: null,
|
|
565
|
+
selectable: false,
|
|
566
|
+
evented: false,
|
|
567
|
+
objectCaching: false,
|
|
568
|
+
originX: "left",
|
|
569
|
+
originY: "top",
|
|
570
|
+
});
|
|
571
|
+
layer.add(bleedObj);
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
// Offset Dieline Border
|
|
575
|
+
const offsetPathData = (0, geometry_1.generateDielinePath)({
|
|
576
|
+
shape,
|
|
577
|
+
width: cutW,
|
|
578
|
+
height: cutH,
|
|
579
|
+
radius: cutR,
|
|
580
|
+
x: cx,
|
|
581
|
+
y: cy,
|
|
582
|
+
features: cutFeatures,
|
|
583
|
+
pathData: this.state.pathData,
|
|
584
|
+
canvasWidth: canvasW,
|
|
585
|
+
canvasHeight: canvasH,
|
|
586
|
+
});
|
|
587
|
+
const offsetBorderObj = new fabric_1.Path(offsetPathData, {
|
|
588
|
+
fill: null,
|
|
589
|
+
stroke: offsetLine.style === "hidden" ? null : offsetLine.color,
|
|
590
|
+
strokeWidth: offsetLine.width,
|
|
591
|
+
strokeDashArray: offsetLine.style === "dashed"
|
|
592
|
+
? [offsetLine.dashLength, offsetLine.dashLength]
|
|
593
|
+
: undefined,
|
|
594
|
+
selectable: false,
|
|
595
|
+
evented: false,
|
|
596
|
+
originX: "left",
|
|
597
|
+
originY: "top",
|
|
598
|
+
});
|
|
599
|
+
layer.add(offsetBorderObj);
|
|
600
|
+
}
|
|
601
|
+
// 4. Draw Dieline (Visual Border)
|
|
602
|
+
const borderPathData = (0, geometry_1.generateDielinePath)({
|
|
603
|
+
shape,
|
|
604
|
+
width: visualWidth,
|
|
605
|
+
height: visualHeight,
|
|
606
|
+
radius: visualRadius,
|
|
607
|
+
x: cx,
|
|
608
|
+
y: cy,
|
|
609
|
+
features: absoluteFeatures,
|
|
610
|
+
pathData: this.state.pathData,
|
|
611
|
+
canvasWidth: canvasW,
|
|
612
|
+
canvasHeight: canvasH,
|
|
613
|
+
});
|
|
614
|
+
const borderObj = new fabric_1.Path(borderPathData, {
|
|
615
|
+
fill: "transparent",
|
|
616
|
+
stroke: mainLine.style === "hidden" ? null : mainLine.color,
|
|
617
|
+
strokeWidth: mainLine.width,
|
|
618
|
+
strokeDashArray: mainLine.style === "dashed"
|
|
619
|
+
? [mainLine.dashLength, mainLine.dashLength]
|
|
620
|
+
: undefined,
|
|
621
|
+
selectable: false,
|
|
622
|
+
evented: false,
|
|
623
|
+
originX: "left",
|
|
624
|
+
originY: "top",
|
|
625
|
+
});
|
|
626
|
+
layer.add(borderObj);
|
|
627
|
+
// Enforce z-index: Dieline > User
|
|
628
|
+
const userLayer = this.canvasService.getLayer("user");
|
|
629
|
+
if (layer && userLayer) {
|
|
630
|
+
const layerIndex = this.canvasService.canvas.getObjects().indexOf(layer);
|
|
631
|
+
const userIndex = this.canvasService.canvas
|
|
632
|
+
.getObjects()
|
|
633
|
+
.indexOf(userLayer);
|
|
634
|
+
if (layerIndex < userIndex) {
|
|
635
|
+
this.canvasService.canvas.moveObjectTo(layer, userIndex + 1);
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
else {
|
|
639
|
+
// If no user layer, just bring to front (safe default)
|
|
640
|
+
this.canvasService.canvas.bringObjectToFront(layer);
|
|
641
|
+
}
|
|
642
|
+
// Ensure Ruler is above Dieline if it exists
|
|
643
|
+
const rulerLayer = this.canvasService.getLayer("ruler-overlay");
|
|
644
|
+
if (rulerLayer) {
|
|
645
|
+
this.canvasService.canvas.bringObjectToFront(rulerLayer);
|
|
646
|
+
}
|
|
647
|
+
layer.dirty = true;
|
|
648
|
+
this.canvasService.requestRenderAll();
|
|
649
|
+
// Emit change event so other tools (like FeatureTool) can react
|
|
650
|
+
if (emitEvent && this.context) {
|
|
651
|
+
const geometry = this.getGeometry();
|
|
652
|
+
if (geometry) {
|
|
653
|
+
this.context.eventBus.emit("dieline:geometry:change", geometry);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
getGeometry() {
|
|
658
|
+
if (!this.canvasService)
|
|
659
|
+
return null;
|
|
660
|
+
const { displayUnit, shape, width, height, radius, offset, mainLine, pathData, } = this.state;
|
|
661
|
+
const canvasW = this.canvasService.canvas.width || 800;
|
|
662
|
+
const canvasH = this.canvasService.canvas.height || 600;
|
|
663
|
+
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
664
|
+
// Update Viewport System (Ensure it's up to date)
|
|
665
|
+
this.canvasService.viewport.setPadding(paddingPx);
|
|
666
|
+
this.canvasService.viewport.updatePhysical(width, height);
|
|
667
|
+
const layout = this.canvasService.viewport.layout;
|
|
668
|
+
const scale = layout.scale;
|
|
669
|
+
const cx = layout.offsetX + layout.width / 2;
|
|
670
|
+
const cy = layout.offsetY + layout.height / 2;
|
|
671
|
+
const visualWidth = layout.width;
|
|
672
|
+
const visualHeight = layout.height;
|
|
673
|
+
return {
|
|
674
|
+
shape,
|
|
675
|
+
unit: "mm",
|
|
676
|
+
displayUnit,
|
|
677
|
+
x: cx,
|
|
678
|
+
y: cy,
|
|
679
|
+
width: visualWidth,
|
|
680
|
+
height: visualHeight,
|
|
681
|
+
radius: radius * scale,
|
|
682
|
+
offset: offset * scale,
|
|
683
|
+
scale,
|
|
684
|
+
strokeWidth: mainLine.width,
|
|
685
|
+
pathData,
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
async exportCutImage() {
|
|
689
|
+
if (!this.canvasService)
|
|
690
|
+
return null;
|
|
691
|
+
const userLayer = this.canvasService.getLayer("user");
|
|
692
|
+
if (!userLayer)
|
|
693
|
+
return null;
|
|
694
|
+
// 1. Generate Path Data
|
|
695
|
+
const { shape, width, height, radius, features, pathData } = this.state;
|
|
696
|
+
const canvasW = this.canvasService.canvas.width || 800;
|
|
697
|
+
const canvasH = this.canvasService.canvas.height || 600;
|
|
698
|
+
const paddingPx = this.resolvePadding(canvasW, canvasH);
|
|
699
|
+
// Update Viewport System
|
|
700
|
+
this.canvasService.viewport.setPadding(paddingPx);
|
|
701
|
+
this.canvasService.viewport.updatePhysical(width, height);
|
|
702
|
+
const layout = this.canvasService.viewport.layout;
|
|
703
|
+
const scale = layout.scale;
|
|
704
|
+
const cx = layout.offsetX + layout.width / 2;
|
|
705
|
+
const cy = layout.offsetY + layout.height / 2;
|
|
706
|
+
const visualWidth = layout.width;
|
|
707
|
+
const visualHeight = layout.height;
|
|
708
|
+
const visualRadius = radius * scale;
|
|
709
|
+
// Scale Features
|
|
710
|
+
const absoluteFeatures = (features || []).map((f) => {
|
|
711
|
+
const featureScale = scale;
|
|
712
|
+
return {
|
|
713
|
+
...f,
|
|
714
|
+
x: f.x,
|
|
715
|
+
y: f.y,
|
|
716
|
+
width: (f.width || 0) * featureScale,
|
|
717
|
+
height: (f.height || 0) * featureScale,
|
|
718
|
+
radius: (f.radius || 0) * featureScale,
|
|
719
|
+
};
|
|
720
|
+
});
|
|
721
|
+
const cutFeatures = absoluteFeatures.filter((f) => !f.skipCut);
|
|
722
|
+
const generatedPathData = (0, geometry_1.generateDielinePath)({
|
|
723
|
+
shape,
|
|
724
|
+
width: visualWidth,
|
|
725
|
+
height: visualHeight,
|
|
726
|
+
radius: visualRadius,
|
|
727
|
+
x: cx,
|
|
728
|
+
y: cy,
|
|
729
|
+
features: cutFeatures,
|
|
730
|
+
pathData,
|
|
731
|
+
canvasWidth: canvasW,
|
|
732
|
+
canvasHeight: canvasH,
|
|
733
|
+
});
|
|
734
|
+
// 2. Prepare for Export
|
|
735
|
+
const clonedLayer = await userLayer.clone();
|
|
736
|
+
const clipPath = new fabric_1.Path(generatedPathData, {
|
|
737
|
+
originX: "left",
|
|
738
|
+
originY: "top",
|
|
739
|
+
left: 0,
|
|
740
|
+
top: 0,
|
|
741
|
+
absolutePositioned: true,
|
|
742
|
+
});
|
|
743
|
+
clonedLayer.clipPath = clipPath;
|
|
744
|
+
// 3. Calculate Crop Area (The Dieline Bounds)
|
|
745
|
+
const bounds = clipPath.getBoundingRect();
|
|
746
|
+
// 4. Export
|
|
747
|
+
const dataUrl = clonedLayer.toDataURL({
|
|
748
|
+
format: "png",
|
|
749
|
+
multiplier: 2,
|
|
750
|
+
left: bounds.left,
|
|
751
|
+
top: bounds.top,
|
|
752
|
+
width: bounds.width,
|
|
753
|
+
height: bounds.height,
|
|
754
|
+
});
|
|
755
|
+
return dataUrl;
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
exports.DielineTool = DielineTool;
|