← MetalMaster
更新日志
v0.54.0 · 与仓库 CHANGELOG.md 同一份,发布即最新
落地页开更新日志与博客;分支策略与 CI 上闸门(2026-08-17)
用户:"landing 页里面也增加一个 blog 和 changelog 的环节……都是静态的信息, 每次更新发布之前都应该把它们刷新一下"、"未来上线应该有一个 PR 的过程"、 "只有合到 main 上才能正常安装使用,其他的合到 dev 分支在测试环境做"。
/changelog 与 /blog(公开静态页,与 /welcome 同级,不碰用户数据): 更新日志直接读仓库 CHANGELOG.md 渲染——发布带着仓库走,页面天然是 最新的,不存在"忘了刷新"的第二份;博客读 docs/blog/*.md。落地页新增 「最近更新」「博客」两节各列最新三条。服务端 md 渲染器与助手气泡同一条 安全纪律(先整体转义、后替换),slug 走白名单。首发两篇:墨迹一本帐、 助手看得见零件也听得见意见。- 分支策略落成闸门:
deploy/restart.sh 开头查分支,非 main 拒绝部署 (紧急逃生 MM_ALLOW_BRANCH=1);新增 deploy/restart-dev.sh 起测试环境 (/srv/metalmaster-dev :8766,与生产数据根/端口隔开);CI push 触发加 dev;docs/RELEASE.md 写清 PR 流程与发布前清单(含"刷新静态信息")。 规矩不靠自觉——tests/test_release_process.py 5 条钉死,变异 4 刀全红。 - 修一处让 CI 3.10 矩阵必炸的语法:装配页标题用了嵌套 f-string 带转义 引号(PEP 701,3.12+),本机 3.12 完全看不出来。顺手加全仓体检门禁: 按
pyproject 声明的 Python 下限扫,声明支持 3.10 就不许偷用 3.12 语法。
助手顺手收集产品反馈(2026-08-17)
用户:"助手只需要筛选一下是不是跟产品优化有关的问题,如果有的话就直接 存下来。未来你可以通过这些反馈来持续优化。"
- 工具箱第五件
save_feedback:模型在对话里听到产品问题或改进建议 (数据算错、功能不好用、希望增加什么)就提炼成一两句存进反馈本,并告诉 用户已记录——不用等用户点「反馈」按钮。筛选标准写在系统提示里:普通 提问、闲聊、对回答的追问不记。 - 与手动反馈同一本帐(feedback.jsonl)、同一个后台,带页面定位与算法 指纹;来源标
via: copilot,后台显示「·助手代录」——处理时知道这是 模型提炼过的转述,不是用户原文。这是工具箱里唯一的"写",写的只是 反馈登记,不碰分析数据、不触发计算。 - 真机验证:一句"利用率要是能按整张钢板套料算就好了"被提炼落盘 (模型自己补全了"排版嵌套"的术语);紧接着的"板厚是多少"没被误存。 变异 2 刀(不标来源 / 装存没存)全红。
助手看得见缩略图了(2026-08-17)
用户:"应该把 3D 的缩略图也作为助手的上下文,可以用来判断这是个什么东西。" 模型看一眼外形,比读一堆特征数字更快认出"这是个支架/法兰/机箱":
copilot.page_image:当前零件/整机的缩略图 → PNG,随最后一条用户消息喂给 多模态模型。与 page_context 同一条纪律——只读已缓存的那张画 (store.cached_thumb,旧版本算的也认:图画的是几何,文件没变图就对), 绝不为一句聊天触发投影。零件线稿栅格化 200 px(复用 Excel 导出那套 thumb_png,实测 ~1.6 KB / 约 50 token),整机位图原样;目录页没有 "当前这一件",不带图。- 两家方言各自的多模态块(OpenAI 的 image_url data-URI / Anthropic 的 base64 source block),注入只动最后一条用户消息;不带图时纯文本原样—— 老行为不变、不多花一个 token。系统提示仅在带图时附看图指引 (图与数据矛盾时以数据为准)。
- 留痕补
img 字段:回放时知道模型当时看没看到图。 - 真机验证(OpenRouter / sonnet):鱼尾板线稿,模型答出"厚板、右侧两孔、 左侧窄长舌片、连接支架类"。变异 4 刀(两方言注入、零件号透传、handler 递图)全红。
出图墨迹一本帐(Ink);助手留痕进后台(2026-08-16 深夜)
用户:"你能不能整体调整一下,因为你画图的时候是能够知道之前的画到哪里了。" 对——此前避让是零散的:气球看 avoid、外移数字看 taken、视图名看 ink_bbox, 各记各的帐,帐本之间就是盲区。现在每个视图一本 Ink 帐(texts/segs), 画一段登一段,所有"要找地方放"的元素(气球、外移尺寸数字、剖切字母、 0,0 基准文字)对着全部已画墨迹计分选位:
- 剖切字母延后放置:
_section_marks 只返回位置任务,字母等几何、尺寸、 气球全画完后从 6 候选里按墨迹代价选位(ets 阶梯剖 A 压线 865 段 → 0; 此前两次盲改回滚的正是它)。 0,0 基准文字:只给视图内侧(向右)候选——向左伸会越出本视图足迹 压进邻居(ets 实测 21 段),而单视图的帐看不见邻居;代价 ≥ 压线 3 段 (与门禁同口径)就省略文字,符号本身已表达零点。- 尺寸数字外移与文字让位:候选计分并入 ink 代价;让位方向从写死 y 改为文字自身法向(竖排数字曾骑在自己的尺寸线上)。
- 整图一本帐试过、回退:能治跨视图越界,但朴素 O(N) 线扫把语料跑时 87s → 549s,还牵动视图 footprint 连锁 5 红——记为下一轮,配空间索引做。
门禁收紧: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 次。逐族修根:
- 气球按墨迹计分选位:候选(三圈扫描 + 视图外上下两排)按"压文字 30 / 压线 3 / 同伴间距 100"的代价取最小——原来只避同伴气球,对几何与文字全盲。 顺带修
svg_segments 漏解析 <path M…L…>(几何主体正是它,第一版对着 空场地打分)。 - 视图名放实际墨迹下沿:档数算术只数主路尺寸,右侧引出、旋转文字不在账上。
- 装不下的尺寸数字外移(GB/T 4458.4):25 mm 尺寸在 1:5 下线长 5 mm、 数字 13 mm,放中点必然横穿;外移带登记互查,两端+步进候选。
ink_bbox 旋转文字按方向投影:竖排数字按水平记,外移长数字整段漏出 足迹(与"圆弧半径不是坐标"同族的墨迹反解盲区,单元门禁钉死)。first_angle_layout 的 below 行 pads 上下颠倒:进门扣了"下伸"、出门扣 "上伸"——俯视朝主视一侧的标注留地被少算 10 mm。以前上伸都小不显形, 数字学会外移后第一次撑到显形(busbar 实测主视底与俯视顶相犯 6.7 mm)。- 杂项:
_fit 符号宽 0.75em(φ/±/× 曾按窄西文估,孔表溢列);0,0 基准文字 让位到符号左侧;数字底沿抬到 0.5H;审计对 rotate(0.0) 不再按旋转包络。
新语料门禁 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)
对"交互是否足够简单"的复盘,按三条主旅程数点击,修掉两步废动作:
- 传一个就看一个:单个 STEP 上传成功后直接进它的页面(大件自然落进 "读取 → 装配树 → 逐件披露"的渐进流程);多文件/ZIP 是批量场景,照旧回 目录页。原先上传完只刷新目录,还得找到那一行再点一下。
- 打开装配页即自动补齐:缺的零件自动在后台补算——大装配走离线任务本就 自动逐件,小装配却要人发现「补算未计算」按钮,越小的文件反而多一步。 护栏:只补缺的(幂等、不清缓存)、有任务在跑不添乱(双份读文件是双份 内存)、任务键去重防反复刷新起一堆进程、失败只记日志不影响开页。
「补算未计算」按钮保留——它还是"手动马上补"的显式入口。 门禁 2 条,变异 4 条全红。
copilot 三轮:助手主动开口(2026-08-16)
有了助手,交互的方向要反过来:不是人打开面板问"有什么要注意的",而是页面 已经知道的事由助手第一句话说出来。brief_of 在渲染时从报告确定性提取 (需人工复核 / N 条风险标记 / 工艺判定不明确 / 结果出自旧算法),欢迎语 直接列点、快捷问题换成针对性的("解释这 7 条风险标记""为什么工艺判定 不明确?");干净的页面保持通用欢迎语,不制造焦虑。零 API 成本。 件76 实测:本页有要注意的点:状态为「需人工复核」、7 条风险标记、工艺判定 不明确。门禁 2 条(主动开口/接线),变异 4 条全红。
钣金 copilot 二轮:特征编号语言 + 快捷问题(2026-08-16)
- 上下文带逐特征明细:折弯(编号/角度/内 R/折线长/方向,截 40)与孔 (编号/类型/直径/切割长,截 30,总数在汇总里是全量的)。编号(B1/H3/C2) 是这个系统的一等公民——页面明细、三维标牌、图纸气球同一套号,现在助手 也认识:可以直接问"B3 是哪道弯"。
- 系统提示教编号体系,要求谈到具体特征时引用编号。
- 回复里的编号可点击:跳到页面明细里那一行(先转义再替换——直接 innerHTML 会把模型输出当 HTML 执行;页面上不存在的编号不做成死链)。
- 快捷问题:面板首开给三个一键提问(报价注意什么/风险复核/展开尺寸 由来)——第一次打开不知道能问什么,是最大的门槛。
门禁 +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, 结构/样式/行为/后端一个包)。两条腿,一条不依赖另一条:
- 反馈:把针对"当前这个模型"的问题写下来点「反馈」,连同文件/零件号/ 算法指纹/提交人落盘(
root/.metalmaster/feedback.jsonl);管理后台新增 「模型反馈」一节:待处理排前、每条带指回模型的链接与算法指纹(复现先看 指纹——反馈时的算法和现在的可能已不是同一版)、可标记处理/重开。 不需要配任何密钥,当下即全链路可用。 - 对话:助手带着当前页已算好的数据(报价精简视图
quoting.condense_report(与 MCP quote_inputs 同产地)+ 工艺类型结论 + 标记)回答问题;绝不为一句聊天触发分析——没算过就如实说"还没算"。 模型调用只用标准库 urllib(零依赖口径),密钥从环境变量取 (METALMASTER_ANTHROPIC_KEY),未配置时如实说明并把人引向反馈。
安全:请求体上限(对话 64 KB / 反馈 16 KB)、登录后可用、反馈进审计日志、 对话每用户每分钟 10 条节流(按 token 计费,一个账号不该有能力烧穿预算)。 门禁 6 条(链路/退化/上下文入 system/不现算/挂件自含/节流),变异 7 条全红。
环面归回转:垫圈判回转体(2026-08-16)
件95(垫圈)曾被判"自由曲面件(铸造/模具)"——环面在面表里不记轴,永远进 不了回转分组,面积全算给"自由曲面"。而环面是解析回转面,主轴即回转轴:
- 面表给环面记主轴与 U 扫掠(孔识别只收圆柱,不受影响);
- freeform 证据不再收环面(一份面积不许投两家的票);
- 分类补"回转证据"出口:共轴×扫掠闭合的加权面积 ≥50% → 回转体(排在钣金 判定之后——卷圆件有板厚,早在钣金分支返回,不会被误伤)。
全语料回归: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)
黑色等待页整个退役(用户:"这个黑色页面应该去掉,然后融入到正常的页面中, 逐步披露计算出的结果")。三层配合:
- 渐进页换肤:样式借最终页的产地(
htmlreport._CSS 主题 + asmview._RC_CSS 装配树),只补进度条几条自有样式——渐进页与最终页长得一致不是美观问题, "刷新一下换了个世界"会让人怀疑两边的数不是同一份。CSS 注释顺手剥掉 (快照每 5 秒整页重拉,注释纯是流量)。 - "读文件中/排队"两态穿同一件站点壳(导航 + 主题 token + 自动刷新), 黑底独立样式删除。
- 重算播种(用户:"装配体重新计算的时候,为啥还是这个可怜的页面?"): 重算时上一版的树/BOM/规模都在手里,清理后立即用旧结构写一版渐进页—— 行全标"未算"(结构不冒充数,旧数可用时
_stale_data 早就端旧页面了), 树照常显示。从点下重算到离线任务读完文件之间不再有空窗。
整机网格走轻车道(用户:"整机三维网格……应该是优先级最高的,怎么会前面 还有任务在跑呢?")。准入规则收成纯函数 admit_job(唯一产地):网格任务 免名额、不免内存——名额是防"两个大件把内存吃光"的手段不是目的,网格是 正在看的那一页要的东西,不该排在批量补算后面;内存那条是硬的,读不到内存 信息也不开轻车道。维护任务不占名额(MAINT_TAG)是第一个先例,这是第二个。
交互细节:零件页「关键事实」置顶(数字先于图——点开一个零件最先要确认 "是不是这个件、多大、多厚");装配树根节点没名字时退到文件名(不再写 「(未命名)」);渐进页树根同样。
围绕 STEP 的独立工具面补齐:折弯 / 三维 / 图纸(2026-08-16)
用户定的方向:"围绕一个 STEP 文件提取的关键信息……把这些功能都做成独立的 工具。"MCP 工具面补齐为 12 个:
bends:折弯明细(角度/内 R/折线长/方向)+ 三种计数口径(n_op 工序 / n_geo 几何 / n_roll 卷圆)。只要折弯就不必抱回整份报告。view_3d:自包含交互三维页(转动/缩放/按角色着色/特征编号标牌)。页面壳 复用 htmlreport._CSS 整份样式——只抄"用到的几条"就是共用结构漏样式的老病。drawing_sheet:工程图纸 HTML,浏览器另存即 1:1 矢量 PDF。标题栏取数与 "双渲染定比例"从 _Store.drawing_html 抽成 sheet.sheet_meta / sheet.render_with_scale——Web 页与 MCP 出的是同一张图纸,规则只有 一份(门禁:"org": "圆木智能" 这个标题栏哨兵在全仓只许出现在 sheet.py)。- 零件级工具(bends/view_3d/drawing_sheet)吃到装配体一律报错并指路 (assembly_bom → 逐件),不再答非所问。
装配页的 BOM 表 / 装配树 / 重算按钮拆成 server/asmview.py(结构+样式+行为 一个包,app.py 3824 → 3349 行),规则零改动。
装配拆解进 MCP;导出独立成模块(2026-08-16)
MCP 终于看得见装配体。 此前把装配体喂给 analyze/classify_part,走的是 "分析体积最大件"那条路——537 个零件的机架顶着最大那块板的标签回答"钣金件"。 现在:assembly_bom 给零件清单(只读结构,48 MB 整机秒级返回); classify_part 带 part_index 逐件判定;不带 part_index 问装配体直接报错 并指路,不再给一个看似确定的错答案。核心层同步补 classify.classify_assembly_part 与 classify_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_shape、classify_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 后缀(补掉两个真实的缩放不变性洞)。
修掉的判据错:
- 皮面判据
skin_width_over_t 1.5 → 1.07。原值落在皮肤那一族内部(照薄板挑 的),厚板的皮被自己的判据挡在门外;1.07 ≈ √(0.99×1.15) 是两族边界的几何中点。 - 厚板整类报不出数:真板厚超出通道 A 绝对上限的件两通道皆空后直接 return, 而专为厚板准备的皮肤法兜底只挂在另一个分支上,最需要它的一类走不到。现两通道 皆空时也走皮肤法(新 flag
T_BY_SKIN_GAP);皮肤法改为候选自洽,去掉会退化成 绝对 1.5 mm 的 t_hint。 - 合理性判据被当成一票否决:皮肤法只试票数最高的候选,过不了就放弃,正确答案 在候选表里也拿不到。改为按票数降序逐个过筛。
- 分类器词表不闭合(崩溃级):
_classify_part 有 6 个出口而证据侧只有 4 条, 选中缺席标签直接抛 ValueError,整件一个数都出不来。立 PART_KINDS 唯一词表 + 静态扫描钉死两边。
展开几何口径修正:
- 折弯材料面积改从真实圆柱面量:
line_len(外侧跨度)在两弯相交的角上高估了折弯 区的料(内面渐缩),adx139058 的体积恒等式 1.000152 全部来自此。改用 L_eff = 内面面积/(θ·Ri) 后为 0.999999,developed_area_ratio 1.0002 → 1.0。 只改材料账,条带仍按 line_len 画(渐缩条带留作后续)。 - 孔按平/弯分块扣:上一条顺带排除了孔在折弯面上的那块,扣孔另有一条 πd²/4 的路, 同一块料扣两遍。孔面积取精确 πr²,只有交集用多边形近似。
- 一维毛坯长度改沿轴扫描:原先对所有面板平段求和,沿折弯轴并排的共轴双耳各算 一次(
notch_ears 56.28 报成 81.28,偏大 44%)。折弯让量的轴向区间先过 oriented_interval 归一。 - 成形子面板的"落在父皮轮廓内"闸从只验质心改为全体采样点(方向保守)。
验证体系(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 上传、可选鉴权与局域网监听
- stdio 保持 6 个分析/产物工具;Streamable HTTP 增加第 7 个工具
upload_step(filename, content_base64),解码后最大 10 MiB,只保存 STEP/STP 并返回绝对路径,超限返回 FILE_TOO_LARGE; - Streamable HTTP 继续额外提供普通
POST /upload,流式上限仍为 200 MiB; - 两种上传共用独立随机目录,在上传成功或最后一次文件工具使用后 5 天连同生成 文件整体清理,以较晚者为准;
- 清理只认本服务写入的所有权标记,并在 MCP 工具使用期间保护目录;
- 远程入口使用独立 MCP 域名,避免与原网页的
/upload 路由冲突; - 配置的 MCP 路径(默认
/mcp)与普通 /upload 新增两个独立、默认关闭的鉴权 开关;命令行可覆盖环境变量,两个入口共用一个 Bearer 机器密码,Streamable HTTP 启动时若开关与密码配置矛盾则拒绝启动; - HTTP 允许绑定
127.0.0.1、localhost、::1 或明确的 RFC 1918 IPv4, 拒绝 0.0.0.0、::、公网/特殊地址、其他主机名和其他 IPv6 地址; - HTTP 的
html_report、unfold_dxf schema 不再提供 out_path,服务固定采用 STEP 旁的输出路径; - 新增独立
docs/http-integration.md,说明第三方架构、工具、鉴权、局域网监听、 MCP/HTTP 输入输出和平台中立部署方式; - MCP 几何工具统一串行进入 OCCT,旁车缓存改为原子替换,避免 HTTP 并发竞态;
0.30.0(2026-08-05)— 切割线明细 + 合并重复实现
切割线明细:每条线一个长度,加起来等于周长
展开图里的轮廓是采样折线(一条直边被切成十几段),直接标注没法看。新增 develop.cut_lines():按"转角小于 3° 即同一条线"把连续段并回逻辑上的一条, 每条给出长度与类型(外轮廓 / 内部开口)。
这份明细存在的意义是可核对——报价员能把每段长度加起来,对上报告顶上那个数。
合并重复实现(本轮最值得做的一件事)
第一版明细自己抄了一遍轮廓切分逻辑,结果外轮廓段合计 11780.7 而 contour_mm 只有 2681.0——差 4.4 倍。成因是拆了每块面板的整圈, 把面板之间的拼缝也当成了切割线,而拼缝一刀都不用切。
修法不是"再对齐一次",而是只留一套实现:
_free_segments() 成为"哪些边要真的切一刀"的唯一定义;_free_perimeter() 退化成它的求和;cut_lines() 也从它出发。
于是"明细加起来 ≠ 周长"在构造上不可能发生。两件实测差 ±0.02 mm。 这个项目最贵的几个 bug 都是"同一件事两套实现、各说各话",这次从源头消掉一处。
清理死代码
facts._evidence_of / _strongest_rival——早先"证据 argmax 否决"撤回后的遗留,无人引用;develop._perimeter——_free_perimeter 已是唯一口径后无人调用。
共减 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 不是 这些件的等厚厚度。两个信号是同一个测量的两面。已写进测试钉死。
判据的前提要说全
第一版漏了两种"把料移出平面"的成形,当场在自己不适用的地方开火:
- 卷圆(
roll_section 比值 5.02):料是卷起来的; - 抽孔翻边(
AGS000190 翻边板):领口的料是从平面里拉出来的。
两件的置信度都被误扣到 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) 显式建模才是切口,否则毛坯轮廓就是干净的台阶——客户答案正是按这个口径给的。
结构:轮廓与材料从此分账
net_area_mm2(材料):原始条带,一毫米假料不许添,体积恒等式把守;contour_mm(刀路):_strips_for_contour 加宽后的条带,客户报价口径;metrics.material_contour_mm(材料周长):新增暴露,交叉校验(厚度面面积÷t、 独立采样并集)从此对它,另加单边不等式:刀路 ≤ 材料。
两本账各有各的裁判——恒等式在构造上不可能再被这条改动破坏。
判据:远侧线上的边段清单(无阈值可调)
从折弯展开出来的材料——无论经圆弧相连还是法兰超出圆弧宽度的自由边——根部边都 精确落在"折弯线+让量"这条线上。所以取这条线上的面板边段清单为准: 1. 段清单而非 min–max 包络——两段之间没边就是真切口,不填(notch_ears 由此免疫); 2. 只保留与条带自身轴向区间相交的段——别处恰好共线的边进不来; 3. eps 属"模型精度"类(吸收浮点噪声),与零件大小无关。
验证
- adx139058 = 2681.0,客户答案 2699(差 −0.67%,全部可解释:客户按尖角记账, 本件法兰四角 R5 圆角,8 角 × (2−π/2)·5 ≈ 17)。回归测试转绿。
- 语料 115 件:只有 adx139058 一族(7 副本)变化,全部 −64.6;恒等式零变动; 变大的 0 件。
- ADX131200 保持 xfail:它的 +123 不是假缝——~58 mm 是 8 个建模出来的折弯 止裂口(激光真实要切),~65 mm 是角部细节;客户答案是简化轮廓口径, 在拿到客户毛坯 DXF 之前不为对齐而对齐。
- 变异测试补了两条曾经的假绿:包络式加宽 → notch_L 轮廓小于外接矩形周长 (几何上不可能),当场红;加宽写进材料口径 → 恒等式 1.0002→1.003, 收紧到 0.001 后当场红。
全套 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 不是算错, 是"哪些边算切割"的口径分歧——见下。
这件的几何:端板很可能是焊上去的,不是折出来的
- 槽形主体只有 40 mm 宽(x −66…−26),两条侧壁高 16.5;
- 两块端板是 81×75,向 +x 探出主体 41 mm;
- 而"折弯" B1/B4 的折弯线在 x −25…15,与底板(x −62.5…−29.5)完全不重叠。
折弯线和它本该连接的底板不在同一段区间上——这不是能折出来的法兰。 展开树因此把端板摆错了位置(还镜像了:三维 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 被二次缩小。
两条测试起初都是假绿
- 视角那条用的夹具是沿 Z 建的板,板厚恰好就是第一条候选轴,把判据换回固定顺序 照样全绿;把板转 90° 仍然全绿——因为夹具的
dims.frame 有值,而 frame 的第一 条轴本来就是板厚方向。排序逻辑只在 frame 为空时才起作用(真实来料 ets01835 正是如此)。最后改成直接测 _axes + 真实件兜底,变异才见红。 - 画布那条一次就抓住了。
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_t、developed_area_tol | 缩放不变,稳 | | 角度 | theta_parallel_deg | 缩放不变,稳 | | 绝对长度 = 模型精度 | coax_dist_tol_mm=0.05 | 是文件格式的属性,不是零件的,稳 | | 绝对长度 = 其他 | t_max_mm=12、pattern_line_tol_mm=1.0 | 危险:零件放大缩小结论就变 |
用缩放机械地找出第四类
把 16 个夹具在 0.25×–4×(16 倍跨度)上全跑一遍,只查无量纲结论。 结果比预期好:只有一处变化——z_offset 放大 4 倍后少一条风险标注, 根源是 t_max_mm=12 这条真正的物理先验(钣金就是薄的),属正当后果。 状态、形态、折弯道数、孔数在 16 倍跨度内一字不变,已固化为门禁测试。
两处"清单会腐烂"的机制缺陷(都是我这两天造成的)
- 缩放门禁的绝对长度清单是手写的,我新加的
t_max_plate_mm、 implausible_size_mm 等 7 个字段全都不在里面——等于新阈值可以悄悄绕过 门禁。改成从 AnalyzerConfig 按 _mm 后缀自动派生,另加一条命名约定 测试兜底,清单再也漏不掉。 machining.py 里 8 个阈值散在 config 之外,违反 config.py 第一行自己写的 规矩。全部搬回 config,其中四条明确标注为"照 108 件标定的经验值,换批需 重标",且一律 info、扣 0 分。
板厚的几何下界:把恒等式从"选择器"改成"验算器"
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 | 切割轮廓(腰形槽/矩形/异形) | 走轮廓刀路,与钻孔是两件事 |
排序不用检出顺序(那会随实现细节漂移),而是沿零件最长边从一头数到另一头 ——跟人拿着图纸从左往右点数是同一个顺序。同一份文件反复解析,号一模一样; 这是编号唯一的用处所在,也是测试里钉得最死的一条。
三维视图上的号
- 号做成绝对定位的小牌子叠在画布上(不进 WebGL:文本渲染和点击命中都现成, 换主题、缩放页面也不用管)。每帧按投影矩阵摆位。
- 默认关。一件几十个孔的板子一上来全是牌子,形体反而看不见了。右上角 三个开关:折弯 B / 孔 H / 切割 C,想看哪类点哪类。
- 密集处近的优先占位:重叠时让给离镜头近的那个,远的按距离淡出—— 没有深度缓冲可读,这是最省的深度线索。
- 双向联动:点图上的牌子 → 下方明细表滚到那一行并高亮;点表里的行 → 图上那个牌子高亮。另有一个输入框,直接敲
H12 回车两边同时定位。
顺带
- 孔与切割补了一张逐个特征的明细表(折叠),此前只有分组汇总。
- 编号同时进
/api/analyze 与导出——所以「我说 H12」这件事对 AI 也成立。
编号写进落盘的报告 JSON,因此 labels.py 计入缓存指纹。(起初把它排除了, 理由是"编号只是呈现"——那是错的:排除意味着改了排序规则后,旧缓存里的号是老的、 新算的是新的,同一个号在两件之间指向不同东西,恰恰把编号唯一的用处毁掉。)
0.19.0(2026-08-03)
折弯线绕成一圈的,压弯做不出来
用户报 ETS01835 多了两道折弯,指的是"一个大孔的两条横边"。查下去是一个腰形 大孔四周翻了一圈边:两条横边(内 R2、长 70)+ 两端两段半圆(内 R35),中间由 6 个圆环角面接起来,围成一个闭环。
四段逐个看没有一处可疑——都满足 Δr=t 同轴对,都通过轴向闸门(轴与相邻墙 法向夹角 0°,标准折弯特征)。只有连起来看才露馅。所以新判据不看角度也不看 半径(那两样与真折弯一模一样,本来就分不开),只看拓扑:
> 压弯的折弯线是一条直线,两端到料边为止。想把同两块板在四处折起来还围成 > 一圈,压弯做不到——那要求板料自己断开再接上。能围出闭环的只有模具。
把折弯面与圆环角面连成图,连通块里边数 ≥ 点数即含回路 → 判为模具拉伸/翻边 (闸门 G1,排在所有单点判据之前,因为它是唯一逐个看看不出来的)。
一并修掉的连带错误:
- 两段 R35 半圆此前被当成卷圆,整件跟着报 ROLLED_SECTION——它其实是孔口 翻边的转角。
- 那一圈的四段圆柱被送进孔识别,两端半圆各变出一个 φ50 沉孔:孔数 37→40, 切割周长 2143→2602 mm(虚高 21%)。切割周长是报价主口径。
- 误判成卷圆导致整件不可展,
total_cut_mm 与 outer_contour_mm 一直是空的; 修完能算了(2575 / 4718 mm)。
不计折弯不等于不要钱:翻边是独立成形工序、要模具,改报 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% 都触发的提示不是信号
- 去除率过高:58 件钣金 27 件触发,50 件铣削件一件都没有。方向正好反了—— 平板躺在外接方块里本来就大半是空气,"去除率 88%"说的是平板的形状,不是铣削 浪费了料。→ 提示只对非钣金件说,数照给(判不准时还要拿来比对),话不乱说。
- 朝向数多:50 件铣削件里 46 件触发。倒角、工艺斜面随手就到 7、8,那是常规。 阈值 6 → 9。
- 内圆角细:84% 触发。中位数就是 R0.59,等于说"绝大多数件都要 φ1.2 的刀"—— 是实情,不是警告。阈值 R1.0 → R0.4(φ0.8,比常规最小刀还细一档)。
改完在 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 件
- 崩溃 11 → 0,failed 17 → 0;
- 44 件真实钣金件的板厚/折弯/毛坯/形态零变化;
- 自动通过 32 → 32;needs_review 49 → 30;not_sheet 3 → 50。
全套 764 项。
0.16.0(2026-08-02)
用户传上来 65 件真实生产图纸(SF26130 批次),结果是 11 件直接崩、17 件 「分析失败」、零件自动通过。逐个查下来是三个互相独立的洞,都不是"这批件特别 怪",而是算法与语义本身有问题。
1. 11 件崩溃:采用项被自己的噪声闸滤掉了
from_masses 的 min_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 件)验证
- 崩溃 11 → 0,failed 17 → 0;
- 自动通过 32 → 32(一件真实钣金件都没被误伤);
- 折弯数零变化;毛坯只在那 3 件被重判为棱柱件的上变化(它们不再出钣金毛坯, 正确);
- 3 件 needs_review → not_sheet,都是 87×40×27 / 62×40×27 / 37×19×17 这样的块料。
三处修复各做了突变验证。新增 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 把计算包的字段目录 与系统层这几列拼成一份,界面与导出都走它,两处不必各写一遍。
文档
- README 加「它解决什么问题」(难点不在读几何,在读的是工艺)与「功能一览」, 并新增「数据存在哪里」一节:完整目录树、每个文件装什么、权限口径, 以及缓存那把四段钥匙为什么这么设计;
- 介绍页特性卡按现在的产品重写(补上 ZIP 来源、结果落盘两条), 示例表加一列「来源压缩包」,把这件事一眼讲明白。
0.13.0(2026-08-02)
支持上传 ZIP:自动解包、挑出 STEP、把目录并进文件名。
订单A/支架/左板.step → 订单A__支架__左板.step。系统里平铺存放,所以来源 必须写进名字里——否则一包传上去,谁是哪个单子哪个部件就全糊了。列表里、导出的 表里都一眼看得出。
上传口按内容判而不只看后缀(PK\x03\x04),有人把 .zip 改名成 .step 传上来也认得出,不会当成一个坏 STEP 存下。
三类雷,逐个拆
路径穿越(Zip Slip):条目名写成 ../../etc/x.step 或 C:\... 就能解到 目标目录外面。这里从不把条目名当路径用——只取各级名字、逐级清洗、再拼成 一个平铺文件名。穿越在结构上就不可能,而不是靠事后检查漏没漏。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,硬解会把好名字改坏。
其他
- 嵌套 ZIP 不递归:逐层展开会让来源路径失控,也给炸弹可乘之机。明说跳过。
- 解出来的名字要过手工上传那道文件名闸(控制字符、引号、路径分隔符)—— 否则 ZIP 就成了绕过清洗的后门(响应头注入那条正是从这儿走的)。
- 撞名不覆盖,按
_1 递增;非 STEP 条目跳过并给出理由,前端把"解出 N 件、 跳过 M 项"如实告诉用户。 - 解包同样受账号隔离约束,不能借 ZIP 往别人目录里塞文件。
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 并在指纹中排除。pyproject 的 attr 也要跟着 指过去: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 等层次令牌与 统一圆角。
- 表格:装进有边框有阴影的容器里,比一张裸表更像"一块东西";表头小号大写 加字距、行分隔线更浅、hover 有底色、选中行左侧一条强调色边;数字全部
tabular-nums 对齐; - 状态徽章加了小圆点——一屏几十行时颜色比文字先被看见;
- "不适用"改成极淡的斜纹底:与"有数"和"—(没值)"三者一眼可分,又不抢眼;
- 筛选框内嵌搜索图标、聚焦有柔和光环;缩略图框加微渐变,hover 转强调色;
- 关键事实卡片、下拉面板、登录卡片都给了合适的阴影与圆角层次;
- 落地页首屏:标志后一团很淡的强调色光晕做视觉重心,标题用
clamp() 随屏 缩放,特性卡 hover 微微抬起; - 全站统一
:focus-visible 焦点环(键盘可达性,也是细节)。
0.11.0(2026-08-02)
交互优化,按"实际用起来卡在哪"来定,不是按"该有什么功能"。
列表页:43 行靠滚是找不着的
- 筛选框:输入即筛文件名,按
/ 聚焦、Esc 清空;页头实时显示"显示 N / 共 M"; - 状态快捷筛选:全部 / 待复核 / 非钣金件 / 未解析。这份语料里 43 件有 10 件待复核,以前得一行行找;
- 状态是单独带在行上的,不从选中的列里取——用户把"状态"列取消勾选后, 按状态筛选不该跟着失灵;
- 整行可点(避开勾选框、按钮、链接),缩略图也是链接;缩略图对读屏器 标为装饰性,不重复念一遍文件名;
- Shift 连选:勾一行、按住 Shift 勾另一行,中间整段一起选中;
- 筛掉的行会自动取消勾选——否则"已选 5 个"里混着看不见的行,导出时才发现不对。
详情页:八九节、上百屏
- 章节跳转条(粘在顶部):三维视图 / 三视图 / 关键事实 / 展开图 / 折弯明细 / 孔与切割 / 通用几何 / 标记,一键直达。needs_review 的件最该先看的「标记」 以前在最下面;
- 锚点是拼好之后统一加的,不改十来处
parts.append("<h2>…")——将来加新章节 自动就有,不会漏; - 上一件 / 下一件(含「第 i/N 件」),快捷键
j / k。逐件核对几十个零件时, 不必每看完一个就退回列表再找下一个; - 这条导航是发送时填的,不是生成时烤进去的:报告 HTML 有缓存,烤进去的话 之后新传一个文件,旧报告里的「下一件」就指错甚至指向不存在的件。
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);真正落料/冲床专有的孔切割周长、孔阵列才标 不适用。另新增两个通用字段(孔状特征数、回转特征清单),非钣金件那一行不再几乎 是空的;面数也改为在通用几何里兜底。
表格格式:每列自己对齐
- 小数位由单位定而不是由值定(mm 一位、mm²/g 零位、比值百分号)—— 同一列位数一致,小数点才对得齐;
- 对齐看字段不看值:以前一列里有数有"—"时,空格子按值判成非数值靠左, 整列参差。字段是不是"量"现在写在数据字典里(计数类没单位但仍是量);
- 状态与形态在 schema 层就出中文,表格与导出不再一个显示"非钣金件"、 一个写
not_sheet。
解析结果落盘:重启不丢,算法一改自动作废
内存缓存进程一停全没,所以每次更新服务,目录页又全变回"未解析"。新增 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 个分组里逐个勾选,勾完即刻决定表格显示与导出内容, 不会再出现"表里的数和导出的数不一样"。
- 默认列覆盖要紧的数:状态 / 形态 / 板厚 / 外形 L×W×H / 毛坯 / 折弯道数 / 孔数 / 切割总周长 / 估重;
- 列选择器按分组罗列,每组可整组全选/清空,另有「恢复默认」;
- 选完的列进 URL(
?cols=a,b,c),可直接把某套列的视图发给同事; - 点表头排序:数值按数排、文本按中文排,空值恒沉底(否则一屏的"—" 会把有值的挤走);
- 表格自带滚动区,表头与工具条常驻可见。
导出:逐列 + CSV
POST /export 接受 c=<字段key> 逐列指定(老的 g=<分组> 仍可用);- 一列都不勾 = 导出全部 60 个字段,不再自作主张挑默认几组;
- 新增 CSV(
fmt=csv)。写出器 server/csvout.py 仍只用标准库, 且写 utf-8-sig——没有 BOM 的话 Excel 打开中文表头是乱码, 这是个必踩的坑;行尾 CRLF(RFC 4180)。逗号与引号按 CSV 规则转义, 真实图纸名("Describe Document - AGS000190-01, PLATE, SEI, 00.1") 逗号成堆,不转义会把一列撑成好几列。
列表页真的能"一屏看清"了:摘要缓存与报告缓存分家
报告缓存每条含完整 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 偏大。)
三条自我约束:
- 解不出就不猜。缺口 ≤0,或解得比成品孔还大(料只会把孔撑大,不会缩小), 一律拒绝,退回"保留成品孔 + 报出来请人工给"。合成夹具
flanged_hole 正是 这种——它是把一段管子并到板上做的,材料本就不守恒。 - 不由 1.5t 那条线决定。领口矮到孔心离板面 ≤1.5t 时孔映射得上,此前就画成品 孔;现在不论高矮,凡翻边孔一律走同一条反解。
- 代价写在标记里:这么一来体积恒等式在该件上被"用掉"了,它是解的依据, 不再是独立校验,所以标记留在 warn 一级请人核对。
手算核对:φ91.8→φ107.6 之间那圈料 2474 mm²,领口中面展开 2085 mm² 加根部圆角, 量级对得上。另有量纲门禁:整件缩放 k 倍,解出的预冲孔直径必须也是 k 倍。
验证
- 全语料 45 件:毛坯尺寸零变化、折弯数零变化;2 件 needs_review→ok, 6 件消掉误报的
LOOP_CYL_MISMATCH,ets01835 孔数 41→37(4 个压窝沉头孔 不再各数两遍)且 UNFOLD_AREA_MISMATCH 一并消失。没有一件变差。 - 突变测试:6 个补丁逐个摘掉,对应测试全部变红。
- 两件真实图纸进
tests/data,新增 tests/test_formed_features.py(10 项, 含刚体变换不变性、缩放协变性与"两块板上的开口不许合并"的反向断言)。 全套 627 项。
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。
顺手收紧的两处(没被打穿,但离得太近)
_resolve_or_404 身份缺失时的默认值是"解析到服务根"。今天所有路由都会先 设身份所以走不到,但只要有人加一条路由忘了设,就是一次彻底的隔离失效, 而集成测试会全绿。改为 403。这条走不到的分支专门写了单测。_ip() 无条件采信 X-Forwarded-For,等于让人往审计日志里灌假 IP。 改为默认用 TCP 对端地址,确有反代时 MM_TRUST_PROXY=1 开启。
没打穿的部分(记下来,将来别退回去)
伪造/畸形/空会话 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.py 把 server 从 sys.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, 不值当破"除几何内核外零依赖"这条线。
账号体系与管理后台
- 口令 PBKDF2-HMAC-SHA256 + 每用户随机盐,明文永不落盘;
- 会话是 HttpOnly Cookie,服务端可吊销;停用或改密即时踢下线;
- 文件按账号隔离:
<服务目录>/<账号>/,越权与路径穿越一律 404; /admin:建号、停用、看每个账号的登录与操作记录;- 首次启动自动建管理员并打印一次性口令。
--no-accounts 保留旧的纯令牌模式(本机自用)。那条路径下没有后台也没有 会话,页面就不显示退出/后台链接——摆着点了就 404 的链接比没有更糟。
目录页重做
一行一件,直接给出状态、形态、板厚、外形、折弯数、孔数、毛坯尺寸; 0 也如实显示 0(此前 falsy 被渲染成"—")。勾选即可导出,每行有「重算」。
顺手修掉的两个前端 bug
- 多选上传只传了第一个文件(
return 写在循环里); - 导出未解析的文件是现算的,几十件要几分钟,而表单直提时浏览器只转圈, 看着像卡死。改成 fetch + 进度浮层,并说明"其中 N 个是首次解析"。
测试
新增 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 差异全部通过
- 曲面退化:经中转格式往返后平面/圆柱被写成 NURBS(靠拟合恢复)✓
- 实体取向反转:部分导出链产生内向法向 ✓
- 皮肤被切成多张共面面:不同 CAD 内核的切分习惯不同 ✓
- 确定性:同一份文件连analyze 两次,逐字段相同 ✓
不可展成形特征必须被抓住,不能静默吸收
压包/凸包/百叶窗拉伸了材料——毛坯比展平后的轮廓大,几何上看不出来。 新增球冠压包夹具,验证三条互相独立的判据都报警:未解释面积 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 项)
同一零件换个"写法",结论必须不变——不变量破了就说明算法依赖了偶然条件:
- 面序无关:墙面列表乱序 12 次,配对结果必须一致;
- 位置无关:摆到离原点 10⁵ mm,板厚/折弯数/毛坯不变;
- 镜像对称:左右件结论相同;
- 缩放协变:支持区间内(0.25×–5×)等比缩放,结果等比;
- 越界拒答:0.1× / 10× 落到板厚区间外时判
not_sheet 并给通用几何事实, 不猜一个数往下算。
全语料精确命中 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%,且没有任何提示。现在按成因分开报告:
UNFOLD_HOLE_UNMAPPED(warn):轴向对得上但离板面太远 → 翻边/拉伸凸缘孔, 毛坯上应是更小的预冲孔,直径取决于翻边工艺,请人工确定;UNFOLD_HOLE_ON_BEND(info):轴不平行任何面板 → 斜孔或打在折弯圆弧上, 通常折后加工。已有 SLANTED_HOLE 说明工艺影响,这里不重复扣分。
平板件也跑展开几何
此前只有折弯件跑 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)
统计口径统一:切割总周长等信息不再因件型而缺失
- 多轴件此前没有切割总周长(容斥公式只对单轴成立,
outer_contour_mm 是 None)。 现在所有口径统一走展开几何:外轮廓 = 各面板/条带周长之和 − 折弯线处的内部拼缝(每条带减 4×折弯线长)。这是精确值,平板/单轴/多轴 一个算法;顺带修正了尖折弯件——旧的容斥估算沿用外皮伸到尖角的口径, sharp_L 偏大 4 mm(198.5 → 194.5,手算 2×(57.26+40)=194.52)、z_offset 偏大 8 mm; - CSV 补齐
outer_contour_mm、blank_length_min/max_mm、blank_width_min/max_mm、 developed_area_ratio;终端表、HTML、CSV、JSON 四个出口口径一致。
修:穿孔次数把拼接件报少了
pierce = 1 + 孔数 假定只有一条外轮廓。拼接件展开后是几块独立料片, 每块都有自己的外轮廓、各要穿孔一次。改为 料片数 + 孔数 (welded_bracket 1 → 4)——激光工时输入此前偏低。
公式的独立路径复核固化为门禁(tests/test_formula_crosscheck.py,64 项)
每条公式都用另一条算法或物理恒等式验,不拿实现对实现:
- 切割总长(二维展开周长)vs 厚度面总面积 / t(纯三维面积路径)—— 干净件吻合到 0.991–1.000;
- 孔切割周长 vs Σπd(解析值)——0.9974–1.0000;
- 展开净面积 vs V/t(体积守恒,中面口径);
- 总长 = 外轮廓 + 孔;穿孔次数 = 料片数 + 孔数;利用率 ≤ 1。
排除在"干净件"之外的三类反向也验:卷圆(弧面未展开)、翻边孔(领口是成形 不是切割)、沉头/沉孔(锥面不由激光轮廓切)——测试断言它们确实分叉, 免得排除名单退化成"凡是对不上就加进来"的清单。
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_RADIUSED、DECISION_RISK、UNFOLD_DISCONNECTED、 UNFOLD_ORPHAN_FACE 一并消失,status 由 needs_review 转 ok; 3. 随之不再有"拼接件"误判,两件都回到单一料片。
13 件 × 6 种姿态毛坯偏差仍为 0.0000 mm;变异测试确认门禁能抓住这条判据的回退。
0.6.4(2026-07-31)
K 因子:从藏在默认值里的超参数,改为明写出来的不确定度
- 多轴件的毛坯此前只给单值,等于把某个 K 的结果当成"那个答案"。现在两个 方向都给 K∈[0.33, 0.50] 的区间(多轴件各道弯轴向不同,两个方向都随 K 变; 单轴件的宽沿折弯轴,与 K 无关,仍不给宽区间)。
build_developed 只要 ~10 ms, 两端各算一次可以忽略; - CLI 全线加
--k-factor(车间知道自己用哪个),越界直接拒绝——中性层不会 越过板厚中面,K > 0.5 物理上不存在。
无可调参数的正确性判据:体积恒等式
折弯是塑性变形、材料不增不减,而 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
mcp ≥ 2 把 FastMCP 拆成了独立包,mcp.server.fastmcp 不再存在。 导入改为两条路径都试;MCP 测试改用 importorskip 跳过——可选集成的 上游变动 不该让核心库的流水线变红。
其它
- 排除又单皮、材料又只有一个板厚宽的连通块(型材端部截面、端板侧面被误判 成墙面):判据用等效条宽 = 2·面积/外环周长,与形状无关。全语料实测 分布干净——真面板最窄 2.22t,厚度面全部 0.97t,阈值 1.5t 落在空隙中间;
- 两件真实客户件连同标准答案固化进
tests/data/; - 新增夹具
hem_tight(Ri = t/2,折回臂与底板间隙恰好 = t 且投影完全重合)。
0.6.2(2026-07-28)
修:拼接件的毛坯利用率误报不自洽
- 0.6.0 把拼接件的毛坯拆成逐块之后,利用率的分子仍是整件材料(V/t)、 分母却只剩主料片——其余料片的材料算进了分子没算进分母,利用率被凭空 抬过 1,
UNFOLD_INCONSISTENT 误报(adx132980 报 1.045,实为 0.959)。 分母改为全部料片毛坯面积之和; - 新增判别夹具
welded_bracket(角支架 + 对接端板):面板图分成 4 块, 测试同时验证「逐块给毛坯」与「只按主料片当分母就会超 1」这两面。
0.6.1(2026-07-27)
每个零部件都能单独重算
- 文件列表逐行、装配 BOM 逐行、零件详报导航栏各一个「重算」按钮, 对应
POST /recompute?path=…[&sub=N]; - 丢缓存后当场重算,算完才回应——只丢缓存就返回的话,页面一刷新还是在等, 并发刷新还会各自触发一次分析;前端拿到响应再刷新,看到的一定是新结果;
- 整件重算连同它的所有装配零件缓存一起丢:总览换了新结果、零件详报还是旧的, 两边对不上比不重算更糟。
0.6.0(2026-07-27)
展开毛坯:多轴件真正算出长宽,不再降级
- 面板树改为通用二维等距摊平——每块面板各带一个 3D→2D 刚体映射,绕折弯线 转入父面板平面。折弯轴方向不再受限,长槽件 + 两端折耳、四面盒这类多轴形态 同样输出毛坯与展开 DXF(此前一律
multi_axis_unsupported 转人工); - 毛坯取最小面积外接矩形而非轴对齐包围盒:二维标架朝向是任取的,轴对齐盒 会随零件摆放而变(metamorphic T1 抓出),最小外接矩形既是姿态不变量, 也正是套料要的最小毛坯;
- 尖折弯展开长修正:平段止于内棱(切线位置)而非外皮伸到的尖角, 外皮比切线多伸 t·tan(θ/2)。sharp_L 由 59.26 改正为 57.26,与折弯扣除法 独立复核一致(旧测试是照着实现写的,把这个错误一起冻住了,一并纠正)。
上游三处配对/归并缺陷(真实件与段差夹具抓出)
- 面板配对必须带符号:另一张皮要在材料一侧(
(v−w)·n_w ≈ −t, FaceRec.normal 是材料外向法向)。只比距离绝对值会在段差/Z 形件上配错—— 上板内皮与下板外皮的间距同样是 t,中间夹的却是空气。配错以后整块面板 以错误的皮进展开树,毛坯凭空少一块(AGS001938 少 29%); - 配对改为全局最佳匹配:候选对按「面内形心距 + 面积不吻合度」排序后再贪心。 按面序先到先得会让先出现的小面抢走大面唯一的对皮(adx139058 上 184 mm² 的 小面抢走了 17672 mm² 的大面),真正的一对反而落单;
- 尖折弯按面板对归并:同一道弯的内外两条棱连接的是同一对面板。只按 「平行 + 间距 < 3t」归并,在段差件上会把相邻两道弯的四条棱串成一片, 归并出的"折弯"轴点落到别的角上,展开时整块面板接不上。
展开轮廓与自洽性
- 面板轮廓改用按线序遍历外环(
ordered_wire_points)。原先复用的 outer_wire_samples docstring 就写着"无序,用于跨度/投影度量"——当多边形用会 算错面积、把半平面裁剪切乱,还会在 DXF 里画出穿过零件的假线; - 折弯连接哪两块面板改由折弯自身几何判定:轴到面板平面的距离 = r 或 r+t, 且垂足落在那张面上(段差高度恰为 2t 时前一条会凑巧成立,只有后一条能排除);
- 面板图不连通即拼接/焊接件:块间无折弯相接就不存在单一毛坯。各连通块分别 摊开并排布,逐块给毛坯尺寸,报
UNFOLD_DISCONNECTED; UNFOLD_INCONSISTENT 自洽性检查不再只对单轴生效——多轴毛坯现在是真算的, 正是最该自查的地方;展开几何自己的告警此前根本没传到结果里,已接通。
新增判别夹具与门禁
z_offset(段差 Z 形):段差高度必须是 2t 才能触发面板配对缺陷,专盯带符号判据;- 展开净面积 ≡ V/t 恒等式门禁:与展开算法完全独立,抓「面板漏摊」(比值偏小) 与「面板重叠/重复计入」(比值偏大)。真实件语料 20 件全部收敛到 ±7% (AGS001938 0.659→0.945、adx139058 0.757→0.992、notch_ears 0.516→0.949), 卷圆件除外(已有
UNFOLD_APPROX); - 面板轮廓有序性、连通块数、段差展开长三项固化为回归测试(209 项全绿)。
0.5.3(2026-07-27)
折弯计数:零半径直角不再被误计(真实件 ADX132980 报 11 道,实为 5 道)
- 建模一致性判据:真实钣金没有 R=0 的折弯,设计者也不会一半建圆角 一半不建。同一零件里既有带半径折弯、又有零半径直角时,后者多是 拼接缝/焊缝/贴合面(如端板抵住侧板、筋板立在面板上)——按疑似拼接 不计入折弯,出备选解释(p=0.75/0.25)+
SHARP_WITH_RADIUSED, 强制人工复核,不静默计入也不静默丢弃; - 整件都没建圆角时(纯尖角件),尖折弯照常计入——判据只在混用时生效;
- 新增判别夹具
mixed_corner(R3 折弯 + 立在面板上的筋板)。
展开(折弯摊平)复核与呈现
- 用完全独立的算法(行业标准「外形尺寸 − 折弯扣除」)交叉复核展开长度, L 件 / U 件 / DYL 真实件三例吻合到 0.01 mm 以内,已固化为回归测试; 180° 包边走「平段 + 让量」路径(折弯扣除公式在 180° 发散,不适用);
- 呈现修正:原先标成「长 239.2 × 宽 316.0」——宽比长大,读起来是错的。 改为按大小排序的
blank_size_mm(长 ≥ 宽),方向语义 (展开方向 / 沿折弯轴)作为附注保留;CSV 增加 blank_long_mm/blank_short_mm。
0.5.2(2026-07-27)
统计口径明确化
- 体积改以立方毫米为主单位(附 cm³),并补进批量 CSV(此前 CSV 无体积列);
- 展开料尺寸明确标注「长 × 宽」,不再只给两个数;
- 「下料切割」正名为切割总周长(外轮廓 + 孔),与 CSV 列
total_cut_mm 对齐; - 终端表、HTML 关键事实卡片、CSV 三处口径统一。
性能(多孔件)
- 先校验后修复:形状本就有效时跳过 ShapeFix(682 面件省 2.9 s); B-rep 校验开并行(墙钟 7.5 s → 4.3 s);
- 相切判定走解析法向:平面/圆柱的法向可直接算,省掉
ShapeAnalysis_Surface 曲面投影——原占 P2 的绝大部分(5.8 ms/边 × 2040 边); - 空间索引(
spatial.py):共线轴线按"原点垂足"分桶,配对/板厚通道 B/ 孔同轴聚类由 O(n²) 降到近 O(n); - 内环空腔判别移到配对后,实体分类调用减半。
- 实测:225 孔 5.7→3.6 s(1.6×)、400 孔 15.4→11.8 s(1.3×)。 诚实说明:682 孔仍约 35 s,剩余成本在 OCCT 内部(STEP 读取、 B-rep 校验、内环采样),非本包 Python 代码——大件仍慢。
0.5.1(2026-07-26)
- 装配体逐件详报可达:BOM 表零件名成为链接,点进去是该零件自己的 完整报告(三维视图 / 展开图 / 风险清单 / 返回装配总览)——装配件不再 只有体积最大件可看;路由
/part?path=…&sub=N,结果同样走 LRU 缓存; - 长分析有反馈:首次分析可能十几秒,点击后弹遮罩并显示已用秒数 (导航期间旧页面仍在,遮罩持续可见,无需改造成异步请求); 已缓存的文件标
data-done=1,秒开不打扰; - CI(
.github/workflows/ci.yml):静态检查 + py3.10/3.12 测试矩阵 (pytest + metalmaster selftest)+ R 层棘轮;用蓄意回归的临时分支 实证三道关确实会失败,而非只看首跑绿灯; - 修
scripts/scoreboard.py 缺陷:原先检测到回归后仍覆写基线, 导致坏结果成为新基线、棘轮只拦得住一次;改为回归时保持基线不变。
0.5.0(2026-07-26)
人能在浏览器里看:本地网页查看器 + 可转动的三维视图。
三维查看器(按算法角色着色)
tessellate.py + viewer.py:STEP → 三角网格(按识别出的角色分组)+ CAD 原始边线 → 内联 WebGL 查看器(无 three.js 等外部库,CSP 友好);- 面按角色着色(皮肤/折弯/孔壁/侧面/未解释)并配可点选图例——三维视图因此 是一张算法理解力的体检图,而不只是模型预览;
- 索引化几何 + 精度裁剪把网格体积压到原来的 47%(真实件 890→416 KB);
- 交互:拖拽旋转 / 滚轮缩放 / Shift 右键平移 / 双击复位;WebGL 不可用时 降级提示,二维三视图与数据不受影响;
report --no-3d 可关闭以减小体积。
网页查看器(metalmaster serve)
- 本地站点:列出目录 STEP、拖入即分析、点开即看完整报告,页内下载 展开 DXF / 原始 STEP / JSON;
- 同一服务对 agent 供 JSON API(
/api/files、/api/inspect、/api/analyze), 人和机器看同一份事实; - 只用 Python 标准库(http.server),不为查看页引入 Web 框架依赖; 默认仅监听 127.0.0.1,路径限制在服务目录内(越界 404), 结果按 mtime 缓存(二次打开 ~2 ms)。
对外访问
serve --host 0.0.0.0 时自动生成访问令牌(服务带上传功能,不应在 公网裸奔);令牌经 ?t= / X-Token 头 / Cookie 三选一校验, --token "" 可显式关闭;启动横幅打印可达地址与暴露提醒。
并发与内存(Web 服务)
- OCCT 运算全局串行:ThreadingHTTPServer 下原本两个请求可并行进入 OCCT 几何运算,而 OCCT 建模算法普遍不保证线程安全——改为加全局锁排队;
- 同文件并发只算一次:按路径加锁 + 双重检查,后到线程直接吃缓存;
- 缓存改 LRU 且限条数(默认 24):每条含完整 HTML(真实件 0.5 MB), 原先无上限,长跑服务会持续吃内存;逐出时同步收缩锁字典。
代码整理
- ruff(F/E9)全清:移除 15 处未用导入、1 处遮蔽几何工具的循环变量 (
sub → panel_group)、1 处空 f-string; - 文档与代码一致性核对:7 个 CLI 子命令、6 个 MCP 工具逐项对齐。
182 用例全绿(新增 23:Web 协议层 15 含真实并发用例 + 三维网格/查看器 8)。
0.4.0(2026-07-25)
定位扩展:从「钣金报价分析引擎」到「STEP 认知服务(钣金深度专长)」。 agent 能了解任意零件/装配体,人能操作、查看、验证。
认识任意 STEP(不限钣金)
assembly.py:XCAF 装配结构——零件清单、STEP 产品名、实例数与位姿; XCAF 原型 + (体积,表面积) 几何哈希双重去重,重复件 ×N 作为报价用量信号 (真实件的产品名 "DYL-中-13" 现已读出并进入报告);facts.py:任意实体都成立的通用几何事实——拓扑计数、按曲面类型的 面积构成、圆柱特征清单(孔/凸台按材料侧区分)、质量属性与惯性回转半径, 据此给出零件类型判断(钣金/回转体/棱柱/薄壁/自由曲面)及其依据;- 非钣金件不再只是拒答:
NOT_SHEETLIKE 现在说明"它更像什么", 并照常输出 geometry 段。
人能看见与验证
projection.py:HLR 消隐三视图 → SVG 线画,按最优姿态投影, 与报告的长×宽×高同一口径;htmlreport.py + metalmaster report:自包含单文件 HTML——三视图、 关键事实卡片、装配零件表、展开图(面板/条带/孔位)、折弯与孔明细、 风险与备选解释(概率条)、标记清单;明暗主题自适应,无外部依赖;metalmaster inspect:一屏认清一个陌生 STEP。
装配体 BOM 级认知
analyze_parts():逐件分析装配中每种零件(实例共享结论,按种类算 一次)——类型判断、外形、估重、钣金口径;metalmaster inspect --parts、 MCP inspect_step 与 HTML 报告的装配表均已接入。
agent 入口
- MCP server 增加
inspect_step(认识任意 STEP,首选入口)与 html_report(把核对工作交给工程师),共 6 个工具。
人能操作与验证
metalmaster batch --html-dir:逐件 HTML 报告 + 索引页,工程师批量复核;- 译码器占位名("Open CASCADE STEP translator …")视同未命名, 调用方回落到文件名等有信息量的标识;真实产品名照常保留;
metalmaster selftest:不装 pytest 也能验证安装与算法——12 个判别夹具 的构造参数即 ground truth,逐件比对、退出码即结论;--html 另出报告 供肉眼核对;- 屏蔽 OCCT C++ 层横幅输出,CLI 终端结果干净可读。
159 用例全绿(新增 15 个服务层测试:装配去重与逐件分析、非钣金事实、 三视图口径一致性、HTML 结构与内容、CLI/MCP 入口、自检命令)。
0.3.2(2026-07-25)
补齐设计文档 §7 干涉矩阵的逐行绿测(M4 完成定义中一直欠账的一项), 4 个新判别夹具当场抓出 2 个真问题:
- n_geo 规格违背(修复):原按"共享任一墙面即合并"做 union-find, 共轴独立翻边耳共享同一块底板 → 被错并成 1 个几何折弯。改为按 所连墙面集合(面板对的代理)是否相同分组:被槽打断的一道弯集合 相同→合并,双耳集合不同→各计。FX-NOTCH-SWEEP 判别夹具(缺口由浅 至深,n_op 恒 1、n_geo 由 1 跳 2)把 §1.2 口径钉死在测试里;
- 弯区/斜面上的孔不可见(修复,风险登记册 R16):内环法只扫平面墙, 轴不平行于任何面板法向的凹圆柱此前落入"板边特征"。改按第一性判据 ——材料在半径外侧即为洞,与轴向无关——计入孔并打
SLANTED_HOLE (需二次装夹/折后加工的工艺信号); - 翻边孔检出结论上浮为报告层
FLANGED_HOLE flag(§7-e 要求"flag 记录检出"); - n_geo 分组新增边界决策:一片墙面集合是另一片真子集时(疑似相切面 缺失)出备选解释;双方各有独有面属双耳正常签名,不打扰用户。
- 新夹具:hole_on_bend / hole_straddle / notch_ears / flanged_hole, 全部纳入 metamorphic 门禁。144 用例全绿(原 113)。
0.3.1(2026-07-25)
- 展开 DXF 非圆孔精确轮廓:内环有序采样多边形贯通保留 (loops → holes → develop),矩形/异形/腰形孔按真实轮廓输出 (周长与切割长一致),圆孔保持 CIRCLE 实体——消除 0.3.0 的 等效圆近似局限;
- MCP server 新增
unfold_dxf 工具:编排器一次调用拿到展开 DXF 文件与摘要(范围/面板/条带/孔数),多轴件降级不写文件; - R22(卷圆多段弧合并)评审后继续搁置:等半径相切弧几何上即同一 圆,真实分段近似形态需实例才能定判据(无语料不预做)。
0.3.0(2026-07-24)
展开几何(develop.py):从毛坯口径到真实套料输入。
- 沿"面板—折弯"树做平面化映射(每面板一个仿射映射,折弯让量作条带插入), 输出展开态 2D 几何:面板外环轮廓(含缺口)+ 折弯条带 + 展开孔位;
- DXF 导出(R12 子集 LINE/CIRCLE,任何 CAM/套料软件可读): 图层 OUTLINE / HOLES / BLANK;新 CLI 子命令
metalmaster unfold; - 展开跨度与毛坯长度互为校验(DYL 真实件 316×239.3 精确吻合, 70 孔全部映射为 DXF 圆);多轴折弯照旧诚实降级;
- Report 保留 internals 引用供二次加工(不入 JSON);
- 修复:两道弯轴向符号相反时条带区间未换算到统一坐标(DYL 曾展开出 632mm)。
- v1 局限:非圆孔在展开图中以等效直径圆近似;尖弯件轮廓取外皮肤(略偏大, 对套料是安全方向)。
0.2.1(2026-07-24)
工艺语义增强(全部影子模式,不改计数):
- 孔阵列识别:同规格圆孔的行/栅格阵列(最近邻方向投票 + 等距校验 + 对称面板同构合并)——数控冲/激光的编程与工时信号;DYL 真实件 70 孔全部归入阵列(4行×13@22 + 2行×6@50 + 2行×3@100)。
- 段差(joggle)分诊:平行反向、间距 < ~5t 的双弯出决策 (两道独立弯 vs 段差模一次成型),间距越近越强制人工复核。
- 回弹标称角度注记:折弯角偏离常用标称 (0.3°, 2°] 提示 回弹补偿建模嫌疑,报价/展开按标称口径确认。
- 明细表折叠:同规格孔超 3 个折叠为汇总行(70 孔件不再刷屏)。
- 性能:热路径去冗余归一化与元组分配(parallel/line_point_dist), 400 孔件回归绊线(进程 CPU 时间 < 15s)+ make_perf_plate 夹具。
0.2.0(2026-07-24)
定位定稿:独立发展的钣金 STEP 报价分析引擎(基础组件)。
新增能力
- 内环第一性原理:孔 = 墙面内边界环,形状无关的完备枚举(矩形/异形孔 可见),精确切割周长 + 最小外接矩形,与圆柱路径双向互验(
loop+cyl); 凸台/压凸根部内环经实体空腔判别排除。 - 展开料尺寸:单轴轮廓展开,K 因子行业区间 [0.33, 0.50] 输出上下界; 多轴折弯诚实降级。
- 报价工艺输入(
quote_inputs):外轮廓切割长度(容斥公式)+ 穿孔次数、 展开净面积与毛坯利用率(V/t 恒等式,兼作展开自洽性校验)、最长折弯线、 方向分布与翻面、最小翻边宽、二次工序清单。 - 决策记录机制:边界/冲突情形输出备选解释与确定性伪概率 (logistic 边际/投票质量占比),采用项概率低时扣置信度强制人工复核。
- NURBS 拟合恢复:转换链退化的解析面(平面/圆柱)采样拟合重建, 纯 NURBS 输入恢复与原件一致的全部统计。
- 工艺注记:螺纹底孔先验(M2–M12)、近弯孔警告、单位/规格先验检验 (狙击 25.4 倍静默错误)、多实体去重清单、小于板厚孔警告、短翻边警告。
- 容差自适应:长度判据容差挂钩形状容差 P95(脏模型)。
- MCP server(
metalmaster[mcp] / metalmaster-mcp): analyze / quote_inputs / operations_estimate 三工具,Agent 编排器 零代码挂载;metalmaster.quoting 提供确定性工时模型(费率归宿主)。
质量机制
- Metamorphic 门禁 T1–T7(刚体/镜像/缩放/加孔/切槽/双 schema/NURBS 退化), 零标注成本;上线首日抓出 2 个潜伏缺陷。
- R 层真实件回归(expected.json 冻结 + 容差比对)+ 记分板棘轮 (
scripts/scoreboard.py,对→错即失败)。 - 8 个判别夹具(构造参数即真值),阈值-夹具配对。
- 裁定回流:
metalmaster_verdict.json schema + scripts/collect_verdicts.py 校准收集器。 - 96 个测试。
修复(均由门禁/判别夹具抓出)
- 非轴对齐姿态下尺寸计算方向反转;等面积皮肤平局导致折弯方向标签不稳定;
- 切割侧壁间距恰为 t 时误配成墙(假尖折弯);
- 包边层叠皮肤的 2t/3t/4t 谐波伪候选稀释板厚投票;
- 卷圆大扫掠规则与决策概率阈值不一致;
- 毛坯利用率分子分母 K 口径不一致导致伪超 1。
0.1.0(2026-07-23)
首版:P1–P8 流水线(STEP 载入修复、面分类、板厚双通道、Δr=t 折弯配对与 闸门验证、尖折弯补全、n_op/n_geo 双口径聚类、孔工艺分类、面板对齐外形、 置信度评分),CLI/Python API 双入口,4 夹具端到端测试。