Documentation API

L'optimiseur en tant que service HTTP : envoyez les pièces et les barres, recevez un plan de coupe vérifié ou un PDF prêt à imprimer. Stable, versionné et rétrocompatible : des champs sont ajoutés, jamais supprimés.

Introduction

URL de base https://api.linearcutting.com. Tout est en JSON, sauf le point d'accès PDF. Les requêtes sont sans état : nous ne stockons pas vos listes de débit.

Pas besoin de clé d'API pour essayer. La ligne ci-dessous s'exécute telle quelle, maintenant. Copiez-la dans un terminal.

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

Voici une liste de débits complète : cinq pièces de 2400, trois de 1800, à couper dans des barres de 6000mm. Vous recevez en retour les calepinages, le nombre de barres nécessaires, la perte, et si le plan est prouvé optimal.

Les appels sans clé utilisent l'accès public gratuit. Évaluez le service avant de payer : 30 requêtes par heure et 250 par mois par adresse IP. Au-delà, une erreur 429 indique la réinitialisation. Les appels échoués ne sont pas décomptés. Pour un vrai projet, prenez une clé dès 5 $/mois. Elle lève les limites, augmente les plafonds et vous alloue une capacité propre. L'activité sur l'accès public ne vous ralentit plus.

Clients officiels

Zéro dépendance, licence MIT, et ils ne nécessitent pas non plus de clé. Si vous êtes en Python ou JavaScript, n'écrivez pas un fetch à la main :

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 sur PyPI · JavaScript sur npm · Source Python · Source JavaScript

Deux garanties solides. verified est toujours vrai en cas de succès : chaque plan de coupe est revérifié par rapport à vos entrées avant de vous être envoyé. Un plan qui ne permet pas de couper votre liste ne vous parviendra donc jamais. optimal est une preuve, pas une estimation. Vrai uniquement quand la recherche a établi qu'aucun meilleur plan n'existe, soit en épuisant les possibilités, soit par un certificat. Si la preuve ne peut être établie dans le temps imparti, nous retournons false au lieu d'espérer.

optimal et stockBarsLowerBound sont deux choses différentes, et leur divergence est intentionnelle. La limite inférieure est une relaxation, soit "longueur totale ÷ longueur de barre", et elle est souvent inatteignable. Cinq pièces de 2400 et trois de 1800 font 17 400 mm, soit 2,9 barres de 6000. La limite est donc de 3. Mais il est impossible de couper ces pièces à partir de 3 barres. Le vrai minimum est 4, et le solveur le prouve. Donc stockBarsUsed: 4 avec stockBarsLowerBound: 3 et optimal: true n'est pas une contradiction. Cela signifie que la limite était lâche, pas que le plan était mauvais. Utilisez optimal pour décider de poursuivre la recherche. Utilisez la limite uniquement pour une vérification de bon sens.

Raccourci

La forme longue est explicite et un peu lourde. Pour le cas courant, utilisez la forme courte : un nombre est une longueur, et x indique la quantité :

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

Chacune de ces écritures signifie la même chose. Utilisez celle qui convient le mieux à votre code :

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

Un troisième élément est une étiquette sur une pièce, et un prix sur une matière : [2400, 5, "rail"], [6000, 20, 45.50]. Et stock est le nom convivial pour l'ancienne division stockLength / stocks. Un simple nombre signifie "cette longueur, achetez-en autant que nécessaire".

La notation abrégée est développée avant tout le reste. C'est donc une façon de saisir, pas un mode différent : une requête courte et la requête longue à laquelle elle correspond retournent des plans identiques au byte près. Combinez-les librement. Chaque requête existante fonctionne toujours exactement comme avant.

Une liste de débits dans une URL

Les petits calculs tiennent dans une chaîne de requête, donc GET fonctionne aussi. C'est pratique pour un lien à partager, à inclure dans un devis ou un rapport de bug :

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

Mêmes authentification, limites et plan qu'avec POST. Vous pouvez l'ouvrir dans un navigateur.

Authentification

Le niveau public ne nécessite pas de clé. Les niveaux payants envoient leur clé dans un en-tête. Les clés contiennent également vos origines de navigateur autorisées, afin que les appels depuis votre propre site fonctionnent sans proxy.

http
X-API-Key: lk_live_9f2c...

Une clé incorrecte ou expirée retourne 401. Les clés sont émises par espace de travail : voir les forfaits.

POST /v1/optimise

Résout une liste de débits. Retourne le plan, les statistiques et les bornes prouvables.

Corps de la requête

FieldTypeDéfautDescription
parts requis tableau- Les pièces dont vous avez besoin. Chaque : length (> 0), quantity (entier ≥ 1), optionnel label, material, leftAngle, rightAngle.
stockLength nombre- Une seule longueur de brut en quantité illimitée. Utilisez ce champ ou stocks, pas les deux.
stocks tableau- Barres disponibles à l'achat. Chaque : length, quantity (null = illimitée), price (null = optimiser la matière au lieu du coût), material et priority optionnels.
offcuts tableau[] Barres que vous possédez déjà : length, quantity, material et priority optionnels. Matière gratuite, jamais comptée comme un achat.
kerfnombre ≥ 00Largeur du trait de scie.
trimbooléenfalseEnlève un trait de scie aux deux extrémités de chaque barre avant débit.
methodstringbalanced balanced, least_waste, offcuts_first, fewest_setups (alias: fewest_setups). La présence de prix sur les stocks bascule le mode balanced en optimisation par coût.
minRemnantnombre ≥ 00Les chutes de cette longueur ou plus sont retournées comme usableRemnants au lieu d'être des déchets.
angleCutsobjetoff{enabled, width, axialSymmetry}. Voir coupes d'onglet.
unitstring"mm"Retourné en écho et imprimé sur les PDF. Ne convertit rien.
unitFormatstringdecimalfraction affiche les dimensions sur le PDF en fractions au 1/64e le plus proche.
timeLimitsecondesautoTemps de calcul, plafonné par l'abonnement. La recherche est itérative : plus de temps ne peut qu'améliorer le résultat.

