@cutoptim/mcp 0.1.0 → 0.1.1

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/tools.gen.js CHANGED
@@ -101,9 +101,27 @@ export const TOOLS = [
101
101
  },
102
102
  "id": {
103
103
  "description": "The row's id, so a `matchedSets` layout can name it. Any JSON value is accepted here. Only an id a set REFERS TO is checked: it must be a non-empty string of printable characters, at most 64 long, or a safe integer (`7` and `\"7\"` are the same id), and it must be on exactly one part. Every other id is ignored, whatever it holds (a float, an object, null, repeated, long, empty): always without a non-empty `matchedSets`, and on the rows no set names with one — so an ERP article number is safe to send."
104
+ },
105
+ "qr": {
106
+ "type": "string",
107
+ "minLength": 1,
108
+ "maxLength": 300,
109
+ "description": "QR payload template for every copy of this row: returned resolved in `labels[].qr` and printed as a QR code (error correction level M) on each of the row's labels in `labelsPdf`. Exactly two tokens are filled in: `{piece}` (the copy, 1-based — the `k` of the `k/n` printed on the label) and `{of}` (the row's `qty`); any other `{…}` stays as sent. At most 300 UTF-8 bytes once filled in, printable characters only (no control characters); not together with `qrs`. A wrong value is a 400 `invalid_request` naming the field, before any quota is used. Metadata only: it never changes the plan or `planHash`. On a small label format a long payload can print below 0.4 mm per module, and `warnings` then says so. 2D only."
110
+ },
111
+ "qrs": {
112
+ "type": "array",
113
+ "minItems": 1,
114
+ "maxItems": 2000,
115
+ "items": {
116
+ "type": "string",
117
+ "minLength": 1,
118
+ "maxLength": 300
119
+ },
120
+ "description": "One QR payload per copy of this row, used verbatim: copy `k` (the `k/n` printed on its label) gets entry `k`, in `labels[].qr` and on `labelsPdf`. Exactly `qty` entries (`qty` defaults to 1), each 1 to 300 UTF-8 bytes of printable characters; not together with `qr`. A wrong value is a 400 `invalid_request` naming the field, before any quota is used. 2D only."
104
121
  }
105
122
  }
106
- }
123
+ },
124
+ "description": "The rectangular parts to cut, one row per part type (`w`, `h`, optional `qty`, `name`, grain, edge banding)."
107
125
  },
108
126
  "stock": {
109
127
  "type": "array",
@@ -159,7 +177,8 @@ export const TOOLS = [
159
177
  "description": "Opaque JSON object (any keys) echoed back VERBATIM on the matching output (a part's meta on every placed piece of it; a stock row's meta on every sheet/rod cut from it). Attach ERP article numbers, order-line ids, customer refs to reconcile the cut plan with your system. Never parsed, never affects placement. Part meta follows its request row: the meta of each row lands on exactly its qty of placed pieces, even when rows share a name; stock meta by stock-row order."
160
178
  }
161
179
  }
162
- }
180
+ },
181
+ "description": "The sheet sizes to cut from, one row per size (`w`, `h`, optional `qty`, `price`)."
163
182
  },
