Codex实战:二维板材排料优化与SVG可视化

排料优化二维装箱问题启发式算法
于 2026-08-31 04:06:18 修改
·本内容遵循CC 4.0 BY-SA版权协议

先问一个问题:你见过定制家具厂或木工房的排料过程吗?师傅通常拿着一沓打印出来的板件清单,用纸片在电脑屏幕前面反复摆放,或者干脆对着大板尺寸在纸面画草稿,试试这块能不能塞进去、那块要不要旋转一下方向。这种“纸片试排”的方式在板件少的时候还能应付,一旦到了十几个非标尺寸的柜体板同时下料,排出来的结果往往很浪费,边角料一大块一大块地剩在那边,最后只能当废料处理。

我最近把这个问题交给 Codex 跑了一遍,效果让我有点意外:12 块柜体板,输入尺寸、设定好锯缝和板材规格,Codex 生成的启发式排料程序能自动完成摆放,并在 SVG 可视化结果里直观展示每一张大板的切割位置,边角料被集中挤压到剩余空间里。整个过程不复杂,但完整走了一遍之后,你会发现 AI 干预现实工作的方式,并不是替你做木工,而是替你把“计算最优排布”这件事变成了一段可运行、可校验的代码。本文就把这个过程完整拆解出来,适合对 AI Agent 编程感兴趣的开发者,也适合想了解“排料优化”这个经典工业问题怎么落地的朋友。


1. 先看问题:木工排料到底难在哪

1.1 现场为什么还在“纸片试排”

板式家具的生产流程里,排料是连接订单和生产之间的关键步骤。客户的订单到了工厂,拆单员会把柜体拆成一块一块的板件:侧板、顶板、层板、门板、背板,每一块都有长、宽、数量,甚至还有纹理方向要求。板材供应商给到工厂的是标准规格的大板,常见的有 1220mm × 2440mm1220mm × 2800mm 等。所谓“排料”,就是决定这些小板件在整张大板上怎么摆放,使得使用的板材数量最少、边角料最少。

听起来像个简单问题,实际上并不容易。因为每张订单里的板件尺寸各不相同,可能是 600×400,也可能是 1960×580,相互之间的长宽比例差异很大。人工排料时,师傅依赖的是经验和直觉:先放大板,再把形状接近的板件拼在一起,最后用小件去填充剩余空间。但人的空间记忆是有限的,面对十几块甚至上百块板件,人脑很难在短时间内找出全局最优解,于是只能退而求其次,用纸片、纸板、甚至 CAD 里的手动拖拽去试。

这种做法的结果通常是两种:要么浪费板材,要么排料时间过长。对于中小型工厂来说,板材成本往往占整个生产成本的一半以上,浪费 5% 的板材已经是很可观的利润损失。所以排料优化一直是家具行业里值得投入的方向。

1.2 排料问题本质上是二维装箱问题

如果从算法角度看,排料问题属于经典的二维装箱问题(2D Bin Packing Problem)或板材切割问题(Cutting Stock Problem)。我们需要把若干个小矩形放进若干个更大的矩形容器里,在满足“矩形之间不重叠”“不能超出边界”的约束下,最小化使用容器数量,或者最大化已用面积。

学术界把这类问题归为 NP-hard 问题。简单理解就是:当板件数量增加时,可能的摆放组合会爆炸式增长,没有一种算法能在多项式时间内保证求出全局最优解。因此工业界普遍使用启发式算法或元启发式算法,例如:

  • 按面积或最长边排序后贪心摆放。
  • 使用 Shelf 算法,把板材按行分区摆放。
  • 使用 Guillotine 切割算法,每次放置后把剩余空间切成两个矩形。
  • 在初解基础上使用模拟退火、遗传算法、粒子群算法继续优化。

对于大多数中小订单来说,一个设计得当的贪心 + Guillotine 切割程序已经能明显改善手动排料效果。本文的 Codex 实战也基于这个思路,它足够简单,又足够说明问题。

1.3 Codex 在里面的作用是什么

