Ugrás a tartalomhoz
← CutOptim Engine API

API-referencia

Alap-URL https://api.cutoptim.com · szerződésverzió v1

Készíts kulcsot az irányítópultodon, majd így küldd el: Authorization: Bearer <key>.

OpenAPI-specifikáció: /engine/openapi.json

Végpontok

POST/v1/optimize/2d2D lapoptimalizálás
POST/v1/optimize/1d1D / lineáris optimalizálás (rúd, profil, cső)
POST/v1/optimize/woodfaanyag-optimalizálás — 1D keresztmetszet-párosítással
POST/v1/optimize/nestvalódi alakú nesting — szabálytalan poligonok fix táblákon, kizárási zónákkal (lézer / plazma / vízsugár)
POST/v1/validate/2dkérés ellenőrzése megoldás nélkül — ingyenes, nincs kulcs, nincs kvóta (2d / 1d / wood / nest)
POST/v1/validate/1dkérés ellenőrzése megoldás nélkül — ingyenes, nincs kulcs, nincs kvóta (2d / 1d / wood / nest)
POST/v1/validate/woodkérés ellenőrzése megoldás nélkül — ingyenes, nincs kulcs, nincs kvóta (2d / 1d / wood / nest)
POST/v1/validate/nestkérés ellenőrzése megoldás nélkül — ingyenes, nincs kulcs, nincs kvóta (2d / 1d / wood / nest)
GET/v1/jobs/{id}egy aszinkron max motoros feladat lekérdezése — visszaadja a státuszát, és ha kész, a tervet (nincs kvóta; csak a saját feladataid)
POST/v1/import/nestdarab-kontúrok kiolvasása SVG- vagy DXF-fájlból — kulcs kell hozzá, kvótát nem fogyaszt
GET/v1/usagea FIÓK aktuális hónapra eső felhasználása és kvótája
GET/v1/healthéletjel — nincs kulcs, nincs kéréslimit, nincs adatbázis

POST /v1/validate/{2d,1d,wood,nest} — response

validbooleanAlways true on a 200 — a malformed body is a 400 with the exact bad field path instead. The body is the SAME one the matching optimize endpoint takes.
modestring"2d" | "1d" | "wood" | "nest" — the endpoint you called.
contractVersionstringShape version. Currently "1".
engineEnabledbooleanNEST ONLY — whether the nesting engine is built into this deployment. Absent for the rectangular modes.
partsobject{ rows, total } — part rows sent, and the total quantity after qty expansion. Check it against the part cap before you spend a call.
stockobject{ rows, total } — the same for stock.
warningsstring[]Approximate, SOLVE-FREE feasibility notes — e.g. a part that fits no stock (2D and nest: bounding box; wood: no matching section long enough). Empty ⇒ every part fits something. Ignores trim, kerf and material: a shape check, not a solve.

Mértékegységek

Az API mértékegység-független. Válassz egy egységet — milliméter, hüvelyk, bármi —, azt használd minden beküldött számhoz, és minden visszakapott szám ugyanabban az egységben lesz. A szerver semmit nem konvertál, és egyetlen mezőnév sem állít egységet.

Ez érvényes a darab- és alapanyag-méretekre, a kerf, tolerance, trim és minOffcut értékekre a bemeneten, valamint minden koordinátára, pozícióra, fennmaradó hosszra, maradékra és vágáshosszra a kimeneten. Ha egy kérésen belül egységeket keversz, olyan tervet kapsz, amely átmegy az ellenőrzésen, és fizikailag hibás — a szerver ezt nem tudja észlelni.

Koordináta-rendszer

Az origó a tábla bal felső sarka: az x a tábla szélessége mentén jobbra, az y a magassága mentén lefelé növekszik. Egy darab x és y értéke a bal felső sarka, a w és h pedig az elhelyezés szerinti mérete — ha a rotated true, akkor már felcserélve —, így az x, y, w, h téglalap minden további számolás nélkül a táblán elfoglalt helyet adja. A maradék-téglalapok ugyanezt a rendszert használják.

A széllevágás elmozdítja az elhelyezéseket: a trim.left minden darabot jobbra, a trim.top minden darabot lefelé tol, mert a darabok a hasznos területen belülre kerülnek, majd azt toljuk vissza a teljes táblára. A trim.right és a trim.bottom a hasznos területet szűkíti, az origót nem mozdítja el. Egy tábla w és h értéke mindig a teljes alapanyag-méret, a széllevágással együtt — és épp ezért számít a széllevágás hulladéknak a yieldPct-ben.

2D — kérés

POST /v1/optimize/2d
Authorization: Bearer co_live_…
Content-Type: application/json

{
  "parts": [{ "name": "Door", "w": 600, "h": 400, "qty": 4, "rotatable": true }],
  "stock": [{ "w": 2440, "h": 1220, "qty": 10, "price": 42 }],
  "options": {
    "kerf": 3,
    "trim": { "left": 10, "top": 10 },
    "minimizeCost": true,
    "effort": "balanced"
  },
  "engine": "heuristic",
  "include": ["cutPlan", "offcuts"]
}

A darab mezői

w, hkötelező — a darab méretei
qtyalapérték 1 — a szerver bontja ki; beleszámít a 2,000 darabos plafonba
nameopcionális megnevezés, minden elhelyezésnél visszaadjuk
rotatablealapérték true — elforgatható-e a darab 90°-kal
grainGroupegy csoport tagjai egy táblán maradnak (erezet-egyeztetés)
prioritykötelezően kivágandó: korlátozott készlet esetén elsőbbséget kap a táblán (respectStock mellett)
edgeBandingélzárás oldalanként: adj meg egy típus-hivatkozást a top / right / bottom / left bármelyikén (szabad szöveg, a saját kódod) — a válasz hivatkozásonként összegzi a folyómétert. Csak metaadat, sosem mozdít el egy darabot. Csak 2D
materialanyag-címke (szabad szöveg, a saját kódod): az azonos anyagú darabok és alapanyag csak egymással kerülnek pakolásra. Az edgeBanding-gel ellentétben megváltoztatja az elrendezést. Hiánya = egyetlen, meghatározatlan halmaz. Minden módban működik

Az alapanyag mezői

A w és a h kötelező. A qty alapértéke 1, és csak respectStock mellett kemény korlát. A price táblánkénti ár, ez határozza meg a totalPrice-t és a költség-módot. A priority (logikai) ezt az alapanyagot használja fel először; a material (szabad szöveg) az azonos anyagú darabokra korlátozza.

Beállítások

kerffűrészlap-vastagság (alapérték 0)
tolerancelegfeljebb ennyivel túllépő vágásokat is elfogad
trimoldalankénti széllevágás: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (alapérték 'auto')
minimizeCosta jelölteket a legkisebb teljes anyagár szerint rangsorolja; több, árazott alapanyagméret esetén kombinálja őket (2D), vagy a legolcsóbb hosszt választja (1D), hogy csökkentse a végösszeget, akkor is, ha ez több anyagot használ
respectStockminden alapanyag-sor qty értékét kemény korlátként kezeli
minOffcutcsak azokat a maradékokat jelenti, amelyek rövidebb oldala legalább ennyi
maxCutStagespanelfűrész fázis-korlát — fázisszám, nem nyers famélység
minimizeRotationsa kevesebb darabot elforgató elrendezéseket részesíti előnyben
effort'fast' | 'balanced' (alapérték 'balanced'). Keresési mélység: a 'balanced' lefuttatja a teljes, több stratégiás best-of keresést; a 'fast' kihagyja az egyetlen drága, táblánkénti területkombináció-keresést — nagy munkákon jelentősen gyorsabb néhány kihasználtsági pont áráért, továbbra is guillotine-vágható és sosem sűrűbb a 'balanced'-nél. Kis munkákon általában azonos. Csak a heuristic motornál

