API-documentatie

De optimizer als HTTP-service: stuur zaagstukken en stafmateriaal, ontvang een geverifieerd zaagplan of een printklare PDF. Stabiel, geversioneerd en achterwaarts compatibel: velden worden toegevoegd, nooit verwijderd.

Inleiding

Basis-URL https://api.linearcutting.com. Alles is JSON, behalve het PDF-eindpunt. Requests zijn stateless: we slaan uw zaaglijsten niet op.

U heeft geen API-sleutel nodig om dit te proberen. De onderstaande regel code werkt, zoals hij er staat. Plak hem in een terminal.

Copy, paste, run
curl -X POST https://api.linearcutting.com/v1/optimise \
  -H "Content-Type: application/json" \
  -d '{"parts": "2400x5, 1800x3", "stock": 6000}'

Dat is een volledige zaaglijst: vijf delen van 2400, drie van 1800, te zagen uit staven van 6000mm. Je krijgt de zaagplannen terug, hoeveel staven er nodig zijn, hoeveel er wordt verspild en of het plan bewijsbaar optimaal is.

Aanroepen zonder sleutel draaien op een gratis publiek niveau. Zo kunt u de software testen voordat u betaalt: 30 aanvragen per uur en 250 per kalendermaand, per IP-adres. Daarna geeft de API een 429-fout terug met de reset-tijd. Mislukte aanroepen tellen niet mee. Voor serieus werk, koop een sleutel vanaf $5 per maand: dit verhoogt beide limieten, verruimt de maximale invoergrootte en geeft u uw eigen capaciteit zodat een drukke middag op het publieke niveau u niet vertraagt.

Officiële clients

Geen afhankelijkheden, MIT-licentie, en ze hebben ook geen sleutel nodig. Als je Python of JavaScript gebruikt, schrijf dan niet zelf een fetch:

pip install linearcutting
import linearcutting as lc

plan = lc.optimise(parts="2400x5, 1800x3", stock=6000)
plan.bars_used   # 4
plan.optimal     # True, proved, not guessed
npm install linearcutting
import { optimise } from "linearcutting";

const plan = await optimise({ parts: "2400x5, 1800x3", stock: 6000 });
plan.barsUsed;   // 4
plan.optimal;    // true

Python op PyPI · JavaScript op npm · Python broncode · JavaScript broncode

Twee garanties waarop u kunt bouwen. verified is altijd waar bij succes: elk zaagplan wordt opnieuw gecontroleerd met uw invoer voordat het wordt teruggestuurd. Een plan dat uw zaaglijst niet kan realiseren, bereikt u dus nooit. optimal is een bewijs, geen gok. Het is alleen waar als de zoektocht heeft vastgesteld dat er geen beter plan bestaat, ofwel door alle mogelijkheden uit te putten ofwel via een certificaat. Als dit niet binnen de tijd kan worden bewezen, is de waarde false. Geen giswerk.

optimal en stockBarsLowerBound zijn verschillende dingen, en ze spreken elkaar met opzet tegen. De ondergrens is een relaxatie, ongeveer "totale lengte ÷ staaflengte", en is vaak niet haalbaar. Vijf stuks van 2400 en drie van 1800 is 17.400 mm. Dat is 2,9 staven van 6000, dus de ondergrens is 3. U kunt die delen niet uit 3 staven zagen; 4 is het werkelijke minimum, en de solver bewijst dat. Dus stockBarsUsed: 4 met stockBarsLowerBound: 3 en optimal: true is geen tegenstrijdigheid. Het betekent dat de ondergrens te ruim was, niet het plan. Gebruik optimal om te beslissen of u verder wilt zoeken. Gebruik de ondergrens alleen als realiteitscheck.

Verkorte notatie

De lange vorm is expliciet en wat omslachtig. Voor de meest gangbare gevallen is er een kortere notatie. Een getal is een lengte, en x betekent het aantal:

The short form
{"parts": "2400x5, 1800x3, 600", "stock": 6000}

Al deze betekenen hetzelfde, dus gebruik de notatie die het best bij uw code past:

All equivalent
{"parts": "2400x5"}
{"parts": ["2400x5"]}
{"parts": [[2400, 5]]}
{"parts": [{"length": 2400, "quantity": 5}]}

