# OpCut 排版引擎输入输出规范 · opcut-io 1.0

> 公开阅读副本。仅对调研引言中的商业产品名称及 §8.1 中的开源内核名称作匿名化处理；接口契约保持一致。

> 版本：1.0（首版）｜制定：指挥官（Architect）｜2026-10-18｜决策者令：调研行业规范——**无统一标准则制定广泛通用格式**（JSON、简单规范、带版本、可演进）；有则从之。
> 调研结论（2026-10-18，可复核）：①几何交换事实标准＝**DXF**（R14/AutoCAD 2000 档，mm；新版本反被劝退）；②学术基准＝**ESICUP** 数据集格式（GitHub ESICUP/datasets）；③**排版引擎的 JSON 输入输出不存在行业统一标准**——各家云排样 API 均为厂商自定义，Frontiers in Mech. Eng. 2022 综述明确"ESICUP 实例之外仍缺通用交换格式"。⇒ 按决策树立本规范，并向两个事实惯例对齐（单位/几何惯例从 DXF，基准语义从 ESICUP）。

## 1. 设计原则

1. **一个规范、两个引擎剖面**：`rect`（矩形一刀切）与 `nest`（异形真形）共用同一信封与版本纪律，几何字段按剖面分叉；
2. **单位恒定 mm**（对齐 DXF 惯例），v1 不设单位字段（防歧义，未来需要时 MINOR 加必填 `units`）；
3. **版本双写**：请求带 `spec`+`spec_version`（可缺省＝按 1.0 处理），响应**恒回显**同值——客户端可探测；
4. **宽容读、严格写**：服务端忽略未知请求字段（并进 `warnings` 提示），响应只增不删；客户端必须容忍未知响应字段；
5. **兼容承诺（语义化）**：MAJOR＝破坏性变更（删字段/改语义，另立新版本号并存旧版 ≥6 个月）；MINOR＝纯增量（新可选字段/新枚举值）；PATCH＝纠错不改契约。引擎算法升级（换构造器/提精度）**不 bump 本规范**——引擎版本走响应 `engine.version` 随行；
6. **简单**：无嵌套超过两层；必填字段 ≤8 个；一切可选字段有安全默认。

## 2. 请求信封（公共）

```json
{
  "spec": "opcut-io",
  "spec_version": "1.0",          // 可缺省＝"1.0"；未知主版本 → 422 SPEC_UNSUPPORTED
  "engine": "rect",                // "rect" | "nest"
  ...剖面字段见 §3/§4...
}
```

## 3. rect 剖面（矩形一刀切引擎）

```json
{
  "stock":  [{"id": "S1", "w": 2440, "h": 1220, "count": 100, "defects": [{"x":0,"y":0,"w":50,"h":40}]}],
  "order":  [{"id": "P1", "w": 450, "h": 300, "qty": 37}],
  "constraints": {"kerf_mm": 5, "trim_mm": 10},
  "options": {"time_budget_s": 3.0, "seed": 1}
}
```
响应：`{"spec","spec_version","status":"solved|infeasible|unknown","sheets":[{"stock_id","placements":[{"part_id","x","y","w","h","rotated"}],"cuts":[…]}],"metrics":{"sheets_used","utilization_percent"},"engine":{"name","version","seconds"},"warnings":[],"billing":{…}}`（cuts/offcuts 仅 rect 剖面有；billing 仅计费调用返回）。

### 3.1 自由矩形排样增量（2026-10-19）

rect 请求新增可选字段 `cut_mode: "guillotine" | "free"`，缺省为 `guillotine`
（直刀贯穿切割），原有求解、预算和计费语义不变。该字段属于兼容的 MINOR 增量，
继续接受 `spec_version: "1.0"`。显式 `guillotine` 与省略字段共用同一幂等请求记录；
同一幂等键从 `guillotine` 改为 `free` 属于请求内容变化，返回 409。

`free` 解除直刀贯穿约束，零件仍为矩形，尺寸仍按成品尺寸输入，并保留平台磨边余量。
现行 `POST /v1/optimize` 的 200 响应与异步任务终态 `result` 均回显 `cut_mode`。
自由模式额外输出 `free_mode: true`，且每个 `plan[]` **省略 `cuts` 字段**，表示结果
没有贯穿切割树或刀序语义；零件坐标仍在 `plan[].parts`，可用区域仍在 `usable`。
`guillotine` 模式保留原有 `cuts` 结构。同步接口等待超时返回的 202 受理响应及其
幂等重放同样回显 `cut_mode`，自由模式另带 `free_mode: true`，沿用任务编号和查询
地址；202 不带 `plan` 或 `cuts`，终态查询 200 的 `result` 继续回显模式。
客户端应依据 `cut_mode` 判断设备是否支持该切割方式。