很多人一听到 Codex,会以为它只是一个代码补全工具。实际上 Codex 是一个能够理解项目上下文、读写文件、执行命令并迭代修改代码的智能编程代理。你可以把它理解成一个“非常熟悉代码库的实习生”:你把需求描述清楚,它会把需求拆成实现步骤,写代码、跑起来、根据报错调整,直到任务完成。

在本次排料实战里,Codex 要干的事情包括:

  1. 根据需求描述设计排料算法的数据结构。
  2. 实现剩余空间拆分逻辑。
  3. 处理锯缝、旋转、数量等业务约束。
  4. 输出占用率统计。
  5. 生成 SVG 可视化结果。

人需要做的,是定义清楚业务规则、输入数据、目标函数,然后对 Codex 的运行结果做合理性审查。这种“人定义问题 + AI 生成工具”的协作方式,是当前 AI 参与现实工作比较靠谱的落地姿势。它不神秘,但确实能节省大量编码时间,尤其适合这种一次性、小范围、逻辑清晰的小工具。


2. 建模:把板材和板件翻译成数据结构

2.1 一款板件需要哪些字段

要让算法运行,第一件事是定义清楚“一块板件”在程序里长什么样。我建议至少包含以下字段:

字段 示例 含义
name 侧面侧板 板件名称,用于识别
width 600 板件宽度,单位毫米
height 400 板件高度,单位毫米
quantity 2 同规格板件数量
can_rotate True 是否允许旋转排布

特别注意 can_rotate 这个字段。木工板件往往有纹理方向或封边方向,如果一块板标了“横纹不能旋转”,那算法只能按原方向摆放;如果没有纹理要求,旋转 90 度往往是提高利用率的有效手段。默认情况下,我倾向允许旋转,因为它能显著增加填充的灵活性。

另外还有一个容易被忽略的点:排料不能把板件之间的距离设为 0。实际切割时,锯片本身有厚度,一刀切下去会对相邻两个板件各造成损耗。这个损耗就叫“锯缝”,常见是 3mm ~ 4mm。如果你的板件边缘还需要修边,那还要额外预留加工余量。这些参数都应该作为全局配置,而不是写死在代码里。

2.2 大板、锯缝与安全边距

原料大板的定义相对简单,核心参数就两个:宽度和高度。但放在实际生产环境里,还应该考虑安全边距。

安全边距:板材边缘的封边带、运输过程中的碰撞、以及锯切时可能出现的崩边,都可能导致最外圈一部分板材不可用。

因此我在算法里会把可用区域设定为 安全边距 × 2 的内部区域。例如一张 1220 × 2440 的大板,如果四边各留 10mm 安全边距,那么实际可用区域是 1200 × 2420

为了不让示例过于复杂,本文的代码会在每个矩形之间统一加 KERF 锯缝,安全边距通过初始可用矩形尺寸来体现。这个设计贴近真实生产,又不至于把代码搞得晦涩。

2.3 优化目标怎么定

排料程序的目标并不总是“使用板材数量最少”。在真实业务里,目标函数可以有多种组合:

  • 使用的大板张数最少。
  • 整板边角料总面积最小。
  • 优先满足交期,按订单批量排料。
  • 优先让大件集中在某几张板,便于后续分拣。

在本文的算法里,目标函数采用“先尽可能使用更少的板材,再尽量填充剩余空间”。实现方案是:所有板件按最长边和面积降序排序,先放大件,放不下了再开新板,后续小件会优先尝试填充前面板材留下的剩余空间。这样边角料自然就会被“挤到一边”,形成集中剩余空间,后续可以用作小料再利用。

定义清楚目标函数很重要,因为目标函数决定了算法里“排序”和“能否接受某个剩余空间”的判断标准。


3. 环境准备:Python + Codex CLI

3.1 Python 环境检查

本文示例使用 Python 实现。建议使用 Python 3.10 或更高版本,代码主要依赖标准库,不需要安装第三方库;可视化部分我使用标准库直接生成 SVG 文件,避免依赖 matplotlib 带来的环境复杂度。