Az include két dolgot csinál. SZŰKÍTÉS — a "cutPlan" és az "offcuts" alapból be van kapcsolva; egy megadott tömb csak azokat a szűkítő tokeneket tartja meg, amelyeket felsorol (üres tömb mindkettőt elhagyja). ADDITÍV EXPORT — az "svg", a "csv" és a "dxf" mindegyike STRING-ként adja hozzá az adott exportot a válaszhoz: az svg egy önálló 2D elrendezésrajz (csak 2D — egy 1d/wood kérés helyette figyelmeztetést ad), a dxf egy R12/AC1009 rajz a STOCK/PARTS/LABELS rétegeken, a csv egy vágásjegyzék. Az export-tokenek nem hatnak a szűkítésre, tehát az include:["svg"] hozzáadja az svg-t, és — mivel egyetlen szűkítő tokent sem nevez meg — elhagyja a cutPlan/offcuts elemeket; a mindent megtartó, svg-vel bővített változathoz használd a ["cutPlan","offcuts","svg"]-t.

parts[].meta · stock[].meta — passthrough (2d · 1d · wood · nest)

parts[].metaobjectOpaque JSON of your own — an ERP article number, an order-line id, a customer ref. Echoed VERBATIM on every placed piece of that part. The optimizer never reads it, so it can never change a layout.
stock[].metaobjectThe same on a stock row: echoed on every sheet / rod / nested sheet cut from it, so the plan reconciles with your system without a lookup table.

Az effort beállítás a számítási időt mérlegeli a kihasználással szemben. Íme ez a kompromisszum, egyetlen igényes munkán mérve — minden szám a valódi pakolóból származik.

effort kapcsoló: anyagkihasználás vs számítási időeffort kapcsoló: anyagkihasználás vs számítási idő. fast: 350 tábla · 76.2% · ≈1.9 s. balanced: 330 tábla · 80.8% · ≈4.8 s. max: fenntartva — a sűrűbbhez lassabb keresés kell. A legtöbb (kisebb) munkán a kettő azonos; a különbség csak az ilyen nagy munkákon nyílik meg. A balanced az alapértelmezett, és sosem sűrűbb annál, mint amit a fast el tud érni.effort kapcsoló: anyagkihasználás vs számítási időEgy igényes munka — nagyjából 1550 darab egy 2,07 × 5,6 m-es táblán. Minden szám a valódi pakolón mérve.74%76%78%80%82%84%02 s4 s6 sszámítási idő · gyorsabban →anyagkihasználás · sűrűbben ↑⇄ az effort kapcsoló+4,6 pont kihasználás · −20 tábla−5,7% anyag · ≈2,5× lassabbfast350 tábla · 76.2% · ≈1.9 s★ balanced · alapértelmezett330 tábla · 80.8% · ≈4.8 smaxfenntartvaa sűrűbbhezlassabb kereséskell
A legtöbb (kisebb) munkán a kettő azonos; a különbség csak az ilyen nagy munkákon nyílik meg. A balanced az alapértelmezett, és sosem sűrűbb annál, mint amit a fast el tud érni.

És amúgy is gyors: még a legnagyobb gyártási munkák — 2000 darab és több — is másodpercek alatt megoldódnak az alapértelmezett motoron, kényelmesen az API időkeretén belül.

2D — válasz

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+10e0c941",
  "contractVersion": "1",
  "deterministic": true,
  "sheets": [
    {
      "w": 2440,
      "h": 1220,
      "price": 42,
      "parts": [
        { "name": "Door", "x": 10, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 613, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 1216, "y": 10, "w": 600, "h": 400, "rotated": false },
        { "name": "Door", "x": 1819, "y": 10, "w": 600, "h": 400, "rotated": false }
      ],
      "offcuts": [
        { "x": 2422, "y": 10, "w": 18, "h": 400 },
        { "x": 10, "y": 413, "w": 2430, "h": 807 }
      ]
    }
  ],
  "metrics": {
    "sheetCount": 1,
    "yieldPct": 32.25,
    "placed": 4,
    "total": 4,
    "cutLines": 5,
    "sawPasses": 5,
    "cutLength": 4030,
    "totalPrice": 42
  },
  "unplaced": [],
  "warnings": [],
  "guillotineValid": true,
  "timing": { "solveMs": 3.13 },
  "cutPlan": [
    { "sheet": 0, "step": 1, "axis": "h", "pos": 410, "length": 2430, "stage": 1 },
    { "sheet": 0, "step": 2, "axis": "v", "pos": 610, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 3, "axis": "v", "pos": 1213, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 4, "axis": "v", "pos": 1816, "length": 400, "stage": 2 },
    { "sheet": 0, "step": 5, "axis": "v", "pos": 2419, "length": 400, "stage": 2 }
  ]
}
  • cutLines vs sawPasses — A cutLines összevonja az egy vonalba eső vágásokat (egy ütköző-beállítás); a sawPasses minden áthaladást számol. Ugyanannak a tervnek két őszinte mértéke — nem azt állítjuk, hogy bármely versenytárs számával egyezik.
  • guillotineValid / cutPlan — Ha egy elrendezés nem vágható éltől élig, a guillotineValid false, a cutPlan pedig null. Ez valódi információ — panelfűrészen nem gyártható le —, nem hiba.
  • unplaced + warnings — A teljesíthetetlen munka 200-at ad, a ki nem fért darabokkal az unplaced listában és egy megjegyzéssel a warnings-ban. Egy terv, amivel dolgozni tudsz, többet ér egy státuszkódnál.
  • edgeBanding — Ha bármelyik darab edgeBanding-et hordoz, a válasz egy edgeBanding blokkal bővül: a folyóméterrel, amit minden típus-hivatkozás elfogyaszt, darabonként és rendelés-szintű összegként. Pontos geometria, hulladékráhagyás nélkül — azt a műhely adja hozzá —, és milliméteres bemenetet feltételez (÷1000 a méterhez). A kulcs élzárás nélküli munkánál teljesen hiányzik.
  • materials / unmatchedMaterials — Ha bármelyik darab vagy alapanyag material-t hordoz, a válasz egy materials blokkal (anyagonkénti összesítés — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) és egy unmatchedMaterials blokkal (az az igény, amelynek anyagához nincs megfelelő alapanyag) bővül. Fánál a material ehelyett keresztmetszet-szekciónként jelenik meg. Mindkét kulcs hiányzik egy anyag nélküli munkánál, amely bájtazonos marad.

Válaszmezők

A mezőnevek és a típusok a szerződés részei, ezért az alábbi táblázatok minden nyelven angolul maradnak — egy lefordított mezőnév olyan API-t dokumentálna, amely nem létezik.

POST /v1/optimize/2d — top level

enginestringWhich engine ran: "heuristic", "balanced" or "max".
engineVersionstringAlgorithm identity — package version + a content hash of the algorithm source. Moves automatically on any packer change, and independently per engine.
contractVersionstringShape version, matching the /v1/ in the path. Currently "1".
deterministicbooleanAlways true. Present so a client can assert the guarantee it relies on.
sheetsarrayOne entry per sheet used, in cutting order.
metricsobjectAggregate numbers for the whole job.
cutPlanarray | nullThe sawing plan. null when guillotineValid is false; absent when excluded via include.
unplacedarrayParts that did not fit, aggregated by name + size. Empty when everything fitted.
warningsstring[]Free-text notes about the plan. Do not parse — branch on unplaced, guillotineValid and metrics.
guillotineValidbooleanTrue when the layout is producible with edge-to-edge cuts, i.e. on a panel saw.
edgeBandingobjectLinear metres of edge banding, grouped by type reference. ABSENT unless a part requested banding via parts[].edgeBanding. 2D only.
materialsarrayPer-material rollup. ABSENT unless a part or stock row carried material — a material-free job stays byte-identical. (OPEN-256)
unmatchedMaterialsarrayDemand whose material has no matching stock at all. ABSENT when it does not happen. A missing-material report, not a did-not-fit one.
svgstringInline SVG of the 2D layout (self-contained, no external refs). Present ONLY when include contains "svg". 2D only. (OPEN-223)
csvstringInline CSV cut list. Present ONLY when include contains "csv".
dxfstringInline DXF (R12/AC1009) on layers STOCK/PARTS/LABELS. Present ONLY when include contains "dxf".
timing.solveMsnumberMilliseconds inside the packer, 2 decimals. Excludes parsing, auth and queueing.

sheets[]