Een derde element is een label voor een deel, en een prijs voor een handelslengte: [2400, 5, "rail"], [6000, 20, 45.50]. En stock is de nieuwe naam voor de oude scheiding stockLength / stocks. Een enkel getal betekent "deze lengte, koop er zoveel van als nodig".

De korte notatie wordt eerst uitgebreid, voordat er iets anders gebeurt. Het is dus een manier van typen, geen andere modus. Een kort verzoek en het lange verzoek waarnaar het wordt uitgebreid, leveren byte-identieke plannen op. Gebruik beide notaties door elkaar; elk bestaand verzoek werkt nog precies zoals voorheen.

Een zaaglijst in een URL

Kleine klussen passen in een query string, dus GET werkt ook. Handig voor een link die u wilt delen, citeren of in een bugrapport plakken:

GET /v1/optimise
curl "https://api.linearcutting.com/v1/optimise?parts=2400x5,1800x3&stock=6000"

Zelfde authenticatie, zelfde limieten, zelfde plan als de POST. U kunt de link gewoon in een browser openen.

Authenticatie

Het gratis pakket heeft geen sleutel nodig. Betaalde pakketten sturen hun sleutel in een header; sleutels bevatten ook de toegestane browser-origins, zodat aanroepen vanaf uw eigen site zonder proxy werken.

http
X-API-Key: lk_live_9f2c...

Een foute of vervallen sleutel geeft 401 terug. Sleutels worden per werkruimte uitgegeven: zie pakketten.

POST /v1/optimise

Optimaliseer een zaaglijst. Geeft het zaagplan, statistieken en bewijsbare grenzen terug.

Aanvraag body

FieldTypeStandaardBeschrijving
parts verplicht array- De delen die u nodig heeft. Elk: length (> 0), quantity (geheel getal ≥ 1), optioneel label, material, leftAngle, rightAngle.
stockLength getal- Een enkele, onbeperkte handelslengte. Gebruik deze of stocks, niet beide.
stocks array- Beschikbare handelslengtes. Per stuk: length, quantity (null = onbeperkt), price (null = optimaliseer op materiaal in plaats van op kosten), optioneel material, priority.
offcuts array[] Reststukken in eigen voorraad: length, quantity, optioneel material, priority. Gratis materiaal, telt nooit als aankoop.
kerfgetal ≥ 00Zaagbreedte die per zaagsnede verloren gaat.
trimbooleanfalseSnij aan beide kanten van elk stuk stafmateriaal een zaagbreedte af voor het verzagen.
methodstringbalanced balanced, least_waste, offcuts_first, fewest_setups (alias: fewest_setups). Prijzen op stocks zetten balanced om naar laagste kosten.
minRemnantgetal ≥ 00Reststukken gelijk aan of langer dan deze lengte worden geretourneerd als usableRemnants in plaats van afval.
angleCutsobjectuit{enabled, width, axialSymmetry}. Zie verstekzagen.
unitstring"mm"Wordt teruggegeven en op PDF's afgedrukt. Rekent niets om.
unitFormatstringdecimalfraction geeft PDF-afmetingen weer als breuken, afgerond op 1/64.
timeLimitsecondenautoRekenbudget, beperkt door abonnement. De zoekopdracht is 'anytime': een groter budget verbetert alleen het resultaat.

Respons

