← MetalMaster

更新日志

v0.54.0 · 与仓库 CHANGELOG.md 同一份,发布即最新

落地页开更新日志与博客;分支策略与 CI 上闸门(2026-08-17)

用户:"landing 页里面也增加一个 blog 和 changelog 的环节……都是静态的信息, 每次更新发布之前都应该把它们刷新一下"、"未来上线应该有一个 PR 的过程"、 "只有合到 main 上才能正常安装使用,其他的合到 dev 分支在测试环境做"。

助手顺手收集产品反馈(2026-08-17)

用户:"助手只需要筛选一下是不是跟产品优化有关的问题,如果有的话就直接 存下来。未来你可以通过这些反馈来持续优化。"

助手看得见缩略图了(2026-08-17)

用户:"应该把 3D 的缩略图也作为助手的上下文,可以用来判断这是个什么东西。" 模型看一眼外形,比读一堆特征数字更快认出"这是个支架/法兰/机箱":

出图墨迹一本帐(Ink);助手留痕进后台(2026-08-16 深夜)

用户:"你能不能整体调整一下,因为你画图的时候是能够知道之前的画到哪里了。" 对——此前避让是零散的:气球看 avoid、外移数字看 taken、视图名看 ink_bbox, 各记各的帐,帐本之间就是盲区。现在每个视图一本 Ink 帐(texts/segs), 画一段登一段,所有"要找地方放"的元素(气球、外移尺寸数字、剖切字母、 0,0 基准文字)对着全部已画墨迹计分选位:

门禁收紧:test_no_text_collisions_corpus 在"互压 == 0"之外加压线 ≥ 3 段 即红(全语料);test_balloon_tags_are_labels_not_sentences 钉死气球标签 是编号不是句子。变异 4 刀全红。

助手问答留痕("让这个智能体变成可用、好用、可追溯"的"可追溯"):每次 问答记 jsonl(时间/账号/页面/问题/回答/用了哪些工具/tokens/算法指纹),管理 后台新增"助手对话"节;token 用量只入日志不回前端。

出图干涉整治(2026-08-16 晚)

用户:"出图的部分问题比较多,干涉得太明显。"真图(1950×50 长轨 @1:5)实测 9 对文字互压 + 气球压几何线 82 次。逐族修根:

新语料门禁 test_no_text_collisions_corpus文字互压 == 0,全语料(判据 与气球避让同一双眼睛:sheet.svg_text_boxes/svg_segments)。压线检查本轮 不进门禁——剩余是贴线 1–3 段(制图允许)与剖切字母在阶梯剖落进几何带 (已回滚两次盲改,记为下一轮靶子)。变异 5 条 4 红 + 单元 1 红。

助手工具箱;零件出图不再驱动整文件计算(2026-08-16)

助手有工具了(用户:"要给助手更多的工具")。四件,全部只读/纯算—— 重算/导出这类动作刻意不给("替用户做主"的教训):part_brief(装配里任一件 的摘要)、feature_detail(超出截断清单的编号明细)、operations_estimate (确定性工时模型)、file_brief(目录里其他文件)。工具定义与执行只有一份, OpenAI(tool_calls)与 Anthropic(tool_use)两种线格式各自适配;单问最多 4 轮工具调用(防兜圈烧钱),单个结果截 3500 字。执行器与页面上下文同一条 纪律:只读缓存、绝不触发分析。

零件出图与主文件脱钩(用户:"/drawing?sub=0 为啥又驱动回了文件计算?")。 两处病:_maybe_offload 的 BREP 旁路明文排除了出图(kind != "drawing"), 零件出图按 48 MB 主文件的大小起整文件任务;drawing_html 的形状回退直接 load_step 整个文件。旁路对出图同样成立;回退先走 BREP 旁车(几毫秒), 拿不到才读整文件。门禁模拟"重启后 LRU 为空"的真实场景(不打空 LRU 时 get_part 顺手塞形状,回退路径永远没被踩到——变异抓出来的测试盲区)。

变异 7+2 条全红(其一以挂死形式被抓住;本轮教训:变异脚本的恢复必须放 finally——超时曾把变异态留在树上)。

copilot 五轮:回复按 Markdown 渲染(2026-08-16)

助手回复此前按纯文本渲染,模型输出的粗体/列表/表格(系统提示明说允许表格) 以源码裸露。挂件内置极简渲染器 md()先整体转义再替换(注入的任何标签 只剩文字,有变异门禁钉住),只认约定的几样——粗体、行内代码、有序/无序列表、 表格(首行表头、分隔行跳过)、特征编号链接;块级样式全套跟着走(回复气泡 关掉 pre-wrap,块标签间的换行不放大成空行)。渲染结果用 node 执行同一段 代码验证过(列表/表格/XSS 三组样例)。门禁 +1,变异 3 条全红。

copilot 四轮:目录页助手(2026-08-16)

批量管理的枢纽页也有助手了。目录页挂件是目录形态:主动开口说 "N 件未解析、M 件需人工复核、K 件出自旧算法"(用页面已拿在手里的行现算, 零额外读盘),快捷问题换成批量口径(哪些没解析/哪些要复核/整体情况); 单件口径的三问不再混进目录页。对话上下文为整批文件的状态一览 (catalog_context,只读摘要缓存,绝不触发解析)。 门禁 2 条(目录形态/空 path 路由接线),变异 5 条全红。

交互做减法:上传直达 + 装配页自动补齐(2026-08-16)

对"交互是否足够简单"的复盘,按三条主旅程数点击,修掉两步废动作:

「补算未计算」按钮保留——它还是"手动马上补"的显式入口。 门禁 2 条,变异 4 条全红。

copilot 三轮:助手主动开口(2026-08-16)

有了助手,交互的方向要反过来:不是人打开面板问"有什么要注意的",而是页面 已经知道的事由助手第一句话说出来。brief_of 在渲染时从报告确定性提取 (需人工复核 / N 条风险标记 / 工艺判定不明确 / 结果出自旧算法),欢迎语 直接列点、快捷问题换成针对性的("解释这 7 条风险标记""为什么工艺判定 不明确?");干净的页面保持通用欢迎语,不制造焦虑。零 API 成本。 件76 实测:本页有要注意的点:状态为「需人工复核」、7 条风险标记、工艺判定 不明确。门禁 2 条(主动开口/接线),变异 4 条全红。

钣金 copilot 二轮:特征编号语言 + 快捷问题(2026-08-16)

门禁 +2(编号语言/快捷问题与转义),变异 6 条全红。

导出校对牌号;两个带牌号路径的潜伏崩溃(2026-08-16)

牌号不符的缓存不许端出去。 零件缓存槽不分牌号(path#index 一个槽, 换牌号重算会覆盖),指定牌号导出时盘上那份可能按别的牌号算——铝按钢差 2.9 倍,直接落在报价上。cached_part_entry/part_report_dict 的内存与磁盘命中 都加牌号校对(不符按未命中现算);不指定牌号则沿用盘上现有的,不逼人重算。 存储结构不动、缓存不翻倍。

配门禁时挖出两个潜伏崩溃——凡带牌号的零件分析路径从来就是坏的: dataclasses 三处使用从未 import(NameError);_remember 的键解析写在 "材料进键"之前,把 0|Q235 直接 int()(ValueError)。两处都修, 变异 5 条全红。

页面 AI 助手与模型反馈(2026-08-16)

模型页面(文件页/零件页)右下角新增分析助手server/copilot.py, 结构/样式/行为/后端一个包)。两条腿,一条不依赖另一条:

安全:请求体上限(对话 64 KB / 反馈 16 KB)、登录后可用、反馈进审计日志、 对话每用户每分钟 10 条节流(按 token 计费,一个账号不该有能力烧穿预算)。 门禁 6 条(链路/退化/上下文入 system/不现算/挂件自含/节流),变异 7 条全红。

环面归回转:垫圈判回转体(2026-08-16)

件95(垫圈)曾被判"自由曲面件(铸造/模具)"——环面在面表里不记轴,永远进 不了回转分组,面积全算给"自由曲面"。而环面是解析回转面,主轴即回转轴:

全语料回归:13 件真实语料仅垫圈一件改判(freeform → turned),铜排/鱼尾板/ 件76 等全部不动;21 个合成夹具判型零变化。变异 3 条全红。

平板不扫 K 包络(2026-08-16)

K 因子只进折弯让量(develop.py 里它的两处用途都在折弯条带/补丁上)——没有 一道弯的平板,K 取什么值展开结果按构造相同,而 _apply_developed 照样把 K 区间两端各算了一遍。注释里那句"多跑两次可以忽略"对 400 孔的冲孔板不成立: 一次 0.5 s,白算 1 s。现在无折弯(bend_strips 空且 n_op/n_geo/n_roll 全零) 时复用同一个实例——区间坍缩成一个点,正是平板的真实不确定度:零。体积恒等式 自检照跑。冲孔板整件 CPU 时间 4.76 → 3.86 s(−19%)。折弯件的 K 区间照扫 (门禁两个方向各一条,变异均红)。

OCP 容器迭代的 258 倍坑(2026-08-16)

shapes_in_list 里一句 list(NCollection_List)——OCP 的迭代器绑定每步跨一次 C++/Python 边界并做类型分发,实测 1212 个两元素列表要 1290 ms;改用 Size/First/Last 是 5 ms(258 倍)。它在建邻接表的循环里(每条边一次), 是整件分析的第一大热点:冲孔板占 24%。流形几何一条边恰好属两张面, 0/1/2 元素走快路径覆盖实际全部,≥3(非流形)退回慢迭代。逐边对照新旧取法 结果一致;性能预算门禁在机器被其他项目抢占的情况下也回到绿(此前被挤爆)。

黑色等待页退役;重算播种;网格轻车道(2026-08-16)

黑色等待页整个退役(用户:"这个黑色页面应该去掉,然后融入到正常的页面中, 逐步披露计算出的结果")。三层配合:

整机网格走轻车道(用户:"整机三维网格……应该是优先级最高的,怎么会前面 还有任务在跑呢?")。准入规则收成纯函数 admit_job(唯一产地):网格任务 免名额、不免内存——名额是防"两个大件把内存吃光"的手段不是目的,网格是 正在看的那一页要的东西,不该排在批量补算后面;内存那条是硬的,读不到内存 信息也不开轻车道。维护任务不占名额(MAINT_TAG)是第一个先例,这是第二个。

交互细节:零件页「关键事实」置顶(数字先于图——点开一个零件最先要确认 "是不是这个件、多大、多厚");装配树根节点没名字时退到文件名(不再写 「(未命名)」);渐进页树根同样。

围绕 STEP 的独立工具面补齐:折弯 / 三维 / 图纸(2026-08-16)

用户定的方向:"围绕一个 STEP 文件提取的关键信息……把这些功能都做成独立的 工具。"MCP 工具面补齐为 12 个:

装配页的 BOM 表 / 装配树 / 重算按钮拆成 server/asmview.py(结构+样式+行为 一个包,app.py 3824 → 3349 行),规则零改动。

装配拆解进 MCP;导出独立成模块(2026-08-16)

MCP 终于看得见装配体。 此前把装配体喂给 analyze/classify_part,走的是 "分析体积最大件"那条路——537 个零件的机架顶着最大那块板的标签回答"钣金件"。 现在:assembly_bom 给零件清单(只读结构,48 MB 整机秒级返回); classify_partpart_index 逐件判定;不带 part_index 问装配体直接报错 并指路,不再给一个看似确定的错答案。核心层同步补 classify.classify_assembly_partclassify_step 的装配体守卫。成本如实写进文档:read_assembly 的记忆化 只收 ≤8 MB(上限是刻意的——一条缓存持有全部零件实体,不设限进程会被几何 撑爆);大装配逐件判定每次都重读文件(48 MB 实测 ~58 s),批量拆大装配 请走 Web 层的 BREP 拆件旁车。

导出拆成 server/export.py build_export(store, form, uname) 是纯函数 ——不认识 HTTP,错误走带状态码的 ExportError,CLI/MCP 以后直接调它,不必 假扮请求。app.py 里的 _export 只剩收表单、落审计、发字节三件事(app.py 3926 → 3824 行)。规则一条没改,原有导出门禁全部原样通过。

工艺类型判定独立成能力;三视图的圆画满一圈(2026-08-16)

「判断一个零部件的工艺类型」拆成可独立调用、可独立服务的能力。 新增 metalmaster/classify.py:一个 Verdict(标签 / 中文说法 / 判定依据 / 是否明确 / 竞争假设 / 正交的铜排标签 / 板厚 / 长宽高 / 标记码),三个入口 (verdict_of 从已算好的报告提炼、classify_shapeclassify_step), 并作为 classification 块随报告序列化——页面、导出、MCP 读的是同一份。 MCP 新增 classify_part 工具(stdio 与 HTTP 两条路都挂)。

没有另写一份"轻量判据"。 只跑面表+事实能省掉一半时间,但最终标签取决于 "等厚性证伪之后的改判",那一步要先有折弯数、卷圆数、翻边孔。抄近路得到的是 一个在件76 那类件上仍然回答"钣金件"的第二产地——而件76 正是要修的那件。

