@lucablockltd/ultimate-packaging 1.0.3 → 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.
@@ -6,7 +6,7 @@
6
6
  ## Quick Start
7
7
 
8
8
  ```bash
9
- npm install PinitS/ultimate-packaging
9
+ npm install @lucablockltd/ultimate-packaging
10
10
  ```
11
11
 
12
12
  Requires `react >= 18.0.0` and `react-dom >= 18.0.0`.
@@ -18,13 +18,13 @@ Requires `react >= 18.0.0` and `react-dom >= 18.0.0`.
18
18
  Use `DIE_LINE_LAYOUT` — one component for ALL models. No switch-case needed.
19
19
 
20
20
  ```tsx
21
- import { DIE_LINE_LAYOUT, modelList } from "ultimate-packaging";
22
- import type { DieLineLayoutRef } from "ultimate-packaging";
21
+ import { DIE_LINE_LAYOUT, modelList } from "@lucablockltd/ultimate-packaging";
22
+ import type { DieLineLayoutRef } from "@lucablockltd/ultimate-packaging";
23
23
 
24
24
  const ref = useRef<DieLineLayoutRef>(null);
25
25
 
26
26
  // Get default attributes for any model
27
- const model = modelList.find(m => m.id === "BECF-1010A")!;
27
+ const model = modelList.find((m) => m.id === "BECF-1010A")!;
28
28
 
29
29
  <DIE_LINE_LAYOUT
30
30
  ref={ref}
@@ -33,18 +33,18 @@ const model = modelList.find(m => m.id === "BECF-1010A")!;
33
33
  unit="mm"
34
34
  mode="DIE_LINE"
35
35
  isShowDimensions={true}
36
- />
36
+ />;
37
37
  ```
38
38
 
39
39
  ### DIE_LINE_LAYOUT Props
40
40
 
41
- | Prop | Type | Required | Default | Description |
42
- |------|------|----------|---------|-------------|
43
- | `modelId` | `string` | YES | — | One of: `"BECF-1010A"`, `"BECF-1030A"`, `"BECF-1040A"`, `"BECF-11D01"` |
44
- | `attributes` | `object` | YES | — | Box dimensions (see Model Attributes below) |
45
- | `unit` | `"mm" \| "cm" \| "in"` | no | `"mm"` | Display unit for dimension labels |
46
- | `mode` | `ModelMode` | no | `"DIE_LINE"` | `"DIE_LINE"` = interactive canvas, `"AUTO_LAYOUT"` = raw SVG |
47
- | `isShowDimensions` | `boolean` | no | `false` | Show dimension annotation lines |
41
+ | Prop | Type | Required | Default | Description |
42
+ | ------------------ | ---------------------- | -------- | ------------ | ------------------------------------------------------------------------------------------------------ |
43
+ | `modelId` | `string` | YES | — | One of: `"BECF-1010A"`, `"BECF-1030A"`, `"BECF-1040A"`, `"BECF-11D01"`, `"BECF-12101"`, `"BECF-12109"` |
44
+ | `attributes` | `object` | YES | — | Box dimensions (see Model Attributes below) |
45
+ | `unit` | `"mm" \| "cm" \| "in"` | no | `"mm"` | Display unit for dimension labels |
46
+ | `mode` | `ModelMode` | no | `"DIE_LINE"` | `"DIE_LINE"` = interactive canvas, `"AUTO_LAYOUT"` = raw SVG |
47
+ | `isShowDimensions` | `boolean` | no | `false` | Show dimension annotation lines |
48
48
 
49
49
  ### DIE_LINE_LAYOUT Ref Methods
50
50
 
@@ -55,7 +55,10 @@ const ref = useRef<DieLineLayoutRef>(null);
55
55
  ref.current.getAttributes(); // → { length: 60, width: 25, ... }
56
56
 
57
57
  // Export as PNG image
58
- const blob = await ref.current.exportImage({ isShowDimension: true, originalSize: true });
58
+ const blob = await ref.current.exportImage({
59
+ isShowDimension: true,
60
+ originalSize: true,
61
+ });
59
62
 
