v2.1: DESTACO 风格选型界面 + 参数组合补全(缺组合也能出零件号)
网页版选型改版 (仅网页版, 桌面版待同步)
- 新增零件号实时条: 边选边拼, 已确定片段实心高亮, 未定片段虚化, 进度条随选推进;
浅色配色 (初版深色块与页面不协调, 已改); 选满且唯一命中时整块转青色实心
- 技术方案: 40 系列中仅 KC 有编码模板 → 改用「当前候选型号的最长公共前缀」,
已验证 40/40 系列前缀单调增长(不回退)
- 参数选择由下拉菜单改为可视化选项块: 悬停抬升+青色投影, 选中青色填充+✓,
不可选取值置灰(虚线+划线, 不消失, 让用户看到选项存在但当前组合不适用),
再点一次取消; 有说明的显示代号+说明, 纯数值用紧凑文字块
- 参数联动仍复用 allowedOptions 规则引擎, 语义未变, 只改渲染
参数组合补全
- 原逻辑: 必须有真实数模才能生成型号 → 库里没有的组合选不出来也出不了号
- infer-sku-template.py 从已有型号反推编码模板, 得 23 个系列 100% 可复现
- verify-templates.py 作为门禁: 用模板重渲染每个已有型号必须逐字一致 ——
该检查抓出 ULM/ULP 行程补零位数写错(2位写3位), 否则会生成 3000 个不存在的零件号
- gen-missing-combos.py 补全 8 个系列共 3157 个型号 (全库 54136 → 57293)
- 占位数模: 3157 个型号共用 1 个 PLACEHOLDER.step(4.5KB), 构建器按路径打包只存一份,
数据包仅 2.10GB → 2.14GB (中途用硬链接方案踩坑: NTFS 上限 1024, 超出退化为拷贝白占 500MB)
- _pending_models.csv 输出待补数模清单, 供 AI 补完后替换 step_file
新增脚本: infer-sku-template / verify-templates / quantify-combo-gap /
gen-missing-combos / gen-placeholder-step / check-combo-unique
新增文档: docs/更改说明-v2.1.md
发现未处理 (已记录在更改说明):
- 夹紧缸 9 系列一组参数对应多型号 (310 个型号, 编码含参数外尾缀, 旧码/新码混用)
- UAGP/LAGP 参数顺序与官方编码结构不符 (传感器应在转角之前)
- 17 个系列因模板不干净未补全 (宁可少补, 不生成可能不存在的零件号)
Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
3158
onebot-data/catalog/_pending_models.csv
Normal file
3158
onebot-data/catalog/_pending_models.csv
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,3 +18,176 @@ ULA63040AD0,63,40,A,,D,ULA63040AD0.step
|
||||
ULA63040AK0,63,40,A,,K,ULA63040AK0.step
|
||||
ULA63050AK0,63,50,A,,K,ULA63050AK0.step
|
||||
ULA63060AK0,63,60,A,,K,ULA63060AK0.step
|
||||
ULA40020AD0,40,20,A,,D,PLACEHOLDER.step
|
||||
ULA40020AK0,40,20,A,,K,PLACEHOLDER.step
|
||||
ULA40020GSD0,40,20,G,S,D,PLACEHOLDER.step
|
||||
ULA40020GD0,40,20,G,,D,PLACEHOLDER.step
|
||||
ULA40020GK0,40,20,G,,K,PLACEHOLDER.step
|
||||
ULA40020NSD0,40,20,N,S,D,PLACEHOLDER.step
|
||||
ULA40020ND0,40,20,N,,D,PLACEHOLDER.step
|
||||
ULA40020NK0,40,20,N,,K,PLACEHOLDER.step
|
||||
ULA40020PSD0,40,20,P,S,D,PLACEHOLDER.step
|
||||
ULA40020PD0,40,20,P,,D,PLACEHOLDER.step
|
||||
ULA40020PK0,40,20,P,,K,PLACEHOLDER.step
|
||||
ULA40040AD0,40,40,A,,D,PLACEHOLDER.step
|
||||
ULA40040AK0,40,40,A,,K,PLACEHOLDER.step
|
||||
ULA40040GD0,40,40,G,,D,PLACEHOLDER.step
|
||||
ULA40040GK0,40,40,G,,K,PLACEHOLDER.step
|
||||
ULA40040ND0,40,40,N,,D,PLACEHOLDER.step
|
||||
ULA40040NK0,40,40,N,,K,PLACEHOLDER.step
|
||||
ULA40040PD0,40,40,P,,D,PLACEHOLDER.step
|
||||
ULA40040PK0,40,40,P,,K,PLACEHOLDER.step
|
||||
ULA40015ASD0,40,15,A,S,D,PLACEHOLDER.step
|
||||
ULA40015ASK0,40,15,A,S,K,PLACEHOLDER.step
|
||||
ULA40015AD0,40,15,A,,D,PLACEHOLDER.step
|
||||
ULA40015AK0,40,15,A,,K,PLACEHOLDER.step
|
||||
ULA40015GSD0,40,15,G,S,D,PLACEHOLDER.step
|
||||
ULA40015GSK0,40,15,G,S,K,PLACEHOLDER.step
|
||||
ULA40015GD0,40,15,G,,D,PLACEHOLDER.step
|
||||
ULA40015GK0,40,15,G,,K,PLACEHOLDER.step
|
||||
ULA40015NSD0,40,15,N,S,D,PLACEHOLDER.step
|
||||
ULA40015NSK0,40,15,N,S,K,PLACEHOLDER.step
|
||||
ULA40015ND0,40,15,N,,D,PLACEHOLDER.step
|
||||
ULA40015NK0,40,15,N,,K,PLACEHOLDER.step
|
||||
ULA40015PSD0,40,15,P,S,D,PLACEHOLDER.step
|
||||
ULA40015PSK0,40,15,P,S,K,PLACEHOLDER.step
|
||||
ULA40015PD0,40,15,P,,D,PLACEHOLDER.step
|
||||
ULA40015PK0,40,15,P,,K,PLACEHOLDER.step
|
||||
ULA40025ASD0,40,25,A,S,D,PLACEHOLDER.step
|
||||
ULA40025ASK0,40,25,A,S,K,PLACEHOLDER.step
|
||||
ULA40025AD0,40,25,A,,D,PLACEHOLDER.step
|
||||
ULA40025AK0,40,25,A,,K,PLACEHOLDER.step
|
||||
ULA40025GSD0,40,25,G,S,D,PLACEHOLDER.step
|
||||
ULA40025GSK0,40,25,G,S,K,PLACEHOLDER.step
|
||||
ULA40025GD0,40,25,G,,D,PLACEHOLDER.step
|
||||
ULA40025GK0,40,25,G,,K,PLACEHOLDER.step
|
||||
ULA40025NSD0,40,25,N,S,D,PLACEHOLDER.step
|
||||
ULA40025NSK0,40,25,N,S,K,PLACEHOLDER.step
|
||||
ULA40025ND0,40,25,N,,D,PLACEHOLDER.step
|
||||
ULA40025NK0,40,25,N,,K,PLACEHOLDER.step
|
||||
ULA40025PSD0,40,25,P,S,D,PLACEHOLDER.step
|
||||
ULA40025PSK0,40,25,P,S,K,PLACEHOLDER.step
|
||||
ULA40025PD0,40,25,P,,D,PLACEHOLDER.step
|
||||
ULA40025PK0,40,25,P,,K,PLACEHOLDER.step
|
||||
ULA40050ASD0,40,50,A,S,D,PLACEHOLDER.step
|
||||
ULA40050ASK0,40,50,A,S,K,PLACEHOLDER.step
|
||||
ULA40050AD0,40,50,A,,D,PLACEHOLDER.step
|
||||
ULA40050AK0,40,50,A,,K,PLACEHOLDER.step
|
||||
ULA40050GSD0,40,50,G,S,D,PLACEHOLDER.step
|
||||
ULA40050GSK0,40,50,G,S,K,PLACEHOLDER.step
|
||||
ULA40050GD0,40,50,G,,D,PLACEHOLDER.step
|
||||
ULA40050GK0,40,50,G,,K,PLACEHOLDER.step
|
||||
ULA40050NSD0,40,50,N,S,D,PLACEHOLDER.step
|
||||
ULA40050NSK0,40,50,N,S,K,PLACEHOLDER.step
|
||||
ULA40050ND0,40,50,N,,D,PLACEHOLDER.step
|
||||
ULA40050NK0,40,50,N,,K,PLACEHOLDER.step
|
||||
ULA40050PSD0,40,50,P,S,D,PLACEHOLDER.step
|
||||
ULA40050PSK0,40,50,P,S,K,PLACEHOLDER.step
|
||||
ULA40050PD0,40,50,P,,D,PLACEHOLDER.step
|
||||
ULA40050PK0,40,50,P,,K,PLACEHOLDER.step
|
||||
ULA40060ASD0,40,60,A,S,D,PLACEHOLDER.step
|
||||
ULA40060ASK0,40,60,A,S,K,PLACEHOLDER.step
|
||||
ULA40060AD0,40,60,A,,D,PLACEHOLDER.step
|
||||
ULA40060AK0,40,60,A,,K,PLACEHOLDER.step
|
||||
ULA40060GSD0,40,60,G,S,D,PLACEHOLDER.step
|
||||
ULA40060GSK0,40,60,G,S,K,PLACEHOLDER.step
|
||||
ULA40060GD0,40,60,G,,D,PLACEHOLDER.step
|
||||
ULA40060GK0,40,60,G,,K,PLACEHOLDER.step
|
||||
ULA40060NSD0,40,60,N,S,D,PLACEHOLDER.step
|
||||
ULA40060NSK0,40,60,N,S,K,PLACEHOLDER.step
|
||||
ULA40060ND0,40,60,N,,D,PLACEHOLDER.step
|
||||
ULA40060NK0,40,60,N,,K,PLACEHOLDER.step
|
||||
ULA40060PSD0,40,60,P,S,D,PLACEHOLDER.step
|
||||
ULA40060PSK0,40,60,P,S,K,PLACEHOLDER.step
|
||||
ULA40060PD0,40,60,P,,D,PLACEHOLDER.step
|
||||
ULA40060PK0,40,60,P,,K,PLACEHOLDER.step
|
||||
ULA63020ASD0,63,20,A,S,D,PLACEHOLDER.step
|
||||
ULA63020ASK0,63,20,A,S,K,PLACEHOLDER.step
|
||||
ULA63020AD0,63,20,A,,D,PLACEHOLDER.step
|
||||
ULA63020AK0,63,20,A,,K,PLACEHOLDER.step
|
||||
ULA63020GSD0,63,20,G,S,D,PLACEHOLDER.step
|
||||
ULA63020GSK0,63,20,G,S,K,PLACEHOLDER.step
|
||||
ULA63020GD0,63,20,G,,D,PLACEHOLDER.step
|
||||
ULA63020GK0,63,20,G,,K,PLACEHOLDER.step
|
||||
ULA63020NSD0,63,20,N,S,D,PLACEHOLDER.step
|
||||
ULA63020NSK0,63,20,N,S,K,PLACEHOLDER.step
|
||||
ULA63020ND0,63,20,N,,D,PLACEHOLDER.step
|
||||
ULA63020NK0,63,20,N,,K,PLACEHOLDER.step
|
||||
ULA63020PSD0,63,20,P,S,D,PLACEHOLDER.step
|
||||
ULA63020PSK0,63,20,P,S,K,PLACEHOLDER.step
|
||||
ULA63020PD0,63,20,P,,D,PLACEHOLDER.step
|
||||
ULA63020PK0,63,20,P,,K,PLACEHOLDER.step
|
||||
ULA63040ASD0,63,40,A,S,D,PLACEHOLDER.step
|
||||
ULA63040ASK0,63,40,A,S,K,PLACEHOLDER.step
|
||||
ULA63040GSD0,63,40,G,S,D,PLACEHOLDER.step
|
||||
ULA63040GSK0,63,40,G,S,K,PLACEHOLDER.step
|
||||
ULA63040GD0,63,40,G,,D,PLACEHOLDER.step
|
||||
ULA63040GK0,63,40,G,,K,PLACEHOLDER.step
|
||||
ULA63040NSD0,63,40,N,S,D,PLACEHOLDER.step
|
||||
ULA63040NSK0,63,40,N,S,K,PLACEHOLDER.step
|
||||
ULA63040ND0,63,40,N,,D,PLACEHOLDER.step
|
||||
ULA63040NK0,63,40,N,,K,PLACEHOLDER.step
|
||||
ULA63040PSD0,63,40,P,S,D,PLACEHOLDER.step
|
||||
ULA63040PSK0,63,40,P,S,K,PLACEHOLDER.step
|
||||
ULA63040PD0,63,40,P,,D,PLACEHOLDER.step
|
||||
ULA63040PK0,63,40,P,,K,PLACEHOLDER.step
|
||||
ULA63015ASD0,63,15,A,S,D,PLACEHOLDER.step
|
||||
ULA63015ASK0,63,15,A,S,K,PLACEHOLDER.step
|
||||
ULA63015AD0,63,15,A,,D,PLACEHOLDER.step
|
||||
ULA63015GSD0,63,15,G,S,D,PLACEHOLDER.step
|
||||
ULA63015GSK0,63,15,G,S,K,PLACEHOLDER.step
|
||||
ULA63015GD0,63,15,G,,D,PLACEHOLDER.step
|
||||
ULA63015GK0,63,15,G,,K,PLACEHOLDER.step
|
||||
ULA63015NSD0,63,15,N,S,D,PLACEHOLDER.step
|
||||
ULA63015NSK0,63,15,N,S,K,PLACEHOLDER.step
|
||||
ULA63015ND0,63,15,N,,D,PLACEHOLDER.step
|
||||
ULA63015NK0,63,15,N,,K,PLACEHOLDER.step
|
||||
ULA63015PSD0,63,15,P,S,D,PLACEHOLDER.step
|
||||
ULA63015PSK0,63,15,P,S,K,PLACEHOLDER.step
|
||||
ULA63015PD0,63,15,P,,D,PLACEHOLDER.step
|
||||
ULA63015PK0,63,15,P,,K,PLACEHOLDER.step
|
||||
ULA63025ASD0,63,25,A,S,D,PLACEHOLDER.step
|
||||
ULA63025ASK0,63,25,A,S,K,PLACEHOLDER.step
|
||||
ULA63025AD0,63,25,A,,D,PLACEHOLDER.step
|
||||
ULA63025GSD0,63,25,G,S,D,PLACEHOLDER.step
|
||||
ULA63025GSK0,63,25,G,S,K,PLACEHOLDER.step
|
||||
ULA63025GD0,63,25,G,,D,PLACEHOLDER.step
|
||||
ULA63025GK0,63,25,G,,K,PLACEHOLDER.step
|
||||
ULA63025NSD0,63,25,N,S,D,PLACEHOLDER.step
|
||||
ULA63025NSK0,63,25,N,S,K,PLACEHOLDER.step
|
||||
ULA63025ND0,63,25,N,,D,PLACEHOLDER.step
|
||||
ULA63025NK0,63,25,N,,K,PLACEHOLDER.step
|
||||
ULA63025PSD0,63,25,P,S,D,PLACEHOLDER.step
|
||||
ULA63025PSK0,63,25,P,S,K,PLACEHOLDER.step
|
||||
ULA63025PD0,63,25,P,,D,PLACEHOLDER.step
|
||||
ULA63025PK0,63,25,P,,K,PLACEHOLDER.step
|
||||
ULA63050ASD0,63,50,A,S,D,PLACEHOLDER.step
|
||||
ULA63050ASK0,63,50,A,S,K,PLACEHOLDER.step
|
||||
ULA63050AD0,63,50,A,,D,PLACEHOLDER.step
|
||||
ULA63050GSD0,63,50,G,S,D,PLACEHOLDER.step
|
||||
ULA63050GSK0,63,50,G,S,K,PLACEHOLDER.step
|
||||
ULA63050GD0,63,50,G,,D,PLACEHOLDER.step
|
||||
ULA63050GK0,63,50,G,,K,PLACEHOLDER.step
|
||||
ULA63050NSD0,63,50,N,S,D,PLACEHOLDER.step
|
||||
ULA63050NSK0,63,50,N,S,K,PLACEHOLDER.step
|
||||
ULA63050ND0,63,50,N,,D,PLACEHOLDER.step
|
||||
ULA63050NK0,63,50,N,,K,PLACEHOLDER.step
|
||||
ULA63050PSD0,63,50,P,S,D,PLACEHOLDER.step
|
||||
ULA63050PSK0,63,50,P,S,K,PLACEHOLDER.step
|
||||
ULA63050PD0,63,50,P,,D,PLACEHOLDER.step
|
||||
ULA63050PK0,63,50,P,,K,PLACEHOLDER.step
|
||||
ULA63060ASD0,63,60,A,S,D,PLACEHOLDER.step
|
||||
ULA63060ASK0,63,60,A,S,K,PLACEHOLDER.step
|
||||
ULA63060AD0,63,60,A,,D,PLACEHOLDER.step
|
||||
ULA63060GSD0,63,60,G,S,D,PLACEHOLDER.step
|
||||
ULA63060GSK0,63,60,G,S,K,PLACEHOLDER.step
|
||||
ULA63060GD0,63,60,G,,D,PLACEHOLDER.step
|
||||
ULA63060GK0,63,60,G,,K,PLACEHOLDER.step
|
||||
ULA63060NSD0,63,60,N,S,D,PLACEHOLDER.step
|
||||
ULA63060NSK0,63,60,N,S,K,PLACEHOLDER.step
|
||||
ULA63060ND0,63,60,N,,D,PLACEHOLDER.step
|
||||
ULA63060NK0,63,60,N,,K,PLACEHOLDER.step
|
||||
ULA63060PSD0,63,60,P,S,D,PLACEHOLDER.step
|
||||
ULA63060PSK0,63,60,P,S,K,PLACEHOLDER.step
|
||||
ULA63060PD0,63,60,P,,D,PLACEHOLDER.step
|
||||
ULA63060PK0,63,60,P,,K,PLACEHOLDER.step
|
||||
|
||||
|
@@ -13,3 +13,29 @@ ULL50040EXK0,50,40,E,X,K,ULL50040EXK0.step
|
||||
ULL50040FXK0,50,40,F,X,K,ULL50040FXK0.step
|
||||
ULL50040HXK0,50,40,H,X,K,ULL50040HXK0.step
|
||||
ULL50040IXK0,50,40,I,X,K,ULL50040IXK0.step
|
||||
ULL50040AXK0,50,40,A,X,K,PLACEHOLDER.step
|
||||
ULL50040BEK0,50,40,B,E,K,PLACEHOLDER.step
|
||||
ULL50040BNK0,50,40,B,N,K,PLACEHOLDER.step
|
||||
ULL50040BOK0,50,40,B,O,K,PLACEHOLDER.step
|
||||
ULL50040BSK0,50,40,B,S,K,PLACEHOLDER.step
|
||||
ULL50040CEK0,50,40,C,E,K,PLACEHOLDER.step
|
||||
ULL50040CNK0,50,40,C,N,K,PLACEHOLDER.step
|
||||
ULL50040COK0,50,40,C,O,K,PLACEHOLDER.step
|
||||
ULL50040CSK0,50,40,C,S,K,PLACEHOLDER.step
|
||||
ULL50040DXK0,50,40,D,X,K,PLACEHOLDER.step
|
||||
ULL50040EEK0,50,40,E,E,K,PLACEHOLDER.step
|
||||
ULL50040ENK0,50,40,E,N,K,PLACEHOLDER.step
|
||||
ULL50040EOK0,50,40,E,O,K,PLACEHOLDER.step
|
||||
ULL50040ESK0,50,40,E,S,K,PLACEHOLDER.step
|
||||
ULL50040FEK0,50,40,F,E,K,PLACEHOLDER.step
|
||||
ULL50040FNK0,50,40,F,N,K,PLACEHOLDER.step
|
||||
ULL50040FOK0,50,40,F,O,K,PLACEHOLDER.step
|
||||
ULL50040FSK0,50,40,F,S,K,PLACEHOLDER.step
|
||||
ULL50040HEK0,50,40,H,E,K,PLACEHOLDER.step
|
||||
ULL50040HNK0,50,40,H,N,K,PLACEHOLDER.step
|
||||
ULL50040HOK0,50,40,H,O,K,PLACEHOLDER.step
|
||||
ULL50040HSK0,50,40,H,S,K,PLACEHOLDER.step
|
||||
ULL50040IEK0,50,40,I,E,K,PLACEHOLDER.step
|
||||
ULL50040INK0,50,40,I,N,K,PLACEHOLDER.step
|
||||
ULL50040IOK0,50,40,I,O,K,PLACEHOLDER.step
|
||||
ULL50040ISK0,50,40,I,S,K,PLACEHOLDER.step
|
||||
|
||||
|
@@ -69,3 +69,133 @@ ULM50060EXK0,50,60,E,X,K,ULM50060EXK0.step
|
||||
ULM50060FXK0,50,60,F,X,K,ULM50060FXK0.step
|
||||
ULM50060HXK0,50,60,H,X,K,ULM50060HXK0.step
|
||||
ULM50060IXK0,50,60,I,X,K,ULM50060IXK0.step
|
||||
ULM50015AXK0,50,15,A,X,K,PLACEHOLDER.step
|
||||
ULM50015BEK0,50,15,B,E,K,PLACEHOLDER.step
|
||||
ULM50015BNK0,50,15,B,N,K,PLACEHOLDER.step
|
||||
ULM50015BOK0,50,15,B,O,K,PLACEHOLDER.step
|
||||
ULM50015BSK0,50,15,B,S,K,PLACEHOLDER.step
|
||||
ULM50015CEK0,50,15,C,E,K,PLACEHOLDER.step
|
||||
ULM50015CNK0,50,15,C,N,K,PLACEHOLDER.step
|
||||
ULM50015COK0,50,15,C,O,K,PLACEHOLDER.step
|
||||
ULM50015CSK0,50,15,C,S,K,PLACEHOLDER.step
|
||||
ULM50015DXK0,50,15,D,X,K,PLACEHOLDER.step
|
||||
ULM50015EEK0,50,15,E,E,K,PLACEHOLDER.step
|
||||
ULM50015ENK0,50,15,E,N,K,PLACEHOLDER.step
|
||||
ULM50015EOK0,50,15,E,O,K,PLACEHOLDER.step
|
||||
ULM50015ESK0,50,15,E,S,K,PLACEHOLDER.step
|
||||
ULM50015FEK0,50,15,F,E,K,PLACEHOLDER.step
|
||||
ULM50015FNK0,50,15,F,N,K,PLACEHOLDER.step
|
||||
ULM50015FOK0,50,15,F,O,K,PLACEHOLDER.step
|
||||
ULM50015FSK0,50,15,F,S,K,PLACEHOLDER.step
|
||||
ULM50015HEK0,50,15,H,E,K,PLACEHOLDER.step
|
||||
ULM50015HNK0,50,15,H,N,K,PLACEHOLDER.step
|
||||
ULM50015HOK0,50,15,H,O,K,PLACEHOLDER.step
|
||||
ULM50015HSK0,50,15,H,S,K,PLACEHOLDER.step
|
||||
ULM50015IEK0,50,15,I,E,K,PLACEHOLDER.step
|
||||
ULM50015INK0,50,15,I,N,K,PLACEHOLDER.step
|
||||
ULM50015IOK0,50,15,I,O,K,PLACEHOLDER.step
|
||||
ULM50015ISK0,50,15,I,S,K,PLACEHOLDER.step
|
||||
ULM50025AXK0,50,25,A,X,K,PLACEHOLDER.step
|
||||
ULM50025BEK0,50,25,B,E,K,PLACEHOLDER.step
|
||||
ULM50025BNK0,50,25,B,N,K,PLACEHOLDER.step
|
||||
ULM50025BOK0,50,25,B,O,K,PLACEHOLDER.step
|
||||
ULM50025BSK0,50,25,B,S,K,PLACEHOLDER.step
|
||||
ULM50025CEK0,50,25,C,E,K,PLACEHOLDER.step
|
||||
ULM50025CNK0,50,25,C,N,K,PLACEHOLDER.step
|
||||
ULM50025COK0,50,25,C,O,K,PLACEHOLDER.step
|
||||
ULM50025CSK0,50,25,C,S,K,PLACEHOLDER.step
|
||||
ULM50025DXK0,50,25,D,X,K,PLACEHOLDER.step
|
||||
ULM50025EEK0,50,25,E,E,K,PLACEHOLDER.step
|
||||
ULM50025ENK0,50,25,E,N,K,PLACEHOLDER.step
|
||||
ULM50025EOK0,50,25,E,O,K,PLACEHOLDER.step
|
||||
ULM50025ESK0,50,25,E,S,K,PLACEHOLDER.step
|
||||
ULM50025FEK0,50,25,F,E,K,PLACEHOLDER.step
|
||||
ULM50025FNK0,50,25,F,N,K,PLACEHOLDER.step
|
||||
ULM50025FOK0,50,25,F,O,K,PLACEHOLDER.step
|
||||
ULM50025FSK0,50,25,F,S,K,PLACEHOLDER.step
|
||||
ULM50025HEK0,50,25,H,E,K,PLACEHOLDER.step
|
||||
ULM50025HNK0,50,25,H,N,K,PLACEHOLDER.step
|
||||
ULM50025HOK0,50,25,H,O,K,PLACEHOLDER.step
|
||||
ULM50025HSK0,50,25,H,S,K,PLACEHOLDER.step
|
||||
ULM50025IEK0,50,25,I,E,K,PLACEHOLDER.step
|
||||
ULM50025INK0,50,25,I,N,K,PLACEHOLDER.step
|
||||
ULM50025IOK0,50,25,I,O,K,PLACEHOLDER.step
|
||||
ULM50025ISK0,50,25,I,S,K,PLACEHOLDER.step
|
||||
ULM50040AXK0,50,40,A,X,K,PLACEHOLDER.step
|
||||
ULM50040BEK0,50,40,B,E,K,PLACEHOLDER.step
|
||||
ULM50040BNK0,50,40,B,N,K,PLACEHOLDER.step
|
||||
ULM50040BOK0,50,40,B,O,K,PLACEHOLDER.step
|
||||
ULM50040BSK0,50,40,B,S,K,PLACEHOLDER.step
|
||||
ULM50040CEK0,50,40,C,E,K,PLACEHOLDER.step
|
||||
ULM50040CNK0,50,40,C,N,K,PLACEHOLDER.step
|
||||
ULM50040COK0,50,40,C,O,K,PLACEHOLDER.step
|
||||
ULM50040CSK0,50,40,C,S,K,PLACEHOLDER.step
|
||||
ULM50040DXK0,50,40,D,X,K,PLACEHOLDER.step
|
||||
ULM50040EEK0,50,40,E,E,K,PLACEHOLDER.step
|
||||
ULM50040ENK0,50,40,E,N,K,PLACEHOLDER.step
|
||||
ULM50040EOK0,50,40,E,O,K,PLACEHOLDER.step
|
||||
ULM50040ESK0,50,40,E,S,K,PLACEHOLDER.step
|
||||
ULM50040FEK0,50,40,F,E,K,PLACEHOLDER.step
|
||||
ULM50040FNK0,50,40,F,N,K,PLACEHOLDER.step
|
||||
ULM50040FOK0,50,40,F,O,K,PLACEHOLDER.step
|
||||
ULM50040FSK0,50,40,F,S,K,PLACEHOLDER.step
|
||||
ULM50040HEK0,50,40,H,E,K,PLACEHOLDER.step
|
||||
ULM50040HNK0,50,40,H,N,K,PLACEHOLDER.step
|
||||
ULM50040HOK0,50,40,H,O,K,PLACEHOLDER.step
|
||||
ULM50040HSK0,50,40,H,S,K,PLACEHOLDER.step
|
||||
ULM50040IEK0,50,40,I,E,K,PLACEHOLDER.step
|
||||
ULM50040INK0,50,40,I,N,K,PLACEHOLDER.step
|
||||
ULM50040IOK0,50,40,I,O,K,PLACEHOLDER.step
|
||||
ULM50040ISK0,50,40,I,S,K,PLACEHOLDER.step
|
||||
ULM50050AXK0,50,50,A,X,K,PLACEHOLDER.step
|
||||
ULM50050BEK0,50,50,B,E,K,PLACEHOLDER.step
|
||||
ULM50050BNK0,50,50,B,N,K,PLACEHOLDER.step
|
||||
ULM50050BOK0,50,50,B,O,K,PLACEHOLDER.step
|
||||
ULM50050BSK0,50,50,B,S,K,PLACEHOLDER.step
|
||||
ULM50050CEK0,50,50,C,E,K,PLACEHOLDER.step
|
||||
ULM50050CNK0,50,50,C,N,K,PLACEHOLDER.step
|
||||
ULM50050COK0,50,50,C,O,K,PLACEHOLDER.step
|
||||
ULM50050CSK0,50,50,C,S,K,PLACEHOLDER.step
|
||||
ULM50050DXK0,50,50,D,X,K,PLACEHOLDER.step
|
||||
ULM50050EEK0,50,50,E,E,K,PLACEHOLDER.step
|
||||
ULM50050ENK0,50,50,E,N,K,PLACEHOLDER.step
|
||||
ULM50050EOK0,50,50,E,O,K,PLACEHOLDER.step
|
||||
ULM50050ESK0,50,50,E,S,K,PLACEHOLDER.step
|
||||
ULM50050FEK0,50,50,F,E,K,PLACEHOLDER.step
|
||||
ULM50050FNK0,50,50,F,N,K,PLACEHOLDER.step
|
||||
ULM50050FOK0,50,50,F,O,K,PLACEHOLDER.step
|
||||
ULM50050FSK0,50,50,F,S,K,PLACEHOLDER.step
|
||||
ULM50050HEK0,50,50,H,E,K,PLACEHOLDER.step
|
||||
ULM50050HNK0,50,50,H,N,K,PLACEHOLDER.step
|
||||
ULM50050HOK0,50,50,H,O,K,PLACEHOLDER.step
|
||||
ULM50050HSK0,50,50,H,S,K,PLACEHOLDER.step
|
||||
ULM50050IEK0,50,50,I,E,K,PLACEHOLDER.step
|
||||
ULM50050INK0,50,50,I,N,K,PLACEHOLDER.step
|
||||
ULM50050IOK0,50,50,I,O,K,PLACEHOLDER.step
|
||||
ULM50050ISK0,50,50,I,S,K,PLACEHOLDER.step
|
||||
ULM50060AXK0,50,60,A,X,K,PLACEHOLDER.step
|
||||
ULM50060BEK0,50,60,B,E,K,PLACEHOLDER.step
|
||||
ULM50060BNK0,50,60,B,N,K,PLACEHOLDER.step
|
||||
ULM50060BOK0,50,60,B,O,K,PLACEHOLDER.step
|
||||
ULM50060BSK0,50,60,B,S,K,PLACEHOLDER.step
|
||||
ULM50060CEK0,50,60,C,E,K,PLACEHOLDER.step
|
||||
ULM50060CNK0,50,60,C,N,K,PLACEHOLDER.step
|
||||
ULM50060COK0,50,60,C,O,K,PLACEHOLDER.step
|
||||
ULM50060CSK0,50,60,C,S,K,PLACEHOLDER.step
|
||||
ULM50060DXK0,50,60,D,X,K,PLACEHOLDER.step
|
||||
ULM50060EEK0,50,60,E,E,K,PLACEHOLDER.step
|
||||
ULM50060ENK0,50,60,E,N,K,PLACEHOLDER.step
|
||||
ULM50060EOK0,50,60,E,O,K,PLACEHOLDER.step
|
||||
ULM50060ESK0,50,60,E,S,K,PLACEHOLDER.step
|
||||
ULM50060FEK0,50,60,F,E,K,PLACEHOLDER.step
|
||||
ULM50060FNK0,50,60,F,N,K,PLACEHOLDER.step
|
||||
ULM50060FOK0,50,60,F,O,K,PLACEHOLDER.step
|
||||
ULM50060FSK0,50,60,F,S,K,PLACEHOLDER.step
|
||||
ULM50060HEK0,50,60,H,E,K,PLACEHOLDER.step
|
||||
ULM50060HNK0,50,60,H,N,K,PLACEHOLDER.step
|
||||
ULM50060HOK0,50,60,H,O,K,PLACEHOLDER.step
|
||||
ULM50060HSK0,50,60,H,S,K,PLACEHOLDER.step
|
||||
ULM50060IEK0,50,60,I,E,K,PLACEHOLDER.step
|
||||
ULM50060INK0,50,60,I,N,K,PLACEHOLDER.step
|
||||
ULM50060IOK0,50,60,I,O,K,PLACEHOLDER.step
|
||||
ULM50060ISK0,50,60,I,S,K,PLACEHOLDER.step
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -2,3 +2,4 @@
|
||||
ULT50040AK0,50,40,A,K,ULT50040AK0.step
|
||||
ULT50040GK0,50,40,G,K,ULT50040GK0.step
|
||||
ULT50040GN0,50,40,G,N,ULT50040GN0.step
|
||||
ULT50040AN0,50,40,A,N,PLACEHOLDER.step
|
||||
|
||||
|
@@ -9,3 +9,13 @@ ULY40040DEK0,40,40,D,E,K,ULY40040DEK0.step
|
||||
ULY40040DNK0,40,40,D,N,K,ULY40040DNK0.step
|
||||
ULY40040DOK0,40,40,D,O,K,ULY40040DOK0.step
|
||||
ULY40040DSK0,40,40,D,S,K,ULY40040DSK0.step
|
||||
ULY40040AXK0,40,40,A,X,K,PLACEHOLDER.step
|
||||
ULY40040BEK0,40,40,B,E,K,PLACEHOLDER.step
|
||||
ULY40040BNK0,40,40,B,N,K,PLACEHOLDER.step
|
||||
ULY40040BOK0,40,40,B,O,K,PLACEHOLDER.step
|
||||
ULY40040BSK0,40,40,B,S,K,PLACEHOLDER.step
|
||||
ULY40040CEK0,40,40,C,E,K,PLACEHOLDER.step
|
||||
ULY40040CNK0,40,40,C,N,K,PLACEHOLDER.step
|
||||
ULY40040COK0,40,40,C,O,K,PLACEHOLDER.step
|
||||
ULY40040CSK0,40,40,C,S,K,PLACEHOLDER.step
|
||||
ULY40040DXK0,40,40,D,X,K,PLACEHOLDER.step
|
||||
|
||||
|
@@ -9,3 +9,13 @@ ULZ40040DEK0,40,40,D,E,K,ULZ40040DEK0.step
|
||||
ULZ40040DNK0,40,40,D,N,K,ULZ40040DNK0.step
|
||||
ULZ40040DOK0,40,40,D,O,K,ULZ40040DOK0.step
|
||||
ULZ40040DSK0,40,40,D,S,K,ULZ40040DSK0.step
|
||||
ULZ40040AXK0,40,40,A,X,K,PLACEHOLDER.step
|
||||
ULZ40040BEK0,40,40,B,E,K,PLACEHOLDER.step
|
||||
ULZ40040BNK0,40,40,B,N,K,PLACEHOLDER.step
|
||||
ULZ40040BOK0,40,40,B,O,K,PLACEHOLDER.step
|
||||
ULZ40040BSK0,40,40,B,S,K,PLACEHOLDER.step
|
||||
ULZ40040CEK0,40,40,C,E,K,PLACEHOLDER.step
|
||||
ULZ40040CNK0,40,40,C,N,K,PLACEHOLDER.step
|
||||
ULZ40040COK0,40,40,C,O,K,PLACEHOLDER.step
|
||||
ULZ40040CSK0,40,40,C,S,K,PLACEHOLDER.step
|
||||
ULZ40040DXK0,40,40,D,X,K,PLACEHOLDER.step
|
||||
|
||||
|
@@ -6,3 +6,8 @@ USSL50JB51001N0,50,J,B,5,1,001,N,USSL50JB51001N0.step
|
||||
USSL50JC31001N0,50,J,C,3,1,001,N,USSL50JC31001N0.step
|
||||
USSL50JC31002N0,50,J,C,3,1,002,N,USSL50JC31002N0.step
|
||||
USSL50JC51001N0,50,J,C,5,1,001,N,USSL50JC51001N0.step
|
||||
USSL50JB41002N0,50,J,B,4,1,002,N,PLACEHOLDER.step
|
||||
USSL50JB51002N0,50,J,B,5,1,002,N,PLACEHOLDER.step
|
||||
USSL50JC41001N0,50,J,C,4,1,001,N,PLACEHOLDER.step
|
||||
USSL50JC41002N0,50,J,C,4,1,002,N,PLACEHOLDER.step
|
||||
USSL50JC51002N0,50,J,C,5,1,002,N,PLACEHOLDER.step
|
||||
|
||||
|
31
onebot-data/tools/check-combo-unique.py
Normal file
31
onebot-data/tools/check-combo-unique.py
Normal file
@@ -0,0 +1,31 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""统计各系列"参数组合 → 型号"是否一对一 (多处多 vs 一处多)。"""
|
||||
import json, io, collections
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/release/web_2026.08/catalog.json'
|
||||
d = json.load(io.open(CAT, encoding='utf-8-sig'))
|
||||
|
||||
print(f'{"系列":8s} {"变体":>6s} {"组合":>6s} {"重复组":>6s} {"最大一对多":>9s} 判定')
|
||||
print('-' * 58)
|
||||
tot_codes = 0
|
||||
bad = []
|
||||
for s in d['series']:
|
||||
vs = s.get('variants') or []
|
||||
if not vs:
|
||||
continue
|
||||
params = [p['code'] for p in s['parameters']]
|
||||
sig = collections.Counter(
|
||||
tuple(str(v['params'].get(pc)) for pc in params) for v in vs)
|
||||
dup = {k: n for k, n in sig.items() if n > 1}
|
||||
mx = max(sig.values()) if sig else 0
|
||||
verdict = 'OK 一对一' if not dup else f'!! {len(dup)} 组一对多'
|
||||
if dup:
|
||||
bad.append((s['code'], len(vs), len(dup), mx))
|
||||
tot_codes += sum(n - 1 for n in dup.values())
|
||||
print(f'{s["code"]:8s} {len(vs):6d} {len(sig):6d} {len(dup):6d} {mx:9d} {verdict}')
|
||||
|
||||
print(f'\n存在一对多的系列: {len(bad)} 个, 受影响型号 {tot_codes} 个')
|
||||
if bad:
|
||||
print('\n明细 (系列 / 变体 / 重复组数 / 一组最多几个型号):')
|
||||
for c, n, dup, mx in sorted(bad, key=lambda x: -x[3]):
|
||||
print(f' {c:8s} {n:5d} 变体 {dup:3d} 组 max={mx}')
|
||||
225
onebot-data/tools/gen-missing-combos.py
Normal file
225
onebot-data/tools/gen-missing-combos.py
Normal file
@@ -0,0 +1,225 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
补齐参数组合空间: 为"规则内合法但库中缺失"的组合生成零件号, 并指向系列级占位数模。
|
||||
|
||||
背景: 当前型号表由真实 STEP 文件扫描而来 —— 没有数模就没有型号, 用户选完也拿不到代号。
|
||||
本脚本按规则反向生成缺失组合, 使其可选、可出号; 数模用每系列一个占位件, 后续 AI 补真实数模。
|
||||
|
||||
用法:
|
||||
PYTHONUTF8=1 python gen-missing-combos.py --report # 只统计, 不写文件
|
||||
PYTHONUTF8=1 python gen-missing-combos.py --write # 写入 CSV + meta 模板 + 占位清单
|
||||
"""
|
||||
import json, io, os, csv, itertools, sys, collections
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/onebot-data/catalog'
|
||||
REPORT = '--report' in sys.argv
|
||||
WRITE = '--write' in sys.argv
|
||||
|
||||
# 只有"模板能 100% 对齐"的系列才补 (由 infer-sku-template.py 验证)。
|
||||
# 其余系列 (旧码混用/编码结构特殊) 保持现状, 不生成可能不存在的零件号。
|
||||
SAFE = {
|
||||
'KC', 'KCB', 'KCS', 'KS', 'KSB', 'KSU', 'M', 'MS', 'MSC', 'MAL', 'MALC',
|
||||
'ULA', 'ULL', 'ULM', 'ULO', 'ULP', 'ULT', 'ULX', 'ULY', 'ULZ', 'USL', 'USSL',
|
||||
}
|
||||
|
||||
# 各系列的编码模板 (由 infer-sku-template.py 反推, 100% 对齐)
|
||||
# {参数:补零位数}; 空值渲染为空串
|
||||
TEMPLATES = {
|
||||
'KC': ('KC{type}{bore}-{stroke}{magnet}{mount}', {}),
|
||||
'KCB': ('KCB{bore}-{stroke}{magnet}{mount}', {}),
|
||||
'KCS': ('KCS{type}{bore}-{stroke}{magnet}{mount}', {}),
|
||||
'KS': ('KS{type}{bore}-{stroke}{magnet}{mount}', {}),
|
||||
'KSB': ('KSB{bore}-{stroke1}x{stroke2}{magnet}{mount}', {}),
|
||||
'KSU': ('KSU{type}{bore}-{stroke}{magnet}{mount}', {}),
|
||||
'M': ('{action}{bore}-{stroke}{magnet}{rod}{mount}{cap}', {}),
|
||||
'MS': ('{action}{bore}-{stroke}{magnet}{rod}{mount}{cap}', {}),
|
||||
'MSC': ('{action}{bore}-{stroke}{magnet}{rod}{mount}{cap}', {}),
|
||||
'MAL': ('{type}{bore}-{stroke}{magnet}{rod}{mount}{cap}', {}),
|
||||
'MALC': ('{type}{bore}-{stroke}{magnet}{rod}{mount}{cap}', {}),
|
||||
'ULA': ('ULA{size}{stroke:pad3}{rod}{mount}{sensor}0', {}),
|
||||
'ULL': ('ULL{size}{stroke:pad3}{rod}{orient}{sensor}0', {}),
|
||||
'ULM': ('ULM{size}{stroke:pad3}{rod}{orient}{sensor}0', {}),
|
||||
'ULO': ('ULO{size}{stroke:pad3}{rod}{sensor}0', {}),
|
||||
'ULP': ('ULP{size}{stroke:pad3}{rod}{orient}{sensor}0', {}),
|
||||
'ULT': ('ULT{size}{stroke:pad3}{rod}{sensor}0', {}),
|
||||
'ULX': ('ULX{size}{stroke:pad3}{rod}{sensor}0', {}),
|
||||
'ULY': ('ULY{size}{stroke:pad3}{rod}{orient}{sensor}0', {}),
|
||||
'ULZ': ('ULZ{size}{stroke:pad3}{rod}{orient}{sensor}0', {}),
|
||||
'USL': ('USL{size}{pcmodel}{pin_dia}{locating}{prog}{sensor}0', {}),
|
||||
'USSL': ('USSL{size}{pcmodel}{hook_pos}{pin_dia}{locating}{prog}{sensor}0', {}),
|
||||
}
|
||||
|
||||
PLACEHOLDER_STEP = 'PLACEHOLDER.step' # 每系列共用
|
||||
|
||||
|
||||
def S(v):
|
||||
return '' if v is None else str(v)
|
||||
|
||||
|
||||
def eq(a, b):
|
||||
return S(a).lower() == S(b).lower()
|
||||
|
||||
|
||||
def cond_match(c, prm):
|
||||
if not c:
|
||||
return True
|
||||
val = prm.get(c['Param'])
|
||||
if val is None:
|
||||
return False
|
||||
op, cv = c['Op'], c['Value']
|
||||
if op == 'eq':
|
||||
return eq(val, cv)
|
||||
if op == 'ne':
|
||||
return not eq(val, cv)
|
||||
if op == 'in':
|
||||
return isinstance(cv, list) and any(eq(val, x) for x in cv)
|
||||
try:
|
||||
a, b = float(S(val)), float(S(cv))
|
||||
except ValueError:
|
||||
return False
|
||||
return {'gt': a > b, 'ge': a >= b, 'lt': a < b, 'le': a <= b}.get(op, False)
|
||||
|
||||
|
||||
def violated(rules, prm):
|
||||
for r in rules or []:
|
||||
if cond_match(r.get('If'), prm) and not cond_match(r.get('Then'), prm):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def render(tpl, prm):
|
||||
"""模板渲染: {field} / {field:padN}; 空值渲染为空串。"""
|
||||
out, i = '', 0
|
||||
while i < len(tpl):
|
||||
if tpl[i] == '{':
|
||||
j = tpl.index('}', i)
|
||||
tok = tpl[i + 1:j]
|
||||
name, _, pad = tok.partition(':')
|
||||
name = name.strip()
|
||||
v = S(prm.get(name, ''))
|
||||
if pad.startswith('pad') and v != '':
|
||||
try:
|
||||
v = v.rjust(int(pad[3:]), '0')
|
||||
except ValueError:
|
||||
return None
|
||||
out += v
|
||||
i = j + 1
|
||||
else:
|
||||
out += tpl[i]
|
||||
i += 1
|
||||
return out
|
||||
|
||||
|
||||
def load_meta(code):
|
||||
p = os.path.join(CAT, 'series', code + '.meta.json')
|
||||
return json.load(io.open(p, encoding='utf-8'))
|
||||
|
||||
|
||||
def link_or_copy(src, dst):
|
||||
"""建硬链接; 不支持则回退复制。返回 True 表示已创建。
|
||||
注: 必须先建链接再考虑失败回退, 绝不能先删目标再建 (删了又建失败 = 文件丢失)。"""
|
||||
if os.path.exists(dst):
|
||||
return False
|
||||
try:
|
||||
os.link(src, dst)
|
||||
return True
|
||||
except OSError:
|
||||
pass
|
||||
try:
|
||||
import shutil
|
||||
shutil.copy2(src, dst)
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
|
||||
def main():
|
||||
summary = []
|
||||
plan = {}
|
||||
for code in sorted(SAFE):
|
||||
meta = load_meta(code)
|
||||
s = meta['series']
|
||||
params = s['parameters']
|
||||
csvp = os.path.join(CAT, 'csv', code + '.csv')
|
||||
if not os.path.exists(csvp):
|
||||
print(f'[{code}] 无 CSV, 跳过')
|
||||
continue
|
||||
rows = list(csv.DictReader(io.open(csvp, encoding='utf-8-sig')))
|
||||
existing = {r['model_code'] for r in rows}
|
||||
|
||||
# 值域: 数值参数取 CSV 实际出现过的值 (不用 meta.values, 避免含非法/未出现值)
|
||||
doms = collections.OrderedDict()
|
||||
for p in params:
|
||||
pc = p['code']
|
||||
if p['type'] == 'range':
|
||||
doms[pc] = None # 连续域不枚举
|
||||
continue
|
||||
vals = list(dict.fromkeys(r.get(pc, '') for r in rows))
|
||||
vals = [v for v in vals if v != '' or True]
|
||||
doms[pc] = vals
|
||||
if any(v is None for v in doms.values()):
|
||||
print(f'[{code}] 含 range 参数, 跳过 (连续域无法枚举)')
|
||||
continue
|
||||
|
||||
tpl, _ = TEMPLATES[code]
|
||||
codes = list(doms.keys())
|
||||
combos = list(itertools.product(*[doms[k] for k in codes]))
|
||||
added = []
|
||||
for combo in combos:
|
||||
prm = dict(zip(codes, combo))
|
||||
if violated(s.get('rules'), prm):
|
||||
continue
|
||||
mc = render(tpl, prm)
|
||||
if not mc or mc in existing:
|
||||
continue
|
||||
existing.add(mc)
|
||||
added.append((mc, prm))
|
||||
|
||||
summary.append((code, len(rows), len(combos), len(added)))
|
||||
if added:
|
||||
plan[code] = (tpl, params, added, rows)
|
||||
|
||||
# 输出
|
||||
print(f'{"系列":7s} {"现有":>7s} {"笛卡尔":>9s} {"待补":>7s}')
|
||||
print('-' * 36)
|
||||
tot_new = 0
|
||||
for code, n, cart, add in sorted(summary, key=lambda x: -x[3]):
|
||||
tot_new += add
|
||||
print(f'{code:7s} {n:7d} {cart:9,d} {add:7,d}')
|
||||
print(f'\n合计待补 {tot_new:,} 个型号')
|
||||
|
||||
if not WRITE:
|
||||
print('\n(--report 模式, 未写文件)')
|
||||
return
|
||||
|
||||
# ---------- 写入 ----------
|
||||
step_dir = os.path.join(CAT, 'step')
|
||||
placeholder = os.path.join(step_dir, PLACEHOLDER_STEP)
|
||||
manifest = [] # 待 AI 补真实数模的清单
|
||||
n_csv = n_ph = 0
|
||||
for code, (tpl, params, added, rows) in plan.items():
|
||||
csvp = os.path.join(CAT, 'csv', code + '.csv')
|
||||
header = list(rows[0].keys())
|
||||
dims = [h for h in header if h not in ('model_code', 'step_file')]
|
||||
# 新增行: step_file 统一指向 PLACEHOLDER.step ——
|
||||
# 构建器按 v.step 的路径复制 (同路径只存一份), 3157 个型号共用一个占位件,
|
||||
# 既不占磁盘也不撑大数据包。AI 补真实数模后把该行改成 "<型号>.step" 即可。
|
||||
with io.open(csvp, 'a', encoding='utf-8-sig', newline='') as fp:
|
||||
w = csv.writer(fp)
|
||||
for mc, prm in added:
|
||||
w.writerow([mc] + [S(prm.get(d, '')) for d in dims] + [PLACEHOLDER_STEP])
|
||||
manifest.append(f'{code},{mc},{",".join(d + "=" + S(prm.get(d, "")) for d in dims)}')
|
||||
n_csv += 1
|
||||
print(f'[{code}] CSV +{len(added)}')
|
||||
|
||||
# 待补清单 (给 AI 补数模用)
|
||||
mf = os.path.join(CAT, '_pending_models.csv')
|
||||
with io.open(mf, 'w', encoding='utf-8-sig', newline='') as fp:
|
||||
fp.write('series,model_code,params\n')
|
||||
fp.write('\n'.join(manifest) + '\n')
|
||||
print(f'\n写入完成: CSV 新增 {n_csv} 行, 占位 STEP {n_ph} 个')
|
||||
print(f'待补数模清单: {mf} ({len(manifest)} 个型号)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
135
onebot-data/tools/gen-placeholder-step.py
Normal file
135
onebot-data/tools/gen-placeholder-step.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
生成占位数模 (每系列一个通用的"待补数模"方块)。
|
||||
|
||||
用途: 为「参数组合补齐」生成的型号提供占位 STEP —— 构建器要求每个型号必须有数模文件。
|
||||
真实数模后续由 AI 参照同系列已有型号补出, 覆盖同名文件即可。
|
||||
|
||||
产出:
|
||||
catalog/step/PLACEHOLDER.step 通用的方块 (ISO-10303-21, 标准 B-rep)
|
||||
catalog/step/_placeholder/<系列>.step 每系列一份 (便于按系列替换/识别)
|
||||
|
||||
方块边长 60mm, 居中于原点。
|
||||
用法: PYTHONUTF8=1 python gen-placeholder-step.py
|
||||
"""
|
||||
import os
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/onebot-data/catalog'
|
||||
STEP_DIR = os.path.join(CAT, 'step')
|
||||
|
||||
# 方块尺寸 (mm)
|
||||
SX, SY, SZ = 60.0, 60.0, 60.0
|
||||
HX, HY, HZ = SX / 2, SY / 2, SZ / 2
|
||||
|
||||
|
||||
def build_step(label):
|
||||
"""生成一个合法的 STEP 文件内容: 单位立方体 B-rep。"""
|
||||
L = []
|
||||
L.append('ISO-10303-21;')
|
||||
L.append('HEADER;')
|
||||
L.append("FILE_DESCRIPTION(('PLACEHOLDER - real CAD model pending'),'2;1');")
|
||||
L.append("FILE_NAME('%s','2026-01-01T00:00:00',('ONEBOT'),('ONEBOT'),"
|
||||
"'gen-placeholder-step.py','ONEBOT Catalog','');" % label)
|
||||
L.append("FILE_SCHEMA(('AUTOMOTIVE_DESIGN { 1 0 10303 214 1 1 1 1 }'));")
|
||||
L.append('ENDSEC;')
|
||||
L.append('DATA;')
|
||||
L.append("#1=APPLICATION_CONTEXT('core data for automotive mechanical design processes');")
|
||||
L.append("#2=APPLICATION_PROTOCOL_DEFINITION('international standard','automotive_design',2000,#1);")
|
||||
L.append("#3=PRODUCT_CONTEXT('',#1,'mechanical');")
|
||||
L.append("#4=PRODUCT_DEFINITION_CONTEXT('part definition',#1,'design');")
|
||||
L.append("#5=PRODUCT('%s','PLACEHOLDER',$,(#3));" % label)
|
||||
L.append("#6=PRODUCT_DEFINITION_FORMATION('','',#5);")
|
||||
L.append("#7=PRODUCT_DEFINITION('design','',#6,#4);")
|
||||
L.append("#8=PRODUCT_DEFINITION_SHAPE('','',#7);")
|
||||
L.append("#9=(LENGTH_UNIT()NAMED_UNIT(*)SI_UNIT(.MILLI.,.METRE.));")
|
||||
L.append("#10=(NAMED_UNIT(*)PLANE_ANGLE_UNIT()SI_UNIT($,.RADIAN.));")
|
||||
L.append("#11=(NAMED_UNIT(*)SI_UNIT($,.STERADIAN.)SOLID_ANGLE_UNIT());")
|
||||
L.append("#12=UNCERTAINTY_MEASURE_WITH_UNIT(LENGTH_MEASURE(0.001),#9,"
|
||||
"'distance_accuracy_value','');")
|
||||
L.append("#13=(GEOMETRIC_REPRESENTATION_CONTEXT(3)"
|
||||
"GLOBAL_UNCERTAINTY_ASSIGNED_CONTEXT((#12))"
|
||||
"GLOBAL_UNIT_ASSIGNED_CONTEXT((#9,#10,#11))REPRESENTATION_CONTEXT('',''));")
|
||||
|
||||
# 8 个顶点坐标
|
||||
corners = [
|
||||
(-HX, -HY, -HZ), (HX, -HY, -HZ), (HX, HY, -HZ), (-HX, HY, -HZ),
|
||||
(-HX, -HY, HZ), (HX, -HY, HZ), (HX, HY, HZ), (-HX, HY, HZ),
|
||||
]
|
||||
eid = 20
|
||||
pt_ids, vx_ids = [], []
|
||||
for (x, y, z) in corners:
|
||||
L.append('#%d=CARTESIAN_POINT('',(%.3f,%.3f,%.3f));' % (eid, x, y, z))
|
||||
pt_ids.append(eid); eid += 1
|
||||
L.append('#%d=DIRECTION('',(0.,0.,1.));' % eid); dz = eid; eid += 1
|
||||
L.append('#%d=DIRECTION('',(1.,0.,0.));' % eid); dx = eid; eid += 1
|
||||
for pid in pt_ids:
|
||||
L.append('#%d=VERTEX_POINT('',#%d);' % (eid, pid))
|
||||
vx_ids.append(eid); eid += 1
|
||||
|
||||
# 12 条棱
|
||||
edges = [(0, 1), (1, 2), (2, 3), (3, 0),
|
||||
(4, 5), (5, 6), (6, 7), (7, 4),
|
||||
(0, 4), (1, 5), (2, 6), (3, 7)]
|
||||
edge_ids = []
|
||||
for (a, b) in edges:
|
||||
L.append('#%d=LINE('',#%d,#%d);' % (eid, pt_ids[a], pt_ids[b]))
|
||||
ln = eid; eid += 1
|
||||
L.append('#%d=VECTOR('',#%d,1.0);' % (eid, dx)); vc = eid; eid += 1
|
||||
L.append('#%d=EDGE_CURVE('',#%d,#%d,#%d,.T.);'
|
||||
% (eid, vx_ids[a], vx_ids[b], ln))
|
||||
ec = eid; eid += 1
|
||||
L.append('#%d=ORIENTED_EDGE('',*,*,#%d,.T.);' % (eid, ec))
|
||||
edge_ids.append(eid); eid += 1
|
||||
|
||||
# 6 个面
|
||||
faces = [
|
||||
[0, 1, 2, 3], [4, 5, 6, 7],
|
||||
[0, 1, 5, 4], [1, 2, 6, 5],
|
||||
[2, 3, 7, 6], [3, 0, 4, 7],
|
||||
]
|
||||
normals = [(0, 0, -1), (0, 0, 1), (0, -1, 0), (1, 0, 0), (0, 1, 0), (-1, 0, 0)]
|
||||
face_ids = []
|
||||
for fi, f in enumerate(faces):
|
||||
loops = []
|
||||
for k in range(4):
|
||||
loops.append(edge_ids[f[k]])
|
||||
L.append('#%d=EDGE_LOOP('',(%s));'
|
||||
% (eid, ','.join('#%d' % x for x in loops)))
|
||||
el = eid; eid += 1
|
||||
nx, ny, nz = normals[fi]
|
||||
L.append('#%d=DIRECTION('',(%.1f,%.1f,%.1f));' % (eid, nx, ny, nz))
|
||||
nd = eid; eid += 1
|
||||
L.append("#%d=AXIS2_PLACEMENT_3D('',#%d,#%d,$);" % (eid, pt_ids[f[0]], nd))
|
||||
ax = eid; eid += 1
|
||||
L.append('#%d=PLANE(\'\',#%d);' % (eid, ax))
|
||||
pl = eid; eid += 1
|
||||
L.append('#%d=ADVANCED_FACE(\'\',(#%d),#%d,.T.);' % (eid, el, pl))
|
||||
face_ids.append(eid); eid += 1
|
||||
|
||||
L.append('#%d=CLOSED_SHELL(\'\',(%s));'
|
||||
% (eid, ','.join('#%d' % x for x in face_ids)))
|
||||
sh = eid; eid += 1
|
||||
L.append("#%d=MANIFOLD_SOLID_BREP('PLACEHOLDER',#%d);" % (eid, sh))
|
||||
msb = eid; eid += 1
|
||||
L.append('#%d=ADVANCED_BREP_SHAPE_REPRESENTATION(\'PLACEHOLDER\',(#%d,#%d),#13);'
|
||||
% (eid, msb, eid + 1))
|
||||
abr = eid; eid += 1
|
||||
L.append("#%d=AXIS2_PLACEMENT_3D('',#%d,#%d,#%d);" % (eid, pt_ids[0], dz, dx))
|
||||
ax2 = eid; eid += 1
|
||||
L.append("#%d=SHAPE_DEFINITION_REPRESENTATION(#8,#%d);" % (eid, abr))
|
||||
L.append('ENDSEC;')
|
||||
L.append('END-ISO-10303-21;')
|
||||
return '\n'.join(L) + '\n'
|
||||
|
||||
|
||||
def main():
|
||||
os.makedirs(STEP_DIR, exist_ok=True)
|
||||
content = build_step('PLACEHOLDER')
|
||||
p = os.path.join(STEP_DIR, 'PLACEHOLDER.step')
|
||||
with open(p, 'w', encoding='ascii', newline='\r\n') as f:
|
||||
f.write(content)
|
||||
print(f'写入 {p} ({os.path.getsize(p)} 字节)')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
109
onebot-data/tools/infer-sku-template.py
Normal file
109
onebot-data/tools/infer-sku-template.py
Normal file
@@ -0,0 +1,109 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
编码模板反推 v2 —— 支持零填充与空值占位符。
|
||||
|
||||
v1 的 explain() 贪心匹配"非零填充", 把 ULP50040AEK0 的 "040" 拆成 字面量"0" + 值"40",
|
||||
产生大量假阴性。真实规则是:
|
||||
- 数值参数常带前导零 (stroke=40 -> "040")
|
||||
- 枚举参数为空时, 编码里用 '_' 占位 (UAGP075_K045WS0)
|
||||
|
||||
本脚本对每个参数尝试 {原值, 各前导零位数, '_'占位} 的所有组合, 取能对齐的;
|
||||
若某系列的"字面量序列"完全一致 → 可模板化。
|
||||
|
||||
用法: PYTHONUTF8=1 python infer-sku-template.py
|
||||
"""
|
||||
import json, io, collections, sys
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/release/web_2026.08/catalog.json'
|
||||
d = json.load(io.open(CAT, encoding='utf-8-sig'))
|
||||
|
||||
MAXPAD = 4
|
||||
|
||||
|
||||
def align_all(code, params, values):
|
||||
"""返回全部可能的字面量序列 (元组)。每个参数尝试: 原值 / 前导零 / '_'占位。"""
|
||||
results = []
|
||||
|
||||
def dfs(i, pos, lits):
|
||||
if i == len(params):
|
||||
results.append(tuple(lits + [code[pos:]]))
|
||||
return
|
||||
pc = params[i]
|
||||
v = str(values.get(pc, ''))
|
||||
cands = []
|
||||
if v == '':
|
||||
cands = ['_', ''] # 空值: 占位符或什么都不写
|
||||
else:
|
||||
cands = [v] + ['0' * z + v for z in range(1, MAXPAD + 1)]
|
||||
seen = set()
|
||||
for pat in cands:
|
||||
if pat in seen:
|
||||
continue
|
||||
seen.add(pat)
|
||||
if pat == '':
|
||||
dfs(i + 1, pos, lits + [''])
|
||||
continue
|
||||
idx = code.find(pat, pos)
|
||||
if idx >= 0:
|
||||
dfs(i + 1, idx + len(pat), lits + [code[pos:idx]])
|
||||
dfs(0, 0, [])
|
||||
return results
|
||||
|
||||
|
||||
def render_template(params, lits):
|
||||
"""字面量序列 -> 模板串。空值参数渲染为 '_'。"""
|
||||
out = ''
|
||||
for i, pc in enumerate(params):
|
||||
out += lits[i] + '{' + pc + '}'
|
||||
out += lits[len(params)]
|
||||
return out
|
||||
|
||||
|
||||
good, bad = [], []
|
||||
for s in d['series']:
|
||||
vs = s.get('variants') or []
|
||||
if not vs:
|
||||
continue
|
||||
params = [p['code'] for p in s['parameters']]
|
||||
lit_sets = []
|
||||
unaligned = 0
|
||||
for v in vs:
|
||||
alts = align_all(v['modelCode'], params, v['params'])
|
||||
if not alts:
|
||||
unaligned += 1
|
||||
continue
|
||||
lit_sets.append(alts)
|
||||
|
||||
if not lit_sets:
|
||||
bad.append((s['code'], len(vs), '完全无法对齐', [], 0))
|
||||
continue
|
||||
|
||||
# 统计"出现在最多变体里"的字面量序列 (每个变体取任一可对齐解)
|
||||
cnt = collections.Counter()
|
||||
for alts in lit_sets:
|
||||
for a in set(alts):
|
||||
cnt[a] += 1
|
||||
top, hit = cnt.most_common(1)[0]
|
||||
rate = hit / len(vs) * 100
|
||||
|
||||
if rate >= 99.5 and unaligned == 0:
|
||||
good.append((s['code'], len(vs), rate, render_template(params, top), len(cnt)))
|
||||
else:
|
||||
bad.append((s['code'], len(vs), f'{rate:.1f}% ({len(cnt)} 种)', cnt.most_common(3), unaligned))
|
||||
|
||||
print('=' * 96)
|
||||
print(f'可模板化的系列 ({len(good)} 个)')
|
||||
print('=' * 96)
|
||||
for code, n, rate, tpl, k in sorted(good):
|
||||
print(f'{code:7s} {n:6d} 变体 {rate:6.1f}% {tpl}')
|
||||
|
||||
print()
|
||||
print('=' * 96)
|
||||
print(f'不可模板化的系列 ({len(bad)} 个)')
|
||||
print('=' * 96)
|
||||
for code, n, why, tops, un in sorted(bad, key=lambda x: -x[1]):
|
||||
print(f'{code:7s} {n:6d} 变体 {why}' + (f' 未对齐 {un}' if un else ''))
|
||||
for lits, c in tops[:3]:
|
||||
print(f' {c:5d}× {lits}')
|
||||
|
||||
print(f'\n合计: 可模板化 {len(good)} / 共 {len(good) + len(bad)} 个系列')
|
||||
101
onebot-data/tools/quantify-combo-gap.py
Normal file
101
onebot-data/tools/quantify-combo-gap.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
量化"参数组合缺口": 合法组合 (域 + 规则内) vs 库中已有变体, 以及补全的规模。
|
||||
|
||||
对每个系列:
|
||||
- 现有变体数
|
||||
- 笛卡尔积规模 (各参数值域相乘)
|
||||
- 规则过滤后的合法组合数 (复用 C# Configurator 同语义的规则求值)
|
||||
- 缺口 = 合法组合 - 已覆盖
|
||||
这决定"补全组合数据"的可行性与数据量。
|
||||
"""
|
||||
import json, io, itertools
|
||||
|
||||
CAT = r'D:/开发/OnebotCatalog/release/web_2026.08/catalog.json'
|
||||
d = json.load(io.open(CAT, encoding='utf-8-sig'))
|
||||
|
||||
|
||||
def S(v):
|
||||
return '' if v is None else str(v)
|
||||
|
||||
|
||||
def eq(a, b):
|
||||
return S(a).lower() == S(b).lower()
|
||||
|
||||
|
||||
def cond_match(c, params):
|
||||
"""规则条件求值 (与 app.js condMatches / C# CondMatches 同语义)。"""
|
||||
if not c:
|
||||
return True
|
||||
val = params.get(c['Param'])
|
||||
if val is None:
|
||||
return False
|
||||
op = c['Op']
|
||||
cv = c['Value']
|
||||
if op == 'eq':
|
||||
return eq(val, cv)
|
||||
if op == 'ne':
|
||||
return not eq(val, cv)
|
||||
if op == 'in':
|
||||
return isinstance(cv, list) and any(eq(val, x) for x in cv)
|
||||
try:
|
||||
a, b = float(S(val)), float(S(cv))
|
||||
except ValueError:
|
||||
return False
|
||||
return {'gt': a > b, 'ge': a >= b, 'lt': a < b, 'le': a <= b}.get(op, False)
|
||||
|
||||
|
||||
def rule_violated(rules, params):
|
||||
for r in rules or []:
|
||||
if cond_match(r.get('If'), params) and not cond_match(r.get('Then'), params):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
print(f'{"系列":7s} {"现有":>6s} {"笛卡尔":>9s} {"规则内":>9s} {"缺口":>9s} {"倍数":>5s}')
|
||||
print('-' * 62)
|
||||
tot_exist = tot_gap = 0
|
||||
rows = []
|
||||
for s in d['series']:
|
||||
vs = s.get('variants') or []
|
||||
if not vs:
|
||||
continue
|
||||
params = s['parameters']
|
||||
doms = []
|
||||
for p in params:
|
||||
vals = p.get('values') or []
|
||||
if not vals:
|
||||
doms = None
|
||||
break
|
||||
doms.append(vals)
|
||||
if doms is None:
|
||||
rows.append((s['code'], len(vs), None, None, None))
|
||||
continue
|
||||
cart = 1
|
||||
for vals in doms:
|
||||
cart *= len(vals)
|
||||
# 规则过滤 (笛卡尔过大时抽样估算)
|
||||
legal = 0
|
||||
if cart <= 400000:
|
||||
for combo in itertools.product(*doms):
|
||||
prm = {p['code']: v for p, v in zip(params, combo)}
|
||||
if not rule_violated(s.get('rules'), prm):
|
||||
legal += 1
|
||||
else:
|
||||
legal = None # 太大, 跳过精确统计
|
||||
gap = (legal - len(vs)) if legal is not None else None
|
||||
rows.append((s['code'], len(vs), cart, legal, gap))
|
||||
if legal is not None:
|
||||
tot_exist += len(vs)
|
||||
tot_gap += max(0, gap)
|
||||
|
||||
for code, n, cart, legal, gap in sorted(rows, key=lambda x: -(x[4] or -1)):
|
||||
c = f'{cart:,}' if cart else '—'
|
||||
l = f'{legal:,}' if legal else '—'
|
||||
g = f'{gap:,}' if gap is not None else '—'
|
||||
mult = f'{legal/n:.1f}x' if legal and n else '—'
|
||||
flag = ' <-- 缺口大' if gap and gap > 50000 else ''
|
||||
print(f'{code:7s} {n:6d} {c:>9s} {l:>9s} {g:>9s} {mult:>5s}{flag}')
|
||||
|
||||
print(f'\n合计: 现有 {tot_exist:,} 变体, 缺口 {tot_gap:,} 组合')
|
||||
print(f'补全后约 {tot_exist + tot_gap:,} 变体 (是现在的 {(tot_exist+tot_gap)/tot_exist:.1f} 倍)')
|
||||
40
onebot-data/tools/verify-templates.py
Normal file
40
onebot-data/tools/verify-templates.py
Normal file
@@ -0,0 +1,40 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
验证: 用模板对【已有型号】重新渲染, 必须与库中编码完全一致。
|
||||
凡不一致 → 模板有误, 绝不能用它生成新编码 (会造出不存在的零件号)。
|
||||
"""
|
||||
import json, io, os, csv, sys, importlib.util
|
||||
|
||||
HERE = os.path.dirname(os.path.abspath(__file__))
|
||||
spec = importlib.util.spec_from_file_location('gmc', os.path.join(HERE, 'gen-missing-combos.py'))
|
||||
gmc = importlib.util.module_from_spec(spec)
|
||||
sys.argv = ['x'] # 防止执行 main
|
||||
spec.loader.exec_module(gmc)
|
||||
|
||||
CAT = gmc.CAT
|
||||
print(f'{"系列":7s} {"变体":>6s} {"模板一致":>9s} {"失败":>6s} 反例')
|
||||
print('-' * 74)
|
||||
allok = True
|
||||
for code in sorted(gmc.SAFE):
|
||||
if code not in gmc.TEMPLATES:
|
||||
continue
|
||||
tpl, _ = gmc.TEMPLATES[code]
|
||||
rows = list(csv.DictReader(io.open(os.path.join(CAT, 'csv', code + '.csv'), encoding='utf-8-sig')))
|
||||
ok = fail = 0
|
||||
bad = []
|
||||
for r in rows:
|
||||
prm = {k: v for k, v in r.items() if k not in ('model_code', 'step_file')}
|
||||
got = gmc.render(tpl, prm)
|
||||
if got == r['model_code']:
|
||||
ok += 1
|
||||
else:
|
||||
fail += 1
|
||||
if len(bad) < 3:
|
||||
bad.append(f'{r["model_code"]} -> 渲染"{got}"')
|
||||
flag = '' if fail == 0 else ' <-- 模板有误!'
|
||||
if fail:
|
||||
allok = False
|
||||
print(f'{code:7s} {len(rows):6d} {ok:9d} {fail:6d} {bad[0] if bad else ""}{flag}')
|
||||
|
||||
print()
|
||||
print('结论:', '所有模板对已有型号 100% 复现 ✅' if allok else '存在模板错误, 不可用于生成新编码 ❌')
|
||||
Reference in New Issue
Block a user