feat: P07小臂骨架顶板整条加高19mm补强+前后外缘R3圆角,gusset弧R28->R14随顶板联动相切融入;重算全关节行程J4反向-121->-111,同步重建模型数据与查看器

This commit is contained in:
cbb
2026-09-28 01:00:04 +08:00
parent 85856e9821
commit 5518d12abb
31 changed files with 257 additions and 1283 deletions
+3 -3
View File
@@ -104,10 +104,10 @@
"名称": "小臂骨架",
"类型": "3D打印",
"数量": 1,
"体积cm3": 209.6,
"质量g_PLA": 259.9,
"体积cm3": 289.1,
"质量g_PLA": 358.5,
"包络": "[-16.5 92.0 187.0 .. 53.5 174.0 367.0] (70.0x82.0x180.0)",
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉"
"说明": "V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉"
},
{
"件号": "P08_腕偏转座",
+102
View File
@@ -0,0 +1,102 @@
[
{
"id": "S01",
"规格": "M2 x 10",
"螺纹": "M2",
"长度mm": 10,
"头型": "圆柱头内六角",
"数量": 18,
"位置": "J1~J6 输出毂:P04/P05/P06/P07/P08/P09 各 3 颗",
"备注": "Ø2.8 通孔 + Ø5.6 沉孔,BCD 29(3 孔均布),旋入电机输出毂螺纹孔(深 6mm)"
},
{
"id": "S02",
"规格": "M3 x 10",
"螺纹": "M3",
"长度mm": 10,
"头型": "圆柱头内六角",
"数量": 20,
"位置": "P04/P05/P06/P07/P08 各 4 颗 -> 下级电机尾端面",
"备注": "Ø2.8 通孔 + Ø5.5 沉孔(深 4mm),旋入电机尾端面 4 螺纹孔 (±17.55, ±20.46)"
},
{
"id": "S03",
"规格": "M3 x 16",
"螺纹": "M3",
"长度mm": 16,
"头型": "沉头内六角",
"数量": 4,
"位置": "P03 后置重载法兰 -> M1 电机尾端面",
"备注": "Ø3.4 通孔 + Ø6.5 沉孔,孔距 35.1 x 40.9,轴向锁死防拉脱"
},
{
"id": "S04",
"规格": "M3 x 8",
"螺纹": "M3",
"长度mm": 8,
"头型": "沉头内六角",
"数量": 4,
"位置": "P03 前置止脱锁紧法兰 -> M1 电机前端面",
"备注": "Ø3.2 通孔 + Ø6.0 沉孔,BCD 55(R27.5,4 孔 17.8/107.8/197.8/287.8°)"
},
{
"id": "S05",
"规格": "M4 x 25",
"螺纹": "M4",
"长度mm": 25,
"头型": "圆柱头内六角",
"数量": 4,
"位置": "P01 立柱分段对锁:A-B 缝 2 颗 + B-C 缝 2 颗",
"备注": "Ø4.4 通孔,非对称偏心孔位物理防反装;需另配 M4 螺母 4 枚"
},
{
"id": "S06",
"规格": "M4 x 16",
"螺纹": "M4",
"长度mm": 16,
"头型": "沉头内六角",
"数量": 4,
"位置": "P09 末端工具法兰 -> 末端工具/夹爪",
"备注": "Ø3.7 通孔 + Ø7.0 沉孔(深 4mm),4 孔分布圆 R20"
},
{
"id": "S07",
"规格": "M5 x 25",
"螺纹": "M5",
"长度mm": 25,
"头型": "圆柱头内六角",
"数量": 4,
"位置": "P02 底座中核心 -> P01 立柱底部榫头",
"备注": "Ø5.5 通孔 + Ø10 沉孔(深 6mm),非对称孔位防反装,轴向力由榫座面承载"
},
{
"id": "S08",
"规格": "M5 x 25",
"螺纹": "M5",
"长度mm": 25,
"头型": "圆柱头内六角",
"数量": 4,
"位置": "P02 前/后力臂 -> 中核心:前臂 2 颗 + 后臂 2 颗",
"备注": "Ø5.5 通孔 + Ø10 沉孔;需另配 M5 六角螺母 4 枚(嵌入螺母沉槽)"
},
{
"id": "S09",
"规格": "M5 x 35",
"螺纹": "M5",
"长度mm": 35,
"头型": "圆柱头内六角",
"数量": 4,
"位置": "P03 肩吊臂安装台 -> P01_C 立柱顶面",
"备注": "Ø5.5 通孔,穿过 15mm 凸台旋入立柱 20mm"
},
{
"id": "S10",
"规格": "M6 x 35",
"螺纹": "M6",
"长度mm": 35,
"头型": "外六角",
"数量": 6,
"位置": "底座 6 组 28x7 紧固腰槽 -> 工作台/桌面",
"备注": "需另配 M6 螺母 + 平垫圈各 6(建议加弹垫防松)"
}
]
Binary file not shown.

Before

Width:  |  Height:  |  Size: 189 KiB

After

Width:  |  Height:  |  Size: 114 KiB

+2 -2
View File
@@ -25,11 +25,11 @@
},
{
"axis": "J4",
"lo": -121.0,
"lo": -111.0,
"hi": 16.0,
"continuous": false,
"zero": 0.0,
"note": "正向 +16° / 反向 -121°,总行程 137°(全对全动态扫描验证,含相邻件与电机)"
"note": "正向 +16° / 反向 -111°,总行程 127°(全对全动态扫描验证,含相邻件与电机)"
},
{
"axis": "J5",
+20 -1178
View File
File diff suppressed because it is too large Load Diff
+34 -43
View File
@@ -133,8 +133,8 @@ def p_chest():
def split_p01_poka_yoke(p01):
"""拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印):
带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 7.1,内腔收窄 51->44 换取双厚壁,
单边配合间隙 0.3)、非对称 M4 横向对锁穿钉孔(物理防反装防呆)、以及本体 3D 凹刻朝向标识。
带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁,
单边配合间隙 0.5)、非对称 M4 横向对锁穿钉孔(物理防反装防呆)、以及本体 3D 凹刻朝向标识。
"""
z_bot = 22.0
z_s1 = 172.0
@@ -149,7 +149,7 @@ def split_p01_poka_yoke(p01):
cut_above_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_s1, z_top + 10.0)
p01_a_base = p01.cut(cut_above_s1)
tongue1_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1))
.rect(58.2, 58.2).extrude(h_lap)
.rect(57.8, 57.8).extrude(h_lap)
.edges("|Z").fillet(3.4).edges(">Z").chamfer(1.5)).val()
tongue1_cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1 - 1.0))
.rect(44.0, 44.0).extrude(h_lap + 2.0)).val()
@@ -176,11 +176,11 @@ def split_p01_poka_yoke(p01):
cut_above_s2 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_s2, z_top + 10.0)
p01_b_base = p01.cut(cut_below_s1).cut(cut_above_s2)
pocket1 = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s1 - 0.1))
.rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.7)).val()
.rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.9)).val()
p01_b_base = p01_b_base.cut(pocket1)
tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2))
.rect(58.2, 58.2).extrude(h_lap)
.rect(57.8, 57.8).extrude(h_lap)
.edges("|Z").fillet(3.4).edges(">Z").chamfer(1.5)).val()
tongue2_cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2 - 1.0))
.rect(44.0, 44.0).extrude(h_lap + 2.0)).val()
@@ -204,7 +204,7 @@ def split_p01_poka_yoke(p01):
cut_below_s2 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s2)
p01_c_base = p01.cut(cut_below_s2)
pocket2 = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2 - 0.1))
.rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.7)).val()
.rect(58.8, 58.8).extrude(h_lap + 0.6).edges("|Z").fillet(3.9)).val()
p01_c = p01_c_base.cut(pocket2)
for xx in xx_bolts:
p01_c = p01_c.cut(Ycyl(2.2, Y_COL - 40.0, Y_COL + 40.0, xx, z_bolt2))
@@ -772,24 +772,32 @@ def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket"):
s = s.fuse(disc)
# 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph)
# 后缘不取恒定 v=-5(那会在 y=123 处相对 R35 柱面 (x=53.14) 外凸 0.36mm,
# 形成一条 z 向竖鳍,并在顶端被 cut_top_solid 切出 z=452 硬台面):
# 改为从柱面 (v=0, y=128) 平滑长到顶壁 (v=+29, y=157):
# - 低端后角落在 R35 柱面上,与立柱共面零外凸,底端裙边不再有拼接竖筋;
# - 高端后角与 top_fill 矩形 (y 157..174) 齐平,z=452 台面与 R2 小圆角
# 不再需要,立柱到竖直壁完全由放样曲面相切过渡。
Z_top = z_pitch - 35.0
z_start = z_bot
N = 16
N = 24
wp = cq.Workplane("XY")
for i in range(N + 1):
t = i / float(N)
z_curr = z_start + t * (Z_top - z_start)
st = 3.0 * (t**2) - 2.0 * (t**3)
sb = 6.0 * (t**5) - 15.0 * (t**4) + 10.0 * (t**3) # 后缘 smoothstep(两端切线水平)
y_center = y_cyl_max + st * (y_out - y_cyl_max)
y_side = HL.Y_AXIS + st * (y_out - HL.Y_AXIS)
v_c = y_center - HL.Y_AXIS
v_s = y_side - HL.Y_AXIS
v_b = sb * (y_hub_in - HL.Y_AXIS) # 0 (柱面) -> +29 (顶壁)
v_mid = max(v_c, v_s + 0.02)
w = (cq.Workplane("XY", origin=(HL.X_ARM, HL.Y_AXIS, z_curr))
.moveTo(-35.0, v_s)
.threePointArc((0.0, v_mid), (35.0, v_s))
.lineTo(35.0, -5.0)
.lineTo(-35.0, -5.0)
.lineTo(35.0, v_b)
.lineTo(-35.0, v_b)
.close())
wp = wp.add(w)
@@ -951,6 +959,7 @@ def p_forearm_bone(j4, j5, half=None):
y_front = Y_COVER - 0.5 # 155.5
y_rear = Y_REAR - 8.0 # 92.0
z_top_housing = Z_WR + 39.0 # 261.0 (顶面加厚 4.0mm,消除过薄破面问题)
z_roof_top = z_top_housing + 19.0 # 280.0 (顶板整条加高 19mm,用户确认方案4)
# 1. J5 电机前后安装环
collar_front = Ycyl(35.0, HL.Y_COVER - 6.0, HL.Y_COVER - 0.5, X_ARM, Z_WR)
@@ -964,8 +973,11 @@ def p_forearm_bone(j4, j5, half=None):
# 3. M5 电机壳体水平顶部顶板 (Z: Z_WR..z_top_housing, Y: y_rear..y_front)
roof_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
.moveTo(y_rear, Z_WR)
.lineTo(y_rear, z_top_housing)
.lineTo(y_front, z_top_housing)
.lineTo(y_rear, z_roof_top)
.lineTo(y_front - 3.0, z_roof_top)
.threePointArc((y_front - 3.0 + 3.0 * math.cos(math.pi / 4),
z_roof_top - 3.0 + 3.0 * math.sin(math.pi / 4)),
(y_front, z_roof_top - 3.0))
.lineTo(y_front, Z_WR)
.close()
.extrude(35.0, both=True))
@@ -983,16 +995,17 @@ def p_forearm_bone(j4, j5, half=None):
# 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块")
# 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm)
# 起点在顶板平整面 (y_arc_start, z_top_housing),精确相切至立柱内侧面 (y_hub_in, z_arc_end)
# 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end)
# 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起!
R_arc = 28.0
# 注:顶板整条加高19mm(z_roof_top=280)后,R28改R14使弧仍与顶板面光滑相切,不出现折角
R_arc = 14.0 # 随顶板加高联动重定:相切起点(144.5,280)->终点(158.5,294),避开P06环底z=297留3mm
y_arc_start = y_hub_in - R_arc
z_arc_end = z_top_housing + R_arc
z_arc_end = z_roof_top + R_arc
p_mid_arc = (y_arc_start + R_arc * math.cos(math.pi / 4),
z_arc_end - R_arc * math.sin(math.pi / 4))
gusset_w = (cq.Workplane("YZ", origin=(X_ARM, 0, 0))
.moveTo(y_arc_start, z_top_housing)
.moveTo(y_arc_start, z_roof_top)
.threePointArc(p_mid_arc, (y_hub_in, z_arc_end))
.lineTo(y_out, z_arc_end)
.lineTo(y_out, z_top_housing)
@@ -1033,37 +1046,15 @@ def p_forearm_bone(j4, j5, half=None):
# ========== 关键圆角优化 ==========
try:
# 后侧顶板外边缘倒角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角)
# 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角)
wp_s = cq.Workplane(obj=s)
sel_rear = cq.selectors.BoxSelector(
(X_ARM - 40, y_rear - 1.0, z_top_housing - 1.0),
(X_ARM + 40, y_rear + 1.0, z_top_housing + 1.0)
(X_ARM - 40, y_rear - 1.0, z_roof_top - 1.0),
(X_ARM + 40, y_rear + 1.0, z_roof_top + 1.0)
)
s = wp_s.edges(sel_rear).chamfer(CHAMFER_EDGE).val()
s = wp_s.edges(sel_rear).fillet(FILLET_SMALL).val()
except Exception as e:
print(f"P07 顶板后边缘倒角异常: {e}")
try:
# 圆润化 J4 圆盘外沿
wp_s = cq.Workplane(obj=s)
sel_disc = cq.selectors.BoxSelector(
(X_ARM - 40, y_out - 2, Z_EL - 40),
(X_ARM + 40, y_out + 2, Z_EL + 40)
)
s = wp_s.edges(sel_disc).fillet(FILLET_SMALL).val()
except Exception:
pass
try:
# 圆润化垂直立柱外侧边棱
wp_s = cq.Workplane(obj=s)
sel_leg = cq.selectors.BoxSelector(
(X_ARM - 37, y_out - 2, z_top_housing),
(X_ARM + 37, y_out + 2, Z_EL + 40)
)
s = wp_s.edges(sel_leg).fillet(FILLET_SMALL).val()
except Exception:
pass
print(f"P07 顶板后边缘圆角异常: {e}")
return s
@@ -1185,7 +1176,7 @@ def build():
("P06_大臂骨架", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488,
"Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体,单实体0干涉"),
("P07_小臂骨架", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed,
"V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉"),
"V2 L型结构:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉"),
("P08_腕偏转座", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706,
"V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉"),
("P09_工具法兰", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626,
+4 -4
View File
@@ -28,10 +28,10 @@ def rebuild_viewer():
print(" - 读取几何网格与关节运动学数据...", end="", flush=True)
data = io.open(WEB + "/mesh_arm.json", encoding="utf-8").read()
sheets = io.open(WEB + "/sheets.jsdata", encoding="utf-8").read()
col = io.open(DOCS + "/collision.json", encoding="utf-8").read() if os.path.exists(
DOCS + "/collision.json") else "[]"
trv = io.open(DOCS + "/travel.json", encoding="utf-8").read() if os.path.exists(
DOCS + "/travel.json") else "[]"
screws = io.open(DOCS + "/screws.json", encoding="utf-8").read() if os.path.exists(
DOCS + "/screws.json") else "[]"
tpl = io.open(SRC + "/viewer_tpl.html", encoding="utf-8").read()
print(f" 完成 ({len(data)/1024:.0f} KB) ✓", flush=True)
@@ -39,8 +39,8 @@ def rebuild_viewer():
out = (tpl.replace("/*__THREE__*/", three)
.replace("/*__DATA__*/", data)
.replace("/*__SHEETS__*/", sheets)
.replace("/*__COL__*/", col)
.replace("/*__TRAVEL__*/", trv))
.replace("/*__TRAVEL__*/", trv)
.replace("/*__SCREWS__*/", screws))
assert "/*__" not in out, [l for l in out.split("\n") if "/*__" in l][:3]
dst = WEB + "/机器人手臂3D查看器.html"
io.open(dst, "w", encoding="utf-8").write(out)
+82 -43
View File
@@ -9,9 +9,9 @@
html,body{margin:0;height:100%;background:var(--bg);color:var(--tx);
font:13px/1.55 "Microsoft YaHei","Segoe UI",system-ui,sans-serif;overflow:hidden;
-webkit-user-select:none;user-select:none}
#app{display:grid;grid-template-columns:274px minmax(430px,1fr) 320px;grid-template-rows:46px 1fr 26px;
#app{display:grid;grid-template-columns:274px minmax(430px,1fr);grid-template-rows:46px 1fr 26px;
height:100%;min-width:0;overflow-x:auto}
header{grid-column:1/4;display:flex;align-items:center;gap:8px;padding:0 12px;flex-wrap:nowrap;
header{grid-column:1/-1;display:flex;align-items:center;gap:8px;padding:0 12px;flex-wrap:nowrap;
overflow-x:auto;overflow-y:hidden;background:var(--pan);border-bottom:1px solid var(--line)}
header .q{white-space:nowrap;flex:none;color:var(--dim);font-size:12px}
header b{font-size:14px;white-space:nowrap;flex:none}
@@ -22,19 +22,15 @@ button:hover{border-color:var(--ac);color:#fff}
button.on{background:#22406b;border-color:var(--ac)}
aside{background:var(--pan);overflow:auto;padding:8px;min-width:0}
#left{border-right:1px solid var(--line)}
#right{border-left:1px solid var(--line)}
#app.r{grid-template-columns:274px minmax(430px,1fr) 320px}
#app.l{grid-template-columns:274px minmax(430px,1fr)}
#stage{position:relative;overflow:hidden;min-width:0;
background:radial-gradient(120% 120% at 50% 0%,#1b2028 0%,#0d0f13 70%)}
canvas{display:block;width:100%;height:100%}
footer{grid-column:1/4;background:var(--pan);border-top:1px solid var(--line);
footer{grid-column:1/-1;background:var(--pan);border-top:1px solid var(--line);
display:flex;align-items:center;gap:14px;padding:0 12px;color:var(--dim);font-size:12px;
white-space:nowrap;overflow:hidden}
@media (max-width:1150px){
#app{grid-template-columns:250px minmax(400px,1fr)}
#right{display:none}
#app.r #right{display:block}
}
@media (max-width:760px){
#app{grid-template-columns:minmax(0,1fr)}
@@ -75,6 +71,27 @@ label em.ok{color:#5ddc90}
svg{display:block;width:100%;height:auto;background:#fff;border-radius:6px}
#sheet{display:none;padding:10px;overflow:auto;height:100%}
small{color:var(--dim)}
/* ---- edge BOM dock (collapsible drawer at the right edge) ---- */
#bomTab{position:fixed;right:0;top:64px;z-index:30;writing-mode:vertical-rl;text-orientation:mixed;
background:var(--pan2);color:var(--tx);border:1px solid var(--line);border-right:none;
border-radius:6px 0 0 6px;padding:12px 6px;font:inherit;cursor:pointer;letter-spacing:3px}
#bomTab:hover{border-color:var(--ac);color:#fff}
#bomTab b{color:var(--ac)}
#bomDock{position:fixed;right:0;top:46px;bottom:26px;width:min(430px,94vw);z-index:29;
transform:translateX(103%);transition:transform .22s ease;display:flex;flex-direction:column;
background:var(--pan);border-left:1px solid var(--ac);box-shadow:-10px 0 28px rgba(0,0,0,.5)}
#bomDock.open{transform:none}
#bomDock .dk-hd{display:flex;align-items:center;gap:8px;padding:9px 10px;flex:none;
border-bottom:1px solid var(--line)}
#bomDock .dk-hd b{font-size:13px}
#bomDock .dk-hd .q{color:var(--dim);font-size:11px}
#bomDock .dk-x{margin-left:auto}
#bomDock .dk-bd{overflow:auto;padding:8px 11px 16px;flex:1;min-height:0}
.dk-sec{margin:4px 0 5px;color:var(--ac);font-size:12px;letter-spacing:.4px}
.dk-note{color:var(--dim);font-size:11px;margin:1px 0 6px}
#screws td{white-space:normal}
#screws td:first-child{white-space:nowrap}
@media (max-width:760px){#bomTab{top:auto;bottom:36px}#bomDock{top:46px;bottom:26px}}
</style>
</head>
<body>
@@ -97,7 +114,6 @@ small{color:var(--dim)}
<button id="bXRay" class="on">电机半透</button>
<button id="bFit">复位</button>
<button id="bL" title="显示/隐藏左栏">左栏</button>
<button id="bR" title="显示/隐藏右栏">右栏</button>
</header>
<aside id="left">
@@ -121,15 +137,6 @@ small{color:var(--dim)}
<div id="sheet"></div>
</div>
<aside id="right">
<h3>BOM / 明细</h3>
<table id="bom"><thead><tr><th>#</th><th>件号</th><th>cm3</th><th>g</th></tr></thead><tbody></tbody></table>
<h3>接口自检(实测值)</h3>
<div class="kv" id="iface"></div>
<h3>碰撞检查</h3>
<div id="col"></div>
</aside>
<footer>
<span>左键旋转 · 滚轮缩放 · 右键平移(拖动) · 单击选中高亮 · <b>右键单击隐藏部件(或按H键)</b> · 左侧全选/勾选恢复</span>
<span class="sp"></span>
@@ -137,6 +144,21 @@ small{color:var(--dim)}
</footer>
</div>
<button id="bomTab" title="展开 / 收起 物料清单 BOM">物料清单 <b>BOM</b></button>
<aside id="bomDock">
<div class="dk-hd">
<b>物料清单 BOM</b><span class="q" id="bomStat"></span>
<button id="bomHide" class="dk-x" title="收起">收起 ▶</button>
</div>
<div class="dk-bd">
<div class="dk-sec">紧固件 / 螺丝清单(全部紧固点)</div>
<div class="dk-note" id="scrSum"></div>
<table id="screws"><thead><tr><th>规格</th><th>长 mm</th><th>数量</th><th>位置 / 用途</th></tr></thead><tbody></tbody></table>
<div class="dk-sec">打印件 BOM(原明细,PLA)</div>
<table id="bom"><thead><tr><th>#</th><th>件号</th><th>cm3</th><th>g</th></tr></thead><tbody></tbody></table>
</div>
</aside>
<script>/*__THREE__*/</script>
<script>
const DATA = /*__DATA__*/;
@@ -249,6 +271,10 @@ setExpl(0); // start assembled, not exploded
/* ---- camera orbit ---- */
let az = -0.9, po = 1.15, rd = 620, tx = 120, ty = 190, tz = -30;
// lookAt with the world up-axis degenerate-rolls the image when looking near-vertically,
// so top view pins the screen-up to -Z: model X runs right, depth runs down-screen
const UP_Y = new V3(0, 1, 0), UP_TOP = new V3(0, 0, -1);
let camUp = UP_Y.clone();
function fitTo(b) {
const c = [(b[0][0]+b[1][0])/2, (b[0][1]+b[1][1])/2, (b[0][2]+b[1][2])/2];
tx = c[0]; ty = c[1]; tz = c[2];
@@ -268,6 +294,7 @@ let BB = [[1e9,1e9,1e9],[-1e9,-1e9,-1e9]];
})();
fitTo(BB);
function updCam() {
cam.up.copy(camUp);
cam.position.set(tx + rd*Math.sin(po)*Math.cos(az), ty + rd*Math.cos(po), tz + rd*Math.sin(po)*Math.sin(az));
cam.lookAt(tx, ty, tz);
}
@@ -496,30 +523,40 @@ document.getElementById('sum').innerHTML =
'<i>总质量(PLA)</i><span>' + gTot.toFixed(0) + ' g</span>' +
'<i>电机数量</i><span>' + NMOT + ' 台(同款 电机.STEP)</span>' +
'<i>工作空间</i><span>约 ' + Math.round(BB[1][2]) + ' x ' + Math.round(BB[1][0]) + ' mm</span>';
/* ---- measured interfaces ---- */
const md = DATA.measured, Mt = md.M, Ct = md.C;
document.getElementById('iface').innerHTML = [
['电机外形', 'Ø' + Mt.hous_d + ' x ' + Mt.hous_len + ',整机长 ' + Mt.cover_z + ',尾部 PCBA 方 ' + Mt.pcba_w],
['减速', '两级 0.5M 行星:24T 太阳 / 3x36T 行星 / 96T 内齿'],
['输出接口', 'Ø' + Mt.neck_d + 'x' + Mt.neck_depth + ' 颈孔 + Ø' + Mt.centre_d + ' 中心孔'],
['螺栓', '3 x M2 @ BCD ' + Mt.m2_bcd + ',时钟 ' + Mt.m2_clock.join('/')],
['贴合面', '电机尾基准 + ' + md.HUB_FACE_Z + ' = 从动盘贴合面(联轴器两盖板面跨距 ' + Ct.gap + ')'],
['抱箍', '带宽 ' + Ct.clamp_od_r*2 + ',凸耳 R' + Ct.lug_r1 + ',轴向 ' + md.CLAMP_SPAN.join('~')],
['齿', Ct.n_teeth + ' 齿端面花键 R' + Ct.tooth_r0 + '~' + Ct.tooth_r1 + ',齿高 ' + Ct.tooth_s],
].map(r => '<i>' + r[0] + '</i><span>' + r[1] + '</span>').join('');
/* ---- collision ---- */
const CL = /*__COL__*/;
document.getElementById('col').innerHTML = CL.length
? '<table><tr><th>零件对</th><th>mm3</th></tr>' +
CL.map(r => '<tr><td>' + r[0] + ' x ' + r[1] + '</td><td class="bad">' + r[2] + '</td></tr>').join('') + '</table>'
: '<span class="good">0 对干涉(全部零件布尔交集体积 &lt; 2 mm3)</span>';
/* ---- edge BOM dock: fasteners + merged parts BOM ---- */
const SCREWS = /*__SCREWS__*/;
const skb = document.querySelector('#screws tbody');
const bySize = {};
let nTot = 0;
for (const s of SCREWS) {
nTot += s.数量;
bySize[s.螺纹] = (bySize[s.螺纹] || 0) + s.数量;
const tr = document.createElement('tr');
tr.innerHTML = '<td><b>' + s.规格 + '</b><br><small>' + s.头型 + '</small></td>' +
'<td>' + s.长度mm + '</td><td>' + s.数量 + '</td>' +
'<td>' + s.位置 + '<br><small>' + s.备注 + '</small></td>';
skb.appendChild(tr);
}
const tally = Object.keys(bySize).sort().map(k => k + 'x' + bySize[k]).join(' / ');
document.getElementById('scrSum').textContent =
'共 ' + SCREWS.length + ' 种 ' + nTot + ' 颗(' + tally + ');另需 M4 螺母 4、M5 螺母 4、M6 螺母+平垫圈各 6,见各行备注';
document.getElementById('bomStat').textContent =
'紧固件 ' + nTot + ' 颗 / 打印件 ' + DATA.parts.filter(p=>p.printed).length + ' 种';
const dock = document.getElementById('bomDock'), bTab = document.getElementById('bomTab');
function dockSet(open) {
dock.classList.toggle('open', open);
bTab.style.display = open ? 'none' : ''; // 展开时拉手藏掉,否则压住抽屉右缘
bTab.innerHTML = open ? '收起 <b>清单</b>' : '物料清单 <b>BOM</b>';
}
bTab.onclick = () => dockSet(!dock.classList.contains('open'));
document.getElementById('bomHide').onclick = () => dockSet(false);
document.addEventListener('keydown', e => { if (e.key === 'Escape') dockSet(false); });
/* ---- toolbar ---- */
const bAsm = document.getElementById('bAsm');
const bExp = document.getElementById('bExp');
const bWire = document.getElementById('bWire');
const bXRay = document.getElementById('bXRay');
const bL = document.getElementById('bL');
const bR = document.getElementById('bR');
const sheet = document.getElementById('sheet');
const stage = document.getElementById('stage');
const on = (id, f) => document.getElementById(id).onclick = f;
@@ -532,27 +569,29 @@ on('bWire', () => { bWire.classList.toggle('on'); const w = bWire.classList.cont
meshes.forEach(m => { m.material.wireframe = w; }); requestRender(); });
on('bXRay', () => { bXRay.classList.toggle('on'); const o = bXRay.classList.contains('on');
meshes.forEach(m => { if (!m.userData.printed && m.userData.name[0]==='M') { m.material.opacity = o ? .34 : 1; m.material.transparent = o; } }); requestRender(); });
on('bFit', () => { fitTo(BB); az = -0.9; po = 1.15; requestRender(); });
on('bFit', () => { fitTo(BB); az = -0.9; po = 1.15; camUp.copy(UP_Y); requestRender(); });
const app = document.getElementById('app');
on('bL', () => { app.classList.toggle('l'); bL.classList.toggle('on'); });
on('bR', () => { app.classList.toggle('r'); bR.classList.toggle('on'); });
bL.classList.add('on'); bR.classList.add('on');
bL.classList.add('on');
document.getElementById('cam').onchange = e => {
const k = e.target.value;
camUp.copy(UP_Y); // upright roll for every view unless pinned below
if (k==='iso'){az=-0.9;po=1.15;} if(k==='front'){az=0;po=1.57;}
if(k==='side'){az=1.5708;po=1.57;}
// looking down the arm, the height dimension is irrelevant - frame by the PLAN extent
if(k==='top'){az=-0.9;po=0.12;rd=Math.max(BB[1][0]-BB[0][0],BB[1][2]-BB[0][2])*1.55+90;}
// true plan view: pin the roll (near-vertical lookAt otherwise rolls the image) and
// frame by the plan extent - the height dimension is irrelevant looking down
if(k==='top'){az=-0.9;po=0.02;camUp.copy(UP_TOP);
rd=Math.max(BB[1][0]-BB[0][0],BB[1][2]-BB[0][2])*1.2+180;}
const hub = id => { const j = JO.find(q => q.id === id); return j ? VC(j.hub) : [tx, ty, tz]; };
if (k === 'wrist') { const a = hub('J5'), b = hub('J6'); // frame the whole hand,
tx = (a[0]+b[0])/2; ty = (a[1]+b[1])/2; tz = (a[2]+b[2])/2; rd = 340; } // not just its hub
tx = (a[0]+b[0])/2; ty = (a[1]+b[1])/2; tz = (a[2]+b[2])/2; rd = 340; az = -0.6; po = 1.25; }
// shoulder close-up has to hold BOTH the roll drum (J1) and the pitch fork (J2) - aiming at
// one hub alone filled the frame with a single Ø88 cylinder and showed nothing
if (k === 'joint') { const a = hub('J1'), h = hub('J2');
tx = (a[0]+h[0])/2; ty = (a[1]+h[1])/2; tz = (a[2]+h[2])/2; rd = 470; }
tx = (a[0]+h[0])/2; ty = (a[1]+h[1])/2; tz = (a[2]+h[2])/2; rd = 470; az = -0.6; po = 1.25; }
if (k === 'elbow') { const h = hub('J4');
tx = h[0]; ty = h[1] - 35; tz = h[2]; rd = 280; az = -0.6; po = 1.25; }
if (k === 'hand') { const h = hub('J6'); tx = h[0]; ty = h[1]; tz = h[2] - 45; rd = 215; }
if (k === 'hand') { const h = hub('J6'); tx = h[0]; ty = h[1]; tz = h[2] - 45; rd = 215; az = -0.6; po = 1.25; }
requestRender();
};
document.querySelectorAll('.tab[data-view]').forEach(b => b.onclick = () => {
+3 -3
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

File diff suppressed because one or more lines are too long