164
183
  "options": {
165
184
  "type": "object",
@@ -253,8 +272,148 @@ export const TOOLS = [
253
272
  "setOutlines": {
254
273
  "default": true,
255
274
  "description": "A boolean. With `matchedSets` and the `svg` export: draw the dashed frame and the \"SET A\" badge of every set instance, in ONE separate group (`<g id=\"matched-sets\" data-role=\"annotation\">`) a laser or CNC import can drop in one move. `false` leaves that group out (the position codes in the part labels stay). Read only in a request with sets — there a value other than `true`, `false` or `null` is a 400; without sets the field is ignored, whatever it holds."
275
+ },
276
+ "labels": {
277
+ "type": "object",
278
+ "additionalProperties": false,
279
+ "description": "Label settings — the same settings as the Label format panel of the CutOptim app, under these names. They shape `labelsPdf` and add `piece`, `of`, `sheetNo`, `stockRow` and `sheetMeta` to every `labels[]` entry. Every field is optional; `{}` is valid and only adds those fields. Absent: the label PDF is the A4 sheet and `labels[]` is exactly as without this object. Validated STRICTLY, unlike the rest of `options`: an unknown key, a wrong type or a value out of range is a 400 `invalid_request` with `details.path`, before any call is counted (also on `/v1/validate/2d`). `null` for a field means the field is absent. Sent without `include` `labels` or `labelsPdf`, it changes nothing and the answer says so in `warnings`. 2D only.",
280
+ "properties": {
281
+ "format": {
282
+ "type": "string",
283
+ "enum": [
284
+ "sheet",
285
+ "roll",
286
+ "apli-03056",
287
+ "brother-dk-11201"
288
+ ],
289
+ "default": "sheet",
290
+ "description": "`sheet` = the A4 sheet of labels (3 columns); `roll` = one label per page, `width` × `height` mm, for a label printer; `apli-03056` = APLI 03056 sheets (24 labels of 64.6 × 33.9 mm on A4, 3 × 8); `brother-dk-11201` = Brother DK-11201 roll labels (29 × 90 mm, one per page)."
291
+ },
292
+ "width": {
293
+ "type": "number",
294
+ "minimum": 20,
295
+ "maximum": 300,
296
+ "default": 100,
297
+ "description": "Label width in mm, `roll` only."
298
+ },
299
+ "height": {
300
+ "type": "number",
301
+ "minimum": 15,
302
+ "maximum": 300,
303
+ "default": 40,
304
+ "description": "Label height in mm, `roll` only."
305
+ },
306
+ "offset": {
307
+ "type": "object",
308
+ "additionalProperties": false,
309
+ "description": "Printer offset in mm: moves everything printed on the label right (`x`) and down (`y`); negative values move it back. Applied rounded to 0.1 mm.",
310
+ "properties": {
311
+ "x": {
312
+ "type": "number",
313
+ "minimum": -20,
314
+ "maximum": 20,
315
+ "default": 0,
316
+ "description": "Shift right in mm (negative = left)."
317
+ },
318
+ "y": {
319
+ "type": "number",
320
+ "minimum": -20,
321
+ "maximum": 20,
322
+ "default": 0,
323
+ "description": "Shift down in mm (negative = up)."
324
+ }
325
+ }
326
+ },
327
+ "dimSize": {
328
+ "type": "string",
329
+ "enum": [
330
+ "normal",
331
+ "large",
332
+ "xl"
333
+ ],
334
+ "default": "normal",
335
+ "description": "Size of the dimension text on the label."
336
+ },
337
+ "partNo": {
338
+ "type": "boolean",
339
+ "default": false,
340
+ "description": "Print the part number, top left and largest: the 1-based row of the FIRST `parts` row with that name (rows sharing a name share a number; with unique names it equals `labels[].partNo`)."
341
+ },
342
+ "order": {
343
+ "type": "string",
344
+ "enum": [
345
+ "input",
346
+ "cut"
347
+ ],
348
+ "default": "input",
349
+ "description": "`input` = labels in `parts` order; `cut` = sheet by sheet, in the order the saw plan frees the pieces."
350
+ },
351
+ "stages": {
352
+ "type": "array",
353
+ "maxItems": 6,
354
+ "items": {
355
+ "type": "string",
356
+ "minLength": 1,
357
+ "maxLength": 6
358
+ },
359
+ "description": "Work stages printed as a row of circles to tick off by pen; empty or absent = no row. Each entry is a built-in key — `rc` (rough cut), `c2s` (cut to size), `eb` (edge banded, printed only on a banded part), `s` (sanded), `d` (drilled, not printed yet) — or free text of at most 6 characters, printed as sent (keys are lower case: `RC` is free text). Spaces are trimmed; printable characters only."
360
+ },
361
+ "stageWords": {
362
+ "type": "object",
363
+ "additionalProperties": false,
364
+ "description": "Your own word for a built-in stage key, at most 6 characters; empty or absent = the English abbreviation (RC, C2S, EB, S, D).",
365
+ "properties": {
366
+ "rc": {
367
+ "type": "string",
368
+ "maxLength": 6,
369
+ "description": "Your word for `rc` (rough cut); default RC."
370
+ },
371
+ "c2s": {
372
+ "type": "string",
373
+ "maxLength": 6,
374
+ "description": "Your word for `c2s` (cut to size); default C2S."
375
+ },
376
+ "eb": {
377
+ "type": "string",
378
+ "maxLength": 6,
379
+ "description": "Your word for `eb` (edge banded); default EB."
380
+ },
381
+ "s": {
382
+ "type": "string",
383
+ "maxLength": 6,
384
+ "description": "Your word for `s` (sanded); default S."
385
+ },
386
+ "d": {
387
+ "type": "string",
388
+ "maxLength": 6,
389
+ "description": "Your word for `d` (drilled); default D."
390
+ }
391
+ }
392
+ },
393
+ "factoryEdges": {
394
+ "type": "boolean",
395
+ "default": false,
396
+ "description": "Mark FE on the edges of a piece that lie on a sheet edge with no `trim` on that side (the board's factory edge)."
397
+ },
398
+ "hiddenFaceHint": {
399
+ "type": "boolean",
400
+ "default": false,
401
+ "description": "Print the \"hidden side\" mark."
402
+ },
403
+ "parts": {
404
+ "type": "array",
405
+ "maxItems": 500,
406
+ "items": {
407
+ "type": "integer",
408
+ "minimum": 1,
409
+ "maximum": 999999
410
+ },
411
+ "description": "Print only the labels of these part numbers (the numbers `partNo` prints), e.g. to re-print after sanding. Empty or absent = all."
412
+ }
413
+ }
256
414
  }
257
- }
415
+ },
416
+ "description": "Kerf, trim, first-cut direction, cost and stock rules, effort and label output; every field is optional."
258
417
  },
259
418
  "engine": {
260
419
  "type": "string",
@@ -627,6 +786,12 @@ export const TOOLS = [
627
786
  "stage": {
628
787
  "type": "integer",
629
788
  "minimum": 1
789
+ },
790
+ "angle": {
791
+ "type": "number"
792
+ },
793
+ "bevel": {
794
+ "type": "number"
630
795
  }
631
796
  }
632
797
  }
@@ -996,6 +1161,28 @@ export const TOOLS = [
996
1161
  },
997
1162
  "meta": {
998
1163
  "type": "object"
1164
+ },
1165
+ "piece": {
1166
+ "type": "integer",
1167
+ "minimum": 1
1168
+ },
1169
+ "of": {
1170
+ "type": "integer",
1171
+ "minimum": 1
1172
+ },
1173
+ "sheetNo": {
1174
+ "type": "integer",
1175
+ "minimum": 1
1176
+ },
1177
+ "stockRow": {
1178
+ "type": "integer",
1179
+ "minimum": 0
1180
+ },
1181
+ "sheetMeta": {
1182
+ "type": "object"
1183
+ },
1184
+ "qr": {
1185
+ "type": "string"
999
1186
  }
1000
1187
  }
1001
1188
  }