w, hnumberFULL stock dimensions, trim included — not the usable area.
pricenumber | nullPrice of the stock row this sheet came from, or null if none was given.
partsarrayPlacements on this sheet.
offcutsarrayUsable leftover rectangles, filtered by options.minOffcut. Absent (not empty) when excluded via include.
metaobjectPresent only when the stock row carried meta — echoed verbatim from stock[].meta. (OPEN-224)

sheets[].parts[]

namestringThe requested name, or the generated default "Part <row>".
x, ynumberTop-left corner of the part, from the top-left corner of the sheet.
w, hnumberDimensions AS PLACED — already swapped when rotated is true. No client-side swap needed.
rotatedbooleanTrue if the part was turned 90° from the requested w×h. Informational only.
metaobjectPresent only when the part carried meta — echoed verbatim from parts[].meta on every placed piece. (OPEN-224)

sheets[].offcuts[]

x, y, w, hnumberA leftover rectangle, in the same coordinate system as the placements.

metrics (2D)

sheetCountintegerSheets used. Equals sheets.length.
yieldPctnumberPlaced part area ÷ total FULL sheet area × 100, 2 decimals. Trim and kerf count as waste.
placedintegerPieces placed, after qty expansion.
totalintegerPieces requested, after qty expansion.
cutLinesintegerCollinear cuts merged: same axis, same coordinate, same stage counted once — one fence setting.
sawPassesintegerEvery cut, one per strip crossed — how many separate passes the saw makes.
cutLengthnumberTotal distance sawn, 3 decimals. Same under either counting convention. In your unit.
totalPricenumberSum of the used sheets’ prices, 2 decimals. 0 when no stock row carried a price.

unplaced[] (2D)

namestringThe part name.
w, hnumberAs requested.
qtyintegerHow many of this part could not be placed.

edgeBanding (2D — present only when a part is banded)

totalMetersnumberOrder-wide total across every banded edge, 3 decimals. ⚠️ Assumes mm input: top/bottom edges run the part width, left/right the height, ×qty, ÷1000. In another unit it is your raw edge length ÷ 1000.
byType[]{ reference, meters }Order total split by the caller-supplied type reference, sorted by reference.
byPart[]{ name, meters, byType }One entry per requested part row that has any banded edge; its byType splits that part’s metres by reference.

cutPlan[]

sheetinteger0-based index into sheets (2D) or rods (1D). Named sheet in both.
stepinteger1-based order within THIS sheet — it restarts at 1 on every sheet.
axis"h" | "v""h": blade travels along x, separating top from bottom. "v": along y, separating left from right.
posnumberThe blade’s LOW-COORDINATE edge — the y value for "h", the x value for "v" — not its centre line. The kerf occupies pos to pos + kerf.
lengthnumberDistance the blade travels on this cut: the extent of the region crossed. Always 0 in 1D.
stageinteger1-based machine pass. Increments only when the axis flips relative to the parent cut.

2D · edgeBanding

Ha bármelyik darab edgeBanding-et hordoz, a válasz egy edgeBanding blokkal bővül: a folyóméterrel, amit minden típus-hivatkozás elfogyaszt, darabonként és rendelés-szintű összegként. Pontos geometria, hulladékráhagyás nélkül — azt a műhely adja hozzá —, és milliméteres bemenetet feltételez (÷1000 a méterhez). A kulcs élzárás nélküli munkánál teljesen hiányzik.

POST /v1/optimize/2d

{
  "parts": [
    {
      "name": "Door",
      "w": 600,
      "h": 400,
      "qty": 2,
      "edgeBanding": {
        "top": "ABS oak 22",
        "bottom": "ABS oak 22",
        "left": "ABS white 22",
        "right": "ABS white 22"
      }
    },
    { "name": "Shelf", "w": 800, "h": 300, "edgeBanding": { "top": "ABS oak 22" } }
  ],
  "stock": [{ "w": 2440, "h": 1220, "qty": 10, "price": 42 }],
  "options": { "kerf": 3 }
}
{
  "edgeBanding": {
    "totalMeters": 4.8,
    "byType": [
      { "reference": "ABS oak 22", "meters": 3.2 },
      { "reference": "ABS white 22", "meters": 1.6 }
    ],
    "byPart": [
      {
        "name": "Door",
        "meters": 4,
        "byType": [
          { "reference": "ABS oak 22", "meters": 2.4 },
          { "reference": "ABS white 22", "meters": 1.6 }
        ]
      },
      {
        "name": "Shelf",
        "meters": 0.8,
        "byType": [{ "reference": "ABS oak 22", "meters": 0.8 }]
      }
    ]
  }
}

material

Ha bármelyik darab vagy alapanyag material-t hordoz, a válasz egy materials blokkal (anyagonkénti összesítés — material, sheetCount/rodCount, yieldPct, placed, total, totalPrice) és egy unmatchedMaterials blokkal (az az igény, amelynek anyagához nincs megfelelő alapanyag) bővül. Fánál a material ehelyett keresztmetszet-szekciónként jelenik meg. Mindkét kulcs hiányzik egy anyag nélküli munkánál, amely bájtazonos marad.

POST /v1/optimize/2d

{
  "parts": [
    { "name": "Door", "w": 600, "h": 400, "qty": 4, "material": "MDF 18" },
    { "name": "Shelf", "w": 800, "h": 300, "qty": 6, "material": "Oak 18" },
    { "name": "Back panel", "w": 1000, "h": 500, "qty": 2, "material": "Ply 6" }
  ],
  "stock": [
    { "w": 2440, "h": 1220, "qty": 10, "price": 42, "material": "MDF 18" },
    { "w": 2440, "h": 1220, "qty": 10, "price": 68, "material": "Oak 18", "priority": true }
  ],
  "options": { "kerf": 3 },
  "engine": "heuristic"
}
{
  "materials": [
    {
      "material": "MDF 18",
      "sheetCount": 1,
      "yieldPct": 32.25,
      "placed": 4,
      "total": 4,
      "totalPrice": 42
    },
    {
      "material": "Oak 18",
      "sheetCount": 1,
      "yieldPct": 48.37,
      "placed": 6,
      "total": 6,
      "totalPrice": 68
    }
  ],
  "unmatchedMaterials": [
    {
      "material": "Ply 6",
      "parts": [{ "name": "Back panel", "w": 1000, "h": 500, "qty": 2 }]
    }
  ]
}

materials[] — per-material rollup (2d · 1d · nest)

materialstringThe tag exactly as you sent it. Free text, matched exactly.
sheetCount | rodCountintegerStock consumed for this material — sheetCount on 2D and nest, rodCount on 1D.
yieldPct | densitynumberThis material’s own fill — yieldPct on the rectangular modes, density on nest (a polygon fill, not comparable to yieldPct).
placed, totalintegerPieces placed and requested for this material, after qty expansion.
totalPricenumberSum of the prices of the stock used for this material.

unmatchedMaterials[] — demand with no matching stock

materialstringThe tag that has no stock of its own anywhere in the request.
partsarrayThe demand rows in that material, in the mode’s unplaced shape: name, w, h, qty on 2D; name, length, qty on 1D; name, qty on nest.

cutPlan — mit jelent fizikailag egy lépés

Egy step egy fűrészlap-mozgatás, és a lista abban a sorrendben áll, ahogy tényleg fűrészelni lehet: a szülővágás előbb szerepel, mint az általa létrehozott darabon belüli vágások, mert egy csíkot nem lehet keresztbe vágni, amíg le nem szabtad. Az axis "h" azt jelenti, hogy a fűrészlap az x mentén halad, és a felső részt választja el az alsótól; az axis "v" azt, hogy az y mentén halad, és a bal oldalt választja el a jobbtól. A pos a fűrészlap KISEBB KOORDINÁTÁJÚ éle — "h" esetén az y, "v" esetén az x érték —, nem a középvonala: a fűrészrés a pos és a pos + kerf között foglal helyet, tehát a fűrészlap abba az irányba eszik, amerre a koordináta növekszik — "h" esetén lefelé, "v" esetén jobbra. A vonal kisebb koordinátájú oldalán lévő anyag — "h" esetén a vonal fölött, "v" esetén tőle balra — az a darab, amelyet ez a vágás leválaszt. A length az, hogy a fűrészlap azon az egy vágáson milyen messzire jut: az általa átvágott terület kiterjedése, nem a teljes tábla szélessége.

