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:
wangruiguo
2026-09-15 02:14:55 +08:00
parent 3c4f236cba
commit fc5ee1dd36
19 changed files with 7355 additions and 25 deletions

View 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()