@@ -1139,9 +1326,81 @@ export const TOOLS = [
1139
1326
  "meta": {
1140
1327
  "type": "object",
1141
1328
  "description": "Opaque JSON object (any keys) echoed back VERBATIM on the matching output (a part's meta on every placed piece of it; a stock row's meta on every sheet/rod cut from it). Attach ERP article numbers, order-line ids, customer refs to reconcile the cut plan with your system. Never parsed, never affects placement. Part meta follows its request row: the meta of each row lands on exactly its qty of placed pieces, even when rows share a name; stock meta by stock-row order."
1329
+ },
1330
+ "ends": {
1331
+ "type": "object",
1332
+ "description": "The angled ends of a part, read only with `options.mitrePairing`. Two parts of one section whose facing ends lie in one plane (after a turn `flip` allows) can be cut with ONE saw cut between them: the triangle between the long and the short point is not wasted. Without `mitrePairing` the ends are not used (a warning says so) and the part packs as its `length`.",
1333
+ "properties": {
1334
+ "start": {
1335
+ "type": "object",
1336
+ "required": [
1337
+ "angle"
1338
+ ],
1339
+ "description": "One end cut of a part, in degrees from square (0 = a square end), signed. The part is measured long point to long point (`length` is its overall length). A part whose two end cuts would cross inside its section (shorter than its leads) cannot be made: a 400 `invalid_request` naming `parts[i].ends` says how long it must at least be.",
1340
+ "properties": {
1341
+ "angle": {
1342
+ "type": "number",
1343
+ "exclusiveMinimum": -90,
1344
+ "exclusiveMaximum": 90,
1345
+ "description": "The face angle: the mitre or plumb cut as seen on the `depth` side (wood: on `sh` as entered)."
1346
+ },
1347
+ "bevel": {
1348
+ "type": "number",
1349
+ "exclusiveMinimum": -90,
1350
+ "exclusiveMaximum": 90,
1351
+ "default": 0,
1352
+ "description": "The edge angle: the bevel as seen on the `thickness` side (wood: on `sw` as entered). A compound cut has both."
1353
+ }
1354
+ }
1355
+ },
1356
+ "end": {
1357
+ "type": "object",
1358
+ "required": [
1359
+ "angle"
1360
+ ],
1361
+ "description": "One end cut of a part, in degrees from square (0 = a square end), signed. The part is measured long point to long point (`length` is its overall length). A part whose two end cuts would cross inside its section (shorter than its leads) cannot be made: a 400 `invalid_request` naming `parts[i].ends` says how long it must at least be.",
1362
+ "properties": {
1363
+ "angle": {
1364
+ "type": "number",
1365
+ "exclusiveMinimum": -90,
1366
+ "exclusiveMaximum": 90,
1367
+ "description": "The face angle: the mitre or plumb cut as seen on the `depth` side (wood: on `sh` as entered)."
1368
+ },
1369
+ "bevel": {
1370
+ "type": "number",
1371
+ "exclusiveMinimum": -90,
1372
+ "exclusiveMaximum": 90,
1373
+ "default": 0,
1374
+ "description": "The edge angle: the bevel as seen on the `thickness` side (wood: on `sw` as entered). A compound cut has both."
1375
+ }
1376
+ }
1377
+ }
1378
+ }
1379
+ },
1380
+ "flip": {
1381
+ "type": "string",
1382
+ "enum": [
1383
+ "none",
1384
+ "face",
1385
+ "edge",
1386
+ "any"
1387
+ ],
1388
+ "default": "none",
1389
+ "description": "Mitre pairing only: which 180° turns of the part on the bar are allowed, so its angled end can meet a neighbour's. `none` (default — a profiled moulding or one-sided stock), `face` (end for end, the crown or top edge stays up — roof members), `edge` (end for end over the edge — flat stock, frames), `any`."
1390
+ },
1391
+ "depth": {
1392
+ "type": "number",
1393
+ "exclusiveMinimum": 0,
1394
+ "description": "Mitre pairing only: the side of the bar the `angle` of `ends` is seen on. Required with `ends` (a 1D part has no cross-section to take it from). Unit-agnostic: pick one unit (mm, inch, …) for every length in the request and every length in the response is in that same unit. Nothing is converted server-side."
1395
+ },
1396
+ "thickness": {
1397
+ "type": "number",
1398
+ "exclusiveMinimum": 0,
1399
+ "description": "Mitre pairing only: the other side, the one the `bevel` is seen on. Required with `ends`. Unit-agnostic: pick one unit (mm, inch, …) for every length in the request and every length in the response is in that same unit. Nothing is converted server-side."
1142
1400
  }
1143
1401
  }
1144
- }
1402
+ },
1403
+ "description": "The lengths to cut, one row per part type (`length`, optional `qty`, `name`, mitred `ends`)."
1145
1404
  },
1146
1405
  "stock": {
1147
1406
  "type": "array",
@@ -1183,7 +1442,8 @@ export const TOOLS = [
1183
1442
  "description": "Opaque JSON object (any keys) echoed back VERBATIM on the matching output (a part's meta on every placed piece of it; a stock row's meta on every sheet/rod cut from it). Attach ERP article numbers, order-line ids, customer refs to reconcile the cut plan with your system. Never parsed, never affects placement. Part meta follows its request row: the meta of each row lands on exactly its qty of placed pieces, even when rows share a name; stock meta by stock-row order."
1184
1443
  }
1185
1444
  }
1186
- }
1445
+ },
1446
+ "description": "The bar lengths to cut from, one row per length (`length`, optional `qty`, `price`)."
1187
1447
  },
1188
1448
  "options": {
1189
1449
  "type": "object",
@@ -1232,6 +1492,11 @@ export const TOOLS = [
1232
1492
  "minimum": 0,
1233
1493
  "description": "Output filter: report a leftover only if it is at least this long.",
1234
1494
  "default": 0
1495
+ },
1496
+ "mitrePairing": {
1497
+ "type": "boolean",
1498
+ "default": false,
1499
+ "description": "Pair parts of one section whose facing ends (`parts[].ends`) can share ONE saw cut. Off by default: a request without it is solved exactly as before. On, a plan with pairs is returned only when it ranks strictly better than the unpaired plan by the packer's own ranking (parts placed first, then material — or price with `minimizeCost`); otherwise the unpaired plan comes back. Pairs only, no chains of three or more."
1235
1500
  }
1236
1501
  }
1237
1502
  },
@@ -1339,6 +1604,37 @@ export const TOOLS = [
1339
1604
  },
1340
1605
  "meta": {
1341
1606
  "type": "object"
1607
+ },
1608
+ "sharedCut": {
1609
+ "type": "object",
1610
+ "required": [
1611
+ "angle",
1612
+ "bevel",
1613
+ "kerfAlongAxis",
1614
+ "lead"
1615
+ ],
1616
+ "properties": {
1617
+ "angle": {
1618
+ "type": "number"
1619
+ },
1620
+ "bevel": {
1621
+ "type": "number"
1622
+ },
1623
+ "kerfAlongAxis": {
1624
+ "type": "number"
1625
+ },
1626
+ "lead": {
1627
+ "type": "number"
1628
+ }
1629
+ }
1630
+ },
1631
+ "flipped": {
1632
+ "type": "string",
1633
+ "enum": [
1634
+ "face",
1635
+ "edge",
1636
+ "roll"
1637
+ ]
1342
1638
  }
1343
1639
  }
1344
1640
  }
@@ -1390,6 +1686,9 @@ export const TOOLS = [
1390
1686
  },
1391
1687
  "toleranceAcceptedCount": {
1392
1688
  "type": "integer"
1689
+ },
1690
+ "sharedCuts": {
1691
+ "type": "integer"
1393
1692
  }
1394
1693
  }