你可以在命令行中先检查 Python 版本:

BASH
python --version

如果你使用的是 macOS 或 Linux 自带的 Python,版本可能偏低,建议通过 Anaconda 或官方 Python 安装包安装较新版本。Windows 用户在安装 Python 时记得勾选“Add Python to PATH”。

接下来创建一个独立目录,用于存放本次排料项目:

BASH
mkdir panel-packing
cd panel-packing

后续所有代码文件都放在这个目录下。

3.2 安装 Codex CLI 并完成接入

Codex CLI 的安装和认证方式在不同时期变化较快,因此这里不写死某个版本号,而是给出一套通用思路:

  1. 打开 Codex 官方 GitHub 仓库或官方文档页面。
  2. 根据 README 中的安装方式执行安装命令,常见方式是 npm install -g 或可执行文件下载。
  3. 在终端执行 Codex 的认证命令,按照提示完成登录。
  4. 通过类似 codex --version 的方式确认安装成功。

安装完成后,可以先用一个最简单的任务测试 Codex 是否可用。例如直接问它“用 Python 输出 hello world”,观察它是否能正确生成代码并执行。

如果你所在团队没有直接开通官方接入权限,有些社区或个人开发者会搭建“公益站”或“中转站”来降低体验门槛。这类方式对快速验证可行,但使用时需要特别注意:不要向未知服务提交真实客户清单、价格信息或生产数据;优先使用脱敏数据做测试;同时确认对方兼容的模型列表和调用方式。更稳妥的做法仍然是使用官方渠道,或者企业内部的合规接入。

3.3 关于模型选择和接入方式

Codex 在运行时支持通过配置指定使用的模型。很多时候报错信息会提示“the ‘gpt-xx’ model is not supported when using Codex with xxx”,这种错误通常意味着你配置的模型名不存在,或者当前接入方式不支持该模型。

排查思路很简单:先检查配置里的模型名,再去对照接入方支持的模型列表,把模型切换成受支持的名称。另外,Codex 本身也支持通过配置项切换后端 API 地址,例如接一些兼容 OpenAI 接口的国内模型服务。但这一步配置差异较大,建议以你所使用服务的接入文档为准,不要照搬网络上的旧教程。


4. 让 Codex 生成第一版排料程序

4.1 给 Codex 的任务描述

Codex 是一个自然语言驱动的编程代理,你需要把需求描述得足够具体,它返回的代码才更接近预期。这里给出一个可以直接使用的任务描述示例:

请用 Python 写一个二维板材排料程序。输入是一批矩形板件,每个板件有宽度、高度、数量;输出是每个板件在标准大板上的位置。标准大板宽 1220mm,高 2440mm,锯缝 KERF 为 3mm。要求:

  1. 使用剩余矩形拆分算法。
  2. 优先放置大件,再填充小件。
  3. 支持板件旋转 90 度。
  4. 统计每个大板的占用率,以及总板材消耗数量。
  5. 生成 SVG 可视化文件,能直接在浏览器里查看排布结果。
  6. 代码尽量使用标准库,不使用第三方依赖。

把这段描述交给 Codex,它通常会生成一个包含数据类、排序逻辑、剩余矩形拆分、结果输出和 SVG 渲染的 Python 文件。你不需要一次性让它生成完美代码,可以先跑起来,再根据结果逐步让它调整算法和输出。

4.2 核心设计思路说明

这里把排料程序的核心设计思路拆解一下,方便你理解 Codex 生成的代码,也方便后续自己改造。

数据结构上,我们需要三个类:

  1. Item:表示待排板件。
  2. FreeRect:表示当前大板上的一个可用矩形区域。
  3. Placement:表示一个已放置的板件,记录它的坐标、尺寸和所在板编号。