明确的钣金件不再输出机加工口径(用户口径)。判不准的照旧输出:两套数 同时在手上,是工艺拿不准时唯一的退路。分界线用 Verdict.confident 一个定义 (改判过 / 记了竞争假设 / 挂着质疑板厚的标记 → 不明确)。顺带把图纸标题栏的 重量从 machining_inputs 改回 dims.mass_g——质量本就只该有一个产地。

三视图:圆要画满一圈。 _edges_to_polylines 把圆弧的起终角直接取了 t0/t1, 而那是圆自身坐标系里的参数角。HLR 把整圆切成两半后,两半在各自坐标系里 都是 0→180,于是上半圈画两遍、下半圈整个丢掉——件95(垫圈 OD17/ID13)正面 那两个同心圆在图上只剩两道弧。改为按实际起点在视图里的方位定起角、按圆的法向 定扫掠方向,并剔除长度可忽略的碎弧(件95 上有一条 0.02° 的接缝边,记了它反而 让对应的边一条都不画)。

三视图共用一把比例尺projection.common_scale):原先每张各自撑满画布, 一块 100×17×2 的条在俯视与侧视上比例差近 6 倍。

导出:旧算法算过的结果直接导出、不重算(要不要更新是用户的决定),混着 两代算法的数则多一列「结果版本」逐行注明;缩略图内嵌进 Excel(xlsx 写出器 支持 drawing/media,线稿用 Bresenham 扫成 PNG,不重新投影)。

冗余清理:删掉两个全仓无调用者的函数(tessellate.tessellate_solids ——整机三维早已改走"每种一份网格 + 实例位姿";fixtures.gen.roundtrip); 展示串的拼装规则抽成 table.kind_display_of,dict 适配器与 Report 适配器共用 一份,铜排「铜排(钣金件)」与装配体短路不会再各写一遍。

判据结构化与四处判据错的根治(2026-08-10)

算法侧本波不加特性,只做两件事:把判据的结构立起来,以及修判据本身的错。 完整记录见 docs/ALGORITHM.md 的「一¾、判据的结构与这一波的算法改动」。

判据阈值收成唯一产地。「这两个方向算不算同向」原有 8 把尺子(本值、×2、×4、 裸 2/3/5/10°、dot>0.999||a·b|−1|>0.02),收成四级命名阶梯,各有物理定义, 选级别而不许乘倍率;「两个折弯角算不算同一个」按四个不同的问题分别命名;以 t 为单位的长度判据全部命名(0.95t 曾抄三遍)。守门禁止所有 *_deg 乘倍率、 parallel() 传裸数字、手搓共轴判定、核心路径的裸角度与裸 ×t 系数;绝对长度一律 带 _mm 后缀(补掉两个真实的缩放不变性洞)。

修掉的判据错:

展开几何口径修正:

验证体系(docs/ALGORITHM.md D 节): 锚点按 external / kernel / derived / frozen 分级并写进 expected.json;毛坯长度有一维 vs 二维双路径(12 件 0.00%)与 11 个夹具的手推真值;切割周长的独立裁判从 5 件 ≤2% 扩到 15 件 ≤0.5%,另加凸包 周长在 12 件上取等号;折开缝首次有独立实现互验;面板落位有等距/无缝/无叠三条直接 判据;体积恒等式从 2% 收到 1e-4(19/24 件)。每个豁免必须声明成因并断言该成因 可观测。

其它: build_developed 524 → 326 行(抽 5 个纯函数,构造级论证 + 跨树 27 件 3307 坐标点逐位一致);删 6 个无人调用的函数(含一个会引人走回头路的废弃判据)。

远程 MCP 上传、可选鉴权与局域网监听

0.30.0(2026-08-05)— 切割线明细 + 合并重复实现

切割线明细:每条线一个长度,加起来等于周长

展开图里的轮廓是采样折线(一条直边被切成十几段),直接标注没法看。新增 develop.cut_lines():按"转角小于 3° 即同一条线"把连续段并回逻辑上的一条, 每条给出长度与类型(外轮廓 / 内部开口)。

这份明细存在的意义是可核对——报价员能把每段长度加起来,对上报告顶上那个数。

合并重复实现(本轮最值得做的一件事)

第一版明细自己抄了一遍轮廓切分逻辑,结果外轮廓段合计 11780.7contour_mm 只有 2681.0——差 4.4 倍。成因是拆了每块面板的整圈, 把面板之间的拼缝也当成了切割线,而拼缝一刀都不用切。

修法不是"再对齐一次",而是只留一套实现

于是"明细加起来 ≠ 周长"在构造上不可能发生。两件实测差 ±0.02 mm。 这个项目最贵的几个 bug 都是"同一件事两套实现、各说各话",这次从源头消掉一处。

清理死代码

共减 46 行。另确认:算法代码里零件名只出现在注释中,没有任何一处按文件名分支

两个客户样例的最终核对

| | 标准答案 | 客户口径 | 差 | |---|---|---|---| | adx139058-00_5 | 2699.0 | 2698.6 | −0.4(−0.01%) | | ADX131200MJYF-00 | 4341.7 | 4408.6 | +66.9(+1.54%) |

口径构成透明:简化毛坯轮廓 + 非圆开口。自洽性:切割线明细 = 轮廓周长(±0.02), 体积恒等式 1.0002 / 1.0000。

ADX131200 剩的 +1.54% 不去凑。要定案需要客户那边的毛坯尺寸 (我们算 912.65 × 709.65,中性层法与外形法交叉验证一致: 638 + 2×(33+2.827) = 644 + 2×36 − 2×3.173 = 709.65)。

全套 940 项。

0.29.0(2026-08-05)— 客户口径换成「简化毛坯轮廓」

用户要求:先确保切割总周长合理;若仍对不上标准答案,可另开一个 2 号口径, 但 2 号也不能过拟合;若 2 号本身就更合理,就不必另开

试出来的新口径两条都占,所以直接替换,没有另开

| | 改前(真实轮廓+非圆开口) | 改后(简化毛坯轮廓+非圆开口) | |---|---|---| | adx139058(标准答案 2699.0) | −0.67% | −0.01% | | ADX131200(标准答案 4341.7) | +2.83% | +1.54% |

「简化毛坯轮廓」的定义(无阈值)

把每块面板/条带换成它的外接矩形,再求并集外轮廓。报价常用的"理想化毛坯" 口径:不数圆角的缩短,也不数凸耳与工艺缺口这些型面细节,只认骨架形状。 外接矩形是确定的,不需要"多小算小"的判断

实测 22 件:简化/真实 轮廓比中位 1.000(p10 0.996、p90 1.010)——多数件 根本不变,只在有凸耳/缺口的地方才分开,正是意图所在。

未归因的残差,明写在案

ADX131200 仍差 +66.9 mm(+1.5%),归不出具体几何项。已排除凸耳/缺口 (简化轮廓已扣掉那 123)。不为消掉它加特判——那正是本项目反复栽过的坑。 要定案需要客户那边一个信息:该件的毛坯尺寸(我们算的是 912.65 × 709.65)。

一处表述纠正

先前说"客户反推的外轮廓 3177.7 比毛坯外接矩形还小,几何上不可能"——那个 3177.7 是在「客户口径=外轮廓+方孔」这个假设下反推的中间量,不是标准答案。 假设若不成立,结论也不成立。现用口径不依赖该假设。

全套 940 项。变异验证:把简化轮廓退回真实轮廓 → 2 条红。

0.28.0(2026-08-05)— 切割周长的多口径并列 + 口径长在数据上

新增「轮廓刀路总长」列,对齐客户标准答案

对两个客户标准答案逐条比过所有候选口径,只有一条两件都落在 3% 内:

| 候选口径 | adx139058 | ADX131200 | 最差 | |---|---|---|---| | 外轮廓 + 非圆开口(不含圆孔) | −0.7% | +2.8% | 2.8% | | 外轮廓 + 全部孔(total_cut_mm) | +11.4% | +8.0% | 11.4% | | 材料轮廓 + 全部孔 | +13.8% | +8.0% | 13.8% | | 外轮廓(不含任何孔) | −0.7% | −24.0% | 24.0% | | 三维壁面 (SIDE+HOLE)/t | +14.2% | +7.9% | 14.2% | | 毛坯外接矩形周长 | −1.9% | −25.3% | 25.3% |

它有物理含义、不是尺寸阈值:圆孔冲/钻,不走切割路径;异形开口(腰形槽/ 矩形/异形)必须走轮廓刀路。这是车间按成形方式的区分,对两件是同一条规则, 没有任何逐件特判。

残差成因都已查明:adx139058 的 −18 是 R5 圆角(客户按尖角记账); ADX131200 的 +123 是 8 个建模出来的折弯止裂口(客户用简化轮廓)。

明写的不确定性:现有两件都没有大圆孔,所以本规则与「大圆孔也走轮廓刀路」 在样本上无法区分。新增测试 test_customer_caliber_beats_alternatives 比的是 各口径的相对优劣——将来若有人为凑某一件去改定义,只要另一件变差就当场变红, 比放宽容差诚实。两条变异(改成含全部孔 / 只算外轮廓)分别红 3 条和 2 条。

口径与假设长在数据上

这些数背后都有前提,最要紧的一条是「圆孔按冲/钻计,不入刀路」。读的人当场看不到, 就会拿一个口径的数去对另一个口径的答案(本项目为此来回查了好几轮)。

表头改为:短说明直接印在列名下(≤18 字),长的挂一个问号、悬停或键盘聚焦 展开浮窗。用 <button> 而非原生 title——原生的要等一秒、没有样式、也看不出 可交互;<button> 还能 Tab 聚焦(:focus-visible 同样展开)。

total_cut_mm 也补上了口径说明,并指路「若你的报价按圆孔冲/钻计,请看轮廓刀路 总长那一列」。

全套 940 项,0 失败(上一轮那次 perf_plate 超支未再出现,进一步印证是环境负载)。

0.27.0(2026-08-05)— 切割必须穿透材料

按"重点盯切割总周长"做了一次四路交叉审计(展开刀路 / 展开材料 / 三维壁面面积÷t / 内环拓扑),查出圆柱路径仍在造假特征——占孔切割长的 15.3%,多件 100%。

判据:深度 ≥ 半个板厚

实测把两个总体分得一干二净:

| 来源 | n | 深度/板厚 最小 | 中位 | <0.5t 的 | |---|---|---|---|---| | 有内环佐证的真切割 | 231 | 1.000 | 1.000 | 0 个(0%) | | 圆柱独有 | 41 | 0.015 | 0.172 | 29 个(71%) |

231 个真切割特征,深度/板厚的最小值恰好 1.000,无一例外——切割必须穿透材料, 这是物理必然,不是统计规律。而幻影特征最深的不到半个板厚(最小 0.015, 即 1.5% 的板厚)。中间空着整整一倍,取 0.5t 落在空当正中,对真切割留 2 倍裕量。

典型形态:15 mm 板上报出"深 0.5 的腰形槽"——那是棱边倒圆被凑成的。

这条与 v0.24.0 的轴向判据互不替代:那些槽的轴不平行于墙法向,轴向判据抓不到, 却占了那些件孔切割长的 100%。

效果

抽样 58 件:9 件变化(全在同一批客户来料),孔切割总量 −10.1%没有任何一件的切割长变大(正确——只该去掉假特征),无状态变化。

审计中标记的 SF26130-C201-D8 从 545.0 落到 430.4,正好等于内环法(拓扑枚举) 的实测值——两条独立的路从此对上。

一条未解决的环境问题

test_perf_plate_patterns_and_budget 在本机当前状态下超预算(16.8s vs 15.0)。 改动前后一模一样,且一小时前 v0.26.0 跑完整套时它是过的;机器 load 2.75/5 核, 整套耗时从 9 分钟涨到 18 分钟。判定为环境性能漂移,不调这个预算—— 调松它等于把一个真实信号关掉。

全套 936 通过 / 1 环境性失败。

0.26.0(2026-08-05)— 平板件的等厚性证明

上一版查毛坯利用率时留下的头号问题:一批 15 mm 板报出的毛坯只覆盖了最大那张 面,另有 20% 的料在它之外。这一版把它证明出来并说清楚。

判据:0 折弯时 V/t ≤ 最大平面的面积

平板的材料体积恒等于「轮廓面积 × 板厚」。V/t 超过最大的那张平面,就说明料跑到了 这块平板之外——它不是等厚平板,而是带台阶/凸台/筋的机加件。

无参数,且不依赖展开算得对不对(比展开自检更早一层)。倒角只会让 V 变小 (V/t < A_max),所以越界只有一个方向的成因,不存在"正常件偶然越界"。

29 件 0 折弯的真实来料实测:正常的恰好 1.000,越界的从 1.050 起, 中间是空的。命中 16 件,全部集中在同一批客户来料,其他批次与全部夹具零误报。

顺带纠正一条我先前判反的结论

先前说"展开丢面板"——错了。实测 展开自检 = A_max/(V/t) 逐件精确成立 (7 件里 6 件差 0.00–0.01%):展开正确摊出了那张面,错的是 V/t,因为 t 不是 这些件的等厚厚度。两个信号是同一个测量的两面。已写进测试钉死。

判据的前提要说全