1395
1694
  },
@@ -1434,6 +1733,12 @@ export const TOOLS = [
1434
1733
  "stage": {
1435
1734
  "type": "integer",
1436
1735
  "minimum": 1
1736
+ },
1737
+ "angle": {
1738
+ "type": "number"
1739
+ },
1740
+ "bevel": {
1741
+ "type": "number"
1437
1742
  }
1438
1743
  }
1439
1744
  }
@@ -1632,9 +1937,71 @@ export const TOOLS = [
1632
1937
  "meta": {
1633
1938
  "type": "object",
1634
1939
  "description": "Opaque JSON object (any keys) echoed back VERBATIM on the matching output (a part's meta on every placed piece of it; a stock row's meta on every sheet/rod cut from it). Attach ERP article numbers, order-line ids, customer refs to reconcile the cut plan with your system. Never parsed, never affects placement. Part meta follows its request row: the meta of each row lands on exactly its qty of placed pieces, even when rows share a name; stock meta by stock-row order."
1940
+ },
1941
+ "ends": {
1942
+ "type": "object",
1943
+ "description": "Angled ends (mitre pairing). The `angle` is seen on `sh` AS ENTERED and the `bevel` on `sw`, so send the section the way the angles were measured.",
1944
+ "properties": {
1945
+ "start": {
1946
+ "type": "object",
1947
+ "required": [
1948
+ "angle"
1949
+ ],
1950
+ "description": "One end cut of a part, in degrees from square (0 = a square end), signed. The part is measured long point to long point (`length` is its overall length). A part whose two end cuts would cross inside its section (shorter than its leads) cannot be made: a 400 `invalid_request` naming `parts[i].ends` says how long it must at least be.",
1951
+ "properties": {
1952
+ "angle": {
1953
+ "type": "number",
1954
+ "exclusiveMinimum": -90,
1955
+ "exclusiveMaximum": 90,
1956
+ "description": "The face angle: the mitre or plumb cut as seen on the `depth` side (wood: on `sh` as entered)."
1957
+ },
1958
+ "bevel": {
1959
+ "type": "number",
1960
+ "exclusiveMinimum": -90,
1961
+ "exclusiveMaximum": 90,
1962
+ "default": 0,
1963
+ "description": "The edge angle: the bevel as seen on the `thickness` side (wood: on `sw` as entered). A compound cut has both."
1964
+ }
1965
+ }
1966
+ },
1967
+ "end": {
1968
+ "type": "object",
1969
+ "required": [
1970
+ "angle"
1971
+ ],
1972
+ "description": "One end cut of a part, in degrees from square (0 = a square end), signed. The part is measured long point to long point (`length` is its overall length). A part whose two end cuts would cross inside its section (shorter than its leads) cannot be made: a 400 `invalid_request` naming `parts[i].ends` says how long it must at least be.",
1973
+ "properties": {
1974
+ "angle": {
1975
+ "type": "number",
1976
+ "exclusiveMinimum": -90,
1977
+ "exclusiveMaximum": 90,
1978
+ "description": "The face angle: the mitre or plumb cut as seen on the `depth` side (wood: on `sh` as entered)."
1979
+ },
1980
+ "bevel": {
1981
+ "type": "number",
1982
+ "exclusiveMinimum": -90,
1983
+ "exclusiveMaximum": 90,
1984
+ "default": 0,
1985
+ "description": "The edge angle: the bevel as seen on the `thickness` side (wood: on `sw` as entered). A compound cut has both."
1986
+ }
1987
+ }
1988
+ }
1989
+ }
1990
+ },
1991
+ "flip": {
1992
+ "type": "string",
1993
+ "enum": [
1994
+ "none",
1995
+ "face",
1996
+ "edge",
1997
+ "any"
1998
+ ],
1999
+ "default": "none",
2000
+ "description": "Mitre pairing only: which 180° turns of the part on the bar are allowed, so its angled end can meet a neighbour's. `none` (default — a profiled moulding or one-sided stock), `face` (end for end, the crown or top edge stays up — roof members), `edge` (end for end over the edge — flat stock, frames), `any`."
1635
2001
  }
1636
2002
  }
1637
- }
2003
+ },
2004
+ "description": "The timber parts to cut, one row per part type: cross-section `sw` × `sh`, `length`, optional `qty`."
1638
2005
  },
1639
2006
  "stock": {
1640
2007
  "type": "array",
@@ -1743,6 +2110,11 @@ export const TOOLS = [
1743
2110
  "minimum": 0,
1744
2111
  "description": "Output filter: report a leftover only if it is at least this long.",
1745
2112
  "default": 0
2113
+ },
2114
+ "mitrePairing": {
2115
+ "type": "boolean",
2116
+ "default": false,
2117
+ "description": "Pair parts of one section whose facing ends (`parts[].ends`) can share ONE saw cut. Off by default: a request without it is solved exactly as before. On, a plan with pairs is returned only when it ranks strictly better than the unpaired plan by the packer's own ranking (parts placed first, then material — or price with `minimizeCost`); otherwise the unpaired plan comes back. Pairs only, no chains of three or more."
1746
2118
  }
1747
2119
  }
1748
2120
  },
@@ -1883,6 +2255,37 @@ export const TOOLS = [
1883
2255
  },
1884
2256
  "meta": {
1885
2257
  "type": "object"
2258
+ },
2259
+ "sharedCut": {
2260
+ "type": "object",
2261
+ "required": [
2262
+ "angle",
2263
+ "bevel",
2264
+ "kerfAlongAxis",
2265
+ "lead"
2266
+ ],
2267
+ "properties": {
2268
+ "angle": {
2269
+ "type": "number"
2270
+ },
2271
+ "bevel": {
2272
+ "type": "number"
2273
+ },
2274
+ "kerfAlongAxis": {
2275
+ "type": "number"
2276
+ },
2277
+ "lead": {
2278
+ "type": "number"
2279
+ }
2280
+ }
2281
+ },
2282
+ "flipped": {
2283
+ "type": "string",
2284
+ "enum": [
2285
+ "face",
2286
+ "edge",
2287
+ "roll"
2288
+ ]
1886
2289
  }
1887
2290
  }
1888
2291
  }
@@ -2048,6 +2451,9 @@ export const TOOLS = [
2048
2451
  },
