Documentación de la API

El optimizador como servicio HTTP: envíe las piezas y el material, reciba un plan de corte verificado o un PDF listo para imprimir. Estable, versionado y retrocompatible: se añaden campos, nunca se eliminan.

Introducción

URL base https://api.linearcutting.com. Todo es JSON, excepto el endpoint de PDF. Las peticiones no tienen estado: no guardamos sus listas de corte.

No necesita una clave de API para probar esto. La siguiente línea se puede ejecutar tal cual. Péguela en 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}'

Eso es una lista de corte completa: cinco piezas de 2400, tres de 1800, cortadas de barras de 6000mm. Recibes los esquemas de corte, cuántas barras se necesitan, cuánto se desperdicia y si el plan es demostrablemente óptimo.

Las llamadas no autenticadas se ejecutan en un nivel público gratuito para que pueda evaluar el producto antes de pagar: 30 peticiones por hora y 250 al mes natural, por dirección IP. Superado ese límite, devuelve un 429 que indica cuándo se restablece. Las llamadas fallidas no cuentan. Para un proyecto real, consiga una clave desde 5 $ al mes: elimina ambos límites, aumenta los topes de tamaño y le da concurrencia propia para que una tarde de mucho trabajo en el nivel público no le ralentice.

Clientes oficiales

Cero dependencias, con licencia MIT, y tampoco necesitan clave. Si usa Python o JavaScript, no implemente un fetch manualmente:

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 en PyPI · JavaScript en npm · Código fuente de Python · Código fuente de JavaScript

Dos garantías fundamentales. verified es siempre true si la operación se completa con éxito: cada plan de corte se vuelve a auditar con sus datos de entrada antes de ser devuelto, para que nunca reciba un plan que no sirva para cortar su lista. optimal es una prueba, no una estimación; es true solo cuando la búsqueda ha confirmado que no existe un plan de corte mejor, ya sea por agotar las posibilidades o mediante un certificado. Cuando no se puede demostrar dentro del tiempo asignado, devolvemos false en lugar de suponer.

optimal y stockBarsLowerBound son cosas distintas y discrepan a propósito. El límite inferior es una relajación, básicamente «longitud total de partes ÷ longitud de la barra», y a menudo es inalcanzable. Cinco partes de 2400 y tres de 1800 suman 17.400 mm, que son 2,9 barras de 6000, por lo que el límite inferior es 3. Pero no es posible cortar esas partes de 3 barras; 4 es el mínimo real, y el optimizador lo demuestra. Por tanto, stockBarsUsed: 4 con stockBarsLowerBound: 3 y optimal: true no es una contradicción. Significa que el límite era impreciso, no que el plan de corte fuera incorrecto. Utilice optimal para decidir si seguir buscando y el límite inferior solo como una comprobación.

Forma abreviada

La forma larga es explícita y un poco enrevesada. Para el caso común, use la forma corta: un número es una longitud y x significa la cantidad:

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

Todos estos ejemplos significan lo mismo, así que usa el que mejor se adapte a tu código:

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

Un tercer elemento es una etiqueta en una parte y un precio en el material de stock: [2400, 5, "rail"], [6000, 20, 45.50]. Y stock es el nombre corto para la antigua división stockLength / stocks; un número simple significa «esta longitud, compre tantas como necesite».

La forma corta se expande antes que nada, por lo que es una forma de escribir en lugar de un modo diferente: una solicitud corta y la solicitud larga a la que se expande devuelven planes de corte idénticos byte por byte. Puede mezclarlas libremente; todas las solicitudes existentes siguen funcionando exactamente igual.

Una lista de corte en una URL

Los trabajos pequeños caben en una cadena de consulta, así que GET también funciona. Esto es útil para un enlace que quieras compartir, presupuestar o adjuntar en un informe de error:

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

Misma autenticación, mismos límites, mismo plan que con POST. Puedes abrirlo en un navegador si quieres.

Autenticación

El nivel público no necesita clave. Los niveles de pago envían su clave en una cabecera; las claves también contienen los orígenes de navegador permitidos, para que las llamadas desde tu propio sitio web funcionen sin un proxy.

http
X-API-Key: lk_live_9f2c...

Una clave incorrecta o caducada devuelve 401. Las claves se emiten por espacio de trabajo: ver planes.

POST /v1/optimise

Resuelve una lista de corte. Devuelve el plan, estadísticas y límites demostrables.

Cuerpo de la petición

FieldTipoPor defectoDescripción
parts requerido array- Las piezas que necesitas. Cada una: length (> 0), quantity (entero ≥ 1), y opcionalmente label, material, leftAngle, rightAngle.
stockLength número- Un único largo de material ilimitado. Usa este campo o stocks, no ambos.
stocks array- Longitudes de compra. Cada una: length, quantity (null = ilimitada), price (null = optimizar material en lugar de coste), material opcional, priority.
offcuts array[] Longitudes que ya tiene en stock: length, quantity, material opcional, priority. Es material gratuito, no se cuenta como compra.
kerfnúmero ≥ 00Ancho del corte.
trimbooleanofalseDespuntar ambos extremos de cada barra antes de cortar.
methodstringbalanced balanced, least_waste, offcuts_first, fewest_setups (alias: fewest_setups). Los precios en stocks cambian balanced a la opción de menor coste.
minRemnantnúmero ≥ 00Los sobrantes de esta longitud o más largos se devuelven como usableRemnants en lugar de desperdicio.
angleCutsobjetodesactivado{enabled, width, axialSymmetry}. Vea cortes en ángulo.
unitcadena"mm"Se devuelve en la respuesta y se imprime en los PDF. No convierte ninguna unidad.
unitFormatcadenadecimalfraction representa las dimensiones en el PDF como fracciones redondeadas a 1/64.
timeLimitsegundosautomáticoTiempo de cálculo, limitado por su plan. La búsqueda siempre devuelve un resultado; un tiempo de cálculo mayor solo lo mejora.