第一版漏了两种"把料移出平面"的成形,当场在自己不适用的地方开火:

两件的置信度都被误扣到 0.52。补上前提(n_roll、翻边孔、DRAWN_RING、 预冲孔)后恢复。这不是"例外清单",是把前提说全——判据的适用范围本来就该 写在判据里。

一条待查的线索

SF26130-C201-D8展开自检1/比值 差 3.5%(其余 6 件差 0.00–0.01%), 成因是孔在两条路上记账不同(面面积已扣孔,展开净面积又扣了一次)。 记在测试注释里,另行处理。

全套 928 项。

0.25.1(2026-08-05)— 不报几何上不可能的数

全量体检(128 件缓存)发现一个定义级的错误:毛坯利用率 = 展开净面积 / 毛坯 外接框面积,净面积是外接框的子集,所以恒 ≤ 1。而 16 件报出了 > 1, 最严重的 SF26130-C3B09-D3 报 3.543

根因链一查到底: `` 板厚 6.531 ← T_BELOW_GEOMETRIC_FLOOR 已判定「低于几何下界 7.22」 ↓ V/t 虚高 展开只摊出 4 张墙面里的 1399/2708 mm²(体积恒等式 0.2822) ↓ 毛坯框随之缩小 利用率 4956 / 1728 = 3.543 `` 三个独立信号指向同一处。

处置是不报,不是夹到 1——夹到 1 等于把"展开失败"伪装成"利用率 100%": 报价员看到"不适用"会去查,看到 1.000 不会。与本项目一贯口径一致 (宁可承认量不出来,不可拿默认值冒充测量值)。新增 BLANK_UTILIZATION_IMPOSSIBLE(warn),并置利用率为不适用。

一个没有采纳的"改进"

顺带试了把板厚可行性余量从 1.2 收到 1.03(贴近恒等式硬界)。聚合指标看着漂亮: 体积恒等式平均偏离 0.0514→0.0410、利用率最大超出 2.543→0.252

但那是差生离场,不是成绩提高:7 件改了板厚的件全部退出钣金主线;在两版 都仍是钣金件的 64 件上,指标一字不差(中位 0.0002、平均 0.0410、>5% 13 件、 利用率超 1 的 16 件、最大 0.252)。已回退,不采用。

变异测试两条:关掉检测 → 2 红;改成夹到 1 → "报警而非静默夹紧"那条红 (它专门区分"置空"与"夹紧")。

全套 916 项。

0.25.0(2026-08-05)— 外轮廓假缝修复:第三次,靠结构分账过关

前两次修假缝(折弯条带只按圆弧宽度铺,两侧留出深度恰为一个折弯让量的窄缝, 被 _free_perimeter 当成外轮廓)都栽在同一处:加宽写进了材料口径, 体积恒等式当场崩(notch_ears 1.1621)。这次先查了行业惯例:止裂口(bend relief) 显式建模才是切口,否则毛坯轮廓就是干净的台阶——客户答案正是按这个口径给的。

结构:轮廓与材料从此分账

两本账各有各的裁判——恒等式在构造上不可能再被这条改动破坏。

判据:远侧线上的边段清单(无阈值可调)

从折弯展开出来的材料——无论经圆弧相连还是法兰超出圆弧宽度的自由边——根部边都 精确落在"折弯线+让量"这条线上。所以取这条线上的面板边段清单为准: 1. 段清单而非 min–max 包络——两段之间没边就是真切口,不填(notch_ears 由此免疫); 2. 只保留与条带自身轴向区间相交的段——别处恰好共线的边进不来; 3. eps 属"模型精度"类(吸收浮点噪声),与零件大小无关。

验证

全套 906 项。

0.24.1(2026-08-05)

圆柱路径越权的收尾:跨折弯线的孔要留下

0.24.0 的规则「圆柱特征若轴平行于平面墙法向、却没有对应内环,即剔除」漏了一种 情形:骑在折弯线上的孔。它的轴确实垂直于平面墙,但它把平面墙的内环撑破 (并进外环),内环法因此看不全它——那正是圆柱路径该管的。自检 hole_straddle 的孔数因此 1→0。收紧为:同时不与任何折弯面相邻才剔除。自检 16 件全部通过。

外轮廓的假缺口:第二次尝试,仍是过拟合,已回退

成因早已定位:折弯条带只按圆弧面宽度铺,两侧留下深度恰为一个折弯让量的空带, _free_perimeter 把空带的边当成外轮廓。两件客户答案都指向它 (adx139058 多 46.6、ADX131200 多 123.0)。

这次换了判据——不再无差别按并集铺,而是要求空带上下两侧都有料才补。 adx139058 从 +47.0 收到 +14.7,看着有效。但体积恒等式再次崩了notch_ears 1.0000 → 1.1621(与上一版并集方案分毫不差的同一个数)、 corner_bracket → 1.0898、U_slotted → 1.0716、welded_bracket → 1.0792。

同一个夹具、同一个数值——说明"两侧都有料"这个判据在这些件上与"并集"等价, 根本没有分辨出真缺口。第二次尝试,同样回退。

体积恒等式(展开净面积 ≡ V/t,折弯不增减材料)两次都当场抓住了它。这条判据 无参数、不可调,是这个项目里最可靠的一道闸。

0.24.0(2026-08-04)— 切割总长的无参数校验

用户报 adx139058-00_5 的切割总周长应为 2699,我们给 3072.3

先说结论:这一版没有把它改成 2699,我认为也不该硬凑

查下来问题不在周长算法。新增一条只用体积、表面积、板厚的恒等式做交叉验算:

> 钣金件的表面积只有两种来源——两张皮(各 V/t)与每道切口留下的侧壁(长 × t)。 > 于是 A = 2·(V/t) + 切割总长 · t,即 切割总长 = (A − 2V/t) / t

它不依赖展开算得对不对,也不依赖面的角色分类。对这一件:

| | 值 | |---|---| | 恒等式(只用 A、V、t) | 3100.3 | | 我们的展开口径 | 3072.3(差 −0.9%) | | 用户的标准答案 | 2699(差 −12.9%) |

孔那部分另有独立佐证:孔壁面积 816.8 ÷ 2.5 = 326.7,与我们报的孔切割完全一致

所以这个模型里确实存在约 3100 mm 的切口侧壁。 差的那 400 mm 不是算错, 是"哪些边算切割"的口径分歧——见下。

这件的几何:端板很可能是焊上去的,不是折出来的

折弯线和它本该连接的底板不在同一段区间上——这不是能折出来的法兰。 展开树因此把端板摆错了位置(还镜像了:三维 x 是 −66…15,展开图放成 −20…61), 毛坯宽度从 ~81 涨到 97.14。这件本身带着 UNFOLD_MULTI_AXIS 警告。

若把两块端板按独立下料件排除,主体毛坯的切割长是 2614.9——2699 落在它与 3072 之间。需要你确认:2699 是否只算主体、不含两块端板? 这一条我不能替你定。

顺带查出 5 件"无人复核却量级不对"的件

同一条校验在 61 件上跑了一遍:中位偏差 1.0%(说明它确实能当校验用), 但有 19 件超过 25% 的门槛,其中最严重的一件(AGS000020-19)切割长是恒等式的 9.2 倍,此前状态是 ok、一声不吭。新增 CUT_LENGTH_INCONSISTENT(warn)。

门槛取 25% 而不是 5%:恒等式的误差源是二阶的(折弯处内外弧面积差、倒角), 实测中位 1.0%、夹具最大 6%——5% 那条线会随语料漂移,"量级对不上"不会

全套 901 项。

0.23.1(2026-08-04)— 图纸的视角与字号都是错的

用户反馈"这个图不行,视角还有字号都有比较大的问题"。两处都确认是硬伤, 而且都能量化:

主视图对着零件的侧棱

ets01835 的包围盒是 359×31×500,最薄的是 y 方向。而我直接拿世界坐标系的 Z 当主视方向——正对的是 359×31 的厚度边,等于把一块大板立起来看侧棱,整张图 是废的。

改成按各轴的实际尺度排序,最薄的那条当主视方向:主视图永远正对最大的面。 这不是习惯问题——正对最大面,轮廓与绝大多数特征才同时可见且投影成真形。

同一个"10px",实际渲染成 5.6px 到 18.8px

每个视图的 viewBox 贴着自己的内容走,整个 SVG 又被 CSS 缩到格子宽度——两套缩放 叠在一起,而字号只声明在第一套里。实测七个视图的字号跨度 3.4 倍,多数小到 看不清。

改成所有视图共用一块固定画布(560×400):1 个 viewBox 单位 = 1 个屏幕像素, 字号所见即所得;几何仍按公共比例缩放并居中,跨视图能对量。同时把网格列宽从 280px 提到 420px,贴近画布宽度,避免 SVG 被二次缩小。

两条测试起初都是假绿

112 件复核:0 处尺寸落空、0 件异常、0 处几何越出画布;视图数分布不变。

0.23.0(2026-08-04)— 从 STEP 生成工程图

每份 STEP 多一个入口"工程图":自动选投影面、标注尺寸与公差、内部结构自动加剖面。

视图为什么这样选:这是一道集合覆盖题

"投影面尽可能少,但所有尺寸都标得出来"有个精确的说法——集合覆盖。 每个尺寸只在特定方向上量得出真值,于是变成"用最少的视图覆盖全部尺寸"。 候选只有三条主轴,枚举 2³ 取最小即可:精确解,无启发式,无可调参数

三条判据都是制图的定义,不是标定出来的:

| 尺寸 | 什么时候量得到 | |---|---| | 线性(沿 d) | d 躺在投影面内才是真长 → \|d·n\| ≈ 0 | | 直径(轴 a) | 顺着轴看圆才是圆 → \|a·n\| ≈ 1 | | 深度 / 折弯角 | 要横着看才量得到 → \|a·n\| ≈ 0 |

112 件实测:0 处尺寸落空,0 件异常;视图数中位是 2(68 件只需两个视图, 26 件三个,仅 6 件需要七个)。

剖面不是为了好看,是因为虚线量不了

盲孔深度、沉孔台阶、内腔——它们在任何外部视图里都只是虚线,而虚线不是尺寸 界线。这类尺寸单独归组走剖视。同一视向的合成一个阶梯剖(刀折几次穿过所有 特征),不是每处一刀。特征轴不平行于任何主轴时补斜视图——那是制图本来就有 的做法,不是变通。

一处几何关系我先搞反了:全剖视图的剖切面法向就是视图方向(顺着箭头看剖切 面),我却取了另一条轴,结果一个剖面只剩 44 段线、另一个整个切没了(0 段)。

公差:能读的读,读不到的按标准给,绝不编

112 件真实来料里带公差实体的是 0 件——连其中 14 个 AP242 也没有。所以任何 "从这个 STEP 标出公差"的说法都是编的。但图纸上"没标公差的尺寸"本就有确切含义: 按未注公差执行。于是:文件里带 PMI 就读它、注明来源是文件;读不到就按 ISO 2768 的尺寸段给,并在图纸上写明这是假设的标准等级、请按实际图纸核对。 两者在尺寸表里分列"公差来源",不混为一谈。等级可用 ?tol=f|m|c|v 切换。

可读性

同规格的孔并成一条(9×φ7,不是九条 φ7),ets01835 从 71 条标注降到 35 条; 所有视图统一比例(比例不一致就没法跨视图对量)。图上的特征号与分析报告、 三维视图里的 B/H/C 完全一致——你说"H7",三处指的是同一个孔。

全套 882 项。

0.22.0(2026-08-04)— 判据体检:按形式分类,而不是逐个调值

用户要求把整个算法再过一遍,找更稳、不会过拟合的判据。结论是:判据稳不稳, 取决于它的形式,不取决于把值调得多准。按形式分只有四类:

| 形式 | 例子 | 稳不稳 | |---|---|---| | 无量纲比值 | roll_r_over_tdeveloped_area_tol | 缩放不变,稳 | | 角度 | theta_parallel_deg | 缩放不变,稳 | | 绝对长度 = 模型精度 | coax_dist_tol_mm=0.05 | 是文件格式的属性,不是零件的,稳 | | 绝对长度 = 其他 | t_max_mm=12pattern_line_tol_mm=1.0 | 危险:零件放大缩小结论就变 |

用缩放机械地找出第四类

把 16 个夹具在 0.25×–4×(16 倍跨度)上全跑一遍,只查无量纲结论。 结果比预期好:只有一处变化——z_offset 放大 4 倍后少一条风险标注, 根源是 t_max_mm=12 这条真正的物理先验(钣金就是薄的),属正当后果。 状态、形态、折弯道数、孔数在 16 倍跨度内一字不变,已固化为门禁测试。

两处"清单会腐烂"的机制缺陷(都是我这两天造成的)

板厚的几何下界:把恒等式从"选择器"改成"验算器"

t ≥ 2V/A恒等式——两张皮的面积合计不可能超过零件总表面积。111 件里 4 件的最终板厚违反了它,其中一件走的是钣金主线(t=6.53,下界 7.22), 它的展开、毛坯、估重全建立在一个几何上证明为错的板厚上。