2049
2452
  "toleranceAcceptedCount": {
2050
2453
  "type": "integer"
2454
+ },
2455
+ "sharedCuts": {
2456
+ "type": "integer"
2051
2457
  }
2052
2458
  }
2053
2459
  },
@@ -2094,6 +2500,12 @@ export const TOOLS = [
2094
2500
  "stage": {
2095
2501
  "type": "integer",
2096
2502
  "minimum": 1
2503
+ },
2504
+ "angle": {
2505
+ "type": "number"
2506
+ },
2507
+ "bevel": {
2508
+ "type": "number"
2097
2509
  }
2098
2510
  }
2099
2511
  }
@@ -3017,9 +3429,25 @@ export const TOOLS = [
3017
3429
  "meta": {
3018
3430
  "type": "object"
3019
3431
  },
3020
- "id": {}
3432
+ "id": {},
3433
+ "qr": {
3434
+ "type": "string",
3435
+ "minLength": 1,
3436
+ "maxLength": 300
3437
+ },
3438
+ "qrs": {
3439
+ "type": "array",
3440
+ "minItems": 1,
3441
+ "maxItems": 2000,
3442
+ "items": {
3443
+ "type": "string",
3444
+ "minLength": 1,
3445
+ "maxLength": 300
3446
+ }
3447
+ }
3021
3448
  }
3022
- }
3449
+ },
3450
+ "description": "The rectangular parts to cut, one row per part type (`w`, `h`, optional `qty`, `name`, grain, edge banding)."
3023
3451
  },
3024
3452
  "stock": {
3025
3453
  "type": "array",
@@ -3067,7 +3495,8 @@ export const TOOLS = [
3067
3495
  "type": "object"
3068
3496
  }
3069
3497
  }
3070
- }
3498
+ },
3499
+ "description": "The sheet sizes to cut from, one row per size (`w`, `h`, optional `qty`, `price`)."
3071
3500
  },
3072
3501
  "options": {
3073
3502
  "type": "object",
@@ -3146,8 +3575,128 @@ export const TOOLS = [
3146
3575
  },
3147
3576
  "setOutlines": {
3148
3577
  "default": true
3578
+ },
3579
+ "labels": {
3580
+ "type": "object",
3581
+ "additionalProperties": false,
3582
+ "properties": {
3583
+ "format": {
3584
+ "type": "string",
3585
+ "enum": [
3586
+ "sheet",
3587
+ "roll",
3588
+ "apli-03056",
3589
+ "brother-dk-11201"
3590
+ ],
3591
+ "default": "sheet"
3592
+ },
3593
+ "width": {
3594
+ "type": "number",
3595
+ "minimum": 20,
3596
+ "maximum": 300,
3597
+ "default": 100
3598
+ },
3599
+ "height": {
3600
+ "type": "number",
3601
+ "minimum": 15,
3602
+ "maximum": 300,
3603
+ "default": 40
3604
+ },
3605
+ "offset": {
3606
+ "type": "object",
3607
+ "additionalProperties": false,
3608
+ "properties": {
3609
+ "x": {
3610
+ "type": "number",
3611
+ "minimum": -20,
3612
+ "maximum": 20,
3613
+ "default": 0
3614
+ },
3615
+ "y": {
3616
+ "type": "number",
3617
+ "minimum": -20,
3618
+ "maximum": 20,
3619
+ "default": 0
3620
+ }
3621
+ }
3622
+ },
3623
+ "dimSize": {
3624
+ "type": "string",
3625
+ "enum": [
3626
+ "normal",
3627
+ "large",
3628
+ "xl"
3629
+ ],
3630
+ "default": "normal"
3631
+ },
3632
+ "partNo": {
3633
+ "type": "boolean",
3634
+ "default": false
3635
+ },
3636
+ "order": {
3637
+ "type": "string",
3638
+ "enum": [
3639
+ "input",
3640
+ "cut"
3641
+ ],
3642
+ "default": "input"
3643
+ },
3644
+ "stages": {
3645
+ "type": "array",
3646
+ "maxItems": 6,
3647
+ "items": {
3648
+ "type": "string",
3649
+ "minLength": 1,
3650
+ "maxLength": 6
3651
+ }
3652
+ },
3653
+ "stageWords": {
3654
+ "type": "object",
3655
+ "additionalProperties": false,
3656
+ "properties": {
3657
+ "rc": {
3658
+ "type": "string",
3659
+ "maxLength": 6
3660
+ },
3661
+ "c2s": {
3662
+ "type": "string",
3663
+ "maxLength": 6
3664
+ },
3665
+ "eb": {
3666
+ "type": "string",
3667
+ "maxLength": 6
3668
+ },
3669
+ "s": {
3670
+ "type": "string",
3671
+ "maxLength": 6
3672
+ },
3673
+ "d": {
3674
+ "type": "string",
3675
+ "maxLength": 6
3676
+ }
3677
+ }
3678
+ },
3679
+ "factoryEdges": {
3680
+ "type": "boolean",
3681
+ "default": false
3682
+ },
3683
+ "hiddenFaceHint": {
3684
+ "type": "boolean",
3685
+ "default": false
3686
+ },
3687
+ "parts": {
3688
+ "type": "array",
3689
+ "maxItems": 500,
3690
+ "items": {
3691
+ "type": "integer",
3692
+ "minimum": 1,
3693
+ "maximum": 999999
3694
+ }
3695
+ }
3696
+ }
3149
3697
  }
3150
- }
3698
+ },
3699
+ "description": "Kerf, trim, first-cut direction, cost and stock rules, effort and label output; every field is optional."
3151
3700
  },