Respuesta

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" }
}
FieldDescripción
plan.combinations[]Una entrada por cada esquema de corte distinto. usedComb.combination enumera las piezas cortadas de esa barra, length es el origen, source es material o retal, y count es cuántas barras cortar de esa manera.
usedComb.wastetotal = origen − piezas; dividido en saw (ancho de corte + recorte) y remnant (el sobrante final).
plan.purchases[]La lista de la compra: length, count, más price/cost cuando se han indicado precios.
plan.usableRemnantsSobrantes ≥ minRemnant, de mayor a menor: el material de retales para el futuro.
plan.unusedOffcutsRetales que el plan no ha necesitado.
plan.stockBarsUsedTotal de barras a comprar.
plan.optimalPrueba. Es `true` solo cuando el plan alcanza un mínimo demostrable (o si la búsqueda se ha completado de forma exhaustiva). Se puede mostrar al usuario como "el mejor resultado posible".
plan.verifiedSiempre `true` si el proceso tiene éxito: la demanda coincide, cada esquema de corte es físicamente posible y se respeta el suministro de retales.
plan.stockBarsLowerBound, costLowerBoundMínimos demostrables. Cuando optimal es `false`, la diferencia indica el margen máximo de error del plan.
statsTotales del trabajo: piezas, longitudes, yieldPercent, cutCount, layoutCount, totalCost, trueWaste y byMaterial cuando se usan grupos de materiales.

POST /v1/optimise/pdf

Mismo cuerpo de solicitud, más un jobName opcional. Devuelve application/pdf: la página 1 es un resumen para el jefe de proyecto (orden de compra, costes, certificado de optimalidad); las páginas siguientes son hojas para el operario de la sierra con posiciones de corte, etiquetas para las piezas y una casilla de verificación por barra. Las llamadas con clave muestran la atribución o marca del cliente.

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

Grupos de materiales

Etiquete parts, stocks y offcuts con material y cada material se optimiza como un trabajo independiente dentro de una misma solicitud: los cortes solo se asignan a material del mismo tipo. Los diagramas y las compras se devuelven etiquetados, y stats.byMaterial desglosa las cifras por grupo.

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 en una barra o retal es una preferencia no estricta: entre planes de igual calidad, los elementos priorizados se usan primero. Nunca se sacrifica la calidad del plan.

Cortes en ángulo / inglete

Asigne a las piezas un leftAngle y rightAngle y active angleCuts. Los ángulos son los ángulos inferiores de la pieza en su posición de corte; introduzca un ángulo superior como un número negativo (120 y -60 son el mismo corte). Rango válido después de esa transformación: de 10 a 170 grados.

angleCuts fieldDescripción
enabledInterruptor general. Los ángulos en las piezas solo se aceptan si es `true`.
widthAncho del material, en la misma unidad que las longitudes. Un corte en ángulo se desplaza lateralmente width / tan(angle).
axialSymmetrynone, X, Y, Z o XYZ: volteos que permite el material, que deciden cómo pueden anidar los ingletes. El tubo cuadrado es normalmente XYZ; el material con una sola cara es none.

Cuando dos ingletes contiguos son paralelos, un solo corte de sierra crea ambos bordes. Los diagramas en modo de ángulo añaden un array parts ordenado (con la orientación usada) y cutPositions: cada corte medido desde el extremo cero de la barra, como un par [superior, inferior] si es en ángulo, o un solo número si es recto.

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

Errores

El cuerpo de los errores es { "error": "...", "detail": "..." }. El campo detail está escrito para mostrarlo a una persona: nombra la pieza o el material que causa el error.

StatuserrorCuándo
400validationLongitudes o cantidades incorrectas, método desconocido, una pieza que no cabe en ninguna barra, demanda que excede el material finito, reglas de ángulo no cumplidas, o envío simultáneo de stocks y stockLength.
401badKeyClave de API inexistente, desconocida o desactivada.
413partsLimitMás piezas de las que permite su plan.
429rateLimitedLímite de frecuencia o mensual alcanzado. Respete el encabezado Retry-After.
503busyOptimizador a plena capacidad. Reintente en breve.

Límites de frecuencia

Los límites restringen, nunca generan una factura por excedente. Las solicitudes con errores de validación no cuentan para su límite mensual. Las cifras completas están en la página de planes.

GuardGratuitoPlanes de pago
Límite10 peticiones / 5 min por IP20-240 / min por nivel
Piezas totales300500 - 5,000
Líneas de piezas distintas60hasta 400
Límite de cálculo20 s60 - 300 s

El número de líneas de piezas distintas está limitado, además del total de piezas, porque el coste del cálculo depende de la diversidad de longitudes: 300 cortes idénticos se optimizan en milisegundos, pero 300 distintos no.

Ejemplos

Combinación más económica de varios largos de material (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

Piezas para marcos con cortes a inglete (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"])

Gestionar un trabajo grande y lento

Las listas grandes pueden tardar de segundos a minutos. La búsqueda es continua, así que ajusta timeLimit al tiempo que tu interfaz pueda esperar y muestra optimal para indicar si la solución está probada o es simplemente la mejor encontrada hasta el momento.

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