@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.
@@ -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
- ```tsx
74
- import { AUTO_LAYOUT, calculateAutoLayout } from "@lucablockltd/ultimate-packaging";
75
- import type {
76
- AutoLayoutRef,
77
- AutoLayoutConfig,
78
- AutoLayoutResult,
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 ref = useRef<AutoLayoutRef>(null);
82
- const [result, setResult] = useState<AutoLayoutResult | null>(null);
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, // mm gap between pieces (offset contour)
102
- spacing: 5, // mm margin on sides
103
- griper: 10, // mm machine gripper zone
104
- colorbarHeight: 5, // mm colorbar strip height
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 (null = no render) |
116
- | `onResult` | `(result: AutoLayoutResult \| null) => void` | no | Called when layout is calculated |
117
- | `onModifiedPapers` | `(papers: AutoLayoutPaperResult[]) => void` | no | Called when user modifies layout manually |
118
- | `isShowSummary` | `boolean` | no | Show/hide summary panel (default: `true`) |
119
- | `isShowAction` | `AutoLayoutAction[]` | no | Which action buttons to show. If omitted, all are shown. Values: `"EXPORT_SHEET"`, `"EXPORT_PDF"`, `"MODIFY_LAYOUT"` |
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
- // Get calculation result
125
- ref.current.getResult(); // → AutoLayoutResult
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 layout image (PNG) for specific paper
128
- const png = await ref.current.exportImage(0); // paper index 0
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 layout as PDF
131
- const pdf = await ref.current.exportPdf(0);
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 with dieline + layout image files
326
+ // 5. Get packed result includes images as File objects (useful for upload/FormData)
134
327
  const packed = await ref.current.getPackedResult(0);
135
- // → { ...paperResult, quantity, dielineFile: File, layoutFile: File }
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
- // → AutoLayoutResult { papers, quantity, totalProduced, totalSheets, remainingNeeded, recommendedPaperId }
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: offsetContour6 } = resolveModelFunctions(
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 ? offsetContour6(contour, config.layoutDistance / 2) : contour;
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(