1D linear cutting
Lengths cut from bars where every bar can serve every part.
1D — linear
POST /v1/optimize/1d
{
"parts": [{ "name": "Rail", "length": 1200, "qty": 6 }],
"stock": [{ "length": 3000, "qty": 5, "price": 12.5 }],
"options": { "kerf": 3, "trim": { "start": 10, "end": 0 } }
}Parts take length (plus qty, name, priority); stock takes length, qty and price. Both parts and stock also accept an optional material tag (stock also priority) — material restricts a part to stock of the same material, and the response then adds materials and unmatchedMaterials as in 2D. Options are kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock and minOffcut. The response returns rods instead of sheets, each with its parts, remaining length and offcuts.
1D — response
{
"engine": "heuristic",
"engineVersion": "1.0.0+10e0c941",
"contractVersion": "1",
"deterministic": true,
"rods": [
{
"length": 3000,
"price": 12.5,
"remaining": 584,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [584]
},
{
"length": 3000,
"price": 12.5,
"remaining": 584,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [584]
},
{
"length": 3000,
"price": 12.5,
"remaining": 584,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [584]
}
],
"metrics": {
"rodCount": 3,
"yieldPct": 80,
"placed": 6,
"total": 6,
"cuts": 6,
"totalPrice": 37.5,
"toleranceAcceptedCount": 0
},
"unplaced": [],
"warnings": [],
"timing": { "solveMs": 2.19 },
"cutPlan": [
{ "sheet": 0, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 0, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
{ "sheet": 1, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 1, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
{ "sheet": 2, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 2, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 }
]
}rods replaces sheets and there is no guillotineValid, because a linear cut is always producible. Each part’s pos is the offset of its near end from the bar end that trim.start trims, so the first part starts exactly at trim.start and each following pos adds one kerf. remaining is the USABLE off-cut: the kerf of the cut that frees it from the last piece is already deducted, so it is the reclaimable length, not the raw gap.
It is reported on every rod even when it is below minOffcut — minOffcut only filters the offcuts array, which holds at most one entry. When several plans tie on parts placed, material, rods and price, the one whose leftover is longest (compared longest first) is returned, so the remnant tends to sit on one rod instead of being spread over many; this never changes the rod count or the price.
In the cut plan, sheet is the rod index, axis is always "v", stage is always 1 and length is always 0: a bar crosscut has no travel distance to report, which is also why the 1D metrics carry cuts but no cutLength.
rods[]
| Field | Type | Description |
|---|---|---|
| length | number | FULL bar length as supplied in stock. |
| price | number | null | Price of the stock row, or null. |
| remaining | number | USABLE off-cut left on this bar, rounded to 3 decimals: the kerf of the cut that frees it from the last piece is already deducted, so it is the reclaimable length, not the raw gap. Reported even when it is below options.minOffcut — minOffcut filters offcuts, not this. |
| parts | PlacedPart1D[] | In cutting order along the bar. |
| offcuts | number[] | At most one entry: [remaining] when it is greater than 0 and at least options.minOffcut, otherwise []. ABSENT when include excludes offcuts. |
| material | string | The material this rod was cut from. Absent when the job carries no material. |
| meta | object | 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. |
rods[].parts[]
| Field | Type | Description |
|---|---|---|
| name | string | The requested name, or the generated default. |
| pos | number | Offset of the part’s NEAR end from the bar end that trim.start trims. The first part therefore starts at trim.start, and each subsequent pos adds one kerf. 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. |
| length | number | The part length, as requested. |
| meta | object | 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. |
metrics (1D)
| Field | Type | Description |
|---|---|---|
| rodCount | integer | Bars used. Equals rods.length. |
| yieldPct | number | Placed length ÷ total FULL bar length × 100, rounded to 2 decimals. Trim and kerf count as waste. |
| placed | integer | Pieces placed, after qty expansion. |
| total | integer | Pieces requested, after qty expansion. |
| cuts | integer | Total crosscuts across all used bars — one per placed piece, since each piece is freed by a cut at its far end. There is no length to report for a bar cut, which is why 1D has no cutLength. |
| totalPrice | number | Sum of used bar prices, rounded to 2 decimals. |
| toleranceAcceptedCount | integer | How many pieces fitted only because options.tolerance allowed an overshoot. |
unplaced[] (1D)
| Field | Type | Description |
|---|---|---|
| name | string | The part name. |
| length | number | As requested. |
| qty | integer | How many could not be placed. |