200 application/json
{
  "plan": {
    "combinations": [
      {
        "usedComb": {
          "combination": [2400, 2400, 1200],
          "length": 6500,
          "source": "stock",
          "waste": { "total": 492, "remnant": 484, "saw": 8 }
        },
        "count": 2
      }
    ],
    "purchases":      [{ "length": 6500, "count": 2 }],
    "usableRemnants": [],
    "unusedOffcuts":  [],
    "stockBarsUsed":  2,
    "stockBarsLowerBound": 2,
    "totalWaste":     984,
    "optimal":        true,
    "verified":       true,
    "solveTimeMs":    12.5
  },
  "stats": {
    "totalParts": 8, "totalPartsLength": 15600, "totalUsedLength": 16600,
    "yieldPercent": 93.98, "cutCount": 8, "layoutCount": 1,
    "totalCost": null, "trueWaste": 984
  },
  "costLowerBound": null,
  "meta": { "method": "balanced", "kerf": 4, "tier": "public",
            "poweredBy": "linearcutting.com" }
}
FieldOmschrijving
plan.combinations[]Eén item per uniek zaagplan. usedComb.combination geeft de onderdelen weer die van die staf gezaagd zijn, length is de bronlengte, source is stock of offcut, en count is het aantal staven dat op die manier gezaagd wordt.
usedComb.wastetotal = bron − onderdelen; opgesplitst in saw (zaagsnede + afkortverlies) en remnant (het overblijvende einde).
plan.purchases[]De boodschappenlijst: length, count, plus price/cost als je prijzen hebt opgegeven.
plan.usableRemnantsReststukken ≥ minRemnant, de grootste eerst: de voorraad reststukken voor morgen.
plan.unusedOffcutsReststukken die het plan niet nodig had.
plan.stockBarsUsedTotaal aan te schaffen staven.
plan.optimalBewijs. Alleen 'true' als het plan een bewijsbare ondergrens heeft bereikt (of de zoekopdracht volledig is voltooid). Veilig om aan gebruikers te tonen als "best mogelijk".
plan.verifiedAltijd 'true' bij succes: vraag voldaan, elk zaagplan past fysiek, voorraad reststukken gerespecteerd.
plan.stockBarsLowerBound, costLowerBoundBewijsbare minima. Als optimal 'false' is, geeft het verschil aan hoeveel dit plan er maximaal naast kan zitten.
statsTotalen voor de opdracht: onderdelen, lengtes, yieldPercent, cutCount, layoutCount, totalCost, trueWaste, en byMaterial als materiaalgroepen worden gebruikt.

POST /v1/optimise/pdf

Dezelfde request body, plus een optionele jobName. Retourneert application/pdf: pagina 1 is een overzicht voor de projectmanager (inkooporder, kosten, het optimaliteitscertificaat); de volgende pagina's zijn werkbladen voor de zager met doorlopende zaagposities, onderdeelstickers en een afvinkvakje per staf. Bij aanroepen met een sleutel wordt de huisstijl van de klant weergegeven.

bash
curl -X POST https://api.linearcutting.com/v1/optimise/pdf \
  -H "Content-Type: application/json" \
  -H "X-API-Key: lk_live_9f2c..." \
  -d '{ "parts": [{"length": 2400, "quantity": 4}], "stockLength": 6500,
       "kerf": 4, "jobName": "Smith deck order" }' \
  --output cutting-plan.pdf

GET /v1/health

json
{ "status": "ok", "version": "1.3.0", "solver": "Linear Cutlist Optimizer v2" }

Materiaalgroepen

Tag parts, stocks en offcuts met materiaal en elk materiaal wordt als een aparte taak binnen één aanvraag opgelost: zaagsnedes worden enkel toegepast op materiaal van hetzelfde type. Layouts en inkopen komen getagd terug, en stats.byMaterial splitst de cijfers per groep uit.

json
{
  "parts": [
    { "length": 2400, "quantity": 4, "material": "40x40 SHS" },
    { "length": 1300, "quantity": 6, "material": "Pine 90x45" }
  ],
  "stocks": [
    { "length": 6500, "quantity": null, "material": "40x40 SHS" },
    { "length": 5400, "quantity": null, "material": "Pine 90x45", "priority": true }
  ],
  "kerf": 3
}

priority: true op een handelslengte of reststuk is een zachte voorkeur: bij gelijkwaardige plannen worden geprioriteerde items eerst gebruikt. Dit gaat nooit ten koste van de kwaliteit van het plan.

Hoek- / versteksnedes

Geef delen een leftAngle en rightAngle en schakel angleCuts in. Hoeken zijn de onderste hoeken van het stuk zoals het ligt; voer een bovenhoek in als een negatief getal (120 en -60 zijn dezelfde snede). Geldig bereik na die omzetting: 10 tot 170 graden.

angleCuts fieldOmschrijving
enabledHoofdschakelaar. Hoeken op delen worden alleen geaccepteerd indien true.
widthMateriaalbreedte, in dezelfde eenheid als de lengtes. Een hoeksnede neemt zijdelings width / tan(angle) in beslag.
axialSymmetrynone, X, Y, Z of XYZ: welke rotaties het materiaal toestaat, wat bepaalt hoe verstekken genest kunnen worden. Vierkante koker is doorgaans XYZ; enkelzijdig materiaal is none.