挑候选时用的 20% 余量(_feasible 里的 1.2)是照当时语料定的,注释写着 "1.2 落在空隙中间"——111 件重测下来那个空隙已经没有了。但我没有把 1.2 改成 别的数:那只是照新语料再标定一次,同一个错误。试着收到 1.02,7 件板厚跟着变, 而哪个对我无从验证。

所以选择逻辑一个字不动,定稿后按恒等式验算,违反就报 T_BELOW_GEOMETRIC_FLOOR 并给出蕴含的下界("板厚至少 7.22 mm"——可复核的事实)。112 件实测: 板厚变动 0 件,新增 4 条如实报告。

顺带纠正一处已被数据推翻的注释

sheetness ≥ 0.5 的判据线是推导出来的(等价于"面内尺寸 ≥ 2 倍板厚"), 这一点没问题。但注释写着"46 件真实钣金 0.667–1.00、机加件 0.31,分离很干净" ——111 件重测:钣金件低至 0.507、非钣金高至 1.186,两个总体大面积重叠, 一半以上的非钣金件在线的上面。真正分开它们的是 has_skin_pair(板厚必须是 两张皮之间的距离)。已按实测改写,并确认贴线的件会照实进决策记录并转人工。

全套 867 项。

0.21.2(2026-08-03)

0.21.1 把"在不在规格表上"整条拿掉,连一个真信号一起扔了——CI 上一条既有测试 当场变红:t=7.62 mm 的板(= 0.3 英寸整)不再被怀疑单位。它本该被怀疑。

漏掉的那个判据是"分毫不差":真的单位误标换算出来落在整数档上, 7.62/25.4 = 0.30000;而 15 mm 板换算是 15/25.4 = 0.5906,离 0.6 差 1.57%。 按规格表那个 3% 的宽容差,两者都算"命中"——这才是 17 件误报的直接成因。 新增一条严容差(0.5%)专用于换算值:分毫不差才配叫精确命中。

于是三条判据各司其职,谁也不越位:

| 情形 | 结论 | |---|---| | 读数在现实里讲不通(板厚越界 / 外形 > 3 m),换算后讲得通 | 强怀疑,转人工 | | 读数讲得通,但换算后分毫不差落在标准板厚上 | 值得看一眼,转人工 | | 读数讲得通,换算后只是"差不多"(15 mm 板这一类) | 不怀疑,仅 info |

这一版还纠正了我自己写错的一条测试:原来断言"15.24 mm 也该放过"——错的, 15.24 恰好等于 0.6 英寸,分毫不差,本来就该看一眼。把自己编的断言当成事实, 是另一种过拟合。

112 件实测与前两版相同(消 17 条误报、置信度上升、下降 0 件);全套 822 项。 教训另记一条:这轮为了省时间只跑了改动相关的测试,于是漏掉 test_process_notes.py 里那条——"跑少一点"要按影响面挑,不是按记忆挑。

0.21.1(2026-08-03)— 把 0.21.0 里过拟合的那一半拆掉

用户一句"我怕过拟合",回头审自己昨天写的东西,最可疑的一处确实是过拟合

往规格表里补 15.0 是照着数据补表

0.21.0 消掉那 17 件误报的办法,是把 15 mm 等中厚板规格补进 common_thickness_mm。 那正是"看见 17 件 15 mm 板,就往表里加 15"的形状——下一批来个表上没有的厚度, 同样的误报还会再来一次。

根子在于触发条件用错了先验。 25.4 倍的单位错误在形状上是测不出来的(缩放 不变:比例、薄板性、折弯角全都不变),只能靠绝对先验拆穿。而绝对先验有两种:

| | 稳健性 | |---|---| | 一张清单(常用板厚规格表) | 天生不完备,缺一档就是一次误报 | | 几条范围(板厚 0.3–25.4 mm、单件外形 < 3 m) | 不完备性小得多,不随来料批次变 |

触发条件改成按声明读出来的板厚与外形在现实里讲不讲得通、换算之后讲不讲得通, 不再是"在不在表上"。规格表恢复原样(仍停在 12.0),退回它本来的位置: 一条 info 提示。

验证方式是把表清空到只剩一个值,那 17 件仍不误报——证明修复不依赖表里的 任何一个数。112 件实测结果与 0.21.0 完全相同(消掉 17 条误报、置信度上升、 下降 0 件),但不再依赖我补进去的那些值。

顺带:厚度超出表的覆盖范围时,措辞从"不合常用规格"改成"超出规格表覆盖范围, 未作规格核对"——中厚板常备哪几档取决于客户的供应链,不该由这张表替他断言。

给另一个阈值上围栏

量了 270° 那条扫掠角判据在真实数据上的余量:判为"非孔"的段最大 268.5°, 判为"孔"的段最小 273.2°——只剩 4.7° 的空当。这么窄说明阈值本身不稳。

做不到稳,就退而求其次把影响范围钉死:钣金件的孔数与切割周长走内环法 (拓扑枚举,与扫掠角无关)。新增测试把阈值从 200 拉到 340,真实件上结果必须 一字不变。靠近边界的段几乎都是面(外圆边,本就不计孔数),唯一靠边界的 凹面是一块钣金件上的 φ20.5 沉孔——内环法照样找得到,不依赖这条线。

0.21.0(2026-08-03)

从"哪些件还在转人工、为什么"倒推着查,两条都是系统自己跟自己打架—— 同一份报告里两个数对不上,比只给一个错数更难查,因为读的人不知道该信哪个。

一批 17 件被当成"英寸误标"

板厚规格表停在 12.0 mm——正是旧的 t_max_mm。0.17.0 把板厚判定放宽到 25.4 mm 收下中厚板下料件,却没同步这张表,于是每一件 15 mm 板都"不合常用规格"; 再被 ÷25.4 的分支捡走:15/25.4 = 0.5906,落在 0.6 mm 规格 ±0.03 的容差里, 于是报"换算后 0.59 精确命中——单位声明存疑"。17 件真实来料因此全部转人工。

补上中厚板规格(GB/T 709 常备系列 14/15/16/18/20/22/25/25.4),并写成断言: 规格表必须覆盖到与 t_max_plate_mm 同一个范围。

补表之后反向能力掉了,测试当场抓住:0.6 mm 薄板按英寸误标读成 15.24 mm, 与真 15 mm 板只差 1.6%,而 15 mm 热轧板轧制公差本身就有 ±0.5 mm——光看板厚 这一个数,两者分不开。所以换第二个信号:单位误标会把每一个尺寸放大 25.4 倍,一件 500 mm 的板读成 12.7 m。外形不离谱时就没有理由怀疑单位。

「采用 t=11.98 mm,p=0.00」,而报告顶上写着 15.00

板厚自检改选(T_RESELECTED)取的是"两张大皮之间的实测距离",它未必在投票 候选里;而决策记录照旧按"离最终值最近的候选"填 chosen,于是报出一个既不是 最终值、概率还是 0 的选项——采用一个概率为零的项,这话本身就不成立。

改选发生时改记真正的岔路口:*按两张大皮实测* vs *按投票簇*,并写明后者是被 皮肤配对判据证伪的。另立一条通用不变式做测试:决策记录里"采用"的那个值, 必须就是报告给出的那个值,且概率不为零

112 件实测:18 件变化——17 件消掉 UNITS_GAUGE_MISMATCH、置信度上升 (0.30→0.55、0.45→0.70 这一档),1 件补上一条本就该有的决策记录; 置信度下降 0 件,新增误报 0 件

0.20.2(2026-08-03)

铣削件也能"指着号说话"

0.20.0 的特征编号只覆盖钣金件——非钣金件走的是提前返回路径,压根不跑孔识别, 于是铣削件在三维视图上一个号都没有。而用户这批件"大概率机加工"。

钣金那条孔识别在这条路上确实跑不了(它以"穿透板厚"为前提),但机加工口径 已经把孔逐个找出来了,直接拿来编号即可。为此让 machining._holes 除了按规格 归并的汇总,另出一份逐个孔的清单(带位置)——汇总那份报价够用,要在图上 给每个孔挂号就得知道每个孔各自在哪。

编号规则与钣金件同一套,两条路上的 H3 是同一种东西——用户不必先想"这件是 钣金还是铣削"再决定号怎么读。铣削件不判通/盲(没有"板厚"可比),也不给切割 周长(那是落料口径)。

111 件逐件对比:状态/形态/板厚/折弯/展开/孔数/置信度一项没动,纯属新增。

运维

deploy/restart.sh 之外,操作记一条教训:pkill -f "<模式>"匹配到发起它的 那个 shell 自己(命令行里就有那串字)。今天两次因此把自己打断——一次让站点 502。凡按模式杀进程,一律先 pgrep 拿 PID 再 kill

0.20.1(2026-08-03)

「什么是孔」全系统只该有一个答案

补完编号后顺手对了一下两条口径,发现同一件上同时成立两句话:真实来料 SF26130-C3B05-D1 的通用几何口径说"孔总数 5",机加工口径说"0 个孔"。 翻出来看,那 5 个是 27°–44° 的棱边倒圆——凹圆柱不假,孔却谈不上。

这是今早在 machining.py 里修过的同一个毛病(圆角当孔),只是留在了另一个模块: facts.refresh_cylinders每一张非折弯圆柱面都计入,凹的叫"孔"、凸的叫 "凸台",不看扫掠角。现在两处共用同一个函数 machining.wrapping_fids(), 判据只有一条:包不包住轴。

判据本身踩了两个坑,都是量真实数据才发现的: 1. 不能逐面判——不少 CAD 导出把整孔沿接缝劈成两个 180° 半圆柱,逐面看 都不到 270°,一整孔凭空消失。所以按同轴归并后求扫掠角之和。 2. 同轴还不够,还得首尾相接——SF26130-C3A01-1-D3 上四处 R0.3 倒圆共用 一条轴线,位置分散在 ±31 与 ±40 四段,各扫 115°;只按同轴求和是 461°, 凭空凑出一个"孔"。按轴向切成连续段逐段判(与 _holes 同一条规矩)。

112 件实测:45 件的孔总数变了,全是非钣金件;钣金口径的孔数逐件一字未动 (钣金件走的是钣金那条孔识别,本就不受影响)。改完 50 件非钣金件里两个口径 逐件一致,其中 3 件有孔。列里也不再把倒圆写成"孔",改叫"圆角"。

0.20.0(2026-08-03)

每个特征一个号,图上表里指的是同一个东西

