Metadata-Version: 2.4
Name: ecode-cutting
Version: 0.2.9
Summary: 可直接导入调用的纯 Python 一维与二维切割排料依赖库
Author: Thorne
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Provides-Extra: nesting
Requires-Dist: shapely<3,>=2.1; extra == "nesting"
Provides-Extra: linear
Requires-Dist: packingsolver==0.1.1277; python_version >= "3.12" and extra == "linear"

# ecode-cutting

作者：Thorne。

独立的 Python 切割排料依赖包，Python 3.11+。基础型材/矩形直切仅使用标准库；
可选的异形轮廓排版通过 `[nesting]` 安装 Shapely 几何后端。
安装到调用方项目后，通过 `import ecode_cutting` 在同一进程内计算。
调用方自行组织服务、路由、鉴权与任务调度；依赖库负责排料算法和结果导出。

## 安装与调用

当前版本为 **0.2.9**，包含独立轮廓排版、真实余料校验、一维严格规格上限和可选 PackingSolver 直切搜索。
0.2.8 修正曲线保护轮廓的贴边候选，避免本来放得下的圆因多边形扩张和坐标舍入被漏排。
安装当前版本：`python -m pip install ecode-cutting==0.2.9`。
需要异形轮廓与开源直切后端时，使用 `python -m pip install "ecode-cutting[nesting,linear]==0.2.9"`；PackingSolver 需要 Python 3.12+。
发布名称为 `ecode-cutting`，导入名称为 `ecode_cutting`。
开发时可在仓库根目录运行 `python -m pip install ./python`，也可以安装本地 wheel。

完整中文说明见 [使用手册](src/ecode_cutting/USAGE.md)，包含参数、方法、结果字段、
多方案选择、斜角/卷材、导出、存档和兼容入口。手册也随 wheel 安装，可直接读取：

```python
from importlib.resources import files

manual = files("ecode_cutting").joinpath("USAGE.md").read_text(encoding="utf-8")
```

```python
from ecode_cutting import CutEngine

engine = CutEngine(saw_width=3, trim_left=5, trim_right=5)
engine.add_linear_stock(6000, 10, id="原料")
engine.add_linear_part(1200, 20, id="订单A")
result = engine.execute_linear()
response = result.to_dict()  # JSON 可序列化，直接交给 Web 框架返回
```

二维使用 `add_stock(width, height, count)`、`add_part(width, height, count,
rotatable=True)` 和 `execute()`。斜角一维通过 `cross_section`、
`angle_start`、`angle_end` 设置；卷材使用 `set_vertical_cut_direction(True)`
或 `set_horizontal_cut_direction(True)`。每次请求创建自己的引擎。

## 异形零件与余料

`python -m pip install -e "./python[nesting]"` 安装可选轮廓能力。直接调用：

```python
from ecode_cutting import optimize_nesting_plans, adjust_nesting_layout

task = {
    "stocks": [{"width": 100, "height": 100, "count": 1,
                "outline": [[0,0],[100,0],[100,40],[40,40],[40,100],[0,100]],
                "waste": True, "trimmed": True}],
    "parts": [{"width": 30, "height": 30, "count": 3, "rotatable": True}],
    "options": {"saw_width": 3, "rotation_step": 90},
    "search": {"max_plans": 3, "max_searches": 12, "time_limit": 3},
}
portfolio = optimize_nesting_plans(task, time_limit=5)
best = portfolio["plans"][0]  # 返回可直接序列化的字典
board = best["result"]["layouts"][0]
placements = [{k: p[k] for k in ("input_index", "instance_index", "x", "y", "rotation")}
              for p in board["parts"]]
rebuilt = adjust_nesting_layout({"task": task, "layout": {
    "stock_index": board["input_index"], "instance_index": board["instance_index"],
    "placements": placements,
}})
```

外轮廓和内孔参与真实碰撞、刀缝、面积及余料计算。数量 `count=0` 明确表示不限量，
有限数量不重复使用；`trimmed=True` 的余料不重复修边。`area_lower_bound` 是忽略几何后的张数下界，
`strategy` 记录本方案使用的放置策略。算法、开源参考和实测对比见 [轮廓排版说明](src/ecode_cutting/NESTING.md)。

