@pooder/kit 3.4.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 +17 -0
- package/dist/index.d.mts +62 -56
- package/dist/index.d.ts +62 -56
- package/dist/index.js +978 -862
- package/dist/index.mjs +977 -861
- package/package.json +2 -2
- package/src/CanvasService.ts +25 -1
- package/src/background.ts +230 -230
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +272 -218
- package/src/feature.ts +767 -0
- package/src/film.ts +194 -194
- package/src/geometry.ts +172 -375
- package/src/image.ts +512 -471
- package/src/index.ts +1 -1
- package/src/mirror.ts +128 -128
- package/src/ruler.ts +500 -500
- package/src/tracer.ts +570 -486
- package/src/white-ink.ts +373 -373
- package/src/hole.ts +0 -786
package/src/geometry.ts
CHANGED
|
@@ -1,98 +1,23 @@
|
|
|
1
1
|
import paper from "paper";
|
|
2
2
|
|
|
3
|
-
export type
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
innerRadius: number;
|
|
22
|
-
outerRadius: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function resolveHolePosition(
|
|
26
|
-
hole: HoleData,
|
|
27
|
-
geometry: { x: number; y: number; width: number; height: number },
|
|
28
|
-
canvasSize: { width: number; height: number },
|
|
29
|
-
): { x: number; y: number } {
|
|
30
|
-
if (hole.anchor) {
|
|
31
|
-
const { x, y, width, height } = geometry;
|
|
32
|
-
let bx = x; // center x
|
|
33
|
-
let by = y; // center y
|
|
34
|
-
|
|
35
|
-
// Calculate anchor base position based on shape bounds
|
|
36
|
-
// Note: geometry.x/y is the CENTER of the shape
|
|
37
|
-
const left = x - width / 2;
|
|
38
|
-
const right = x + width / 2;
|
|
39
|
-
const top = y - height / 2;
|
|
40
|
-
const bottom = y + height / 2;
|
|
41
|
-
|
|
42
|
-
switch (hole.anchor) {
|
|
43
|
-
case "top-left":
|
|
44
|
-
bx = left;
|
|
45
|
-
by = top;
|
|
46
|
-
break;
|
|
47
|
-
case "top-center":
|
|
48
|
-
bx = x;
|
|
49
|
-
by = top;
|
|
50
|
-
break;
|
|
51
|
-
case "top-right":
|
|
52
|
-
bx = right;
|
|
53
|
-
by = top;
|
|
54
|
-
break;
|
|
55
|
-
case "center-left":
|
|
56
|
-
bx = left;
|
|
57
|
-
by = y;
|
|
58
|
-
break;
|
|
59
|
-
case "center":
|
|
60
|
-
bx = x;
|
|
61
|
-
by = y;
|
|
62
|
-
break;
|
|
63
|
-
case "center-right":
|
|
64
|
-
bx = right;
|
|
65
|
-
by = y;
|
|
66
|
-
break;
|
|
67
|
-
case "bottom-left":
|
|
68
|
-
bx = left;
|
|
69
|
-
by = bottom;
|
|
70
|
-
break;
|
|
71
|
-
case "bottom-center":
|
|
72
|
-
bx = x;
|
|
73
|
-
by = bottom;
|
|
74
|
-
break;
|
|
75
|
-
case "bottom-right":
|
|
76
|
-
bx = right;
|
|
77
|
-
by = bottom;
|
|
78
|
-
break;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
x: bx + (hole.offsetX || 0),
|
|
83
|
-
y: by + (hole.offsetY || 0),
|
|
84
|
-
};
|
|
85
|
-
} else if (hole.x !== undefined && hole.y !== undefined) {
|
|
86
|
-
// Legacy / Direct coordinates (Normalized relative to Dieline Geometry)
|
|
87
|
-
// Formula: absolute = normalized * width + (center - width/2)
|
|
88
|
-
// This handles padding correctly.
|
|
89
|
-
const { x, width, y, height } = geometry;
|
|
90
|
-
return {
|
|
91
|
-
x: hole.x * width + (x - width / 2) + (hole.offsetX || 0),
|
|
92
|
-
y: hole.y * height + (y - height / 2) + (hole.offsetY || 0),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return { x: 0, y: 0 };
|
|
3
|
+
export type FeatureOperation = "add" | "subtract";
|
|
4
|
+
export type FeatureShape = "rect" | "circle";
|
|
5
|
+
|
|
6
|
+
export interface DielineFeature {
|
|
7
|
+
id: string;
|
|
8
|
+
groupId?: string;
|
|
9
|
+
operation: FeatureOperation;
|
|
10
|
+
shape: FeatureShape;
|
|
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;
|
|
96
21
|
}
|
|
97
22
|
|
|
98
23
|
export interface GeometryOptions {
|
|
@@ -102,7 +27,7 @@ export interface GeometryOptions {
|
|
|
102
27
|
radius: number;
|
|
103
28
|
x: number;
|
|
104
29
|
y: number;
|
|
105
|
-
|
|
30
|
+
features: Array<DielineFeature>;
|
|
106
31
|
pathData?: string;
|
|
107
32
|
canvasWidth?: number;
|
|
108
33
|
canvasHeight?: number;
|
|
@@ -113,6 +38,24 @@ export interface MaskGeometryOptions extends GeometryOptions {
|
|
|
113
38
|
canvasHeight: number;
|
|
114
39
|
}
|
|
115
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Resolves the absolute position of a feature based on normalized coordinates.
|
|
43
|
+
*/
|
|
44
|
+
export function resolveFeaturePosition(
|
|
45
|
+
feature: DielineFeature,
|
|
46
|
+
geometry: { x: number; y: number; width: number; height: number },
|
|
47
|
+
): { x: number; y: number } {
|
|
48
|
+
const { x, y, width, height } = geometry;
|
|
49
|
+
// geometry.x/y is the Center.
|
|
50
|
+
const left = x - width / 2;
|
|
51
|
+
const top = y - height / 2;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
x: left + feature.x * width,
|
|
55
|
+
y: top + feature.y * height,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
116
59
|
/**
|
|
117
60
|
* Initializes paper.js project if not already initialized.
|
|
118
61
|
*/
|
|
@@ -153,9 +96,6 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
153
96
|
path.pathData = pathData;
|
|
154
97
|
// Align center
|
|
155
98
|
path.position = center;
|
|
156
|
-
// Scale to match width/height if needed?
|
|
157
|
-
// For now, assume pathData is correct size, but we might want to support resizing.
|
|
158
|
-
// If width/height are provided and different from bounds, we could scale.
|
|
159
99
|
if (
|
|
160
100
|
width > 0 &&
|
|
161
101
|
height > 0 &&
|
|
@@ -166,7 +106,6 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
166
106
|
}
|
|
167
107
|
return path;
|
|
168
108
|
} else {
|
|
169
|
-
// Fallback
|
|
170
109
|
return new paper.Path.Rectangle({
|
|
171
110
|
point: [x - width / 2, y - height / 2],
|
|
172
111
|
size: [Math.max(0, width), Math.max(0, height)],
|
|
@@ -175,225 +114,148 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
|
|
|
175
114
|
}
|
|
176
115
|
|
|
177
116
|
/**
|
|
178
|
-
* Creates
|
|
179
|
-
* For Rect/Circle, we can just adjust params.
|
|
180
|
-
* For Custom shapes, we need a true offset algorithm (Paper.js doesn't have a robust one built-in for all cases,
|
|
181
|
-
* but we can simulate it or use a simple scaling if offset is small, OR rely on a library like Clipper.js.
|
|
182
|
-
* However, since we want to avoid heavy deps, let's try a simple approach:
|
|
183
|
-
* If it's a simple shape, we re-create it.
|
|
184
|
-
* If it's custom, we unfortunately have to scale it for now as a poor-man's offset,
|
|
185
|
-
* UNLESS we implement a stroke expansion.
|
|
186
|
-
*
|
|
187
|
-
* Stroke Expansion Trick:
|
|
188
|
-
* 1. Create path
|
|
189
|
-
* 2. Set strokeWidth = offset * 2
|
|
190
|
-
* 3. Convert stroke to path (paper.js has path.expand())
|
|
191
|
-
* 4. Union original + expanded (for positive offset) or Subtract (for negative).
|
|
117
|
+
* Creates a Paper.js Item for a single feature.
|
|
192
118
|
*/
|
|
193
|
-
function
|
|
194
|
-
|
|
195
|
-
|
|
119
|
+
function createFeatureItem(
|
|
120
|
+
feature: DielineFeature,
|
|
121
|
+
center: paper.Point,
|
|
196
122
|
): paper.PathItem {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
radius:
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
stroker.strokeColor = new paper.Color("black");
|
|
217
|
-
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
218
|
-
// Round join usually looks better for offsets
|
|
219
|
-
stroker.strokeJoin = "round";
|
|
220
|
-
stroker.strokeCap = "round";
|
|
221
|
-
|
|
222
|
-
// Expand stroke to path
|
|
223
|
-
// @ts-ignore - paper.js types might be missing expand depending on version, but it exists in recent versions
|
|
224
|
-
// If expand is not available, we might fallback to scaling.
|
|
225
|
-
// Assuming modern paper.js
|
|
226
|
-
let expanded: paper.Item;
|
|
227
|
-
try {
|
|
228
|
-
// @ts-ignore
|
|
229
|
-
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
230
|
-
} catch (e) {
|
|
231
|
-
// Fallback if expand fails or not present
|
|
232
|
-
stroker.remove();
|
|
233
|
-
// Fallback to scaling (imperfect)
|
|
234
|
-
const scaleX =
|
|
235
|
-
(original.bounds.width + offset * 2) / original.bounds.width;
|
|
236
|
-
const scaleY =
|
|
237
|
-
(original.bounds.height + offset * 2) / original.bounds.height;
|
|
238
|
-
original.scale(scaleX, scaleY);
|
|
239
|
-
return original;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
stroker.remove();
|
|
243
|
-
|
|
244
|
-
// The expanded stroke is a "ring".
|
|
245
|
-
// For positive offset: Union(Original, Ring)
|
|
246
|
-
// For negative offset: Subtract(Original, Ring) ? No, that makes a hole.
|
|
247
|
-
// For negative offset: We want the "inner" boundary of the ring.
|
|
248
|
-
|
|
249
|
-
// Actually, expand() returns a Group or Path.
|
|
250
|
-
// If it's a closed path, the ring has an outer and inner boundary.
|
|
251
|
-
|
|
252
|
-
let result: paper.PathItem;
|
|
253
|
-
|
|
254
|
-
if (offset > 0) {
|
|
255
|
-
// @ts-ignore
|
|
256
|
-
result = original.unite(expanded);
|
|
257
|
-
} else {
|
|
258
|
-
// For negative offset (shrink), we want the original MINUS the stroke?
|
|
259
|
-
// No, the stroke is centered on the line.
|
|
260
|
-
// So the inner edge of the stroke is at -offset.
|
|
261
|
-
// We want the area INSIDE the inner edge.
|
|
262
|
-
// That is Original SUBTRACT the Ring?
|
|
263
|
-
// Yes, if we subtract the ring, we lose the border area.
|
|
264
|
-
// @ts-ignore
|
|
265
|
-
result = original.subtract(expanded);
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// Cleanup
|
|
269
|
-
original.remove();
|
|
270
|
-
expanded.remove();
|
|
123
|
+
let item: paper.PathItem;
|
|
124
|
+
|
|
125
|
+
if (feature.shape === "rect") {
|
|
126
|
+
const w = feature.width || 10;
|
|
127
|
+
const h = feature.height || 10;
|
|
128
|
+
const r = feature.radius || 0;
|
|
129
|
+
item = new paper.Path.Rectangle({
|
|
130
|
+
point: [center.x - w / 2, center.y - h / 2],
|
|
131
|
+
size: [w, h],
|
|
132
|
+
radius: r,
|
|
133
|
+
});
|
|
134
|
+
} else {
|
|
135
|
+
// Circle
|
|
136
|
+
const r = feature.radius || 5;
|
|
137
|
+
item = new paper.Path.Circle({
|
|
138
|
+
center: center,
|
|
139
|
+
radius: r,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
271
142
|
|
|
272
|
-
|
|
143
|
+
if (feature.rotation) {
|
|
144
|
+
item.rotate(feature.rotation, center);
|
|
273
145
|
}
|
|
274
146
|
|
|
275
|
-
return
|
|
147
|
+
return item;
|
|
276
148
|
}
|
|
277
149
|
|
|
278
150
|
/**
|
|
279
|
-
* Internal helper to generate the
|
|
280
|
-
* Caller is responsible for cleanup.
|
|
151
|
+
* Internal helper to generate the Perimeter Shape (Base + Edge Features).
|
|
281
152
|
*/
|
|
282
|
-
function
|
|
153
|
+
function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
283
154
|
// 1. Create Base Shape
|
|
284
155
|
let mainShape = createBaseShape(options);
|
|
285
156
|
|
|
286
|
-
const {
|
|
287
|
-
|
|
288
|
-
if (
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
})
|
|
305
|
-
: new paper.Path.Circle({
|
|
306
|
-
center: center,
|
|
307
|
-
radius: hole.outerRadius,
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
// REMOVED: Intersects check. We want to process all holes defined in config.
|
|
311
|
-
// If a hole is completely outside, it might form an island, but that's better than missing it.
|
|
312
|
-
// Users can remove the hole if they don't want it.
|
|
313
|
-
|
|
314
|
-
// Create Cut (Inner Radius)
|
|
315
|
-
const cut =
|
|
316
|
-
hole.shape === "square"
|
|
317
|
-
? new paper.Path.Rectangle({
|
|
318
|
-
point: [
|
|
319
|
-
center.x - hole.innerRadius,
|
|
320
|
-
center.y - hole.innerRadius,
|
|
321
|
-
],
|
|
322
|
-
size: [hole.innerRadius * 2, hole.innerRadius * 2],
|
|
323
|
-
})
|
|
324
|
-
: new paper.Path.Circle({
|
|
325
|
-
center: center,
|
|
326
|
-
radius: hole.innerRadius,
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
// Union Lugs
|
|
330
|
-
if (!lugsPath) {
|
|
331
|
-
lugsPath = lug;
|
|
157
|
+
const { features } = options;
|
|
158
|
+
|
|
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
|
+
|
|
165
|
+
const adds: paper.PathItem[] = [];
|
|
166
|
+
const subtracts: paper.PathItem[] = [];
|
|
167
|
+
|
|
168
|
+
edgeFeatures.forEach((f) => {
|
|
169
|
+
const pos = resolveFeaturePosition(f, options);
|
|
170
|
+
const center = new paper.Point(pos.x, pos.y);
|
|
171
|
+
const item = createFeatureItem(f, center);
|
|
172
|
+
|
|
173
|
+
if (f.operation === "add") {
|
|
174
|
+
adds.push(item);
|
|
332
175
|
} else {
|
|
176
|
+
subtracts.push(item);
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
// 2. Process Additions (Union)
|
|
181
|
+
if (adds.length > 0) {
|
|
182
|
+
for (const item of adds) {
|
|
333
183
|
try {
|
|
334
|
-
const temp =
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
184
|
+
const temp = mainShape.unite(item);
|
|
185
|
+
mainShape.remove();
|
|
186
|
+
item.remove();
|
|
187
|
+
mainShape = temp;
|
|
338
188
|
} catch (e) {
|
|
339
|
-
console.error("Geometry: Failed to unite
|
|
340
|
-
|
|
341
|
-
lug.remove();
|
|
189
|
+
console.error("Geometry: Failed to unite feature", e);
|
|
190
|
+
item.remove();
|
|
342
191
|
}
|
|
343
192
|
}
|
|
193
|
+
}
|
|
344
194
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
} else {
|
|
195
|
+
// 3. Process Subtractions (Difference)
|
|
196
|
+
if (subtracts.length > 0) {
|
|
197
|
+
for (const item of subtracts) {
|
|
349
198
|
try {
|
|
350
|
-
const temp =
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
199
|
+
const temp = mainShape.subtract(item);
|
|
200
|
+
mainShape.remove();
|
|
201
|
+
item.remove();
|
|
202
|
+
mainShape = temp;
|
|
354
203
|
} catch (e) {
|
|
355
|
-
console.error("Geometry: Failed to
|
|
356
|
-
|
|
204
|
+
console.error("Geometry: Failed to subtract feature", e);
|
|
205
|
+
item.remove();
|
|
357
206
|
}
|
|
358
207
|
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// 2. Add Lugs to Main Shape (Union) - Additive Fusion
|
|
362
|
-
if (lugsPath) {
|
|
363
|
-
try {
|
|
364
|
-
const temp = mainShape.unite(lugsPath);
|
|
365
|
-
mainShape.remove();
|
|
366
|
-
// @ts-ignore
|
|
367
|
-
lugsPath.remove();
|
|
368
|
-
mainShape = temp;
|
|
369
|
-
} catch (e) {
|
|
370
|
-
console.error("Geometry: Failed to unite lugsPath to mainShape", e);
|
|
371
|
-
}
|
|
372
208
|
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return mainShape;
|
|
212
|
+
}
|
|
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);
|
|
373
235
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
e,
|
|
386
|
-
);
|
|
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;
|
|
387
247
|
}
|
|
248
|
+
} catch (e) {
|
|
249
|
+
console.error("Geometry: Failed to apply surface feature", e);
|
|
250
|
+
item.remove();
|
|
388
251
|
}
|
|
389
252
|
}
|
|
390
|
-
|
|
391
|
-
return
|
|
253
|
+
|
|
254
|
+
return result;
|
|
392
255
|
}
|
|
393
256
|
|
|
394
257
|
/**
|
|
395
258
|
* Generates the path data for the Dieline (Product Shape).
|
|
396
|
-
* Logic: (BaseShape UNION IntersectingLugs) SUBTRACT Cuts
|
|
397
259
|
*/
|
|
398
260
|
export function generateDielinePath(options: GeometryOptions): string {
|
|
399
261
|
const paperWidth = options.canvasWidth || options.width * 2 || 2000;
|
|
@@ -401,10 +263,11 @@ export function generateDielinePath(options: GeometryOptions): string {
|
|
|
401
263
|
ensurePaper(paperWidth, paperHeight);
|
|
402
264
|
paper.project.activeLayer.removeChildren();
|
|
403
265
|
|
|
404
|
-
const
|
|
266
|
+
const perimeter = getPerimeterShape(options);
|
|
267
|
+
const finalShape = applySurfaceFeatures(perimeter, options.features, options);
|
|
405
268
|
|
|
406
|
-
const pathData =
|
|
407
|
-
|
|
269
|
+
const pathData = finalShape.pathData;
|
|
270
|
+
finalShape.remove();
|
|
408
271
|
|
|
409
272
|
return pathData;
|
|
410
273
|
}
|
|
@@ -419,16 +282,14 @@ export function generateMaskPath(options: MaskGeometryOptions): string {
|
|
|
419
282
|
|
|
420
283
|
const { canvasWidth, canvasHeight } = options;
|
|
421
284
|
|
|
422
|
-
// 1. Canvas Background
|
|
423
285
|
const maskRect = new paper.Path.Rectangle({
|
|
424
286
|
point: [0, 0],
|
|
425
287
|
size: [canvasWidth, canvasHeight],
|
|
426
288
|
});
|
|
427
289
|
|
|
428
|
-
|
|
429
|
-
const mainShape =
|
|
290
|
+
const perimeter = getPerimeterShape(options);
|
|
291
|
+
const mainShape = applySurfaceFeatures(perimeter, options.features, options);
|
|
430
292
|
|
|
431
|
-
// 3. Subtract Product from Mask
|
|
432
293
|
const finalMask = maskRect.subtract(mainShape);
|
|
433
294
|
|
|
434
295
|
maskRect.remove();
|
|
@@ -441,92 +302,27 @@ export function generateMaskPath(options: MaskGeometryOptions): string {
|
|
|
441
302
|
}
|
|
442
303
|
|
|
443
304
|
/**
|
|
444
|
-
* Generates the path data for the Bleed Zone
|
|
305
|
+
* Generates the path data for the Bleed Zone.
|
|
445
306
|
*/
|
|
446
307
|
export function generateBleedZonePath(
|
|
447
|
-
|
|
308
|
+
originalOptions: GeometryOptions,
|
|
309
|
+
offsetOptions: GeometryOptions,
|
|
448
310
|
offset: number,
|
|
449
311
|
): string {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
const paperHeight =
|
|
312
|
+
const paperWidth =
|
|
313
|
+
originalOptions.canvasWidth || originalOptions.width * 2 || 2000;
|
|
314
|
+
const paperHeight =
|
|
315
|
+
originalOptions.canvasHeight || originalOptions.height * 2 || 2000;
|
|
453
316
|
ensurePaper(paperWidth, paperHeight);
|
|
454
317
|
paper.project.activeLayer.removeChildren();
|
|
455
318
|
|
|
456
|
-
// 1. Original Shape
|
|
457
|
-
const
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
//
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
// The issue is: do holes shrink/expand with bleed?
|
|
464
|
-
// Usually, bleed is only for the outer cut. Holes are internal cuts.
|
|
465
|
-
// Internal cuts usually also have bleed if they are die-cut, but maybe different direction?
|
|
466
|
-
// For simplicity, let's assume we offset the FINAL shape (including holes).
|
|
467
|
-
|
|
468
|
-
// Actually, getDielineShape calls createBaseShape.
|
|
469
|
-
// Let's modify generateBleedZonePath to use createOffsetShape logic if possible,
|
|
470
|
-
// OR just perform offset on the final shape result.
|
|
471
|
-
|
|
472
|
-
// The previous logic was: create base shape with adjusted width/height/radius.
|
|
473
|
-
// This works for Rect/Circle.
|
|
474
|
-
// For Custom, we need createOffsetShape.
|
|
475
|
-
|
|
476
|
-
let shapeOffset: paper.PathItem;
|
|
477
|
-
|
|
478
|
-
if (options.shape === "custom") {
|
|
479
|
-
// For custom shape, we offset the base shape first, then apply holes?
|
|
480
|
-
// Or offset the final result?
|
|
481
|
-
// Bleed is usually "outside" the cut line.
|
|
482
|
-
// If we have a donut, bleed is outside the outer circle AND inside the inner circle?
|
|
483
|
-
// Or just outside the outer?
|
|
484
|
-
// Let's assume bleed expands the solid area.
|
|
485
|
-
|
|
486
|
-
// So we take the final shape (Original) and expand it.
|
|
487
|
-
// We can use the same Stroke Expansion trick on the final shape.
|
|
488
|
-
|
|
489
|
-
// Since shapeOriginal is already the final shape (Base - Holes),
|
|
490
|
-
// we can try to offset it directly.
|
|
491
|
-
|
|
492
|
-
const stroker = shapeOriginal.clone() as paper.Path;
|
|
493
|
-
stroker.strokeColor = new paper.Color("black");
|
|
494
|
-
stroker.strokeWidth = Math.abs(offset) * 2;
|
|
495
|
-
stroker.strokeJoin = "round";
|
|
496
|
-
stroker.strokeCap = "round";
|
|
497
|
-
|
|
498
|
-
let expanded: paper.Item;
|
|
499
|
-
try {
|
|
500
|
-
// @ts-ignore
|
|
501
|
-
expanded = stroker.expand({ stroke: true, fill: false, insert: false });
|
|
502
|
-
} catch (e) {
|
|
503
|
-
// Fallback
|
|
504
|
-
stroker.remove();
|
|
505
|
-
shapeOffset = shapeOriginal.clone();
|
|
506
|
-
// scaling fallback...
|
|
507
|
-
return shapeOffset.pathData; // Fail gracefully
|
|
508
|
-
}
|
|
509
|
-
stroker.remove();
|
|
510
|
-
|
|
511
|
-
if (offset > 0) {
|
|
512
|
-
// @ts-ignore
|
|
513
|
-
shapeOffset = shapeOriginal.unite(expanded);
|
|
514
|
-
} else {
|
|
515
|
-
// @ts-ignore
|
|
516
|
-
shapeOffset = shapeOriginal.subtract(expanded);
|
|
517
|
-
}
|
|
518
|
-
expanded.remove();
|
|
519
|
-
} else {
|
|
520
|
-
// Legacy logic for standard shapes (still valid and fast)
|
|
521
|
-
// Adjust dimensions for offset
|
|
522
|
-
const offsetOptions: GeometryOptions = {
|
|
523
|
-
...options,
|
|
524
|
-
width: Math.max(0, options.width + offset * 2),
|
|
525
|
-
height: Math.max(0, options.height + offset * 2),
|
|
526
|
-
radius: options.radius === 0 ? 0 : Math.max(0, options.radius + offset),
|
|
527
|
-
};
|
|
528
|
-
shapeOffset = getDielineShape(offsetOptions);
|
|
529
|
-
}
|
|
319
|
+
// 1. Generate Original Shape
|
|
320
|
+
const pOriginal = getPerimeterShape(originalOptions);
|
|
321
|
+
const shapeOriginal = applySurfaceFeatures(pOriginal, originalOptions.features, originalOptions);
|
|
322
|
+
|
|
323
|
+
// 2. Generate Offset Shape
|
|
324
|
+
const pOffset = getPerimeterShape(offsetOptions);
|
|
325
|
+
const shapeOffset = applySurfaceFeatures(pOffset, offsetOptions.features, offsetOptions);
|
|
530
326
|
|
|
531
327
|
// 3. Calculate Difference
|
|
532
328
|
let bleedZone: paper.PathItem;
|
|
@@ -538,7 +334,6 @@ export function generateBleedZonePath(
|
|
|
538
334
|
|
|
539
335
|
const pathData = bleedZone.pathData;
|
|
540
336
|
|
|
541
|
-
// Cleanup
|
|
542
337
|
shapeOriginal.remove();
|
|
543
338
|
shapeOffset.remove();
|
|
544
339
|
bleedZone.remove();
|
|
@@ -547,8 +342,8 @@ export function generateBleedZonePath(
|
|
|
547
342
|
}
|
|
548
343
|
|
|
549
344
|
/**
|
|
550
|
-
* Finds the nearest point on the Dieline geometry for a given target point.
|
|
551
|
-
* Used for constraining
|
|
345
|
+
* Finds the nearest point on the Dieline geometry (Base Shape ONLY) for a given target point.
|
|
346
|
+
* Used for constraining feature movement.
|
|
552
347
|
*/
|
|
553
348
|
export function getNearestPointOnDieline(
|
|
554
349
|
point: { x: number; y: number },
|
|
@@ -557,6 +352,8 @@ export function getNearestPointOnDieline(
|
|
|
557
352
|
ensurePaper(options.width * 2, options.height * 2);
|
|
558
353
|
paper.project.activeLayer.removeChildren();
|
|
559
354
|
|
|
355
|
+
// We constrain to the BASE shape, not including other features,
|
|
356
|
+
// because usually you want to snap to the main edge.
|
|
560
357
|
const shape = createBaseShape(options);
|
|
561
358
|
|
|
562
359
|
const p = new paper.Point(point.x, point.y);
|