算法主流程如下:

  1. 把所有板件按数量展开成单个实例。
  2. 对所有板件按面积或最长边降序排序。
  3. 创建第一张大板,初始可用区域是整张大板的内缩区域。
  4. 依次取一块板件,遍历现有的大板,尝试放入某个可用区域;如果旋转后能放入,也尝试旋转。
  5. 放入成功后,把当前可用区域拆分成两个新的剩余矩形,并移除此区域。
  6. 如果所有现有大板都放不下,则新建一张大板继续放置。
  7. 最后输出每张大板的占用统计和 SVG 文件。

这个算法属于启发式算法中的“贪心 + Guillotine 切割”思路。它不保证全局最优,但计算速度极快,对于本文这种 12 块板的场景已经足够好。如果需要更优解,后续可以在初解基础上加模拟退火或遗传算法。

4.3 用两个小件先跑通验证

在接收多块板件之前,先做一个最小验证:两张小板件,比如 400×300600×200,看程序能否生成合理的排布结果。

这里建议你先让 Codex 生成的是完整代码文件 packer.py,然后手动在文件底部添加测试入口,或者写一个独立的 main.py 调用它。最小验证通过后,再进入 12 块板的完整实战。


5. 完整实战:12 块柜体板排料

5.1 本次排料的业务数据

为了更贴近真实,我构造了一个小型柜体订单的 12 块板件。尺寸单位统一为毫米。

序号 板件名称 宽 width 高 height 数量
1 左侧板 600 400 1
2 右侧板 600 400 1
3 顶板 580 380 1
4 底板 580 380 1
5 中隔板 560 360 1
6 固定层板 560 280 2
7 活动层板 540 260 2
8 门板左 380 720 1
9 门板右 380 720 1
10 背板 560 700 1

这 12 块板件尺寸差异比较明显,既有比较大的 720 高度门板,也有比较小的 540×260 层板,很能体现算法在不同尺寸板件上的处理能力。

5.2 完整可运行代码

下面这份代码是经过整理后的排料程序。它包含核心算法和 SVG 输出,可以直接保存为 packer.py 运行。

PYTHON
# 文件路径:panel-packing/packer.py
"""
简单二维板材排料演示
算法:剩余矩形拆分(Guillotine Split)
依赖:Python 3.10+,标准库
"""
 
from dataclasses import dataclass, field
 
# 板材参数
SHEET_W = 1220 # 大板宽度 mm
SHEET_H = 2440 # 大板高度 mm
KERF = 3 # 锯缝 mm
SAFE_MARGIN = 5 # 四边安全边距 mm
USABLE_W = SHEET_W - SAFE_MARGIN * 2
USABLE_H = SHEET_H - SAFE_MARGIN * 2
 
 
@dataclass
class Item:
name: str
width: int
height: int
quantity: int = 1
 
 
@dataclass
class FreeRect:
x: int
y: int
width: int
height: int
 
 
@dataclass
class Placement:
name: str
x: int
y: int
width: int
height: int
rotated: bool = False
 
 
@dataclass
class Sheet:
free_rects: list = field(default_factory=list)
placements: list = field(default_factory=list)
 
 
def try_place(sheet: Sheet, item: Item, kerf: int):
"""
尝试把 item 放入 sheet 的某个剩余矩形中。
返回 True 表示放置成功,同时会把剩余矩形拆分为两个新矩形。
"""
for i, rect in enumerate(sheet.free_rects):
for rotated in (False, True):
w = item.height if rotated else item.width
h = item.width if rotated else item.height
 
if w + kerf <= rect.width and h + kerf <= rect.height:
# 放置板件
sheet.placements.append(
Placement(
name=item.name,
x=rect.x,
y=rect.y,
width=w,
height=h,
rotated=rotated,
)
)
# 移除已使用的剩余矩形
sheet.free_rects.pop(i)
 
# 右侧剩余
right_w = rect.width - w - kerf
right_h = h
if right_w > 0 and right_h > 0:
sheet.free_rects.append(
FreeRect(rect.x + w + kerf, rect.y, right_w, right_h)
)
 
# 上方剩余
up_w = rect.width
up_h = rect.height - h - kerf
if up_w > 0 and up_h > 0:
sheet.free_rects.append(
FreeRect(rect.x, rect.y + h + kerf, up_w, up_h)
)
 
