feat: P07小臂骨架顶板整条加高19mm补强+前后外缘R3圆角,gusset弧R28->R14随顶板联动相切融入;重算全关节行程J4反向-121->-111,同步重建模型数据与查看器
@@ -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_腕偏转座",
|
||||
|
||||
@@ -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(建议加弹垫防松)"
|
||||
}
|
||||
]
|
||||
|
Before Width: | Height: | Size: 189 KiB After Width: | Height: | Size: 114 KiB |
@@ -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",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 对干涉(全部零件布尔交集体积 < 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 = () => {
|
||||
|
||||
|
After Width: | Height: | Size: 127 KiB |
|
After Width: | Height: | Size: 76 KiB |
|
After Width: | Height: | Size: 78 KiB |
|
After Width: | Height: | Size: 146 KiB |
|
After Width: | Height: | Size: 157 KiB |
|
After Width: | Height: | Size: 173 KiB |
|
After Width: | Height: | Size: 154 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 95 KiB |
|
After Width: | Height: | Size: 156 KiB |
|
After Width: | Height: | Size: 196 KiB |
|
After Width: | Height: | Size: 147 KiB |
|
After Width: | Height: | Size: 86 KiB |
|
After Width: | Height: | Size: 194 KiB |
|
After Width: | Height: | Size: 99 KiB |
|
After Width: | Height: | Size: 94 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 172 KiB |
|
After Width: | Height: | Size: 130 KiB |