Réponse

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" }
}
FieldDescription
plan.combinations[]Une entrée par calepinage distinct. usedComb.combination liste les pièces coupées dans cette barre, length est la source, source est stock ou offcut, et count est le nombre de barres à couper de cette façon.
usedComb.wastetotal = source − pièces ; divisé en saw (trait de scie + coupe de propreté) et remnant (la fin de barre restante).
plan.purchases[]La liste d'achats : length, count, plus price/cost si vous avez fourni des prix.
plan.usableRemnantsChutes ≥ minRemnant, les plus grandes d'abord : le stock de chutes pour demain.
plan.unusedOffcutsChutes que le plan n'a pas utilisées.
plan.stockBarsUsedTotal de barres à acheter.
plan.optimalPreuve. Vrai uniquement quand le plan atteint un minimum prouvable (ou si la recherche s'est terminée de manière exhaustive). Peut être affiché aux utilisateurs comme "meilleur résultat possible".
plan.verifiedToujours vrai en cas de succès : demande satisfaite, chaque calepinage est physiquement possible, stock de chutes respecté.
plan.stockBarsLowerBound, costLowerBoundMinimums prouvables. Si optimal est faux, l'écart vous indique la marge d'erreur maximale de ce plan.
statsTotaux du projet : pièces, longueurs, yieldPercent, cutCount, layoutCount, totalCost, trueWaste, et byMaterial si des groupes de matériaux sont utilisés.

POST /v1/optimise/pdf

Même corps de requête, avec un jobName optionnel. Retourne application/pdf : la page 1 est un résumé pour le chef de projet (bon de commande, coûts, certificat d'optimalité) ; les pages suivantes sont des fiches pour l'opérateur de scie avec les positions de coupe, les étiquettes de pièces et une case à cocher par barre. L'attribution ou la marque des appelants avec clé est affichée.

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

Groupes de matériaux

Marquez les pièces, barres et chutes avec une matière et chaque matière est résolue comme une tâche distincte au sein d'une même requête : les coupes ne sont faites que sur des barres de même matière. Les schémas de coupe et les achats sont retournés avec leurs marqueurs, et stats.byMaterial ventile les chiffres par groupe.

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 sur une barre ou une chute est une préférence non stricte : parmi les plans de qualité égale, les éléments priorisés sont utilisés en premier. La qualité du plan n'est jamais compromise.

Coupes d'angle / d'onglet

Donnez aux pièces un leftAngle et un rightAngle et activez angleCuts. Les angles sont les angles inférieurs de la pièce à plat ; entrez un angle supérieur comme un nombre négatif (120 et -60 sont la même coupe). Plage valide après cette transformation : 10 à 170 degrés.

angleCuts fieldDescription
enabledInterrupteur général. Les angles sur les pièces ne sont acceptés que si la valeur est true.
widthLargeur de la matière, même unité que les longueurs. Une coupe en angle s'étend latéralement de width / tan(angle).
axialSymmetrynone, X, Y, Z ou XYZ : définit les retournements autorisés pour la matière, ce qui détermine comment les onglets peuvent s'imbriquer. Le tube carré est typiquement XYZ ; une matière avec une seule face finie est none.

Lorsque deux onglets voisins sont parallèles, un seul trait de scie produit les deux arêtes. Les schémas de coupe en mode angle ajoutent un tableau ordonné parts (avec l'orientation utilisée) et cutPositions : chaque coupe mesurée depuis l'origine de la barre, sous forme de paire [haut, bas] si elle est en angle, ou d'un nombre unique si elle est droite.

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

Erreurs

Le corps des erreurs est { "error": "...", "detail": "..." }. Le detail est écrit pour être montré à un humain : il nomme la pièce ou la matière en cause.

StatuserreurQuand
400validationLongueurs ou quantités incorrectes, méthode inconnue, une pièce qui ne rentre dans aucune barre, demande excédant le stock fini, règles d'angle non respectées, stocks et stockLength envoyés ensemble.
401badKeyClé API manquante, inconnue ou désactivée.
413partsLimitPlus de pièces que votre forfait ne le permet.
429rateLimitedLimite de requêtes ou plafond mensuel atteint. Respectez Retry-After.
503busySolveur à capacité maximale. Réessayez sous peu.

Limites de requêtes

Les limites bloquent ; elles ne génèrent jamais de facture de dépassement. Les requêtes qui échouent à la validation ne comptent pas dans votre plafond mensuel. Les chiffres complets sont sur la page des forfaits.

GuardGratuitForfaits payants
Cadence10 requêtes / 5 min par IP20-240 / min par niveau
Pièces totales300500 - 5,000
Lignes de pièces distinctes60jusqu'à 400
Budget de calcul20 s60 - 300 s

Le nombre de lignes de pièces distinctes est limité, tout comme le nombre total de pièces, car le coût du calcul dépend de la diversité des longueurs : 300 coupes identiques trouvent une solution optimale en quelques millisecondes, 300 coupes différentes non.

Exemples

Mélange le moins cher à partir de plusieurs longueurs de brut (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

Pièces de cadre avec coupes d'onglet (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"])

Gérer un calcul long et complexe

Les listes importantes peuvent prendre de quelques secondes à plusieurs minutes. La recherche peut être interrompue à tout moment, donc réglez timeLimit sur le délai d'attente acceptable pour votre interface et affichez optimal pour indiquer aux utilisateurs si la solution est prouvée ou simplement la meilleure trouvée jusqu'à présent.

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