@pooder/kit 4.3.1 → 5.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/.test-dist/src/CanvasService.js +249 -0
- package/.test-dist/src/ViewportSystem.js +75 -0
- package/.test-dist/src/background.js +203 -0
- package/.test-dist/src/bridgeSelection.js +20 -0
- package/.test-dist/src/constraints.js +237 -0
- package/.test-dist/src/coordinate.js +74 -0
- package/.test-dist/src/dieline.js +723 -0
- package/.test-dist/src/edgeScale.js +12 -0
- package/.test-dist/src/feature.js +752 -0
- package/.test-dist/src/featureComplete.js +32 -0
- package/.test-dist/src/film.js +167 -0
- package/.test-dist/src/geometry.js +506 -0
- package/.test-dist/src/image.js +1234 -0
- package/.test-dist/src/index.js +35 -0
- package/.test-dist/src/maskOps.js +270 -0
- package/.test-dist/src/mirror.js +104 -0
- package/.test-dist/src/renderSpec.js +2 -0
- package/.test-dist/src/ruler.js +343 -0
- package/.test-dist/src/sceneLayout.js +99 -0
- package/.test-dist/src/sceneLayoutModel.js +196 -0
- package/.test-dist/src/sceneView.js +40 -0
- package/.test-dist/src/sceneVisibility.js +42 -0
- package/.test-dist/src/size.js +332 -0
- package/.test-dist/src/tracer.js +544 -0
- package/.test-dist/src/units.js +30 -0
- package/.test-dist/src/white-ink.js +829 -0
- package/.test-dist/src/wrappedOffsets.js +33 -0
- package/.test-dist/tests/run.js +94 -0
- package/CHANGELOG.md +11 -0
- package/dist/index.d.mts +339 -36
- package/dist/index.d.ts +339 -36
- package/dist/index.js +3572 -850
- package/dist/index.mjs +3565 -852
- package/package.json +2 -2
- package/src/CanvasService.ts +300 -96
- package/src/ViewportSystem.ts +92 -92
- package/src/background.ts +230 -230
- package/src/bridgeSelection.ts +17 -0
- package/src/coordinate.ts +106 -106
- package/src/dieline.ts +897 -973
- package/src/edgeScale.ts +19 -0
- package/src/feature.ts +83 -30
- package/src/film.ts +194 -194
- package/src/geometry.ts +242 -84
- package/src/image.ts +1582 -512
- package/src/index.ts +14 -10
- package/src/maskOps.ts +326 -0
- package/src/mirror.ts +128 -128
- package/src/renderSpec.ts +18 -0
- package/src/ruler.ts +449 -508
- package/src/sceneLayout.ts +121 -0
- package/src/sceneLayoutModel.ts +335 -0
- package/src/sceneVisibility.ts +49 -0
- package/src/size.ts +379 -0
- package/src/tracer.ts +719 -570
- package/src/units.ts +27 -27
- package/src/white-ink.ts +1018 -373
- package/src/wrappedOffsets.ts +33 -0
- package/tests/run.ts +118 -0
- package/tsconfig.test.json +15 -15
package/src/geometry.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import paper from "paper";
|
|
2
|
+
import { pickExitIndex, scoreOutsideAbove } from "./bridgeSelection";
|
|
3
|
+
import { sampleWrappedOffsets, wrappedDistance } from "./wrappedOffsets";
|
|
2
4
|
|
|
3
5
|
export type FeatureOperation = "add" | "subtract";
|
|
4
6
|
export type FeatureShape = "rect" | "circle";
|
|
@@ -71,6 +73,120 @@ function ensurePaper(width: number, height: number) {
|
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
const isBridgeDebugEnabled = () =>
|
|
77
|
+
Boolean((globalThis as any).__POODER_BRIDGE_DEBUG__);
|
|
78
|
+
|
|
79
|
+
function normalizePathItem(shape: paper.PathItem): paper.PathItem {
|
|
80
|
+
let result: any = shape;
|
|
81
|
+
if (typeof result.resolveCrossings === "function") result = result.resolveCrossings();
|
|
82
|
+
if (typeof result.reduce === "function") result = result.reduce({});
|
|
83
|
+
if (typeof result.reorient === "function") result = result.reorient(true, true);
|
|
84
|
+
if (typeof result.reduce === "function") result = result.reduce({});
|
|
85
|
+
return result as paper.PathItem;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function getBridgeDelta(itemBounds: paper.Rectangle, overlap: number) {
|
|
89
|
+
return Math.max(overlap, Math.min(5, Math.max(1, itemBounds.height * 0.02)));
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getExitHit(args: {
|
|
93
|
+
mainShape: paper.Path;
|
|
94
|
+
x: number;
|
|
95
|
+
bridgeBottom: number;
|
|
96
|
+
toY: number;
|
|
97
|
+
eps: number;
|
|
98
|
+
delta: number;
|
|
99
|
+
overlap: number;
|
|
100
|
+
op: FeatureOperation;
|
|
101
|
+
}) {
|
|
102
|
+
const { mainShape, x, bridgeBottom, toY, eps, delta, overlap, op } = args;
|
|
103
|
+
|
|
104
|
+
const ray = new paper.Path.Line({
|
|
105
|
+
from: [x, bridgeBottom],
|
|
106
|
+
to: [x, toY],
|
|
107
|
+
insert: false,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const intersections = mainShape.getIntersections(ray) || [];
|
|
111
|
+
ray.remove();
|
|
112
|
+
|
|
113
|
+
const validHits = intersections.filter((i) => i.point.y < bridgeBottom - eps);
|
|
114
|
+
if (validHits.length === 0) return null;
|
|
115
|
+
|
|
116
|
+
validHits.sort((a, b) => b.point.y - a.point.y);
|
|
117
|
+
const flags = validHits.map((h) => {
|
|
118
|
+
const above = h.point.add(new paper.Point(0, -delta));
|
|
119
|
+
const below = h.point.add(new paper.Point(0, delta));
|
|
120
|
+
return {
|
|
121
|
+
insideAbove: mainShape.contains(above),
|
|
122
|
+
insideBelow: mainShape.contains(below),
|
|
123
|
+
};
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const idx = pickExitIndex(flags);
|
|
127
|
+
if (idx < 0) return null;
|
|
128
|
+
|
|
129
|
+
if (isBridgeDebugEnabled()) {
|
|
130
|
+
console.debug("Geometry: Bridge ray", {
|
|
131
|
+
x,
|
|
132
|
+
validHits: validHits.length,
|
|
133
|
+
idx,
|
|
134
|
+
delta,
|
|
135
|
+
overlap,
|
|
136
|
+
op,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const hit = validHits[idx];
|
|
141
|
+
return { point: hit.point, location: hit };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function selectOuterChain(args: {
|
|
145
|
+
mainShape: paper.Path;
|
|
146
|
+
pointsA: paper.Point[];
|
|
147
|
+
pointsB: paper.Point[];
|
|
148
|
+
delta: number;
|
|
149
|
+
overlap: number;
|
|
150
|
+
op: FeatureOperation;
|
|
151
|
+
}) {
|
|
152
|
+
const { mainShape, pointsA, pointsB, delta, overlap, op } = args;
|
|
153
|
+
|
|
154
|
+
const scoreA = scoreOutsideAbove(
|
|
155
|
+
pointsA.map((p) => ({
|
|
156
|
+
outsideAbove: !mainShape.contains(p.add(new paper.Point(0, -delta))),
|
|
157
|
+
})),
|
|
158
|
+
);
|
|
159
|
+
const scoreB = scoreOutsideAbove(
|
|
160
|
+
pointsB.map((p) => ({
|
|
161
|
+
outsideAbove: !mainShape.contains(p.add(new paper.Point(0, -delta))),
|
|
162
|
+
})),
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
const ratioA = scoreA / pointsA.length;
|
|
166
|
+
const ratioB = scoreB / pointsB.length;
|
|
167
|
+
|
|
168
|
+
if (isBridgeDebugEnabled()) {
|
|
169
|
+
console.debug("Geometry: Bridge chain", {
|
|
170
|
+
scoreA,
|
|
171
|
+
scoreB,
|
|
172
|
+
lenA: pointsA.length,
|
|
173
|
+
lenB: pointsB.length,
|
|
174
|
+
ratioA,
|
|
175
|
+
ratioB,
|
|
176
|
+
delta,
|
|
177
|
+
overlap,
|
|
178
|
+
op,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const ratioEps = 1e-6;
|
|
183
|
+
if (Math.abs(ratioA - ratioB) > ratioEps) {
|
|
184
|
+
return ratioA > ratioB ? pointsA : pointsB;
|
|
185
|
+
}
|
|
186
|
+
if (scoreA !== scoreB) return scoreA > scoreB ? pointsA : pointsB;
|
|
187
|
+
return pointsA.length <= pointsB.length ? pointsA : pointsB;
|
|
188
|
+
}
|
|
189
|
+
|
|
74
190
|
/**
|
|
75
191
|
* Creates the base dieline shape (Rect/Circle/Ellipse/Custom)
|
|
76
192
|
*/
|
|
@@ -182,87 +298,129 @@ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
|
182
298
|
const bridgeBottom = itemBounds.top;
|
|
183
299
|
|
|
184
300
|
if (bridgeBottom > bridgeTop) {
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
301
|
+
const overlap = 2;
|
|
302
|
+
const rayPadding = 10;
|
|
303
|
+
const eps = 0.1;
|
|
304
|
+
const delta = getBridgeDelta(itemBounds, overlap);
|
|
305
|
+
|
|
306
|
+
const toY = bridgeTop - rayPadding;
|
|
307
|
+
const inset = Math.min(1, Math.max(0, itemBounds.width * 0.01));
|
|
308
|
+
const xLeft = itemBounds.left + inset;
|
|
309
|
+
const xRight = itemBounds.right - inset;
|
|
310
|
+
|
|
311
|
+
if (!(mainShape instanceof paper.Path)) {
|
|
312
|
+
throw new Error("Geometry: Bridge requires base shape to be a Path");
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const leftHit = getExitHit({
|
|
316
|
+
mainShape,
|
|
317
|
+
x: xLeft,
|
|
318
|
+
bridgeBottom,
|
|
319
|
+
toY,
|
|
320
|
+
eps,
|
|
321
|
+
delta,
|
|
322
|
+
overlap,
|
|
323
|
+
op: f.operation,
|
|
324
|
+
});
|
|
325
|
+
const rightHit = getExitHit({
|
|
326
|
+
mainShape,
|
|
327
|
+
x: xRight,
|
|
328
|
+
bridgeBottom,
|
|
329
|
+
toY,
|
|
330
|
+
eps,
|
|
331
|
+
delta,
|
|
332
|
+
overlap,
|
|
333
|
+
op: f.operation,
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
if (!leftHit || !rightHit || xRight - xLeft <= eps) {
|
|
337
|
+
throw new Error("Geometry: Bridge ray intersection not found");
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const path = mainShape as paper.Path;
|
|
341
|
+
const pathLength = path.length;
|
|
342
|
+
const leftOffset = leftHit.location.offset;
|
|
343
|
+
const rightOffset = rightHit.location.offset;
|
|
344
|
+
|
|
345
|
+
const distanceA = wrappedDistance(pathLength, leftOffset, rightOffset);
|
|
346
|
+
const distanceB = wrappedDistance(pathLength, rightOffset, leftOffset);
|
|
347
|
+
const countFor = (d: number) => Math.max(8, Math.min(80, Math.ceil(d / 6)));
|
|
348
|
+
|
|
349
|
+
const offsetsA = sampleWrappedOffsets(
|
|
350
|
+
pathLength,
|
|
351
|
+
leftOffset,
|
|
352
|
+
rightOffset,
|
|
353
|
+
countFor(distanceA),
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
const offsetsB = sampleWrappedOffsets(
|
|
357
|
+
pathLength,
|
|
358
|
+
rightOffset,
|
|
359
|
+
leftOffset,
|
|
360
|
+
countFor(distanceB),
|
|
361
|
+
);
|
|
362
|
+
|
|
363
|
+
const pointsA = offsetsA
|
|
364
|
+
.map((o) => path.getPointAt(o))
|
|
365
|
+
.filter((p): p is paper.Point => Boolean(p));
|
|
366
|
+
const pointsB = offsetsB
|
|
367
|
+
.map((o) => path.getPointAt(o))
|
|
368
|
+
.filter((p): p is paper.Point => Boolean(p));
|
|
369
|
+
|
|
370
|
+
if (pointsA.length < 2 || pointsB.length < 2) {
|
|
371
|
+
throw new Error("Geometry: Bridge contour sampling failed");
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
let topBase = selectOuterChain({
|
|
375
|
+
mainShape,
|
|
376
|
+
pointsA,
|
|
377
|
+
pointsB,
|
|
378
|
+
delta,
|
|
379
|
+
overlap,
|
|
380
|
+
op: f.operation,
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const dist2 = (a: paper.Point, b: paper.Point) => {
|
|
384
|
+
const dx = a.x - b.x;
|
|
385
|
+
const dy = a.y - b.y;
|
|
386
|
+
return dx * dx + dy * dy;
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
if (dist2(topBase[0], leftHit.point) > dist2(topBase[0], rightHit.point)) {
|
|
390
|
+
topBase = topBase.slice().reverse();
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
topBase = topBase.slice();
|
|
394
|
+
topBase[0] = leftHit.point;
|
|
395
|
+
topBase[topBase.length - 1] = rightHit.point;
|
|
396
|
+
|
|
397
|
+
const capShiftY = f.operation === "subtract" ? -Math.max(overlap * 2, delta) : overlap;
|
|
398
|
+
const topPoints = topBase.map(
|
|
399
|
+
(p) => p.add(new paper.Point(0, capShiftY)),
|
|
400
|
+
);
|
|
401
|
+
|
|
402
|
+
const bridgeBottomY = bridgeBottom + overlap * 2;
|
|
403
|
+
const bridgePoly = new paper.Path({ insert: false });
|
|
404
|
+
for (const p of topPoints) bridgePoly.add(p);
|
|
405
|
+
bridgePoly.add(new paper.Point(xRight, bridgeBottomY));
|
|
406
|
+
bridgePoly.add(new paper.Point(xLeft, bridgeBottomY));
|
|
407
|
+
bridgePoly.closed = true;
|
|
408
|
+
|
|
409
|
+
const unitedItem = item.unite(bridgePoly);
|
|
410
|
+
item.remove();
|
|
411
|
+
bridgePoly.remove();
|
|
412
|
+
|
|
413
|
+
if (f.operation === "add") {
|
|
414
|
+
adds.push(unitedItem);
|
|
415
|
+
} else {
|
|
416
|
+
subtracts.push(unitedItem);
|
|
417
|
+
}
|
|
418
|
+
} else {
|
|
419
|
+
if (f.operation === "add") {
|
|
420
|
+
adds.push(item);
|
|
260
421
|
} else {
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
} else {
|
|
264
|
-
subtracts.push(item);
|
|
265
|
-
}
|
|
422
|
+
subtracts.push(item);
|
|
423
|
+
}
|
|
266
424
|
}
|
|
267
425
|
} else {
|
|
268
426
|
if (f.operation === "add") {
|
|
@@ -280,7 +438,7 @@ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
|
280
438
|
const temp = mainShape.unite(item);
|
|
281
439
|
mainShape.remove();
|
|
282
440
|
item.remove();
|
|
283
|
-
mainShape = temp;
|
|
441
|
+
mainShape = normalizePathItem(temp);
|
|
284
442
|
} catch (e) {
|
|
285
443
|
console.error("Geometry: Failed to unite feature", e);
|
|
286
444
|
item.remove();
|
|
@@ -295,7 +453,7 @@ function getPerimeterShape(options: GeometryOptions): paper.PathItem {
|
|
|
295
453
|
const temp = mainShape.subtract(item);
|
|
296
454
|
mainShape.remove();
|
|
297
455
|
item.remove();
|
|
298
|
-
mainShape = temp;
|
|
456
|
+
mainShape = normalizePathItem(temp);
|
|
299
457
|
} catch (e) {
|
|
300
458
|
console.error("Geometry: Failed to subtract feature", e);
|
|
301
459
|
item.remove();
|
|
@@ -336,12 +494,12 @@ function applySurfaceFeatures(
|
|
|
336
494
|
const temp = result.unite(item);
|
|
337
495
|
result.remove();
|
|
338
496
|
item.remove();
|
|
339
|
-
result = temp;
|
|
497
|
+
result = normalizePathItem(temp);
|
|
340
498
|
} else {
|
|
341
499
|
const temp = result.subtract(item);
|
|
342
500
|
result.remove();
|
|
343
501
|
item.remove();
|
|
344
|
-
result = temp;
|
|
502
|
+
result = normalizePathItem(temp);
|
|
345
503
|
}
|
|
346
504
|
} catch (e) {
|
|
347
505
|
console.error("Geometry: Failed to apply surface feature", e);
|