首版 `free` 支持一种有效原片规格（`count > 0`）及有限库存，旋转许可须统一：
零件全部 `grain=none`，或全部禁止旋转。`cut_model`、`max_evals`、原片 `defects`、
零件 `value`、多种有效原片、混合旋转许可均返回 422 `FREE_MODE_UNSUPPORTED`，
同步、异步和批量入口都在落单及计费前拒绝；批量含此类输入时整批拒绝。
自由模式使用 `time_budget_s` 墙钟预算，不静默忽略确定评估次数预算。

## 4. nest 剖面（异形真形引擎）

```json
{
  "stock":  {"w": 2250, "h": 3210},
  "parts":  [{"id": "P1", "qty": 2, "outline": [[0,0],[520,0],[520,180],[260,300],[0,180]]}],
  "constraints": {"kerf_mm": 0, "trim_mm": 0, "break_margin_mm": 0, "allowed_angles_step": 90},
  "options": {"time_budget_s": 30.0, "seed": 1, "route": "wheel_score_break"}
}
```
几何规则（对齐 DXF 惯例）：`outline`=外轮廓顶点序对 [[x,y]…]，单位 mm，**末点可不重复首点**（服务端自动闭合）；弧线由上游按弓高误差 ≤0.1mm 离散为折线（DXF 惯例，v1 不传弧）；顶点 ≤512，禁自交、面积>0（422 报白话错）。
响应：`{"sheets":[{"id","w","h","placements":[{"part_id","x","y","angle","outline":[[x,y]…]}],"utilization_percent","cut_tier":"separable|non_separable"}],"metrics":{"sheets_used","utilization_percent","lower_bound_sheets"},"verify":{"status","issues"},"route","engine":{…}}`。

## 5. 版本与兼容纪律（写死）

| 事项 | 规则 |
|---|---|
| 探测 | 客户端发任意请求读响应 `spec_version`；服务端收到未知 `spec` 名或 MAJOR>1 → 422 `SPEC_UNSUPPORTED` |
| MINOR 增量 | 只加可选字段/新枚举；旧客户端零影响；服务端在 `warnings` 提示"字段较新" |
| 弃用 | 字段弃用先标 `deprecated`（文档＋响应 warnings）≥1 个 MINOR 周期，才可在下一 MAJOR 删除 |
| 别名 | `outline` 为 nest 几何正名；历史名 `poly` **永久等价接受**（响应输出用正名） |
| 引擎升级 | 算法/构造器/精度升级不动本规范；`engine.version` 随响应走，客户端可记录 |
| DXF | v1.1 预留位：`parts[].dxf_ref`（引用上传的 DXF 图层）与 DXF 版式导出——只加不改 |

## 6. 错误码（剖面无关）

`401 AUTH_REQUIRED`｜`403 SCOPE_DENIED`｜`402 INSUFFICIENT_BALANCE`｜`422 VALIDATION_FAILED`（details 白话逐条）｜`422 SPEC_UNSUPPORTED`｜`429 RATE_LIMITED`（Retry-After）｜`504 SOLVE_TIMEOUT`。

## 7. 实现与登记

- 规范正本＝本文件（只增不改，改版另立 `opcut-io-spec-1.x.md`）；JSON Schema 由服务端模型导出并随接口文档发布；
- 现行端点映射：rect=`POST /v1/optimize`（同步/202 jobs 双口），nest=`POST /nesting/optimize`；两端口均接受并回显 `spec`/`spec_version`（本版落地）；
- 网站 API 文档板块（/docs）按本规范呈现双引擎使用方法（接入指南/字段表/错误码/计费/示例 curl）。

## 8. MINOR 增量登记（1.0 系内，只加不改）

### 8.1 inner（零件内部嵌套 DXF 图纸，2026-10-19 主人令）

行业习惯依据：DXF=几何事实标准；BLOCK/INSERT 为标准复用机制；常见开源排样内核惯例＝闭合环为形状、包含关系判内孔、开放实体忽略；弧线按弓高 ≤0.1mm 离散；单位 mm；层名定角色。