A stage egy gépi áthaladás. 1-nél kezdődik, és csak akkor lép eggyel, ha az axis a szülővágáshoz képest átfordul, tehát egy tábla hat csíkra szabása egy fázis, a csíkok keresztbe vágása pedig a következő. Ez a „háromfázisú vágás” panelfűrészes értelme, nem a vágásfa mélysége, és ezt korlátozza a maxCutStages. A sheet a sheets tömb 0-alapú indexe, a step pedig minden táblán újraindul 1-től, nem fut végig az egész munkán.

A cutPlan értéke null — nem hiányzó, nem üres —, valahányszor a guillotineValid false: egy éltől élig nem vágható elrendezéshez nincs visszaadható vágási sorrend. A válaszból teljesen kimarad, ha nem szerepeltetted az include-ban.

1D — lineáris

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 } }
}

A darabok length értéket kapnak (plusz qty, name, priority); az alapanyag length, qty és price értéket. A darabok és az alapanyag egy opcionális material címkét is elfogad (az alapanyag priority-t is) — a material az azonos anyagú alapanyagra korlátozza a darabot, a válasz pedig a 2D-hez hasonlóan materials és unmatchedMaterials blokkal bővül. A beállítások: kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock és minOffcut. A válasz sheets helyett rods elemeket ad, mindegyikben a rá eső darabokkal, a fennmaradó hosszal és a maradékokkal.

1D — válasz

{
  "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 }
  ]
}

A rods váltja fel a sheets-et, és nincs guillotineValid, mert a lineáris vágás mindig legyártható. Minden darab pos értéke a közelebbi végének eltolása attól a rúdvégtől, amelyet a trim.start levág, tehát az első darab pontosan a trim.start értékénél kezdődik, és minden következő pos eggyel több kerf-fel nő. A remaining a HASZNÁLHATÓ maradék: a darabot az utolsó darabról leválasztó vágás kerf-je már le van vonva belőle, tehát az újrahasznosítható hossz, nem a nyers hézag. Minden rúdnál szerepel, akkor is, ha kisebb, mint a minOffcut — a minOffcut csak az offcuts tömböt szűri, amelyben legfeljebb egy elem van. A vágástervben a sheet a rúd indexe, az axis mindig "v", a stage mindig 1, a length pedig mindig 0: egy rúd keresztvágásának nincs jelenthető úthossza, és épp ezért ad az 1D metrics cuts értéket, cutLength-et viszont nem.

Fa — keresztmetszet

POST /v1/optimize/wood

{
  "parts": [
    { "name": "Rafter", "sw": 50, "sh": 100, "length": 2400, "qty": 2 },
    { "name": "Noggin", "sw": 50, "sh": 100, "length": 600, "qty": 4 },
    { "name": "Beam", "sw": 50, "sh": 150, "length": 3000, "qty": 2 }
  ],
  "stock": [
    { "name": "C24 50x100", "sw": 50, "sh": 100, "length": 4000, "qty": 5, "price": 12.5 },
    { "name": "C24 50x150", "sw": 50, "sh": 150, "length": 4000, "qty": 3, "price": 18 }
  ],
  "options": { "kerf": 3 }
}

A faanyagnak van olyan azonossága, ami egy puszta rúdnak nincs: egy 50×150-es darab nem jöhet ki 50×100-as alapanyagból, akármennyi hossz marad is rajta. A darabok és az alapanyag ezért viszi az sw és sh mezőt, a keresztmetszet két oldalát, tetszőleges sorrendben — az 50×100 és a 100×50 ugyanaz a gerenda megfordítva, és egy szekcióba kerül. A munka keresztmetszetenként szétválik, minden szekció a saját alapanyagához párosul és külön oldódik meg, egyetlen hívás pedig az egészet visszaadja. A darabok és az alapanyag egy opcionális material címkét is elfogad (az alapanyag priority-t is): ezzel egy tölgy 50×100 és egy fenyő 50×100 két külön szekcióvá válik, és minden szekció viszi a saját anyagát. A beállítások ugyanazok, mint 1D-ben.

Fa — válasz

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+7c1f3a62",
  "contractVersion": "1",
  "deterministic": true,
  "sections": [
    {
      "section": "50x100",
      "sw": 50,
      "sh": 100,
      "stockName": "C24 50x100",
      "rods": [
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 391,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [391]
        },
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 391,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [391]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 90,
        "placed": 6,
        "total": 6,
        "cuts": 6,
        "totalPrice": 25
      },
      "unplaced": []
    },
    {
      "section": "50x150",
      "sw": 50,
      "sh": 150,
      "stockName": "C24 50x150",
      "rods": [
        {
          "length": 4000,
          "price": 18,
          "remaining": 997,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [997]
        },
        {
          "length": 4000,
          "price": 18,
          "remaining": 997,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [997]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 75,
        "placed": 2,
        "total": 2,
        "cuts": 2,
        "totalPrice": 36
      },
      "unplaced": []
    }
  ],
  "unmatched": [],
  "metrics": {
    "sectionCount": 2,
    "rodCount": 4,
    "yieldPct": 82.5,
    "placed": 8,
    "total": 8,
    "cuts": 8,
    "totalPrice": 61,
    "toleranceAcceptedCount": 0
  },
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 2.96 },
  "cutPlan": [
    {
      "section": "50x100",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    }
  ]
}

A felső szinten a sections váltja fel a rods-ot: minden bejegyzés egy keresztmetszet, saját rods tömbbel (alakja azonos az 1D-ével) és saját metrics blokkal, így az anyagonkénti számok újraszámolás nélkül megvannak. Az unmatched-nek nincs 1D-s megfelelője — ez az az igény, amelynek a keresztmetszetéhez egyáltalán nem adtál meg alapanyagot; ez más probléma, mint az unplaced (volt alapanyag, de nem fért el), és más a megoldása is, ezért a kettő sosem keveredik. A metrics.total minden kért darabot számol, az unmatched-ben lévőket is. A vágástervben minden lépés megnevezi a section-jét is, a sheet pedig a rúd indexe AZON A SZEKCIÓN BELÜL, nem az egész munkára futó számláló.

POST /v1/optimize/wood — top level (what differs from 1D)

sectionsarrayReplaces rods at the top level: one entry per cross-section, each matched to its own stock and solved on its own.
unmatchedarrayDemand whose cross-section has NO stock at all. Deliberately separate from unplaced (had stock, did not fit) — the fix differs, so the two are never mixed.
unplacedarrayDid-not-fit demand, aggregated across the sections that DID have stock. Same shape as 1D.
metricsobjectJob-wide totals across every section (below).
cutPlanarray | nullAs 1D, except every step also carries section, and sheet is the rod index WITHIN that section, not a job-wide counter.
csv, dxfstringInline export, present only when include names the token. svg is 2D-only — a wood request asking for it gets a warning instead.

sections[]

sectionstringNormalised cross-section key, e.g. "50x100" — short side first, so 50×100 and 100×50 are one section.
sw, shnumberThe two cross-section sides: sw the SHORT one, sh the long one, whatever order they arrived in.
stockNamestring | nullThe name of the stock row this section was matched to, or null when that row carried none.
materialstringPresent only when the section carries a material — an oak 50×100 and a pine 50×100 are two sections. (OPEN-256)
rodsarrayIdentical in shape to the 1D rods[] above, meta and offcuts included.
metricsobjectThis section’s own totals: rodCount, yieldPct, placed, total, cuts, totalPrice — so the per-section figures need no recomputation.
unplacedarrayThis section’s parts that had stock and still did not fit.

metrics (wood)

sectionCountintegerCross-sections solved. Equals sections.length.
rodCountintegerBars used across every section.
yieldPctnumberPlaced length ÷ total FULL bar length × 100 over the whole job, 2 decimals.
placedintegerPieces placed, after qty expansion.
totalintegerPieces REQUESTED, after qty expansion — unmatched ones included.
cutsintegerCrosscuts across every used bar.
totalPricenumberSum of the used bars’ prices, 2 decimals.
toleranceAcceptedCountintegerPieces that fitted only because options.tolerance allowed an overshoot.

