@lucablockltd/ultimate-packaging 1.1.0 → 1.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/dist/AGENTS_README.md +299 -30
- package/dist/index.js +239 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +239 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/AGENTS_README.md
CHANGED
|
@@ -69,18 +69,186 @@ const pdf = await ref.current.exportDimension();
|
|
|
69
69
|
## 2. Auto-Layout (Pack Dielines on Paper)
|
|
70
70
|
|
|
71
71
|
Use `AUTO_LAYOUT` to automatically arrange dieline pieces on paper sheets.
|
|
72
|
+
The engine calculates the optimal rotation and grid placement to maximize pieces per sheet.
|
|
72
73
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
74
|
+
### AutoLayoutConfig — Full Reference
|
|
75
|
+
|
|
76
|
+
`AutoLayoutConfig` has **3 required fields** and **5 optional print-setting fields**.
|
|
77
|
+
If you omit any optional field, the system uses a sensible default automatically.
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface AutoLayoutConfig {
|
|
81
|
+
// ─── REQUIRED ───────────────────────────────────────────────
|
|
82
|
+
papers: AutoLayoutPaper[]; // At least 1 paper size to test
|
|
83
|
+
model: AutoLayoutModel; // Which box/bag model and its dimensions
|
|
84
|
+
quantity: number; // Total pieces needed (e.g. 1000)
|
|
85
|
+
|
|
86
|
+
// ─── OPTIONAL (print settings) ──────────────────────────────
|
|
87
|
+
// You can omit ALL of these — the system will use defaults.
|
|
88
|
+
layoutDistance?: number; // default: 3 — mm gap between piece cut-lines
|
|
89
|
+
spacing?: number; // default: 5 — mm margin from paper edges
|
|
90
|
+
griper?: number; // default: 10 — mm machine gripper zone
|
|
91
|
+
colorbarHeight?: number; // default: 5 — mm height of color test strip
|
|
92
|
+
isShowColorbar?: boolean; // default: true — whether to show colorbar
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
#### Required Field #1: `papers` — Paper sizes to test
|
|
97
|
+
|
|
98
|
+
An array of paper sizes. The engine will calculate layout for EVERY paper and rank them by efficiency.
|
|
99
|
+
Each paper needs 4 fields — all required:
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
interface AutoLayoutPaper {
|
|
103
|
+
paperId: string; // Unique ID (any string, e.g. "1", "paper-a4", "custom-1")
|
|
104
|
+
paperName: string; // Display name (e.g. "20x28\"", "A4", "25x36\"")
|
|
105
|
+
paperWidth: number; // Width in millimeters
|
|
106
|
+
paperHeight: number; // Height in millimeters
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Common paper sizes (inches → mm, multiply by 25.4):**
|
|
111
|
+
|
|
112
|
+
| Name | Inches | paperWidth (mm) | paperHeight (mm) |
|
|
113
|
+
| -------- | -------- | --------------- | ----------------- |
|
|
114
|
+
| 20x28" | 20 × 28 | 508 | 711.2 |
|
|
115
|
+
| 25x36" | 25 × 36 | 635 | 914.4 |
|
|
116
|
+
| 25x12" | 25 × 12 | 635 | 304.8 |
|
|
117
|
+
| 23x35" | 23 × 35 | 584.2 | 889 |
|
|
118
|
+
|
|
119
|
+
**Example:**
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
const papers: AutoLayoutPaper[] = [
|
|
123
|
+
{ paperId: "1", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
|
|
124
|
+
{ paperId: "2", paperName: '25x36"', paperWidth: 635, paperHeight: 914.4 },
|
|
125
|
+
];
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### Required Field #2: `model` — Which packaging model and its dimensions
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
interface AutoLayoutModel {
|
|
132
|
+
modelId: string; // Must be one of the supported model IDs (see below)
|
|
133
|
+
attributes: Record<string, number>; // Dimensions in mm — different per model
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**Supported `modelId` values and their required `attributes`:**
|
|
138
|
+
|
|
139
|
+
| modelId | Type | Required attributes |
|
|
140
|
+
| ------------ | ----------------------- | ---------------------------------------------------------------- |
|
|
141
|
+
| `BECF-1010A` | Tuck End Box Type A | `length`, `width`, `height`, `glueArea`, `dustFlap`, `tuckFlap` |
|
|
142
|
+
| `BECF-1030A` | Tuck End Box Type C | `length`, `width`, `height`, `glueArea`, `dustFlap`, `tuckFlap` |
|
|
143
|
+
| `BECF-1040A` | Tuck End Box Type B | `length`, `width`, `height`, `glueArea`, `dustFlap`, `tuckFlap` |
|
|
144
|
+
| `BECF-11D01` | Standard Box | `length`, `width`, `height`, `flapHeight`, `glueArea` |
|
|
145
|
+
| `BECF-12101` | Carton Bag / Shopping Bag Type A | `length`, `width`, `height`, `glueArea` |
|
|
146
|
+
| `BECF-12109` | Carton Bag / Shopping Bag Type B | `length`, `width`, `height`, `glueArea` |
|
|
147
|
+
|
|
148
|
+
**Example — Tuck End Box:**
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
const model: AutoLayoutModel = {
|
|
152
|
+
modelId: "BECF-1010A",
|
|
153
|
+
attributes: {
|
|
154
|
+
length: 60, // box length (mm)
|
|
155
|
+
width: 25, // box width (mm)
|
|
156
|
+
height: 100, // box height (mm)
|
|
157
|
+
glueArea: 15, // glue flap width (mm)
|
|
158
|
+
dustFlap: 15, // dust flap extension (mm)
|
|
159
|
+
tuckFlap: 15, // tuck flap height (mm)
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Example — Standard Box:**
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
const model: AutoLayoutModel = {
|
|
168
|
+
modelId: "BECF-11D01",
|
|
169
|
+
attributes: {
|
|
170
|
+
length: 100,
|
|
171
|
+
width: 50,
|
|
172
|
+
height: 150,
|
|
173
|
+
flapHeight: 50,
|
|
174
|
+
glueArea: 13,
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Example — Carton Bag:**
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
const model: AutoLayoutModel = {
|
|
183
|
+
modelId: "BECF-12101",
|
|
184
|
+
attributes: {
|
|
185
|
+
length: 100, // A — front/back panel width (mm)
|
|
186
|
+
width: 50, // B — side panel / gusset (mm)
|
|
187
|
+
height: 150, // C — bag body height (mm)
|
|
188
|
+
glueArea: 13, // glue flap width (mm)
|
|
189
|
+
},
|
|
190
|
+
};
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Tip:** You can use pre-defined default attributes instead of hardcoding:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
import {
|
|
197
|
+
BECF_1010A_DEFAULT_ATTRIBUTES,
|
|
198
|
+
BECF_12101_DEFAULT_ATTRIBUTES,
|
|
79
199
|
} from "@lucablockltd/ultimate-packaging";
|
|
80
200
|
|
|
81
|
-
const
|
|
82
|
-
|
|
201
|
+
const model: AutoLayoutModel = {
|
|
202
|
+
modelId: "BECF-1010A",
|
|
203
|
+
attributes: { ...BECF_1010A_DEFAULT_ATTRIBUTES },
|
|
204
|
+
};
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
#### Required Field #3: `quantity` — Total pieces needed
|
|
208
|
+
|
|
209
|
+
A positive integer. The engine calculates how many sheets are needed to produce this quantity.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
quantity: 1000 // need 1000 pieces total
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
#### Optional Print Settings (all have defaults)
|
|
216
|
+
|
|
217
|
+
These 5 fields control the printing press layout. **You can omit any or all of them.**
|
|
83
218
|
|
|
219
|
+
| Field | Type | Default | What it does |
|
|
220
|
+
| ---------------- | --------- | ------- | ------------------------------------------------------------------------------------------------ |
|
|
221
|
+
| `layoutDistance` | `number` | `3` | Gap between piece cut-lines in mm. Creates an offset contour around each piece (half on each side). Set to `0` for pieces touching edge-to-edge. |
|
|
222
|
+
| `spacing` | `number` | `5` | Margin from all paper edges in mm. Reserved border where no pieces are placed. |
|
|
223
|
+
| `griper` | `number` | `10` | Machine gripper zone depth in mm. Area reserved for the printing press feed mechanism. Placed on the bottom edge (landscape) or left edge (portrait). |
|
|
224
|
+
| `colorbarHeight` | `number` | `5` | Height of the color test strip in mm. A row of colored squares for print registration, placed on the opposite side of the gripper. |
|
|
225
|
+
| `isShowColorbar` | `boolean` | `true` | Whether to show the colorbar. If `false`, `colorbarHeight` is ignored and that space becomes usable for pieces. |
|
|
226
|
+
|
|
227
|
+
### Minimal Config Example (only required fields)
|
|
228
|
+
|
|
229
|
+
This is the **simplest possible config** — all print settings use defaults:
|
|
230
|
+
|
|
231
|
+
```tsx
|
|
232
|
+
const config: AutoLayoutConfig = {
|
|
233
|
+
papers: [
|
|
234
|
+
{ paperId: "1", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
|
|
235
|
+
],
|
|
236
|
+
model: {
|
|
237
|
+
modelId: "BECF-1010A",
|
|
238
|
+
attributes: { length: 60, width: 25, height: 100, glueArea: 15, dustFlap: 15, tuckFlap: 15 },
|
|
239
|
+
},
|
|
240
|
+
quantity: 1000,
|
|
241
|
+
// layoutDistance → defaults to 3
|
|
242
|
+
// spacing → defaults to 5
|
|
243
|
+
// griper → defaults to 10
|
|
244
|
+
// colorbarHeight → defaults to 5
|
|
245
|
+
// isShowColorbar → defaults to true
|
|
246
|
+
};
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Full Config Example (all fields explicit)
|
|
250
|
+
|
|
251
|
+
```tsx
|
|
84
252
|
const config: AutoLayoutConfig = {
|
|
85
253
|
papers: [
|
|
86
254
|
{ paperId: "1", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
|
|
@@ -98,52 +266,153 @@ const config: AutoLayoutConfig = {
|
|
|
98
266
|
},
|
|
99
267
|
},
|
|
100
268
|
quantity: 1000,
|
|
101
|
-
layoutDistance: 3,
|
|
102
|
-
spacing: 5,
|
|
103
|
-
griper: 10,
|
|
104
|
-
colorbarHeight: 5,
|
|
269
|
+
layoutDistance: 3,
|
|
270
|
+
spacing: 5,
|
|
271
|
+
griper: 10,
|
|
272
|
+
colorbarHeight: 5,
|
|
105
273
|
isShowColorbar: true,
|
|
106
274
|
};
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Using the AUTO_LAYOUT Component (React)
|
|
278
|
+
|
|
279
|
+
```tsx
|
|
280
|
+
import { AUTO_LAYOUT } from "@lucablockltd/ultimate-packaging";
|
|
281
|
+
import type {
|
|
282
|
+
AutoLayoutRef,
|
|
283
|
+
AutoLayoutConfig,
|
|
284
|
+
AutoLayoutResult,
|
|
285
|
+
} from "@lucablockltd/ultimate-packaging";
|
|
286
|
+
|
|
287
|
+
const ref = useRef<AutoLayoutRef>(null);
|
|
288
|
+
const [result, setResult] = useState<AutoLayoutResult | null>(null);
|
|
107
289
|
|
|
108
290
|
<AUTO_LAYOUT ref={ref} config={config} onResult={setResult} />;
|
|
109
291
|
```
|
|
110
292
|
|
|
111
293
|
### AUTO_LAYOUT Props
|
|
112
294
|
|
|
113
|
-
| Prop | Type | Required | Description |
|
|
114
|
-
| ------------------ | -------------------------------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
115
|
-
| `config` | `AutoLayoutConfig \| null` | YES | Layout configuration
|
|
116
|
-
| `onResult` | `(result: AutoLayoutResult \| null) => void` | no |
|
|
117
|
-
| `onModifiedPapers` | `(papers: AutoLayoutPaperResult[]) => void` | no |
|
|
118
|
-
| `isShowSummary` | `boolean` | no | Show/hide summary panel (
|
|
119
|
-
| `isShowAction` | `AutoLayoutAction[]` | no | Which action buttons to show.
|
|
295
|
+
| Prop | Type | Required | Default | Description |
|
|
296
|
+
| ------------------ | -------------------------------------------- | -------- | ---------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
297
|
+
| `config` | `AutoLayoutConfig \| null` | YES | — | Layout configuration. Pass `null` to clear/hide the layout. |
|
|
298
|
+
| `onResult` | `(result: AutoLayoutResult \| null) => void` | no | — | Callback fired when layout calculation completes. Receives the full result object. |
|
|
299
|
+
| `onModifiedPapers` | `(papers: AutoLayoutPaperResult[]) => void` | no | — | Callback fired when user manually drags/modifies piece positions on the layout. |
|
|
300
|
+
| `isShowSummary` | `boolean` | no | `true` | Show/hide the summary stats panel above each paper (pieces/sheet, waste %, etc.) |
|
|
301
|
+
| `isShowAction` | `AutoLayoutAction[]` | no | all actions shown | Which action buttons to show. Values: `"EXPORT_SHEET"`, `"EXPORT_PDF"`, `"MODIFY_LAYOUT"`. Omit to show all. |
|
|
120
302
|
|
|
121
303
|
### AUTO_LAYOUT Ref Methods
|
|
122
304
|
|
|
305
|
+
Access these via `ref.current` after the component mounts:
|
|
306
|
+
|
|
123
307
|
```tsx
|
|
124
|
-
|
|
125
|
-
|
|
308
|
+
const ref = useRef<AutoLayoutRef>(null);
|
|
309
|
+
|
|
310
|
+
// 1. Get the full calculation result
|
|
311
|
+
const result = ref.current.getResult();
|
|
312
|
+
// → AutoLayoutResult | null
|
|
313
|
+
|
|
314
|
+
// 2. Export a specific paper's layout as PNG image
|
|
315
|
+
const pngBlob = await ref.current.exportImage(0); // 0 = first paper index
|
|
316
|
+
// → Blob (image/png)
|
|
126
317
|
|
|
127
|
-
// Export
|
|
128
|
-
const
|
|
318
|
+
// 3. Export a specific paper's layout as PDF
|
|
319
|
+
const pdfBlob = await ref.current.exportPdf(0);
|
|
320
|
+
// → Blob (application/pdf)
|
|
129
321
|
|
|
130
|
-
// Export
|
|
131
|
-
const
|
|
322
|
+
// 4. Export the dieline pattern as PNG
|
|
323
|
+
const dielineBlob = await ref.current.exportDielineImage();
|
|
324
|
+
// → Blob (image/png)
|
|
132
325
|
|
|
133
|
-
// Get packed result
|
|
326
|
+
// 5. Get packed result — includes images as File objects (useful for upload/FormData)
|
|
134
327
|
const packed = await ref.current.getPackedResult(0);
|
|
135
|
-
// → {
|
|
328
|
+
// → AutoLayoutPackedResult {
|
|
329
|
+
// ...all AutoLayoutPaperResult fields,
|
|
330
|
+
// quantity: number,
|
|
331
|
+
// dielineFile: File, // dieline pattern PNG
|
|
332
|
+
// layoutFile: File, // layout arrangement PNG
|
|
333
|
+
// }
|
|
136
334
|
```
|
|
137
335
|
|
|
138
|
-
### Programmatic Calculation (No UI)
|
|
336
|
+
### Programmatic Calculation (No UI / No React)
|
|
337
|
+
|
|
338
|
+
Use `calculateAutoLayout()` to compute layout without rendering any component.
|
|
339
|
+
Same config, same result — just no visual output.
|
|
139
340
|
|
|
140
341
|
```tsx
|
|
141
342
|
import { calculateAutoLayout } from "@lucablockltd/ultimate-packaging";
|
|
343
|
+
import type { AutoLayoutConfig, AutoLayoutResult } from "@lucablockltd/ultimate-packaging";
|
|
344
|
+
|
|
345
|
+
const config: AutoLayoutConfig = {
|
|
346
|
+
papers: [
|
|
347
|
+
{ paperId: "A", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
|
|
348
|
+
{ paperId: "B", paperName: '25x36"', paperWidth: 635, paperHeight: 914.4 },
|
|
349
|
+
],
|
|
350
|
+
model: {
|
|
351
|
+
modelId: "BECF-1010A",
|
|
352
|
+
attributes: { length: 60, width: 25, height: 100, glueArea: 15, dustFlap: 15, tuckFlap: 15 },
|
|
353
|
+
},
|
|
354
|
+
quantity: 1000,
|
|
355
|
+
// print settings are optional — defaults will be used
|
|
356
|
+
};
|
|
142
357
|
|
|
143
|
-
const result = calculateAutoLayout(config);
|
|
144
|
-
|
|
358
|
+
const result: AutoLayoutResult = calculateAutoLayout(config);
|
|
359
|
+
|
|
360
|
+
// Result structure:
|
|
361
|
+
// result.recommendedPaperId → "B" (best paper by least total area)
|
|
362
|
+
// result.quantity → 1000
|
|
363
|
+
// result.totalProduced → 1008 (may exceed quantity due to full sheets)
|
|
364
|
+
// result.totalSheets → total sheets across all papers
|
|
365
|
+
// result.remainingNeeded → 0 (if all produced)
|
|
366
|
+
// result.papers → AutoLayoutPaperResult[] (one per paper, sorted best-first)
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
### AutoLayoutResult — Output Reference
|
|
370
|
+
|
|
371
|
+
```typescript
|
|
372
|
+
interface AutoLayoutResult {
|
|
373
|
+
papers: AutoLayoutPaperResult[]; // Results per paper size, sorted by efficiency (best first)
|
|
374
|
+
quantity: number; // Original requested quantity
|
|
375
|
+
totalProduced: number; // Total pieces produced (may exceed quantity)
|
|
376
|
+
totalSheets: number; // Total sheets needed across all papers
|
|
377
|
+
remainingNeeded: number; // Shortfall (0 if quantity is met)
|
|
378
|
+
recommendedPaperId: string | null; // paperId of the most efficient paper (least total area)
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
interface AutoLayoutPaperResult {
|
|
382
|
+
paperId: string; // Matches the input paper's paperId
|
|
383
|
+
paperName: string; // Display name
|
|
384
|
+
paperWidth: number; // mm
|
|
385
|
+
paperHeight: number; // mm
|
|
386
|
+
producedPerSheet: number; // How many pieces fit on ONE sheet
|
|
387
|
+
totalSheets: number; // Sheets needed for the requested quantity
|
|
388
|
+
totalProduced: number; // producedPerSheet × totalSheets
|
|
389
|
+
excessCount: number; // Overproduction beyond quantity
|
|
390
|
+
wastePercent: number; // Unused paper area percentage (0–100)
|
|
391
|
+
independentSheets: number; // Sheets needed if ONLY this paper is used
|
|
392
|
+
independentTotalArea: number; // Total paper area in mm² if only this paper
|
|
393
|
+
placements: AutoLayoutPlacement[]; // Position of each piece on the sheet
|
|
394
|
+
gripperSide: GripperSide; // Which edge the gripper is on
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
interface AutoLayoutPlacement {
|
|
398
|
+
x: number; // mm from paper left edge
|
|
399
|
+
y: number; // mm from paper top edge
|
|
400
|
+
rotation: number; // 0 | 90 | 180 | 270 degrees
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
type GripperSide = "top" | "bottom" | "left" | "right";
|
|
404
|
+
// Automatically determined: landscape paper → "bottom", portrait → "left"
|
|
145
405
|
```
|
|
146
406
|
|
|
407
|
+
### Layout Algorithm Summary
|
|
408
|
+
|
|
409
|
+
1. The engine tries all 4 rotations (0°, 90°, 180°, 270°) for each piece on each paper
|
|
410
|
+
2. For each rotation, it packs pieces in a grid within the **usable bounds**
|
|
411
|
+
3. **Usable bounds** = paper area minus: `spacing` (all edges) + `griper` (one edge) + `colorbarHeight` (opposite edge)
|
|
412
|
+
4. The rotation that fits the **most pieces** wins
|
|
413
|
+
5. Papers are sorted by `independentTotalArea` (ascending) — the paper that uses the least total area to fulfill quantity is ranked #1
|
|
414
|
+
6. `recommendedPaperId` = the #1 ranked paper
|
|
415
|
+
|
|
147
416
|
---
|
|
148
417
|
|
|
149
418
|
## 3. Available Models & Attributes
|
package/dist/index.js
CHANGED
|
@@ -3757,6 +3757,64 @@ function generateBecf12109(attr) {
|
|
|
3757
3757
|
crease.push(line6(xSide2Mid, yFoldBottomStart, xEnd, D + C - 2));
|
|
3758
3758
|
return { cut, crease, viewBox: { width: totalWidth, height: totalHeight } };
|
|
3759
3759
|
}
|
|
3760
|
+
function generateOuterContour6(attr) {
|
|
3761
|
+
const { length: A, width: B, height: C, glueArea } = attr;
|
|
3762
|
+
const D = calcD2(C, B);
|
|
3763
|
+
const kulak = glueArea ?? calcKulak2(B);
|
|
3764
|
+
const totalWidth = kulak + 2 * A + 2 * B - 2;
|
|
3765
|
+
const totalHeight = D + C + B / 2 + DIP2;
|
|
3766
|
+
return [
|
|
3767
|
+
{ x: 0, y: 0 },
|
|
3768
|
+
{ x: totalWidth, y: 0 },
|
|
3769
|
+
{ x: totalWidth, y: totalHeight },
|
|
3770
|
+
{ x: 0, y: totalHeight }
|
|
3771
|
+
];
|
|
3772
|
+
}
|
|
3773
|
+
function offsetContour6(points, distance) {
|
|
3774
|
+
const n = points.length;
|
|
3775
|
+
const result = [];
|
|
3776
|
+
for (let i = 0; i < n; i++) {
|
|
3777
|
+
const prev = points[(i - 1 + n) % n];
|
|
3778
|
+
const curr = points[i];
|
|
3779
|
+
const next = points[(i + 1) % n];
|
|
3780
|
+
const e1x = curr.x - prev.x;
|
|
3781
|
+
const e1y = curr.y - prev.y;
|
|
3782
|
+
const e2x = next.x - curr.x;
|
|
3783
|
+
const e2y = next.y - curr.y;
|
|
3784
|
+
const len1 = Math.sqrt(e1x * e1x + e1y * e1y);
|
|
3785
|
+
const len2 = Math.sqrt(e2x * e2x + e2y * e2y);
|
|
3786
|
+
if (len1 === 0 || len2 === 0) {
|
|
3787
|
+
result.push(curr);
|
|
3788
|
+
continue;
|
|
3789
|
+
}
|
|
3790
|
+
const n1x = e1y / len1;
|
|
3791
|
+
const n1y = -e1x / len1;
|
|
3792
|
+
const n2x = e2y / len2;
|
|
3793
|
+
const n2y = -e2x / len2;
|
|
3794
|
+
const ax = n1x + n2x;
|
|
3795
|
+
const ay = n1y + n2y;
|
|
3796
|
+
const aLen = Math.sqrt(ax * ax + ay * ay);
|
|
3797
|
+
if (aLen < 1e-3) {
|
|
3798
|
+
result.push({ x: curr.x + n1x * distance, y: curr.y + n1y * distance });
|
|
3799
|
+
continue;
|
|
3800
|
+
}
|
|
3801
|
+
const nx = ax / aLen;
|
|
3802
|
+
const ny = ay / aLen;
|
|
3803
|
+
const dot = n1x * nx + n1y * ny;
|
|
3804
|
+
const d = distance / Math.max(dot, 0.1);
|
|
3805
|
+
result.push({ x: curr.x + nx * d, y: curr.y + ny * d });
|
|
3806
|
+
}
|
|
3807
|
+
return result;
|
|
3808
|
+
}
|
|
3809
|
+
function contourToPath6(points) {
|
|
3810
|
+
if (points.length === 0) return "";
|
|
3811
|
+
let d = `M${points[0].x} ${points[0].y}`;
|
|
3812
|
+
for (let i = 1; i < points.length; i++) {
|
|
3813
|
+
d += ` L${points[i].x} ${points[i].y}`;
|
|
3814
|
+
}
|
|
3815
|
+
d += " Z";
|
|
3816
|
+
return d;
|
|
3817
|
+
}
|
|
3760
3818
|
function generateDimensions6(attr, unit) {
|
|
3761
3819
|
const { length: A, width: B, height: C, glueArea } = attr;
|
|
3762
3820
|
const D = calcD2(C, B);
|
|
@@ -5041,6 +5099,156 @@ function computeLayoutForPaper4(contourPoints, dielineW, dielineH, _paperWidth,
|
|
|
5041
5099
|
};
|
|
5042
5100
|
}
|
|
5043
5101
|
|
|
5102
|
+
// src/utils/autoLayout/calculate/bags-pillows/becf-12109/index.ts
|
|
5103
|
+
function prepareRotation5(contourPoints, dielineW, dielineH, deg) {
|
|
5104
|
+
const rotated = normalizePoints(contourPoints, deg);
|
|
5105
|
+
return {
|
|
5106
|
+
profile: buildProfile(rotated),
|
|
5107
|
+
bbOffset: computeDielineBBOffset(contourPoints, dielineW, dielineH, deg),
|
|
5108
|
+
deg
|
|
5109
|
+
};
|
|
5110
|
+
}
|
|
5111
|
+
function pairGrid4(rdA, rdB, bounds) {
|
|
5112
|
+
const pA = rdA.profile, pB = rdB.profile;
|
|
5113
|
+
const gapAB = findMinStepX(pA, pB, 0);
|
|
5114
|
+
if (gapAB <= 0) return emptyResult();
|
|
5115
|
+
const pairW = gapAB + pB.width;
|
|
5116
|
+
const pairH = Math.max(pA.height, pB.height);
|
|
5117
|
+
const pairStepX = gapAB + findMinStepX(pB, pA, 0);
|
|
5118
|
+
const pairStepY = Math.max(
|
|
5119
|
+
findMinStepY(pA, pA, 0),
|
|
5120
|
+
findMinStepY(pA, pB, gapAB),
|
|
5121
|
+
findMinStepY(pB, pA, -gapAB),
|
|
5122
|
+
findMinStepY(pB, pB, 0)
|
|
5123
|
+
);
|
|
5124
|
+
if (pairStepX <= 0 || pairStepY <= 0) return emptyResult();
|
|
5125
|
+
const usableW = bounds.right - bounds.left;
|
|
5126
|
+
const usableH = bounds.bottom - bounds.top;
|
|
5127
|
+
const pairCols = Math.max(0, Math.floor((usableW - pairW) / pairStepX) + 1);
|
|
5128
|
+
const pairRows = Math.max(0, Math.floor((usableH - pairH) / pairStepY) + 1);
|
|
5129
|
+
if (pairCols === 0 || pairRows === 0) return emptyResult();
|
|
5130
|
+
const placements = [];
|
|
5131
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
5132
|
+
for (let r = 0; r < pairRows; r++) {
|
|
5133
|
+
for (let c = 0; c < pairCols; c++) {
|
|
5134
|
+
const baseX = bounds.left + c * pairStepX;
|
|
5135
|
+
const baseY = bounds.top + r * pairStepY;
|
|
5136
|
+
const oxA = baseX;
|
|
5137
|
+
const oyA = baseY;
|
|
5138
|
+
placements.push({ x: oxA + rdA.bbOffset.dx, y: oyA + rdA.bbOffset.dy, rotation: rdA.deg });
|
|
5139
|
+
if (oxA < minX) minX = oxA;
|
|
5140
|
+
if (oyA < minY) minY = oyA;
|
|
5141
|
+
if (oxA + pA.width > maxX) maxX = oxA + pA.width;
|
|
5142
|
+
if (oyA + pA.height > maxY) maxY = oyA + pA.height;
|
|
5143
|
+
const oxB = baseX + gapAB;
|
|
5144
|
+
const oyB = baseY;
|
|
5145
|
+
placements.push({ x: oxB + rdB.bbOffset.dx, y: oyB + rdB.bbOffset.dy, rotation: rdB.deg });
|
|
5146
|
+
if (oxB < minX) minX = oxB;
|
|
5147
|
+
if (oyB < minY) minY = oyB;
|
|
5148
|
+
if (oxB + pB.width > maxX) maxX = oxB + pB.width;
|
|
5149
|
+
if (oyB + pB.height > maxY) maxY = oyB + pB.height;
|
|
5150
|
+
}
|
|
5151
|
+
}
|
|
5152
|
+
const pairsRightEdge = bounds.left + (pairCols - 1) * pairStepX + pairW;
|
|
5153
|
+
const pairsBottomEdge = bounds.top + (pairRows - 1) * pairStepY + pairH;
|
|
5154
|
+
const gapRight = bounds.right - pairsRightEdge;
|
|
5155
|
+
const gapBottom = bounds.bottom - pairsBottomEdge;
|
|
5156
|
+
const extraRotations = [rdA, rdB];
|
|
5157
|
+
for (const rdExtra of extraRotations) {
|
|
5158
|
+
const pe = rdExtra.profile;
|
|
5159
|
+
if (pe.width <= gapRight) {
|
|
5160
|
+
for (let r = 0; r < pairRows; r++) {
|
|
5161
|
+
const ox = pairsRightEdge;
|
|
5162
|
+
const oy = bounds.top + r * pairStepY;
|
|
5163
|
+
if (ox + pe.width <= bounds.right && oy + pe.height <= bounds.bottom) {
|
|
5164
|
+
placements.push({ x: ox + rdExtra.bbOffset.dx, y: oy + rdExtra.bbOffset.dy, rotation: rdExtra.deg });
|
|
5165
|
+
if (ox + pe.width > maxX) maxX = ox + pe.width;
|
|
5166
|
+
}
|
|
5167
|
+
}
|
|
5168
|
+
break;
|
|
5169
|
+
}
|
|
5170
|
+
}
|
|
5171
|
+
for (const rdExtra of extraRotations) {
|
|
5172
|
+
const pe = rdExtra.profile;
|
|
5173
|
+
if (pe.height <= gapBottom) {
|
|
5174
|
+
for (let c = 0; c < pairCols; c++) {
|
|
5175
|
+
const oxA = bounds.left + c * pairStepX;
|
|
5176
|
+
const oy = pairsBottomEdge;
|
|
5177
|
+
if (oxA + pe.width <= bounds.right && oy + pe.height <= bounds.bottom) {
|
|
5178
|
+
placements.push({ x: oxA + rdExtra.bbOffset.dx, y: oy + rdExtra.bbOffset.dy, rotation: rdExtra.deg });
|
|
5179
|
+
if (oy + pe.height > maxY) maxY = oy + pe.height;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
break;
|
|
5183
|
+
}
|
|
5184
|
+
}
|
|
5185
|
+
return { placements, count: placements.length, minX, minY, maxX, maxY };
|
|
5186
|
+
}
|
|
5187
|
+
function computeLayoutForPaper5(contourPoints, dielineW, dielineH, _paperWidth, _paperHeight, bounds) {
|
|
5188
|
+
const rots = [0, 90, 180, 270];
|
|
5189
|
+
const rd = /* @__PURE__ */ new Map();
|
|
5190
|
+
for (const deg of rots) {
|
|
5191
|
+
rd.set(deg, prepareRotation5(contourPoints, dielineW, dielineH, deg));
|
|
5192
|
+
}
|
|
5193
|
+
const results = [];
|
|
5194
|
+
for (const deg of rots) {
|
|
5195
|
+
results.push(singleRotGrid(rd.get(deg), bounds));
|
|
5196
|
+
}
|
|
5197
|
+
const allPairs = [
|
|
5198
|
+
[0, 180],
|
|
5199
|
+
[90, 270],
|
|
5200
|
+
[0, 90],
|
|
5201
|
+
[0, 270],
|
|
5202
|
+
[90, 180],
|
|
5203
|
+
[180, 270]
|
|
5204
|
+
];
|
|
5205
|
+
for (const [a, b] of allPairs) {
|
|
5206
|
+
const rdA = rd.get(a), rdB = rd.get(b);
|
|
5207
|
+
const stepA = findMinStepX(rdA.profile, rdA.profile, 0);
|
|
5208
|
+
if (stepA <= 0) continue;
|
|
5209
|
+
for (let off = 0; off < stepA; off += 1) {
|
|
5210
|
+
results.push(dualRotGrid(rdA, rdB, off, bounds));
|
|
5211
|
+
results.push(dualRotGrid(rdB, rdA, off, bounds));
|
|
5212
|
+
}
|
|
5213
|
+
}
|
|
5214
|
+
for (const [a, b] of allPairs) {
|
|
5215
|
+
results.push(pairGrid4(rd.get(a), rd.get(b), bounds));
|
|
5216
|
+
results.push(pairGrid4(rd.get(b), rd.get(a), bounds));
|
|
5217
|
+
}
|
|
5218
|
+
const singleResults = [];
|
|
5219
|
+
const multiResults = [];
|
|
5220
|
+
for (const r of results) {
|
|
5221
|
+
if (r.count === 0) continue;
|
|
5222
|
+
const rotations = new Set(r.placements.map((p) => p.rotation));
|
|
5223
|
+
if (rotations.size > 1) {
|
|
5224
|
+
multiResults.push(r);
|
|
5225
|
+
} else {
|
|
5226
|
+
singleResults.push(r);
|
|
5227
|
+
}
|
|
5228
|
+
}
|
|
5229
|
+
function pickBest(arr) {
|
|
5230
|
+
let b = arr[0] ?? emptyResult();
|
|
5231
|
+
let bArea = (b.maxX - b.minX) * (b.maxY - b.minY);
|
|
5232
|
+
for (const r of arr) {
|
|
5233
|
+
const area = (r.maxX - r.minX) * (r.maxY - r.minY);
|
|
5234
|
+
if (r.count > b.count || r.count === b.count && area < bArea) {
|
|
5235
|
+
b = r;
|
|
5236
|
+
bArea = area;
|
|
5237
|
+
}
|
|
5238
|
+
}
|
|
5239
|
+
return b;
|
|
5240
|
+
}
|
|
5241
|
+
const bestSingle = pickBest(singleResults);
|
|
5242
|
+
const bestMulti = pickBest(multiResults);
|
|
5243
|
+
const best = bestMulti.count >= bestSingle.count && bestMulti.count > 0 ? bestMulti : bestSingle;
|
|
5244
|
+
centerLayout(best, bounds);
|
|
5245
|
+
return {
|
|
5246
|
+
rotation: best.placements[0]?.rotation ?? 0,
|
|
5247
|
+
placements: best.placements,
|
|
5248
|
+
producedPerSheet: best.count
|
|
5249
|
+
};
|
|
5250
|
+
}
|
|
5251
|
+
|
|
5044
5252
|
// src/utils/autoLayout/calculate/index.ts
|
|
5045
5253
|
function resolveModelFunctions(modelId, attrs) {
|
|
5046
5254
|
switch (modelId) {
|
|
@@ -5076,6 +5284,14 @@ function resolveModelFunctions(modelId, attrs) {
|
|
|
5076
5284
|
offsetContour: offsetContour5
|
|
5077
5285
|
};
|
|
5078
5286
|
}
|
|
5287
|
+
case "BECF-12109": {
|
|
5288
|
+
const a = attrs;
|
|
5289
|
+
return {
|
|
5290
|
+
contour: generateOuterContour6(a),
|
|
5291
|
+
dieline: generateBecf12109(a),
|
|
5292
|
+
offsetContour: offsetContour6
|
|
5293
|
+
};
|
|
5294
|
+
}
|
|
5079
5295
|
case "BECF-1010A":
|
|
5080
5296
|
default: {
|
|
5081
5297
|
const a = attrs;
|
|
@@ -5097,6 +5313,8 @@ function resolveLayoutCalculator(modelId) {
|
|
|
5097
5313
|
return computeLayoutForPaper3;
|
|
5098
5314
|
case "BECF-12101":
|
|
5099
5315
|
return computeLayoutForPaper4;
|
|
5316
|
+
case "BECF-12109":
|
|
5317
|
+
return computeLayoutForPaper5;
|
|
5100
5318
|
default:
|
|
5101
5319
|
return computeLayoutForPaperDefault;
|
|
5102
5320
|
}
|
|
@@ -5110,11 +5328,11 @@ function calculateAutoLayout(rawConfig) {
|
|
|
5110
5328
|
colorbarHeight: rawConfig.colorbarHeight ?? 5,
|
|
5111
5329
|
isShowColorbar: rawConfig.isShowColorbar ?? true
|
|
5112
5330
|
};
|
|
5113
|
-
const { contour, dieline, offsetContour:
|
|
5331
|
+
const { contour, dieline, offsetContour: offsetContour7 } = resolveModelFunctions(
|
|
5114
5332
|
config.model.modelId,
|
|
5115
5333
|
config.model.attributes
|
|
5116
5334
|
);
|
|
5117
|
-
const offsetContourPoints = config.layoutDistance > 0 ?
|
|
5335
|
+
const offsetContourPoints = config.layoutDistance > 0 ? offsetContour7(contour, config.layoutDistance / 2) : contour;
|
|
5118
5336
|
const cbDepth = config.isShowColorbar ? config.colorbarHeight : 0;
|
|
5119
5337
|
const computeLayout = resolveLayoutCalculator(config.model.modelId);
|
|
5120
5338
|
const paperResults = [];
|
|
@@ -6054,6 +6272,17 @@ function resolveModel(modelId, attrs, layoutDistance) {
|
|
|
6054
6272
|
generateContour: (a) => generateOuterContour5(a)
|
|
6055
6273
|
};
|
|
6056
6274
|
}
|
|
6275
|
+
case "BECF-12109": {
|
|
6276
|
+
const typedAttrs = attrs;
|
|
6277
|
+
const contour = generateOuterContour6(typedAttrs);
|
|
6278
|
+
return {
|
|
6279
|
+
dieline: generateBecf12109(typedAttrs),
|
|
6280
|
+
contourPath: contourToPath6(contour),
|
|
6281
|
+
offsetContourPoints: layoutDistance > 0 ? offsetContour6(contour, layoutDistance / 2) : contour,
|
|
6282
|
+
generate: (a) => generateBecf12109(a),
|
|
6283
|
+
generateContour: (a) => generateOuterContour6(a)
|
|
6284
|
+
};
|
|
6285
|
+
}
|
|
6057
6286
|
case "BECF-1010A":
|
|
6058
6287
|
default: {
|
|
6059
6288
|
const typedAttrs = attrs;
|
|
@@ -6103,6 +6332,14 @@ function renderDieLine2(modelId, attrs) {
|
|
|
6103
6332
|
renderAs: "group"
|
|
6104
6333
|
}
|
|
6105
6334
|
);
|
|
6335
|
+
case "BECF-12109":
|
|
6336
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
6337
|
+
DIE_LINE_BECF_12109,
|
|
6338
|
+
{
|
|
6339
|
+
attributes: attrs,
|
|
6340
|
+
renderAs: "group"
|
|
6341
|
+
}
|
|
6342
|
+
);
|
|
6106
6343
|
case "BECF-1010A":
|
|
6107
6344
|
default:
|
|
6108
6345
|
return /* @__PURE__ */ jsxRuntime.jsx(
|