60
63
  // Export dimensions as PDF
61
64
  const pdf = await ref.current.exportDimension();
@@ -66,68 +69,350 @@ const pdf = await ref.current.exportDimension();
66
69
  ## 2. Auto-Layout (Pack Dielines on Paper)
67
70
 
68
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.
69
73
 
70
- ```tsx
71
- import { AUTO_LAYOUT, calculateAutoLayout } from "ultimate-packaging";
72
- import type { AutoLayoutRef, AutoLayoutConfig, AutoLayoutResult } from "ultimate-packaging";
74
+ ### AutoLayoutConfig — Full Reference
73
75
 
74
- const ref = useRef<AutoLayoutRef>(null);
75
- const [result, setResult] = useState<AutoLayoutResult | null>(null);
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
+ ```
76
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,
199
+ } from "@lucablockltd/ultimate-packaging";
200
+
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.**
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
77
232
  const config: AutoLayoutConfig = {
78
233
  papers: [
79
- { paperId: "1", paperName: "20x28\"", paperWidth: 508, paperHeight: 711.2 },
80
- { paperId: "2", paperName: "25x36\"", paperWidth: 635, paperHeight: 914.4 },
234
+ { paperId: "1", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
81
235
  ],
82
236
  model: {
83
237
  modelId: "BECF-1010A",
84
238
  attributes: { length: 60, width: 25, height: 100, glueArea: 15, dustFlap: 15, tuckFlap: 15 },
85
239
  },
86
240
  quantity: 1000,
87
- layoutDistance: 3, // mm gap between pieces (offset contour)
88
- spacing: 5, // mm margin on sides
89
- griper: 10, // mm machine gripper zone
90
- colorbarHeight: 5, // mm colorbar strip height
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
252
+ const config: AutoLayoutConfig = {
253
+ papers: [
254
+ { paperId: "1", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
255
+ { paperId: "2", paperName: '25x36"', paperWidth: 635, paperHeight: 914.4 },
256
+ ],
257
+ model: {
258
+ modelId: "BECF-1010A",
259
+ attributes: {
260
+ length: 60,
261
+ width: 25,
262
+ height: 100,
263
+ glueArea: 15,
264
+ dustFlap: 15,
265
+ tuckFlap: 15,
266
+ },
267
+ },
268
+ quantity: 1000,
269
+ layoutDistance: 3,
270
+ spacing: 5,
271
+ griper: 10,
272
+ colorbarHeight: 5,
91
273
  isShowColorbar: true,
92
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);
93
289
 
94
- <AUTO_LAYOUT ref={ref} config={config} onResult={setResult} />
290
+ <AUTO_LAYOUT ref={ref} config={config} onResult={setResult} />;
95
291
  ```
96
292
 
97
293
  ### AUTO_LAYOUT Props
98
294
 
99
- | Prop | Type | Required | Description |
100
- |------|------|----------|-------------|
101
- | `config` | `AutoLayoutConfig \| null` | YES | Layout configuration (null = no render) |
102
- | `onResult` | `(result: AutoLayoutResult \| null) => void` | no | Called when layout is calculated |
103
- | `onModifiedPapers` | `(papers: AutoLayoutPaperResult[]) => void` | no | Called when user modifies layout manually |
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. |
104
302
 
105
303
  ### AUTO_LAYOUT Ref Methods
106
304
 
305
+ Access these via `ref.current` after the component mounts:
306
+
107
307
  ```tsx
108
- // Get calculation result
109
- 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
110
313
 
111
- // Export layout image (PNG) for specific paper
112
- const png = await ref.current.exportImage(0); // paper index 0
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)
113
317
 
114
- // Export layout as PDF
115
- const pdf = await ref.current.exportPdf(0);
318
+ // 3. Export a specific paper's layout as PDF
319
+ const pdfBlob = await ref.current.exportPdf(0);
320
+ // → Blob (application/pdf)
116
321
 
117
- // Get packed result with dieline + layout image files
322
+ // 4. Export the dieline pattern as PNG
323
+ const dielineBlob = await ref.current.exportDielineImage();
324
+ // → Blob (image/png)
325
+
326
+ // 5. Get packed result — includes images as File objects (useful for upload/FormData)
118
327
  const packed = await ref.current.getPackedResult(0);
119
- // → { ...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
+ // }
120
334
  ```
121
335
 
122
- ### 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.
123
340
 
124
341
  ```tsx
125
- import { calculateAutoLayout } from "ultimate-packaging";
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
+ };
357
+
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
+ }
126
380
 