unmatched[]

sectionstringThe cross-section key nothing in stock matched.
sw, shnumberThat cross-section’s two sides.
materialstringPresent when the cross-section DOES exist in stock but only in a different material. (OPEN-256)
partsarrayThe demand rows in that section, in the 1D unplaced shape: name, length, qty.
qtyintegerTotal pieces in this section that had no stock at all.

Valódi alakú nesting

POST /v1/optimize/nest

{
  "parts": [
    {
      "name": "bracket",
      "polygon": [[0, 0], [300, 0], [300, 100], [100, 100], [100, 300], [0, 300]],
      "qty": 4,
      "allowedRotations": [0, 90, 180, 270]
    },
    {
      "name": "gusset",
      "polygon": [[0, 0], [280, 0], [0, 280]],
      "qty": 4,
      "allowedRotations": [0, 90, 180, 270]
    }
  ],
  "stock": [
    {
      "w": 2440,
      "h": 1220,
      "price": 12.5,
      "exclusions": [{ "polygon": [[0, 0], [300, 0], [0, 300]], "quality": 0 }]
    }
  ]
}

A fenti három mód téglalapokat pakol. A POST /v1/optimize/nest TETSZŐLEGES POLIGONOKAT pakol: egy darab egy körvonal (polygon, opcionális belső holes lyukakkal), nem egy width×height, így a darabok egymás konkáv zsebeibe illeszkednek, és a befoglaló téglalap által elpazarolt bemarás-üresség visszanyerhető — egy reprezentatív munkán 6 tábla ott, ahol ugyanezek a darabok befoglaló téglalappal 9-et igényelnek. Ez másfajta algoritmus (egy geometriai ütközésmotor, nem a guillotine pakoló), lézeres, plazmás és vízsugaras vágáshoz. Két dolog is jár vele, amit a téglalapos API nem tud kifejezni: táblánkénti kizárási zónák (stock[].exclusions — egy hiba, egy leszorító helye, egy előre nyomtatott terület; a quality-0 zóna tiltott terület minden darab számára) és valódi alakú lyukak. A material szerinti partíció és a meta átadás úgy működik, mint mindenhol máshol. Az alábbi példa egy valódi, rögzített hívás — nyolc darab egyetlen táblán, egy kizárt sérült sarokkal.

POST /v1/optimize/nest — request (top level)

partsarrayOne or more NestPart (see below). Required.
stockarrayOne or more NestStock sheet types (see below). Required.
optionsobjectSolve options (see below). Optional.
enginestring"lbf" (default, single-pass, instant) or "sparrow" (advertised for a future higher-density build; currently served by lbf with a warning).
includestring[]Additive inline export of the ACHIEVED nest: "svg" a self-contained styled drawing (one titled band per sheet, parts as filled polygons with holes, exclusion zones hatched), "dxf" an R12/AC1009 document on layers STOCK/PARTS/HOLES/ZONES/LABELS. Unit-agnostic, exactly like the request coordinates. The geometry in sheets[] is returned either way.

parts[] (NestPart)

polygonnumber[][]The part outline: an ordered ring of ≥3 [x, y] vertices. Given closed (first == last) or open; winding order is not required — it is oriented internally. Required UNLESS the row carries source.
sourceobjectOPEN-262 — read the outline out of an SVG or DXF FILE instead of listing coordinates (fields below). EXACTLY ONE of polygon / source: both, or neither, is a 400.
holesnumber[][][]Optional interior holes — a part with a cut-out. Each hole is a ring like polygon. NOT accepted alongside source: the file already carries its own holes, and two sources of truth for one geometry is not a thing we resolve silently.
qtyintegerCopies to place. Default 1.
allowedRotationsnumber[] | "continuous"Allowed rotations in DEGREES (e.g. [0,90,180,270]). Omit or "continuous" for free rotation.
minQualityintegerThe part may only be placed where sheet quality ≥ this. Default 1 → it avoids every quality-0 exclusion zone. Raise it to keep the part off inferior-but-not-forbidden zones too.
prioritybooleanMust-cut: wins sheet space when stock is capped (options.respectStock).
materialstringOPEN-256 — a part of material X nests only on material-X sheets; the job is partitioned by material.
namestringOptional label, echoed on every placement. Defaults to "Part <1-based row index>".
metaobjectOPEN-224 — opaque JSON (your ERP ids), echoed verbatim on every placed copy. Never affects the layout.

stock[] (NestStock)

w, hnumberRectangular sheet size. Give w & h OR polygon, not both.
polygonnumber[][]Arbitrary sheet outline (an off-cut remnant, a non-rectangular board); overrides w/h.
qtyintegerDefault 1. A HARD cap only when options.respectStock is true.
pricenumberPer-sheet price, for cost mode + totalPrice.
exclusionsobject[]OPEN-233 — per-sheet excluded / inferior zones. Each: { polygon: number[][], quality?: integer }. quality 0 (the default) = a hard no-go region for any part; a higher quality only excludes parts whose minQuality demands at least that. This is what the rectangular API cannot express.
materialstringOPEN-256 — this sheet serves only material-matching parts.
metaobjectOPEN-224 — echoed on every sheet cut from this stock row.

options (NestOptions)

minSeparationnumberMinimum clearance between parts and between a part and any hazard (sheet edge / exclusion zone). Use for kerf / beam / torch width. Default 0.
seedintegerDeterminism: a fixed seed → a reproducible layout. Omit and the server pins a fixed default so the response stays reproducible.
minimizeCostbooleanRank plans by total sheet price rather than sheet count.
respectStockbooleanTreat each stock qty as a hard cap.
simplifyTolerancenumberPolygon simplification tolerance (max area deviation as a fraction). Speeds up dense DXF outlines with hundreds of vertices. 0 disables.
timeBudgetMsintegerWall-clock budget for the metaheuristic (engine "sparrow"). Ignored by "lbf" (single-pass).

Nest — válasz

{
  "engine": "lbf",
  "engineVersion": "1.0.0+nest-d4046d5-07546022",
  "contractVersion": "1",
  "deterministic": true,
  "sheets": [
    {
      "w": 2440,
      "h": 1220,
      "price": 12.5,
      "density": 0.1217,
      "parts": [
        { "name": "bracket", "sheet": 0, "x": 300.003, "y": 400.226, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 300.133, "y": 700.617, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 300.061, "y": 1000.969, "rotation": -180 },
        { "name": "bracket", "sheet": 0, "x": 400.095, "y": 1102.332, "rotation": -180 },
        { "name": "gusset", "sheet": 0, "x": 530.102, "y": 50.205, "rotation": 90 },
        { "name": "gusset", "sheet": 0, "x": 300.058, "y": 380.171, "rotation": -90 },
        { "name": "gusset", "sheet": 0, "x": 580.15, "y": 380.193, "rotation": 90 },
        { "name": "gusset", "sheet": 0, "x": 300.204, "y": 660.982, "rotation": -90 }
      ],
      "exclusions": [{ "polygon": [[0, 0], [300, 0], [0, 300]], "quality": 0 }]
    }
  ],
  "metrics": { "sheetCount": 1, "density": 0.1217, "placed": 8, "total": 8, "totalPrice": 12.5 },
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 44.19 }
}

A sheets minden bejegyzése egy felhasznált tábla; egy elhelyezett darab a merev transzformációt viszi (rotation fokban, majd x/y eltolás), NEM egy újra kiadott poligont — forgasd el a bemeneti körvonaladat rotation-nel az origója körül, és add hozzá az (x, y)-t, hogy pontosan rekonstruáld az elhelyezést. A rotation lehet negatív; a rekonstrukció előjeltől függetlenül pontos. ⚠️ A density az elhelyezett POLIGON területe a felhasznált tábla területéhez viszonyítva — az őszinte kitöltöttség, a konkáv zsebeket üresként számolva —, és NEM összehasonlítható egy téglalapos pakoló yieldPct-jével (amely minden befoglaló téglalapot tömörnek számol, ezért rosszabb eredményre is magasabbat mutat); a kettő közt keresztül összevethető mérőszám az azonos darabokon vett sheetCount. Az elrendezés determinisztikus: állítsd be az options.seed értéket a reprodukálásához. Az exclusions minden táblán visszaköszön a rendereléshez.