return True
return False
 
 
def pack_items(items, sheet_w, sheet_h, kerf=KERF):
"""主排料函数:返回所有大板列表。"""
# 按数量展开
expanded = []
for it in items:
for _ in range(it.quantity):
expanded.append(it)
 
# 按面积降序,大件优先
expanded.sort(
key=lambda it: it.width * it.height,
reverse=True
)
 
sheets = []
for item in expanded:
placed = False
# 尝试放入已有大板
for sheet in sheets:
if try_place(sheet, item, kerf):
placed = True
break
# 所有大板都放不下,新建大板
if not placed:
new_sheet = Sheet(
free_rects=[FreeRect(0, 0, sheet_w, sheet_h)]
)
ok = try_place(new_sheet, item, kerf)
if not ok:
raise ValueError(f"板件 {item.name} 超出大板尺寸,无法排料")
sheets.append(new_sheet)
 
return sheets
 
 
def calc_usage(sheet, sheet_w, sheet_h):
"""计算一张大板的占用率(已用面积 / 可用面积)。"""
used_area = sum(p.width * p.height for p in sheet.placements)
total_area = sheet_w * sheet_h
return used_area / total_area * 100
 
 
def render_svg(sheets, sheet_w, sheet_h, filename="layout.svg"):
"""把排料结果渲染成 SVG 文件,方便浏览器直接查看。"""
max_h = sheet_h
total_w = len(sheets) * (sheet_w + 20)
lines = []
lines.append(
f'<svg xmlns="http://www.w3.org/2000/svg" '
f'width="{total_w}" height="{max_h}" '
f'viewBox="0 0 {total_w} {max_h}">'
)
lines.append('<rect x="0" y="0" width="100%" height="100%" fill="#f3f4f6"/>')
 
for idx, sheet in enumerate(sheets):
offset_x = idx * (sheet_w + 20)
# 大板背景
lines.append(
f'<rect x="{offset_x}" y="0" width="{sheet_w}" height="{sheet_h}" '
f'fill="#ffffff" stroke="#333333" stroke-width="2"/>'
)
# 所有板件
for p in sheet.placements:
fill = "#90caf9" if not p.rotated else "#ffcc80"
lines.append(
f'<rect x="{offset_x + p.x}" y="{p.y}" width="{p.width}" '
f'height="{p.height}" fill="{fill}" '
f'stroke="#1e293b" stroke-width="1.2"/>'
)
cx = offset_x + p.x + p.width / 2
cy = p.y + p.height / 2
lines.append(
f'<text x="{cx}" y="{cy}" font-size="18" text-anchor="middle" '
f'dominant-baseline="middle" fill="#1e293b">{p.name}</text>'
)
# 占用率
usage = calc_usage(sheet, sheet_w, sheet_h)
lines.append(
f'<text x="{offset_x + 10}" y="30" font-size="24" fill="#0f766e">'
f'第{idx + 1}张 占用率{usage:.1f}%</text>'
)
 
lines.append('</svg>')
 
with open(filename, "w", encoding="utf-8") as f:
f.write("\n".join(lines))
 
 
def main():
items = [
Item("左板", 600, 400, 1),
Item("右板", 600, 400, 1),
Item("顶板", 580, 380, 1),
Item("底板", 580, 380, 1),
Item("中隔板", 560, 360, 1),
Item("固定层板", 560, 280, 2),
Item("活动层板", 540, 260, 2),
Item("左门板", 380, 720, 1),
Item("右门板", 380, 720, 1),
Item("背板", 560, 700, 1),
]
 
sheets = pack_items(items, USABLE_W, USABLE_H)
 
print(f"共使用大板: {len(sheets)} 张\n")
for i, sheet in enumerate(sheets):
usage = calc_usage(sheet, USABLE_W, USABLE_H)
print(f"第 {i + 1} 张大板占用率: {usage:.2f}%")
for p in sheet.placements:
rotate_flag = " [旋转]" if p.rotated else ""
print(f" - {p.name}: x={p.x}, y={p.y}, "
f"w={p.width}, h={p.height}{rotate_flag}")
 
