Dokumentacja API

Optymalizator jako usługa HTTP: wyślij listę cięć i materiał, odbierz gotowy plan cięcia lub PDF do druku. Stabilny, wersjonowany i kompatybilny wstecz: pola są dodawane, nigdy usuwane.

Wprowadzenie

Bazowy URL: https://api.linearcutting.com. Wszystko jest w formacie JSON, oprócz punktu końcowego PDF. Zapytania są bezstanowe: nie przechowujemy twoich list cięć.

Nie potrzebujesz klucza API, żeby to wypróbować. Poniższa komenda zadziała od razu. Wklej ją do terminala.

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

To jest cała lista cięć: pięć elementów po 2400, trzy po 1800, do wycięcia ze sztang 6000mm. W odpowiedzi otrzymasz rozkłady, wymaganą liczbę sztang, ilość odpadu oraz informację, czy plan jest dowodliwie optymalny.

Nieuwierzytelnione wywołania API działają w planie darmowym. Pozwala to przetestować usługę przed zakupem. Obowiązuje limit na adres IP: 30 zapytań na godzinę i 250 miesięcznie. Po jego przekroczeniu API zwraca błąd 429 z informacją o terminie resetu. Nieudane wywołania nie wliczają się do limitu. Do zastosowań produkcyjnych kup klucz (od 5$ miesięcznie). Zwiększa on limity zapytań i rozmiarów oraz zapewnia osobną pulę zasobów, więc duży ruch w planie darmowym nie wpływa na Twoje operacje.

Oficjalni klienci

Zero zależności, licencja MIT i nie wymagają klucza. Jeśli pracujesz w Pythonie lub JavaScripcie, nie pisz ręcznie 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 na PyPI · JavaScript na npm · Kod źródłowy Pythona · Kod źródłowy JavaScript

Dwie solidne gwarancje. verified ma zawsze wartość true przy powodzeniu: każdy plan jest ponownie sprawdzany względem danych wejściowych przed odesłaniem, więc wadliwy plan nigdy do Ciebie nie dotrze. optimal to dowód, a nie przypuszczenie. Wartość jest true tylko wtedy, gdy algorytm ustalił, że nie istnieje lepszy plan, albo przez wyczerpanie możliwości, albo przez certyfikat. Gdy nie da się tego udowodnić w dostępnym czasie, zwracamy false, zamiast zgadywać.

optimal i stockBarsLowerBound to dwie różne wartości, które celowo mogą być niezgodne. Dolny próg to wartość przybliżona, w uproszczeniu „suma długości ÷ długość sztangi” i często jest nieosiągalny. Pięć sztuk 2400 i trzy 1800 to 17 400 mm, czyli 2,9 sztangi o dł. 6000, więc próg wyniesie 3. W rzeczywistości nie da się wyciąć tych elementów z 3 sztang; prawdziwe minimum to 4 i dowodzi tego algorytm. Zatem stockBarsUsed: 4 przy stockBarsLowerBound: 3 i optimal: true nie jest sprzecznością. Oznacza to, że próg był niedokładny, a nie, że plan był zły. Użyj optimal, by decydować, czy kontynuować obliczenia. Próg traktuj tylko jako kontrolę poprawności.

Zapis skrócony

Forma długa jest szczegółowa i trochę rozwlekła. W typowym przypadku użyj skróconej: liczba oznacza długość, a x oznacza liczbę sztuk:

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

Każdy z poniższych zapisów oznacza to samo, więc użyj tego, który najlepiej pasuje do Twojego kodu:

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

Trzeci element to etykieta dla części i cena dla materiału: [2400, 5, "szyna"], [6000, 20, 45.50]. stock to nowa, wygodniejsza nazwa dla dawnego podziału na stockLength / stocks. Sama liczba oznacza „ta długość, kup ile potrzebujesz”.

Zapis skrócony jest rozwijany przed innymi operacjami, więc jest to tylko inny sposób zapisu, a nie osobny tryb: krótkie zapytanie i jego rozwinięta, długa forma zwrócą identyczne plany cięcia. Można je dowolnie mieszać. Wszystkie dotychczasowe zapytania działają tak samo jak wcześniej.

Lista cięć w adresie URL