POST /v1/optimize/nest — top level

enginestringWhich nesting engine ran: "lbf" or "sparrow".
engineVersionstringThe nesting engine's algorithm identity (jagua-rs revision + build hash). Versions independently of the rectangular engines.
contractVersionstringNest contract version, currently "1". Versions independently of the /v1/ rectangular contract — it is a different path and engine family.
deterministicbooleanAlways true — guaranteed by the pinned seed.
sheetsarrayOne entry per used sheet.
metricsobjectJob totals (see below).
unplacedarrayDemand that could not be placed — a plan-plus-warning, not an error.
warningsstring[]e.g. an engine substitution ("sparrow" served by "lbf"), unplaced parts, or a material with no matching stock.
materialsarrayOPEN-256 per-material rollup — present only when parts/stock carry material.
unmatchedMaterialsarrayParts whose material has no matching stock — present only when it happens.
importedobjectOPEN-262 — what the request’s source files contributed (fields above). ABSENT for a coordinates-only job, which is what keeps such a request byte-identical to before the feature existed.
svg, dxfstringThe achieved nest as an inline drawing — present ONLY when include contains that token. svg is self-contained (no external refs); dxf is R12/AC1009.
timingobject{ solveMs: number } — the solve time; environment-dependent.

sheets[] (nest)

w, hnumberPresent for rectangular sheets.
polygonnumber[][]Present for arbitrary-outline sheets instead of w/h.
pricenumber | nullThe stock row's price, or null.
densitynumberThis sheet's fill = placed polygon area / sheet area.
partsarrayPlacements on this sheet (see below).
exclusionsobject[]The zones that applied to this sheet, echoed for rendering.
materialstringPresent when the sheet carried a material (OPEN-256).
metaobjectEchoed from the stock row's meta (OPEN-224).

sheets[].parts[] (nest — placed)

namestringThe requested name, or the generated default.
sheetinteger0-based index into sheets.
x, ynumberTranslation, applied AFTER rotation about the part's origin.
rotationnumber⚠️ Degrees, applied about the part's own origin FIRST. A placed part carries this rigid transform, NOT a re-emitted polygon: rotate your input outline by rotation, then add (x, y) to reconstruct the placement exactly. May be negative; the reconstruction is exact regardless of sign.
materialstringThe material this copy nested from (OPEN-256).
metaobjectThe part's opaque passthrough (OPEN-224).

metrics (nest)

sheetCountintegerSheets used. Equals sheets.length. ⚠️ The honest, cross-comparable metric between nesting and the rectangular modes.
densitynumber⚠️ Placed POLYGON area / used sheet area — the honest fill (concave pockets count as empty). NOT comparable to a rectangular packer's yieldPct, which counts each bounding box as solid and so reads higher for a worse result.
placedintegerPart copies placed, after qty expansion.
totalintegerPart copies requested, after qty expansion.
totalPricenumberSum of used sheet prices.

unplaced[] (nest)

namestringThe part name.
qtyintegerHow many copies could not be placed.

rods[]

lengthnumberFULL bar length as supplied in stock.
pricenumber | nullPrice of the stock row, or null.
remainingnumberUSABLE off-cut left on this bar, 3 decimals — the kerf of the cut that frees it from the last piece is already deducted (OPEN-257), so it is the reclaimable length, not the raw gap. Reported even when below minOffcut.
partsarrayPlacements, in cutting order along the bar.
offcutsnumber[]At most one entry: [remaining] (the kerf-corrected usable off-cut, OPEN-257) when it is > 0 and ≥ minOffcut, else []. Absent when excluded via include.
metaobjectPresent only when the stock row carried meta — echoed from stock[].meta (OPEN-224). Applies to 1D and wood rods.

rods[].parts[]

namestringThe requested name, or the generated default.
posnumberOffset of the part’s NEAR end from the bar end that trim.start trims.
lengthnumberThe part length, as requested.
metaobjectPresent only when the part carried meta — echoed from parts[].meta (OPEN-224).

metrics (1D)

rodCountintegerBars used. Equals rods.length.
yieldPctnumberPlaced length ÷ total FULL bar length × 100, 2 decimals. Trim and kerf count as waste.
placedintegerPieces placed, after qty expansion.
totalintegerPieces requested, after qty expansion.
cutsintegerTotal crosscuts across all used bars — one per placed piece.
totalPricenumberSum of used bar prices, 2 decimals.
toleranceAcceptedCountintegerPieces that fitted only because options.tolerance allowed an overshoot.

unplaced[] (1D)

namestringThe part name.
lengthnumberAs requested.
qtyintegerHow many could not be placed.

Darabok fájlból (SVG · DXF)

Egy darabnak nem kell koordinátákként megérkeznie. Tegyél egy SVG- vagy DXF-dokumentumot a parts[].source mezőbe, és a szerver ugyanazzal a beolvasóval szedi ki belőle a kontúrt — és a lyukait —, amelyikkel a CutOptim alkalmazás dolgozik, amikor a Nesting módjára ejtesz rá egy rajzot. A fájl KIZÁRÓLAG a geometriát váltja ki: a qty, material, allowedRotations, minQuality, priority és meta pontosan úgy viselkedik, mint egy polygon-darabon, tehát egy CAD-fájlokban már meglévő darab-könyvtárhoz nem kell saját görbe- és ívlebontót írnod. Egy source EGY darabot ír le; a több különálló alkatrészt tartalmazó rajz 400-at ad, és az alábbi import-végpontra mutat. A válasz ekkor egy imported blokkot visz: hány sor jött fájlból, hány csúcsot adtak, és milyen mértékegységet deklaráltak azok a fájlok — jelentve, sosem alkalmazva, mert ez az API semmit nem vált át.

Semmi nem tárolódik. A bájtok kizárólag a kérés törzseként léteznek, a memóriában dolgozzuk fel őket, és a válasz kiírásakor megszűnnek: nincs lemez, nincs adatbázis, nincs ideiglenes fájl, nincs naplósor. Utólag nincs mit törölni, és semmi nem marad meg — ugyanaz az állapotmentesség, amit minden más végpont tart.

POST /v1/optimize/nest

{
  "parts": [
    {
      "source": {
        "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 200\">\n  <path d=\"M0 0 H120 V80 H0 Z\"/>\n  <path d=\"M40 30 H80 V50 H40 Z\"/>\n</svg>",
        "filename": "washer-plate.svg"
      },
      "qty": 4,
      "meta": { "erpId": "ART-8891" }
    }
  ],
  "stock": [{ "w": 2440, "h": 1220 }]
}

A mezőnevek és a típusok a szerződés részei, ezért az alábbi táblázatok minden nyelven angolul maradnak — egy lefordított mezőnév olyan API-t dokumentálna, amely nem létezik.

{
  "imported": { "parts": 1, "vertices": 8, "units": [] },
  "parts_after_import": [
    {
      "name": "washer-plate",
      "polygon": [[0, 0], [120, 0], [120, 80], [0, 80]],
      "holes": [[[40, 30], [80, 30], [80, 50], [40, 50]]],
      "qty": 4,
      "meta": { "erpId": "ART-8891" }
    }
  ]
}

parts[].source — geometry from an SVG / DXF file

contentstringThe file TEXT — not base64, not a URL, not a multipart upload. Both formats are text documents, so they travel inside the JSON body like any other field. At most 4,000,000 characters per file and 8,000,000 across one request; over either is a 413.
format"svg" | "dxf"Omit and the server sniffs the content (an <svg tag ⇒ svg, otherwise dxf). An explicit value ALWAYS wins — including over a filename whose extension disagrees, which is the case worth setting it for.
filenamestringUsed for format detection AND as the part name when the row has no name of its own (extension stripped). It never touches a filesystem — there is no file on our side to name.
flattenTolerancenumberCurve and arc flattening tolerance, in the FILE’s own coordinate unit. Default 0.2. Larger = fewer vertices; this is the lever when a dense outline trips the 2,000-vertex per-ring cap.

imported — what the files contributed (response, absent without a source)