3152
3701
  "engine": {
3153
3702
  "type": "string",
@@ -3156,7 +3705,8 @@ export const TOOLS = [
3156
3705
  "balanced",
3157
3706
  "max"
3158
3707
  ],
3159
- "default": "heuristic"
3708
+ "default": "heuristic",
3709
+ "description": "Which optimizer to run. `heuristic` is the guillotine multi-strategy packer: highest yield, every layout edge-to-edge saw-cuttable, always a full `cutPlan`. `balanced` is a MaxRects free-nesting packer: much faster on large jobs for a small yield cost, but its layouts are often not guillotine (`guillotineValid: false`, `cutPlan: null`), and it does not model `tolerance`, `minimizeCost`, `grainGroup`, `maxCutStages` or `minimizeRotations` — set one of those and a string in `warnings` tells you it was ignored. `max` is the ASYNCHRONOUS tree-search tier (2D only): it reaches the proven optimum on far more jobs at the cost of seconds-to-a-minute per solve, so instead of a plan it returns **202** with a `jobId` — poll `GET /v1/jobs/{id}` for the result. Still deterministic and guillotine-valid. Where the tier is not enabled on a deployment, `engine: \"max\"` fails closed with **503**. **What `max` models.** It packs ONE stock format at full sheet size with unlimited supply and a fixed 3-stage guillotine pattern. Anything that would make the returned plan physically WRONG is therefore REFUSED with **400** at submission — before a call is reserved and before you wait for the solve: a second `stock` row, `options.trim`, `options.respectStock`, any `material`, any `grainGroup`, a non-empty `matchedSets` (an empty one is fine). Options that only make the plan less tuned are accepted and listed in `warnings`: `tolerance`, `minimizeCost`, `maxCutStages` (fixed at 3), `minimizeRotations`, `firstCut`, `effort`. A `max` result reports no `offcuts`. Send those jobs to `heuristic`. The default never changes silently."
3160
3710
  },
3161
3711
  "include": {
3162
3712
  "type": "array",
@@ -3173,7 +3723,8 @@ export const TOOLS = [
3173
3723
  "pdf",
3174
3724
  "labelsPdf"
3175
3725
  ]
3176
- }
3726
+ },
3727
+ "description": "Three kinds of token. NARROWING (`cutPlan`, `offcuts`): omit the field entirely to get both; a present array keeps only the narrowing tokens it lists (an empty array drops both). ADDITIVE EXPORT (`svg`, `csv`, `dxf`): each adds an inline export STRING to the response (`svg` a standalone 2D layout — 2D only, a warning is returned for 1D/wood; `dxf` an R12/AC1009 drawing; `csv` a cut list, RFC 4180 quoted (a part name with a comma or a quote stays one field) and formula-injection-guarded (a name beginning with `=`, `+`, `-`, `@`, TAB or CR is prefixed with an apostrophe so a spreadsheet reads it as text, not a formula — CWE-1236). 2D DATA (`labels`, `planHash`, `pdf`, `labelsPdf`): `labels` adds per-piece label data (`labels[]`, see `Label2D`), `planHash` a 64-hex fingerprint of the plan (the same plan always gives the same hash), `pdf` the cutting-plan PDF and `labelsPdf` the label PDF, both base64-encoded and drawn by the same code as the CutOptim app. These four are 2D only: a 1D or wood request returns a `warnings` entry instead. `pdf` renders at most 500 parts (total quantity); a larger 2D request with `pdf` is a 413 `too_large` with `details.path: \"include\"`. Export and data tokens do not affect narrowing, so `[\"svg\"]` adds svg and — listing no narrowing token — drops cutPlan/offcuts; use `[\"cutPlan\",\"offcuts\",\"svg\"]` to keep everything."
3177
3728
  },
3178
3729
  "matchedSets": {
3179
3730
  "maxItems": 50,
@@ -3254,7 +3805,8 @@ export const TOOLS = [
3254
3805
  }
3255
3806
  }
3256
3807
  }
3257
- }
3808
+ },
3809
+ "description": "Matched sets (grain matching): groups of parts cut side by side from one piece of board. Send a NON-EMPTY ARRAY of sets (at most 50); members are named by `parts[].id`. Only a non-empty array switches the feature on: absent, `null`, `[]` — and any value that is not an array — is a request without sets, answered exactly as before (no error). With sets the response adds `sheets[].parts[].matchedSet` on every member and a `matchedSets` rollup; `csv` gains `Set` and `Position` columns at the end, `svg` the `matched-sets` frame group, `dxf` the position code on LABELS; `pdf` and `labelsPdf` draw the members as ordinary parts for now (a `warnings` line says so; the set marks are in `sheets[].parts[].matchedSet`, `csv` and `svg`). Engine `max` refuses a non-empty array (400 `details.unsupported`); an empty one is fine. 2D only: the 1D, wood and nest endpoints ignore this field and `parts[].id`."
3258
3810
  }
3259
3811
  }
3260
3812
  },
@@ -3380,9 +3932,71 @@ export const TOOLS = [
3380
3932
  },
3381
3933
  "meta": {
3382
3934
  "type": "object"
3935
+ },
3936
+ "ends": {
3937
+ "type": "object",
3938
+ "properties": {
3939
+ "start": {
3940
+ "type": "object",
3941
+ "required": [
3942
+ "angle"
3943
+ ],
3944
+ "properties": {
3945
+ "angle": {
3946
+ "type": "number",
3947
+ "exclusiveMinimum": -90,
3948
+ "exclusiveMaximum": 90
3949
+ },
3950
+ "bevel": {
3951
+ "type": "number",
3952
+ "exclusiveMinimum": -90,
3953
+ "exclusiveMaximum": 90,
3954
+ "default": 0
3955
+ }
3956
+ }
3957
+ },
3958
+ "end": {
3959
+ "type": "object",
3960
+ "required": [
3961
+ "angle"
3962
+ ],
3963
+ "properties": {
3964
+ "angle": {
3965
+ "type": "number",
3966
+ "exclusiveMinimum": -90,
3967
+ "exclusiveMaximum": 90
3968
+ },
3969
+ "bevel": {
3970
+ "type": "number",
3971
+ "exclusiveMinimum": -90,
3972
+ "exclusiveMaximum": 90,
3973
+ "default": 0
3974
+ }
3975
+ }
3976
+ }
3977
+ }
3978
+ },
3979
+ "flip": {
3980
+ "type": "string",
3981
+ "enum": [
3982
+ "none",
3983
+ "face",
3984
+ "edge",
3985
+ "any"
3986
+ ],
3987
+ "default": "none"
3988
+ },
3989
+ "depth": {
3990
+ "type": "number",
3991
+ "exclusiveMinimum": 0
3992
+ },
3993
+ "thickness": {
3994
+ "type": "number",
3995
+ "exclusiveMinimum": 0
3383
3996
  }
3384
3997
  }
3385
- }
3998
+ },
3999
+ "description": "The lengths to cut, one row per part type (`length`, optional `qty`, `name`, mitred `ends`)."
3386
4000
  },