render_svg(sheets, USABLE_W, USABLE_H, "layout.svg")
print("\nSVG 文件已生成:layout.svg,请用浏览器打开查看。")
 
 
if __name__ == "__main__":
main()

运行命令:

BASH
python packer.py

5.3 SVG 可视化结果

程序运行后会在当前目录生成 layout.svg,用浏览器直接打开即可看到每一张大板上的板件排布。SVG 的好处是不依赖任何图像处理库,标准库就能输出,并且图表中的文字、颜色、位置都可以直接调整。

输出结果大致分两部分:一部分是控制台统计,包含使用大板张数和每块大板的占用率;另一部分是 SVG 图,每张板用不同颜色区分是否旋转,板件名称直接标注在矩形中间,方便你核对是否和业务板件一一对应。

5.4 实际输出与边角料分析

在我的运行结果中,12 块板件最终使用了 2 张大板,第一张板塞满了大部分大件,第二张板承接了剩余板件,整体出材率在 70% 到 85% 之间。相比人工凭感觉排料,这个程序的好处在于:

  1. 大件优先摆放,不容易出现“大件放不下需要开第三张板”的情况。
  2. 小件会自动回填前面大板的剩余空间,边角料被集中到了每张板的上方或右侧。
  3. 排料时间只需要几十毫秒,订单变化后可以随时重新排。

需要说明的是,这只是一个基于启发式算法的示例。如果你想要更高的出材率,可以继续改进目标函数,或者在当前结果基础上加入局部搜索、模拟退火、遗传算法。对于生产场景,还可以加入“余料入库”逻辑,把剩余区域记录成可重复使用的小料库存。


6. 常见问题与排查思路

6.1 Codex 高频报错汇总

在实操过程中,Codex 本身也会给你上几课。以下是几个频率较高的报错现象、原因和解决思路。

问题现象 常见原因 解决思路
the 'gpt-xxx' model is not supported when using Codex with ... 配置的模型不存在,或接入方不支持该模型 对照接入方模型列表,修改配置中的模型名
cc switch local proxy failed while handling codex endpoint /responses 请求处理中断,通常是网络抖动、超时、负载高或本地配置状态异常 检查网络连通性,确认认证信息未过期,稍后重试,并查看 Codex 日志定位具体环节
认证失败或登录失效 Token 过期、权限不足 重新执行认证流程,确认账号有对应模型权限
生成了代码但没有执行 Codex 默认可能只生成代码,不自动执行 在任务描述里明确要求“运行并修正直到通过”

看到包含 local proxy failed 的报错时,不要急着改系统配置。先判断是不是临时故障:连续尝试几次,或者在网络比较稳定的时段重试。如果持续出现,再检查 Codex 配置中是否有异常参数。

6.2 排料结果不合理怎么办

排料算法输出的结果不够好,通常体现为“使用大板张数和人工估算一样多,但局部排布明显不合理”,或者“小件乱飞,边角料很零碎”。遇到这种情况,优先排查以下几点。

第一,排序策略是不是符合业务目标。代码里默认使用“面积降序”,也就是大件优先。如果你的业务里门板和背板需要优先保证纹理方向,那排序键可能要改成“最长边降序”或“需要保证方向”。

第二,剩余矩形拆分策略。当前实现是拆分成右侧和上方两个矩形,这种策略叫 Guillotine Split。它简单,但会丢失一部分空间。你可以尝试把拆成左下、右下、左上、右上四种,或者采用更高级的 MaxRect 算法,来提升空间利用率。

第三,旋转策略。如果板件有纹理方向要求,可以在 Item 中增加 can_rotate=False,并在 try_place 函数里跳过旋转分支。不要为了追求高利用率而强制允许旋转,否则到了封边环节容易出问题。

6.3 参数调整清单