partsintegerPart ROWS whose geometry came from a file.
verticesintegerTotal vertices those files produced after flattening, outlines and holes together — the number to watch against the per-ring cap.
unitsstring[]⚠️ Units the files DECLARED (a DXF $INSUNITS), not units we applied. Empty when none declared one. More than one entry also raises a warning: mixing a millimetre drawing with an inch one produces a plan that validates and is physically wrong, and the server must not "fix" that by converting — no field in this API asserts a unit.

POST /v1/import/nest — egy fájl, az összes kontúr

Ha egyetlen rajz több különböző darabot tartalmaz, előbb importáld: ez a végpont visszaadja az összes zárt kontúrt, a legnagyobbal kezdve, pontosan abban az alakban, amit egy parts[] sor kér. Illeszd be, amelyikre szükséged van, add hozzá a saját qty és material értékedet, és azt küldd a /v1/optimize/nest-re. Ez az útja annak is, hogy megnézd, mi van a fájlban, mielőtt futtatásra költenél. Kulcs kell hozzá — tetszőleges geometria lebontása valódi CPU-munka, a névtelen CPU pedig rossz üzlet —, de semmit nem foglal le: a kvótád érintetlen marad, és rate-limit fejléc sem jön vissza, pontosan úgy, mint egy job lekérdezésekor.

POST /v1/import/nest
Authorization: Bearer co_live_…

{
  "content": "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 400 200\">\n  <path d=\"M0 0 H120 V80 H0 Z\"/>\n  <path d=\"M40 30 H80 V50 H40 Z\"/>\n</svg>",
  "filename": "washer-plate.svg"
}
{
  "format": "svg",
  "parts": [
    {
      "name": "washer-plate",
      "polygon": [[0, 0], [120, 0], [120, 80], [0, 80]],
      "holes": [[[40, 30], [80, 30], [80, 50], [40, 50]]]
    }
  ],
  "vertices": 8,
  "warnings": [],
  "engineEnabled": true,
  "contractVersion": "1"
}

POST /v1/import/nest — request

contentstringThe file text. Required. Same 4,000,000-character cap as parts[].source.
format"svg" | "dxf"Omit to sniff; an explicit value wins over the filename extension.
filenamestringUsed for detection and to NAME the results: a file with one outline keeps the bare name, several are numbered "<name> 1", "<name> 2", …
flattenTolerancenumberAs on parts[].source. Default 0.2.

POST /v1/import/nest — response

format"svg" | "dxf"The parser that actually ran — the useful bit when you let the server sniff.
parts[]{ name, polygon, holes? }One entry per closed outline, LARGEST FIRST. Each is already in the shape a parts[] row wants: paste it in and add your own qty / material / allowedRotations. Deterministic — the same bytes always give the same vertices in the same order.
verticesintegerTotal vertices after flattening. Compare it against the 2,000-per-ring cap before you build a large request.
sourceUnit"mm" | "in"A DXF $INSUNITS declaration, when the file carries one. REPORTED, NEVER APPLIED. Absent for SVG (the format has no unit) and for a DXF that declares none.
warningsstring[]What the parser could not honour — an SVG transform= attribute (CAD part exports are flat, so we do not apply them), or geometry that never closed into a contour. Empty means the file was read whole.
engineEnabledbooleanWhether THIS deployment can also SOLVE a nest. Importing is pure parsing and works everywhere; where the nesting engine is not built in, /v1/optimize/nest answers 503 and this flag says so up front. Same field /v1/validate/nest carries.
contractVersionstringNest contract version. Currently "1".

Motorok

  • heuristic (alapértelmezett) — a guillotine, több stratégiát végigpróbáló pakoló. Legjobb kihasználás, minden elrendezés fűrésszel vágható, mindig teljes cutPlan.
  • balanced — MaxRects alapú, szabad beágyazású pakoló. Nagy munkákon sokkal gyorsabb (2000 darabon mérve ~25×) kissé rosszabb kihasználás árán, és az elrendezései gyakran nem guillotine-vághatók (guillotineValid: false, cutPlan: null). Nem modellezi a tolerance, minimizeCost, grainGroup, maxCutStages és minimizeRotations beállítást — ha megadod valamelyiket, figyelmeztetés jelzi, hogy nem vettük figyelembe.
  • max — az aszinkron, fakereséses szint (csak 2D): a bizonyított optimumot jóval több munkán éri el, cserébe egy-egy számítás másodpercektől egy percig tart. Továbbra is determinisztikus és guillotine-vágható. Közvetlenül nem ad tervet — lásd lentebb az Aszinkron feladatokat. EGYETLEN készlet-formátumot modellez, teljes lapméreten, korlátlan darabszámmal és fix 3 fázisú guillotine-mintával, ezért második készlet-sor, trim, respectStock, material vagy grainGroup esetén 400-zal utasítja el a kérést, még a hívás lefoglalása előtt; a tolerance, minimizeCost, maxCutStages, minimizeRotations, firstCut és effort átmegy, de figyelmen kívül marad egy warninggel, és a max eredmény nem jelent maradékot. Ezeket a munkákat küldd a heuristic motornak.

Aszinkron feladatok (engine = max)

Egy max számítás másodpercektől egy percig tart, ezért a POST /v1/optimize/2d az engine:"max" értékkel nem ad vissza tervet — 202 Accepted választ ad egy jobId-vel, és a hívás beküldéskor számlázódik. Kérdezd le a GET /v1/jobs/{id} végpontot, amíg a status "succeeded" nem lesz (a result ilyenkor ugyanazt a 2D választ tartalmazza, amit egy szinkron számítás ad) vagy "failed" (az error tartalmazza az üzenetet). A lekérdezés nem fogyaszt kvótát; csak a saját feladataidat látod. Ahol ez a szint nincs engedélyezve egy telepítésen, ott az engine:"max" fail-closed módon 503-mal elbukik.

fieldtypemeaning
jobIdstringThe 202 body's id. Poll GET /v1/jobs/{id}.
statusstringqueuedrunningsucceeded | failed.
mode · enginestringAlways "2d" and "max".
pollAfterMsinteger202 only — suggested delay before the first poll.
resultobjectPresent once succeeded — the same shape as a synchronous 2D response.
errorstringPresent once failed — the reason.
quotaobject202 only — reserved, used and limit: the one call metered at submission, and where the ACCOUNT stands this month.
createdAtstringPoll only — when the job was submitted (ISO-8601).
finishedAtstring | nullPoll only — when the worker finished; null while queued or running.

Determinizmus és verziózás

Minden válasz tartalmazza az engineVersion értéket. Az algoritmus determinisztikus, ezért a továbbfejlesztése megváltoztatja a kimenetet ugyanarra a bemenetre — ami törő változás, ha cache-elsz. A viselkedést úgy szögezheted le, hogy az engine paramétert kifejezetten megadod, és figyeled az engineVersion értékét; az útvonal-verzió (/v1/) csak akkor változik, ha a válasz szerkezete változik.

A motorok verziói egymástól függetlenek, így az egyik változása soha nem mozdítja el a másik verzióját.

Hibák

400invalid_requestSémahiba. A details.path a hibás mezőre mutat.
401unauthorizedHiányzó vagy ismeretlen API-kulcs.
402quota_exceededA havi kvóta elfogyott. A Retry-After megadja, hány másodperc van a hónap átfordulásáig.
403key_revokedA kulcs létezik, de nem használható: vagy vissza lett vonva, vagy a fiók Engine API-előfizetése már nem aktív. A message megmondja, melyikről van szó.
404not_foundNincs ilyen útvonal — ezt kapod akkor is, ha a helyes útvonalat rossz metódussal hívod.
413too_largeA bemenet átlép egy korlátot (lásd a Korlátokat).
429busyÉpp betelt a kapacitás. A Retry-After másodpercben — ez soha nem számít bele a kvótádba.
500internalVáratlan hiba, vagy nem érhető el az azonosítási backend (a kérések fail-closed módon elbuknak).
503service_unavailableA kért motor most nem szolgálható ki — a nesting motor, vagy az aszinkron max motor. A max esetében két oka lehet, és a message megmondja, melyik: a szint nincs beépítve ebbe a deploymentbe, vagy be van építve, de a jobokat megoldó worker nem válaszol. Fail-closed a hívás lefoglalása előtt, tehát semmibe nem kerül.
504solve_timeoutA számítás túllépte a kemény időkorlátot. A téglalap-módokon ezt a proxy kényszeríti ki; a /v1/optimize/nest esetében a motor a saját, rövidebb büdzséjét, és solve_timeout kóddal, a szokásos borítékban válaszol.