此前只有折弯有编号(明细表里那列 #),孔与切割只有"按类型×规格分组"的汇总 ——想指着说"这个孔"就没法说。现在三个系列各带前缀:

| 前缀 | 指什么 | 为什么单独一列 | |---|---|---| | B | 折弯 | 号沿用原来的 #,不重排——用户已经在用它 | | H | 孔(圆孔/沉头/沉孔/盲孔/翻边) | 车间按规格换冲头、换钻头 | | C | 切割轮廓(腰形槽/矩形/异形) | 走轮廓刀路,与钻孔是两件事 |

排序不用检出顺序(那会随实现细节漂移),而是沿零件最长边从一头数到另一头 ——跟人拿着图纸从左往右点数是同一个顺序。同一份文件反复解析,号一模一样; 这是编号唯一的用处所在,也是测试里钉得最死的一条。

三维视图上的号

顺带

编号写进落盘的报告 JSON,因此 labels.py 计入缓存指纹。(起初把它排除了, 理由是"编号只是呈现"——那是错的:排除意味着改了排序规则后,旧缓存里的号是老的、 新算的是新的,同一个号在两件之间指向不同东西,恰恰把编号唯一的用处毁掉。)

0.19.0(2026-08-03)

折弯线绕成一圈的,压弯做不出来

用户报 ETS01835 多了两道折弯,指的是"一个大孔的两条横边"。查下去是一个腰形 大孔四周翻了一圈边:两条横边(内 R2、长 70)+ 两端两段半圆(内 R35),中间由 6 个圆环角面接起来,围成一个闭环。

四段逐个看没有一处可疑——都满足 Δr=t 同轴对,都通过轴向闸门(轴与相邻墙 法向夹角 0°,标准折弯特征)。只有连起来看才露馅。所以新判据不看角度也不看 半径(那两样与真折弯一模一样,本来就分不开),只看拓扑:

> 压弯的折弯线是一条直线,两端到料边为止。想把同两块板在四处折起来还围成 > 一圈,压弯做不到——那要求板料自己断开再接上。能围出闭环的只有模具。

把折弯面与圆环角面连成图,连通块里边数 ≥ 点数即含回路 → 判为模具拉伸/翻边 (闸门 G1,排在所有单点判据之前,因为它是唯一逐个看看不出来的)。

一并修掉的连带错误:

不计折弯不等于不要钱:翻边是独立成形工序、要模具,改报 DRAWN_RING(info)。

108 件真实来料逐件对比:只有这一件变了,其余板厚/折弯/展开/孔/置信度一字未动。 另有一条专门防过拟合的测试:角件、U 件、Z 折、包边、圆孔翻边、卷圆件共 8 个夹具 的折弯道数,逐个按改动前实测值钉死——角件的两道折弯在角上由圆环面连着, 拓扑上离"闭环"只差一步,是最容易被误伤的形状。

详情页上的重算/翻页全线 404

页面里的相对路径按服务根算,账号 admin 的件出来是 admin/x.stp;而 resolve() 把它接在 root/admin/ 后面找 root/admin/admin/x.stp。目录页的按钮 没事(它另算了一份正确的 rel),所以从目录页点进详情一切正常,只有详情页自己 生成的链接是坏的。抽出 _Store.url_rel(),两处说同一套话。

重启脚本

deploy/restart.sh。此前手敲的 pkill -f "m server ..."匹配到发起重启的 那个 shell 自己(命令行里就有这串字),新进程还没起来旧的连同脚本一起被打掉 ——站点直接 502。改成按端口找 PID。

0.18.1(2026-08-03)

把 0.18.0 的机加工口径放到 108 件真实来料上量了一遍,量出两处硬伤。

凹圆柱不都是孔

折弯内圆角、棱边倒圆同样是凹圆柱,而且又长又细:一条 200 mm 长的 R3 折弯被算成 "φ6 深 200 的孔",深径比 33——在 2 mm 板上纯属胡说。真实来料里 58 件钣金有 41 件被报「深孔」,最离谱的一件算出深径比 536

判据是包不包住轴线:孔壁绕轴一圈(拆成两个半圆柱时合起来也是一圈), 圆角只扫过约 90°。按同轴各段扫掠角之和卡 270°。修完之后:钣金件的最大深径比 从中位 23 降到 0.37——2 mm 板上的 φ8 通孔本来就该是 0.25。

顺带修掉同一处第二个错:同轴 ≠ 同一个孔。折弯件两片法兰上对齐的两个孔, 轴线是一条、中间是空的,整段跨度当深度自然荒唐。只把首尾相接的段并成一个孔。

一个 90% 都触发的提示不是信号

改完在 108 件上重测:钣金件 0 条机加工提示,铣削件内圆角 16%、朝向数 4%、 去除率与深孔 0%。去除率一条这批没触发是对的——它们实心得多。

三处修复各有一条回归测试,逐条还原验证过会红。

0.18.0(2026-08-03)

用户说这批件"大概率机加工,但说不准"。既然说不准,就不押注——把机加工那条 路补齐,两套口径并行给。

机加工口径(metalmaster/machining.py

钣金件有一整套口径(板厚/折弯/展开/切割长),而铣削件此前只拿得到外形、体积、 估重——不足以报价。铣削的成本动因是另一套,现在都给:

| | 为什么是它 | |---|---| | 毛坯块 长×宽×高 + 毛坯重 | 买多大一块料——材料成本按它算,不是按成品重 | | 去除率(削掉的/毛坯) | 铣削机时的主要动因 | | 加工面朝向数 | 复杂度下限。长方块基线是 6(上下左右前后) | | 最小内圆角 | 卡住能用的最小刀具直径——越小刀越细、走刀越慢 | | 孔清单:直径×深度×深径比 | 深孔要啄钻、要加长刀,与浅孔不是一个价 |

四条提示(去除率 >80%、内圆角 <R1、深径比 >5、朝向数 >6)都是加价项信号, 一律 info、不扣置信度——它们不是"分析出了问题"。

这套数不依赖"是不是钣金",任何零件都算得出。工艺判不准时,两套数同时在 手上,比逼着人二选一有用。可作表格列、可导出。

一次校准

"加工面朝向数"最早按 >3 报警,结果一块平板都触发——平板、L 件都恰好是 6, 那就是任何长方块的六个面。等于没信息。基线改成 6,超过 6 才说明有斜面/多向 特征。写成断言,免得日后又收紧。

别把默认值当结果(终端表)

一件铣削件此前顶着"钣金报价统计表",并写着"折弯 0 道、孔切割周长 0 mm" ——那些字段停在流水线的初始值上,读的人会当成"一块没折弯的钣金"。这与网页表格 里"不适用"那次修的是同一个问题,终端渲染器漏掉了。现在标题随口径走, 钣金专用行(折弯/孔切割周长/孔阵列)在非钣金件上不再印。

0.17.0(2026-08-02)

站在"这批图纸要拿来报价"的角度回看,发现板厚被测错了——而这是最坏的一种 错:数看着完全正常。

顺着一个问题查到底

62 件待复核的件里,报价要的数其实齐全(板厚/外形/估重/孔数/折弯数 62/62), 只有切割总长 0/62。顺着这条线查:切割长来自展开轮廓 → 展开说"无面板可展开" → 面板配对为 0 → 在检出的板厚上根本配不出两张皮

再往下:一件 47×32×20.6 的料,两张大面实际相隔 15.00 mm,V/15 ≈ 大面面积 (1563 vs 1499),是块规规矩矩的 15 mm 下料件。而工具报的板厚是 8.72 —— 某个台阶/槽宽的间距。板厚一错,材料规格、毛坯、切割长全跟着错。

根因是产品假设t_max_mm = 12.0("钣金"的习惯口径)。用户这批 15 mm 的件 从一开始就不在候选区间里,通道 A 只能在 12 以内挑一个。

两处修复

板厚必须是"两张皮之间的距离"facetable.has_skin_pair)。通道 A 统计所有 平行平面对,台阶深、槽宽、凸台高都在投票里,碎面一多就能盖过真正的两张大皮。 选出的 t 上若配不出一对相对的皮肤面,就按"皮肤法"重新量。皮肤不一定是平面—— 卷圆件的两张皮是同轴圆柱(通道 B 的 Δr=t),这条也认。

厚板可表示:新增 t_max_plate_mm = 25.4(1 英寸),只用在皮肤法兜底那条 路。不直接抬高 t_max_mm——它是绝对长度,抬高会让更多无关间距进入投票, 结论就跟零件的绝对尺寸挂钩了(同一个件缩小一半结论会变,均匀缩放的 metamorphic 门禁当场抓出)。兜底那条路要求先配得出皮肤,无关间距进不来。

结果:这批件的板厚 52/65 稳定读出 15.0

一次撤回,记在这里

中途加过一条"证据 argmax 否决":_kind_evidence 已经算出各假设能解释的表面积, 谁强听谁的。它在几件实心块料上看着很对(真实钣金件里最强对手/钣金证据 ≤0.10, 块料 1.60,隔着 16 倍),却把 roll_section(卷圆件)判成了回转体——卷圆的 钣金件本来就有大量共轴回转面,"看着像回转体"是它的正常样子。

三个数据点上成立的规律,不足以推翻一个成立的分类。已撤回,并留了反向断言。

全语料 112 件

全套 764 项。

0.16.0(2026-08-02)

用户传上来 65 件真实生产图纸(SF26130 批次),结果是 11 件直接崩、17 件 「分析失败」、零件自动通过。逐个查下来是三个互相独立的洞,都不是"这批件特别 怪",而是算法与语义本身有问题。

1. 11 件崩溃:采用项被自己的噪声闸滤掉了

from_massesmin_share 是给噪声候选设的闸。厚板的平行面距离簇很多, 流水线最终采用的那个未必票数最大——占比低于 5% 就被滤没了,随后"采用项必须在 候选中"的不变量直接抛 ValueError,整件分析失败。

改为:先按闸筛,再把采用项补回来。补回之后仍找不到,才是真正的标签词表 不一致——那是代码 bug,仍然抛。(这条保护不能连着一起弄没,反向断言钉住。)

2. 17 件「分析失败」:低置信度 ≠ 失败

_score 的注释写着 failed = "拿不出结果",代码却只按置信度阈值分档。于是一批 11 mm 厚板——板厚、外形、孔全都算出来了,只因决策记录多、扣分多——被标成 「分析失败」。用户看到的是"这工具在我的图纸上跑不动",而答案其实是全的。

现在三个词各归各位:failed 只留给真的没有结果(文件读不出形状、或连外形都 没有);有结果但把握不够一律 needs_review,让人去看那几条标记。

3. 形态判定:薄板性只是准入,结论要服从证据

一件 37×19×17 mm、「厚 11.8 mm」 的块料,薄板性 0.53 勉强过闸就被判成钣金件 ——尽管它自己的棱柱证据(1409 mm²)比钣金证据(879)还大_kind_evidence 早就把各假设能解释的表面积算出来了,结论却没用它。

改为证据 argmax。分离很干净:真实钣金件里「最强对手/钣金证据」≤ 0.10 (多数 0.02),这块料是 1.60,中间隔着 16 倍——所以这是 argmax,不是调出来的 阈值。

全语料(112 件)验证

三处修复各做了突变验证。新增 tests/test_real_batch_regressions.py 9 项, 全套 763 项。

0.15.0(2026-08-02)

补上一个明显的窟窿:没法删文件

支持 ZIP 之后这事变得要紧——一次拖进来几十件,传错一个包就只能一直挂着。 origins.prune() 当初就是按"文件会被删掉"写的,但系统里根本没有删除入口。

删除

工具条上加「删除」(勾了文件才可点)。不可撤销,所以三件事都做到:

1. 只删得掉自己的——路径一律过 store.resolve,越权与穿越在那儿就拦住了; 2. 连带清干净——报告缓存、目录页摘要、磁盘结果、来源台账一并清除。留着任何 一份,下次访问就会拿到"已经不存在的文件"的旧结果,比不删更糟; 3. 留痕——进活动日志(删除是最该留痕的操作)。

点删除会先弹确认,并把具体文件名列出来(最多 8 个 + 总数),不是只给个数字。

顺手修掉一个正筛着时才会犯的错

表头那个「全选」勾的是所有行,包括被筛选隐藏的。之前只影响导出(多出几件 屏幕上没显示的),现在有了删除就要命了——会直接删掉看不见的文件,且不可撤销。 改为只作用于看得见的行;半选状态的计算也跟着只数可见行。

0.14.0(2026-08-02)

ZIP 来源可追可筛;README 与介绍页按现在的产品形态重写。

「这个包里有哪些图纸」

上一版把目录并进了文件名,但包名丢了——传完就查不回"订单2026A.zip 里到底 有哪些件"。(包名不适合再塞进文件名:名字会长得没法看,而且常与顶层目录重复。)

新增来源台账 server/origins.py:每落一件记下来自哪个包、包内原始路径、 上传时间,按账号一本 JSON,落盘 0600。于是:

未解析的件也显示来源列:刚传完一整包还没来得及解析时,"这件是哪个包来的" 恰恰是最先想看的。只有真正需要分析才得出的列才留空。

分层:系统层自己的列

"从哪个 ZIP 来"是上传时的事实——计算包只拿得到一个 shape,永远不可能知道, 所以不塞进 metalmaster/schema.py。新增 server/columns.py 把计算包的字段目录 与系统层这几列拼成一份,界面与导出都走它,两处不必各写一遍。

文档

0.13.0(2026-08-02)

支持上传 ZIP:自动解包、挑出 STEP、把目录并进文件名

订单A/支架/左板.step订单A__支架__左板.step。系统里平铺存放,所以来源 必须写进名字里——否则一包传上去,谁是哪个单子哪个部件就全糊了。列表里、导出的 表里都一眼看得出。

上传口按内容判而不只看后缀(PK\x03\x04),有人把 .zip 改名成 .step 传上来也认得出,不会当成一个坏 STEP 存下。

三类雷,逐个拆

路径穿越(Zip Slip):条目名写成 ../../etc/x.stepC:\... 就能解到 目标目录外面。这里从不把条目名当路径用——只取各级名字、逐级清洗、再拼成 一个平铺文件名。穿越在结构上就不可能,而不是靠事后检查漏没漏。7 种写法逐个测。

解压炸弹:条目数、单件大小、解压总量三道硬闸,任一超限整包拒绝—— 不是"解一半",半包数据比没有更坏。

压缩比这条最值得说:最早写成"超过 300 倍即拒",结果一个 1.5 MB 的普通 STEP 压了 339 倍当场被判成炸弹。STEP 是高度重复的文本,几百倍再正常不过。有害的 是绝对膨胀而不是比例——所以比例只对大文件(>64 MB)生效,真正兜底的是总量。 这条取舍写成了断言,免得日后有人"顺手收紧"又误伤真实图纸。

文件名编码:包里若没置 UTF-8 标志,zipfile 按规范退回 cp437 解, 中文就成了 ┴π╝■/╓º╝▄A.step。判据很干脆——真正的中文名 encode 回 cp437 会 失败(cp437 里没有汉字),所以能编回去的就是被误解过的字节,重新按 UTF-8 / GBK 解一遍。GBK 那条只在解出汉字时才采纳,否则 café.step 这种也能编回 cp437,硬解会把好名字改坏。

其他

tests/test_unzip.py 36 项 + 端到端 5 项。全套 740 项。

0.12.2(2026-08-02)

上一版新加的版本号断言用了 tomllib —— 那是 3.11 才进标准库的,而本项目 requires-python = ">=3.10",CI 的 py3.10 作业当场挂掉(py3.12 是绿的,所以 本地跑不出来)。改成直接读文本,与 tests/test_layering.py 里读 pyproject 的 做法保持一致。

0.12.1(2026-08-02)

修 0.12.0 里我自己造的两个问题。

设计令牌被误还原,页面引用了 19 个未定义变量

0.12.0 做视觉时,为验证"改样式不掉缓存"临时改了 htmlreport.py 一行, 验证完用 git checkout 还原——连同那次未提交的设计令牌重写一起还原掉了。 于是发出去的版本里,server/pages.py 引用着 19 个不存在的 CSS 变量。

浏览器对无效值的处理是"当这条声明不存在",所以不报错、不空白,只是背景、 圆角、阴影统统悄悄消失——看着就是"样式变丑了"。lint 和渲染测试都抓不到。 令牌已恢复,并补上两条断言:用到的变量必须都有定义深色模式必须覆盖 全部颜色令牌(漏一个就会在深色下留一块浅色)。

发版不该把分析结果全废掉

__version__ 原先在 __init__.py 里,而 __init__.py 在代码指纹内——于是每 发一版,几十件的分析结果全部作废。版本号跟计算结果毫无关系。

单拎到 metalmaster/_version.py 并在指纹中排除。pyprojectattr 也要跟着 指过去:setuptools 是静态读取的,跟不进 from ._version import __version__ 的转发(指错了装包时直接 AttributeError),这条也写了断言。

全套 699 项。

0.12.0(2026-08-02)

视觉整体过一遍;顺手修掉一个每次调样式都会咬人的设计缺陷。

先修缺陷:调个样式不该把几十件分析全废掉

落盘缓存的钥匙里带着 metalmaster//*.py 全部**源码的哈希——包括 htmlreport.py 这种纯渲染模块。于是每次动一下 CSS,43 件的分析结果全部作废、 要重算一遍。这次做视觉正好撞上。

指纹改为只盯会改变落盘内容(报告 JSON + 缩略图)的模块。排除掉的是 htmlreport / viewer / cli / mcp_server / table / schema / fixtures ——前两个是渲染,中间两个是入口,后面两个是读取时才作用的(落盘存的是 status: "ok" 这类原始枚举,中文标签和字段目录在读出来时才套上去,改译名下次 读就变了,不必重算)。

方向仍然是安全的那边:新增模块默认计入指纹,忘了登记的代价是多算一次, 而不是拿旧版本的数糊弄人。12 个模块逐个参数化测试,正反都钉住。

(本次因为指纹口径本身变了,缓存不可避免地失效一次;之后调样式不会再掉。)

视觉

设计口径:一套灰阶 + 一个强调色(烧橙)。强调色只出现在可操作需注意的地方——链接、主按钮、选中态、告警;数据本身一律中性色, 免得一屏都在喊。浅色底改成暖白 #fcfcfb 而非纯白,深色底带一点蓝, 长时间看图纸不刺眼。新增 --surface/--border-soft/--shadow 等层次令牌与 统一圆角。

0.11.0(2026-08-02)

交互优化,按"实际用起来卡在哪"来定,不是按"该有什么功能"。

列表页:43 行靠滚是找不着的

详情页:八九节、上百屏

0.10.1(2026-08-02)

挂上域名(HTTPS 反代)后的收口:网络面收窄、访客落到介绍页、标签页图标修好。

网络面:后端不再直接对外

挂了 metalmaster.ymp1.yuanmu-ai.com 之后,后端没有任何理由再监听 0.0.0.0。 改绑 127.0.0.1,由 nginx 终止 TLS 后转发。防火墙本来就只放 80/443, 现在即便规则被清掉,进程也只在回环上。部署配置连同说明收进 deploy/

有了可信反代,MM_TRUST_PROXY=1 才第一次成立——审计日志里终于是真实客户端 IP,而不是清一色 127.0.0.1。(默认仍关闭:没有反代时采信 X-Forwarded-For 等于让任何人往审计日志里灌假 IP。)

反代侧还补上了应用不方便做的三件事:Secure cookie(proxy_cookie_flags)、 /login 限速 10 r/m(把爆破挡在 Python 之外,顺带防"用 PBKDF2 烧 CPU")、 HSTS。上传体积与 MAX_UPLOAD_BYTES 对齐到 200 MB,免得大 STEP 被 nginx 抢先 413;读超时 3600s,因为 STEP 解析是同步的。

访客落到介绍页

未登录访问 / 现在给介绍页,而不是把人一脚踢到登录表单——挂上域名之后, 第一次来的人打开的就是根路径。安全性不变:介绍页纯静态、不碰任何数据; 其余路径(/api/*/part/admin、导出)照旧一律拦下,测试里逐条钉住。

标签页图标:两处原因都修了

1. data URI 里留着裸空格——有的浏览器直接当无效资源丢掉,图标就一直不出来。 现在整段 percent-encode。 2. 详情页(报告 HTML)压根没有 favicon。用户看的多半就是这个页面。

顺带把标志收进计算包的呈现模块(htmlreport.logo_svg / FAVICON / HEAD_ICON), server 直接取——之前 server 里另写了一份,是第二个真相源。报告页是自包含 HTML, 图标同样用内联 data URI,离线发出去也能显示。

0.10.0(2026-08-02)

四件事:非钣金件的数据口径、表格格式、解析结果落盘、品牌与落地页。

非钣金件:把"不适用"和"0"分开

一块铣削件走不到钣金流水线,于是 bend_counts 停在初始值 {n_op: 0, …}thickness_mm 停在某对平行面的间距上。照原样填进表格,那一行会写着 "折弯 0 道、板厚 5.88"——读的人当成"一块没折弯的钣金"。喂给报价的 CSV 里 尤其危险:0 是会被求和、被筛选的。

新增哨兵 schema.NA,三种"没数"分开呈现:不适用 / —(没值) / 0。 表格里灰底斜体、排序沉底;CSV 与 Excel 写"不适用"而不是留空。

但"孔总数"不该不适用——铣削件当然有孔。分组级的一刀切太粗,改成字段级: 孔总数 / 不同孔径数 / 孔规格分组退回通用几何那份同轴圆柱清单(钣金件上这条 与钣金口径的孔数相互印证,都是 6);真正落料/冲床专有的孔切割周长、孔阵列才标 不适用。另新增两个通用字段(孔状特征数、回转特征清单),非钣金件那一行不再几乎 是空的;面数也改为在通用几何里兜底。

表格格式:每列自己对齐

解析结果落盘:重启不丢,算法一改自动作废

内存缓存进程一停全没,所以每次更新服务,目录页又全变回"未解析"。新增 server/diskcache.py:结果写成明文 JSON,每条带一把四段钥匙—— 计算代码指纹metalmaster/**/*.py 全部源码的哈希)、配置指纹、源文件 大小+mtime、格式版本,全对上才认。

那个源码指纹是整套设计的支点:改界面不掉缓存、改算法必掉缓存自动成立, 不依赖谁记得手动清。缓存用错比重算慢糟得多——读的人不会知道自己看的是上个版本 算出来的数。「重算」会连磁盘那份一起清(否则清了内存又从磁盘读回旧结果, 重算就成了假动作)。落盘 0600、不进 STEP 清单、写失败不影响分析。

/api/analyze 与导出改走磁盘:重启后导出几十件是秒出,不再重算一遍。

列表页缩略图

每行一张等轴测消隐线画。详情页那套写法一张 90 KB,几十行同屏会把页面撑爆; 列表这版只画可见轮廓、坐标归一到 100 格取整去重、所有折线并成一条 path, 93.9 KB → 5 KB。跟着结果一起落盘,重启后照样有。

标志与落地页

标志是一条等厚料带折出来的 M:折弯半径是圆角接头,料厚是线宽——正是本工具 读几何的方式(看板料截面,而不是看形状像什么)。内联 SVG + currentColor, 浅色/深色自动跟随,同一份还当标签页图标。assets/ 下另有带展开虚线的版本与 横版字标。新增公开介绍页 /welcome(纯静态,在登录闸门之前,不碰任何数据)。

全套 680 项。

0.9.0(2026-08-02)

界面按"统一、简洁、清晰"重做目录页,并把导出打通到逐列与 CSV。

一份字段目录,同时管"看到什么"和"导出什么"

目录页的列与导出的列现在是同一份东西——都来自 metalmaster/schema.py 的 60 个字段。点「列」从 9 个分组里逐个勾选,勾完即刻决定表格显示与导出内容, 不会再出现"表里的数和导出的数不一样"。

导出:逐列 + CSV

列表页真的能"一屏看清"了:摘要缓存与报告缓存分家

报告缓存每条含完整 HTML(真实件 0.5 MB),所以上限只有 24 条——超过之后先分析 的件在目录页又变回"未解析",列表页等于没数可看。而目录页只要那一行扁平数据 (几百字节)。两者拆开:报告缓存照旧 24 条,摘要缓存放到 5000 条。实测分析 30 件 (报告缓存只有 24),目录页 30 件全部有数。

另加「解析全部(N)」:顺序解析尚未解析的件并显示进度(第 i/N 件 + 件名)。 串行发请求——服务端 OCCT 本来就是串行的,并发只会排队还看不出进度。

顺带

默认列此前在 JS 里硬编码了一份,是第二个真相源;改为渲染时从 schema 注入。 JS 语法门禁也改成检查真正下发到浏览器的那份(含注入结果),而不是模板。

0.8.4(2026-08-02)

一件"需要人工复核"的平板(AGS000190-01)查下去,牵出四处同一类根因的缺陷: 判据在证据缺失时的倒向,和 OCCT 轴向取反带来的区间错位。四处都不改毛坯尺寸、 不改折弯数,却会让平板变卷圆件、一个孔数成两个、切割长报成三倍。

1. 零证据时不能默认"是折弯"

平板上的抽孔领口(翻边孔)经圆环面过渡到板面、顶端是垂直于轴的环面—— 一堵"相切墙"都没有。G2 轴向闸门里那句 max(..., default=0.0) 于是在零证据下 直接投给"普通折弯",R53.8 的领口成了卷圆:整件被当作卷圆件(外轮廓标为不可展、 切割总周长报不出来)、BEND_NO_WALL 报警、状态掉进 needs_review。

没有局部证据就换全局证据,但别换判据——物理上始终是同一句话:折弯是把料绕一根 躺在板面里的轴转过去。这正是 unfold.unrollable 用的判据,现在提到 facetable.axis_lies_in_a_panel,两处说同一套话。

2. 同一特征的两张面,轴向可能是相反的

OCCT 写 STEP 时,哪一侧是 +u 由建模历史决定,不是几何量。于是抽孔领口的内外壁 区间读出来是 (-12,-6)(6,12),凭空多出 12 mm 间隙——被轴向间隙判据拆成 两个孔;压窝沉头孔的 φ7 孔壁与两张锥面同样对不齐,一个孔报成 countersunk φ22.75 + round φ7 两个。深度也跟着错(6 mm 的领口报成 24 mm)。

新增 geom.oriented_interval,比较任何轴向区间之前先翻齐;pairing 里那份 同样的逻辑改用共享实现,不留两份。

3. 一个孔特征只能认领一圈内环

通孔在两张皮上各有一圈;沉头/翻边孔的两圈还不一样大。旧写法每个特征只认领一圈, 剩下那圈成了孤儿——被当作"有内环却没有对应圆柱",误报 LOOP_CYL_MISMATCH (NURBS 退化嫌疑),孔数还多算一个。改为由内环去挑特征,一个特征可以收下 属于它的好几圈。

判归属用拓扑连通而不是距离:抽孔领口的内环离孔壁 6 mm(隔着圆角,该合), 而 ets01835 上主板的开口离其后方小板上的槽只有 3.5 mm(隔着空气,不该合)。 距离在这里根本分不开,走不走得到才是"是不是同一个开口"。

4. 切割周长取最小的那圈

落料时切的是最小的那个孔——沉窝的锥面、翻边的领口都是之后成形出来的,不是切出来 的。取大口会把一个 φ7 压窝沉头孔的切割长报成 π·22.75,是实际的三倍多。

翻边孔的预冲孔:直接解出来画进展开图

翻边孔在成品上是 φ107.6,但毛坯上是个更小的预冲孔——那圈料被拉上去成了领口。 直径取决于翻边工艺(拉伸量、是否变薄),几何推不出来;但体积守恒给得出它应有的 面积:展开净面积恒等于 V/t。只有一个这样的孔时这是一元方程,解得出来就按它 画进展开图与 DXF:AGS000190-01 上是 φ91.8

此前展开图上一个孔都没有——DXF 直接拿去下料就是一整块板,比画个近似的更危险。

两条互相独立的算法,报成区间,并交人工裁定。 一条用体积(毛坯体积 = 零件 体积 → 毛坯面积 = V/t),一条用长度(中性面弧长:圆角展开 + 直壁高,就是算折弯 展开那一套)。本件给 φ91.8φ94.4,差 2.8%——互不依赖还能对上,说明两条 都没算错。画进 DXF 的取较小者:孔可以再扩,料补不回来。

方向说清楚:两条都假定料厚不变,所以都是下界。真实翻边会把领口壁拉薄, 同样的料铺得更开,实际预冲孔只会更大(本件 CAD 恰是按等厚建模的:Δr 处处 3.00、顶端环宽也恰好 3.00)。缺工艺数据时无法给这两个值排序,故按 p=0.5/0.5 记为待裁定决策进决策表,供人按自家工艺选定或改写;该件 status 也因此为 needs_review——复核内容就是这一个数,几何本身已全部确定。

(上一版把这句写成"不计变薄",方向是对的,但把恒等式本身说成了近似。恒等式 是精确的:塑性变形体积守恒,毛坯是等厚的,所以毛坯面积恒等于 V/t。不确定性 不在这条式子,而在"CAD 体积是否等于真实毛坯体积"——等厚建模时 CAD 偏大。)

三条自我约束:

手算核对:φ91.8→φ107.6 之间那圈料 2474 mm²,领口中面展开 2085 mm² 加根部圆角, 量级对得上。另有量纲门禁:整件缩放 k 倍,解出的预冲孔直径必须也是 k 倍。

验证

0.8.1(2026-08-01)

对新上的账号体系做了一轮对抗性测试——把服务当敌人打,不测"正常用法下权限 对不对",测"被人存心搞时会不会破"。119 条攻击里 4 条打穿,都已修复;每条修复 都做了突变验证(把补丁摘掉,对应的测试必须变红),有两条测试正是这样被发现 写成了假绿的。

打穿的四条

1. HTTP 响应拆分(最严重) — 上传时的文件名是查询参数,攻击者完全可控。 evil"\r\nX-Injected: yes\r\n\r\nPWNED.step 存到磁盘后,下载时被内插进 Content-Disposition,CRLF 直接劈开响应头:能加任意响应头,\r\n\r\n 之后还能写一整段自己的响应体——即从本站源提供任意内容。低权账号即可发起。 修法两道:文件名闸门拒收控制字符/引号/路径分隔符;Content-Disposition 改用 RFC 5987 构造,不再内插。顺带修好了中文文件名——原先直接内插会在把响应头按 latin-1 编码时抛 UnicodeEncodeError,变成 500。

2. 用户名 .. 逃出服务根 — 用户名即目录名,而 .. 完全符合旧的 "只含字母数字下划线点连字符",root / ".." 一步跳出服务目录。正则改为首字符 必须是字母或数字,顺带挡掉 .hidden-rf

3. 账号库世界可读.metalmaster/0775、JSON 是 0664,里面装着 口令摘要和当前有效的会话 token。同机器上的任何本地账号读一眼 sessions.json 就能冒充任何人。改为 0700/0600,且先 chmod 再改名, 不留可读窗口。

4. 登录无节流 — 既能慢速爆破,也能反过来用 PBKDF2 的 20 万轮烧 CPU (未认证即可发起)。按账号与来源 IP 两把尺子各卡一次,冷却翻倍封顶 15 分钟, 返回 429。

顺手收紧的两处(没被打穿,但离得太近)

没打穿的部分(记下来,将来别退回去)

伪造/畸形/空会话 token、Cookie 名前缀混淆、账号体系下的旧 ?t= 通道、会话固定、 登出后复用、过期会话、停用后复用、16 种路径穿越写法 × 5 条读路由、跨账号读与 导出、/admin/* 的大小写与编码绕过、文件名与显示名的存储型 XSS、登录页的反射型 XSS、错误页泄露服务端路径——共 115 条,全部拒绝。

0.8.0(2026-08-01)

从"一个能看报告的本地小站"变成能给一队人用的东西:账号、结构化导出、目录页。 同时把计算与系统两层在代码上彻底分开。

分层:metalmaster 是纯计算包,server/ 不随包发布

webapp.py 搬出计算包,拆成 server/{app,auth,pages,xlsx}.py。计算包从此 不 import 任何 server 里的东西——tests/test_layering.pyserversys.meta_path 里屏蔽掉再 import 整个包来证明这件事,光靠约定不算数。 pyproject 只收 metalmaster*;版本号改为从 metalmaster.__version__ 取 (此前 pyproject 里手抄了一份,已经漂到 0.5.3 对 0.7.5)。

结构化数据:一份 STEP → 一行数据

新增 metalmaster/schema.py:60 个字段、9 个分组的唯一定义源。 界面上的勾选项、Excel 的列、/api/schema 全部由它生成,不再各写一遍 (test_schema_catalog_matches_export_headers 盯着这一点)。 提取器只读分析结果字典,绝不重算——导出与页面显示必然一致。

POST /export:勾选的文件 × 勾选的字段组 → xlsx。写出器 server/xlsx.py 也是标准库现写的(zip + 几张 XML)——为了导一张表引入 openpyxl, 不值当破"除几何内核外零依赖"这条线。

账号体系与管理后台

--no-accounts 保留旧的纯令牌模式(本机自用)。那条路径下没有后台也没有 会话,页面就不显示退出/后台链接——摆着点了就 404 的链接比没有更糟。

目录页重做

一行一件,直接给出状态、形态、板厚、外形、折弯数、孔数、毛坯尺寸; 0 也如实显示 0(此前 falsy 被渲染成"—")。勾选即可导出,每行有「重算」。

顺手修掉的两个前端 bug

测试

新增 test_server_accounts.py(27 项:账号库不变量 + 真 HTTP 打权限边界)、 test_server_pages.py(11 项:标签闭合、转义、有 node 就过一遍 JS 语法)、 test_layering.py(31 项)。全套 500 项。

0.7.5(2026-08-01)

这一轮是系统性的精度与鲁棒性排查:语料只有 46 件且含重复,靠更多真实件 验证不现实;改为对夹具施加成套变换,检查不变量。不变量一破,就说明算法 依赖了某个偶然条件——这比"在这几个样本上对不对"更能防住过拟合。

精度:折弯让量对所有角度成立(此前只验过 90°)

新增参数化夹具 make_bend(θ),15°–170° 扫一遍,两条独立判据同时卡: 展开长与构造参数手算差 ≤0.005 mm,中面口径体积恒等式 全部精确 1.0000

(过程中又踩到夹具坑:两条腿若写成正切向,大角度时会插进弧内,OCCT 建出 自交的无效实体——看着像"算法在大角度上失准"。夹具生成器现在自带 "实体必须有效且体积与理论一致"的自检。)

鲁棒性:真实世界常见的 STEP 差异全部通过

不可展成形特征必须被抓住,不能静默吸收

压包/凸包/百叶窗拉伸了材料——毛坯比展平后的轮廓大,几何上看不出来。 新增球冠压包夹具,验证三条互相独立的判据都报警:未解释面积 19.5%、 体积恒等式 0.795、毛坯利用率不自洽;状态不为 ok,不会被自动报价流程误用。 任何一条单独失效,另外两条仍能兜住。

tests/test_robustness.py 现 81 项。全套 415 项。

0.7.4(2026-08-01)

修:面板配对依赖面的枚举顺序——同一零件换个导出顺序给出 3 种毛坯

一块面板的两张皮都是材料外向法向,必然反向。判据里漏了这一条, "带符号"检验就变得不对称——(w,v) 通过而 (v,w) 不通过——于是候选集合 随面的枚举顺序而变。welded_bracket 实测 12 次乱序给出 3 种不同的毛坯 (85.97×60.97/2 片、60.97×55/3 片、60.97×55/4 片)。 STEP 重新导出常会改面序,这在真实使用中随时可能踩到。

补上"外向法向必须反向"这条零阈值物理约束后:16 个夹具 × 12 次乱序 0 次不一致;welded_bracket 收敛到唯一且物理正确的答案 (85.97×60.97 = corner_bracket 的毛坯 + 对接板 = 2 片), 体积恒等式 0.9849 → 1.0000

非钣金件不再报一个看似有效的板厚

超出支持区间(板厚 0.3–12 mm)时会测到某对无关平行面的间距——0.1× 缩放的 L 件真实 t=0.2 mm,却报 t=2.8 mm。薄板性判据虽然兜住了(判为 not_sheet), 但 JSON 里留着一个能被误用的数。终端表现在写明它是什么: "—(非钣金口径不适用;测得平行面间距 2.80 mm,未通过薄板性校验)"。

新增鲁棒性门禁 tests/test_robustness.py(55 项)

同一零件换个"写法",结论必须不变——不变量破了就说明算法依赖了偶然条件

全语料精确命中 1.0000:36 / 46(±1% 内 42)。

0.7.3(2026-08-01)

卷圆件现在真的展开了(此前报的是折叠后的包围盒,长度错 59%)

卷圆的展开弧长就是 θ·(r + K·t)——与折弯同一个公式。它只是不进 n_op (卷圆是另一道工序,单独计 n_roll),那是工序口径,与展开无关。 此前把卷圆整个排除在展开之外,卷圆件报的是折叠后的外形包围盒: roll_section 报 87.7×47,真值 139.9×30。修正后毛坯与手算 leg + θ(R+K·t) 精确一致,体积恒等式 0.199 → 1.0000

但绕孔轴的 360° 卷不算:抽孔/翻边凸缘是绕垂直板面的孔轴拉出来的, 它在毛坯上对应的是更小的预冲孔,不是一段能展平的弧。判据是轴的朝向—— 可摊平的卷圆轴平行于板面,抽孔凸缘轴垂直于板面,分得很干净。 混为一谈会让整件的展开算崩(AGS000190 的毛坯一度变成 None)。

修:卷圆夹具本身是无效实体

make_roll 原来用手搭闭合轮廓拉伸:150° 的弧配上切向直腿,那条线会自交, OCCT 建出的是 IsValid()==False 的实体,体积只相当于约 100° 扫掠。 夹具坏了却看着像算法的错——体积恒等式因此永远对不上。 改用回转构造(截面矩形绕轴转),体积与理论差 0.00000%,并加了 "实体必须有效且体积与理论一致"的门禁。

自由端的折弯/卷圆也出料

只接到一块面板的折弯片(卷圆件的弧就是这样:一端接切向直腿、另一端悬空) 此前被跳过。现在与"封闭截面被切开的那道弯"走同一条路——材料接在该面板外侧。

全语料精确命中 1.0000:35 / 46(±1% 内 41)。剩余偏差只剩两类: 抽孔预冲孔径(需翻边工艺)、拼接件的贴合面。

0.7.2(2026-08-01)

体积恒等式(中面展开净面积 ≡ V/t)当探针,一口气挖出三个各自独立的缺陷。 全语料精确命中 1.0000 的从 25 件增到 34 件(±1% 内 40/46)。

修:同轴配对的"轴向重叠"判据依赖圆柱面的取向

ax_interval 是各自沿自身 axis_dir 投影出来的。同一道折弯的内外两张圆柱面, OCCT 给的轴向可能相反——那时两个区间互为相反数([2.5,67.5] vs [-67.5,-2.5]), 看起来完全不重叠,配对被否掉,整道折弯就漏了AGS001938-04 因此少一道 90° 折弯:展开面积差 5%、面板图还断成两块被判为拼接件; 而它的左右对件 -03 因为两个轴向恰好同向就没事。修正后两件完全一致 (n_op=5、1 料片、毛坯 151.1×119.9)。

修:带圆角的折弯不该做切线裁剪

裁剪用的是无限半平面。面板上有两道轴向不平行的折弯时,第二道弯的切线会把 属于第一道弯那半边的材料一并切掉——corner_bracket 的底板因此少 250 mm²(6.3%)。 而带圆角的折弯根本不需要裁:皮肤面在 B-rep 里本来就止于切线(内外两张皮的 切点是从轴引出的同一条垂线的两个足,面内位置相同)。只有尖折弯需要裁——它的 外皮一直伸到尖角,多出 t·tan(θ/2)。 corner_bracket 0.9366 → 1.0000,welded_bracket 0.929 → 0.985, ets01835 0.947 → 0.976。

修:封闭截面被切开的那道折弯,材料丢了

口形/帽形型材的面板图成环,展开必须切开一刀——但被切那道折弯的料仍然存在, 切开后它落在毛坯端部。此前只记 cycles 不出条带:AGS000020-19 的 4 道弯只出 3 条带,少 3346 mm²(5.4%)。补上后精确回到 1.0000

抽孔/翻边领口:给出体积守恒反推的差额

配对修好后,AGS000190 的抽孔凸缘(内外圆柱 Δr=t)被正确识别为绕孔轴的 360° 卷圆——语义对,但毛坯上的预冲孔比成品孔小,我们没有建模。 UNFOLD_AREA_MISMATCH 现在给出差额面积及其等效圆直径 (该件 6624 mm² ≈ φ91.8),用的人可直接拿去核对预冲孔; 我们不把它画进 DXF——那要假设翻边不减薄。

0.7.1(2026-08-01)

修:尖折弯(r=0)的折弯让量公式错了 21.5%

r>0 时中性面是半径 r+K·t 的圆弧,让量 θ·(r+K·t)——这没问题。但 r=0 时中性面 不是圆弧,而是同样的尖角(距内表面 K·t):两条中性线在角内相交,路径长是 2·K·t·tan(θ/2),不是 θ·K·t。90° 时前者 2Kt、后者 1.571Kt。

这不是口径之争,是几何——K=0.5 时 2Kt 恰为 t,正是角部那块 t×t 方料的中线长, 可用体积恒等式(中面展开净面积 ≡ V/t)逐件验证:修正后 sharp_L 0.9926 → 1.0000、z_offset 0.9870 → 1.0000。全语料精确命中 1.0000 的 从 16 件增到 25 件(mixed_corner、ADX131200、SASV、sasv401880101 等一并归位)。

影响:sharp_L 展开长 57.26 → 57.60,z_offset 64.51 → 65.20(K=0.40)。

修:映射不到面板的孔被静默丢弃

AGS000190-01 是块 3 mm 平板 + φ107.6 拉伸翻边凸缘(凸缘从板面拉到 z=12), 孔心离板面 9 mm > 1.5t,落不进展开图——此前直接 continue:展开 DXF 少一个 Ø107 开口、展开面积多 5.7%,且没有任何提示。现在按成因分开报告:

平板件也跑展开几何

此前只有折弯件跑 build_developed,于是平板上的"孔没进展开图"这类问题 完全看不见。现在平板/单轴/多轴一视同仁——外轮廓、孔映射、体积自检都有。

0.7.0(2026-08-01)

零件类型判定:不是板件不再报"分析失败"

st416-170-009 是个 φ17.7×7.8 的小机加件(外圆 φ17.7 + φ8 内孔 + 7 个锥面, 115 个小平面占 61% 面积)。它能"测出等厚"(两平行面相距一定距离),于是被当作 钣金件往下跑,得出利用率 223% 这种物理上不可能的数,最后报"分析失败"—— 调用方会以为程序出错,其实是零件不在钣金口径内

三处修正,都用几何本身的判据:

1. 板厚候选的物理可行性闸门。板件的两张皮都是表面的一部分,故 2·(V/t) ≤ 表面积 恒成立。st416 的最强候选 t=1.25 给出 2V/(tA)=1.45, 意即"两张皮比整个零件的表面积还大"——几何上不可能,不予采用。 留 20% 余量的物理理由:焊接件的贴合面是内部面、不计入表面积,理想界限会被 轻微突破(welded_bracket 实测 1.001);实测分布把余量定死——真实钣金最高 1.001,越界的 st416 是 1.454,阈值 1.2 落在空隙中间。 不可行的候选照列在决策里并写明理由,不悄悄删——删了会把"变厚度嫌疑" 这条告警一起删掉。 2. 薄板性判据sheetness = 2·(V/t)/表面积 = 1/(1+2t/L)。≥0.5 等价于 "面内尺寸 ≥ 2t",这是"板"这个词的最低要求。实测 46 件真实钣金 0.667–1.00, st416 只有 0.31(面内尺寸不到 1 个板厚)。判为非板件后,钣金主线拒答。 3. 新增 not_sheet 状态ok | needs_review | not_sheet | failed)。 它与 failed 是两回事:分析成功了——认出不是板件、通用几何事实照常输出, 只是钣金口径不适用。终端表、HTML 徽标、CLI 退出码(4,不与 failed 的 3 混淆)、 装配逐件的 sheet_metal_applicable、MCP 输出全部同步。

全语料 49 件复核:0 个 failed,46 个真实钣金件类型判断一个没变。

0.6.7(2026-07-31)

统计表:值为 0 / 无内容的行也一律显示

折弯工艺二次工序孔阵列疑似螺纹底孔零件名 此前在没有内容时 整行消失。报价的人因此无法区分"确实是 0"和"根本没算"。现在恒显示,并写明 是哪一种("无折弯工序(0 道)"、"无(孔径未命中螺纹底孔表)"…)。

外轮廓:拼缝按真实重叠扣除,不再假设"完全共享"

原公式假设每条折弯条带的两条长边与相邻面板完全共享,减 4×折弯线长。 实际折弯线常比相邻面板宽(adx139058 的端部折弯线 40 mm,腹板只有 33 mm), 还可能出现条带一角埋进邻板内部的微小重叠。改为按交点把每条边精确切分、 逐段判定是否被其它多边形的闭区域覆盖——是精确算法不是采样近似, 包围盒剪枝后 build_developed 仍是 ~9 ms。

与独立实现的采样法并集周长逐件比对(已固化为门禁):9 件全部吻合到 0.2% 内; adx132980 由 1477.8 修正到 1481.8(数值解 1481.5)。

卷圆件不再给出口径对不上的切割周长

卷圆弧长没有展开,此时量到的"外轮廓"是折叠后的投影周长——roll_section 毛坯 87.7×47,却报 100 mm。宁可不给也不给一个看着精确、口径却对不上的数: 该行仍在,写明"外轮廓不可用:含卷圆,弧长未展开"。

0.6.6(2026-07-31)

统计口径统一:切割总周长等信息不再因件型而缺失

修:穿孔次数把拼接件报少了

pierce = 1 + 孔数 假定只有一条外轮廓。拼接件展开后是几块独立料片, 每块都有自己的外轮廓、各要穿孔一次。改为 料片数 + 孔数 (welded_bracket 1 → 4)——激光工时输入此前偏低。

公式的独立路径复核固化为门禁(tests/test_formula_crosscheck.py,64 项)

每条公式都用另一条算法或物理恒等式验,不拿实现对实现:

排除在"干净件"之外的三类反向也验:卷圆(弧面未展开)、翻边孔(领口是成形 不是切割)、沉头/沉孔(锥面不由激光轮廓切)——测试断言它们确实分叉, 免得排除名单退化成"凡是对不上就加进来"的清单。

0.6.5(2026-07-31)

判墙判据改用等效条宽,一条几何量替掉三处启发式

判墙原先要求"面内外接次跨度 > 1.5t"。可 face_planar_extents 给的是外接 尺寸:材料厚度面只要拐个弯——型材端部的 L 形截面(139 mm² 却框在 45.7×29.6 里)、绕折弯圆角包过去的端板侧面(3.5 mm 高量成 3.77,阈值 3.75 擦边通过) ——外接框就很大,于是混进墙面。

改用等效条宽 = 2·面积/外环周长:长条形时它就是条带的真实宽度,与形状无关。 实测两族分得很开——真面板皮肤最窄 2.22t,材料厚度面全部 0.92–0.99t, 阈值 1.5t 落在空隙中间。全语料 113 张分歧面全部是厚度面,且分歧单向 (只会把面从"宽"降为"窄",不会反向)。

根上修对之后,三处下游补丁同时变成死代码,已全部删除: 1. develop 里"排除单皮且只有一个板厚宽的连通块"的特例——全语料触发 0 次; 2. adx132980 的 6 处、adx139058 的 2 处「零半径直角疑似拼接」决策——那是 墙面误判的假象:墙-墙锐边判据看到的其实是真面板与厚度面之间的棱。 两件的折弯数(5 / 4)与毛坯(610.65×127.98 / 1227.28×97.14)分毫未变, 但 SHARP_WITH_RADIUSEDDECISION_RISKUNFOLD_DISCONNECTEDUNFOLD_ORPHAN_FACE 一并消失,status 由 needs_review 转 ok; 3. 随之不再有"拼接件"误判,两件都回到单一料片。

13 件 × 6 种姿态毛坯偏差仍为 0.0000 mm;变异测试确认门禁能抓住这条判据的回退。

0.6.4(2026-07-31)

K 因子:从藏在默认值里的超参数,改为明写出来的不确定度

无可调参数的正确性判据:体积恒等式

折弯是塑性变形、材料不增不减,而 CAD 实体的体积对应的正是中面,所以 K=0.5 口径下展开净面积必须精确等于 V/t。这条判据不含任何超参数, K 取多少都掩盖不了"漏摊面板"(比值偏小)或"面板重叠"(比值偏大)。

实测:L / U / DYL / 包边 / 缺口耳 / 平板等 16 件精确 = 1.0000,两件真实客户件 0.9999 / 1.0002。偏离的都有已知成因:卷圆 0.199(弧长未展开)、翻边孔 0.959 (领口不在展开图)、多轴角件 0.93–0.95(两弯交角区未建模)、尖折弯 0.993 (尖角超出切线的部分被裁)。比值作为常驻指标 metrics.developed_area_ratio 输出,偏离 >2% 报 UNFOLD_AREA_MISMATCH(info 级,如实报告不改状态)。

关于两件真实件与图纸的残差

adx132980 要对上图纸需 K=0.391(≈ 默认 0.40,合理);adx139058 的宽度需 K=0.568——超出物理上限。而体积恒等式证明我们的几何是完整的(1.0002), 展开图两端分别由翻边展开边和端板外边(纯二维尺寸、与 K 无关)决定。 结论:那 0.27 mm 用这个实体展开不可能得到,来自图纸方的余量或读数,不是算错。

0.6.3(2026-07-31)

修:多轴件展开尺寸偏小(客户标准答案对不上)

用户给出两件的展开标准答案,逐一对照后挖出三个各自独立的缺陷。 三处都不是"调参",每一处都配了变异测试(把它改回去,门禁必须变红):

1. 折弯轴映射漏了一次坐标系复合(结构性 bug,影响最大)。 _place_child 把折弯轴映射到展开平面时只取了父面板的局部坐标 parent.local(),没有再过父面板自身的摆放矩阵 a2/b2。根面板的摆放是 单位阵、两者恰好相同,所以这个错误只在链条上第二道及以后的弯显形: 子面板会在展开图里多转一个角度。段差件转 37° 后毛坯从 64.5 变成 76.5。 2. 折弯外推方向用面板形心判定,在"面板被两道弯夹在中间"时退化——中间那段 平段的形心几乎落在切线上,符号由浮点噪声决定。改用离切线最远的材料 在哪一侧:平段长度总大于折弯区宽度,判据有真实余量。 3. 一条"折弯是否相切于该面板"的几何判据是死代码:修好上游两处(面板配对 带符号、尖折弯按面板对归并)之后,全语料停用它结果一字不差;而它依赖 轴对齐包围盒、不是旋转不变量。已删除。

修复后与客户标准答案:adx132980-00 610.65 × 127.98(图纸 610.6 × 127.9, 偏差 0.01% / 0.06%);adx139058-00 1227.28 × 97.14(图纸 1228 × 97.8, 偏差 0.06% / 0.67%)。两件的图纸值分别指向 K≈0.40 与 K≈0.50,单一 K 不可能让 宽度同时进 0.5%——宽度按 K 因子区间包络验收,不硬凑某个 K。

门禁:补上"毛坯与摆放姿态无关"这一条

此前 metamorphic T1 只比折弯数与板厚、不比毛坯,上面三个缺陷因此可以一直 藏着。新增 tests/test_blank_accuracy.py:13 件(含 2 件真实客户件)× 6 种姿态, 毛坯必须一模一样(等距展开是数学恒等式,容差 0.05 mm 纯为 STEP 往返噪声); 外加与图纸的精度门禁。

门禁:metamorphic 签名改按物理容差比对

原先"各自四舍五入到固定位数再精确相等",真值一旦压在进位边界上就随机翻车 (45.6549 → 45.6 还是 45.7 取决于最后一位浮点噪声)。改为逐字段给物理容差 (板厚 0.02、折弯 0.3、孔 0.1、外形 0.2、毛坯 0.2 mm),"多大的差算差异"有了明确口径。

修:MCP 可选依赖的上游变动打断了 CI

其它

0.6.2(2026-07-28)

修:拼接件的毛坯利用率误报不自洽

0.6.1(2026-07-27)

每个零部件都能单独重算

0.6.0(2026-07-27)

展开毛坯:多轴件真正算出长宽,不再降级

上游三处配对/归并缺陷(真实件与段差夹具抓出)

展开轮廓与自洽性

新增判别夹具与门禁

0.5.3(2026-07-27)

折弯计数:零半径直角不再被误计(真实件 ADX132980 报 11 道,实为 5 道)

展开(折弯摊平)复核与呈现

0.5.2(2026-07-27)

统计口径明确化

性能(多孔件)

0.5.1(2026-07-26)

0.5.0(2026-07-26)

人能在浏览器里看:本地网页查看器 + 可转动的三维视图。

三维查看器(按算法角色着色)

网页查看器(metalmaster serve

对外访问

并发与内存(Web 服务)

代码整理

182 用例全绿(新增 23:Web 协议层 15 含真实并发用例 + 三维网格/查看器 8)。

0.4.0(2026-07-25)

定位扩展:从「钣金报价分析引擎」到「STEP 认知服务(钣金深度专长)」。 agent 能了解任意零件/装配体,人能操作、查看、验证。

认识任意 STEP(不限钣金)

人能看见与验证

装配体 BOM 级认知

agent 入口

人能操作与验证

159 用例全绿(新增 15 个服务层测试:装配去重与逐件分析、非钣金事实、 三视图口径一致性、HTML 结构与内容、CLI/MCP 入口、自检命令)。

0.3.2(2026-07-25)

补齐设计文档 §7 干涉矩阵的逐行绿测(M4 完成定义中一直欠账的一项), 4 个新判别夹具当场抓出 2 个真问题:

0.3.1(2026-07-25)

0.3.0(2026-07-24)

展开几何(develop.py):从毛坯口径到真实套料输入。

0.2.1(2026-07-24)

工艺语义增强(全部影子模式,不改计数):

0.2.0(2026-07-24)

定位定稿:独立发展的钣金 STEP 报价分析引擎(基础组件)。

新增能力

质量机制

修复(均由门禁/判别夹具抓出)

0.1.0(2026-07-23)

首版:P1–P8 流水线(STEP 载入修复、面分类、板厚双通道、Δr=t 折弯配对与 闸门验证、尖折弯补全、n_op/n_geo 双口径聚类、孔工艺分类、面板对齐外形、 置信度评分),CLI/Python API 双入口,4 夹具端到端测试。