- 请求增量（rect 剖面，纯可选）：`parts[].inner: {"dxf_ref": "<上传件ID>", "layers": ["CUT_INNER","MARK",…]}`（layers 可缺省＝全部图形层）。
- 新端点：`POST /io/dxf`（multipart，≤2MB，DXF R14/AutoCAD 2000）→ `{"dxf_ref","entities":{外轮廓数,内孔数,标记数},"warnings"}`；解析规则：CIRCLE/ARC/ELLIPSE/LWPOLYLINE/POLYLINE 全按弓高 0.1mm 折线化；闭合环按**包含关系**分级（最外=外轮廓校验件包络，被包含=内孔/图形）；开放实体归 MARK 层或忽略；未知层归 MARK。
- 响应增量：`placements[].inner_graphics: [{"layer","points":[[x,y]…]}]`——随件位姿（x/y/rotated）变换后的绝对坐标 mm；版式 SVG 下载版按层叠画（虚线/细线，不与切割线混淆）。
- 语义边界：inner 图形**不参与排样算法**（包络排样不变、利用率分母不变）；不与 kerf/刀缝交互（生产侧按层出机台数据属后续版本）。
- **曲线输出双格式（2026-10-19 主人追加）**：`parts[].inner.output_curve: "polyline"（缺省）| "arc"`。`polyline`＝按弓高 0.1mm 折线化（§8.1 原行为，响应 `inner_graphics[].points`）；`arc`＝保留真弧段（响应 `inner_graphics[].segments`，元素 `{type:"line",p1:[x,y],p2:[x,y]}` 或 `{type:"arc",cx,cy,r,a1_deg,a2_deg,ccw}`，整圆＝单 arc 段），前端/下载 SVG 以 path A 指令渲染，保真更高、大半径载荷更小。两种格式服务端均可出，切换只改该参数，其余字段零变化。
- **自动包络（2026-10-19 主人追加）**：`POST /io/dxf` 响应增 `envelope: {w, h}`（外轮廓外接框，mm）——/try 前端上传即自动填件尺寸（可手改）；`parts[].inner.margin_mm`（≥0，缺省 0，四周外扩量）→ 件尺寸＝(envelope.w+2m)×(envelope.h+2m)，`inner_graphics` 随外扩平移（件原点＝包络左下角外扩 m 处），margin 只改件尺寸不进算法内部。
- 校验：dxf_ref 未知/超期 404；DXF 解析失败 422 白话逐条；内轮廓超出件包络 422；output_curve/margin_mm 非法 422。

### 8.2 1.1 质量锚增量（ALG-028，2026-10-20）

- rect 请求可选 `quality_anchor`：有限利用率数值 `0 < util <= 1`、三元组 `[complete, parts_value, R]`，或由非空字符串案号映射到上述形态的非空对象。complete 严格为整数0/1，parts_value 为有限非负数，R 为有限数且允许负值；拒绝 bool、数字字符串、NaN、Infinity。映射必须显式提供命中键的 `case_id`，并验证所有映射项。
- 纯输入保护：服务端不读取历史、缓存或文件作为锚，不用锚生成候选。无深度时仅审计原基线；有深度时候选任一分量低于锚则保留原基线。基线仍可能低于锚，此时保留原业务解并如实披露。
- 利用率比较无浮点容差；调用方应直接使用原始0到1面积比，不应先转成显示百分数再除100，后者可能使下限上移一个最小浮点步长并正确触发拦截。
- 请求非法、映射案号缺失/未命中或 `free` 模式带锚，均计费前422 `QUALITY_ANCHOR_INVALID`；异步及批量入口同样预检。锚和case_id进入请求记录，同幂等键修改任一值返回409；无锚或显式null保留旧记录字节。
- 有锚的200、202受理和jobs结果回显顶层 `quality_anchor` 原输入，幂等重放保留；最终结果增加 `engine.anchor_status`。其中 `miss/axis` 披露候选拦截或最终未达标，`baseline_miss/baseline_axes`、`candidate_miss/candidate_axes`、`selected_miss/selected_axes` 区分三阶段；轴为 `util/complete/parts_value/R`，无未达标轴时为null；候选未比较时 `candidate_miss=null`。以 `selected_miss` 判断最终解是否仍未达标，不能仅凭miss判断最终质量。
- 无锚不增加响应字段、不传锚参数、不改变计费。兼容默认 `spec_version="1.0"`，客户端可显式声明1.1；本增量只加可选字段，nest剖面保持原契约。
