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.
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 :
import linearcutting as lc
plan = lc.optimise(parts="2400x5, 1800x3", stock=6000)
plan.bars_used # 4
plan.optimal # True, proved, not guessed 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é :
{"parts": "2400x5, 1800x3, 600", "stock": 6000} Chacune de ces écritures signifie la même chose. Utilisez celle qui convient le mieux à votre code :
{"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 :
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.
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
| Field | Type | Défaut | Description |
|---|---|---|---|
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. |
kerf | nombre ≥ 0 | 0 | Largeur du trait de scie. |
trim | booléen | false | Enlève un trait de scie aux deux extrémités de chaque barre avant débit. |
method | string | balanced | 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. |
minRemnant | nombre ≥ 0 | 0 | Les chutes de cette longueur ou plus sont retournées comme usableRemnants au lieu d'être des déchets. |
angleCuts | objet | off | {enabled, width, axialSymmetry}. Voir coupes d'onglet. |
unit | string | "mm" | Retourné en écho et imprimé sur les PDF. Ne convertit rien. |
unitFormat | string | decimal | fraction affiche les dimensions sur le PDF en fractions au 1/64e le plus proche. |
timeLimit | secondes | auto | Temps de calcul, plafonné par l'abonnement. La recherche est itérative : plus de temps ne peut qu'améliorer le résultat. |
Réponse
{
"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" }
} | Field | Description |
|---|---|
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.waste | total = 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.usableRemnants | Chutes ≥ minRemnant, les plus grandes d'abord : le stock de chutes pour demain. |
plan.unusedOffcuts | Chutes que le plan n'a pas utilisées. |
plan.stockBarsUsed | Total de barres à acheter. |
plan.optimal | Preuve. 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.verified | Toujours vrai en cas de succès : demande satisfaite, chaque calepinage est physiquement possible, stock de chutes respecté. |
plan.stockBarsLowerBound, costLowerBound | Minimums prouvables. Si optimal est faux, l'écart vous indique la marge d'erreur maximale de ce plan. |
stats | Totaux 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.
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
{ "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.
{
"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 field | Description |
|---|---|
enabled | Interrupteur général. Les angles sur les pièces ne sont acceptés que si la valeur est true. |
width | Largeur de la matière, même unité que les longueurs. Une coupe en angle s'étend latéralement de width / tan(angle). |
axialSymmetry | none, 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.
"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.
| Status | erreur | Quand |
|---|---|---|
| 400 | validation | Longueurs 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. |
| 401 | badKey | Clé API manquante, inconnue ou désactivée. |
| 413 | partsLimit | Plus de pièces que votre forfait ne le permet. |
| 429 | rateLimited | Limite de requêtes ou plafond mensuel atteint. Respectez Retry-After. |
| 503 | busy | Solveur à 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.
| Guard | Gratuit | Forfaits payants |
|---|---|---|
| Cadence | 10 requêtes / 5 min par IP | 20-240 / min par niveau |
| Pièces totales | 300 | 500 - 5,000 |
| Lignes de pièces distinctes | 60 | jusqu'à 400 |
| Budget de calcul | 20 s | 60 - 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)
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)
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.
{ "parts": [ /* 900 parts */ ], "stockLength": 6500,
"kerf": 4, "timeLimit": 60 }