A hiba törzse

Minden hiba, amelyet maga a motor állít elő, ugyanezt a borítékot használja. Az error értékére ágazz, mert az stabil kód; a message-re soha, mert a szövegezése verziók között változhat. A details az invalid_request esetén van jelen, ahol a path nevezi meg a hibás mezőt, és a too_large esetén, ahol a max és a got adja meg a korlátot, illetve azt, amit beküldtél.

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8

{
  "error": "invalid_request",
  "message": "parts[0].h: must be greater than 0",
  "details": { "path": "parts[0].h" }
}
HTTP/1.1 402 Payment Required
Retry-After: 41400
Content-Type: application/json; charset=utf-8

{ "error": "quota_exceeded", "message": "monthly quota exhausted" }
  • A 402 és a 429 is Retry-After fejlécet visz, másodpercben. A 402-nél a kvóta visszaállásáig számol vissza, ami a következő hónap 1-jén 00:00 UTC; a 429-nél rövid várakozás, és a 429 soha nem fogyaszt kvótát — a lefoglalt hívást visszaadjuk.
  • Az útvonal-hiba not_found kóddal válaszol; ez a kód szándékosan nincs a fenti listában, mert a router állítja elő, nem az API-szerződés. 404-et kapsz és nem 405-öt, ha az útvonal helyes, de a metódus nem: mind a négy optimalizáló végpont csak POST-ot fogad.
  • A téglalap-módokon az 504 a reverse proxytól jön, nem a motortól, ezért a törzse a proxyé, nem ez a JSON-boríték; az alábbi bemeneti korlátokon belül elérhetetlen kell lennie. A /v1/optimize/nest a kivétel: az a számítás alfolyamat, saját büdzsével, amit szándékosan a proxy korlátja alatt tartunk — ott a timeout ezt a borítékot használja, solve_timeout kóddal.

Kéréslimit-fejlécek

A sikeres optimalizáló hívás X-RateLimit-Limit (a FIÓK havi plafonja — a fiók összes kulcsa egyetlen kereten osztozik) és X-RateLimit-Remaining (a fiókból a hónapra hátralévő hívások száma, ezt a hívást már leszámítva) fejlécet visz. Ezeket kizárólag az optimalizáló végpontok küldik: a számláló a számítás engedélyezésének részeként foglalódik le, így a /v1/usage és a /v1/health nem tud mit jelenteni.

X-RateLimit-LimitintegerThe ACCOUNT’s monthly quota — the same number GET /v1/usage returns as limit, not a per-key cap (049).
X-RateLimit-RemainingintegerCalls left this month on the ACCOUNT, after this one.
Retry-AfterintegerSeconds to wait. Sent with 402 and 429 only.

GET /v1/usage

{
  "plan": "studio",
  "used": 137,
  "limit": 10000,
  "remaining": 9863,
  "periodEnd": "2026-08-01",
  "keyPrefix": "co_live_ab12",
  "contractVersion": "1"
}

Csak olvasás: nem fogyaszt hívást és nem küld rate-limit fejlécet. ⚠️ A used és a limit a FIÓKRA vonatkozik, nem arra a kulcsra, amivel hívtál: a fiók minden aktív kulcsa ugyanabból a keretből fogyaszt, tehát több kulcs nem jelent több kvótát. A used az aktuális UTC naptári hónapot számolja az összes kulcson, a remaining a limit mínusz used és sosem megy nulla alá, a periodEnd a nullázás napja sima YYYY-MM-DD dátumként, a keyPrefix pedig annak a kulcsnak a nem titkos előtagja, amivel hívtál. Magát a kulcsot egyetlen végpont sem adja vissza — csak a hash-e tárolódik, tehát az elveszett kulcsot cserélni kell, nem visszaszerezni.

planstringTier slug frozen onto the key when it was created.
usedintegerCalls counted in the current UTC calendar month across EVERY key on the ACCOUNT — revoked keys included, so revoking a key cannot un-spend what it spent (053).
limitintegerThe ACCOUNT’s cap: the largest monthly quota frozen onto any of its ACTIVE keys (049). Not a per-key allowance — more keys do not add quota.
remainingintegerlimit − used, never negative.
periodEndstringReset day as YYYY-MM-DD — a date, not a timestamp.
keyPrefixstringNon-secret display prefix of the calling key.
contractVersionstringShape version. Currently "1".

GET /v1/health

{
  "status": "healthy",
  "service": "cutoptim-engine",
  "contractVersion": "1",
  "engineVersion": "1.0.0+10e0c941",
  "engines": ["heuristic", "balanced"],
  "modes": ["2d", "1d", "wood", "nest"],
  "nestEngines": ["lbf"],
  "maxEngines": ["max"],
  "uptimeSec": 16
}

Nincs kulcs, nincs kvóta, nincs adatbázis. Szándékosan nem nyúl semmilyen állapothoz, így a kulcstároló kiesése nem tudja halottnak mutatni a szolgáltatást egy orchestrator felé. Az engines felsorolja azokat az id-ket, amelyeket ez a telepítés elfogad az engine paraméterben, az engineVersion pedig az alapértelmezett motor verziója.

statusstringAlways "healthy" when the process answers.
servicestringAlways "cutoptim-engine".
contractVersionstringShape version. Currently "1".
engineVersionstringThe DEFAULT engine’s version, not a per-engine list.
enginesstring[]SYNCHRONOUS engine ids this deployment accepts in engine — ["heuristic","balanced"]. The async max tier is reported separately in maxEngines, never here.
modesstring[]Optimize paths this deployment serves: "2d", "1d", "wood", plus "nest" only where the nesting engine is built in. Endpoint discovery without reading this page.
nestEnginesstring[]Nesting engine ids this deployment can serve — ["lbf"] on the production API, [] where the Rust nesting stage is not built in.
maxEnginesstring[]The async tree-search tier — ["max"] where it is enabled, [] otherwise. Health never advertises a capability it cannot serve.
uptimeSecintegerWhole seconds since process start.

Korlátok

  • 2,000 darab kérésenként (összes mennyiség, a qty kibontása után)
  • 50 alapanyag-sor · a kéréstörzs legfeljebb 1 MB
  • 10 aktív kulcs fiókonként — EGY havi kvótán osztoznak: a kulcsok környezeteket és integrációkat választanak szét, nem növelik a keretet
  • 10 MB kérés-törzs azon a két nest-úton, amelyik rajzot hordozhat (/v1/optimize/nest és /v1/import/nest); egy source fájl legfeljebb 4 000 000 karakter, kérésenként összesen 8 000 000
  • a párhuzamosság szerver-oldalon korlátozott — a burst 429-et kap, nem kerül lassú várólistára. A kulcs nélküli validate végpontoknak ezen felül per-cím plafonjuk van (429 + Retry-After); kulccsal ez sosem throttle-öz. Egy fiók egyszerre 5 max jobot tarthat queued/running állapotban.

OpenAPI-specifikáció

Egy gépi olvasásra szánt OpenAPI 3.1 dokumentum leírja mind a tizenkét végpontot, minden kéréstörzset, minden válaszszerkezetet és minden hibát. Erre állítsd rá a kliens-generátorodat, ahelyett hogy ezt a lapot írnád át kézzel. Maga a dokumentum csak angol nyelvű: szerződés-tokenekből áll, és az OpenAPI-nak nincs lokalizációs mechanizmusa.

curl -s https://cutoptim.com/engine/openapi.json > cutoptim-engine.json

OpenAPI 3.1 dokumentum megnyitása →

Letölthető anyag
Engine API one-pager

A two-page summary of the Engine API — the four modes (2D, 1D, wood and true-shape nest), a request and response, determinism and pricing. Print-ready, with a QR back to the docs.

PDF2 pagesFree
PDF letöltése