Małe zadania mieszczą się w zapytaniu, więc można użyć metody GET. To przydatne do udostępniania linków, wycen lub zgłaszania błędów:

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

Te same dane uwierzytelniające, te same limity i ten sam plan co dla POST. Można otworzyć w przeglądarce.

Uwierzytelnianie

Plan publiczny nie wymaga klucza. W planach płatnych klucz przesyłany jest w nagłówku; klucze zawierają też dozwolone domeny, więc zapytania z własnej strony działają bez serwera proxy.

http
X-API-Key: lk_live_9f2c...

Nieprawidłowy lub nieważny klucz zwraca błąd 401. Klucze są wydawane dla obszaru roboczego: zobacz plany.

POST /v1/optimise

Optymalizuje listę cięć. Zwraca plan, statystyki i gwarantowane granice.

Treść zapytania

FieldTypDomyślnieOpis
parts wymagane tablica- Elementy do wycięcia. Każdy: length (> 0), quantity (int ≥ 1), opcjonalnie label, material, leftAngle, rightAngle.
stockLength liczba- Pojedyncza długość materiału w nieograniczonej ilości. Użyj tego lub stocks, nie obu.
stocks tablica- Długości handlowe. Każda: length, quantity (null = bez limitu), price (null = optymalizuj pod kątem materiału, nie kosztu), opcjonalnie material, priority.
offcuts tablica[] Odcinki, które już masz: length, quantity, opcjonalnie material, priority. Materiał darmowy, nie wliczany do zakupów.
kerfliczba ≥ 00Szerokość rzazu.
trimlogicznyfalsePrzytnij oba końce każdej sztangi materiału przed cięciem.
methodciąg znakówbalanced balanced, least_waste, offcuts_first, fewest_setups (alias: fewest_setups). Podanie cen w stocks przełącza tryb balanced na optymalizację wg najniższego kosztu.
minRemnantliczba ≥ 00Odcinki o tej długości lub dłuższe wracają jako usableRemnants, a nie odpad.
angleCutsobiektwyłączone{enabled, width, axialSymmetry}. Zobacz cięcia pod kątem.
unitciąg znaków"mm"Zwracane w odpowiedzi i drukowane na PDF. Nic nie jest przeliczane.
unitFormatciąg znakówdecimalfraction renderuje wymiary na PDF jako ułamki z dokładnością do 1/64.
timeLimitsekundyautoLimit czasu na obliczenia, ograniczony przez plan. Wyszukiwanie jest typu 'anytime': dłuższy limit może tylko polepszyć wynik.

Odpowiedź

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" }
}
FieldOpis
plan.combinations[]Jeden wpis na każdy odrębny rozkład. usedComb.combination zawiera listę elementów ciętych z danej sztangi, length to jej długość, source to stock lub offcut, a count to liczba sztang do pocięcia w ten sposób.
usedComb.wastetotal = materiał − elementy; z podziałem na saw (rzaz + naddatek) i remnant (końcówka).
plan.purchases[]Lista zakupów: length, count, oraz price/cost, jeśli podano ceny.
plan.usableRemnantsKońcówki ≥ minRemnant, od największej: materiał na przyszłe zlecenia.
plan.unusedOffcutsOdpady użytkowe, których plan nie wykorzystał.
plan.stockBarsUsedCałkowita liczba sztang do zakupu.
plan.optimalDowód. Prawda tylko wtedy, gdy plan osiągnął dowodliwe minimum (lub gdy poszukiwanie zostało zakończone). Można bezpiecznie pokazać użytkownikom jako „najlepszy możliwy”.
plan.verifiedZawsze prawda w przypadku sukcesu: zapotrzebowanie zaspokojone, każdy rozkład fizycznie możliwy, dostępność odpadów użytkowych zachowana.
plan.stockBarsLowerBound, costLowerBoundDowodliwe minima. Gdy optimal jest fałszem, różnica pokazuje, jak bardzo plan może odbiegać od optimum.
statsPodsumowanie zlecenia: elementy, długości, yieldPercent, cutCount, layoutCount, totalCost, trueWaste, oraz byMaterial w przypadku użycia grup materiałowych.

POST /v1/optimise/pdf