127
- const result = calculateAutoLayout(config);
128
- // AutoLayoutResult { papers, quantity, totalProduced, totalSheets, remainingNeeded, recommendedPaperId }
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"
129
405
  ```
130
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
+
131
416
  ---
132
417
 
133
418
  ## 3. Available Models & Attributes
@@ -135,13 +420,13 @@ const result = calculateAutoLayout(config);
135
420
  ### Get All Models
136
421
 
137
422
  ```tsx
138
- import { modelList } from "ultimate-packaging";
423
+ import { modelList } from "@lucablockltd/ultimate-packaging";
139
424
 
140
- modelList.forEach(model => {
141
- console.log(model.id); // "BECF-1010A"
142
- console.log(model.nameEN); // "TUCK END BOXES TYPE A"
143
- console.log(model.nameTH); // "กล่องฝาเสียบ ก้นเสียบ A"
144
- console.log(model.dimension); // ["DIE_LINE"]
425
+ modelList.forEach((model) => {
426
+ console.log(model.id); // "BECF-1010A"
427
+ console.log(model.nameEN); // "TUCK END BOXES TYPE A"
428
+ console.log(model.nameTH); // "กล่องฝาเสียบ ก้นเสียบ A"
429
+ console.log(model.dimension); // ["DIE_LINE"]
145
430
  console.log(model.attributes); // { length: 60, width: 25, ... }
146
431
  });
147
432
  ```
@@ -152,36 +437,57 @@ modelList.forEach(model => {
152
437
 
153
438
  ```typescript
154
439
  interface TuckEndBoxAttributes {
155
- length: number; // box length (mm)
156
- width: number; // box width (mm)
157
- height: number; // box height (mm)
158
- glueArea: number; // glue flap width (mm)
159
- dustFlap: number; // dust flap extension (mm)
160
- tuckFlap: number; // tuck flap height (mm)
440
+ length: number; // box length (mm)
441
+ width: number; // box width (mm)
442
+ height: number; // box height (mm)
443
+ glueArea: number; // glue flap width (mm)
444
+ dustFlap: number; // dust flap extension (mm)
445
+ tuckFlap: number; // tuck flap height (mm)
161
446
  }
162
447
  ```
163
448
 
164
- | Model | length | width | height | glueArea | dustFlap | tuckFlap |
165
- |-------|--------|-------|--------|----------|----------|----------|
166
- | BECF-1010A | 60 | 25 | 100 | 15 | 15 | 15 |
167
- | BECF-1030A | 105 | 50 | 155 | 15 | 25 | 14 |
168
- | BECF-1040A | 56 | 56 | 150 | 15 | 30 | 14 |
449
+ | Model | length | width | height | glueArea | dustFlap | tuckFlap |
450
+ | ---------- | ------ | ----- | ------ | -------- | -------- | -------- |
451
+ | BECF-1010A | 60 | 25 | 100 | 15 | 15 | 15 |
452
+ | BECF-1030A | 105 | 50 | 155 | 15 | 25 | 14 |
453
+ | BECF-1040A | 56 | 56 | 150 | 15 | 30 | 14 |
169
454
 
170
455
  #### Standard Box (BECF-11D01)
171
456
 
172
457
  ```typescript
173
458
  interface StandardBoxAttributes {
174
- length: number; // box length (mm)
175
- width: number; // box width (mm)
176
- height: number; // box height (mm)
459
+ length: number; // box length (mm)
460
+ width: number; // box width (mm)
461
+ height: number; // box height (mm)
177
462
  flapHeight: number; // top/bottom flap height (mm)
178
- glueArea: number; // glue flap width (mm)
463
+ glueArea: number; // glue flap width (mm)
179
464
  }
180
465
  ```
181
466
 
182
- | Model | length | width | height | flapHeight | glueArea |
183
- |-------|--------|-------|--------|------------|----------|
184
- | BECF-11D01 | 100 | 50 | 150 | 50 | 13 |
467
+ | Model | length | width | height | flapHeight | glueArea |
468
+ | ---------- | ------ | ----- | ------ | ---------- | -------- |
469
+ | BECF-11D01 | 100 | 50 | 150 | 50 | 13 |
470
+
471
+ #### Carton Bags / Shopping Bags (BECF-12101, BECF-12109)
472
+
473
+ ```typescript
474
+ interface CartonBagAttributes {
475
+ length: number; // A — bag length / front-back panel width (mm)
476
+ width: number; // B — bag width / side panel gusset (mm)
477
+ height: number; // C — bag body height (mm)
478
+ glueArea: number; // glue flap width (mm)
479
+ }
480
+ ```
481
+
482
+ Auto-calculated values (not user inputs):
483
+
484
+ - `D` — top flap height = min(C - 2 - B/2, 40)
485
+ - `DIP` — bottom glue tab height (15mm for 12101, 13mm for 12109)
486
+
487
+ | Model | length | width | height | glueArea |
488
+ | ------------------- | ------ | ----- | ------ | -------- |
489
+ | BECF-12101 (Type A) | 100 | 50 | 150 | 13 |
490
+ | BECF-12109 (Type B) | 100 | 50 | 150 | 13 |
185
491
 
186
492
  ### Default Attribute Constants
187
493
 
@@ -191,7 +497,9 @@ import {
191
497
  BECF_1030A_DEFAULT_ATTRIBUTES,
192
498
  BECF_1040A_DEFAULT_ATTRIBUTES,
193
499
  BECF_11D01_DEFAULT_ATTRIBUTES,
194
- } from "ultimate-packaging";
500
+ BECF_12101_DEFAULT_ATTRIBUTES,
501
+ BECF_12109_DEFAULT_ATTRIBUTES,
502
+ } from "@lucablockltd/ultimate-packaging";
195
503
  ```
196
504
 
197
505
  ---
@@ -202,21 +510,21 @@ import {
202
510
 
203
511
  ```typescript
204
512
  interface AutoLayoutConfig {
205
- papers: AutoLayoutPaper[]; // paper sizes to try
206
- model: AutoLayoutModel; // { modelId, attributes }
207
- quantity: number; // total pieces needed
208
- layoutDistance: number; // mm between piece cut-lines
209
- spacing: number; // mm margin on paper sides
210
- griper: number; // mm machine gripper zone
211
- colorbarHeight: number; // mm colorbar strip
212
- isShowColorbar: boolean; // show colorbar on layout
513
+ papers: AutoLayoutPaper[]; // paper sizes to try
514
+ model: AutoLayoutModel; // { modelId, attributes }
515
+ quantity: number; // total pieces needed
516
+ layoutDistance?: number; // mm between piece cut-lines (default: 3)
517
+ spacing?: number; // mm margin on paper sides (default: 5)
518
+ griper?: number; // mm machine gripper zone (default: 10)
519
+ colorbarHeight?: number; // mm colorbar strip (default: 5)
520
+ isShowColorbar?: boolean; // show colorbar on layout (default: true)
213
521
  }
214
522
 
215
523
  interface AutoLayoutPaper {
216
524
  paperId: string;
217
525
  paperName: string;
218
- paperWidth: number; // mm
219
- paperHeight: number; // mm
526
+ paperWidth: number; // mm
527
+ paperHeight: number; // mm
220
528
  }
221
529
  ```
222
530
 
@@ -224,7 +532,7 @@ interface AutoLayoutPaper {
224
532
 
225
533
  ```typescript
226
534
  interface AutoLayoutResult {
227
- papers: AutoLayoutPaperResult[]; // results per paper size
535
+ papers: AutoLayoutPaperResult[]; // results per paper size
228
536
  quantity: number;
229
537
  totalProduced: number;
230
538
  totalSheets: number;
@@ -237,20 +545,20 @@ interface AutoLayoutPaperResult {
237
545
  paperName: string;
238
546
  paperWidth: number;
239
547
  paperHeight: number;
240
- producedPerSheet: number; // pieces per sheet
548
+ producedPerSheet: number; // pieces per sheet
241
549
  totalSheets: number;
242
550
  totalProduced: number;
243
- excessCount: number; // overproduction
244
- wastePercent: number; // paper waste %
245
- independentSheets: number; // sheets if using only this paper
246
- independentTotalArea: number; // total paper area (mm²)
551
+ excessCount: number; // overproduction
552
+ wastePercent: number; // paper waste %
553
+ independentSheets: number; // sheets if using only this paper
554
+ independentTotalArea: number; // total paper area (mm²)
247
555
  placements: AutoLayoutPlacement[];
248
- gripperSide: GripperSide; // "top" | "bottom" | "left" | "right"
556
+ gripperSide: GripperSide; // "top" | "bottom" | "left" | "right"
249
557
  }
250
558
 
251
559
  interface AutoLayoutPlacement {
252
- x: number; // mm from paper left
253
- y: number; // mm from paper top
560
+ x: number; // mm from paper left
561
+ y: number; // mm from paper top
254
562
  rotation: number; // 0 | 90 | 180 | 270
255
563
  }
256
564
  ```
@@ -260,8 +568,8 @@ interface AutoLayoutPlacement {
260
568
  ```typescript
261
569
  interface AutoLayoutPackedResult extends AutoLayoutPaperResult {
262
570
  quantity: number;
263
- dielineFile: File; // dieline pattern image
264
- layoutFile: File; // layout arrangement image
571
+ dielineFile: File; // dieline pattern image
572
+ layoutFile: File; // layout arrangement image
265
573
  }
266
574
  ```
267
575
 
@@ -275,15 +583,17 @@ type ModelMode = "DIE_LINE" | "3D" | "AUTO_LAYOUT";
275
583
 
276
584
  ## 5. Theme Configuration
277
585
 
586
+ `configurePackaging()` must be called **ONCE before rendering** any packaging components. All fields are optional — only override what you need.
587
+
278
588
  ```tsx
279
- import { configurePackaging } from "ultimate-packaging";
589
+ import { configurePackaging } from "@lucablockltd/ultimate-packaging";
280
590
 
281
- // Call ONCE at app startup, BEFORE rendering any components
282
591
  configurePackaging({
592
+ fontDimensionSize: 4.2, // dimension label font size (default: 4.2)
283
593
  modelTheme: {
284
594
  colorBackground: "#FAFAFA",
285
- colorDieLine: "#FF0000", // cut lines
286
- colorFoldLine: "#00FF00", // crease lines
595
+ colorDieLine: "#FF0000", // cut lines
596
+ colorFoldLine: "#00FF00", // crease lines
287
597
  },
288
598
  autoLayoutTheme: {
289
599
  colorBackground: "#F5F5F5",
@@ -299,6 +609,95 @@ configurePackaging({
299
609
  });
300
610
  ```
301
611
 
612
+ ### Where to call `configurePackaging()` per framework
613
+
614
+ #### Vite / CRA (Client-side SPA)
615
+
616
+ Call in `main.tsx` before `createRoot().render()`:
617
+
618
+ ```tsx
619
+ // main.tsx
620
+ import { configurePackaging } from "@lucablockltd/ultimate-packaging";
621
+
622
+ configurePackaging({ modelTheme: { colorDieLine: "#FF0000" } });
623
+
624
+ createRoot(document.getElementById("root")!).render(<App />);
625
+ ```
626
+
627
+ #### Next.js (App Router)
628
+
629
+ Create a client-side provider component and use it in the root layout:
630
+
631
+ ```tsx
632
+ // components/PackagingProvider.tsx
633
+ "use client";
634
+
635
+ import { configurePackaging } from "@lucablockltd/ultimate-packaging";
636
+
637
+ // Runs once on module load (client-side only)
638
+ configurePackaging({
639
+ modelTheme: { colorDieLine: "#FF0000" },
640
+ autoLayoutTheme: { colorDistanceLine: "#0088FF" },
641
+ });
642
+
643
+ export default function PackagingProvider({
644
+ children,
645
+ }: {
646
+ children: React.ReactNode;
647
+ }) {
648
+ return <>{children}</>;
649
+ }
650
+ ```
651
+
652
+ ```tsx
653
+ // app/layout.tsx
654
+ import PackagingProvider from "@/components/PackagingProvider";
655
+
656
+ export default function RootLayout({ children }: { children: React.ReactNode }) {
657
+ return (
658
+ <html>
659
+ <body>
660
+ <PackagingProvider>{children}</PackagingProvider>
661
+ </body>
662
+ </html>
663
+ );
664
+ }
665
+ ```
666
+
667
+ > **Why a `"use client"` provider?** — `configurePackaging()` sets a global variable. In Next.js App Router, server components run on the server where this global has no effect. Using `"use client"` ensures the config runs in the browser before any packaging components render.
668
+
669
+ #### Next.js (Pages Router)
670
+
671
+ Call in `_app.tsx`:
672
+
673
+ ```tsx
674
+ // pages/_app.tsx
675
+ import { configurePackaging } from "@lucablockltd/ultimate-packaging";
676
+ import type { AppProps } from "next/app";
677
+
678
+ configurePackaging({
679
+ modelTheme: { colorDieLine: "#FF0000" },
680
+ autoLayoutTheme: { colorDistanceLine: "#0088FF" },
681
+ });
682
+
683
+ export default function App({ Component, pageProps }: AppProps) {
684
+ return <Component {...pageProps} />;
685
+ }
686
+ ```
687
+
688
+ #### Remix
689
+
690
+ Call in `root.tsx` or a client-only module:
691
+
692
+ ```tsx
693
+ // app/root.tsx
694
+ import { configurePackaging } from "@lucablockltd/ultimate-packaging";
695
+
696
+ configurePackaging({
697
+ modelTheme: { colorDieLine: "#FF0000" },
698
+ });
699
+ ```
700
+
302
701
  ---
303
702
 
304
703
  ## 6. Complete Imports Cheat Sheet
@@ -306,15 +705,17 @@ configurePackaging({
306
705
  ```tsx
307
706
  // Components
308
707
  import {
309
- DIE_LINE_LAYOUT, // Recommended: auto-switches by modelId
310
- AUTO_LAYOUT, // Auto-layout packing on paper
311
- MODEL_BECF_1010A, // Direct model components (if needed)
708
+ DIE_LINE_LAYOUT, // Recommended: auto-switches by modelId
709
+ AUTO_LAYOUT, // Auto-layout packing on paper
710
+ MODEL_BECF_1010A, // Direct model components (if needed)
312
711
  MODEL_BECF_1030A,
313
712
  MODEL_BECF_1040A,
314
713
  MODEL_BECF_11D01,
315
- Colorbar, // Sub-components for custom rendering
714
+ MODEL_BECF_12101,
715
+ MODEL_BECF_12109,
716
+ Colorbar, // Sub-components for custom rendering
316
717
  Gripper,
317
- } from "ultimate-packaging";
718
+ } from "@lucablockltd/ultimate-packaging";
318
719
 
319
720
  // Types
320
721
  import type {
@@ -329,8 +730,11 @@ import type {
329
730
  AutoLayoutPlacement,
330
731
  AutoLayoutPaper,
331
732
  AutoLayoutModel,
733
+ AutoLayoutAction,
332
734
  TuckEndBoxAttributes,
333
735
  StandardBoxAttributes,
736
+ CartonBagAttributes,
737
+ CartonBag12109Attributes,
334
738
  ModelMode,
335
739
  PackagingModel,
336
740
  GripperSide,
@@ -338,24 +742,26 @@ import type {
338
742
  ModelThemeConfig,
339
743
  AutoLayoutThemeConfig,
340
744
  UltimatePackagingConfig,
341
- } from "ultimate-packaging";
745
+ } from "@lucablockltd/ultimate-packaging";
342
746
 
343
747
  // Data & Constants
344
748
  import {
345
- modelList, // PackagingModel[] — all models with defaults
749
+ modelList, // PackagingModel[] — all models with defaults
346
750
  BECF_1010A_DEFAULT_ATTRIBUTES,
347
751
  BECF_1030A_DEFAULT_ATTRIBUTES,
348
752
  BECF_1040A_DEFAULT_ATTRIBUTES,
349
753
  BECF_11D01_DEFAULT_ATTRIBUTES,
754
+ BECF_12101_DEFAULT_ATTRIBUTES,
755
+ BECF_12109_DEFAULT_ATTRIBUTES,
350
756
  MODEL_THEME_CONFIG,
351
757
  AUTO_LAYOUT_THEME_CONFIG,
352
- } from "ultimate-packaging";
758
+ } from "@lucablockltd/ultimate-packaging";
353
759
 
354
760
  // Functions
355
761
  import {
356
- configurePackaging, // Set global theme
357
- calculateAutoLayout, // Compute layout without UI
358
- } from "ultimate-packaging";
762
+ configurePackaging, // Set global theme
763
+ calculateAutoLayout, // Compute layout without UI
764
+ } from "@lucablockltd/ultimate-packaging";
359
765
  ```
360
766
 
361
767
  ---
@@ -365,8 +771,8 @@ import {
365
771
  ### Pattern A: Display a Dieline with Controls
366
772
 
367
773
  ```tsx
368
- import { DIE_LINE_LAYOUT, modelList } from "ultimate-packaging";
369
- import type { DieLineLayoutRef } from "ultimate-packaging";
774
+ import { DIE_LINE_LAYOUT, modelList } from "@lucablockltd/ultimate-packaging";
775
+ import type { DieLineLayoutRef } from "@lucablockltd/ultimate-packaging";
370
776
 
371
777
  function DielineEditor() {
372
778
  const ref = useRef<DieLineLayoutRef>(null);
@@ -375,13 +781,20 @@ function DielineEditor() {
375
781
 
376
782
  const handleModelChange = (id: string) => {
377
783
  setModelId(id);
378
- setAttrs(modelList.find(m => m.id === id)!.attributes);
784
+ setAttrs(modelList.find((m) => m.id === id)!.attributes);
379
785
  };
380
786
 
381
787
  return (
382
788
  <>
383
- <select value={modelId} onChange={e => handleModelChange(e.target.value)}>
384
- {modelList.map(m => <option key={m.id} value={m.id}>{m.nameEN}</option>)}
789
+ <select
790
+ value={modelId}
791
+ onChange={(e) => handleModelChange(e.target.value)}
792
+ >
793
+ {modelList.map((m) => (
794
+ <option key={m.id} value={m.id}>
795
+ {m.nameEN}
796
+ </option>
797
+ ))}
385
798
  </select>
386
799
  <DIE_LINE_LAYOUT ref={ref} modelId={modelId} attributes={attrs} />
387
800
  </>
@@ -392,16 +805,37 @@ function DielineEditor() {
392
805
  ### Pattern B: Auto-Layout with Export
393
806
 
394
807
  ```tsx
395
- import { AUTO_LAYOUT } from "ultimate-packaging";
396
- import type { AutoLayoutRef, AutoLayoutConfig, AutoLayoutResult } from "ultimate-packaging";
808
+ import { AUTO_LAYOUT } from "@lucablockltd/ultimate-packaging";
809
+ import type {
810
+ AutoLayoutRef,
811
+ AutoLayoutConfig,
812
+ AutoLayoutResult,
813
+ } from "@lucablockltd/ultimate-packaging";
397
814
 
398
815
  function LayoutPlanner() {
399
816
  const ref = useRef<AutoLayoutRef>(null);
400
817
  const [result, setResult] = useState<AutoLayoutResult | null>(null);
401
818
 
402
819
  const config: AutoLayoutConfig = {
403
- papers: [{ paperId: "1", paperName: "20x28\"", paperWidth: 508, paperHeight: 711.2 }],
404
- model: { modelId: "BECF-1010A", attributes: { length: 60, width: 25, height: 100, glueArea: 15, dustFlap: 15, tuckFlap: 15 } },
820
+ papers: [
821
+ {
822
+ paperId: "1",
823
+ paperName: '20x28"',
824
+ paperWidth: 508,
825
+ paperHeight: 711.2,
826
+ },
827
+ ],
828
+ model: {
829
+ modelId: "BECF-1010A",
830
+ attributes: {
831
+ length: 60,
832
+ width: 25,
833
+ height: 100,
834
+ glueArea: 15,
835
+ dustFlap: 15,
836
+ tuckFlap: 15,
837
+ },
838
+ },
405
839
  quantity: 500,
406
840
  layoutDistance: 3,
407
841
  spacing: 5,
@@ -420,7 +854,12 @@ function LayoutPlanner() {
420
854
  return (
421
855
  <>
422
856
  <AUTO_LAYOUT ref={ref} config={config} onResult={setResult} />
423
- {result && <p>Best: {result.recommendedPaperId} — {result.papers[0].producedPerSheet} pcs/sheet</p>}
857
+ {result && (
858
+ <p>
859
+ Best: {result.recommendedPaperId} —{" "}
860
+ {result.papers[0].producedPerSheet} pcs/sheet
861
+ </p>
862
+ )}
424
863
  <button onClick={handleExport}>Export</button>
425
864
  </>
426
865
  );
@@ -430,14 +869,24 @@ function LayoutPlanner() {
430
869
  ### Pattern C: Headless Calculation (No React)
431
870
 
432
871
  ```tsx
433
- import { calculateAutoLayout } from "ultimate-packaging";
872
+ import { calculateAutoLayout } from "@lucablockltd/ultimate-packaging";
434
873
 
435
874
  const result = calculateAutoLayout({
436
875
  papers: [
437
- { paperId: "A", paperName: "20x28\"", paperWidth: 508, paperHeight: 711.2 },
438
- { paperId: "B", paperName: "25x36\"", paperWidth: 635, paperHeight: 914.4 },
876
+ { paperId: "A", paperName: '20x28"', paperWidth: 508, paperHeight: 711.2 },
877
+ { paperId: "B", paperName: '25x36"', paperWidth: 635, paperHeight: 914.4 },
439
878
  ],
440
- model: { modelId: "BECF-1030A", attributes: { length: 105, width: 50, height: 155, glueArea: 15, dustFlap: 25, tuckFlap: 14 } },
879
+ model: {
880
+ modelId: "BECF-1030A",
881
+ attributes: {
882
+ length: 105,
883
+ width: 50,
884
+ height: 155,
885
+ glueArea: 15,
886
+ dustFlap: 25,
887
+ tuckFlap: 14,
888
+ },
889
+ },
441
890
  quantity: 1000,
442
891
  layoutDistance: 3,
443
892
  spacing: 5,
@@ -447,8 +896,10 @@ const result = calculateAutoLayout({
447
896
  });
448
897
 
449
898
  console.log(result.recommendedPaperId); // "B"
450
- result.papers.forEach(p => {
451
- console.log(`${p.paperName}: ${p.producedPerSheet} pcs/sheet, ${p.wastePercent}% waste`);
899
+ result.papers.forEach((p) => {
900
+ console.log(
901
+ `${p.paperName}: ${p.producedPerSheet} pcs/sheet, ${p.wastePercent}% waste`,
902
+ );
452
903
  });
453
904
  ```
454
905