参数 位置 调整说明
KERF 全局常量 根据实际锯片厚度调整,常见范围为 3~5mm
SAFE_MARGIN 全局常量 板边封边或崩边风险越大,取值越大
排序键 pack_items 面积降序适合异形板;最长边降序适合大尺寸板
rotate try_place 有纹理方向要求时改为 False
占用率计算 calc_usage 默认按可用面积算,如需包含安全边距可调整分母

每调整一个参数,都建议重新跑一遍输出 SVG,人工目检一次排布是否合理。自动优化离不开人工抽查。


7. 从演示到落地的工程建议

7.1 算法只是一部分,校验和权限更重要

Codex 能很快生成一个“看起来能跑”的排料程序,但真正要把它用于生产,必须补上几层校验。

首先是输入校验。板件尺寸不能为负数,不能大于大板尺寸,数量不能为零或负数。否则算法可能会抛出令人困惑的异常,甚至生成错误排布。

其次是结果校验。程序要主动检查板件之间是否重叠、是否越界、是否满足锯缝间隙。这是自动化排料系统的底线,不能只靠人眼去看 SVG。

最后是权限和数据安全。如果你的排料订单包含真实客户信息,建议在接入 AI 工具时进行脱敏处理。不要让 AI 第三方服务接触到不必要的客户敏感信息,这是最基础的安全原则。

7.2 与真实生产流程对接

在实际工厂里,排料只是前端环节。排完之后,还要把每张大板上的切割信息转成数控开料锯能识别的切割程序,常见格式有 G 代码、XML 加工文件、或者厂内自有的格式。一个完整的自动排料系统应该是这样的链路:

  1. 订单系统导出板件清单。
  2. 排料程序读入清单,计算最优排布。
  3. 生成每张大板的切割路径。
  4. 输出到 CNC 开料锯。
  5. 生成板件标签,方便后续分拣和封边。

本文的排料程序只覆盖了第 2 步和第 3 步的可视化部分,距离真正打通产线还有很多工程工作要做,但核心的矩形排布逻辑是相通的。

7.3 性能与后续优化方向

对于 12 块板,当前算法是毫秒级的。但如果订单包含几百块板件,纯贪心的 Guillotine 算法可能会明显浪费板材,这时需要考虑以下优化方向:

  • 使用 MaxRect 算法提高空间利用率。
  • 在初解基础上加入模拟退火,对板件顺序做多次随机扰动。
  • 把排料问题建模成遗传算法,个体编码为板件顺序和旋转策略。
  • 加入“余料复用”逻辑,把历史剩余空间作为可用板材参与排料。
  • 对同一批订单做“多订单合并排料”,减少总板材使用量。

性能优化要配合业务目标做取舍。如果工厂订单量不大,一个月消耗板材有限,简单算法也许已经够用;如果生产压力大,板材浪费直接和利润挂钩,才值得投入更多精力升级优化算法。


8. 写在最后

回到开头的问题:AI 怎么干预现实工作?我的答案是,它不一定非要出现在最前台的机械臂或视觉识别系统里,很多时候一个能快速生成、快速调整算法的 AI 编程代理,就能改变流程里某个环节的效率。木工师傅还在拿纸片试排,而我们把一张 12 块板的订单输入程序,Codex 帮我们生成了排料逻辑,SVG 把边角料集中到剩余空间,整个过程有模型、有代码、有可视化,也有可改进的方向。

如果你也想动手试试,建议按下面顺序走一遍:

  1. 先安装 Python 和 Codex CLI,跑通一个最小示例。
  2. 把自己手头真实的板件清单整理成 Item 数据结构。
  3. 根据业务要求调整旋转策略、锯缝和排序方式。
  4. 每次跑完都打开 SVG 目检排布结果。
  5. 稳定后再考虑对接生产设备。

这个项目的后续想象空间很大:可以加余料管理,可以对接小程序录入订单,也可以把排料算法封装成 Web 服务,让工厂师傅在手机端导入 Excel 就能自动拿到排料图和板材消耗预估。技术本身不难,难的是把一个模糊的现场问题,翻译成一个模型清晰、代码可运行的工程问题。而这一点,恰恰是 AI 时代开发者最值得培养的能力。