3387
4001
  "stock": {
3388
4002
  "type": "array",
@@ -3418,7 +4032,8 @@ export const TOOLS = [
3418
4032
  "type": "object"
3419
4033
  }
3420
4034
  }
3421
- }
4035
+ },
4036
+ "description": "The bar lengths to cut from, one row per length (`length`, optional `qty`, `price`)."
3422
4037
  },
3423
4038
  "options": {
3424
4039
  "type": "object",
@@ -3458,8 +4073,13 @@ export const TOOLS = [
3458
4073
  "type": "number",
3459
4074
  "minimum": 0,
3460
4075
  "default": 0
4076
+ },
4077
+ "mitrePairing": {
4078
+ "type": "boolean",
4079
+ "default": false
3461
4080
  }
3462
- }
4081
+ },
4082
+ "description": "The 1D option set is deliberately smaller: there is no `firstCut`, `maxCutStages` or `minimizeRotations`, because none of them means anything on a bar."
3463
4083
  },
3464
4084
  "engine": {
3465
4085
  "type": "string",
@@ -3467,7 +4087,8 @@ export const TOOLS = [
3467
4087
  "heuristic",
3468
4088
  "balanced"
3469
4089
  ],
3470
- "default": "heuristic"
4090
+ "default": "heuristic",
4091
+ "description": "`max` is not offered here: it is a 2D (and, separately, nesting) tier, and `engine: \"max\"` on this path is a **400** before a call is charged. In 1D `balanced` resolves to the same packer as `heuristic` — the linear packer is already a best-of-multiple-strategies search, so there is no separate balanced mode. The value is accepted and echoed back so a single client can send one engine id for both endpoints."
3471
4092
  },
3472
4093
  "include": {
3473
4094
  "type": "array",
@@ -3484,7 +4105,8 @@ export const TOOLS = [
3484
4105
  "pdf",
3485
4106
  "labelsPdf"
3486
4107
  ]
3487
- }
4108
+ },
4109
+ "description": "Three kinds of token. NARROWING (`cutPlan`, `offcuts`): omit the field entirely to get both; a present array keeps only the narrowing tokens it lists (an empty array drops both). ADDITIVE EXPORT (`svg`, `csv`, `dxf`): each adds an inline export STRING to the response (`svg` a standalone 2D layout — 2D only, a warning is returned for 1D/wood; `dxf` an R12/AC1009 drawing; `csv` a cut list, RFC 4180 quoted (a part name with a comma or a quote stays one field) and formula-injection-guarded (a name beginning with `=`, `+`, `-`, `@`, TAB or CR is prefixed with an apostrophe so a spreadsheet reads it as text, not a formula — CWE-1236). 2D DATA (`labels`, `planHash`, `pdf`, `labelsPdf`): `labels` adds per-piece label data (`labels[]`, see `Label2D`), `planHash` a 64-hex fingerprint of the plan (the same plan always gives the same hash), `pdf` the cutting-plan PDF and `labelsPdf` the label PDF, both base64-encoded and drawn by the same code as the CutOptim app. These four are 2D only: a 1D or wood request returns a `warnings` entry instead. `pdf` renders at most 500 parts (total quantity); a larger 2D request with `pdf` is a 413 `too_large` with `details.path: \"include\"`. Export and data tokens do not affect narrowing, so `[\"svg\"]` adds svg and — listing no narrowing token — drops cutPlan/offcuts; use `[\"cutPlan\",\"offcuts\",\"svg\"]` to keep everything."
3488
4110
  }
3489
4111
  }
3490
4112
  },
@@ -3620,9 +4242,63 @@ export const TOOLS = [
3620
4242
  },
3621
4243
  "meta": {
3622
4244
  "type": "object"
4245
+ },
4246
+ "ends": {
4247
+ "type": "object",
4248
+ "properties": {
4249
+ "start": {
4250
+ "type": "object",
4251
+ "required": [
4252
+ "angle"
4253
+ ],
4254
+ "properties": {
4255
+ "angle": {
4256
+ "type": "number",
4257
+ "exclusiveMinimum": -90,
4258
+ "exclusiveMaximum": 90
4259
+ },
4260
+ "bevel": {
4261
+ "type": "number",
4262
+ "exclusiveMinimum": -90,
4263
+ "exclusiveMaximum": 90,
4264
+ "default": 0
4265
+ }
4266
+ }
4267
+ },
4268
+ "end": {
4269
+ "type": "object",
4270
+ "required": [
4271
+ "angle"
4272
+ ],
4273
+ "properties": {
4274
+ "angle": {
4275
+ "type": "number",
4276
+ "exclusiveMinimum": -90,
4277
+ "exclusiveMaximum": 90
4278
+ },
4279
+ "bevel": {
4280
+ "type": "number",
4281
+ "exclusiveMinimum": -90,
4282
+ "exclusiveMaximum": 90,
4283
+ "default": 0
4284
+ }
4285
+ }
4286
+ }
4287
+ }
4288
+ },
4289
+ "flip": {
4290
+ "type": "string",
4291
+ "enum": [
4292
+ "none",
4293
+ "face",
4294
+ "edge",
4295
+ "any"
4296
+ ],
4297
+ "default": "none"
3623
4298
  }
3624
4299
  }
3625
- }
4300
+ },
4301
+ "description": "The timber parts to cut, one row per part type: cross-section `sw` × `sh`, `length`, optional `qty`."
3626
4302
  },
3627
4303
  "stock": {
3628
4304
  "type": "array",
@@ -3672,7 +4348,8 @@ export const TOOLS = [
3672
4348
  "type": "object"
3673
4349
  }
3674
4350
  }
3675
- }
4351
+ },
4352
+ "description": "At most 50 rows. Since every cross-section needs at least one stock row, this is also the practical ceiling on cross-sections in a single job; a wider catalogue should be split into several requests."
3676
4353
  },
3677
4354
  "options": {
3678
4355
  "type": "object",
@@ -3712,8 +4389,13 @@ export const TOOLS = [
3712
4389
  "type": "number",
3713
4390
  "minimum": 0,
3714
4391
  "default": 0
4392
+ },
4393
+ "mitrePairing": {
4394
+ "type": "boolean",
4395
+ "default": false
3715
4396
  }
3716
- }
4397
+ },
4398
+ "description": "Identical to `Options1D` — a cross-section changes what fits, not how a bar is cut."
3717
4399
  },