To samo ciało żądania, plus opcjonalne jobName. Zwraca application/pdf: strona 1 to podsumowanie dla menedżera (zamówienie, koszty, certyfikat optymalności); kolejne strony to arkusze dla operatora piły z pozycjami cięć, etykietami elementów i polem wyboru dla każdej sztangi. Użytkownicy z kluczem otrzymują wydruk z własnym logo lub nazwą.

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

Grupy materiałowe

Oznacz parts, stocks i offcuts za pomocą material, a każdy materiał zostanie zoptymalizowany jako osobne zadanie w ramach jednego zapytania: cięcia są wykonywane tylko na materiale tego samego rodzaju. Plany rozkroju i zakupy wracają z tagami, a stats.byMaterial przedstawia wyniki dla każdej grupy.

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 dla sztangi lub odrzynka jest miękkim priorytetem: spośród tak samo dobrych planów, elementy z priorytetem zostaną użyte w pierwszej kolejności. Nigdy nie odbywa się to kosztem jakości planu.

Cięcia pod kątem

Nadaj częściom leftAngle i rightAngle i włącz angleCuts. Kąty to kąty dolne leżącego elementu; kąt górny wprowadź jako liczbę ujemną (120 i -60 to to samo cięcie). Prawidłowy zakres po przekształceniu: 10 do 170 stopni.

angleCuts fieldOpis
enabledGłówny przełącznik. Kąty na częściach są akceptowane tylko, gdy ma wartość true.
widthSzerokość materiału, w tej samej jednostce co długości. Cięcie pod kątem sięga na bok o width / tan(angle).
axialSymmetrynone, X, Y, Z lub XYZ: określa dozwolone obroty materiału, co decyduje o zagnieżdżaniu cięć ukośnych. Profil kwadratowy to typowo XYZ; materiał jednostronny to none.

Gdy dwa sąsiednie cięcia ukośne są równoległe, jedno cięcie piłą tworzy obie krawędzie. Plany rozkroju w trybie kątowym dodają uporządkowaną tablicę parts (z użytą orientacją) i cutPositions: każde cięcie mierzone od początku sztangi, jako para [góra, dół], gdy jest pod kątem, lub jako pojedyncza liczba, gdy jest proste.

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

Błędy

Treść błędu ma postać { "error": "...", "detail": "..." }. Pole detail jest przeznaczone dla człowieka: wskazuje część lub materiał powodujący błąd.

StatusBłądKiedy
400validationBłędne długości lub ilości, nieznana metoda, część niepasująca do żadnego materiału, zapotrzebowanie przekraczające dostępny materiał, naruszone zasady cięć kątowych, wysłano jednocześnie stocks i stockLength.
401badKeyBrakujący, nieznany lub wyłączony klucz API.
413partsLimitWięcej części niż pozwala na to Twój plan taryfowy.
429rateLimitedOsiągnięto limit zapytań lub limit miesięczny. Zastosuj się do nagłówka Retry-After.
503busySolver jest obciążony. Spróbuj ponownie za chwilę.

Limity zapytań

Limity ograniczają, nigdy nie generują dodatkowych opłat. Zapytania, które nie przejdą walidacji, nie są wliczane do limitu miesięcznego. Szczegóły na stronie planów taryfowych.

GuardDarmowyPłatne plany
Limit10 zapytań / 5 min na adres IP20-240 / min zależnie od planu
Całkowita liczba elementów300500 - 5,000
Liczba unikalnych pozycji60do 400
Budżet na obliczenia20 s60 - 300 s

Limit obejmuje zarówno pozycje na liście, jak i łączną liczbę elementów, ponieważ koszt obliczeń zależy od zróżnicowania długości: 300 identycznych cięć daje optymalny wynik w milisekundy, 300 różnych nie.

Przykłady

Najtańszy miks z kilku długości materiału (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

Elementy ramy cięte pod kątem (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"])

Obsługa dużych, czasochłonnych zadań

Duże listy mogą być przetwarzane od kilku sekund do kilku minut. Wyszukiwanie można przerwać w dowolnym momencie, więc ustaw timeLimit na wartość akceptowalną dla Twojego interfejsu i użyj optimal, aby poinformować użytkowników, czy wynik jest ostateczny, czy tylko najlepszy z dotychczas znalezionych.

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