ecode-cutting 0.2.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ecode_cutting/USAGE.md +673 -0
- ecode_cutting/__init__.py +18 -0
- ecode_cutting/_common.py +158 -0
- ecode_cutting/_linear_fast.py +231 -0
- ecode_cutting/compat.py +659 -0
- ecode_cutting/engine.py +558 -0
- ecode_cutting/exporters.py +362 -0
- ecode_cutting/linear.py +532 -0
- ecode_cutting/models.py +230 -0
- ecode_cutting/portfolio.py +234 -0
- ecode_cutting/py.typed +0 -0
- ecode_cutting/rectangular.py +357 -0
- ecode_cutting/serialization.py +234 -0
- ecode_cutting/validation.py +234 -0
- ecode_cutting-0.2.3.dist-info/METADATA +190 -0
- ecode_cutting-0.2.3.dist-info/RECORD +18 -0
- ecode_cutting-0.2.3.dist-info/WHEEL +5 -0
- ecode_cutting-0.2.3.dist-info/top_level.txt +1 -0
ecode_cutting/USAGE.md
ADDED
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
# ecode-cutting 中文使用手册
|
|
2
|
+
|
|
3
|
+
作者:Thorne。适用版本:0.2.3。Python:3.11 及以上。
|
|
4
|
+
|
|
5
|
+
本包提供一维型材与二维板材排料、材料及库存管理、斜角、卷材、多方案搜索、
|
|
6
|
+
结果查询、文件导出和任务存档。计算只使用 Python 标准库,不加载 CutGLib DLL。
|
|
7
|
+
其他项目安装本包后直接导入,在自己的进程中调用;服务、路由和数据库由调用方开发。
|
|
8
|
+
|
|
9
|
+
## 1. 安装和入口选择
|
|
10
|
+
|
|
11
|
+
本地 wheel 安装(在 wheel 所在目录执行):
|
|
12
|
+
|
|
13
|
+
```powershell
|
|
14
|
+
python -m pip install ./ecode_cutting-0.2.3-py3-none-any.whl
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
开发安装(在源仓库根目录执行):
|
|
18
|
+
|
|
19
|
+
```powershell
|
|
20
|
+
python -m pip install -e ./python
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
发布到 PyPI 后,其他项目可以使用:
|
|
24
|
+
|
|
25
|
+
```powershell
|
|
26
|
+
python -m pip install ecode-cutting==0.2.3
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
安装名称是 `ecode-cutting`,代码中的导入名称是 `ecode_cutting`。
|
|
30
|
+
|
|
31
|
+
- 新项目使用 `CutEngine`:小写下划线方法,返回 `Result` 等 Python 数据模型。
|
|
32
|
+
- 迁移 CutGLib 手册调用使用 `CutGLibEngine`:PascalCase、错误字符串及 `bool/out` 适配。
|
|
33
|
+
- `Options` 集中描述工艺和搜索选项,两个入口共享纯 Python 计算内核。
|
|
34
|
+
|
|
35
|
+
一次独立计算任务创建一个引擎实例,按“设置参数 → 添加材料/原料/零件 → 执行 → 读取或导出”调用。
|
|
36
|
+
|
|
37
|
+
## 2. 最小一维调用
|
|
38
|
+
|
|
39
|
+
尺寸使用同一种单位,下例全部按毫米理解。
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
from ecode_cutting import CutEngine
|
|
43
|
+
from ecode_cutting.validation import assert_valid
|
|
44
|
+
|
|
45
|
+
engine = CutEngine(saw_width=3, trim_left=5, trim_right=5)
|
|
46
|
+
engine.add_linear_stock(6000, 10, id="原料-6000")
|
|
47
|
+
engine.add_linear_part(1200, 20, id="订单-A")
|
|
48
|
+
result = engine.execute_linear()
|
|
49
|
+
|
|
50
|
+
assert_valid(result, engine.stocks, engine.parts, engine.options)
|
|
51
|
+
assert result.complete and result.placed_part_count == 20
|
|
52
|
+
assert result.used_stock_count == 5
|
|
53
|
+
print(result.used_stock_count, f"{result.fill_ratio:.2%}")
|
|
54
|
+
output = result.to_dict()
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`add_*` 成功返回 `True`;`add_material()` 返回材料索引。
|
|
58
|
+
不要把 `add_linear_part()` 的返回值当作零件索引。
|
|
59
|
+
|
|
60
|
+
## 3. 二维板材调用
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
from ecode_cutting import CutEngine
|
|
64
|
+
from ecode_cutting.validation import assert_valid
|
|
65
|
+
|
|
66
|
+
engine = CutEngine()
|
|
67
|
+
engine.add_stock(100, 80, 2, id="板材")
|
|
68
|
+
engine.add_part(50, 40, 8, rotatable=False, id="面板")
|
|
69
|
+
result = engine.execute()
|
|
70
|
+
|
|
71
|
+
assert_valid(result, engine.stocks, engine.parts, engine.options)
|
|
72
|
+
assert result.complete and result.used_stock_count == 2
|
|
73
|
+
assert result.fill_ratio == 1
|
|
74
|
+
print(result.to_dict()["placed_part_count"])
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`width` 对应 x 方向,`height` 对应 y 方向,坐标原点在原料左上角,x 向右、y 向下。
|
|
78
|
+
`rotatable=True` 允许二维零件旋转 90°;一维斜角掉头/翻转由单独选项控制。
|
|
79
|
+
|
|
80
|
+
## 4. 输入方法与数量约定
|
|
81
|
+
|
|
82
|
+
以下为 `CutEngine` 的公开输入签名,星号之后的参数应使用参数名传入。
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
CutEngine(options: Options | None = None, **settings)
|
|
86
|
+
add_material(id="", description="", thickness=0.0) -> int
|
|
87
|
+
get_material(material) -> Material
|
|
88
|
+
add_stock(width, height, count=1, id="", waste=False, material=-1) -> bool
|
|
89
|
+
add_part(width, height, count=1, rotatable=True, id="", material=-1) -> bool
|
|
90
|
+
add_linear_stock(length, count=1, *, id="", waste=False, material=-1) -> bool
|
|
91
|
+
add_linear_part(length, count=1, *, id="", material=-1,
|
|
92
|
+
angle_start=0.0, angle_end=0.0) -> bool
|
|
93
|
+
clear(*, reset_options=False) -> None
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
- 原料 `count=0` 表示无限供应;正数表示实际库存数量。
|
|
97
|
+
- 零件 `count=0` 表示没有需求。原生入口不接受负数量。
|
|
98
|
+
- `id` 是调用方的字符串标签,可以重复,不作为计算索引。
|
|
99
|
+
- `material` 是 `add_material()` 返回的整数索引;`-1` 表示未指定。
|
|
100
|
+
- 未指定材料的原料 `material=-1` 能接收任意材料零件;指定材料的原料只接收相同材料,
|
|
101
|
+
零件侧的 `-1` 不具有通配含义。
|
|
102
|
+
- `waste=True` 表示输入的库存余料,求解器优先考虑它;`result.unplaced` 才是未完成需求。
|
|
103
|
+
- 所有尺寸、锯缝、修边、截面和材料厚度按调用方约定使用单位,不自动推断毫米或英寸。
|
|
104
|
+
- 尺寸必须为正有限数值,数量必须为整数,布尔字段必须传 `True/False`。
|
|
105
|
+
|
|
106
|
+
材料和库存余料的完整示例:
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from ecode_cutting import CutEngine
|
|
110
|
+
|
|
111
|
+
engine = CutEngine()
|
|
112
|
+
aluminum = engine.add_material("AL", "铝材", 2.0)
|
|
113
|
+
engine.add_linear_stock(100, 1, id="库存余料", waste=True, material=aluminum)
|
|
114
|
+
engine.add_linear_stock(100, 0, id="常规原料", material=aluminum)
|
|
115
|
+
engine.add_linear_part(60, material=aluminum)
|
|
116
|
+
engine.add_linear_part(40, material=aluminum)
|
|
117
|
+
result = engine.execute_linear()
|
|
118
|
+
|
|
119
|
+
assert result.complete and result.used_stock_count == 1
|
|
120
|
+
assert result.layouts[0].waste
|
|
121
|
+
assert engine.get_material(aluminum).description == "铝材"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
新增输入、`clear()` 和方向设置方法会清除旧结果,需要重新计算。
|
|
125
|
+
`clear()` 默认保留选项;`clear(reset_options=True)` 同时恢复默认选项。
|
|
126
|
+
直接修改 `engine.options` 不会主动删除旧结果,应重新执行后再读取或导出。
|
|
127
|
+
|
|
128
|
+
## 5. 计算方法和快速入口
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
execute(callback=None) -> Result 二维排料
|
|
132
|
+
execute_linear(callback=None) -> Result 一维完整求解器
|
|
133
|
+
execute_linear_fast(callback=None) -> Result 一维容量索引快速入口
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
三个单方案方法都会更新 `engine.result`。计算前访问该属性会抛 `RuntimeError`。
|
|
137
|
+
|
|
138
|
+
快速入口适合超过 64 件的直端订单。小订单、斜角、最小余料、规格种类限制和
|
|
139
|
+
组合原料等复杂情形自动回退完整求解器,调用方式和结果模型保持一致。
|
|
140
|
+
在没有取消或预算提前截断时,已测适用样例的快速/普通入口标准化输出一致。
|
|
141
|
+
|
|
142
|
+
```python
|
|
143
|
+
from ecode_cutting import CutEngine
|
|
144
|
+
|
|
145
|
+
engine = CutEngine(max_attempts=1)
|
|
146
|
+
engine.add_linear_stock(100, 0)
|
|
147
|
+
engine.add_linear_part(10, 5000)
|
|
148
|
+
result = engine.execute_linear_fast()
|
|
149
|
+
|
|
150
|
+
assert result.complete and result.used_stock_count == 500
|
|
151
|
+
assert result.placed_part_count == 5000
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## 6. 多方案搜索、总利用率和客户选择
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
optimize_linear_plans(*, max_plans=3, time_limit=5.0,
|
|
158
|
+
max_searches=12, callback=None) -> OptimizationPortfolio
|
|
159
|
+
optimize_plans(*, max_plans=3, time_limit=5.0,
|
|
160
|
+
max_searches=12, callback=None) -> OptimizationPortfolio
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
前者是一维,后者是二维。`max_plans` 和 `max_searches` 为正整数;前者限制返回数量,
|
|
164
|
+
后者限制尝试的策略数。总预算取方法 `time_limit` 与 `engine.options.time_limit`
|
|
165
|
+
的较小值;引擎选项为 `None` 时只使用方法预算。方法的 `time_limit` 必须为
|
|
166
|
+
非负有限秒数,不接受 `None`。零预算可以没有方案,始终先判断 `portfolio.best is not None`。
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from ecode_cutting import CutEngine
|
|
170
|
+
from ecode_cutting.exporters import export_csv
|
|
171
|
+
from ecode_cutting.validation import assert_valid
|
|
172
|
+
|
|
173
|
+
engine = CutEngine()
|
|
174
|
+
engine.add_linear_stock(100, 20, id="短料")
|
|
175
|
+
engine.add_linear_stock(120, 20, id="长料")
|
|
176
|
+
for length in (60, 40, 55, 45, 35, 35, 30):
|
|
177
|
+
engine.add_linear_part(length)
|
|
178
|
+
|
|
179
|
+
portfolio = engine.optimize_linear_plans(max_plans=3, time_limit=2)
|
|
180
|
+
for plan in portfolio.plans:
|
|
181
|
+
m = plan.metrics
|
|
182
|
+
print(plan.id, plan.label, m.utilization_percent, m.used_stock_count, m.cut_count)
|
|
183
|
+
|
|
184
|
+
assert portfolio.best is not None
|
|
185
|
+
selected_id = portfolio.best.id # 实际项目使用客户提交的本次方案 id。
|
|
186
|
+
selected = next(plan for plan in portfolio.plans if plan.id == selected_id)
|
|
187
|
+
assert_valid(selected.result, engine.stocks, engine.parts, engine.options)
|
|
188
|
+
export_csv(selected.result, "selected.csv")
|
|
189
|
+
output = portfolio.to_dict()
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
二维调用同一个方法集合中的 `optimize_plans()`:
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from ecode_cutting import CutEngine
|
|
196
|
+
|
|
197
|
+
engine = CutEngine()
|
|
198
|
+
engine.add_stock(100, 80, 20)
|
|
199
|
+
engine.add_stock(120, 100, 20)
|
|
200
|
+
engine.add_part(50, 40, 3)
|
|
201
|
+
engine.add_part(30, 20, 4)
|
|
202
|
+
engine.add_part(45, 30, 2)
|
|
203
|
+
portfolio = engine.optimize_plans(max_plans=3, time_limit=2)
|
|
204
|
+
|
|
205
|
+
assert portfolio.best is not None and portfolio.best.complete
|
|
206
|
+
print(portfolio.best.metrics.utilization_percent)
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
多方案返回值的含义:
|
|
210
|
+
|
|
211
|
+
- `plans`:方案元组;几何及刀序去重,不使用仅改变实例编号的结果凑数量。
|
|
212
|
+
- `best`:排序后的首方案,或 `None`。排序依次比较总用料、物理原料数、切数和版式数。
|
|
213
|
+
- `elapsed/cancelled/timed_out/diagnostics`:本次总耗时、取消、总超时及诊断信息。
|
|
214
|
+
- `searches_started/searches_completed`:开始/正常返回的求解调用数,正常返回包含中断后返回部分解。
|
|
215
|
+
- 方案 `id/label/strategy/result/metrics/pareto_optimal`:本次标识、标签、搜索偏好、原始结果、指标及候选内 Pareto 标志。
|
|
216
|
+
- 方案 `complete/partial`:是否完成全部需求。只保留本次搜索达到的最高完成件数的候选。
|
|
217
|
+
|
|
218
|
+
`metrics` 指标:
|
|
219
|
+
|
|
220
|
+
- `requested_parts/placed_parts/unplaced_parts`:需求、完成、未完成件数。
|
|
221
|
+
- `completion_ratio`:完成比例,0–1。
|
|
222
|
+
- `used_stock_count`:实际物理根数/张数,组合原料按组件数量统计。
|
|
223
|
+
- `used_layout_count`:展开后的排料布局数量;`layout_count` 是不同几何/刀序版式数量。
|
|
224
|
+
- `total_stock_size/total_part_size`:总已用原料尺寸和有效成品尺寸;`unit` 为 `length` 或 `area`。
|
|
225
|
+
- `utilization`:0–1 的总利用率;`utilization_percent`:百分数。
|
|
226
|
+
- `cut_count`:主切割与修边/截断刀数量之和。
|
|
227
|
+
|
|
228
|
+
总利用率按总有效长度/总用料长度(一维)或总真实面积/总用料面积(二维)计算,
|
|
229
|
+
不是每根、每张百分比的算术平均。斜角使用真实轮廓换算有效长度;卷材按裁下净板计算。
|
|
230
|
+
它不是“比旧算法提高了多少”的百分比。标签和 Pareto 标志仅比较已搜到候选。
|
|
231
|
+
|
|
232
|
+
多方案方法不覆盖 `engine.result`,也不改写原输入和配置。选择后保留对应的
|
|
233
|
+
`plan.result`,直接调用 `ecode_cutting.exporters`;不要把 `plan.id` 当作跨任务的永久编号,
|
|
234
|
+
也不要用 `engine.export_*` 导出尚未写入该引擎的候选方案。
|
|
235
|
+
|
|
236
|
+
如果调用方已有模型列表,也可以使用不需要引擎实例的公开函数:
|
|
237
|
+
|
|
238
|
+
```python
|
|
239
|
+
from ecode_cutting import Options, Part, Stock, search_portfolio
|
|
240
|
+
|
|
241
|
+
portfolio = search_portfolio(
|
|
242
|
+
[Stock(100, count=2)], [Part(40, count=3)], Options(),
|
|
243
|
+
linear=True, max_plans=3, time_limit=1, max_searches=4,
|
|
244
|
+
)
|
|
245
|
+
assert portfolio.best is not None and portfolio.best.complete
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
完整签名为 `search_portfolio(stocks, parts, options, *, linear, max_plans=3,`
|
|
249
|
+
`time_limit=5.0, max_searches=12, callback=None)`;二维传 `linear=False`。
|
|
250
|
+
|
|
251
|
+
## 7. 常用工艺和搜索选项
|
|
252
|
+
|
|
253
|
+
可以使用 `CutEngine(saw_width=3, time_limit=5)`,也可以传 `CutEngine(Options(...))`。
|
|
254
|
+
以下列出全部 `Options` 字段及默认值:
|
|
255
|
+
|
|
256
|
+
### 尺寸、锯缝与修边
|
|
257
|
+
|
|
258
|
+
- `saw_width=0.0`:非负锯缝宽度。
|
|
259
|
+
- `trim_left=0.0/trim_right=0.0/trim_top=0.0/trim_bottom=0.0`:非负修边量,一维使用左右修边。
|
|
260
|
+
- `max_decimal_point=3`:整数 0–6,尺寸按该精度转为整数,采用 ties-to-even 舍入。
|
|
261
|
+
- `waste_size_min=0.0`:最小可接受余料尺寸约束,0 关闭;可能影响能否排入。
|
|
262
|
+
- `complete_mode=True`:单种零件完全无法装入任何原料时抛输入错误;库存不足仍返回部分结果。
|
|
263
|
+
|
|
264
|
+
### 一维型材和组合原料
|
|
265
|
+
|
|
266
|
+
- `cross_section=0.0`:型材截面高度;有非零切角时必须为正。
|
|
267
|
+
- `linear_allow_rotate=False`:允许求解器调整一维零件的掉头方向。
|
|
268
|
+
- `linear_allow_flipping=False`:允许斜角翻转。
|
|
269
|
+
- `linear_exact_angle=True`:使用严格角度匹配规则。
|
|
270
|
+
- `linear_sort_ascending=False`:指定升序排料;为 True 时保留升序约束。
|
|
271
|
+
- `linear_max_size_per_stock=0`:非负整数,0 关闭;与利用率阈值及已完成规格需求联动,
|
|
272
|
+
特定条件下允许新增规格,不能作为绝不可突破的硬上限。
|
|
273
|
+
- `linear_max_size_per_stock_threshold=0.5`:0–1 的利用率阈值,与上一限制配合。
|
|
274
|
+
- `max_size_per_stock=0`:组合原料长度规格种类策略;0 关闭组合,1 允许同一种
|
|
275
|
+
长度规格的多根组合,大于 1 限制组合中的长度规格种类,-1 不限种类,仍受实际库存限制。
|
|
276
|
+
- `allow_combine_stock_regular_and_waste=False`:是否允许常规原料和库存余料混合组合。
|
|
277
|
+
|
|
278
|
+
### 二维、卷材与布局
|
|
279
|
+
|
|
280
|
+
- `cut_direction="auto"`:`auto/vertical/horizontal`;建议使用方向设置方法。
|
|
281
|
+
- `roll_mode=False`:卷材模式,必须明确横向或纵向。
|
|
282
|
+
- `max_cut_level=2147483647`:非负最大切割换向层级。
|
|
283
|
+
- `minimize_sheet_rotation=False`:二维搜索时的方向偏好,不替代零件的旋转许可。
|
|
284
|
+
- `use_layout_minimization=False`:优先复用版式的搜索偏好,不承诺全局最少版式。
|
|
285
|
+
- `max_layout_size=0`:仅上一开关为 True 时限制每组相同布局的张数,0 不限制。
|
|
286
|
+
- `use_large_stock_first=False`:优先大规格原料的搜索偏好。
|
|
287
|
+
|
|
288
|
+
### 搜索预算
|
|
289
|
+
|
|
290
|
+
- `time_limit=10.0`:非负秒数;`None` 不设时间限制,0 立即触发时间限制。
|
|
291
|
+
- `max_attempts=4`:正整数,限制求解器内部尝试次数。
|
|
292
|
+
- `seed=0`:非负整数随机种子,固定搜索次序;预算截断点仍受机器速度影响。
|
|
293
|
+
|
|
294
|
+
布尔选项必须传布尔值。参数在创建引擎、添加输入或执行边界进行校验,
|
|
295
|
+
不能只以“构造对象成功”判断整项任务有效。特殊工艺应先用自己的典型订单验证。
|
|
296
|
+
|
|
297
|
+
## 8. 斜角、组合原料和卷材
|
|
298
|
+
|
|
299
|
+
### 斜角一维
|
|
300
|
+
|
|
301
|
+
```python
|
|
302
|
+
from ecode_cutting import CutEngine
|
|
303
|
+
from ecode_cutting.validation import assert_valid
|
|
304
|
+
|
|
305
|
+
engine = CutEngine(cross_section=10, linear_allow_rotate=True)
|
|
306
|
+
engine.add_linear_stock(100, 2)
|
|
307
|
+
engine.add_linear_part(30, angle_start=45, angle_end=135, id="斜角-A")
|
|
308
|
+
engine.add_linear_part(20, angle_start=135, angle_end=45, id="斜角-B")
|
|
309
|
+
result = engine.execute_linear()
|
|
310
|
+
|
|
311
|
+
assert_valid(result, engine.stocks, engine.parts, engine.options)
|
|
312
|
+
assert result.complete
|
|
313
|
+
print(result.layouts[0].parts[0].polygon)
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
`angle_start/angle_end` 为度数,可为 0 或 10–170;0 是默认直端,90 也表示直角。
|
|
317
|
+
存在非零角度时设置正 `cross_section`。`Placement.polygon` 保存真实斜角轮廓,
|
|
318
|
+
利用率按真实轮廓计算;名义包围盒指标另见 `nominal_fill_ratio`。
|
|
319
|
+
|
|
320
|
+
### 组合原料
|
|
321
|
+
|
|
322
|
+
```python
|
|
323
|
+
from ecode_cutting import CutEngine
|
|
324
|
+
|
|
325
|
+
engine = CutEngine(max_size_per_stock=-1)
|
|
326
|
+
engine.add_linear_stock(60, 3, id="长度60")
|
|
327
|
+
engine.add_linear_stock(40, 2, id="长度40")
|
|
328
|
+
engine.add_linear_part(90, 2)
|
|
329
|
+
result = engine.execute_linear()
|
|
330
|
+
|
|
331
|
+
assert result.complete and len(result.layouts) == 2
|
|
332
|
+
assert result.used_stock_count == 4
|
|
333
|
+
assert all(len(layout.components) == 2 for layout in result.layouts)
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
本例两条组合布局由四根物理原料组成。`Layout.components` 记录每个原料组件,
|
|
337
|
+
包括输入索引、物理实例、长度、标签、余料标志和材料。
|
|
338
|
+
|
|
339
|
+
### 卷材和尾段回收
|
|
340
|
+
|
|
341
|
+
```python
|
|
342
|
+
from ecode_cutting import CutEngine
|
|
343
|
+
from ecode_cutting.validation import assert_valid
|
|
344
|
+
|
|
345
|
+
engine = CutEngine()
|
|
346
|
+
engine.set_horizontal_cut_direction(True)
|
|
347
|
+
engine.add_stock(100, 1000, 1, id="卷材")
|
|
348
|
+
engine.add_part(50, 40, 4, rotatable=False)
|
|
349
|
+
result = engine.execute()
|
|
350
|
+
|
|
351
|
+
assert_valid(result, engine.stocks, engine.parts, engine.options)
|
|
352
|
+
layout = result.layouts[0]
|
|
353
|
+
assert (layout.width, layout.height) == (100, 80)
|
|
354
|
+
tail, = layout.roll_remnants
|
|
355
|
+
assert (tail.x, tail.y, tail.width, tail.height) == (0, 80, 100, 920)
|
|
356
|
+
assert engine.remaining_part_count == len(layout.all_remnants)
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
- `set_vertical_cut_direction(roll_mode=False)`:纵向首刀;卷材模式裁短 `width`。
|
|
360
|
+
- `set_horizontal_cut_direction(roll_mode=False)`:横向首刀;卷材模式裁短 `height`。
|
|
361
|
+
- `set_auto_cut_direction()`:恢复自动方向,同时关闭卷材模式。
|
|
362
|
+
- `Layout.remnants`:裁下净板内部的余料。
|
|
363
|
+
- `Layout.roll_remnants`:净板以外的原卷尾段,坐标相对于输入原料,已扣除截断锯缝。
|
|
364
|
+
- `Layout.all_remnants`:上述两者汇总,现有余料查询统一枚举。
|
|
365
|
+
|
|
366
|
+
卷材截断刀在 `trim_cuts` 中;图像和 PTX 描述本次裁下净板。
|
|
367
|
+
调用方可以将 `roll_remnants` 回收到自己的库存系统,库不会自动修改外部数据库。
|
|
368
|
+
|
|
369
|
+
## 9. 读取结果、索引和查询方法
|
|
370
|
+
|
|
371
|
+
`Result` 的主要字段:
|
|
372
|
+
|
|
373
|
+
- `complete/placed_part_count/used_stock_count`:需求是否全完成、放置件数、物理原料数。
|
|
374
|
+
- `layouts`:已使用原料的布局;组合任务中一条布局可能包含多根组件原料。
|
|
375
|
+
- `unplaced`:未完成需求,每条含 `input_index/count/id/reason`,与余料不同。
|
|
376
|
+
- `elapsed/attempts/cancelled/timed_out`:耗时、尝试次数、取消及超时。
|
|
377
|
+
- `fill_ratio`:真实成品总面积/已用布局总面积;`nominal_fill_ratio` 是包围盒名义面积比。
|
|
378
|
+
一维各截面不同时,需要明确按长度比较的场景使用多方案 `metrics.utilization`。
|
|
379
|
+
- `layout_groups`:相同布局分组;`to_dict()` 返回可 JSON 序列化的结果字典。
|
|
380
|
+
|
|
381
|
+
`Layout.parts` 中每个 `Placement` 提供:`input_index/instance_index/id/material`、
|
|
382
|
+
`width/height/x/y`、`rotated/flipped`、两端切角和真实 `polygon`。
|
|
383
|
+
`cuts/trim_cuts` 的每条 `Cut` 提供端点、层级、角度和 `part_indices`;
|
|
384
|
+
`part_indices` 是该布局内部关联零件的局部索引,刀线坐标表示锯缝中心线。
|
|
385
|
+
|
|
386
|
+
索引规则:
|
|
387
|
+
|
|
388
|
+
- 索引从 0 开始,标签 `id` 与整数索引分开。
|
|
389
|
+
- `stocks/parts/materials` 为输入行元组;同一输入行的数量通过实例展开。
|
|
390
|
+
- `Layout.input_index`、`Placement.input_index` 指向原始输入行,`instance_index` 区分该行的物理实例。
|
|
391
|
+
- `get_result_part(part)` 的 `part` 是需求实例的展开索引,按输入行顺序展开。
|
|
392
|
+
- 原生查询中的 `stock` 为 `result.layouts` 索引;兼容入口库存索引还包含未用库存,两者不能混用。
|
|
393
|
+
|
|
394
|
+
结果读取的完整示例:
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from ecode_cutting import CutEngine
|
|
398
|
+
|
|
399
|
+
engine = CutEngine()
|
|
400
|
+
engine.add_linear_stock(100, 2, id="原料")
|
|
401
|
+
engine.add_linear_part(40, 3, id="订单")
|
|
402
|
+
result = engine.execute_linear()
|
|
403
|
+
|
|
404
|
+
located = engine.get_result_part(0)
|
|
405
|
+
assert located is not None
|
|
406
|
+
stock_index, placement = located
|
|
407
|
+
assert placement.id == "订单"
|
|
408
|
+
assert engine.get_stock_info(stock_index) is result.layouts[stock_index]
|
|
409
|
+
for index in range(engine.remaining_part_count):
|
|
410
|
+
stock_index, remnant = engine.get_remaining_part(index)
|
|
411
|
+
print(stock_index, remnant.width, remnant.height, remnant.x, remnant.y)
|
|
412
|
+
for index in range(engine.get_cuts_count()):
|
|
413
|
+
stock_index, cut = engine.get_cut(index)
|
|
414
|
+
print(stock_index, cut.x1, cut.y1, cut.x2, cut.y2)
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
原生查询方法按用途如下,非法索引通常抛 `IndexError`,未排入的有效零件实例返回 `None`:
|
|
418
|
+
|
|
419
|
+
```text
|
|
420
|
+
get_result_part(part) / get_result_linear_part(part) -> (stock, Placement) | None
|
|
421
|
+
get_stock_info(stock) / get_linear_stock_info(stock) -> Layout
|
|
422
|
+
get_part_count_on_stock(stock) -> int
|
|
423
|
+
get_part_index_on_stock(stock, part) -> 展开的全局零件索引
|
|
424
|
+
get_part_material(part) / get_stock_material(stock) -> 材料索引
|
|
425
|
+
get_remaining_part(part) / get_remaining_linear_part(part) -> (stock, Remnant)
|
|
426
|
+
get_remaining_part_material(part) -> 材料索引
|
|
427
|
+
get_remaining_part_count_on_stock(stock) -> int
|
|
428
|
+
get_remaining_part_index_on_stock(stock, part) -> 全局余料索引
|
|
429
|
+
get_cuts_count() / get_linear_cuts_count() -> 主刀数量
|
|
430
|
+
get_cut(cut) / get_linear_cut(cut) -> (stock, Cut)
|
|
431
|
+
get_stock_cut_count(stock) -> 主刀数量
|
|
432
|
+
get_stock_cut(stock, cut) / get_linear_stock_cut(stock, cut) -> Cut
|
|
433
|
+
get_stock_trim_cut_count(stock) -> 修边/截断刀数量
|
|
434
|
+
get_stock_trim_cut(stock, cut) -> Cut
|
|
435
|
+
get_part_cuts_count(part) -> 该实例关联的主刀数量
|
|
436
|
+
get_part_cut(part, cut) -> (stock, Cut)
|
|
437
|
+
get_linear_combine_stock_count(stock) -> 组件数,普通未组合原料为0
|
|
438
|
+
get_linear_combine_stock_components(stock) -> tuple[StockComponent, ...]
|
|
439
|
+
get_linear_combine_stock_info(stock, index) -> StockComponent
|
|
440
|
+
get_layout_info(layout) -> LayoutGroup
|
|
441
|
+
get_layout_fill_ratio(layout) -> float
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
`get_part_count_on_stock()` 对应原料局部位置,再由 `get_part_index_on_stock()` 返回全局展开索引。
|
|
445
|
+
余料的逐原料索引同理。`get_cuts_count()` 只统计 `cuts`,不含 `trim_cuts`。
|
|
446
|
+
普通原料的组合计数为 0,但 `get_linear_combine_stock_components()` 仍可返回描述自身的一个组件。
|
|
447
|
+
|
|
448
|
+
常用引擎属性有 `result/part_count/stock_count/used_stock_count/placed_part_count/remaining_part_count/`
|
|
449
|
+
`layout_count/elapsed_time/is_linear_run`。其中 `stock_count` 是已用布局条数,组合任务使用
|
|
450
|
+
`used_stock_count` 统计物理组件;`layout_count` 是布局分组数,不等同多方案指标的不同版式数。
|
|
451
|
+
`get_layout_fill_ratio()` 的截面一维模式保留按剩余有效长度计算的用棒率语义。
|
|
452
|
+
|
|
453
|
+
## 10. 导出文件和内存流
|
|
454
|
+
|
|
455
|
+
原生 `engine.export_*()` 导出最近一次单方案计算结果,成功返回 `None`。
|
|
456
|
+
CSV/DXF/PTX 接受路径、文本流或二进制流,文本使用 UTF-8。
|
|
457
|
+
|
|
458
|
+
```python
|
|
459
|
+
from io import BytesIO, StringIO
|
|
460
|
+
from pathlib import Path
|
|
461
|
+
from ecode_cutting import CutEngine
|
|
462
|
+
from ecode_cutting.exporters import render_svg
|
|
463
|
+
|
|
464
|
+
engine = CutEngine()
|
|
465
|
+
engine.add_stock(100, 80, 2)
|
|
466
|
+
engine.add_part(50, 40, 8, rotatable=False)
|
|
467
|
+
engine.execute()
|
|
468
|
+
engine.export_csv("result.csv")
|
|
469
|
+
engine.export_dxf("parts.dxf", include_part_ids=True)
|
|
470
|
+
engine.export_cuts_dxf("cuts.dxf", include_cut_number=True, include_stock_panel=True)
|
|
471
|
+
engine.export_ptx("result.ptx", job_name="JOB-001", customer="客户", units=0, unit_scale=1)
|
|
472
|
+
engine.create_stock_image(0, "layout.png", max_size=200)
|
|
473
|
+
engine.create_stock_image(0, "layout.svg", max_size=200)
|
|
474
|
+
|
|
475
|
+
csv_stream = StringIO()
|
|
476
|
+
engine.export_csv(csv_stream)
|
|
477
|
+
assert csv_stream.getvalue()
|
|
478
|
+
png_stream = BytesIO()
|
|
479
|
+
engine.create_stock_image(0, png_stream, max_size=100)
|
|
480
|
+
assert png_stream.getvalue().startswith(b"\x89PNG")
|
|
481
|
+
svg_text = render_svg(engine.result.layouts[0], linear=False, max_size=200)
|
|
482
|
+
assert svg_text.startswith("<svg")
|
|
483
|
+
assert all(Path(name).stat().st_size > 0 for name in ("result.csv", "parts.dxf", "cuts.dxf", "result.ptx"))
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
`create_stock_image()` 对 `.svg` 路径写 SVG,其他路径和二进制流写 PNG;
|
|
487
|
+
需要 SVG 内存文本时调用 `render_svg(layout, linear=False, max_size=1000)`。
|
|
488
|
+
`render_png(...)` 返回 PNG 字节。不会自动创建目标父目录。
|
|
489
|
+
`create_linear_stock_image()` 为 `create_stock_image()` 的原生别名。
|
|
490
|
+
|
|
491
|
+
PTX 仅支持二维断头切割。`units` 写入格式单位标志,`unit_scale` 控制实际几何缩放;
|
|
492
|
+
材料厚度按目标单位给出,不随 `unit_scale` 再次缩放。
|
|
493
|
+
|
|
494
|
+
直接导出已选方案时使用下列函数,`result` 传 `plan.result`:
|
|
495
|
+
|
|
496
|
+
```text
|
|
497
|
+
export_csv(result, target)
|
|
498
|
+
export_dxf(result, target, include_part_ids=True, cuts_only=False,
|
|
499
|
+
include_cut_number=True, include_stock_panel=True)
|
|
500
|
+
export_ptx(result, target, job_name="", customer="", material="",
|
|
501
|
+
material_thickness=0, units=0, unit_scale=1,
|
|
502
|
+
require_all_trim_cuts=False, options=None, materials=())
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
函数从 `ecode_cutting.exporters` 导入。候选方案 PTX 导出应传入原任务 `options=engine.options`
|
|
506
|
+
和 `materials=engine.materials`,保留锯缝、修边及材料厚度信息。
|
|
507
|
+
|
|
508
|
+
## 11. 任务存档和结果存档
|
|
509
|
+
|
|
510
|
+
`engine.to_dict()/to_json()/to_xml()` 保存任务输入、参数及材料,不保存计算结果。
|
|
511
|
+
`CutEngine.from_dict()/from_json()/from_xml()` 恢复任务,需要重新执行。
|
|
512
|
+
结果字典使用 `result.to_dict()`;结果 JSON/XML 往返使用序列化模块。
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
from ecode_cutting import CutEngine
|
|
516
|
+
from ecode_cutting.serialization import (
|
|
517
|
+
result_to_json, result_from_json, result_to_xml, result_from_xml,
|
|
518
|
+
)
|
|
519
|
+
|
|
520
|
+
engine = CutEngine()
|
|
521
|
+
engine.add_linear_stock(100, 2)
|
|
522
|
+
engine.add_linear_part(40, 3)
|
|
523
|
+
result = engine.execute_linear()
|
|
524
|
+
|
|
525
|
+
job_json = engine.to_json()
|
|
526
|
+
restored_job = CutEngine.from_json(job_json)
|
|
527
|
+
assert restored_job.execute_linear().placed_part_count == 3
|
|
528
|
+
assert CutEngine.from_xml(engine.to_xml()).execute_linear().complete
|
|
529
|
+
saved_result = result_to_json(result)
|
|
530
|
+
assert result_from_json(saved_result) == result
|
|
531
|
+
assert result_from_xml(result_to_xml(result)) == result
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
`serialization.job_to_json/job_from_json/job_to_xml/job_from_xml` 也提供直接操作模型的任务存档。
|
|
535
|
+
Python 存档独立版本化;旧 CutGLib XML 只支持已实现的任务字段,不能表达的排除区域会明确拒绝。
|
|
536
|
+
|
|
537
|
+
## 12. 其他服务的业务函数调用
|
|
538
|
+
|
|
539
|
+
任务字典仅允许顶层 `stocks/parts/options/materials`,集合字段为对象数组。
|
|
540
|
+
多方案的 `max_plans/time_limit/max_searches` 单独传给方法,不作为任务顶层字段。
|
|
541
|
+
|
|
542
|
+
```python
|
|
543
|
+
from ecode_cutting import CutEngine
|
|
544
|
+
|
|
545
|
+
def calculate(payload: dict) -> dict:
|
|
546
|
+
engine = CutEngine.from_dict(payload)
|
|
547
|
+
result = engine.execute_linear_fast()
|
|
548
|
+
return result.to_dict()
|
|
549
|
+
|
|
550
|
+
output = calculate({
|
|
551
|
+
"stocks": [{"width": 6000, "count": 10, "id": "原料"}],
|
|
552
|
+
"parts": [{"width": 1200, "count": 20, "id": "订单"}],
|
|
553
|
+
"options": {"saw_width": 3, "trim_left": 5, "trim_right": 5, "time_limit": 5},
|
|
554
|
+
})
|
|
555
|
+
assert output["complete"] and output["used_stock_count"] == 5
|
|
556
|
+
```
|
|
557
|
+
|
|
558
|
+
调用方自行将返回字典交给 Web 框架、持久化或后续加工业务。
|
|
559
|
+
CPU 密集的大订单适合由调用方进程池或任务队列执行;同一引擎不能同时运行两个计算,
|
|
560
|
+
计算中也不能添加输入。仓库 HTTP 示例仅是联调工具,不属于安装包的运行要求。
|
|
561
|
+
|
|
562
|
+
## 13. 取消、超时和未完成需求
|
|
563
|
+
|
|
564
|
+
```python
|
|
565
|
+
from threading import Event
|
|
566
|
+
from ecode_cutting import CutEngine
|
|
567
|
+
|
|
568
|
+
cancel = Event()
|
|
569
|
+
engine = CutEngine(time_limit=5)
|
|
570
|
+
engine.add_linear_stock(100, 0)
|
|
571
|
+
engine.add_linear_part(10, 1000)
|
|
572
|
+
cancel.set() # 示例直接请求取消;实际由调用方设置。
|
|
573
|
+
result = engine.execute_linear(callback=lambda progress: cancel.is_set())
|
|
574
|
+
|
|
575
|
+
assert result.cancelled
|
|
576
|
+
assert sum(item.count for item in result.unplaced) + result.placed_part_count == 1000
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
原生回调接收 `Progress(elapsed, placed, requested, attempt)`,返回 `True` 取消。
|
|
580
|
+
采用合作式检查,可能少量超出预算;不是强制结束线程。取消或超时保留已有可行结果,
|
|
581
|
+
需要同时检查 `complete/cancelled/timed_out`,不要仅因函数返回就认定需求全部完成。
|
|
582
|
+
|
|
583
|
+
库存不足和参数错误的处理:
|
|
584
|
+
|
|
585
|
+
```python
|
|
586
|
+
from ecode_cutting import CutEngine, ValidationError
|
|
587
|
+
|
|
588
|
+
engine = CutEngine()
|
|
589
|
+
engine.add_linear_stock(100, 1)
|
|
590
|
+
engine.add_linear_part(60, 3)
|
|
591
|
+
result = engine.execute_linear()
|
|
592
|
+
assert not result.complete and result.placed_part_count == 1
|
|
593
|
+
assert sum(item.count for item in result.unplaced) == 2
|
|
594
|
+
|
|
595
|
+
try:
|
|
596
|
+
CutEngine().add_linear_stock(-1)
|
|
597
|
+
except ValidationError as error:
|
|
598
|
+
print(error)
|
|
599
|
+
else:
|
|
600
|
+
raise AssertionError("非法尺寸应被拒绝")
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
`ValidationError` 为非法输入;`IndexError` 为查询越界;`RuntimeError` 表示未计算或
|
|
604
|
+
同引擎并发等状态错误;`UnsupportedFeatureError` 表示尚不能表达的输入或导出能力。
|
|
605
|
+
文件/流错误按对应的 Python I/O 异常处理。生产代码按需要捕获这些具体异常。
|
|
606
|
+
|
|
607
|
+
## 14. CutGLib 手册兼容入口
|
|
608
|
+
|
|
609
|
+
```python
|
|
610
|
+
from ecode_cutting import CutGLibEngine, Out
|
|
611
|
+
|
|
612
|
+
engine = CutGLibEngine()
|
|
613
|
+
engine.AddLinearStock(100, 2, "原料")
|
|
614
|
+
engine.AddLinearPart(40, 3, "订单")
|
|
615
|
+
error = engine.ExecuteLinear()
|
|
616
|
+
if error:
|
|
617
|
+
raise RuntimeError(error)
|
|
618
|
+
|
|
619
|
+
stock, length, location, label = Out(), Out(), Out(), Out("")
|
|
620
|
+
ok = engine.GetResultLinearPart(0, stock, length, location, label)
|
|
621
|
+
assert ok and length.value == 40 and label.value == "订单"
|
|
622
|
+
query = engine.GetResultLinearPart(0, include_id=True)
|
|
623
|
+
assert bool(query) and query[0] is True
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
- `Execute()/ExecuteLinear()` 成功返回空字符串,失败、取消或超时返回错误字符串。
|
|
627
|
+
- 查询接受显式 `Out()`;不传时返回 `(成功标志, 输出值...)`,其布尔判断使用首项。
|
|
628
|
+
- 默认输出最短重载;标签可选 `include_id=True`,斜角可选 `include_angles=True`,
|
|
629
|
+
层级可选 `with_level=True`,具体重载由输出个数或显式标志选择。
|
|
630
|
+
- 兼容取消回调接收 `Out`,通过 `stop.value=True` 请求取消;不接收原生 `Progress`。
|
|
631
|
+
- 兼容库存索引包含未使用原料,`StockCount` 与原生布局索引语义不同。
|
|
632
|
+
- `Clear()` 清任务并恢复大部分选项,保留已实测的三个搜索开关和尺寸精度;
|
|
633
|
+
不同于原生 `clear(reset_options=False)`。
|
|
634
|
+
|
|
635
|
+
兼容回调的独立示例:
|
|
636
|
+
|
|
637
|
+
```python
|
|
638
|
+
from ecode_cutting import CutGLibEngine
|
|
639
|
+
|
|
640
|
+
engine = CutGLibEngine()
|
|
641
|
+
engine.AddLinearStock(100, 0)
|
|
642
|
+
engine.AddLinearPart(10, 1000)
|
|
643
|
+
|
|
644
|
+
def cancel(stop):
|
|
645
|
+
stop.value = True
|
|
646
|
+
|
|
647
|
+
error = engine.ExecuteLinear(cancel)
|
|
648
|
+
assert error == "Calculation stopped by the user"
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
不迁移旧产品的机器码、许可证注册和 COM 接口;独立包版本与原产品版本分别表示。
|
|
652
|
+
对非法输入的安全处理及合法排样可能有意不同,不能把“接口可用”理解为所有输入的
|
|
653
|
+
坐标、刀序、图片像素或导出字节与原版完全一致。
|
|
654
|
+
|
|
655
|
+
## 15. 验证、文档和发布作者
|
|
656
|
+
|
|
657
|
+
`ecode_cutting.validation.assert_valid(result, stocks, parts, options)` 检查几何、
|
|
658
|
+
数量、实例、库存、材料及切线约束;`validate_result(...)` 返回违规字符串元组。
|
|
659
|
+
这些检查证明样本可行,不证明任意输入全局最优,也不替代目标机床软件的实际导入验收。
|
|
660
|
+
|
|
661
|
+
发布元数据中的作者为 `Thorne`。作者展示字段与实际登录上传的 PyPI 账号是不同概念,
|
|
662
|
+
实际项目所有者由发布时使用的账号或可信发布配置决定。
|
|
663
|
+
|
|
664
|
+
手册随依赖包安装,可从安装后的资源读取:
|
|
665
|
+
|
|
666
|
+
```python
|
|
667
|
+
from importlib.metadata import metadata
|
|
668
|
+
from importlib.resources import files
|
|
669
|
+
|
|
670
|
+
manual = files("ecode_cutting").joinpath("USAGE.md").read_text(encoding="utf-8")
|
|
671
|
+
assert "Thorne" in manual
|
|
672
|
+
assert metadata("ecode-cutting")["Author"] == "Thorne"
|
|
673
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""纯 Python 切割排料依赖库,由调用方安装后直接导入使用。"""
|
|
2
|
+
|
|
3
|
+
from .engine import CutEngine
|
|
4
|
+
from .compat import CutGLibEngine, Out, QueryResult
|
|
5
|
+
from .portfolio import OptimizationPlan, OptimizationPortfolio, PlanMetrics, search_portfolio
|
|
6
|
+
from .models import (
|
|
7
|
+
Cut, Layout, LayoutGroup, Material, Options, Part, Placement, Progress,
|
|
8
|
+
Remnant, Result, Stock, StockComponent, Unplaced,
|
|
9
|
+
UnsupportedFeatureError, ValidationError,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__version__ = "0.2.3"
|
|
13
|
+
__all__ = [
|
|
14
|
+
"CutEngine", "CutGLibEngine", "Out", "QueryResult", "Cut", "Layout", "LayoutGroup", "Material", "Options", "Part",
|
|
15
|
+
"Placement", "Progress", "Remnant", "Result", "Stock", "StockComponent",
|
|
16
|
+
"Unplaced", "UnsupportedFeatureError", "ValidationError",
|
|
17
|
+
"OptimizationPlan", "OptimizationPortfolio", "PlanMetrics", "search_portfolio",
|
|
18
|
+
]
|