3718
4400
  "engine": {
3719
4401
  "type": "string",
@@ -3721,7 +4403,8 @@ export const TOOLS = [
3721
4403
  "heuristic",
3722
4404
  "balanced"
3723
4405
  ],
3724
- "default": "heuristic"
4406
+ "default": "heuristic",
4407
+ "description": "As in 1D, `max` is not offered here (a **400** before a call is charged). `balanced` resolves to the same packer as `heuristic`: within a section a timber job is a length problem, and free nesting has nothing to offer it. The value is accepted, echoed back, and a warning states which packer actually ran."
3725
4408
  },
3726
4409
  "include": {
3727
4410
  "type": "array",
@@ -3738,7 +4421,8 @@ export const TOOLS = [
3738
4421
  "pdf",
3739
4422
  "labelsPdf"
3740
4423
  ]
3741
- }
4424
+ },
4425
+ "description": "Three kinds of token. NARROWING (`cutPlan`, `offcuts`): omit the field entirely to get both; a present array keeps only the narrowing tokens it lists (an empty array drops both). ADDITIVE EXPORT (`svg`, `csv`, `dxf`): each adds an inline export STRING to the response (`svg` a standalone 2D layout — 2D only, a warning is returned for 1D/wood; `dxf` an R12/AC1009 drawing; `csv` a cut list, RFC 4180 quoted (a part name with a comma or a quote stays one field) and formula-injection-guarded (a name beginning with `=`, `+`, `-`, `@`, TAB or CR is prefixed with an apostrophe so a spreadsheet reads it as text, not a formula — CWE-1236). 2D DATA (`labels`, `planHash`, `pdf`, `labelsPdf`): `labels` adds per-piece label data (`labels[]`, see `Label2D`), `planHash` a 64-hex fingerprint of the plan (the same plan always gives the same hash), `pdf` the cutting-plan PDF and `labelsPdf` the label PDF, both base64-encoded and drawn by the same code as the CutOptim app. These four are 2D only: a 1D or wood request returns a `warnings` entry instead. `pdf` renders at most 500 parts (total quantity); a larger 2D request with `pdf` is a 413 `too_large` with `details.path: \"include\"`. Export and data tokens do not affect narrowing, so `[\"svg\"]` adds svg and — listing no narrowing token — drops cutPlan/offcuts; use `[\"cutPlan\",\"offcuts\",\"svg\"]` to keep everything."
3742
4426
  }
3743
4427
  }
3744
4428
  },
@@ -3947,7 +4631,8 @@ export const TOOLS = [
3947
4631
  "type": "object"
3948
4632
  }
3949
4633
  }
3950
- }
4634
+ },
4635
+ "description": "One or more parts to nest, one NestPart per row."
3951
4636
  },
3952
4637
  "stock": {
3953
4638
  "type": "array",
@@ -4024,7 +4709,8 @@ export const TOOLS = [
4024
4709
  "type": "object"
4025
4710
  }
4026
4711
  }
4027
- }
4712
+ },
4713
+ "description": "One or more sheet types, one NestStock per row."
4028
4714
  },
4029
4715
  "options": {
4030
4716
  "type": "object",
@@ -4068,7 +4754,8 @@ export const TOOLS = [
4068
4754
  ],
4069
4755
  "default": "box"
4070
4756
  }
4071
- }
4757
+ },
4758
+ "description": "Solve options. All optional."
4072
4759
  },
4073
4760
  "engine": {
4074
4761
  "type": "string",
@@ -4077,7 +4764,8 @@ export const TOOLS = [
4077
4764
  "max",
4078
4765
  "sparrow"
4079
4766
  ],
4080
- "default": "lbf"
4767
+ "default": "lbf",
4768
+ "description": "`lbf` (default) is single-pass, synchronous and instant. `max` is ASYNCHRONOUS: 202 + poll `GET /v1/jobs/{id}`; it runs `lbf` first, then a time-budgeted search that fills sheets one at a time looking for FEWER sheets, and never returns more than `lbf`, nor a plan that ranks worse (price with `minimizeCost`, otherwise sheet area). It accepts several sheet sizes per material and `respectStock`; a `polygon` sheet or exclusion zones are served by `lbf` inside the job, with a warning. `sparrow` is a deprecated alias of `max`."
4081
4769
  },
4082
4770
  "include": {
4083
4771
  "type": "array",
@@ -4087,7 +4775,8 @@ export const TOOLS = [
4087
4775
  "svg",
4088
4776
  "dxf"
4089
4777
  ]
4090
- }
4778
+ },
4779
+ "description": "Additive inline export of the achieved layout. `svg` returns a self-contained, styled `<svg>` string (one titled band per sheet, parts drawn as filled polygons with holes, exclusion zones hatched); `dxf` returns an R12 document (closed `POLYLINE`/`VERTEX` outlines on STOCK/PARTS/HOLES/ZONES layers, part names on LABELS). Both are unit-agnostic, exactly like the request coordinates. The geometry in `sheets[]` is returned either way."
4091
4780
  }
4092
4781
  }
4093
4782
  },
@@ -4472,6 +5161,12 @@ export const TOOLS = [
4472
5161
  "stage": {
4473
5162
  "type": "integer",
4474
5163
  "minimum": 1
5164
+ },
5165
+ "angle": {
5166
+ "type": "number"
5167
+ },
5168
+ "bevel": {
5169
+ "type": "number"
4475
5170
  }
4476
5171
  }
4477
5172
  }
@@ -4841,6 +5536,28 @@ export const TOOLS = [
4841
5536
  },
4842
5537
  "meta": {
4843
5538
  "type": "object"
5539
+ },
5540
+ "piece": {
5541
+ "type": "integer",
5542
+ "minimum": 1
5543
+ },
5544
+ "of": {
5545
+ "type": "integer",
5546
+ "minimum": 1
5547
+ },
5548
+ "sheetNo": {
5549
+ "type": "integer",
5550
+ "minimum": 1
5551
+ },
5552
+ "stockRow": {
5553
+ "type": "integer",
5554
+ "minimum": 0
5555
+ },
5556
+ "sheetMeta": {
5557
+ "type": "object"
5558
+ },
5559
+ "qr": {
5560
+ "type": "string"
4844
5561
  }
4845
5562
  }
4846
5563
  }