Waar twee aangrenzende verstekken parallel lopen, maakt één zaagsnede beide kanten. Layouts in hoekmodus voegen een geordende parts-array toe (met de gebruikte oriëntatie) en cutPositions: elke snede gemeten vanaf het nulpunt van de staaf, als een [boven, onder]-paar bij een hoeksnede, of een enkel getal bij een haakse snede.

angle-mode layout (excerpt)
"cutPositions": [[1200, 1300], [2442.265, 2500], 3300]

Fouten

Foutmeldingen hebben de structuur { "error": "...", "detail": "..." }. De detail is bedoeld voor een menselijke lezer: het benoemt het foute deel of materiaal.

StatusFoutWanneer
400validationFoute lengtes of aantallen, onbekende methode, een deel dat op geen enkele bron past, vraag overschrijdt eindige voorraad, hoekregels geschonden, stocks en stockLength beide verstuurd.
401badKeyOntbrekende, onbekende of uitgeschakelde API-sleutel.
413partsLimitMeer delen dan je abonnement toestaat.
429rateLimitedSnelheids- of maandlimiet bereikt. Respecteer Retry-After.
503busySolver op volle capaciteit. Probeer zo opnieuw.

Snelheidslimieten

Limieten beperken de toegang, ze genereren nooit een naheffing. Aanvragen die niet door de validatie komen, tellen niet mee voor je maandlimiet. Alle details staan op de abonnementenpagina.

GuardGratisBetaalde abonnementen
Limiet10 aanvragen / 5 min per IP20-240 / min per pakket
Totaal aantal delen300500 - 5,000
Aantal unieke delen60tot 400
Rekenbudget20 s60 - 300 s

Het aantal unieke regels met delen is begrensd, net als het totaal aantal delen. De rekenkosten hangen namelijk af van de diversiteit aan lengtes: 300 identieke delen zijn in milliseconden geoptimaliseerd, 300 verschillende niet.

Voorbeelden

Goedkoopste mix uit meerdere handelslengtes (JavaScript)

javascript
const res = await fetch("https://api.linearcutting.com/v1/optimise", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-API-Key": "lk_live_9f2c...",
  },
  body: JSON.stringify({
    parts: [
      { length: 3000, quantity: 8 },
      { length: 1200, quantity: 10 },
    ],
    stocks: [
      { length: 6500, quantity: null, price: 45 },   // unlimited
      { length: 12000, quantity: 4, price: 70 },     // only four on hand
    ],
    offcuts: [{ length: 2600, quantity: 1 }],
    kerf: 4,
    minRemnant: 400,
    method: "balanced",   // prices present -> optimises dollars
  }),
});

const { plan, stats } = await res.json();
console.log(plan.purchases);     // what to buy
console.log(stats.totalCost);    // what it costs
console.log(plan.optimal);       // true = provably the cheapest

Frame-onderdelen in verstek (Python)

python
import requests

r = requests.post(
    "https://api.linearcutting.com/v1/optimise",
    json={
        "parts": [
            {"length": 1300, "quantity": 2, "leftAngle": 90, "rightAngle": 45},
            {"length": 1200, "quantity": 2, "leftAngle": 135, "rightAngle": 60},
        ],
        "stockLength": 3310,
        "kerf": 0,
        "angleCuts": {"enabled": True, "width": 100, "axialSymmetry": "none"},
    },
    timeout=30,
)
r.raise_for_status()

for combo in r.json()["plan"]["combinations"]:
    print(combo["count"], "x", combo["usedComb"]["cutPositions"])

Omgaan met een trage, grote klus

Grote lijsten duren seconden tot minuten. Het zoekproces kan op elk moment stoppen, dus stel timeLimit in op wat uw UI kan afwachten en toon optimal om gebruikers te laten weten of het resultaat bewezen is, of gewoon het beste tot nu toe.

json
{ "parts": [ /* 900 parts */ ], "stockLength": 6500,
  "kerf": 4, "timeLimit": 60 }