## 新增快速计算和多方案入口

`0.2.0` 保留原来的单方案入口，另增加：

- `execute_linear_fast()`：直端大订单采用容量索引；复杂工艺自动回退完整算法。
- `optimize_linear_plans(...)`：返回多种真实不同的一维方案。
- `optimize_plans(...)`：返回多种真实不同的二维方案。

```python
portfolio = engine.optimize_linear_plans(max_plans=3, time_limit=5, max_searches=12)
for plan in portfolio.plans:
    m = plan.metrics
    print(plan.id, plan.label, m.utilization_percent, m.used_stock_count, m.cut_count)

response = portfolio.to_dict()  # 前端展示所有方案，再由客户选择 plan.id
```

`metrics.utilization_percent` 是**总有效成品尺寸 / 总已用原料尺寸 × 100**，
不是各根/各张利用率的平均值。一维按有效长度，二维按真实轮廓面积；斜角
按多边形实际面积换算有效长度，名义长度指标仍可从原 Result 查询。卷材按
本次切下的净尺寸计算，不把未切下的整卷当成已耗原料。
每个方案另列完成率、未完成件数、根数/张数、含修边的切数和不同版式数。

返回方案在几何与刀序上去重，最多 `max_plans` 个；唯一方案不会伪造为多个。
只保留本次达到的最高完成件数，部分完成方案有明确标记。标签和 Pareto
标志仅比较本次候选，不声称全局最优。所有候选共享预算并保留已得到的
有效结果；物理工艺约束不放宽。该预算是合作式控制，不承诺硬实时中断。

多方案方法不会改写引擎原有输入、配置和已存结果。选择后可直接导出该方案：

```python
from ecode_cutting.exporters import export_csv

chosen = portfolio.best  # 也可按客户端选定的 id 从 portfolio.plans 中查找
if chosen is not None:
    export_csv(chosen.result, "chosen.csv")
```

快速入口的适用条件、实测性能和验收记录见 `docs/PORTFOLIO.md`。

## 按 CutGLib 手册迁移调用

原生 `CutEngine` 返回 Python 数据模型。需要手册中的大小写方法、错误字符串和
`bool/out` 返回契约时，使用同一纯 Python 内核的 `CutGLibEngine`：

```python
from ecode_cutting import CutGLibEngine, Out

engine = CutGLibEngine()
engine.AddLinearStock(6000, 10, "原料")
engine.AddLinearPart(1200, 20, "订单A")
error = engine.ExecuteLinear()  # 成功返回空字符串
if error:
    raise RuntimeError(error)

stock, length, location, part_id = Out(), Out(), Out(), Out("")
ok = engine.GetResultLinearPart(0, stock, length, location, part_id)
print(ok, stock.value, length.value, location.value, part_id.value)
```

不传 `Out` 时查询返回 `(成功标志, 输出值...)`，该元组的布尔判断使用成功标志。
回调的 `ref bool aStop` 写为 `def on_state(stop): stop.value = cancel_event.is_set()`。
`Clear()` 按原版重置输入和配置；原生 `clear()` 的配置保留行为继续不变。
兼容入口的库存查询包含未用原料，普通未组合原料的组合数为 0。

手册、原 DLL 和 Python 的逐项证据见 `docs/API_AUDIT.md`。版本/旧授权、
原版异常缺陷、不同排样坐标及导出字节的差异均有明确记录，不能理解为任意
输入的输出与原版完全相同。

## 由其他项目直接调用

调用方可以直接传入任务字典并取得计算结果：

```python
from ecode_cutting import CutEngine

engine = CutEngine.from_dict({
    "stocks": [{"width": 6000, "count": 10}],
    "parts": [{"width": 1200, "count": 20, "id": "订单A"}],
    "options": {"saw_width": 3, "time_limit": 5},
})
output = engine.execute_linear().to_dict()
```

多方案调用同一个引擎的 `optimize_linear_plans(max_plans=3, time_limit=5)`
或二维的 `optimize_plans(...)`。`output` 和 `portfolio.to_dict()` 均可由
调用方继续处理、存储或返回。每个独立任务创建自己的引擎。

