@pooder/kit 3.5.0 → 4.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/src/geometry.ts CHANGED
@@ -3,20 +3,25 @@ import paper from "paper";
3
3
  export type FeatureOperation = "add" | "subtract";
4
4
  export type FeatureShape = "rect" | "circle";
5
5
 
6
- export interface EdgeFeature {
6
+ export interface DielineFeature {
7
7
  id: string;
8
- groupId?: string; // For grouping features together (e.g. double-layer hole)
8
+ groupId?: string;
9
9
  operation: FeatureOperation;
10
10
  shape: FeatureShape;
11
- x: number; // Normalized 0-1 relative to geometry bounds
12
- y: number; // Normalized 0-1 relative to geometry bounds
13
- width?: number; // For rect (Physical units)
14
- height?: number; // For rect (Physical units)
15
- radius?: number; // For circle or rect corners (Physical units)
16
- rotation?: number; // Degrees
17
- target?: "original" | "offset" | "both";
18
- color?: string; // Hex color for the marker
19
- strokeDash?: number[]; // Stroke dash array for the marker
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;
21
+ constraints?: {
22
+ type: string;
23
+ params?: any;
24
+ };
20
25
  }
21
26
 
22
27
  export interface GeometryOptions {
@@ -26,7 +31,7 @@ export interface GeometryOptions {
26
31
  radius: number;
27
32
  x: number;
28
33
  y: number;
29
- features: Array<EdgeFeature>;
34
+ features: Array<DielineFeature>;
30
35
  pathData?: string;
31
36
  canvasWidth?: number;
32
37
  canvasHeight?: number;
@@ -41,7 +46,7 @@ export interface MaskGeometryOptions extends GeometryOptions {
41
46
  * Resolves the absolute position of a feature based on normalized coordinates.
42
47
  */
43
48
  export function resolveFeaturePosition(
44
- feature: EdgeFeature,
49
+ feature: DielineFeature,
45
50
  geometry: { x: number; y: number; width: number; height: number },
46
51
  ): { x: number; y: number } {
47
52
  const { x, y, width, height } = geometry;
@@ -116,7 +121,7 @@ function createBaseShape(options: GeometryOptions): paper.PathItem {
116
121
  * Creates a Paper.js Item for a single feature.
117
122
  */
118
123
  function createFeatureItem(
119
- feature: EdgeFeature,
124
+ feature: DielineFeature,
120
125
  center: paper.Point,
121
126
  ): paper.PathItem {
122
127
  let item: paper.PathItem;
@@ -147,20 +152,24 @@ function createFeatureItem(
147
152
  }
148
153
 
149
154
  /**
150
- * Internal helper to generate the Dieline Shape (Paper Item).
151
- * Logic: (Base U Adds) - Subtracts
155
+ * Internal helper to generate the Perimeter Shape (Base + Edge Features).
152
156
  */
153
- function getDielineShape(options: GeometryOptions): paper.PathItem {
157
+ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
154
158
  // 1. Create Base Shape
155
159
  let mainShape = createBaseShape(options);
156
160
 
157
161
  const { features } = options;
158
162
 
159
163
  if (features && features.length > 0) {
164
+ // Filter for Edge Features (Default or explicit 'edge')
165
+ const edgeFeatures = features.filter(
166
+ (f) => !f.placement || f.placement === "edge",
167
+ );
168
+
160
169
  const adds: paper.PathItem[] = [];
161
170
  const subtracts: paper.PathItem[] = [];
162
171
 
163
- features.forEach((f) => {
172
+ edgeFeatures.forEach((f) => {
164
173
  const pos = resolveFeaturePosition(f, options);
165
174
  const center = new paper.Point(pos.x, pos.y);
166
175
  const item = createFeatureItem(f, center);
@@ -174,9 +183,6 @@ function getDielineShape(options: GeometryOptions): paper.PathItem {
174
183
 
175
184
  // 2. Process Additions (Union)
176
185
  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
186
  for (const item of adds) {
181
187
  try {
182
188
  const temp = mainShape.unite(item);
@@ -209,6 +215,49 @@ function getDielineShape(options: GeometryOptions): paper.PathItem {
209
215
  return mainShape;
210
216
  }
211
217
 
218
+ /**
219
+ * Applies Internal/Surface features to a shape.
220
+ */
221
+ function applySurfaceFeatures(
222
+ shape: paper.PathItem,
223
+ features: DielineFeature[],
224
+ options: GeometryOptions,
225
+ ): paper.PathItem {
226
+ const internalFeatures = features.filter((f) => f.placement === "internal");
227
+
228
+ if (internalFeatures.length === 0) return shape;
229
+
230
+ let result = shape;
231
+
232
+ // Internal features are usually subtractive (holes)
233
+ // But we support 'add' too (islands? maybe just unite)
234
+
235
+ for (const f of internalFeatures) {
236
+ const pos = resolveFeaturePosition(f, options);
237
+ const center = new paper.Point(pos.x, pos.y);
238
+ const item = createFeatureItem(f, center);
239
+
240
+ try {
241
+ if (f.operation === "add") {
242
+ const temp = result.unite(item);
243
+ result.remove();
244
+ item.remove();
245
+ result = temp;
246
+ } else {
247
+ const temp = result.subtract(item);
248
+ result.remove();
249
+ item.remove();
250
+ result = temp;
251
+ }
252
+ } catch (e) {
253
+ console.error("Geometry: Failed to apply surface feature", e);
254
+ item.remove();
255
+ }
256
+ }
257
+
258
+ return result;
259
+ }
260
+
212
261
  /**
213
262
  * Generates the path data for the Dieline (Product Shape).
214
263
  */
@@ -218,10 +267,11 @@ export function generateDielinePath(options: GeometryOptions): string {
218
267
  ensurePaper(paperWidth, paperHeight);
219
268
  paper.project.activeLayer.removeChildren();
220
269
 
221
- const mainShape = getDielineShape(options);
270
+ const perimeter = getPerimeterShape(options);
271
+ const finalShape = applySurfaceFeatures(perimeter, options.features, options);
222
272
 
223
- const pathData = mainShape.pathData;
224
- mainShape.remove();
273
+ const pathData = finalShape.pathData;
274
+ finalShape.remove();
225
275
 
226
276
  return pathData;
227
277
  }
@@ -241,7 +291,8 @@ export function generateMaskPath(options: MaskGeometryOptions): string {
241
291
  size: [canvasWidth, canvasHeight],
242
292
  });
243
293
 
244
- const mainShape = getDielineShape(options);
294
+ const perimeter = getPerimeterShape(options);
295
+ const mainShape = applySurfaceFeatures(perimeter, options.features, options);
245
296
 
246
297
  const finalMask = maskRect.subtract(mainShape);
247
298
 
@@ -270,10 +321,12 @@ export function generateBleedZonePath(
270
321
  paper.project.activeLayer.removeChildren();
271
322
 
272
323
  // 1. Generate Original Shape
273
- const shapeOriginal = getDielineShape(originalOptions);
324
+ const pOriginal = getPerimeterShape(originalOptions);
325
+ const shapeOriginal = applySurfaceFeatures(pOriginal, originalOptions.features, originalOptions);
274
326
 
275
327
  // 2. Generate Offset Shape
276
- const shapeOffset = getDielineShape(offsetOptions);
328
+ const pOffset = getPerimeterShape(offsetOptions);
329
+ const shapeOffset = applySurfaceFeatures(pOffset, offsetOptions.features, offsetOptions);
277
330
 
278
331
  // 3. Calculate Difference
279
332
  let bleedZone: paper.PathItem;