`examples/quickstart.py` 和 `examples/multiple_plans.py` 演示直接导入调用。

## 可选的 HTTP 集成测试工具

`examples/web_app.py` 保留用于后续联调和测试，手动运行
`python python/examples/web_app.py` 后，在本机 `127.0.0.1:8000` 提供示例接口。
`tests/test_web.py` 使用临时端口执行真实 HTTP 测试，结束后自动关闭。
这些文件位于包源码之外，不进入 wheel；正式接入仍由其他项目直接导入依赖库。

## 输入与结果约定

- 所有尺寸使用同一种单位；`max_decimal_point` 为 0–6，默认 3。
  内部用整数尺寸计算，舍入采用 ties-to-even。
- 原料 `count=0` 表示无限供应；零件 `count=0` 表示没有需求。有限库存不超用。
- 材料编号由 `add_material()` 返回。原料材料 `-1` 可匹配所有材料；
  指定材料的原料不能接收未指定材料的零件。
- `complete_mode=True` 时，某种零件完全没有可容纳的原料会抛出 `ValidationError`；
  库存数量不足返回部分结果，并在 `unplaced` 中报告全部未完成需求。
- `Result` 保留每个输入及每个实例的映射，不因布局合并丢失订单明细。
  `fill_ratio` 使用真实成品轮廓面积；斜角共切不会因包围盒重叠而超过 100%。
  `nominal_fill_ratio` 另保留名义长度/包围盒面积指标。旧查询
  `get_layout_fill_ratio()` 在设置截面时按原库的用棒率语义返回。
- 卷材的 `Layout.remnants` 为净板内余料，`roll_remnants` 为原卷未消耗的尾段，
  以原料坐标记录并扣除截断锯缝；`all_remnants` 和现有余料查询汇总两者。
  总利用率仍按本次切下的净板计算，尾段可交由调用方回收入库。
- `cuts`、`trim_cuts` 表示锯缝中心线，切割顺序按实际加工前序输出。
- `time_limit` 为总搜索秒数，`None` 表示无时间限制；回调接收 `Progress`，
  返回 `True` 即请求取消。取消或超时保留已有可行结果并设置标志。
- 采用有界多策略搜索，提供可行解和小实例优化，不承诺任意输入的全局最优。

## 导出与存档

`engine.export_csv(target)`、`export_dxf(target)`、`export_cuts_dxf(target)`、
`export_ptx(target)` 支持文件路径或流。
`create_stock_image(index, path)` 支持 PNG 和 SVG。
`to_json()/from_json()`、`to_xml()/from_xml()` 保存及恢复任务；
`ecode_cutting.serialization` 另提供结果往返。

PTX 使用 1.08 记录格式，仅支持二维断头切割，与原库一维 PTX 不支持的边界一致。
机床控制器的实际适配仍需使用目标设备软件验收。
Python 存档格式独立版本化；旧 XML 提供可识别任务属性的导入。
原产品的 COM 注册、Windows 注册表授权和硬件绑定不属于新算法包接口。

## 开发验证

```text
python -m pip install -e ./python
python -m unittest discover -s python/tests -v
python python/examples/quickstart.py
python -m pip wheel ./python --no-deps --wheel-dir ./python/dist
python python/tools/verify_install.py
```

测试包含独立几何与数量核验、断头切割顺序重演、小实例已知最优值、
斜角轮廓、材料/库存、预算/取消、批量输入、导出解析及独立项目安装调用。
功能对照与验证说明见 `MIGRATION.md`；变更按 Conventional Commits 本地提交。


### 0.2.9 型材优化

新增严格的 `linear_max_types_per_stock`，0 表示不限；可选 `[linear]` 安装 PackingSolver 直切搜索后端（Python 3.12+）。用料评分在单解与多方案之间统一，大订单支持局部撤棒重排。业务端默认启用开源候选，包调用方通过 `linear_use_packingsolver=True` 选择启用。完整参数、约束回退及版本要求见使用手册的 0.2.9 节。
