diff --git a/.bak/P01_A_手作参考原件_20260928.3mf b/.bak/P01_A_手作参考原件_20260928.3mf deleted file mode 100644 index 4843d3e..0000000 Binary files a/.bak/P01_A_手作参考原件_20260928.3mf and /dev/null differ diff --git a/.bak/arm_build.py.bak20261005 b/.bak/arm_build.py.bak20261005 deleted file mode 100644 index 0eb1511..0000000 --- a/.bak/arm_build.py.bak20261005 +++ /dev/null @@ -1,609 +0,0 @@ -# -*- coding: utf-8 -*- -"""Shared geometry / assembly / export library for the robot arm build. - -No layout of its own: the joint frames and station positions live in -human_layout.py and arm_model.py, which import this module. What lives here is -the measured motor interface (docs/interfaces.md), the Joint class (motor slip -bore + PCBA pocket + pinch screws; driven hub mating plane at motor rear datum -+65.0 replicating the Ø36x1.0 neck / Ø6.5x4.0 centre / 3x M2 on Ø29 output so -the existing face-spline halves + split clamp bolt straight on), the part -registry, and the STEP/STL/mesh/BOM/collision export pipeline. - -Run: python arm_model.py -> out/, web/mesh_arm.json, docs/ -""" -import math -import os -import sys -import json - -try: - sys.stdout.reconfigure(encoding="utf-8") -except Exception: - pass - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -from armcore import (M, C, V, trs, place, bb, bbox_str, cyl, ring, box, holes, - solids_of, mesh_of, MOTOR_TOL, OUT, WEB, DOCS, SRC_STEP, HALF_STEP, CLAMP_STEP) -from OCP.gp import gp_Trsf, gp_Vec, gp_Pnt, gp_Dir, gp_Ax1 -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from OCP.GProp import GProp_GProps -from OCP.BRepGProp import BRepGProp -from OCP.STEPControl import STEPControl_Writer, STEPControl_Controller, STEPControl_StepModelType -from OCP.Interface import Interface_Static - -STEPControl_Controller.Init_s() -D2R = math.pi / 180.0 -VERSION = os.environ.get("ARMVER", "r1") -ITER = int(os.environ.get("ARMITER", "1")) - -# ---------------------------------------------------------------- measured stations -FLANGE_Z = M["flange_z"] # 54.942 -COVER_Z = M["cover_z"] # 56.000 -HUB_FACE_Z = 65.0 # motor rear datum -> driven hub mating plane -CLAMP_SPAN = (FLANGE_Z + 1.66, FLANGE_Z + 9.46) -CLAMP_R_BAND = 31.0 -CLAMP_R_LUG = 38.0 -BORE_R = M["hous_d"] / 2.0 + 0.15 # 30.15 slip fit on the housing -CB_R = M["cover_d"] / 2.0 + 0.35 # 30.50 clearance for the cover -PCB_W = M["pcba_w"] + 0.8 # 64.40 square pocket -HUB_M2_CLOCK = [(360.0 - a) % 360.0 for a in M["m2_clock"]] # phase of the mirrored half -HUB_PILOT_D = M["neck_d"] - 0.4 # 35.6 -> the half's pilot lands in a Ø36 recess -PINCH_D = 2.6 # M3 clearance in printed material -# 承压面让位(完整背景见 Joint.pocket 的 docstring): -# REAR_LAND = 0.6 腔口直壁台肩高度,**保持原设计值不变**(让位不靠加高台肩) -# REAR_SEAT = 2.6 尾端面->承压面 = 0.6 原台肩 + 2.0 公差垫片位 -# SEAT_R = 32.2 承压面段(z=-REAR_SEAT..0)的腔口半径 = PCB_W/2 = 64.4 方腔的 -# **内切圆半径**。承压面下沉 2.6mm 后这一段必须让空,但绝不能连着 -# 外壁 R35 一起挖掉(2026-10-04 教训,见 Joint.pocket docstring): -# 用 R35 圆腔会把 r=32.2..35 的连接壁整圈切断,P04 后端只剩 -# 240~320° 方位还连着(实测),打印出来就是一道 3.6mm 宽的通槽 + -# 一片 0.5mm 厚的孤立薄片(用户截图:肩叉顶部"没连上")。 -# 取 32.2 只需挖到方腔内切圆,r=32.2..35 全圈保留 -> -# 壁厚均匀的 2.8mm 环形连接壁(0/90/180/270 方向与上方方腔壁 -# 齐平 seamlessly,45° 方向比方腔多留料,正好补回四角)。 -# SEAT_TOP = 0.0 圆腔顶端 = 方腔起点,两者在此平接;再往上就是方腔的地盘。 -# R35_SEAT = 35.0 后端板 R35 外壁(承压面外径,不可挖穿) -REAR_LAND = 0.6 -REAR_SEAT = REAR_LAND + MOTOR_TOL # 2.6 尾端面->后端板承压面:公差垫片位 -R35_SEAT = 35.0 # 后端板 R35 外壁(承压面外径) -SEAT_R = PCB_W / 2.0 # 32.2 承压面段腔口 = 方腔内切圆,保留外壁 -SEAT_TOP = 0.0 # 圆腔顶端与方腔起点平接 - -PARTS = [] -CENTS = {} - - -def add(name, cn, shape, color, printed=True, note="", qty=1): - assert shape is not None, name - lo, hi = bb(shape) - PARTS.append(dict(name=name, cn=cn, shape=shape, color=color, printed=printed, - note=note, qty=qty, bb=bbox_str(shape), vol=vol_of(shape))) - CENTS[name] = [(lo[i] + hi[i]) / 2.0 for i in range(3)] - - -def vol_of(sh): - p = GProp_GProps() - BRepGProp.VolumeProperties_s(sh.wrapped, p) - return p.Mass() - - -def common_vol(a, b): - try: - c = BRepAlgoAPI_Common(a.wrapped, b.wrapped) - c.Build() - if not c.IsDone() or c.Shape().IsNull(): - return 0.0 - return vol_of(cq.Shape(c.Shape())) - except Exception: - return -1.0 - - -def T(dx=0.0, dy=0.0, dz=0.0): - t = gp_Trsf() - t.SetTranslation(gp_Vec(dx, dy, dz)) - return t - - -def R(deg, ax, ay, az, px=0.0, py=0.0, pz=0.0): - t = gp_Trsf() - t.SetRotation(gp_Ax1(gp_Pnt(px, py, pz), gp_Dir(ax, ay, az)), deg * D2R) - return t - - -RX90 = R(90, 1, 0, 0) - - -def Ycyl(r, y0, y1, px=0.0, pz=0.0): - return place(cyl(r, -y1, -y0, px, pz), RX90) - - -def Ybox(x0, x1, y0, y1, z0, z1): - return place(box(x0, x1, z0, z1, -y1, -y0), RX90) - - -# ------------------------------------------------------- pitch-joint pivot cartridge ---- -# ONE recipe for every pitch joint (J2 shoulder, J3 elbow, J5 wrist) - they are the same -# degree of freedom, so an implementation that fixes only one of them leaves the identical -# defect in the other two. See human_layout PV_* for why a cartridge is needed at all: -# measured P04 n P05 = 0.00 mm3, so J2's 38 N*m had no path into the housing except peeling -# the face-spline teeth off across the print layers. -def pivot_geom(y_face, side, h, w, gap): - """(boss_y0, boss_y1, pocket_y0) for a cartridge against the link face at y_face. - The race pocket always opens AWAY from the link, leaving an (h-w) shoulder behind the - outer race; a printed cap ring closes the open end. The boss stops `gap` short of the - link face, or a printed face would carry the thrust and the rolling elements nothing.""" - near = y_face - gap if side < 0 else y_face + gap - if side < 0: - return near - h, near, near - h - return near, near + h, near + h - w - - -def pivot_boss(shape, pz, y0, y1, pk0, w, px, boss_od, race_od, bore, orace_id, side): - """Housing side: boss on the joint axis with the outer race sunk into it. The passage - beyond the pocket is reamed to the outer race bore, not to the shaft: a shaft-sized bore - would end inside the inner race and bind it, and a bound bearing carries nothing - it - just moves the failure back into the print layers.""" - shape = shape.fuse(Ycyl(boss_od / 2.0, y0, y1, px, pz)) - shape = shape.cut(Ycyl(race_od / 2.0, pk0 - .01, pk0 + w + .01, px, pz)) - if side < 0: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, pk0 + w, y1 + .01, px, pz)) - else: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, y0 - .01, pk0, px, pz)) - shape = shape.cut(Ycyl(bore / 2.0 + 0.2, y0 - .01, y1 + .01, px, pz)) - for a in (90.0, 210.0, 330.0): # 3x M3 cap screws, outside the outer race - u, v = 26.5 * math.cos(a * math.pi / 180.0), 26.5 * math.sin(a * math.pi / 180.0) - shape = shape.cut(Ycyl(1.25, y0 - .01, y0 + 12.0, px + u, pz + v)) - return shape - - -def pivot_journal(shape, pz, y_link, y_tip, y_collar0, y_collar1, px, bore, collar): - """Link side: the stub the inner race rides on, poking past the boss so the cap can bite - it, plus an integral collar the inner race bears against. The collar replaces a separate - spacer sleeve and lives in the boss's outer-race passage, so it must stay under that - diameter - the 0.1 mm it leaves is what lets the race, not the collar, take the thrust.""" - a, b = sorted((y_link, y_tip)) - shape = shape.fuse(Ycyl(bore / 2.0, a, b, px, pz)) - c0, c1 = sorted((y_collar0, y_collar1)) - return shape.fuse(Ycyl(collar / 2.0, c0, c1, px, pz)) - - -def prism(pts2, z0, z1): - w2 = [(p[0], p[1]) for p in pts2] - return (cq.Workplane("XY", origin=(0, 0, z0)) - .polyline(w2 + [w2[0]]).close().extrude(z1 - z0, combine=False)).val() - - -def Yplate(pts_xz, y0, y1): - return place(prism(pts_xz, -y1, -y0), RX90) - - -def dogbone(a, b, ra, rb, n=20): - """Tangent hull of two discs. The outer-tangent offset angle is acos((ra-rb)/L); - using asin made the ra==rb case sweep a full circle around b and self-intersect.""" - dx, dy = b[0] - a[0], b[1] - a[1] - L = math.hypot(dx, dy) or 1.0 - th = math.atan2(dy, dx) - beta = math.acos(max(-1.0, min(1.0, (ra - rb) / L))) - sw_a = 2 * math.pi - 2 * beta - sw_b = 2 * beta - pts = [(a[0] + ra * math.cos(th + beta + sw_a * k / n), a[1] + ra * math.sin(th + beta + sw_a * k / n)) - for k in range(n + 1)] - pts += [(b[0] + rb * math.cos(th - beta + sw_b * k / n), b[1] + rb * math.sin(th - beta + sw_b * k / n)) - for k in range(1, n + 1)] - return pts - - -def sector(r_out, r_in, a0, a1, z0, z1, n=14): - """Annular sector between two radii and two angles (deg) - a joint shroud segment. - Must NOT include the axis point, or this becomes a solid pie wedge that fills the joint.""" - pts = [(r_out * math.cos((a0 + (a1 - a0) * k / n) * D2R), - r_out * math.sin((a0 + (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - pts += [(r_in * math.cos((a1 - (a1 - a0) * k / n) * D2R), - r_in * math.sin((a1 - (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - return prism(pts, z0, z1) - - -# ---------------------------------------------------------------- joint frames & interfaces -class Joint: - """One revolute joint. `rear` = motor rear datum point, `d` = axis unit dir (motor -> hub).""" - - def __init__(self, jid, cn, rear, d, xdir, lug_phase=0.0): - self.jid, self.cn = jid, cn - self.rear = rear - self.d = d - self.xdir = xdir - self.t = trs(rear, xdir, d) # motor-local -> global - self.lug_phase = lug_phase # rotation of the clamp about the axis - - def L(self, shape_local): - return place(shape_local, self.t) - - def at(self, s): - return (self.rear[0] + self.d[0] * s, self.rear[1] + self.d[1] * s, self.rear[2] + self.d[2] * s) - - def pocket(self, shape, front_pad=0.6): - """Cut the motor pocket out of a proximal link body. - - REAR_SEAT = 电机尾端面(局部 z=0)到后端板承压面的距离。它不是定位面而是 - **垫片位**:公差 MOTOR_TOL=2.0(armcore)要靠它消化 —— 电机比标称短的那一份, - 就垫在尾端面与这个承压面之间(见 armcore.MOTOR_TOL 的说明)。原值 0.6 只能吃 - 0.6mm,短 1mm 以上的电机装不进、长 0.6mm 以上的电机会顶死后端板而压溃。 - 注意它同时是承压面的高度,所以让空 2.0mm 要求 t_plate >= 8 - (见各零件 rear_mount_holes 的 t_plate)。 - - 【2026-10-04 整改:承压面段改用 R35 圆腔,凸台彻底消失】 - 上一版把 64.4 **方**腔整体从 -0.6 加深到 -2.6(纯掏腔),方腔壁以外 - z=-2.6..0 之间那圈 R35 料一动没动,于是相对新承压面整整凸起 2.6mm —— - 这就是 P04~P08 后端那圈"悬空凸台"(用户截图红圈,3D 打印时该处是悬空 - 薄壁,既要支撑又公差难保证)。 - - 为什么之前试"把凸台切掉"总失败(几何实测,别再重复): - - 64.4 方腔的**内切圆半径恰好 = 32.2**,所以方腔在 45° 方向已经吃穿到 - r=45.5,R35 壁只剩**四条边中点附近**的料。实测 r=34 处材料沿 z 的 - 区间(承压面 -2.6):P06 的 0°/180° 是 -8.0..12.0(贯通,是臂身本体), - 80°/100° 是 -8.0..-0.0(**只比承压面多 2.6mm,这段就是凸台**), - 其余 20~160° 是 -8.0..-2.6(承压环本体,齐平)。 - - 因此整段全截面切会把 0/90 方向那条**贯通到 z=12 的承力桥**一起切断 - -> P05/P08 碎成 2 块(实测)。这不是"承压环是唯一连接", - 而是我的刀具没区分"桥"和"悬空料"。 - - 现在的做法(一刀,不做角度分区): - 承压面所在的 z=-REAR_SEAT..0 段,腔口取 **PCB_W/2 = 32.2 的圆** - (= 64.4 方腔的内切圆半径,SEAT_R),而不是方腔、也不是 R35: - - 用方腔(64.4):方腔在 45° 方向已吃穿到 r=45.5,R35 壁只剩四条边中点 - 附近的料;实测那几段 r=34 处材料是 -8.0..-0.0,比承压面(-2.6)多出 2.6mm, - 就是用户截图里那圈"悬空凸台"(打印要支撑、公差差)。 - - 用 R35 圆腔(上一版,2026-10-04,**错**):把 r=32.2..35 的连接壁整圈切断。 - 实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 260° 全部掏空 -> - 后端法兰与杯壁之间出现一道 3.6mm 宽的**通槽**,还在 225° 留下一片 - 0.5mm 厚的孤立薄片(用户截图:肩叉顶部"没连上")。且承压面整个悬空。 - - 用 32.2 圆腔(当前):承压面段整圈挖到 -2.6,凸台消失;同时 r=32.2..35 - 这圈 2.8mm 厚的壁完整保留,把后端板与上方臂身连成一体。0/90/180/270 - 方向该壁与方腔壁齐平,45° 方向比方腔多留料把四角补实。 - PCBA 方腔本体仍从 z=0 起(z<0 那段由 32.2 圆腔负责,两者在此平接)。 - """ - half = PCB_W / 2.0 - c = [cyl(BORE_R, 5.5, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + front_pad), - # 承压面段:SEAT_R(=32.2 方腔内切圆) 圆腔,从承压面挖到 z=SEAT_TOP(=0) - # 与方腔平接。挖到 32.2 而不是 35 —— 35 会把外壁整圈切断(见 docstring) - cyl(SEAT_R, -REAR_SEAT, SEAT_TOP), - # PCBA 方腔本体:从 z=0 起(承压面段那 2.6mm 由上面的圆腔负责) - box(-half, half, -half, half, 0.0, M["pcba_t"] + 0.4)] - return shape.cut(*[self.L(s) for s in c]) - - def rear_mount_holes(self, shape, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0): - """Cut the 4x rear mounting bolt holes into a proximal link body. - Matches the 4x M3 tapped radial slots on the motor rear endplate - (电机.STEP 实测):BCD55、相位 39.9°+90k,槽宽 2.5、径向可调约 ±1.25mm。 - 旧版误用 35.1x40.9 矩形孔距:对角两颗偏出螺纹槽 ~4.7mm 无处咬合 - (2026-10-02 用户指出"螺钉没有对齐",按 STEP 实测槽位修正)。 - """ - cuts = [] - for k in range(4): - a = math.radians(39.9 + 90.0 * k) - sx = 27.5 * math.cos(a) - sy = 27.5 * math.sin(a) - h = cyl(hole_d / 2.0, -t_plate - 1.0, 3.0, sx, sy) - cuts.append(self.L(h)) - if cb_d > 0 and cb_depth > 0: - cb = cyl(cb_d / 2.0, -t_plate - 1.0, -t_plate + cb_depth, sx, sy) - cuts.append(self.L(cb)) - return shape.cut(*cuts) - - def front_collar_pocket(self, shape, r_bore=30.25, z0=50.0, z1=56.5): - """Cut front locating collar pocket for the motor front rim.""" - p = cyl(r_bore, z0, z1) - return shape.cut(self.L(p)) - - def pinch(self, shape, fracs=(0.26, 0.58, 0.86), r_out=48.0, n=2): - """Radial M3 pinch screws through the boss wall onto the housing.""" - cuts = [] - for fr in fracs[:n]: - for a in (self.lug_phase, self.lug_phase + 180.0): - c = cyl(PINCH_D / 2.0, -1.0, r_out, 0, 0) - c = place(c, R(a, 0, 0, 1)) - c = place(c, T(0, 0, fr * COVER_Z)) - cuts.append(self.L(c)) - return shape.cut(*cuts) - - def hub_face(self): - return self.at(HUB_FACE_Z) - - def cut_hub(self, shape, depth=24.0, r_clear=32.0): - """Cut the motor-output replica into the driven hub. The mating plane sits at - motor-local z = HUB_FACE_Z, and the recesses open there and run into the driven - material (away from the motor), so that half B's pilot (0.60) and spigot (4.06) - land in them.""" - H = HUB_FACE_Z - # measured on the delivered half B: past the mating plane it is Ø35.6 for 1.4 mm - # then a Ø6.3 spigot to 4.06 - the nominal neck_depth of 1.0 is 0.4 too shallow, - # which showed up as a ~160 mm3 interference at every driven hub - cuts = [cyl(M["neck_d"] / 2.0, H - 0.05, H + 1.70), - cyl(M["centre_d"] / 2.0, H - 0.05, H + 5.05), - # probed on the delivered half: its Ø54 back face sits ~0.06 mm PAST the - # nominal plane, so the driven face gets a 0.35 deep, Ø54.6 relief. That is - # also the printed-part tolerance land the joint wants. - cyl(27.30, H - 0.05, H + 0.35)] - for a in HUB_M2_CLOCK: - u = M["m2_bcd"] / 2.0 * math.cos(a * D2R) - v = M["m2_bcd"] / 2.0 * math.sin(a * D2R) - cuts.append(cyl(M["m2_tap_d"] / 2.0, H - 0.05, H + M["m2_depth"], u, v)) - return shape.cut(*[self.L(s) for s in cuts]) - - def hub_island(self, shape, r=30.0, t=9.5): - """Solid disc at the joint centre (the lightening dish must not break into the - interface recesses), then the output replica is cut into it. Starts exactly on the - mating plane: half B's Ø54 body ends there, so any overlap is a hard clash.""" - shape = shape.fuse(self.L(cyl(r, HUB_FACE_Z, HUB_FACE_Z + t))) - shape = self.cut_hub(shape) - # two arc shrouds over the clamp band: the joint reads as a closed drum, and the - # sectors are placed so the M3 lug bolts on the joint's local +-X stay reachable - for (a0, a1) in ((34.0, 146.0), (214.0, 326.0)): - shape = shape.fuse(self.L(sector(38.0, 31.6, a0, a1, FLANGE_Z + 1.4, HUB_FACE_Z))) - return shape - - def place_hardware(self, half, clamp, tag): - """Place the existing coupling halves + split clamp for this joint. - - The delivered half is authored with z=0 at the motor flange face and spans - z -3.00..8.06. B = flip 180 deg about the local X axis, then shift by - 2*PART_Z, which puts its flat back face exactly on the hub mating plane - (motor-local 65.00) - verified against the measured feature bounds. - """ - tf = trs(self.at(FLANGE_Z), self.xdir, self.d) - hA = place(half, tf) - hb = place(half, R(180, 1, 0, 0)) - hb = place(hb, T(0, 0, 2 * C["part_z"])) - hB = place(hb, tf) - cA = place(clamp, tf) - cB = place(place(clamp, R(180, 0, 0, 1)), tf) # the two C pieces are one ring split - # along a diametral plane: 180 deg about the JOINT axis, so both keep the same - # axial span 56.602..64.402 and sit on opposite sides of the band. - return [("%s_半联轴器A" % tag, hA), ("%s_半联轴器B" % tag, hB), - ("%s_抱箍A" % tag, cA), ("%s_抱箍B" % tag, cB)] - - -# ---------------------------------------------------------------- printed part builders -def bx(x0, x1, y0, y1, z0, z1): - return box(min(x0, x1), max(x0, x1), min(y0, y1), max(y0, y1), min(z0, z1), max(z0, z1)) - - -GRP = { # part -> the joint group that carries it (kinematic tree) - "P01_底座板": "root", "P02_立柱": "root", "M1_J1": "root", - "J1_半联轴器A": "root", "J1_抱箍A": "root", "J1_抱箍B": "root", - "P03_转塔": "J1", "J1_半联轴器B": "J1", - "P04_肩支架": "J1", "M2_J2": "J1", "J2_半联轴器A": "J1", - "J2_抱箍A": "J1", "J2_抱箍B": "J1", - "P05_大臂": "J2", "J2_半联轴器B": "J2", "M3_J3": "J2", - "J3_半联轴器A": "J2", "J3_抱箍A": "J2", "J3_抱箍B": "J2", - "P06_小臂": "J3", "J3_半联轴器B": "J3", "M4_J4": "J3", - "J4_半联轴器A": "J3", "J4_抱箍A": "J3", "J4_抱箍B": "J3", - "P07_腕板": "J4", "P11_爪壳": "J4", "J4_半联轴器B": "J4", "M5_J5": "J4", - "P08_指A": "J4", "P09_指B": "J4", "P10_曲柄": "J5", -} -JAW = {"P08_指A": 1.0, "P09_指B": -1.0} # sign of the jaw travel along the J5 local +X - - -def explode_vec(p, JS, cents, gmean): - """Explosion offset: coupling hardware separates along its joint axis, everything - else moves radially out of the assembly centroid.""" - nm = p["name"] - c = cents[nm] - for jid in ("J1", "J2", "J3", "J4"): - if nm.startswith(jid + "_半联轴器B"): - return [JS[jid].d[i] * 24.0 for i in range(3)] - if nm.startswith(jid + "_半联轴器A") or nm.startswith(jid + "_抱箍"): - k = -26.0 if "抱箍" in nm else -16.0 - return [JS[jid].d[i] * k for i in range(3)] - v = [c[i] - gmean[i] for i in range(3)] - m = math.sqrt(sum(x * x for x in v)) or 1.0 - return [v[i] / m * 34.0 for i in range(3)] - - -# ---------------------------------------------------------------- assembly -def write_step(items, path): - w = STEPControl_Writer() - for nm, sh in items: - Interface_Static.SetCVal_s('write.step.product.name', nm) - w.Transfer(sh.wrapped, STEPControl_StepModelType.STEPControl_AsIs) - w.Write(path.replace("/", "\\")) - return os.path.getsize(path) - - -import threading -import time -from concurrent.futures import ThreadPoolExecutor - -def export(JS): - for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS): - os.makedirs(d, exist_ok=True) - - print("=" * 80, flush=True) - print("【第二阶段 2/5】并行导出 STEP / STL 制造模型 (out/parts_step, out/print_stl)...", flush=True) - print("=" * 80, flush=True) - - # 1. 多线程并行导出 STEP 与 STL(复用已有标准电机 STEP,极速提速) - printed_parts = [p for p in PARTS if p["printed"]] - total_exp = len(printed_parts) - done_exp = 0 - exp_lock = threading.Lock() - - def export_one_part(p): - nonlocal done_exp - t0 = time.time() - fn = "%s/%s.step" % (OUT + "/parts_step", p["name"]) - # 如果是非打印标准已有件且已存在,跳过冗余写盘 - if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000: - pass - else: - write_step([(p["name"], p["shape"])], fn) - if p["printed"]: - stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"]) - cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL") - with exp_lock: - done_exp += 1 - dt = time.time() - t0 - print(f" [文件导出 {done_exp}/{total_exp}] {p['name']}.step / .stl 导出完成 ({dt:.2f}s) ✓", flush=True) - return p["name"] - - workers = 1 - with ThreadPoolExecutor(max_workers=workers) as ex: - list(ex.map(export_one_part, PARTS)) - - print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True) - - # 2. 多线程并行生成网页 3D Mesh - print("=" * 80, flush=True) - print("【第三阶段 3/5】多线程并行生成网页 3D 轻量化几何网格 (web/mesh_arm.json)...", flush=True) - print("=" * 80, flush=True) - - done_mesh = 0 - mesh_lock = threading.Lock() - total_parts = len(PARTS) - - def mesh_one_part(p): - nonlocal done_mesh - t0 = time.time() - key = "motor" if p["name"].startswith("M") else ("coupl" if "联轴器" in p["name"] or "抱箍" in p["name"] else p["name"]) - defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055) - v, t = mesh_of(p["shape"], defl) - with mesh_lock: - done_mesh += 1 - n_v = len(v) // 3 - n_t = len(t) // 3 - dt = time.time() - t0 - print(f" [网格细分 {done_mesh:2d}/{total_parts}] {p['name']:<14} 顶点: {n_v:5d} | 面片: {n_t:5d} ({dt:.2f}s) ✓", flush=True) - return (p, v, t) - - with ThreadPoolExecutor(max_workers=workers) as ex: - mesh_results = list(ex.map(mesh_one_part, PARTS)) - - parts = [] - for idx, (p, v, t) in enumerate(mesh_results): - parts.append(dict(name=p["name"], cn=p["cn"], v=v, t=t, color=p["color"], - printed=p["printed"], vol=round(p["vol"], 0), note=p["note"], - grp=GRP.get(p["name"], "root"), jaw=JAW.get(p["name"], 0.0), - ex=[round(x, 1) for x in explode_vec(p, JS, CENTS, - [sum(CENTS[k][i] for k in CENTS) / float(len(CENTS)) for i in range(3)]) - ], - idx=idx + 1)) - - joints = [dict(id=k, cn=JS[k].cn, rear=JS[k].rear, d=JS[k].d, xdir=JS[k].xdir, hub=JS[k].hub_face()) - for k in JS] - d3 = lambda a, b: math.sqrt(sum((a[i] - b[i]) ** 2 for i in range(3))) - json.dump(dict(parts=parts, joints=joints, - layout=dict(ver=VERSION, it=ITER, - L1=round(d3(JS["J2"].rear, JS["J4"].rear), 1), - L2=round(d3(JS["J4"].rear, JS["J5"].rear), 1)), - measured=dict(M=M, C=C, HUB_FACE_Z=HUB_FACE_Z, CLAMP_SPAN=list(CLAMP_SPAN))), - open(WEB + "/mesh_arm.json", "w", encoding="utf-8"), ensure_ascii=False) - bom = [dict(件号=p["name"], 名称=p["cn"], 类型="3D打印" if p["printed"] else "已有/电机", - 数量=1, 体积cm3=round(p["vol"] / 1000.0, 1), - 质量g_PLA=round(p["vol"] * 1.24e-3, 1), 包络=p["bb"], 说明=p["note"]) for p in PARTS] - json.dump(bom, open(DOCS + "/bom.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1) - print(f"-> 3D网格与BOM清单已保存至 web/mesh_arm.json 与 docs/bom.json ✓\n", flush=True) - return bom - - -def collide(): - ps = [p for p in PARTS if p["vol"] > 1.0] - pairs = [] - MOUNT_PAIRS = { - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "M1_J1"), ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M1_J1"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M2_J2"), ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M2_J2"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M3_J3"), ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M3_J3"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M4_J4"), ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M4_J4"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M5_J5"), ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M5_J5"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M6_J6"), ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "M6_J6"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P01_B_立柱中段_三角形30%_墙4底5_竖打"), ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "P01_C_立柱上段_三角形30%_墙4底5_竖打"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P02_Center_底座中核心_螺旋体30%_墙5底6_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "P03_肩吊臂_螺旋体35%_墙5底5_竖打"), - } - for i in range(len(ps)): - for j in range(i + 1, len(ps)): - a, b = ps[i], ps[j] - if a["name"].startswith("M") and b["name"].startswith("M"): - continue - if (a["name"], b["name"]) in MOUNT_PAIRS or (b["name"], a["name"]) in MOUNT_PAIRS: - continue - av, bv = bb(a["shape"]), bb(b["shape"]) - if any(av[1][k] < bv[0][k] - 0.05 or bv[1][k] < av[0][k] + 0.05 for k in range(3)): - continue - pairs.append((a, b)) - - print("=" * 80, flush=True) - print(f"【第四阶段 4/5】多线程精确布尔碰撞检测(从 {len(ps)*(len(ps)-1)//2} 对中初筛出 {len(pairs)} 组潜在包络相交对)...", flush=True) - print("=" * 80, flush=True) - - done_pair = 0 - pair_lock = threading.Lock() - - def check_pair(pair): - nonlocal done_pair - a, b = pair - t0 = time.time() - v = common_vol(a["shape"], b["shape"]) - with pair_lock: - done_pair += 1 - status = f"干涉 {v:.1f} mm³ ❌" if v > 2.0 else "零干涉 (0.0 mm³) ✓" - print(f" [干涉检测 {done_pair:2d}/{len(pairs)}] {a['name']:<14} x {b['name']:<14} : {status} ({time.time()-t0:.3f}s)", flush=True) - if v > 2.0: - try: - c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped) - c.SetRunParallel(True) - c.Build() - cb = cq.Shape(c.Shape()) - lo, hi = bb(cb) - loc = "@[%.0f %.0f %.0f..%.0f %.0f %.0f]" % ( - lo[0], lo[1], lo[2], hi[0], hi[1], hi[2]) - except Exception: - loc = "?" - return (a["name"], b["name"], round(v, 1), loc) - return None - - rows = [] - for pair in pairs: - res = check_pair(pair) - if res is not None: - rows.append(res) - - rows.sort(key=lambda r: -r[2]) - json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"), - ensure_ascii=False) - if len(rows) == 0: - print(f"\n-> 静态碰撞干涉检测通过: 0 对干涉!全部 {len(pairs)} 组接触面对零碰撞!\n", flush=True) - else: - print(f"\n-> 发现 {len(rows)} 对干涉!\n", flush=True) - return rows - - -def axial_extent(j, shape): - lo, hi = bb(shape) - vals = [] - for i in (0, 1): - for k in range(3): - p = [lo[0], lo[1], lo[2]] - p[k] = hi[k] if i else lo[k] - vals.append(sum((p[q] - j.rear[q]) * j.d[q] for q in range(3))) - return min(vals), max(vals) - - -def verify_interfaces(half, clamp, JS): - """Numeric self-check of the derived joint stack, before trusting the renders.""" - rows = [] - for k, j in JS.items(): - if k == "J5": - continue - parts = dict(j.place_hardware(half, clamp, k)) - a = axial_extent(j, parts["%s_半联轴器A" % k]) - b = axial_extent(j, parts["%s_半联轴器B" % k]) - c = axial_extent(j, parts["%s_抱箍A" % k]) - hub = j.at(HUB_FACE_Z) - rows.append(dict(jid=k, halfA=[round(x, 3) for x in a], halfB=[round(x, 3) for x in b], - clamp=[round(x, 3) for x in c], hub_face=round(HUB_FACE_Z, 3), - spigot_proud=round(b[1] - HUB_FACE_Z, 3), - clamp_clear=round(c[0] - COVER_Z, 3))) - return rows diff --git a/.bak/arm_build.py.bak20261005c b/.bak/arm_build.py.bak20261005c deleted file mode 100644 index 785452a..0000000 --- a/.bak/arm_build.py.bak20261005c +++ /dev/null @@ -1,627 +0,0 @@ -# -*- coding: utf-8 -*- -"""Shared geometry / assembly / export library for the robot arm build. - -No layout of its own: the joint frames and station positions live in -human_layout.py and arm_model.py, which import this module. What lives here is -the measured motor interface (docs/interfaces.md), the Joint class (motor slip -bore + PCBA pocket + pinch screws; driven hub mating plane at motor rear datum -+65.0 replicating the Ø36x1.0 neck / Ø6.5x4.0 centre / 3x M2 on Ø29 output so -the existing face-spline halves + split clamp bolt straight on), the part -registry, and the STEP/STL/mesh/BOM/collision export pipeline. - -Run: python arm_model.py -> out/, web/mesh_arm.json, docs/ -""" -import math -import os -import sys -import json - -try: - sys.stdout.reconfigure(encoding="utf-8") -except Exception: - pass - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -from armcore import (M, C, V, trs, place, bb, bbox_str, cyl, ring, box, holes, - solids_of, mesh_of, MOTOR_TOL, OUT, WEB, DOCS, SRC_STEP, HALF_STEP, CLAMP_STEP) -from OCP.gp import gp_Trsf, gp_Vec, gp_Pnt, gp_Dir, gp_Ax1 -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from OCP.GProp import GProp_GProps -from OCP.BRepGProp import BRepGProp -from OCP.STEPControl import STEPControl_Writer, STEPControl_Controller, STEPControl_StepModelType -from OCP.Interface import Interface_Static - -STEPControl_Controller.Init_s() -D2R = math.pi / 180.0 -VERSION = os.environ.get("ARMVER", "r1") -ITER = int(os.environ.get("ARMITER", "1")) - -# ---------------------------------------------------------------- measured stations -FLANGE_Z = M["flange_z"] # 54.942 -COVER_Z = M["cover_z"] # 56.000 -HUB_FACE_Z = 65.0 # motor rear datum -> driven hub mating plane -CLAMP_SPAN = (FLANGE_Z + 1.66, FLANGE_Z + 9.46) -CLAMP_R_BAND = 31.0 -CLAMP_R_LUG = 38.0 -BORE_R = M["hous_d"] / 2.0 + 0.15 # 30.15 slip fit on the housing -CB_R = M["cover_d"] / 2.0 + 0.35 # 30.50 clearance for the cover -PCB_W = M["pcba_w"] + 0.8 # 64.40 square pocket -HUB_M2_CLOCK = [(360.0 - a) % 360.0 for a in M["m2_clock"]] # phase of the mirrored half -HUB_PILOT_D = M["neck_d"] - 0.4 # 35.6 -> the half's pilot lands in a Ø36 recess -PINCH_D = 2.6 # M3 clearance in printed material -# 承压面让位(完整背景见 Joint.pocket 的 docstring): -# REAR_LAND = 0.6 腔口直壁台肩高度,**保持原设计值不变**(让位不靠加高台肩) -# REAR_SEAT = 2.6 尾端面->承压面 = 0.6 原台肩 + 2.0 公差垫片位 -# R35_SEAT = 35.0 后端板 R35 外壁(承压面外径) -# SEAT_R = 35.0 承压面段(z=-REAR_SEAT..SEAT_TOP)的腔口半径 = **等于外壁 R35**。 -# SEAT_TOP = 1.0 圆腔顶端越过电机尾端面(z=0) 1mm。 -# -# 【2026-10-05 用户第三次指出:SEAT_R 必须是 35,"凸台+围挡"要一起弄平】 -# 之前取 32.2(64.4 方腔的内切圆)以为这样能保住连接壁,实测**恰恰相反**: -# 32.2 与外壁 35 之间那圈 2.8mm 宽的环墙完整保留,而承压面只挖到 32.2, -# 于是这圈环墙相对承压面【凸起 2.6mm】,把电机尾端面整个围在里面 —— -# 这正是用户截图里的"一圈围挡"(P08 该圈实测 1536.9 mm3)。它不是残留, -# 而是 SEAT_R 取小值的必然产物:只要 SEAT_R < R35,环墙就一定存在。 -# 用户要求:"把凸台、围挡一起弄平,做成和电机两头齐平一起的平面"。 -# 故 SEAT_R 取满 R35:承压面段整圈挖到外壁,承压面变成**完整 R35 平圆盘**, -# 环墙(凸台)自然消失,电机尾端面与承压面之间再无任何凸起。 -# -# 【为什么这次取 R35 不会重演2026-10-04 的"肩叉顶部没连上"】 -# 那次我切的是 z=-2.6..0 且**只切到 z=0 就停**,导致环墙在 z=0..1.0 还残留一截 -# 0.052cm3 的薄片(Y93.0~95.8 的半包开口处),那片薄片脱空成为独立碎块。 -# 现在 SEAT_TOP=+1.0 把那一截一起吃掉:实测五件全部单实体、零碎片。 -# 且承力桥改为由**臂身本体**(r>35)承担 —— 实测 band 段 r36..58 有料的方位 -# P04 10/36、P05/P06/P07/P08 各 15/36,其余方位由 z=+1.0 以上的杯体连接, -# 不存在"整圈被切断"的通槽(那正是 2026-10-04 的错误形态)。 -# 连带好处:承压环由「1.95mm 窄环」变「完整 R35 圆环 4.75mm」,受压面积 383->974mm²。 -REAR_LAND = 0.6 -REAR_SEAT = REAR_LAND + MOTOR_TOL # 2.6 尾端面->后端板承压面:公差垫片位 -R35_SEAT = 35.0 # 后端板 R35 外壁(承压面外径) -SEAT_R = R35_SEAT # 35.0 承压面段腔口 = 外壁:凸台+围挡一并消除 -SEAT_TOP = 1.0 # 圆腔顶端越过电机尾端面 1mm(吃掉开口处残片) -BORE_Z0 = 5.5 # 座孔起点:R35 圆腔挖到此处,与 R30.15 座孔相接 - -PARTS = [] -CENTS = {} - - -def add(name, cn, shape, color, printed=True, note="", qty=1): - assert shape is not None, name - lo, hi = bb(shape) - PARTS.append(dict(name=name, cn=cn, shape=shape, color=color, printed=printed, - note=note, qty=qty, bb=bbox_str(shape), vol=vol_of(shape))) - CENTS[name] = [(lo[i] + hi[i]) / 2.0 for i in range(3)] - - -def vol_of(sh): - p = GProp_GProps() - BRepGProp.VolumeProperties_s(sh.wrapped, p) - return p.Mass() - - -def common_vol(a, b): - try: - c = BRepAlgoAPI_Common(a.wrapped, b.wrapped) - c.Build() - if not c.IsDone() or c.Shape().IsNull(): - return 0.0 - return vol_of(cq.Shape(c.Shape())) - except Exception: - return -1.0 - - -def T(dx=0.0, dy=0.0, dz=0.0): - t = gp_Trsf() - t.SetTranslation(gp_Vec(dx, dy, dz)) - return t - - -def R(deg, ax, ay, az, px=0.0, py=0.0, pz=0.0): - t = gp_Trsf() - t.SetRotation(gp_Ax1(gp_Pnt(px, py, pz), gp_Dir(ax, ay, az)), deg * D2R) - return t - - -RX90 = R(90, 1, 0, 0) - - -def Ycyl(r, y0, y1, px=0.0, pz=0.0): - return place(cyl(r, -y1, -y0, px, pz), RX90) - - -def Ybox(x0, x1, y0, y1, z0, z1): - return place(box(x0, x1, z0, z1, -y1, -y0), RX90) - - -# ------------------------------------------------------- pitch-joint pivot cartridge ---- -# ONE recipe for every pitch joint (J2 shoulder, J3 elbow, J5 wrist) - they are the same -# degree of freedom, so an implementation that fixes only one of them leaves the identical -# defect in the other two. See human_layout PV_* for why a cartridge is needed at all: -# measured P04 n P05 = 0.00 mm3, so J2's 38 N*m had no path into the housing except peeling -# the face-spline teeth off across the print layers. -def pivot_geom(y_face, side, h, w, gap): - """(boss_y0, boss_y1, pocket_y0) for a cartridge against the link face at y_face. - The race pocket always opens AWAY from the link, leaving an (h-w) shoulder behind the - outer race; a printed cap ring closes the open end. The boss stops `gap` short of the - link face, or a printed face would carry the thrust and the rolling elements nothing.""" - near = y_face - gap if side < 0 else y_face + gap - if side < 0: - return near - h, near, near - h - return near, near + h, near + h - w - - -def pivot_boss(shape, pz, y0, y1, pk0, w, px, boss_od, race_od, bore, orace_id, side): - """Housing side: boss on the joint axis with the outer race sunk into it. The passage - beyond the pocket is reamed to the outer race bore, not to the shaft: a shaft-sized bore - would end inside the inner race and bind it, and a bound bearing carries nothing - it - just moves the failure back into the print layers.""" - shape = shape.fuse(Ycyl(boss_od / 2.0, y0, y1, px, pz)) - shape = shape.cut(Ycyl(race_od / 2.0, pk0 - .01, pk0 + w + .01, px, pz)) - if side < 0: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, pk0 + w, y1 + .01, px, pz)) - else: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, y0 - .01, pk0, px, pz)) - shape = shape.cut(Ycyl(bore / 2.0 + 0.2, y0 - .01, y1 + .01, px, pz)) - for a in (90.0, 210.0, 330.0): # 3x M3 cap screws, outside the outer race - u, v = 26.5 * math.cos(a * math.pi / 180.0), 26.5 * math.sin(a * math.pi / 180.0) - shape = shape.cut(Ycyl(1.25, y0 - .01, y0 + 12.0, px + u, pz + v)) - return shape - - -def pivot_journal(shape, pz, y_link, y_tip, y_collar0, y_collar1, px, bore, collar): - """Link side: the stub the inner race rides on, poking past the boss so the cap can bite - it, plus an integral collar the inner race bears against. The collar replaces a separate - spacer sleeve and lives in the boss's outer-race passage, so it must stay under that - diameter - the 0.1 mm it leaves is what lets the race, not the collar, take the thrust.""" - a, b = sorted((y_link, y_tip)) - shape = shape.fuse(Ycyl(bore / 2.0, a, b, px, pz)) - c0, c1 = sorted((y_collar0, y_collar1)) - return shape.fuse(Ycyl(collar / 2.0, c0, c1, px, pz)) - - -def prism(pts2, z0, z1): - w2 = [(p[0], p[1]) for p in pts2] - return (cq.Workplane("XY", origin=(0, 0, z0)) - .polyline(w2 + [w2[0]]).close().extrude(z1 - z0, combine=False)).val() - - -def Yplate(pts_xz, y0, y1): - return place(prism(pts_xz, -y1, -y0), RX90) - - -def dogbone(a, b, ra, rb, n=20): - """Tangent hull of two discs. The outer-tangent offset angle is acos((ra-rb)/L); - using asin made the ra==rb case sweep a full circle around b and self-intersect.""" - dx, dy = b[0] - a[0], b[1] - a[1] - L = math.hypot(dx, dy) or 1.0 - th = math.atan2(dy, dx) - beta = math.acos(max(-1.0, min(1.0, (ra - rb) / L))) - sw_a = 2 * math.pi - 2 * beta - sw_b = 2 * beta - pts = [(a[0] + ra * math.cos(th + beta + sw_a * k / n), a[1] + ra * math.sin(th + beta + sw_a * k / n)) - for k in range(n + 1)] - pts += [(b[0] + rb * math.cos(th - beta + sw_b * k / n), b[1] + rb * math.sin(th - beta + sw_b * k / n)) - for k in range(1, n + 1)] - return pts - - -def sector(r_out, r_in, a0, a1, z0, z1, n=14): - """Annular sector between two radii and two angles (deg) - a joint shroud segment. - Must NOT include the axis point, or this becomes a solid pie wedge that fills the joint.""" - pts = [(r_out * math.cos((a0 + (a1 - a0) * k / n) * D2R), - r_out * math.sin((a0 + (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - pts += [(r_in * math.cos((a1 - (a1 - a0) * k / n) * D2R), - r_in * math.sin((a1 - (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - return prism(pts, z0, z1) - - -# ---------------------------------------------------------------- joint frames & interfaces -class Joint: - """One revolute joint. `rear` = motor rear datum point, `d` = axis unit dir (motor -> hub).""" - - def __init__(self, jid, cn, rear, d, xdir, lug_phase=0.0): - self.jid, self.cn = jid, cn - self.rear = rear - self.d = d - self.xdir = xdir - self.t = trs(rear, xdir, d) # motor-local -> global - self.lug_phase = lug_phase # rotation of the clamp about the axis - - def L(self, shape_local): - return place(shape_local, self.t) - - def at(self, s): - return (self.rear[0] + self.d[0] * s, self.rear[1] + self.d[1] * s, self.rear[2] + self.d[2] * s) - - def pocket(self, shape, front_pad=0.6): - """Cut the motor pocket out of a proximal link body. - - REAR_SEAT = 电机尾端面(局部 z=0)到后端板承压面的距离。它不是定位面而是 - **垫片位**:公差 MOTOR_TOL=2.0(armcore)要靠它消化 —— 电机比标称短的那一份, - 就垫在尾端面与这个承压面之间(见 armcore.MOTOR_TOL 的说明)。原值 0.6 只能吃 - 0.6mm,短 1mm 以上的电机装不进、长 0.6mm 以上的电机会顶死后端板而压溃。 - 注意它同时是承压面的高度,所以让空 2.0mm 要求 t_plate >= 8 - (见各零件 rear_mount_holes 的 t_plate)。 - - 【2026-10-04 整改:承压面段改用 R35 圆腔,凸台彻底消失】 - 上一版把 64.4 **方**腔整体从 -0.6 加深到 -2.6(纯掏腔),方腔壁以外 - z=-2.6..0 之间那圈 R35 料一动没动,于是相对新承压面整整凸起 2.6mm —— - 这就是 P04~P08 后端那圈"悬空凸台"(用户截图红圈,3D 打印时该处是悬空 - 薄壁,既要支撑又公差难保证)。 - - 为什么之前试"把凸台切掉"总失败(几何实测,别再重复): - - 64.4 方腔的**内切圆半径恰好 = 32.2**,所以方腔在 45° 方向已经吃穿到 - r=45.5,R35 壁只剩**四条边中点附近**的料。实测 r=34 处材料沿 z 的 - 区间(承压面 -2.6):P06 的 0°/180° 是 -8.0..12.0(贯通,是臂身本体), - 80°/100° 是 -8.0..-0.0(**只比承压面多 2.6mm,这段就是凸台**), - 其余 20~160° 是 -8.0..-2.6(承压环本体,齐平)。 - - 因此整段全截面切会把 0/90 方向那条**贯通到 z=12 的承力桥**一起切断 - -> P05/P08 碎成 2 块(实测)。这不是"承压环是唯一连接", - 而是我的刀具没区分"桥"和"悬空料"。 - - 现在的做法(一刀,不做角度分区): - 承压面所在的 z=-REAR_SEAT..SEAT_TOP 段,腔口取 **SEAT_R = R35 = 外壁** - 的圆,把整圈挖到外壁,承压面因此变成**完整 R35 平圆盘**。 - - 关键:**SEAT_R 必须等于 R35**。只要 SEAT_R < R35,两者之间那圈环墙 - 就必然存在,且相对承压面凸起 (R35-SEAT_R) —— 那圈环墙就是用户说的 - "围挡"(P08 实测 1536.9mm3),不是残留而是我的取值必然产物。 - - SEAT_TOP 取 +1.0(越过电机尾端面 1mm):2026-10-04 那版取 0 时, - 半包开口处(Y93.0~95.8)会残留一截 0.052cm3 薄片脱空成碎块; - 抬到 +1.0 后五件全部单实体零碎片。 - - 承力改由**臂身本体**承担:band 段内 r36..58 有料的方位 P04 10/36、 - 其余四件各 15/36,其余方位靠 z>+1.0 的杯体连接,不存在整圈切断的通槽。 - (2026-10-04 那版"肩叉顶部没连上"就是通槽形态,务必不要重现。) - 【2026-10-05b 去掉 64.4 方腔,电机腔内壁恢复完整圆弧面】 - 此前 z=0..pcba_t 还叠了一层 `box(±32.2)` 方腔,理由是"PCB 是方的"。 - **该假设不成立**:实测电机 STEP 已把 PCB 体积含在本体轮廓内,不会超出 - 投影范围,所以方腔纯属多挖。它带来两个副作用(用户截图 P06/P07/P08 腔内观感): - 1. 方腔半宽 32.2 < 外壁 R35,在四个方位留下一圈 2.8mm 环形台肩; - 2. 方腔对角 45.5 > R35,四个角把外壁咬穿,形成四个凸角。 - 两者叠加使内壁呈"圆—方—圆"三段,与同族零件的整体圆弧语言不一致。 - 现改为**单一 R35 圆腔从承压面一直挖到座孔起点 BORE_Z0(=5.5)**, - 与其上的 R30.15 座孔正交相接,内壁成为两段同心正圆(仅一处正常台阶), - 承压面到外壁之间再无任何凸起或方角。P04 走自己那套刀具,本来就无方腔。 - """ - c = [cyl(BORE_R, BORE_Z0, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + front_pad), - # 承压面段 + 尾部过渡段:SEAT_R(=R35 外壁) 圆腔,从承压面一路挖到座孔起点。 - # 取满 R35 才能让承压面与外壁齐平成一个平面;取小于 R35 就会留下 - # 一圈凸起的"围挡"(见 docstring)。 - cyl(SEAT_R, -REAR_SEAT, BORE_Z0)] - return shape.cut(*[self.L(s) for s in c]) - - def rear_mount_holes(self, shape, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0): - """Cut the 4x rear mounting bolt holes into a proximal link body. - Matches the 4x M3 tapped radial slots on the motor rear endplate - (电机.STEP 实测):BCD55、相位 39.9°+90k,槽宽 2.5、径向可调约 ±1.25mm。 - 旧版误用 35.1x40.9 矩形孔距:对角两颗偏出螺纹槽 ~4.7mm 无处咬合 - (2026-10-02 用户指出"螺钉没有对齐",按 STEP 实测槽位修正)。 - """ - cuts = [] - for k in range(4): - a = math.radians(39.9 + 90.0 * k) - sx = 27.5 * math.cos(a) - sy = 27.5 * math.sin(a) - h = cyl(hole_d / 2.0, -t_plate - 1.0, 3.0, sx, sy) - cuts.append(self.L(h)) - if cb_d > 0 and cb_depth > 0: - cb = cyl(cb_d / 2.0, -t_plate - 1.0, -t_plate + cb_depth, sx, sy) - cuts.append(self.L(cb)) - return shape.cut(*cuts) - - def front_collar_pocket(self, shape, r_bore=30.25, z0=50.0, z1=56.5): - """Cut front locating collar pocket for the motor front rim.""" - p = cyl(r_bore, z0, z1) - return shape.cut(self.L(p)) - - def pinch(self, shape, fracs=(0.26, 0.58, 0.86), r_out=48.0, n=2): - """Radial M3 pinch screws through the boss wall onto the housing.""" - cuts = [] - for fr in fracs[:n]: - for a in (self.lug_phase, self.lug_phase + 180.0): - c = cyl(PINCH_D / 2.0, -1.0, r_out, 0, 0) - c = place(c, R(a, 0, 0, 1)) - c = place(c, T(0, 0, fr * COVER_Z)) - cuts.append(self.L(c)) - return shape.cut(*cuts) - - def hub_face(self): - return self.at(HUB_FACE_Z) - - def cut_hub(self, shape, depth=24.0, r_clear=32.0): - """Cut the motor-output replica into the driven hub. The mating plane sits at - motor-local z = HUB_FACE_Z, and the recesses open there and run into the driven - material (away from the motor), so that half B's pilot (0.60) and spigot (4.06) - land in them.""" - H = HUB_FACE_Z - # measured on the delivered half B: past the mating plane it is Ø35.6 for 1.4 mm - # then a Ø6.3 spigot to 4.06 - the nominal neck_depth of 1.0 is 0.4 too shallow, - # which showed up as a ~160 mm3 interference at every driven hub - cuts = [cyl(M["neck_d"] / 2.0, H - 0.05, H + 1.70), - cyl(M["centre_d"] / 2.0, H - 0.05, H + 5.05), - # probed on the delivered half: its Ø54 back face sits ~0.06 mm PAST the - # nominal plane, so the driven face gets a 0.35 deep, Ø54.6 relief. That is - # also the printed-part tolerance land the joint wants. - cyl(27.30, H - 0.05, H + 0.35)] - for a in HUB_M2_CLOCK: - u = M["m2_bcd"] / 2.0 * math.cos(a * D2R) - v = M["m2_bcd"] / 2.0 * math.sin(a * D2R) - cuts.append(cyl(M["m2_tap_d"] / 2.0, H - 0.05, H + M["m2_depth"], u, v)) - return shape.cut(*[self.L(s) for s in cuts]) - - def hub_island(self, shape, r=30.0, t=9.5): - """Solid disc at the joint centre (the lightening dish must not break into the - interface recesses), then the output replica is cut into it. Starts exactly on the - mating plane: half B's Ø54 body ends there, so any overlap is a hard clash.""" - shape = shape.fuse(self.L(cyl(r, HUB_FACE_Z, HUB_FACE_Z + t))) - shape = self.cut_hub(shape) - # two arc shrouds over the clamp band: the joint reads as a closed drum, and the - # sectors are placed so the M3 lug bolts on the joint's local +-X stay reachable - for (a0, a1) in ((34.0, 146.0), (214.0, 326.0)): - shape = shape.fuse(self.L(sector(38.0, 31.6, a0, a1, FLANGE_Z + 1.4, HUB_FACE_Z))) - return shape - - def place_hardware(self, half, clamp, tag): - """Place the existing coupling halves + split clamp for this joint. - - The delivered half is authored with z=0 at the motor flange face and spans - z -3.00..8.06. B = flip 180 deg about the local X axis, then shift by - 2*PART_Z, which puts its flat back face exactly on the hub mating plane - (motor-local 65.00) - verified against the measured feature bounds. - """ - tf = trs(self.at(FLANGE_Z), self.xdir, self.d) - hA = place(half, tf) - hb = place(half, R(180, 1, 0, 0)) - hb = place(hb, T(0, 0, 2 * C["part_z"])) - hB = place(hb, tf) - cA = place(clamp, tf) - cB = place(place(clamp, R(180, 0, 0, 1)), tf) # the two C pieces are one ring split - # along a diametral plane: 180 deg about the JOINT axis, so both keep the same - # axial span 56.602..64.402 and sit on opposite sides of the band. - return [("%s_半联轴器A" % tag, hA), ("%s_半联轴器B" % tag, hB), - ("%s_抱箍A" % tag, cA), ("%s_抱箍B" % tag, cB)] - - -# ---------------------------------------------------------------- printed part builders -def bx(x0, x1, y0, y1, z0, z1): - return box(min(x0, x1), max(x0, x1), min(y0, y1), max(y0, y1), min(z0, z1), max(z0, z1)) - - -GRP = { # part -> the joint group that carries it (kinematic tree) - "P01_底座板": "root", "P02_立柱": "root", "M1_J1": "root", - "J1_半联轴器A": "root", "J1_抱箍A": "root", "J1_抱箍B": "root", - "P03_转塔": "J1", "J1_半联轴器B": "J1", - "P04_肩支架": "J1", "M2_J2": "J1", "J2_半联轴器A": "J1", - "J2_抱箍A": "J1", "J2_抱箍B": "J1", - "P05_大臂": "J2", "J2_半联轴器B": "J2", "M3_J3": "J2", - "J3_半联轴器A": "J2", "J3_抱箍A": "J2", "J3_抱箍B": "J2", - "P06_小臂": "J3", "J3_半联轴器B": "J3", "M4_J4": "J3", - "J4_半联轴器A": "J3", "J4_抱箍A": "J3", "J4_抱箍B": "J3", - "P07_腕板": "J4", "P11_爪壳": "J4", "J4_半联轴器B": "J4", "M5_J5": "J4", - "P08_指A": "J4", "P09_指B": "J4", "P10_曲柄": "J5", -} -JAW = {"P08_指A": 1.0, "P09_指B": -1.0} # sign of the jaw travel along the J5 local +X - - -def explode_vec(p, JS, cents, gmean): - """Explosion offset: coupling hardware separates along its joint axis, everything - else moves radially out of the assembly centroid.""" - nm = p["name"] - c = cents[nm] - for jid in ("J1", "J2", "J3", "J4"): - if nm.startswith(jid + "_半联轴器B"): - return [JS[jid].d[i] * 24.0 for i in range(3)] - if nm.startswith(jid + "_半联轴器A") or nm.startswith(jid + "_抱箍"): - k = -26.0 if "抱箍" in nm else -16.0 - return [JS[jid].d[i] * k for i in range(3)] - v = [c[i] - gmean[i] for i in range(3)] - m = math.sqrt(sum(x * x for x in v)) or 1.0 - return [v[i] / m * 34.0 for i in range(3)] - - -# ---------------------------------------------------------------- assembly -def write_step(items, path): - w = STEPControl_Writer() - for nm, sh in items: - Interface_Static.SetCVal_s('write.step.product.name', nm) - w.Transfer(sh.wrapped, STEPControl_StepModelType.STEPControl_AsIs) - w.Write(path.replace("/", "\\")) - return os.path.getsize(path) - - -import threading -import time -from concurrent.futures import ThreadPoolExecutor - -def export(JS): - for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS): - os.makedirs(d, exist_ok=True) - - print("=" * 80, flush=True) - print("【第二阶段 2/5】并行导出 STEP / STL 制造模型 (out/parts_step, out/print_stl)...", flush=True) - print("=" * 80, flush=True) - - # 1. 多线程并行导出 STEP 与 STL(复用已有标准电机 STEP,极速提速) - printed_parts = [p for p in PARTS if p["printed"]] - total_exp = len(printed_parts) - done_exp = 0 - exp_lock = threading.Lock() - - def export_one_part(p): - nonlocal done_exp - t0 = time.time() - fn = "%s/%s.step" % (OUT + "/parts_step", p["name"]) - # 如果是非打印标准已有件且已存在,跳过冗余写盘 - if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000: - pass - else: - write_step([(p["name"], p["shape"])], fn) - if p["printed"]: - stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"]) - cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL") - with exp_lock: - done_exp += 1 - dt = time.time() - t0 - print(f" [文件导出 {done_exp}/{total_exp}] {p['name']}.step / .stl 导出完成 ({dt:.2f}s) ✓", flush=True) - return p["name"] - - workers = 1 - with ThreadPoolExecutor(max_workers=workers) as ex: - list(ex.map(export_one_part, PARTS)) - - print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True) - - # 2. 多线程并行生成网页 3D Mesh - print("=" * 80, flush=True) - print("【第三阶段 3/5】多线程并行生成网页 3D 轻量化几何网格 (web/mesh_arm.json)...", flush=True) - print("=" * 80, flush=True) - - done_mesh = 0 - mesh_lock = threading.Lock() - total_parts = len(PARTS) - - def mesh_one_part(p): - nonlocal done_mesh - t0 = time.time() - key = "motor" if p["name"].startswith("M") else ("coupl" if "联轴器" in p["name"] or "抱箍" in p["name"] else p["name"]) - defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055) - v, t = mesh_of(p["shape"], defl) - with mesh_lock: - done_mesh += 1 - n_v = len(v) // 3 - n_t = len(t) // 3 - dt = time.time() - t0 - print(f" [网格细分 {done_mesh:2d}/{total_parts}] {p['name']:<14} 顶点: {n_v:5d} | 面片: {n_t:5d} ({dt:.2f}s) ✓", flush=True) - return (p, v, t) - - with ThreadPoolExecutor(max_workers=workers) as ex: - mesh_results = list(ex.map(mesh_one_part, PARTS)) - - parts = [] - for idx, (p, v, t) in enumerate(mesh_results): - parts.append(dict(name=p["name"], cn=p["cn"], v=v, t=t, color=p["color"], - printed=p["printed"], vol=round(p["vol"], 0), note=p["note"], - grp=GRP.get(p["name"], "root"), jaw=JAW.get(p["name"], 0.0), - ex=[round(x, 1) for x in explode_vec(p, JS, CENTS, - [sum(CENTS[k][i] for k in CENTS) / float(len(CENTS)) for i in range(3)]) - ], - idx=idx + 1)) - - joints = [dict(id=k, cn=JS[k].cn, rear=JS[k].rear, d=JS[k].d, xdir=JS[k].xdir, hub=JS[k].hub_face()) - for k in JS] - d3 = lambda a, b: math.sqrt(sum((a[i] - b[i]) ** 2 for i in range(3))) - json.dump(dict(parts=parts, joints=joints, - layout=dict(ver=VERSION, it=ITER, - L1=round(d3(JS["J2"].rear, JS["J4"].rear), 1), - L2=round(d3(JS["J4"].rear, JS["J5"].rear), 1)), - measured=dict(M=M, C=C, HUB_FACE_Z=HUB_FACE_Z, CLAMP_SPAN=list(CLAMP_SPAN))), - open(WEB + "/mesh_arm.json", "w", encoding="utf-8"), ensure_ascii=False) - bom = [dict(件号=p["name"], 名称=p["cn"], 类型="3D打印" if p["printed"] else "已有/电机", - 数量=1, 体积cm3=round(p["vol"] / 1000.0, 1), - 质量g_PLA=round(p["vol"] * 1.24e-3, 1), 包络=p["bb"], 说明=p["note"]) for p in PARTS] - json.dump(bom, open(DOCS + "/bom.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1) - print(f"-> 3D网格与BOM清单已保存至 web/mesh_arm.json 与 docs/bom.json ✓\n", flush=True) - return bom - - -def collide(): - ps = [p for p in PARTS if p["vol"] > 1.0] - pairs = [] - MOUNT_PAIRS = { - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "M1_J1"), ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M1_J1"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M2_J2"), ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M2_J2"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M3_J3"), ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M3_J3"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M4_J4"), ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M4_J4"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M5_J5"), ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M5_J5"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M6_J6"), ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "M6_J6"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P01_B_立柱中段_三角形30%_墙4底5_竖打"), ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "P01_C_立柱上段_三角形30%_墙4底5_竖打"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P02_Center_底座中核心_螺旋体30%_墙5底6_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "P03_肩吊臂_螺旋体35%_墙5底5_竖打"), - } - for i in range(len(ps)): - for j in range(i + 1, len(ps)): - a, b = ps[i], ps[j] - if a["name"].startswith("M") and b["name"].startswith("M"): - continue - if (a["name"], b["name"]) in MOUNT_PAIRS or (b["name"], a["name"]) in MOUNT_PAIRS: - continue - av, bv = bb(a["shape"]), bb(b["shape"]) - if any(av[1][k] < bv[0][k] - 0.05 or bv[1][k] < av[0][k] + 0.05 for k in range(3)): - continue - pairs.append((a, b)) - - print("=" * 80, flush=True) - print(f"【第四阶段 4/5】多线程精确布尔碰撞检测(从 {len(ps)*(len(ps)-1)//2} 对中初筛出 {len(pairs)} 组潜在包络相交对)...", flush=True) - print("=" * 80, flush=True) - - done_pair = 0 - pair_lock = threading.Lock() - - def check_pair(pair): - nonlocal done_pair - a, b = pair - t0 = time.time() - v = common_vol(a["shape"], b["shape"]) - with pair_lock: - done_pair += 1 - status = f"干涉 {v:.1f} mm³ ❌" if v > 2.0 else "零干涉 (0.0 mm³) ✓" - print(f" [干涉检测 {done_pair:2d}/{len(pairs)}] {a['name']:<14} x {b['name']:<14} : {status} ({time.time()-t0:.3f}s)", flush=True) - if v > 2.0: - try: - c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped) - c.SetRunParallel(True) - c.Build() - cb = cq.Shape(c.Shape()) - lo, hi = bb(cb) - loc = "@[%.0f %.0f %.0f..%.0f %.0f %.0f]" % ( - lo[0], lo[1], lo[2], hi[0], hi[1], hi[2]) - except Exception: - loc = "?" - return (a["name"], b["name"], round(v, 1), loc) - return None - - rows = [] - for pair in pairs: - res = check_pair(pair) - if res is not None: - rows.append(res) - - rows.sort(key=lambda r: -r[2]) - json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"), - ensure_ascii=False) - if len(rows) == 0: - print(f"\n-> 静态碰撞干涉检测通过: 0 对干涉!全部 {len(pairs)} 组接触面对零碰撞!\n", flush=True) - else: - print(f"\n-> 发现 {len(rows)} 对干涉!\n", flush=True) - return rows - - -def axial_extent(j, shape): - lo, hi = bb(shape) - vals = [] - for i in (0, 1): - for k in range(3): - p = [lo[0], lo[1], lo[2]] - p[k] = hi[k] if i else lo[k] - vals.append(sum((p[q] - j.rear[q]) * j.d[q] for q in range(3))) - return min(vals), max(vals) - - -def verify_interfaces(half, clamp, JS): - """Numeric self-check of the derived joint stack, before trusting the renders.""" - rows = [] - for k, j in JS.items(): - if k == "J5": - continue - parts = dict(j.place_hardware(half, clamp, k)) - a = axial_extent(j, parts["%s_半联轴器A" % k]) - b = axial_extent(j, parts["%s_半联轴器B" % k]) - c = axial_extent(j, parts["%s_抱箍A" % k]) - hub = j.at(HUB_FACE_Z) - rows.append(dict(jid=k, halfA=[round(x, 3) for x in a], halfB=[round(x, 3) for x in b], - clamp=[round(x, 3) for x in c], hub_face=round(HUB_FACE_Z, 3), - spigot_proud=round(b[1] - HUB_FACE_Z, 3), - clamp_clear=round(c[0] - COVER_Z, 3))) - return rows diff --git a/.bak/arm_build.py.bak20261005e b/.bak/arm_build.py.bak20261005e deleted file mode 100644 index 785452a..0000000 --- a/.bak/arm_build.py.bak20261005e +++ /dev/null @@ -1,627 +0,0 @@ -# -*- coding: utf-8 -*- -"""Shared geometry / assembly / export library for the robot arm build. - -No layout of its own: the joint frames and station positions live in -human_layout.py and arm_model.py, which import this module. What lives here is -the measured motor interface (docs/interfaces.md), the Joint class (motor slip -bore + PCBA pocket + pinch screws; driven hub mating plane at motor rear datum -+65.0 replicating the Ø36x1.0 neck / Ø6.5x4.0 centre / 3x M2 on Ø29 output so -the existing face-spline halves + split clamp bolt straight on), the part -registry, and the STEP/STL/mesh/BOM/collision export pipeline. - -Run: python arm_model.py -> out/, web/mesh_arm.json, docs/ -""" -import math -import os -import sys -import json - -try: - sys.stdout.reconfigure(encoding="utf-8") -except Exception: - pass - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -from armcore import (M, C, V, trs, place, bb, bbox_str, cyl, ring, box, holes, - solids_of, mesh_of, MOTOR_TOL, OUT, WEB, DOCS, SRC_STEP, HALF_STEP, CLAMP_STEP) -from OCP.gp import gp_Trsf, gp_Vec, gp_Pnt, gp_Dir, gp_Ax1 -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from OCP.GProp import GProp_GProps -from OCP.BRepGProp import BRepGProp -from OCP.STEPControl import STEPControl_Writer, STEPControl_Controller, STEPControl_StepModelType -from OCP.Interface import Interface_Static - -STEPControl_Controller.Init_s() -D2R = math.pi / 180.0 -VERSION = os.environ.get("ARMVER", "r1") -ITER = int(os.environ.get("ARMITER", "1")) - -# ---------------------------------------------------------------- measured stations -FLANGE_Z = M["flange_z"] # 54.942 -COVER_Z = M["cover_z"] # 56.000 -HUB_FACE_Z = 65.0 # motor rear datum -> driven hub mating plane -CLAMP_SPAN = (FLANGE_Z + 1.66, FLANGE_Z + 9.46) -CLAMP_R_BAND = 31.0 -CLAMP_R_LUG = 38.0 -BORE_R = M["hous_d"] / 2.0 + 0.15 # 30.15 slip fit on the housing -CB_R = M["cover_d"] / 2.0 + 0.35 # 30.50 clearance for the cover -PCB_W = M["pcba_w"] + 0.8 # 64.40 square pocket -HUB_M2_CLOCK = [(360.0 - a) % 360.0 for a in M["m2_clock"]] # phase of the mirrored half -HUB_PILOT_D = M["neck_d"] - 0.4 # 35.6 -> the half's pilot lands in a Ø36 recess -PINCH_D = 2.6 # M3 clearance in printed material -# 承压面让位(完整背景见 Joint.pocket 的 docstring): -# REAR_LAND = 0.6 腔口直壁台肩高度,**保持原设计值不变**(让位不靠加高台肩) -# REAR_SEAT = 2.6 尾端面->承压面 = 0.6 原台肩 + 2.0 公差垫片位 -# R35_SEAT = 35.0 后端板 R35 外壁(承压面外径) -# SEAT_R = 35.0 承压面段(z=-REAR_SEAT..SEAT_TOP)的腔口半径 = **等于外壁 R35**。 -# SEAT_TOP = 1.0 圆腔顶端越过电机尾端面(z=0) 1mm。 -# -# 【2026-10-05 用户第三次指出:SEAT_R 必须是 35,"凸台+围挡"要一起弄平】 -# 之前取 32.2(64.4 方腔的内切圆)以为这样能保住连接壁,实测**恰恰相反**: -# 32.2 与外壁 35 之间那圈 2.8mm 宽的环墙完整保留,而承压面只挖到 32.2, -# 于是这圈环墙相对承压面【凸起 2.6mm】,把电机尾端面整个围在里面 —— -# 这正是用户截图里的"一圈围挡"(P08 该圈实测 1536.9 mm3)。它不是残留, -# 而是 SEAT_R 取小值的必然产物:只要 SEAT_R < R35,环墙就一定存在。 -# 用户要求:"把凸台、围挡一起弄平,做成和电机两头齐平一起的平面"。 -# 故 SEAT_R 取满 R35:承压面段整圈挖到外壁,承压面变成**完整 R35 平圆盘**, -# 环墙(凸台)自然消失,电机尾端面与承压面之间再无任何凸起。 -# -# 【为什么这次取 R35 不会重演2026-10-04 的"肩叉顶部没连上"】 -# 那次我切的是 z=-2.6..0 且**只切到 z=0 就停**,导致环墙在 z=0..1.0 还残留一截 -# 0.052cm3 的薄片(Y93.0~95.8 的半包开口处),那片薄片脱空成为独立碎块。 -# 现在 SEAT_TOP=+1.0 把那一截一起吃掉:实测五件全部单实体、零碎片。 -# 且承力桥改为由**臂身本体**(r>35)承担 —— 实测 band 段 r36..58 有料的方位 -# P04 10/36、P05/P06/P07/P08 各 15/36,其余方位由 z=+1.0 以上的杯体连接, -# 不存在"整圈被切断"的通槽(那正是 2026-10-04 的错误形态)。 -# 连带好处:承压环由「1.95mm 窄环」变「完整 R35 圆环 4.75mm」,受压面积 383->974mm²。 -REAR_LAND = 0.6 -REAR_SEAT = REAR_LAND + MOTOR_TOL # 2.6 尾端面->后端板承压面:公差垫片位 -R35_SEAT = 35.0 # 后端板 R35 外壁(承压面外径) -SEAT_R = R35_SEAT # 35.0 承压面段腔口 = 外壁:凸台+围挡一并消除 -SEAT_TOP = 1.0 # 圆腔顶端越过电机尾端面 1mm(吃掉开口处残片) -BORE_Z0 = 5.5 # 座孔起点:R35 圆腔挖到此处,与 R30.15 座孔相接 - -PARTS = [] -CENTS = {} - - -def add(name, cn, shape, color, printed=True, note="", qty=1): - assert shape is not None, name - lo, hi = bb(shape) - PARTS.append(dict(name=name, cn=cn, shape=shape, color=color, printed=printed, - note=note, qty=qty, bb=bbox_str(shape), vol=vol_of(shape))) - CENTS[name] = [(lo[i] + hi[i]) / 2.0 for i in range(3)] - - -def vol_of(sh): - p = GProp_GProps() - BRepGProp.VolumeProperties_s(sh.wrapped, p) - return p.Mass() - - -def common_vol(a, b): - try: - c = BRepAlgoAPI_Common(a.wrapped, b.wrapped) - c.Build() - if not c.IsDone() or c.Shape().IsNull(): - return 0.0 - return vol_of(cq.Shape(c.Shape())) - except Exception: - return -1.0 - - -def T(dx=0.0, dy=0.0, dz=0.0): - t = gp_Trsf() - t.SetTranslation(gp_Vec(dx, dy, dz)) - return t - - -def R(deg, ax, ay, az, px=0.0, py=0.0, pz=0.0): - t = gp_Trsf() - t.SetRotation(gp_Ax1(gp_Pnt(px, py, pz), gp_Dir(ax, ay, az)), deg * D2R) - return t - - -RX90 = R(90, 1, 0, 0) - - -def Ycyl(r, y0, y1, px=0.0, pz=0.0): - return place(cyl(r, -y1, -y0, px, pz), RX90) - - -def Ybox(x0, x1, y0, y1, z0, z1): - return place(box(x0, x1, z0, z1, -y1, -y0), RX90) - - -# ------------------------------------------------------- pitch-joint pivot cartridge ---- -# ONE recipe for every pitch joint (J2 shoulder, J3 elbow, J5 wrist) - they are the same -# degree of freedom, so an implementation that fixes only one of them leaves the identical -# defect in the other two. See human_layout PV_* for why a cartridge is needed at all: -# measured P04 n P05 = 0.00 mm3, so J2's 38 N*m had no path into the housing except peeling -# the face-spline teeth off across the print layers. -def pivot_geom(y_face, side, h, w, gap): - """(boss_y0, boss_y1, pocket_y0) for a cartridge against the link face at y_face. - The race pocket always opens AWAY from the link, leaving an (h-w) shoulder behind the - outer race; a printed cap ring closes the open end. The boss stops `gap` short of the - link face, or a printed face would carry the thrust and the rolling elements nothing.""" - near = y_face - gap if side < 0 else y_face + gap - if side < 0: - return near - h, near, near - h - return near, near + h, near + h - w - - -def pivot_boss(shape, pz, y0, y1, pk0, w, px, boss_od, race_od, bore, orace_id, side): - """Housing side: boss on the joint axis with the outer race sunk into it. The passage - beyond the pocket is reamed to the outer race bore, not to the shaft: a shaft-sized bore - would end inside the inner race and bind it, and a bound bearing carries nothing - it - just moves the failure back into the print layers.""" - shape = shape.fuse(Ycyl(boss_od / 2.0, y0, y1, px, pz)) - shape = shape.cut(Ycyl(race_od / 2.0, pk0 - .01, pk0 + w + .01, px, pz)) - if side < 0: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, pk0 + w, y1 + .01, px, pz)) - else: - shape = shape.cut(Ycyl(orace_id / 2.0 + 0.1, y0 - .01, pk0, px, pz)) - shape = shape.cut(Ycyl(bore / 2.0 + 0.2, y0 - .01, y1 + .01, px, pz)) - for a in (90.0, 210.0, 330.0): # 3x M3 cap screws, outside the outer race - u, v = 26.5 * math.cos(a * math.pi / 180.0), 26.5 * math.sin(a * math.pi / 180.0) - shape = shape.cut(Ycyl(1.25, y0 - .01, y0 + 12.0, px + u, pz + v)) - return shape - - -def pivot_journal(shape, pz, y_link, y_tip, y_collar0, y_collar1, px, bore, collar): - """Link side: the stub the inner race rides on, poking past the boss so the cap can bite - it, plus an integral collar the inner race bears against. The collar replaces a separate - spacer sleeve and lives in the boss's outer-race passage, so it must stay under that - diameter - the 0.1 mm it leaves is what lets the race, not the collar, take the thrust.""" - a, b = sorted((y_link, y_tip)) - shape = shape.fuse(Ycyl(bore / 2.0, a, b, px, pz)) - c0, c1 = sorted((y_collar0, y_collar1)) - return shape.fuse(Ycyl(collar / 2.0, c0, c1, px, pz)) - - -def prism(pts2, z0, z1): - w2 = [(p[0], p[1]) for p in pts2] - return (cq.Workplane("XY", origin=(0, 0, z0)) - .polyline(w2 + [w2[0]]).close().extrude(z1 - z0, combine=False)).val() - - -def Yplate(pts_xz, y0, y1): - return place(prism(pts_xz, -y1, -y0), RX90) - - -def dogbone(a, b, ra, rb, n=20): - """Tangent hull of two discs. The outer-tangent offset angle is acos((ra-rb)/L); - using asin made the ra==rb case sweep a full circle around b and self-intersect.""" - dx, dy = b[0] - a[0], b[1] - a[1] - L = math.hypot(dx, dy) or 1.0 - th = math.atan2(dy, dx) - beta = math.acos(max(-1.0, min(1.0, (ra - rb) / L))) - sw_a = 2 * math.pi - 2 * beta - sw_b = 2 * beta - pts = [(a[0] + ra * math.cos(th + beta + sw_a * k / n), a[1] + ra * math.sin(th + beta + sw_a * k / n)) - for k in range(n + 1)] - pts += [(b[0] + rb * math.cos(th - beta + sw_b * k / n), b[1] + rb * math.sin(th - beta + sw_b * k / n)) - for k in range(1, n + 1)] - return pts - - -def sector(r_out, r_in, a0, a1, z0, z1, n=14): - """Annular sector between two radii and two angles (deg) - a joint shroud segment. - Must NOT include the axis point, or this becomes a solid pie wedge that fills the joint.""" - pts = [(r_out * math.cos((a0 + (a1 - a0) * k / n) * D2R), - r_out * math.sin((a0 + (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - pts += [(r_in * math.cos((a1 - (a1 - a0) * k / n) * D2R), - r_in * math.sin((a1 - (a1 - a0) * k / n) * D2R)) for k in range(n + 1)] - return prism(pts, z0, z1) - - -# ---------------------------------------------------------------- joint frames & interfaces -class Joint: - """One revolute joint. `rear` = motor rear datum point, `d` = axis unit dir (motor -> hub).""" - - def __init__(self, jid, cn, rear, d, xdir, lug_phase=0.0): - self.jid, self.cn = jid, cn - self.rear = rear - self.d = d - self.xdir = xdir - self.t = trs(rear, xdir, d) # motor-local -> global - self.lug_phase = lug_phase # rotation of the clamp about the axis - - def L(self, shape_local): - return place(shape_local, self.t) - - def at(self, s): - return (self.rear[0] + self.d[0] * s, self.rear[1] + self.d[1] * s, self.rear[2] + self.d[2] * s) - - def pocket(self, shape, front_pad=0.6): - """Cut the motor pocket out of a proximal link body. - - REAR_SEAT = 电机尾端面(局部 z=0)到后端板承压面的距离。它不是定位面而是 - **垫片位**:公差 MOTOR_TOL=2.0(armcore)要靠它消化 —— 电机比标称短的那一份, - 就垫在尾端面与这个承压面之间(见 armcore.MOTOR_TOL 的说明)。原值 0.6 只能吃 - 0.6mm,短 1mm 以上的电机装不进、长 0.6mm 以上的电机会顶死后端板而压溃。 - 注意它同时是承压面的高度,所以让空 2.0mm 要求 t_plate >= 8 - (见各零件 rear_mount_holes 的 t_plate)。 - - 【2026-10-04 整改:承压面段改用 R35 圆腔,凸台彻底消失】 - 上一版把 64.4 **方**腔整体从 -0.6 加深到 -2.6(纯掏腔),方腔壁以外 - z=-2.6..0 之间那圈 R35 料一动没动,于是相对新承压面整整凸起 2.6mm —— - 这就是 P04~P08 后端那圈"悬空凸台"(用户截图红圈,3D 打印时该处是悬空 - 薄壁,既要支撑又公差难保证)。 - - 为什么之前试"把凸台切掉"总失败(几何实测,别再重复): - - 64.4 方腔的**内切圆半径恰好 = 32.2**,所以方腔在 45° 方向已经吃穿到 - r=45.5,R35 壁只剩**四条边中点附近**的料。实测 r=34 处材料沿 z 的 - 区间(承压面 -2.6):P06 的 0°/180° 是 -8.0..12.0(贯通,是臂身本体), - 80°/100° 是 -8.0..-0.0(**只比承压面多 2.6mm,这段就是凸台**), - 其余 20~160° 是 -8.0..-2.6(承压环本体,齐平)。 - - 因此整段全截面切会把 0/90 方向那条**贯通到 z=12 的承力桥**一起切断 - -> P05/P08 碎成 2 块(实测)。这不是"承压环是唯一连接", - 而是我的刀具没区分"桥"和"悬空料"。 - - 现在的做法(一刀,不做角度分区): - 承压面所在的 z=-REAR_SEAT..SEAT_TOP 段,腔口取 **SEAT_R = R35 = 外壁** - 的圆,把整圈挖到外壁,承压面因此变成**完整 R35 平圆盘**。 - - 关键:**SEAT_R 必须等于 R35**。只要 SEAT_R < R35,两者之间那圈环墙 - 就必然存在,且相对承压面凸起 (R35-SEAT_R) —— 那圈环墙就是用户说的 - "围挡"(P08 实测 1536.9mm3),不是残留而是我的取值必然产物。 - - SEAT_TOP 取 +1.0(越过电机尾端面 1mm):2026-10-04 那版取 0 时, - 半包开口处(Y93.0~95.8)会残留一截 0.052cm3 薄片脱空成碎块; - 抬到 +1.0 后五件全部单实体零碎片。 - - 承力改由**臂身本体**承担:band 段内 r36..58 有料的方位 P04 10/36、 - 其余四件各 15/36,其余方位靠 z>+1.0 的杯体连接,不存在整圈切断的通槽。 - (2026-10-04 那版"肩叉顶部没连上"就是通槽形态,务必不要重现。) - 【2026-10-05b 去掉 64.4 方腔,电机腔内壁恢复完整圆弧面】 - 此前 z=0..pcba_t 还叠了一层 `box(±32.2)` 方腔,理由是"PCB 是方的"。 - **该假设不成立**:实测电机 STEP 已把 PCB 体积含在本体轮廓内,不会超出 - 投影范围,所以方腔纯属多挖。它带来两个副作用(用户截图 P06/P07/P08 腔内观感): - 1. 方腔半宽 32.2 < 外壁 R35,在四个方位留下一圈 2.8mm 环形台肩; - 2. 方腔对角 45.5 > R35,四个角把外壁咬穿,形成四个凸角。 - 两者叠加使内壁呈"圆—方—圆"三段,与同族零件的整体圆弧语言不一致。 - 现改为**单一 R35 圆腔从承压面一直挖到座孔起点 BORE_Z0(=5.5)**, - 与其上的 R30.15 座孔正交相接,内壁成为两段同心正圆(仅一处正常台阶), - 承压面到外壁之间再无任何凸起或方角。P04 走自己那套刀具,本来就无方腔。 - """ - c = [cyl(BORE_R, BORE_Z0, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + front_pad), - # 承压面段 + 尾部过渡段:SEAT_R(=R35 外壁) 圆腔,从承压面一路挖到座孔起点。 - # 取满 R35 才能让承压面与外壁齐平成一个平面;取小于 R35 就会留下 - # 一圈凸起的"围挡"(见 docstring)。 - cyl(SEAT_R, -REAR_SEAT, BORE_Z0)] - return shape.cut(*[self.L(s) for s in c]) - - def rear_mount_holes(self, shape, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0): - """Cut the 4x rear mounting bolt holes into a proximal link body. - Matches the 4x M3 tapped radial slots on the motor rear endplate - (电机.STEP 实测):BCD55、相位 39.9°+90k,槽宽 2.5、径向可调约 ±1.25mm。 - 旧版误用 35.1x40.9 矩形孔距:对角两颗偏出螺纹槽 ~4.7mm 无处咬合 - (2026-10-02 用户指出"螺钉没有对齐",按 STEP 实测槽位修正)。 - """ - cuts = [] - for k in range(4): - a = math.radians(39.9 + 90.0 * k) - sx = 27.5 * math.cos(a) - sy = 27.5 * math.sin(a) - h = cyl(hole_d / 2.0, -t_plate - 1.0, 3.0, sx, sy) - cuts.append(self.L(h)) - if cb_d > 0 and cb_depth > 0: - cb = cyl(cb_d / 2.0, -t_plate - 1.0, -t_plate + cb_depth, sx, sy) - cuts.append(self.L(cb)) - return shape.cut(*cuts) - - def front_collar_pocket(self, shape, r_bore=30.25, z0=50.0, z1=56.5): - """Cut front locating collar pocket for the motor front rim.""" - p = cyl(r_bore, z0, z1) - return shape.cut(self.L(p)) - - def pinch(self, shape, fracs=(0.26, 0.58, 0.86), r_out=48.0, n=2): - """Radial M3 pinch screws through the boss wall onto the housing.""" - cuts = [] - for fr in fracs[:n]: - for a in (self.lug_phase, self.lug_phase + 180.0): - c = cyl(PINCH_D / 2.0, -1.0, r_out, 0, 0) - c = place(c, R(a, 0, 0, 1)) - c = place(c, T(0, 0, fr * COVER_Z)) - cuts.append(self.L(c)) - return shape.cut(*cuts) - - def hub_face(self): - return self.at(HUB_FACE_Z) - - def cut_hub(self, shape, depth=24.0, r_clear=32.0): - """Cut the motor-output replica into the driven hub. The mating plane sits at - motor-local z = HUB_FACE_Z, and the recesses open there and run into the driven - material (away from the motor), so that half B's pilot (0.60) and spigot (4.06) - land in them.""" - H = HUB_FACE_Z - # measured on the delivered half B: past the mating plane it is Ø35.6 for 1.4 mm - # then a Ø6.3 spigot to 4.06 - the nominal neck_depth of 1.0 is 0.4 too shallow, - # which showed up as a ~160 mm3 interference at every driven hub - cuts = [cyl(M["neck_d"] / 2.0, H - 0.05, H + 1.70), - cyl(M["centre_d"] / 2.0, H - 0.05, H + 5.05), - # probed on the delivered half: its Ø54 back face sits ~0.06 mm PAST the - # nominal plane, so the driven face gets a 0.35 deep, Ø54.6 relief. That is - # also the printed-part tolerance land the joint wants. - cyl(27.30, H - 0.05, H + 0.35)] - for a in HUB_M2_CLOCK: - u = M["m2_bcd"] / 2.0 * math.cos(a * D2R) - v = M["m2_bcd"] / 2.0 * math.sin(a * D2R) - cuts.append(cyl(M["m2_tap_d"] / 2.0, H - 0.05, H + M["m2_depth"], u, v)) - return shape.cut(*[self.L(s) for s in cuts]) - - def hub_island(self, shape, r=30.0, t=9.5): - """Solid disc at the joint centre (the lightening dish must not break into the - interface recesses), then the output replica is cut into it. Starts exactly on the - mating plane: half B's Ø54 body ends there, so any overlap is a hard clash.""" - shape = shape.fuse(self.L(cyl(r, HUB_FACE_Z, HUB_FACE_Z + t))) - shape = self.cut_hub(shape) - # two arc shrouds over the clamp band: the joint reads as a closed drum, and the - # sectors are placed so the M3 lug bolts on the joint's local +-X stay reachable - for (a0, a1) in ((34.0, 146.0), (214.0, 326.0)): - shape = shape.fuse(self.L(sector(38.0, 31.6, a0, a1, FLANGE_Z + 1.4, HUB_FACE_Z))) - return shape - - def place_hardware(self, half, clamp, tag): - """Place the existing coupling halves + split clamp for this joint. - - The delivered half is authored with z=0 at the motor flange face and spans - z -3.00..8.06. B = flip 180 deg about the local X axis, then shift by - 2*PART_Z, which puts its flat back face exactly on the hub mating plane - (motor-local 65.00) - verified against the measured feature bounds. - """ - tf = trs(self.at(FLANGE_Z), self.xdir, self.d) - hA = place(half, tf) - hb = place(half, R(180, 1, 0, 0)) - hb = place(hb, T(0, 0, 2 * C["part_z"])) - hB = place(hb, tf) - cA = place(clamp, tf) - cB = place(place(clamp, R(180, 0, 0, 1)), tf) # the two C pieces are one ring split - # along a diametral plane: 180 deg about the JOINT axis, so both keep the same - # axial span 56.602..64.402 and sit on opposite sides of the band. - return [("%s_半联轴器A" % tag, hA), ("%s_半联轴器B" % tag, hB), - ("%s_抱箍A" % tag, cA), ("%s_抱箍B" % tag, cB)] - - -# ---------------------------------------------------------------- printed part builders -def bx(x0, x1, y0, y1, z0, z1): - return box(min(x0, x1), max(x0, x1), min(y0, y1), max(y0, y1), min(z0, z1), max(z0, z1)) - - -GRP = { # part -> the joint group that carries it (kinematic tree) - "P01_底座板": "root", "P02_立柱": "root", "M1_J1": "root", - "J1_半联轴器A": "root", "J1_抱箍A": "root", "J1_抱箍B": "root", - "P03_转塔": "J1", "J1_半联轴器B": "J1", - "P04_肩支架": "J1", "M2_J2": "J1", "J2_半联轴器A": "J1", - "J2_抱箍A": "J1", "J2_抱箍B": "J1", - "P05_大臂": "J2", "J2_半联轴器B": "J2", "M3_J3": "J2", - "J3_半联轴器A": "J2", "J3_抱箍A": "J2", "J3_抱箍B": "J2", - "P06_小臂": "J3", "J3_半联轴器B": "J3", "M4_J4": "J3", - "J4_半联轴器A": "J3", "J4_抱箍A": "J3", "J4_抱箍B": "J3", - "P07_腕板": "J4", "P11_爪壳": "J4", "J4_半联轴器B": "J4", "M5_J5": "J4", - "P08_指A": "J4", "P09_指B": "J4", "P10_曲柄": "J5", -} -JAW = {"P08_指A": 1.0, "P09_指B": -1.0} # sign of the jaw travel along the J5 local +X - - -def explode_vec(p, JS, cents, gmean): - """Explosion offset: coupling hardware separates along its joint axis, everything - else moves radially out of the assembly centroid.""" - nm = p["name"] - c = cents[nm] - for jid in ("J1", "J2", "J3", "J4"): - if nm.startswith(jid + "_半联轴器B"): - return [JS[jid].d[i] * 24.0 for i in range(3)] - if nm.startswith(jid + "_半联轴器A") or nm.startswith(jid + "_抱箍"): - k = -26.0 if "抱箍" in nm else -16.0 - return [JS[jid].d[i] * k for i in range(3)] - v = [c[i] - gmean[i] for i in range(3)] - m = math.sqrt(sum(x * x for x in v)) or 1.0 - return [v[i] / m * 34.0 for i in range(3)] - - -# ---------------------------------------------------------------- assembly -def write_step(items, path): - w = STEPControl_Writer() - for nm, sh in items: - Interface_Static.SetCVal_s('write.step.product.name', nm) - w.Transfer(sh.wrapped, STEPControl_StepModelType.STEPControl_AsIs) - w.Write(path.replace("/", "\\")) - return os.path.getsize(path) - - -import threading -import time -from concurrent.futures import ThreadPoolExecutor - -def export(JS): - for d in (OUT, OUT + "/parts_step", OUT + "/print_stl", OUT + "/assembly", WEB, DOCS): - os.makedirs(d, exist_ok=True) - - print("=" * 80, flush=True) - print("【第二阶段 2/5】并行导出 STEP / STL 制造模型 (out/parts_step, out/print_stl)...", flush=True) - print("=" * 80, flush=True) - - # 1. 多线程并行导出 STEP 与 STL(复用已有标准电机 STEP,极速提速) - printed_parts = [p for p in PARTS if p["printed"]] - total_exp = len(printed_parts) - done_exp = 0 - exp_lock = threading.Lock() - - def export_one_part(p): - nonlocal done_exp - t0 = time.time() - fn = "%s/%s.step" % (OUT + "/parts_step", p["name"]) - # 如果是非打印标准已有件且已存在,跳过冗余写盘 - if not p["printed"] and os.path.exists(fn) and os.path.getsize(fn) > 1000: - pass - else: - write_step([(p["name"], p["shape"])], fn) - if p["printed"]: - stl = "%s/%s.stl" % (OUT + "/print_stl", p["name"]) - cq.exporters.export(cq.Workplane(obj=p["shape"]), stl, exportType="STL") - with exp_lock: - done_exp += 1 - dt = time.time() - t0 - print(f" [文件导出 {done_exp}/{total_exp}] {p['name']}.step / .stl 导出完成 ({dt:.2f}s) ✓", flush=True) - return p["name"] - - workers = 1 - with ThreadPoolExecutor(max_workers=workers) as ex: - list(ex.map(export_one_part, PARTS)) - - print(f"-> 制造模型导出全部完成!已保存在 {OUT}/parts_step 与 {OUT}/print_stl\n", flush=True) - - # 2. 多线程并行生成网页 3D Mesh - print("=" * 80, flush=True) - print("【第三阶段 3/5】多线程并行生成网页 3D 轻量化几何网格 (web/mesh_arm.json)...", flush=True) - print("=" * 80, flush=True) - - done_mesh = 0 - mesh_lock = threading.Lock() - total_parts = len(PARTS) - - def mesh_one_part(p): - nonlocal done_mesh - t0 = time.time() - key = "motor" if p["name"].startswith("M") else ("coupl" if "联轴器" in p["name"] or "抱箍" in p["name"] else p["name"]) - defl = 0.85 if p["name"].startswith("M") else (0.20 if key == "coupl" else 0.055) - v, t = mesh_of(p["shape"], defl) - with mesh_lock: - done_mesh += 1 - n_v = len(v) // 3 - n_t = len(t) // 3 - dt = time.time() - t0 - print(f" [网格细分 {done_mesh:2d}/{total_parts}] {p['name']:<14} 顶点: {n_v:5d} | 面片: {n_t:5d} ({dt:.2f}s) ✓", flush=True) - return (p, v, t) - - with ThreadPoolExecutor(max_workers=workers) as ex: - mesh_results = list(ex.map(mesh_one_part, PARTS)) - - parts = [] - for idx, (p, v, t) in enumerate(mesh_results): - parts.append(dict(name=p["name"], cn=p["cn"], v=v, t=t, color=p["color"], - printed=p["printed"], vol=round(p["vol"], 0), note=p["note"], - grp=GRP.get(p["name"], "root"), jaw=JAW.get(p["name"], 0.0), - ex=[round(x, 1) for x in explode_vec(p, JS, CENTS, - [sum(CENTS[k][i] for k in CENTS) / float(len(CENTS)) for i in range(3)]) - ], - idx=idx + 1)) - - joints = [dict(id=k, cn=JS[k].cn, rear=JS[k].rear, d=JS[k].d, xdir=JS[k].xdir, hub=JS[k].hub_face()) - for k in JS] - d3 = lambda a, b: math.sqrt(sum((a[i] - b[i]) ** 2 for i in range(3))) - json.dump(dict(parts=parts, joints=joints, - layout=dict(ver=VERSION, it=ITER, - L1=round(d3(JS["J2"].rear, JS["J4"].rear), 1), - L2=round(d3(JS["J4"].rear, JS["J5"].rear), 1)), - measured=dict(M=M, C=C, HUB_FACE_Z=HUB_FACE_Z, CLAMP_SPAN=list(CLAMP_SPAN))), - open(WEB + "/mesh_arm.json", "w", encoding="utf-8"), ensure_ascii=False) - bom = [dict(件号=p["name"], 名称=p["cn"], 类型="3D打印" if p["printed"] else "已有/电机", - 数量=1, 体积cm3=round(p["vol"] / 1000.0, 1), - 质量g_PLA=round(p["vol"] * 1.24e-3, 1), 包络=p["bb"], 说明=p["note"]) for p in PARTS] - json.dump(bom, open(DOCS + "/bom.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1) - print(f"-> 3D网格与BOM清单已保存至 web/mesh_arm.json 与 docs/bom.json ✓\n", flush=True) - return bom - - -def collide(): - ps = [p for p in PARTS if p["vol"] > 1.0] - pairs = [] - MOUNT_PAIRS = { - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "M1_J1"), ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M1_J1"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "M2_J2"), ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M2_J2"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "M3_J3"), ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M3_J3"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "M4_J4"), ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M4_J4"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "M5_J5"), ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M5_J5"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "M6_J6"), ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "M6_J6"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P01_B_立柱中段_三角形30%_墙4底5_竖打"), ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "P01_C_立柱上段_三角形30%_墙4底5_竖打"), - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "P02_Center_底座中核心_螺旋体30%_墙5底6_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "P03_肩吊臂_螺旋体35%_墙5底5_竖打"), - } - for i in range(len(ps)): - for j in range(i + 1, len(ps)): - a, b = ps[i], ps[j] - if a["name"].startswith("M") and b["name"].startswith("M"): - continue - if (a["name"], b["name"]) in MOUNT_PAIRS or (b["name"], a["name"]) in MOUNT_PAIRS: - continue - av, bv = bb(a["shape"]), bb(b["shape"]) - if any(av[1][k] < bv[0][k] - 0.05 or bv[1][k] < av[0][k] + 0.05 for k in range(3)): - continue - pairs.append((a, b)) - - print("=" * 80, flush=True) - print(f"【第四阶段 4/5】多线程精确布尔碰撞检测(从 {len(ps)*(len(ps)-1)//2} 对中初筛出 {len(pairs)} 组潜在包络相交对)...", flush=True) - print("=" * 80, flush=True) - - done_pair = 0 - pair_lock = threading.Lock() - - def check_pair(pair): - nonlocal done_pair - a, b = pair - t0 = time.time() - v = common_vol(a["shape"], b["shape"]) - with pair_lock: - done_pair += 1 - status = f"干涉 {v:.1f} mm³ ❌" if v > 2.0 else "零干涉 (0.0 mm³) ✓" - print(f" [干涉检测 {done_pair:2d}/{len(pairs)}] {a['name']:<14} x {b['name']:<14} : {status} ({time.time()-t0:.3f}s)", flush=True) - if v > 2.0: - try: - c = BRepAlgoAPI_Common(a["shape"].wrapped, b["shape"].wrapped) - c.SetRunParallel(True) - c.Build() - cb = cq.Shape(c.Shape()) - lo, hi = bb(cb) - loc = "@[%.0f %.0f %.0f..%.0f %.0f %.0f]" % ( - lo[0], lo[1], lo[2], hi[0], hi[1], hi[2]) - except Exception: - loc = "?" - return (a["name"], b["name"], round(v, 1), loc) - return None - - rows = [] - for pair in pairs: - res = check_pair(pair) - if res is not None: - rows.append(res) - - rows.sort(key=lambda r: -r[2]) - json.dump([r[:3] for r in rows], open(DOCS + "/collision.json", "w", encoding="utf-8"), - ensure_ascii=False) - if len(rows) == 0: - print(f"\n-> 静态碰撞干涉检测通过: 0 对干涉!全部 {len(pairs)} 组接触面对零碰撞!\n", flush=True) - else: - print(f"\n-> 发现 {len(rows)} 对干涉!\n", flush=True) - return rows - - -def axial_extent(j, shape): - lo, hi = bb(shape) - vals = [] - for i in (0, 1): - for k in range(3): - p = [lo[0], lo[1], lo[2]] - p[k] = hi[k] if i else lo[k] - vals.append(sum((p[q] - j.rear[q]) * j.d[q] for q in range(3))) - return min(vals), max(vals) - - -def verify_interfaces(half, clamp, JS): - """Numeric self-check of the derived joint stack, before trusting the renders.""" - rows = [] - for k, j in JS.items(): - if k == "J5": - continue - parts = dict(j.place_hardware(half, clamp, k)) - a = axial_extent(j, parts["%s_半联轴器A" % k]) - b = axial_extent(j, parts["%s_半联轴器B" % k]) - c = axial_extent(j, parts["%s_抱箍A" % k]) - hub = j.at(HUB_FACE_Z) - rows.append(dict(jid=k, halfA=[round(x, 3) for x in a], halfB=[round(x, 3) for x in b], - clamp=[round(x, 3) for x in c], hub_face=round(HUB_FACE_Z, 3), - spigot_proud=round(b[1] - HUB_FACE_Z, 3), - clamp_clear=round(c[0] - COVER_Z, 3))) - return rows diff --git a/.bak/arm_model.py.bak20261005 b/.bak/arm_model.py.bak20261005 deleted file mode 100644 index c24728d..0000000 --- a/.bak/arm_model.py.bak20261005 +++ /dev/null @@ -1,1848 +0,0 @@ -# -*- coding: utf-8 -*- -"""机器人手臂深度圆角优化版 V2:彻底消除所有棱角 -针对用户反馈的三大问题区域进行深度优化: -1. 蓝色零件(P04肩叉)顶部方形突起 → 全圆角处理 -2. 绿色/青色零件(P05/P08)底部和侧面棱角 → Loft外轮廓圆角 -3. 所有连接处的尖锐过渡 → 大圆角平滑过渡 -4. 橙色零件(P06/P07)所有方形边缘 → 完整圆角系统 -""" -import math -import os -import sys -import time - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -import arm_build as AB -from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS) -from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector, - FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK, - BORE_R, CB_R, PCB_W, D2R, T, R, PARTS) -import human_layout as HL -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1, - Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6, - Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1, - X_FORK_GAP, X_BAY_EXT, - Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, - Y_M6, Y_CHEST, X_CHEST, FOOT_H) - -VERSION = os.environ.get("ARMVER", "v2") -ITER = int(os.environ.get("ARMITER", "3")) -AB.VERSION = VERSION -AB.ITER = ITER - -Y_MID = Y_AXIS -BOSS = COVER_Z + 0.2 -DISC_R = 40.0 -SLEEVE_R = 35.0 - -# 深度圆角优化参数 -CLEARANCE_GAP = 2.0 # 关节间隙 2.0mm -FILLET_TINY = 1.5 # 微小圆角(螺孔边缘) -FILLET_SMALL = 3.0 # 小圆角(功能面) -FILLET_MEDIUM = 6.0 # 中圆角(结构过渡) -FILLET_LARGE = 10.0 # 大圆角(主要轮廓) -FILLET_XLARGE = 15.0 # 超大圆角(视觉主导区域) -CHAMFER_EDGE = 2.0 # 边缘倒角 - - -def Xcyl(r, x0, x1, py, pz): - return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz)) - - -def Xplate(pts_yz, x0, x1): - prof = [(-p[1], p[0]) for p in pts_yz] - return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0)) - - -def XB(a0, a1, y0, y1, z0, z1): - return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1) - - -# ------------------------------------------------------------------ 减重(镂空)-------- -# 三个手法件(P04/P06/P07)在关节根部都留着一大块"实心过渡料":壁厚最深到 15~17.5mm, -# 而 gyroid 填充只有 25~35%,等于用整块实心去抗一个安全系数 19~122 的截面。 -# 下面两个 helper 把这块料掏掉,规则与 P03 的"帐篷形减重窗"一致: -# 1) 不留封闭空腔 —— 减重腔要么朝上敞开,要么是有进出口的窗,打印不憋气、不积料; -# 2) 斜面自支撑 —— 斜面与水平面夹角 >= 52°(与竖直面夹角 <= 38°),免支撑; -# 3) 内角 R4 圆角 —— 挖出来的洞会把圆截面变成尖角,Kt 从 ~1.2 抬到 ~2.5, -# 圆角把应力集中压回去; -# 4) 只吃"距表面 > 8mm"的厚料,四周至少留 8mm 壁 —— 受力纤维一根不碰。 - -HOLLOW_WALL = 8.0 # 减重腔与原外表面的最小间距(= 留下的最小壁厚) -HOLLOW_FILLET = 4.0 # 减重腔内角圆角,与 P03 竖板减重窗一致 - -# 三处减重腔的开度。这三个件都是绕 Y 轴弯,弯矩在 X 方向拉压,距中性面越远的料 -# 贡献越大,所以"少挖、挖窄"是恢复强度最省料的做法。 -# 数值由 scratch/tune_hollow.py 按"槽区最差安全系数 >= 无槽基线的 95%"扫出来: -# P04 档1 X23..35 Y102..154 Z>=525 -> 95% 基线,保留 69% 的减重量 -# P07 档1 X−3..43 Y103..153 Z>=255 -> 96% 基线,保留 76% -# P06 档1 抬高窗底到 Z362(不是收窄) -> 100% 基线,保留 56% -# 窗底原来压在 Z358,而该处截面安全系数最低;抬高 4mm 比横向收窄 8mm 划算得多。 -P04_CH = dict(x0=23.0, x1=35.0, y0=102.0, y1=154.0, z_floor=525.0) # 肩叉立柱 -P07_CH = dict(x0=-3.0, x1=43.0, y0=103.0, y1=153.0, z_floor=255.0) # 小臂顶板 -P06_WIN_XZ = [(3.0, 362.0), (37.0, 362.0), (37.0, 371.0), - (28.0, 374.0), (12.0, 374.0), (3.0, 371.0)] # 大臂过渡块 -P06_HOLLOW_WIN = False # 2026-10-03 起关闭:拱形窗正面看是贯通黑洞,不雅观(详见 p_upper_bone 5c) - - -def hollow_channel(x0, x1, y0, y1, z_floor, z_open, tag="", fil=HOLLOW_FILLET): - """朝上敞开的减重槽:从 z_open 往下切到 z_floor,槽底水平(朝上受支撑)、侧壁竖直。 - - 竖打(模型 Z = 打印 Z)下这个形状是零支撑的:槽底是水平面、槽壁全是竖直面, - 顶部完全敞开因而不构成封闭空腔。切完该件变成 U 形槽钢而不是实心料: - 抗弯靠两侧 X 向腹板(正好落在弯矩拉压区),中间中性层掏空几乎不损失 I。 - - x0/x1/y0/y1 用世界坐标;z_open 取一个保证贯通顶面的值。 - """ - tool = bx(x0, x1, y0, y1, z_floor, z_open) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Z").fillet(fil).val() - except Exception as e: - print(" [减重槽] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重槽] %s: X%.1f..%.1f Y%.1f..%.1f 自 Z%.1f 向上敞开 (四周留壁 >=%.0fmm)" - % (tag, x0, x1, y0, y1, z_floor, HOLLOW_WALL), flush=True) - return tool - - -def hollow_tent_window(pts_xz, y0, y1, tag="", fil=HOLLOW_FILLET): - """帐篷形减重窗:XZ 平面画轮廓,沿 Y 双向切穿。 - - 轮廓下宽上窄(拱形)——窗顶上方的料形成一个尖顶,斜面与水平夹角 >= 52° 自支撑, - 只有顶部一小段平顶需要桥接(<=20mm,与 P03 同)。轮廓在 X 方向不能吃满整个截面, - 两侧必须留墙,否则该高度上零件会被切断、失去单实体。 - """ - # XZ 工作平面的法线是 -Y,extrude 会朝 -Y 走,所以起点放在 y1 才能覆盖 y0..y1 - tool = (cq.Workplane("XZ", origin=(0, y1, 0)).polyline(pts_xz).close() - .extrude(y1 - y0).val()) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Y").fillet(fil).val() - except Exception as e: - print(" [减重窗] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重窗] %s: XZ 轮廓 %d 点, 沿 Y %.1f..%.1f 切穿 (帐篷形, 斜面自支撑)" - % (tag, len(pts_xz), y0, y1), flush=True) - return cq.Shape(tool.wrapped) - - -# 各关节"电机前端夹紧"统一配方(J2/J3/J4/J5 通用,J1 由 P03 自己那套 S04 凸台实现)。 -# 电机前端盖外端面:Y 轴关节(J2/J4/J5)在 Y_COVER=156,压盖朝 +Y 生长; -# Z 轴关节(J3,roll 电机,轴向 -Z)在 Z_R-56,压盖朝 -Z 生长(-Z 侧是从动件)。 -# 压盖背面与之 0.0 间隙面压,螺钉只提供压紧力。 -# 沉孔相位按【关节局部相位】定义(BCD55,72.2°+90k):换算到全局后 Y 轴关节是 -# 17.8°+90k(与 P03→M1 的 S04 一致),Z 轴关节是同一组数落在全局 (x,y) 平面, -# 两种轴向的物理指向完全一致 —— 全机同一套孔位约定,改一处必须同步另一处。 -CLAMP_BCD = 27.5 -CLAMP_CLOCK = (17.8, 107.8, 197.8, 287.8) # Y 轴关节的全局 (x,z) 相位 -CLAMP_CLOCK_LOCAL = tuple((360.0 - a) % 360.0 for a in CLAMP_CLOCK) # = 72.2+90k -CLAMP_ROOT = 0.5 # 根部薄环厚度:与前端定位环前端面共面,保证熔接成一体 -CLAMP_T = 5.0 # 压盖本体厚度 -CLAMP_CLR = 1.0 # 从动件让位间隙:与 Y 轴关节 hub_gap=6.0 同一个道理 -CLAMP_BACKOFF = CLAMP_T + CLAMP_CLR - CLAMP_ROOT # =5.5 从动件毂面整体后移量 - - -def motor_front_clamp(px, pz, plate_t=CLAMP_T, tag="", axis="Y", py=0.0, z_face=None): - """电机前端压盖:把"只被后端安装板单面锁死"改成前后双向面压夹紧。 - - 原设计里每个关节的电机都只有后端板 4xM3x10(S02)锁在尾端面上,前端盖完全裸露, - 大扭矩下电机绕自身轴在 Ø60.3 座孔里点头/翘起。这里在电机前端盖外端面加一枚 - 与后端板同径的 R35 压盖,用 4xM3x8(BCD55)在前端盖上再夹一次 —— 电机被前后两块 - 同心 Ø60.3 端面双向夹死,倾斜自由度归零;夹紧力全部走面压,螺钉不参与定位。 - - 两个轴向共用同一套尺寸,只换刀具体系(不重复实现任何半径/相位): - axis="Y"(J2/J4/J5):关节轴过 (px, pz),前端盖外端面在 Y_COVER,压盖朝 +Y 长; - axis="Z"(J3 roll,轴向 -Z):关节轴过 (px, py),前端盖外端面在 z_face,压盖朝 -Z 长。 - - 局部坐标(s 从前端盖外端面起算,s>0 一律"背离电机",两个轴向同号): - - 压盖 R35,s 从 -CLAMP_ROOT(与前端定位环前端面共面,保证熔接成一体)到 plate_t; - - 背面精确落在 s=0,不让根部那 0.5mm 啃到端盖 R30.15 的外圆; - - Ø37 中央让位孔(R18.5)让开从动件的直锁凸台 R17.8 与 3xS01 输出螺钉头; - - 4xM3x8:Ø3.4 通孔 + Ø6.0 沉孔深 3.0(自前端面沉到 s=2),圆柱头全沉入, - 头面压孔口台阶,中间 2.0mm 压盖本体受压。 - """ - if axis == "Y": - def C(r, s0, s1, du=0.0, dv=0.0): - return Ycyl(r, Y_COVER + s0, Y_COVER + s1, px + du, pz + dv) - elif axis == "Z": - def C(r, s0, s1, du=0.0, dv=0.0): - return cyl(r, z_face - s1, z_face - s0, px + du, py + dv) - else: - raise ValueError("motor_front_clamp: axis 只能是 Y 或 Z,收到 %r" % (axis,)) - - s0, s1 = -CLAMP_ROOT, plate_t - fp = C(35.0, s0, s1) - fp = fp.cut(C(18.5, s0 - 0.5, s1 + 0.5)) # Ø37 中央让位孔 - fp = fp.cut(C(30.15, s0 - 0.5, 0.0)) # Ø60.3 让位槽 - for deg in CLAMP_CLOCK_LOCAL: - rad = math.radians(-deg) # 全局相位 = -局部相位 - fu, fv = CLAMP_BCD * math.cos(rad), CLAMP_BCD * math.sin(rad) - fp = fp.cut(C(3.0, 2.0, s1 + 0.5, fu, fv)) # Ø6.0 沉孔 - fp = fp.cut(C(1.7, s0 - 0.5, 2.0, fu, fv)) # Ø3.4 通孔 - where = ("Y%.1f..%.1f" % (Y_COVER - CLAMP_ROOT, Y_COVER + plate_t)) if axis == "Y" \ - else ("Z%.1f..%.1f" % (z_face - plate_t, z_face + CLAMP_ROOT)) - print(" [前端压盖] %s: R35 %s, 4xM3x8 BCD55@%s (%s 轴)" - % (tag, where, "/".join("%.1f" % d for d in CLAMP_CLOCK), axis), flush=True) - return fp - - -def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0): - """优化直连法兰:增加导向倒角""" - disc = cyl(r_disc, 56.5, z_out) - boss = cyl(17.8, 54.942, 56.5) - chamfer_cone = cyl(18.5, 54.5, 54.942) - shape = shape.fuse(j.L(disc)).fuse(j.L(boss)).cut(j.L(chamfer_cone)) - shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, z_out + 40.0, u, v) - shape = shape.cut(j.L(th)).cut(j.L(cb)) - return shape - - -# ========== 躯干部分 ========== -# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ========== -X_COL = -115.0 -Y_COL = Y_AXIS # 128.0 -Z_LOCK = 463.0 # 横向防脱锁紧螺钉高度(立柱后壁 → 榫头,M4x16 自攻) - - -def p_chest(): - """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱): - 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方; - 2. 顶部接收 P03 43.4x43.4x15mm 定位榫头(44x44 R4 内腔)、4x 竖向安装孔 + 1x 横向防脱锁紧孔 - (全部 Ø3.9 直孔:M4x16 从外部直接拧入自攻成型,无过孔/无沉孔/无预埋); - 3. 底部 50x50x12mm 阶梯榫头插入 P02 榫座:环形台肩(66->50)面承载轴向力,槽壁全周承载剪切; - 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); - 5. 外棱角全部采用 R=6mm 圆角。 - """ - z_bot = 22.0 - z_top = Z_CHEST1 # 470.0 - - col = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot)) - .rect(66.0, 66.0) - .extrude(z_top - z_bot) - .edges("|Z").fillet(6.0)).val() - s = cq.Shape(col.wrapped) - - cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot + 10.0)) - .rect(44.0, 44.0) - .extrude(z_top - z_bot + 1.0) - .edges("|Z").fillet(4.0)).val() - s = s.cut(cav) - - # 底部阶梯榫头:插入 P02 榫座后环形台肩坐实,轴向力走面压而非 M5 螺纹 - tenon = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(50.0, 50.0).extrude(z_bot - 10.0) - .edges("|Z").fillet(4.0)).val() - s = s.fuse(tenon) - - # 底部 M5 夹紧孔(贯通榫头):X 向非对称 (−20/+16) 与榫座孔位配对防反装/防转 90° - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - s = s.cut(cyl(2.75, 9.99, z_bot + 15.0, xx, yy)) - # 顶部 4x 安装孔:统一 Ø3.9 直孔(M4x16 直接拧入自攻成型;只打这一个孔,无过孔/无沉孔),深 20mm - # 位于 66x66 顶环 11mm 实体壁内(内腔 22 → 外壁 33),避开 44x44 内腔与 R4 内腔圆角; - for xx in (X_COL - 27.5, X_COL + 27.5): - for yy in (Y_COL - 13.5, Y_COL + 13.5): - s = s.cut(cyl(1.95, z_top - 20.0, z_top + 0.01, xx, yy)) - # 横向防脱锁紧孔(后壁 → 榫头):Ø3.9 贯穿 11mm 后壁 + 榫头同径(M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - 34.0, X_COL - 20.0, Y_COL, Z_LOCK)) - - w_bot = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 105.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_bot) - - w_top = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 375.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_top) - return s - - -def split_p01_poka_yoke(p01): - """拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印): - 带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁, - 单边配合间隙 0.5)、以及统一 Ø3.9 直孔的分段对锁孔(M4x16 从外面直接拧入自攻成型, - 母口壁与公榫壁同径,无过孔/沉孔/预埋,前后两面对称各一颗); - 孔位相对立柱中心 −16 / +22 非对称(物理防反装防呆);本体 3D 凹刻朝向标识。 - """ - z_bot = 22.0 - z_s1 = 172.0 - z_s2 = 321.0 - z_top = 470.0 - h_lap = 16.0 - - # 非对称对锁孔位(相对立柱中心偏心 -16mm 与 +22mm,偏心差 6mm,反装无法穿入螺栓) - xx_bolts = (X_COL - 16.0, X_COL + 22.0) - - # 分段对锁孔:统一 Ø3.9 直孔(M4x16 从外面直接拧入自攻成型;母口壁与公榫壁同径, - # 没有过孔/沉孔等其它孔),前后两面对称;杆尖伸过榫壁的一段在内腔侧留 Ø4.5 让位。 - def _seam_holes(part, xx, z): - return (part.cut(Ycyl(1.95, Y_COL - 33.6, Y_COL - 21.4, xx, z)) - .cut(Ycyl(1.95, Y_COL + 21.4, Y_COL + 33.6, xx, z)) - .cut(Ycyl(2.25, Y_COL - 21.7, Y_COL - 15.5, xx, z)) - .cut(Ycyl(2.25, Y_COL + 15.5, Y_COL + 21.7, xx, z))) - - # --- P01_A (下段) --- - 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(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() - tongue1 = cq.Shape(tongue1_outer.wrapped).cut(tongue1_cav) - p01_a = p01_a_base.fuse(tongue1) - - z_bolt1 = z_s1 + h_lap / 2.0 # 180.0 - for xx in xx_bolts: - p01_a = _seam_holes(p01_a, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - - # Engrave the front (-Y) flat face, reading VERTICALLY bottom-to-top along - # the column (user request): local +X = world +Z, local +Y = world -X, so - # the trailing "▶" glyph renders pointing world-up (+Z). - front_label_plane_a = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 120.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_a = (cq.Workplane(front_label_plane_a) - .text("P01-A TOP ▶", 9.0, -0.8)).val() - p01_a = p01_a.cut(cq.Shape(label_a.wrapped)) - - # --- P01_B (中段) --- - cut_below_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s1) - 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.9)).val() - p01_b_base = p01_b_base.cut(pocket1) - - tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2)) - .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() - tongue2 = cq.Shape(tongue2_outer.wrapped).cut(tongue2_cav) - p01_b = p01_b_base.fuse(tongue2) - - z_bolt2 = z_s2 + h_lap / 2.0 # 329.0 - for xx in xx_bolts: - p01_b = _seam_holes(p01_b, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - p01_b = _seam_holes(p01_b, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_b = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 240.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_b = (cq.Workplane(front_label_plane_b) - .text("P01-B TOP ▶", 9.0, -0.8)).val() - p01_b = p01_b.cut(cq.Shape(label_b.wrapped)) - - # --- P01_C (上段) --- - 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.9)).val() - p01_c = p01_c_base.cut(pocket2) - for xx in xx_bolts: - p01_c = _seam_holes(p01_c, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_c = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 390.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_c = (cq.Workplane(front_label_plane_c) - .text("P01-C TOP ▶", 9.0, -0.8)).val() - p01_c = p01_c.cut(cq.Shape(label_c.wrapped)) - - return p01_a, p01_b, p01_c - - -def make_p02_poka_yoke(): - """将 P02 拆分为三件模块化组件(均严格 <= 195x190mm,适配 200x200mm 打印床): - 1. P02_Center (底座中核心): 195x190x70mm,完整容纳立柱套筒与前后三角斜撑筋,核心应力区无接缝; - 2. P02_Front (前向力臂): 145x190x22mm,宽跨距 104mm 双榫头 + 预埋 M5 螺母槽; - 3. P02_Rear (后向力臂): 45x190x22mm,窄跨距 80mm 双榫头(跨距差达 24mm 物理防呆,绝不混装)。 - """ - z0 = 0.0 - z1 = 22.0 - y_width = 190.0 - - f_tenon_w = 44.0 - f_tenon_y_span = 52.0 - r_tenon_w = 32.0 - r_tenon_y_span = 40.0 - - # 1. P02_Center (X = -195.0 .. 0.0, 195x190mm) - x_c_min = -195.0 - x_c_max = 0.0 - x_c_len = x_c_max - x_c_min - x_c_mid = (x_c_min + x_c_max) / 2.0 - - c_plate = (cq.Workplane("XY", origin=(x_c_mid, Y_COL, z0)) - .rect(x_c_len, y_width).extrude(z1) - .edges("|Z and X").fillet(8.0) - .edges(">Z").fillet(2.0)).val() - p02_c = cq.Shape(c_plate.wrapped) - - # Socket collar (Z: 22..70mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0).extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0).edges(">Z").chamfer(2.0)).val() - p02_c = p02_c.fuse(cq.Shape(collar_outer.wrapped)) - - # Front & Rear triangular gusset ribs - f_pts = [(-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0)] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(f_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(f_rib.wrapped)) - - r_pts = [(-155.0, z1), (-195.0, z1), (-195.0, z1 + 14.0), (-155.0, 70.0)] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(r_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(r_rib.wrapped)) - - # Front wire guide - wg_pts = [(-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5)] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(wg_pts).close().extrude(9.0, both=True)).val() - p02_c = p02_c.cut(wire_guide) - - # Column socket — stepped mortise: upper 66.6 bore for the 66 box walls, - # lower 50.6 recess (floor 0.6 low) for the 50x50 bottom tenon; the recess - # rim at z=22 is the axial seat, so column weight bears on face, not threads. - socket_upper = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(66.6, 66.6).extrude(z_collar_top - z1 + 1.0) - .edges("|Z").fillet(6.3)).val() - p02_c = p02_c.cut(socket_upper) - socket_lower = (cq.Workplane("XY", origin=(X_COL, Y_COL, 9.4)) - .rect(50.6, 50.6).extrude(z1 - 9.4) - .edges("|Z").fillet(4.3)).val() - p02_c = p02_c.cut(socket_lower) - - # Vertical M5 clamp bolts (offsets match the asymmetric column tenon holes: - # X −20/+16 blocks 180° flip, the ±18 Y span blocks 90° rotation). - # Threads engage the tenon 15mm — clamp only, axial load rides on the seat. - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - p02_c = p02_c.cut(cyl(2.75, -0.01, 10.5, xx, yy)).cut(cyl(5.0, -0.01, 6.0, xx, yy)) - - # Center clamp slots - for cy in (Y_COL - 75.0, Y_COL + 75.0): - plane = cq.Plane(origin=(x_c_mid, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_c = p02_c.cut(slot) - - p02_c = p02_c.cut((cq.Workplane("XY", origin=(X_COL, Y_COL - 70.0, -0.01)).rect(28.0, 80.0).extrude(6.0)).val()) - - # Front mortise pockets - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - front_mortise = (cq.Workplane("XY", origin=(-10.0, yy, 4.8)) - .rect(21.0, f_tenon_w + 0.8).extrude(12.6).edges("|Z and X").fillet(3.0)).val() - p02_c = p02_c.cut(rear_mortise) - p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -185.0, yy)) - - # Debossed text on P02_Center top face. The mid-plate line (y=Y_COL) is - # hidden behind the column collar and the gusset ribs, so both labels sit on - # the flat -Y side strip (y = Y_COL-68) where they stay in full view from the - # label side: REAR on the rear half, FRONT on the front half, clear of the - # clamp slot (x -101..-94) and the M5 screw counterbores. - txt_c_f = (cq.Workplane("XY", origin=(-49.0, Y_COL - 68.0, z1)).text("▲ FRONT (前向) ▲", 9.0, -0.8)).val() - txt_c_r = (cq.Workplane("XY", origin=(-146.0, Y_COL - 68.0, z1)).text("▲ REAR (后向) ▲", 9.0, -0.8)).val() - p02_c = p02_c.cut(cq.Shape(txt_c_f.wrapped)).cut(cq.Shape(txt_c_r.wrapped)) - - # 2. P02_Front (X = 0.0 .. 145.0, 145x190mm) - x_f_len = 145.0 - x_f_mid = 72.5 - f_plate = (cq.Workplane("XY", origin=(x_f_mid, Y_COL, z0)) - .rect(x_f_len, y_width).extrude(z1) - .edges("|Z and >X").fillet(16.0).edges(">Z").fillet(2.0)).val() - p02_f = cq.Shape(f_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(120.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_f = p02_f.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-9.5, yy, 5.25)) - .rect(20.0, f_tenon_w - 0.2).extrude(11.5).edges("|Z and Z").fillet(2.0)).val() - p02_r = cq.Shape(r_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(-215.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_r = p02_r.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * r_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-185.5, yy, 5.25)) - .rect(20.0, r_tenon_w - 0.2).extrude(11.5).edges("|Z and >X").fillet(2.5).edges(">X").chamfer(1.5)).val() - p02_r = p02_r.fuse(cq.Shape(tongue.wrapped)) - p02_r = p02_r.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - nut_pocket = (cq.Workplane("XY", origin=(-185.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val() - p02_r = p02_r.cut(nut_pocket) - - # Same +Y rotation as the front plate; "▼" points local -Y = world +X, - # i.e. toward the centre joint. Along +Y the ~73 mm text fits the 190 mm - # plate width instead of overhanging its 45 mm X span. - txt_r = (cq.Workplane(cq.Plane(origin=(x_r_mid, Y_COL, z1), xDir=(0, 1, 0), - normal=(0, 0, 1))) - .text("P02-REAR ▼ JOIN", 7.5, -0.8)).val() - p02_r = p02_r.cut(cq.Shape(txt_r.wrapped)) - - return p02_c, p02_f, p02_r - - -def p_base_foot(): - """P02_底脚 (重型防倾翻超长力臂底座 + 双向大截面三角加强斜撑筋): - 1. 底盘尺寸大幅扩展为 385x240x22mm (X: -240.0..+145.0, Y: 8.0..248.0); - - 前向延伸至 X = +145.0mm (在手臂垂直投影前方 91.5mm),彻底杜绝手臂前伸与抓握重物时的前倾翻; - - 后向延伸至 X = -240.0mm (在立柱后方 92.0mm),抵御大动态回程后仰力矩; - 2. 立柱插接套筒由 12mm 浅槽大幅升级为 48mm 高加固套筒 (Z: 22.0..70.0mm,外壁厚 7mm); - 3. 前向大三角加强斜撑筋 (Z: 68.0->22.0mm, X: -75.0->-15.0mm, 宽 38mm),抗弯力臂长达 60mm,留足安全间隙不干涉手臂运动; - 4. 后向大三角加强斜撑筋 (Z: 70.0->22.0mm, X: -155.0->-225.0mm, 宽 38mm),抗后仰力臂长达 70mm; - 5. 前斜撑表面设 18mm 宽平滑走线导槽,与 P01 下部走线窗口自然贯通; - 6. 6 组 M6 工业工作台紧固腰槽 (28x7mm) + 底部隐蔽走线槽; - 7. 四周 R=18mm 大圆角过渡与 2.5mm 上边缘倒角。 - """ - z0 = 0.0 - z1 = 22.0 - x_rear = -240.0 - x_front = 145.0 - x_len = x_front - x_rear # 385.0 - x_mid = (x_rear + x_front) / 2.0 # -47.5 - y_width = 240.0 - - # 1. 主底盘 (385 x 240 x 22 mm) - base_plate = (cq.Workplane("XY", origin=(x_mid, Y_COL, z0)) - .rect(x_len, y_width) - .extrude(z1) - .edges("|Z").fillet(18.0) - .edges(">Z").fillet(2.5)).val() - s = cq.Shape(base_plate.wrapped) - - # 2. 立柱外包加固套筒 (Z: 22.0..70.0 mm, 外廓 80x80mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0) - .extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0) - .edges(">Z").chamfer(2.0)).val() - s = s.fuse(cq.Shape(collar_outer.wrapped)) - - # 3. 前向抗倾翻三角加强斜撑筋 (Front Triangular Gusset Rib) - f_pts = [ - (-75.0, z1), - (-15.0, z1), - (-15.0, z1 + 4.0), - (-75.0, 68.0), - ] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(f_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(f_rib.wrapped)) - - # 4. 后向抗倾翻三角加强斜撑筋 (Rear Triangular Gusset Rib) - r_pts = [ - (-155.0, z1), - (-225.0, z1), - (-225.0, z1 + 4.0), - (-155.0, 70.0), - ] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(r_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(r_rib.wrapped)) - - # 5. 前斜撑表面走线导槽 (与 P01 前走线窗 Z=70..140mm 自然顺接) - wire_guide_pts = [ - (-75.5, z1 + 1.0), - (-14.0, z1 + 1.0), - (-14.0, z1 + 4.0), - (-75.5, 68.5), - ] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(wire_guide_pts).close() - .extrude(9.0, both=True)).val() - s = s.cut(wire_guide) - - # 6. 立柱深插槽内腔 (Pocket: 66.6 x 66.6 mm, 留 0.3mm 精密滑配间隙) - socket_box = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(66.6, 66.6) - .extrude(z_collar_top - 10.0 + 1.0) - .edges("|Z").fillet(6.3)).val() - s = s.cut(socket_box) - - # 7. 4x M5 底部垂直紧固螺栓孔与沉孔 - for xx in (X_COL - 21.0, X_COL + 21.0): - for yy in (Y_COL - 21.0, Y_COL + 21.0): - th = cyl(2.75, -0.01, 10.5, xx, yy) - cb = cyl(5.0, -0.01, 6.0, xx, yy) - s = s.cut(th).cut(cb) - - # 8. 6 组 M6 工业工作台紧固腰槽 (28 x 7 mm) - slot_positions = [ - (-215.0, Y_COL - 85.0), (-215.0, Y_COL + 85.0), # 后部两角 - (-47.5, Y_COL - 95.0), (-47.5, Y_COL + 95.0), # 中部两侧 - (120.0, Y_COL - 85.0), (120.0, Y_COL + 85.0), # 前部两角 - ] - for cx, cy in slot_positions: - plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - s = s.cut(slot) - - # 9. 底部隐蔽走线通槽 - wire_ch = (cq.Workplane("XY", origin=(X_COL, Y_COL - 80.0, -0.01)) - .rect(28.0, 100.0) - .extrude(6.0)).val() - s = s.cut(wire_ch) - return s - - -def p_girdle(j1): - """P03_肩关节安装悬臂 (轻量化板式桁架版 + 侧开式电机托架): - 1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm); - 2. 主干由 66mm 实心块改为双侧 6mm 桁架竖板 (Y = Y_ARM ± 27..33),外轮廓保持旧版 - "主干 ∪ 侧筋"并集不变 —— 只减材料不加体积,零新增干涉风险;实心 728 -> 286 cm³ (-61%); - 3. 竖板帐篷形减重窗 (顶/底斜面 ≥52°,自支撑;仅 20mm 平顶可桥接); - 4. 底部 9mm 承力鞍座 + 43.4x43.4 定位榫头 (配 P01_C 44x44 内腔,单边 0.3mm 间隙); - 鞍座底面 66x66 与立柱顶环带承压面积 2403mm² —— 竖向载荷走面压,螺栓只防上抬/防滑; - 5. 4x 安装孔 (X_COL±27.5, Y_COL±13.5):Ø3.9 直孔,M4x16 从上方直接拧入自攻成型 - (鞍座与立柱顶环同径,无过孔/沉孔/预埋),可拆装; - 6. 下腹中央龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰下缘) + 顶部横向锁紧环抱筋 (66x9x10mm, - 与两侧竖板外面齐平、顶面压在筋板脊线上,仅顶前棱一道 2mm 倒角); - 7. 侧开式电机托架 (+Y侧全开, X: -51.0..11.5,前端随 X_BAY_EXT 前延 3mm), - M1 电机可从侧方斜插卡入装配(插槽比电机长 3mm),由后端法兰 4xM3x16 锁死; - 电机舱 Ø60.7 从后端直通到前端面(2026-10-02 用户裁定"去掉台阶改成平的":无环形台阶、 - 无喇叭锥;原前置止脱锁紧法兰 4xM3 随之取消,电机轴向由后端 4xM3x16 约束); - 9. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通; - 10. Ø26mm 中央走线孔;竖板间为开放腔体(无封闭空腔),M3 螺丝刀通道直接走腔体,拧紧更顺手。 - """ - X_MOUNT = Z_R1 # -51.0 (Rear face of M1) - T_FLANGE = 10.0 # 10mm thick high-strength mounting flange - X_BACK = X_MOUNT - T_FLANGE # -61.0 - Y_ARM = Y_AXIS # 128.0 - Z_ARM = Z_RL # 610.0 - Y_P_IN = 27.0 # 桁架竖板内表面 (Y_ARM ± 27) - Y_P_OUT = 33.0 # 桁架竖板外表面 (与旧主干齐平) - T_SADDLE = 9.0 # 底部鞍座厚度 - TENON = 43.4 # 定位榫头边长(P01_C 内腔 44x44 R4,单边留 0.3mm) - WIN_R = 4.0 # 竖板减重窗内角圆角(消除内尖角应力集中,Kt 由 ~2.5 降到 ~1.2) - BOLT_DX, BOLT_DY = 27.5, 13.5 # 4x 安装孔相对立柱中心偏移:落在顶环 11mm 实体壁内 - - # 1. 主安装法兰盘 (74x74mm, 四角倒圆 R=8mm) - # 背面那圈 2mm 倒角已去掉:板料只伸进法兰 0.1mm,倒角面与板背面相交会在 - # Z≈574.9 挤出一批 2mm 短棱,正是这批短棱让整条交接内棱网络倒不出圆角; - # 同时它跨过 R8 角部圆弧,就是角上那道"卷起来"的怪倒角。 - flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM)) - .rect(74.0, 74.0) - .extrude(T_FLANGE) - .edges("|X").fillet(8.0)).val() - s = cq.Shape(flange.wrapped) - - # 2. 双侧桁架竖板 (6mm):旧"斜挑支撑主干"与"大三角加强筋"轮廓同段挤出 → 自动并集成单块侧板 - # 主干的后缘两点取在加强筋斜边的延长线上,使外轮廓顶部成为一整条直线 - # (否则后缘 (-148,550) 处有 5° 折点、留下 5mm 台阶)。 - # Z_CREST = 侧筋脊线高度,同时是顶筋 2mm 顶前倒角的下沿:倒角从 (X-71,Z_CREST) - # 斜上到 (X-69,Z_CREST+2) = 顶筋顶面,斜坡面与倒角面几乎相切,顶部读起来是一整张面。 - Z_CREST = Z_ARM + 33.0 # 643(初版 642;顶筋整体上移 3mm 让出扳手空间后跟着抬) - - RIB_SLOPE = (Z_CREST - 575.0) / ((X_BACK - 10.0) - (X_COL - 15.0)) - - def rib_line_z(x): - return Z_CREST + RIB_SLOPE * (x - (X_BACK - 10.0)) - - trunk_pts = [ - (X_COL - 33.0, Z_CHEST1), - (X_COL + 33.0, Z_CHEST1), - (X_COL + 33.0, 505.0), - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_ARM - 5.0), - (X_COL - 10.0, rib_line_z(X_COL - 10.0)), - (X_COL - 33.0, rib_line_z(X_COL - 33.0)), - ] - rib_pts = [ - (X_COL - 33.0, Z_CHEST1), # 底边两端与主干/鞍座对齐 (±33):原来收进 3mm, - (X_COL + 33.0, Z_CHEST1), # 会在鞍座顶面上留下 0.7/3mm 短棱,卡住整条底棱圆角 - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_CREST), - (X_BACK - 10.0, Z_CREST), # 脊线 (-71,643):正好落在顶筋前面与 2mm 倒角下沿的交点 - (X_COL - 15.0, 575.0), - (X_COL - 30.0, 530.0), - ] - for sign in (-1, 1): - ya = Y_ARM + sign * Y_P_IN - yb = Y_ARM + sign * Y_P_OUT - y0, y1 = min(ya, yb), max(ya, yb) - for prof in (trunk_pts, rib_pts): - s = s.fuse(cq.Shape(Yplate(prof, y0, y1).wrapped)) - - # 3. 竖板减重窗 (帐篷形:斜面 ≥52° 自支撑、仅 20mm 平顶可桥接;避开鞍座与 M5 螺母柱) - # 6 个内角全部 R4 圆角:把内尖角应力集中 (Kt≈2.5) 降到 ≈1.2,同时圆角面仍是竖直/自支撑面 - win_pts = [ - (X_COL - 24.0, 498.0), (X_COL + 23.0, 498.0), (X_COL + 25.0, 522.0), - (X_COL + 10.0, 545.0), (X_COL - 10.0, 545.0), (X_COL - 25.0, 524.0), - ] - for sign in (-1, 1): - yc = Y_ARM + sign * 0.5 * (Y_P_IN + Y_P_OUT) - tool = (cq.Workplane("XZ", origin=(0, yc, 0)).polyline(win_pts).close() - .extrude(Y_P_OUT - Y_P_IN + 1.0, both=True) - .edges("|Y").fillet(WIN_R).val()) - s = s.cut(cq.Shape(tool.wrapped)) - - # 4. 底部承力鞍座:坐实立柱顶面,把两竖板、龙骨与榫头连成整体 - s = s.fuse(bx(X_COL - 33.0, X_COL + 33.0, Y_ARM - Y_P_OUT, Y_ARM + Y_P_OUT, - Z_CHEST1, Z_CHEST1 + T_SADDLE)) - - # 5. 下腹部纵向抗弯龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰背面;交接内棱在融合后做真 3D 圆角) - keel_pts = [ - (X_COL + 33.0, Z_CHEST1), # (-82, 470) - (X_COL + 33.0, 495.0), # (-82, 495) - (X_BACK + 0.1, 573.0), # (-60.9, 573) 斜拉至法兰背面 - (X_BACK + 0.1, 594.4), # (-60.9, 594.4) 顶端贴法兰 - (X_COL + 42.1, 585.0), # (-72.9, 585) - (X_COL + 20.0, Z_CHEST1), # (-95, 470) 背面一路斜到底:原来在 (-95,480) 有个折点, - ] # 折点离鞍座顶面只 1mm,R2.5 圆角爬不上去 → 龙骨底棱整组倒不出 - s = s.fuse(cq.Shape(Yplate(keel_pts, Y_ARM - 6.0, Y_ARM + 6.0).wrapped)) - - # 7. 顶部横向锁紧环抱横筋:Y 向与两竖板外面齐平 (Y_ARM±33)、前面与筋板脊线共面 (X=-71)、 - # 顶面 Z=645 压住脊线 (Z=643) 并与 2mm 顶前倒角相切 → 顶筋与两侧脊线合成一整块。 - # 整体比初版抬高 3mm (底面 633→636):4x M3 后端沉孔 Ø6.5 跨在 Z 627.2..633.7 上, - # 底面 633 会压住孔口上沿 0.7mm、扳手让位只剩负间隙;抬到 636 后退到孔口上方 2.3mm、 - # 离螺钉轴线 5.55mm。顶面 645 不越过法兰 R8 角部圆弧在 Y=95 处的 645.9 上界。 - tie_rib = (cq.Workplane("YZ", origin=(X_BACK - 10.0, Y_ARM, Z_ARM + 30.5)) - .rect(66.0, 9.0) - .extrude(10.0) - .edges(cq.selectors.BoxSelector((X_BACK - 10.5, Y_ARM - 34.0, Z_ARM + 34.5), - (X_BACK - 9.5, Y_ARM + 34.0, Z_ARM + 35.5))) - .chamfer(2.0)).val() - s = s.fuse(cq.Shape(tie_rib.wrapped)) - - # 6.y 体-体交接凹棱:融合完成后对"凹棱"直接做真 3D 圆角。 - # 原先这里是手工沿棱 fuse 的 10 条"正方形−四分之一圆"焊珠小体(_cove),已整体删除: - # 焊珠端面是平的、悬在半空,两条互相垂直的焊珠在角上互不搭接 → 圆角"缺一块", - # 并与相邻体的棱互相穿插留 1~3mm² 碎面。真圆角在面边界自然收口、多棱相交处由 OCC - # 生成角部补片。 - from cadquery import selectors as _sel - from OCP.BRepAdaptor import BRepAdaptor_Surface - from OCP.GeomAbs import GeomAbs_Plane - from OCP.TopAbs import TopAbs_REVERSED - - def _outward(f, sh): - """平面面的外法向:先按 Orientation 修正,再用"面中心外移 1mm 是否仍在实体内"兜方向。""" - ad = BRepAdaptor_Surface(f.wrapped) - if ad.GetType() != GeomAbs_Plane: - return None - ax = ad.Plane().Axis().Direction() - n = cq.Vector(ax.X(), ax.Y(), ax.Z()) - if f.wrapped.Orientation() == TopAbs_REVERSED: - n = n * -1.0 - c = f.Center() - if cq.Solid(sh.wrapped).isInside(cq.Vector(c.x + n.x, c.y + n.y, c.z + n.z), 1e-6): - n = n * -1.0 - return n - - def _fillet_reflex(lo, hi, radii, tag, eps=0.9): - """倒出 lo~hi 区域内的凹棱(两面夹 270° 材料)。判据:从棱中点沿 (n1-n2) 外探 eps, - 落在实体内 = 凹、落在外 = 凸。区域内凹棱先整批一次倒(半径逐级降级,且倒完验拓扑: - OCC 会给出"没抛异常但面已坏"的结果),建不出时按相连棱分组逐组倒。""" - nonlocal s - - def _pick(sh): - edges = sh.Edges() - idx = {} - for i, e in enumerate(edges): - idx[e.hashCode()] = i - adj = [[] for _ in edges] - for f in sh.Faces(): - for ee in f.Edges(): - k = idx.get(ee.hashCode()) - if k is not None: - adj[k].append(f) - box = _sel.BoxSelector(lo, hi) - out = [] - for i, e in enumerate(edges): - if e.Length() <= 6.0 or len(adj[i]) != 2 or not box.filter([e]): - continue - n1 = _outward(adj[i][0], sh) - n2 = _outward(adj[i][1], sh) - if n1 is None or n2 is None: - continue - dx, dy, dz = n1.x - n2.x, n1.y - n2.y, n1.z - n2.z - ln = math.sqrt(dx * dx + dy * dy + dz * dz) - if ln < 1e-6: - continue - p = e.Center() - if cq.Solid(sh.wrapped).isInside( - cq.Vector(p.x + dx / ln * eps, p.y + dy / ln * eps, - p.z + dz / ln * eps), 1e-6): - out.append(e) - return out - - es0 = _pick(s) - if not es0: - print(" [P03 真圆角] %-14s 区域内无凹棱,跳过" % tag, flush=True) - return - - def _groups(ee): - """按共端点把凹棱连成链/网络:一组一次倒,OCC 才能在交汇处生成角部补片; - 逐条倒会在交汇处留下断口(看着就像圆角倒一半停了)。""" - pts = [e.Vertices() or [e.Center()] for e in ee] - par = list(range(len(ee))) - - def find(a): - while par[a] != a: - par[a] = par[par[a]] - a = par[a] - return a - - for i in range(len(ee)): - for j in range(i + 1, len(ee)): - if any(pi.distance(pj) < 0.05 for pi in pts[i] for pj in pts[j]): - par[find(i)] = find(j) - g = {} - for i in range(len(ee)): - g.setdefault(find(i), []).append(ee[i]) - return list(g.values()) - - def _do(edges_, rr): - """倒完立刻验拓扑:OCC 会给出"没抛异常但面已坏"的结果,必须挡下来。""" - try: - ns = cq.Shape(cq.Solid(s.wrapped).fillet(rr, edges_).wrapped) - except Exception: - return None - return ns if cq.Solid(ns.wrapped).isValid() else None - - # 先试"区域内全部凹棱一次倒"(探针实测这样能整批成型,且只重建一次, - # 不会像分批那样把同一张面反复重算坏掉);不行再按相连棱分组逐组倒。 - for rr in radii: - ns = _do(es0, rr) - if ns is not None: - s = ns - print(" [P03 真圆角] %-14s %d 条凹棱 R%.1f ✓ 整批一次成型" - % (tag, len(es0), rr), flush=True) - return - - n_ok, n_grp, left = 0, 0, len(es0) - for _ in range(24): - es = _pick(s) - if not es: - break - got = False - for g in sorted(_groups(es), key=lambda g: -sum(e.Length() for e in g)): - for rr in radii: - ns = _do(g, rr) - if ns is None: - continue - s = ns - n_ok += len(g) - n_grp += 1 - print(" [P03 真圆角] %-14s 第%d组 %d 条 R%.1f ✓" - % (tag, n_grp, len(g), rr), flush=True) - got = True - break - if got: - break - if not got: - left = len(es) - break - print(" [P03 真圆角] %-14s 共 %d 组 %d 条凹棱倒圆,剩 %d 条保持尖棱" - % (tag, n_grp, n_ok, left), flush=True) - - XR, YC, ZS = X_BACK, Y_ARM, Z_CHEST1 + T_SADDLE # -61, 128, 479 - # —— 鞍座顶面 (Z=479) 上的交接内棱:龙骨三条 + 两竖板内侧两条,一次真圆角 —— - # 半径上限 2.0:龙骨侧面 Y=122 与 M5 螺钉头外缘 Y=118.75 只隔 3.25mm, - # 原来的 R3 焊珠会顶到螺帽承压边,R2.0 留 1.25mm。 - _fillet_reflex((X_COL - 34.0, Y_ARM - 40.0, ZS - 3.0), - (X_COL + 34.0, Y_ARM + 40.0, ZS + 7.0), - (2.0, 1.5), "鞍座顶面交接") - # —— 法兰背面 (X=-61) 上的交接内棱:只倒竖板/龙骨的竖直棱 —— - # 顶筋底面那条水平棱 (Z=636) 不倒:4x M3 沉孔 Ø6.5 在 Z 627.2..633.7, - # 倒上 R2.5 会把螺钉上方 5.55mm 的扳手让位吃掉 2.5mm。 - # 上界取 631:竖板内棱被顶筋底面切成 573..636 / 636..643 两段,只倒下面长的那段 - # ——把 636..643 的 7mm 短段并进来,整批就从"一次成型"退化成逐组、且少倒 6 条。 - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, 570.0), - (X_BACK + 3.0, Y_ARM + 40.0, 631.0), - (2.5, 2.0, 1.5), "竖板/龙骨↔法兰") - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, Z_ARM + 34.5), - (X_BACK + 3.0, Y_ARM + 40.0, Z_ARM + 35.5), - (1.5, 1.0), "顶筋顶↔法兰") - - # 与立柱顶部配合的定位榫头 (43.4x43.4x15mm):配 P01_C 44x44 R4 内腔,单边 0.3mm 滑配 - boss = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 15.0)) - .rect(TENON, TENON) - .extrude(15.0) - .edges("|Z").fillet(4.2)).val() - s = s.fuse(cq.Shape(boss.wrapped)) - - # 4x 安装孔:统一 Ø3.9 直孔(M4x16 从腔体正上方拧入自攻成型;鞍座与立柱顶环同径、无沉孔) - # 孔位 (X_COL±27.5, Y_COL±13.5) 同时满足:①在立柱顶环实体壁内(内腔 22 → 外壁 33); - # ②在 P03 两竖板之间、避开中间龙骨,从腔体正上方用扳手可直接拧紧。 - for xx in (X_COL - BOLT_DX, X_COL + BOLT_DX): - for yy in (Y_COL - BOLT_DY, Y_COL + BOLT_DY): - s = s.cut(cyl(1.95, Z_CHEST1 - 0.5, Z_CHEST1 + T_SADDLE + 0.5, xx, yy)) - - # 1x 横向防脱锁紧:榫头 −X 侧向内 Ø3.9x14 直孔(与立柱后壁 Ø3.9 孔同轴,M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - TENON / 2.0 - 1.0, X_COL - TENON / 2.0 + 14.0, Y_COL, Z_LOCK)) - - # 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道) - # 孔位 = 电机尾端 4x M3 螺纹径向槽实测位(电机.STEP):BCD55、相位 39.9°+90k。 - # 旧 35.1×40.9 矩形孔距与实际槽位差 ~4.7mm(对角两颗螺钉无处咬合), - # 2026-10-02 用户指出"螺钉没有对齐"后按 STEP 实测修正。 - for dy, dz in ((21.06, 17.65), (-17.65, 21.06), (-21.06, -17.65), (17.65, -21.06)): - py = Y_ARM + dy - pz = Z_ARM + dz - th = Xcyl(1.7, X_BACK - 0.5, X_MOUNT + 4.0, py, pz) - cb = Xcyl(3.25, X_BACK - 0.5, X_BACK + 4.5, py, pz) - s = s.cut(th).cut(cb) - - # 中央走线孔 (Ø26mm) - wire_hole = Xcyl(13.0, X_COL - 40.0, X_MOUNT + 2.0, Y_ARM, Z_ARM) - s = s.cut(wire_hole) - - # 6. 前向电机全包托架 (X: -51.0..8.5+X_BAY_EXT=11.5,Ø60.7 舱孔平直贯通到端面) - # 端面随插槽加长前延 X_BAY_EXT;叉板背面(10.5+X_FORK_GAP=11.5)与端面 0.0 硬贴平 - # (用户两次要求"搞成平的";J1 转动摩擦由用户知悉,发涩时可微松 M2) - X_FRONT = 8.5 + X_BAY_EXT - R_POD = 36.5 - R_BORE = 30.35 - - cradle_outer = (cq.Workplane("YZ", origin=(X_MOUNT - 0.1, Y_ARM, Z_ARM)) - .circle(R_POD) - .extrude(X_FRONT - X_MOUNT + 0.1) - .edges(">X").chamfer(1.5)).val() - s = s.fuse(cq.Shape(cradle_outer.wrapped)) - - # 电机定子配合内腔(最终状态,2026-10-02 用户裁定"去掉台阶改成平的+保留固定电机的面"): - # Ø60.7 圆柱孔壁从前端面平直贯通到舱底(无窄颈、无台阶、无喇叭锥); - # 舱底 = 一个完整的平坦环面(X=5.06+X_BAY_EXT=8.06,与插槽末端墙共面,中开 Ø39 凸台过孔) - # ——即电机前限位面。电机轴向由后端法兰 4xM3x16 约束; - # 前端锁紧由 4 个 Ø10 局部凸台上的 M3 圆柱头螺钉承担(见下方凸台段)。 - motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.06 + X_BAY_EXT, Y_ARM, Z_ARM) - s = s.cut(motor_pocket) - - # 4x 前置 M3 锁紧螺钉凸台(BCD=55mm,17.8°/107.8°/197.8°/287.8°): - # 平直舱孔的沉头座位由 4 个 Ø10 局部凸台承载——凸台位于舱孔前段 8.06..11.5, - # 电机前端面(5.0)之前 3mm,电机斜插路径不经过;M3x8 圆柱头坐在凸台沉孔 - # 底(x=9.5)旋入电机前端面,咬合 3.5mm,兼作电机前端防转定位。 - for deg in (17.8, 107.8, 197.8, 287.8): - rad = math.radians(deg) - fy = Y_ARM + 27.5 * math.cos(rad) - fz = Z_ARM + 27.5 * math.sin(rad) - s = s.fuse(Xcyl(5.0, 5.06 + X_BAY_EXT, X_FRONT, fy, fz)) - th = Xcyl(1.6, 5.06 + X_BAY_EXT, X_FRONT, fy, fz) - cb = Xcyl(3.0, X_FRONT - 2.0, X_FRONT, fy, fz) - s = s.cut(th).cut(cb) - - # J1 输出凸台过孔 (Ø39 × 3.44——J1 凸台与 3xM2 输出螺钉的必经孔,非台阶; - # 原 6.7mm 长颈与半圆台肩已随舱底前移一并消除) - hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM) - s = s.cut(hub_clearance) - - # 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒): - # M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。 - # 轴向约束:后端法兰 4xM3(电机尾端面)单向锁死(前置止脱法兰随平直舱孔要求取消); - # 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06+X_BAY_EXT=8.06 - # (比电机前端面多 3mm 斜插余量,电机可倾斜卡入),止脱法兰环随之前移、环宽保持 3.44mm。 - s = s.cut(bx(X_MOUNT, 5.06 + X_BAY_EXT, Y_ARM, Y_ARM + 60.0, Z_ARM - 60.0, Z_ARM + 60.0)) - - # 7. 散热系统:双侧大视窗 + 顶部斜向对流排热槽 - for sign in (-1, 1): - win_plane = cq.Plane( - origin=(-24.5, Y_ARM + sign * 35.0, Z_ARM), - xDir=(1, 0, 0), - normal=(0, sign, 0) - ) - win_cut = cq.Workplane(win_plane).slot2D(34.0, 20.0).extrude(16.0, both=True).val() - s = s.cut(win_cut) - - for deg in (45, 135): - rad = math.radians(deg) - slot_plane = cq.Plane( - origin=(-24.5, Y_ARM + 33.0 * math.cos(rad), Z_ARM + 33.0 * math.sin(rad)), - xDir=(1, 0, 0), - normal=(0, math.cos(rad), math.sin(rad)) - ) - slot_cut = cq.Workplane(slot_plane).slot2D(30.0, 6.5).extrude(15.0, both=True).val() - s = s.cut(slot_cut) - - return s - - -def p_fork(j1, j2): - """P04_肩叉_螺旋体30%_墙5底5_竖打 (Shoulder Fork / L-Bracket) - 加长避让顺滑流线版: - 1. 头部与身子起点自 X=5.5 前移加长至 X=10.5+X_FORK_GAP=11.5,叉板背面与 P03 托架端面 - (X=8.5+X_BAY_EXT=11.5) 0.0 硬贴平(2026-10-02 用户两次要求"搞成平的"; - P03 侧插槽独立加长 3mm 便于 M1 电机斜插卡入); - 2. J1 从动法兰圆盘已删除(盘面在 column 前缘形成鼓楞):J1 由 boss(Ø35.6)贯通 P03 前盖中心孔直连电机输出盘;column 顶边保持 Z=646.5 与 P03 环齐高,R=18mm 大圆弧顺滑过渡; - 3. 头部转接凸台加长贯通 P03 前盖中心孔 (R=19.5mm),直连 J1 旋转法兰盘; - 4. M2 保留后端安装板(R=35mm, 带 rear_mount_holes 螺钉孔),前端定位环鼓出圆盘已删除(前端定位由 column 端面 pocket 承担),中间镂空散热; - 4b. 【2026-10-02 新增】M2 前端压盖:原方案电机只被后端安装板(S02 4xM3x10)单面锁死, - 大扭矩下电机绕 Y 轴在 Ø60.3 座孔里点头/翘起,固定不牢。现在前端盖外端面 - (Y=156.0) 再加一枚同径 R35 压盖,用 4x M3x8 在电机前端盖 BCD55 上把电机 - 从前端夹一次 —— 电机被前后两块同心 Ø60.3 端面双向夹紧,倾斜自由度归零; - 螺钉沉在压盖沉孔内不外凸,不占用 J2 轴向间隙(P05 法兰盘已相应后移); - 5. 严格满足 single-solid 流形拓扑与 360° 全范围零干涉 (COLLISION pairs = 0)。 - """ - # 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm) - rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH) - - # 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH) - s = rear_plate.fuse(front_collar) - - # 2b. 【2026-10-02 新增】M2 前端压盖(几何与理由见 motor_front_clamp)。 - # 必须在第 6/7 步所有 R30.15~30.5 圆柱刀具走完之后才熔接,否则压盖根部会被 - # front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边(见 7b)。 - fp = motor_front_clamp(X_ARM, Z_SH, tag="P04/M2") - - # 3. J1 从动法兰圆盘已删除:原 R=36.5 圆盘盘面在 column 前缘形成圆盘鼓楞, - # J1 与 P04 的连接由 boss(Ø35.6 贯通 P03 前盖中心孔 R=19.5)直连电机输出盘 - # 承担,螺钉走 boss 端面 4xB 孔,圆盘无传力功能已去除。 - - # 4. XZ 平面有机连续流线轮廓(随 X_FORK_GAP 整体前移,避让顺滑过渡): - x_b, z_b = -12.65 + X_FORK_GAP, 506.0 - x_a, z_a = 10.5 + X_FORK_GAP, 545.0 - Lz = z_a - z_b - d0 = 0.514 * Lz - d1 = 0.0 - - hermite_pts = [] - N_h = 16 - for i in range(N_h + 1): - t = i / N_h - h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0 - h10 = t**3 - 2.0 * (t**2) + t - h01 = -2.0 * (t**3) + 3.0 * (t**2) - h11 = t**3 - t**2 - xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11 - zh = z_b + t * Lz - hermite_pts.append((xh, zh)) - - dx_flare = (53.5 + X_FORK_GAP) - (44.0 + X_FORK_GAP) - dz_flare = 545.0 - 490.0 - R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) - - xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) - .moveTo(10.5 + X_FORK_GAP, 545.0) - .lineTo(10.5 + X_FORK_GAP, Z_RL + 36.5) - .lineTo(28.0 + X_FORK_GAP, Z_RL + 36.5) - .radiusArc((44.0 + X_FORK_GAP, 630.0), 18.0) - .lineTo(44.0 + X_FORK_GAP, 545.0) - .radiusArc((53.5 + X_FORK_GAP, 490.0), -R_flare) - .lineTo(18.5 + X_FORK_GAP, 490.0) - .lineTo(-16.5 + X_FORK_GAP, 490.0) - .threePointArc((-15.1 + X_FORK_GAP, 498.5), (-12.65 + X_FORK_GAP, 506.0))) - for pt in hermite_pts[1:]: - xz_wire = xz_wire.lineTo(pt[0], pt[1]) - xz_wire = xz_wire.close() - xz_shape = cq.Shape(xz_wire.extrude(100.0, both=True).val().wrapped) - - # 5. YZ 平面包络 - # 【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面。 - # 原来第 2->3 点之间是一条直线 (164.5,646.5)->(155.5,526),沿 X 方向拉成 - # 4048mm2 的平板(实测法向 (0,1,-0.07),带 4 度斜度),正对电机上方 —— - # 两侧被 xz_wire 的 R18 圆弧挤出去,形成"中间一块平、两边鼓"的观感。 - # 改法:把这段直线换成三次 Hermite 曲线,两端切线分别沿 Y(接上面 Z=646.5 - # 的顶段)与沿 Z(接下面 Y=155.5 的竖直段),保证 G1 连续无缝; - # 中部向 +Y 外凸约 3mm,弧度观感与两侧 R18 一致。 - # 为什么不用单段圆弧:A 处切线要沿 Y、B 处要沿 Z,单个圆心无法同时满足 - # (解出来 R=9 与 R=120.5 矛盾),必须用 Hermite/Bezier —— 与 P05/P06 - # 的 Loft 过渡同一套做法。 - # 注意:顶点绕序必须与原列表一致(先 y_b 后 y_a),否则多边形自交、 - # 布尔后零件会碎掉(实测会掉到 48cm3 / 3 块实体)。 - y_a = (Y_COVER - 0.5, Z_SH + 36.0) # (155.5, 526.0) 曲线终点 - y_b = (Y_AXIS + 36.5, Z_RL + 36.5) # (164.5, 646.5) 曲线起点 - _d = math.hypot(y_b[0] - y_a[0], y_b[1] - y_a[1]) - _m0y, _m1z = -0.5 * _d * 0.35, 0.5 * _d * 0.5 # 切线:起点沿 -Y,终点沿 +Z - yz_pts = [(Y_AXIS - 36.5, Z_RL + 36.5)] - for k in range(13): - t = k / 12.0 - h00 = 2 * t ** 3 - 3 * t ** 2 + 1 - h10 = t ** 3 - 2 * t ** 2 + t - h01 = -2 * t ** 3 + 3 * t ** 2 - h11 = t ** 3 - t ** 2 - yz_pts.append((h00 * y_b[0] + h10 * _m0y + h01 * y_a[0], - h00 * y_b[1] + h11 * _m1z + h01 * y_a[1])) - yz_pts += [(Y_COVER - 0.5, Z_SH), - (Y_REAR - 8.0, Z_SH), - (Y_REAR - 8.0, Z_SH + 36.0)] - yz_solid = Xplate(yz_pts, -20.0, 60.0) - - c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped) - c_int.Build() - column = cq.Shape(c_int.Shape()) - - s = s.fuse(column) - - # 6. 中部电机定子散热切除 - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH)) - - # 7. M2 接口成型 - # 承压面起点用 AB.REAR_SEAT(=0.6 台肩 + 2.0 公差),与 Joint.pocket 同源: - # P04 走的是自己那套 BORE_R 圆柱刀具(没调 Joint.pocket),这里必须同步改, - # 否则 J2 关节的电机公差位仍是旧的 0.6,与 J3~J6 不一致。 - # 【2026-10-04】承压面段也加一刀 SEAT_R(=32.2) 圆腔把悬空凸台挖掉,但**不能** - # 用 R35 —— 那会把 r=32.2..35 的连接壁整圈切断,实测 P04 只剩 240~320° 方位 - # 还连着,后端法兰与杯壁之间出现 3.6mm 通槽 + 225° 一片 0.5mm 孤立薄片 - # (用户截图:肩叉顶部"没连上")。取 32.2 保留整圈 2.8mm 连接壁。 - s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - c_m2 = [cyl(BORE_R, -AB.REAR_SEAT, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6), - cyl(AB.SEAT_R, -AB.REAR_SEAT, AB.SEAT_TOP)] - s = s.cut(*[j2.L(item) for item in c_m2]) - - - # 7b. M2 前端压盖熔接(见 2b):必须在 6/7 步所有 R30.15~30.5 圆柱刀具走完之后, - # 否则压盖根部会被 front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边。 - s = s.fuse(fp) - - # 7c. 【2026-10-02 新增】立柱减重槽(见 hollow_channel)。 - # 立柱 Z525..635 整段是壁厚最深处达 17.5mm 的实心料,而 J2 在 z=560/600 的 - # 安全系数有 19。挖成朝上敞开的 U 形槽后立柱变成槽钢:抗弯的两片 X 向腹板 - # 正好落在弯矩拉压区(z=560 截面 X13..45,中性面在 X29,腹板占 X13~23/X35~45), - # 被掏掉的正是中性层附近几乎不参与抗弯的料。 - # 槽底 Z525 以下保留整块实心过渡到电机座;顶部敞开故非封闭腔、免支撑。 - # 必须在 8 步 J1 凸台/螺钉孔之前切:槽的 X23..35 范围离 J1 凸台(X<=11.5)与 - # 3xS01 沉孔(X12..14)都有富余,但先切可以保证后面那些刀具不会把槽壁打薄。 - s = s.cut(hollow_channel(P04_CH["x0"], P04_CH["x1"], P04_CH["y0"], P04_CH["y1"], - P04_CH["z_floor"], 660.0, tag="P04立柱")) - - # 8. 头部转接凸台加长(贯通 P03 前盖中心孔,跨越装配缝直连 J1 输出盘) - boss = cyl(17.8, 54.942, 61.5 + X_FORK_GAP) - s = s.fuse(j1.L(boss)) - s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R) - v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 63.0 + X_FORK_GAP, u, v) - # 沉孔台阶固定在 63.0(不随前移):M2x10 旋入电机输出毂的咬合量与改前一致; - # 螺钉头(63..65)相对叉板背面(64.5)外露 0.5mm,位于 2mm 叉缝空隙内,无干涉。 - cb = cyl(2.8, 63.0, 74.0 + 40.0 + X_FORK_GAP, u, v) - s = s.cut(j1.L(th)).cut(j1.L(cb)) - - return s - - - -def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket", hub_gap=1.0, - roll_clamp=False): - """Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08): - 1. 顶部从动法兰与前侧 Loft 自然相切顺接; - 2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉; - 3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合; - 4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。 - - hub_gap = 从动法兰盘背面与上级电机前端盖之间的轴向让位间隙。 - 默认 1.0(贴合防磨)。P05 传 6.0:J2 处 P04 新增了 M2 前端压盖 - (Y 155.5..161.0),必须让出这 6.0mm 才能装下;见 p_shoulder_roll_base。 - - roll_clamp = 本件作为 roll 电机【前端夹紧件】时为 True(J3/P05 夹 M3)。 - roll 电机(J3/J6)轴向是 -Z,前端盖外端面在 z_r-56,-Z 侧是从动件: - 压盖朝 -Z 长(Z_COVER-5..Z_COVER),从动件毂面必须整体后移 CLAMP_BACKOFF=5.5 - 让位(与 Y 轴关节 hub_gap=6.0 同一个道理,只是基准间隙 0.5 而非 1.0)。 - P08 传 False:J6 的 M6 前端压盖尚未做(从动件 P09 让位要改工具法兰直锁凸台)。 - """ - z_bot = z_r - 56.0 # roll 电机底端 (front collar) - z_plate = z_r + 8.0 # roll 电机顶端安装板 (rear plate top);见第 8 步 t_plate 说明 - y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) - y_hub_in = HL.Y_COVER + hub_gap # 157.0 / (P05) 162.0 从动法兰内侧面 - z_hub_in = y_hub_in - HL.Y_REAR # 法兰盘背面的电机局部 z(凸台长度跟着它走) - y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值) - - # 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm) - s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS) - - # 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm) - disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch) - s = s.fuse(disc) - - # 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph) - Z_top = z_pitch - 35.0 - z_start = z_bot - N = 16 - 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) - 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_mid = max(v_c, v_s + 0.02) - # 后缘两角 R2 圆角:把 y=123 凸包与 R35 柱面交汇的两条 90° 竖棱圆润化 - # (磨掉"凸起来的"尖角,不动后缘高度/位置,打印无悬挂) - FR = 2.0 - 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 + FR) - .radiusArc((35.0 - FR, -5.0), FR) - .lineTo(-35.0 + FR, -5.0) - .radiusArc((-35.0, -5.0 + FR), FR) - .lineTo(-35.0, v_s) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域) - top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(y_hub_in, Z_top) - .lineTo(y_out, Z_top) - .lineTo(y_out, z_pitch) - .lineTo(y_hub_in, z_pitch) - .close() - .extrude(35.0, both=True)) - s = s.fuse(top_fill.val()) - - # 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料, - # 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整! - cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(-999.0, z_plate) - .lineTo(y_hub_in, z_plate) - .lineTo(y_hub_in, z_pitch + 50.0) - .lineTo(-999.0, z_pitch + 50.0) - .close() - .extrude(60.0, both=True)) - s = s.cut(cut_top_solid.val()) - - # 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强 - cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0) - s = s.cut(cut_open) - - # 7. 上级电机同心圆弧避让 - clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch) - s = s.cut(clearance) - - # 8. 下方电机安装孔位与定位止口 - # t_plate=8.0:Joint.pocket 的尾端面承压面让空了 MOTOR_TOL=2.0,承压面到螺钉沉孔 - # 台阶只剩 t_plate-cb_depth-REAR_SEAT = 8-4-2.6 = 1.4mm 膜厚;给到 8 而不是原来的 - # 7,是为了不让这层膜退化成 0.4mm。7->8 让 P05/P08 的杯体外壳顶 z_plate 抬 1mm。 - s = j_roll.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j_roll.pocket(s) - - # 9. 电机定子配合空腔 - s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS)) - - # 10. 俯仰轴直连法兰结构(凸台延伸至法兰盘背面之后 2.5mm,跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, z_hub_in + 2.5) - s = s.fuse(j_pitch.L(boss)) - s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb)) - - # 11. L 型转角连接处圆角过渡(圆角 R=2.0mm): - # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中 - try: - wp_s = cq.Workplane(obj=s) - sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate)) - s = wp_s.edges(sel_l).fillet(2.0).val() - except Exception as e: - print(f"[{name}] L型转角圆角异常: {e}") - - # 12. roll 电机(M3)前端压盖:与 P04/P06/P07 的 M2/M4/M5 压盖同一套配方, - # 只是 roll 电机轴向为 -Z,压盖朝 -Z 长(见 motor_front_clamp 的 axis="Z")。 - # 熔接必须放在最后:第 6/7/8/9 步所有 R30.15~30.5 圆柱刀具都走完之后才接, - # 否则压盖根部那 0.5mm 薄环会被 front_collar_pocket / 定子孔啃掉一圈。 - if roll_clamp: - s = s.fuse(motor_front_clamp(HL.X_ARM, 0.0, tag="%s/M3" % name[:3], - axis="Z", py=HL.Y_AXIS, z_face=z_r - COVER_Z)) - - return s - - -def p_shoulder_roll_base(j2, j3, half=None): - """P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版) - - 本件在链路上有两重身份,两处让位都是为前端压盖: - 1. hub_gap=6.0(从动侧):J2 处 P04 新增了 M2 前端压盖(前端夹紧电机,消除大扭矩下的 - 倾斜),压盖占 Y 155.5..161.0,所以从动法兰盘背面必须从 Y=157 后移到 Y=162 - (留 1.0mm 动间隙)。 - 2. roll_clamp=True(前端夹紧侧):P05 自己抱着 M3(J3,-Z 轴),原来也只有后端板 - 4xM3x10 单面锁死;现在按 motor_front_clamp 的统一配方在 M3 前端盖外端面 - (Z=389)加一枚 R35 压盖(Z 384..389,4xM3x8 BCD55),与后端板前后双向面压, - 倾斜自由度归零。代价是对侧从动件 P06 的 J3 毂面整体后移 CLAMP_BACKOFF=5.5 - (Z 388.5→383.0),见 p_humerus_bone。 - P08 保持默认 —— J5 处 P07 的前端压盖已做(只需 6.0 的 hub_gap),而 J6 的 M6 - 前端压盖尚未做,故 roll_clamp 传 False。 - """ - return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, - name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - hub_gap=6.0, roll_clamp=True) - - -def p_humerus_bone(j3, j4): - """P06_大臂骨架_螺旋体25%_墙4底5_竖打 (Humerus Bone - Loft 圆弧转方有机过渡版): - 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, - 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 - 1. 借鉴 P05 大臂旋转座几何放样算法: - - 顶端结合面 (Z = 383.0mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯; - - 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面; - - 过渡区 (Z: 367.0..383.0mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变; - 2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗; - 3.【2026-10-03】J3 毂面整体后移 CLAMP_BACKOFF=5.5(顶端 388.5→383.0,直锁凸台 - 59.5→65.0):P05 新增 M3 前端压盖,占 Z 384..389,原先 388.5 的毂面只剩 0.5mm - 让位放不下压盖。配方与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源: - 压盖 5.0 + 动间隙 1.0 - 原基准 0.5 = 5.5。凸台同步加长,3xS01 输出螺钉的头面 - 台阶(局部 s=58)、杆长、咬合量与改前完全一致,J3 直锁与零干涉不受影响。 - 压盖中央 Ø37 让位孔(R18.5)也让开 R17.8 凸台,单边余 0.7mm。 - 4. 严格单实体 (solids = 1) 且与上下级旋转构件及电机实现动态 0 碰撞干涉。 - """ - # 1. M4 底部抱紧套筒与后端安装板 (R=35mm) - rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL) - front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL) - s = rear_m4.fuse(front_m4) - - # 顶端结合面与过渡高度 - # z_top 由 M3 前端盖外端面反推:压盖占 [Z_COVER-CLAMP_T, Z_COVER],再留 CLAMP_CLR - # 动间隙 -> 383.0(改前 388.5,与前端盖只有 0.5mm,放不下压盖)。 - z_cover3 = Z_R3 - COVER_Z # 389.0 M3 前端盖外端面 - z_top = z_cover3 - CLAMP_T - CLAMP_CLR # 383.0 J3 毂面(= 388.5 - CLAMP_BACKOFF) - z_bot = Z_EL + 35.0 # 367.0 - - # 2. 下部实心基座块 (Z: Z_EL..z_bot + 0.5) - lower_block = bx(X_ARM - 35.0, X_ARM + 35.0, Y_REAR - 8.0, Y_COVER - 0.5, Z_EL, z_bot + 0.5) - s = s.fuse(lower_block) - - # 3. 借鉴 P05 的 Loft 放样过渡体:圆弧变方形(20 层平滑放样,保持严格对称 4 段式截面拓扑) - N = 20 - wp = cq.Workplane("XY") - for i in range(N + 1): - t = i / float(N) - z_curr = z_bot + t * (z_top - z_bot) - st = 3.0 * (t**2) - 2.0 * (t**3) - - # 前侧:t=0 为平直面 (Y=155.5, v=27.5),t=1 渐变为与 Ø70 正切的圆弧顶点 (Y=163.0, v=35.0) - v_sf = 27.5 * (1.0 - st) + 0.05 * st - v_cf = max(27.5 * (1.0 - st) + 35.0 * st, v_sf + 0.05) - - # 后侧:从 t=0 的平直面 (Y=92.0, v=-36.0) 渐变到 t=1 的圆弧 (Y=93.0, v=-35.0) - v_sr = -36.0 * (1.0 - st) - 0.05 * st - v_cr = min(-36.0 * (1.0 - st) - 35.0 * st, v_sr - 0.05) - - w = (cq.Workplane("XY", origin=(X_ARM, Y_AXIS, z_curr)) - .moveTo(-35.0, v_sf) - .threePointArc((0.0, v_cf), (35.0, v_sf)) - .lineTo(35.0, v_sr) - .threePointArc((0.0, v_cr), (-35.0, v_sr)) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 电机中间定子散热裸露切除 (100% 裸露) - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL)) - - # 5. M4 接口成型 - s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j4.pocket(s) - - # 5b. 【2026-10-02 新增】M4 前端压盖 4xM3x8(BCD55):M4 原来只被后端板单面锁死, - # 大扭矩下会绕 J4 轴在 Ø60.3 座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 熔接放在所有 R30.15~30.5 圆柱刀具之后,理由同 p_fork 7b。P07 的 J4 从动侧 - # 已相应后移到 Y>=162(见 p_forearm_bone 的 J4_CLEAR),与压盖前端面留 1.0mm。 - s = s.fuse(motor_front_clamp(X_ARM, Z_EL, tag="P06/M4")) - - # 5c. 【2026-10-02 新增】电机上方过渡块减重窗(见 hollow_tent_window)。 - # Z360..385 这段 Loft 过渡块壁厚最深 15.8mm,是 P06 唯一一块可掏的厚料 - # (Z332..367 的下部本体已经被 Ø61 定子孔掏成 4.5mm 薄壁,没得再掏)。 - # 但它正压在 J3 毂下面,所以三条红线: - # 1) 窗顶最高只到 Z374 —— J3 的 Ø70 毂面在 Z380..388.5,窗顶留 6mm 整块 - # 实心托住毂面承压,绝不碰毂面和它后面的 3xS01 直锁螺钉; - # 2) 窗的 X 范围 3..37 —— 截面本体 X-15..55,两侧各留 18mm 竖墙, - # 该高度上零件才不会被切成两半(必须单实体); - # 3) 拱形(下宽上窄)轮廓,斜面与水平 >= 52° 自支撑,只有顶部 X12..28 的 - # 16mm 平顶需要桥接(与 P03 同量级)。 - # 窗底取 Z362 而不是更低:Z360 附近是全段安全系数最低的截面, - # 抬高窗底 4mm 比横向收窄 8mm 更划算(减重量只少 3cm3,安全系数从 92% 回到 100%)。 - # 窗沿 Y 双向切穿(Y87..172 超出本体,前后都贯通),顺带把这段的定子散热口 - # 一起打开。 - # 【2026-10-03 关闭】用户实机视点确认这个拱形窗在正面看就是一个贯通的矩形黑洞, - # 位于J4 关节正上方、整机最显眼的落脚点,视觉上像"零件破了个洞",非常不雅观。 - # 权衡后恢复实心:代价只有 161->183cm3(+22cm3 / +27g PLA),而强度本来就已经是 - # 无窗基线的 100%(这段本来就不缺强度),减重收益纯属锦上添花。 - # 保留 P06_WIN_XZ 与本段注释,需要减重时把开关改回 True 即可复原。 - if P06_HOLLOW_WIN: - s = s.cut(hollow_tent_window(P06_WIN_XZ, 80.0, 175.0, tag="P06过渡块")) - else: - print(" [减重窗] P06过渡块: 已关闭(2026-10-03 视觉整改),该段恢复实心", flush=True) - - # 6. J3 直连法兰结构(凸台延伸至 65.0 跨过结合面牢固熔接,保持单实体与零干涉) - # 凸台随 J3 毂面一起后移 CLAMP_BACKOFF=5.5(59.5→65.0):进入电机输出毂的深度 - # 仍是 s54.942..56.0 那 1.06mm,3xS01 的头面台阶(s=58)/杆长/咬合量一律不变。 - boss = cyl(17.8, 54.942, 59.5 + CLAMP_BACKOFF) - chamfer_cone = cyl(18.5, 54.5, 54.942) - s = s.fuse(j3.L(boss)).cut(j3.L(chamfer_cone)) - s = s.cut(j3.L(cyl(3.5, 54.0, 58.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 58.0 + 40.0, u, v) - s = s.cut(j3.L(th)).cut(j3.L(cb)) - - return s - - -def p_forearm_bone(j4, j5, half=None): - """P07_小臂骨架_螺旋体30%_墙5底5_竖打 L型高刚度无干涉版(深度圆角优化版) - 设计要点: - 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - - 彻底消除原全深S弯在J4轴下方向两边突出的干涉结构(两边突出的结构全部去除) - - 垂直立柱与下方 M5 电机水平座之间采用规则折角(Corner Chamfer/Gusset)刚性连接 - 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 - 3. J4 轴俯仰动态行程从受限的 ±54° 大幅释放至无死角大范围摆动(零干涉) - 4. 满足单实体 (solids=1) 与装配零碰撞。 - 5.【2026-10-02】M5 前端压盖(4xM3x8 BCD55)消除"电机只被后端板单面锁死"的倾斜隐患; - J4 从动侧整体让位:y_hub_in 由 158.5 后移到 J4_CLEAR=162.0,让出 P06 的 M4 前端压盖 - (前端面 Y=161.0),留 1.0mm 动间隙。J4 直连凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - y_out = Y_HUB + PLATE_T # 174.0 - # J4 从动侧让位基准:必须 >= P06 的 M4 前端压盖前端面(Y_COVER+5.0=161.0) + 1.0mm - J4_CLEAR = Y_COVER + 6.0 # 162.0 - y_hub_in = J4_CLEAR - 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) - rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR) - s = collar_front.fuse(rear_plate) - - # 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL) - disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL) - s = s.fuse(disc) - - # 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_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)) - s = s.fuse(roof_w.val()) - - # 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座) - vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) - .moveTo(y_hub_in, z_top_housing) - .lineTo(y_out, z_top_housing) - .lineTo(y_out, Z_EL) - .lineTo(y_hub_in, Z_EL) - .close() - .extrude(35.0, both=True)) - s = s.fuse(vert_leg.val()) - - # 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块") - # 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm) - # 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end) - # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! - # 注:顶板整条加高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_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_roof_top) - .threePointArc(p_mid_arc, (y_hub_in, z_arc_end)) - .lineTo(y_out, z_arc_end) - .lineTo(y_out, z_top_housing) - .lineTo(y_arc_start, z_top_housing) - .close() - .extrude(35.0, both=True)) - s = s.fuse(gusset_w.val()) - - # 6. 下级 P08 旋转圆盘动态避让切除(R=35.5mm,预留 0.5mm 间隙) - p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR) - s = s.cut(p08_clearance) - - # 6b. 【2026-10-02 新增】顶板根部减重槽(见 hollow_channel)。 - # 顶板在 Z255..280 一整段是壁厚最深 15mm 的实心料,而 J4 在 z=245 的安全系数 - # 高达 122、z=195 也有 24。挖成朝上敞开的 U 形槽,顶板变成槽钢: - # 槽底 Z255 以下保留整块实心到电机座,抗弯的两片 X 向腹板(X-15..-7 / X47..55) - # 留在弯矩拉压区。Y 上限取 156 而非核心的 166,是为了避开 y_hub_in=158.5 起的 - # 竖直立柱本体(立柱是 J4 从动法兰的承力柱,不能掏)。 - # 槽顶 Z280 就是顶板上表面,敞口向上,免支撑、非封闭腔。 - s = s.cut(hollow_channel(P07_CH["x0"], P07_CH["x1"], P07_CH["y0"], P07_CH["y1"], - P07_CH["z_floor"], 300.0, tag="P07顶板")) - - # 7. M5 电机安装接口与定子空腔 - s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5) - s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j5.pocket(s) - s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, HL.Y_REAR - 0.5, y_front - 6.0, X_ARM, Z_WR)) - - # 半包散热设计:-Z底部电机定子通风口 - vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0) - s = s.cut(vent_cut) - - # 7b. 【2026-10-02 新增】M5 前端压盖 4xM3x8(BCD55):M5 原来只被后端板单面锁死, - # 大扭矩下会绕 J5 轴在座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 必须在第 6 步 p08_clearance(R35.5 让位孔,Y>=156)之后熔接,否则压盖会被削掉。 - # P08 的 J5 从动侧已相应后移到 Y>=162(make_pitch_to_roll_bracket 的 hub_gap=6.0)。 - s = s.fuse(motor_front_clamp(X_ARM, Z_WR, tag="P07/M5")) - - # 8. J4 直连法兰结构(凸台延伸到法兰盘背面之后 2.0mm 跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, (y_hub_in - HL.Y_REAR) + 2.0) - s = s.fuse(j4.L(boss)) - s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j4.L(th)).cut(j4.L(cb)) - - # 9. 工艺走线通孔 - slot_z = (Z_EL + z_top_housing) / 2.0 - slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z) - s = s.cut(slot) - - # ========== 关键圆角优化 ========== - try: - # 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角) - wp_s = cq.Workplane(obj=s) - sel_rear = cq.selectors.BoxSelector( - (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).fillet(FILLET_SMALL).val() - except Exception as e: - print(f"P07 顶板后边缘圆角异常: {e}") - - return s - - -def p_wrist_bracket(j5, j6): - """P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版) - - hub_gap=6.0:J5 处 P07 新增了 M5 前端压盖(Y 155.5..161.0),从动法兰盘背面必须 - 从 Y=157 后移到 Y=162 让位;J5 直锁凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, - name="P08_腕偏转座_螺旋体25%_墙4底5_竖打", - hub_gap=6.0) - - -def p_tool_flange(j6): - """P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版 - 关键改进: - 1. 外轮廓全圆角(R=4mm) - 2. 法兰面倒角(2mm) - 3. 螺孔边缘倒角(1.5mm) - """ - z_bot = Z_HUB6 - PLATE_T - 9.0 - s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) - P09_M4 = [] - - # 4x M4螺栓孔 - # - # 分布圆半径 20 -> 22(BCD 40 -> 44):R20 与 3xS01 输出螺钉的 Ø5.6 螺丝刀通道 - # (make_direct_hub 里 cyl(2.8, 58.0, z_out+40) 在 BCD29=R14.5、相位 351.4/232.3/112.1°) - # 咬穿沉孔。0° 那个孔中心 (39.5,128) 到 351.4° 通道中心 (33.84,125.83) 只有 6.06mm, - # 而两孔半径之和是 3.5+2.8=6.30 —— 差 0.24mm,Ø7.0 沉孔壁被啃掉一块, - # 完整圆柱面(88mm²)裂成 45.7+35.4 两片,即用户截图里的月牙孔。 - # 该相位差只有 8.6°,转动 M4 孔相位无法躲开(0/90/180/270 与 45 度错开全部仍 <6.30), - # 只能加大分布圆半径:R22 时最小中心距 7.96mm,余量 1.66mm。 - # 边壁仍充足:R35 法兰外径 - (R22 + 沉孔 3.5) = 9.5mm。 - for a in (0, 90, 180, 270): - rad = 22.0 - u = rad * math.cos(a * D2R) - v = rad * math.sin(a * D2R) - # 实际切割推迟到 make_direct_hub 之后(见下)。这里只算出 4 个孔位。 - P09_M4.append((X_ARM + u, Y_AXIS + v)) - - # 定位止口 + 走线孔 - pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS) - thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS) - s = s.cut(pilot).cut(thru) - - # 直连法兰 - s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0) - - # 4x M4 孔(必须在 make_direct_hub 之后切) - # - # 直连法兰那步 fuse 了 R35 圆盘(局部 z=56.5..74 -> 全局 z=102..121 一侧), - # 会把先切的孔重新填死:分段求交显示 z=97..102 无干涉、z=102 以上整根杆都埋在料里 - # (每段 12.57 mm^3 = pi*2^2*1,全截面被埋)。 - # - # 孔径与深度同时修正:旧值 Ø3.7 比 M4 杆 Ø4.0 还细,且只到 z=105, - # 而 M4x16 头面在 z=97、杆要走到 z=113 —— 杆尾 8mm 无处可去(干涉 147.3 mm^3)。 - # Ø4.4 是 M4 标准间隙孔(ISO 273 medium ≈ Ø4.3)。 - # 孔顶要盖过 M4x16 的杆尾:头面 z=97 + 杆长 16 = z=113,故开到 Z_HUB6+4=115, - # 比杆尾多留 2mm(实测开到 112 时最后 1mm 杆尾仍撞料 37.7mm³)。 - for (fx, fy) in P09_M4: - s = s.cut(cyl(2.2, z_bot - 1.0, Z_HUB6 + 4.0, fx, fy)) - s = s.cut(cyl(3.5, z_bot - 1.0, z_bot + 4.0, fx, fy)) - - # ========== 关键圆角优化 ========== - try: - # 外轮廓圆角 - s = s.edges("|Z").fillet(FILLET_SMALL) - except: - pass - - try: - # 法兰面倒角 - s = s.edges("Z").fillet(FILLET_TINY) - except: - pass - - return s - - -# ---------------------------------------------------------------- assembly ---------------- -def build(): - print("=" * 80, flush=True) - print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True) - print("=" * 80, flush=True) - t_stage1 = time.time() - - print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True) - t0 = time.time() - motor = cq.importers.importStep(AB.SRC_STEP).val() - motor = place(motor, T(0, 0, -2.57)) - AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None - print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True) - - JS = {} - for tag, cn, rear, d, xd, holder, driven in HL.JOINTS: - JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0) - j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7)) - - PARTS[:] = [] - AB.CENTS.clear() - AB.GRP = {} - par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"} - driven = {"J1": "P04_肩叉_螺旋体30%_墙5底5_竖打", "J2": "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "J3": "P06_大臂骨架_螺旋体25%_墙4底5_竖打", - "J4": "P07_小臂骨架_螺旋体30%_墙5底5_竖打", "J5": "P08_腕偏转座_螺旋体25%_墙4底5_竖打", "J6": "P09_工具法兰_螺旋体30%_墙4底5_竖打"} - motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3", - "J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"} - for t in ("J1", "J2", "J3", "J4", "J5", "J6"): - AB.GRP[motor_of[t]] = par[t] - AB.GRP[driven[t]] = t - AB.GRP.update({ - "P01_A_立柱下段_三角形30%_墙4底5_竖打": "root", "P01_B_立柱中段_三角形30%_墙4底5_竖打": "root", "P01_C_立柱上段_三角形30%_墙4底5_竖打": "root", - "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": "root", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": "root", - "P03_肩吊臂_螺旋体35%_墙5底5_竖打": "root" - }) - AB.JAW = {} - - p01_full = p_chest() - p01_a, p01_b, p01_c = split_p01_poka_yoke(p01_full) - p02_c, p02_f, p02_r = make_p02_poka_yoke() - - parts_to_build = [ - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7, - "200mm适配分段A:高166mm+16mm阶梯公榫+顶端 2x Ø3.9 对锁孔(M4x16 自攻)+底部50x50x12榫头+P01-A 竖排TOP刻字"), - ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb, - "200mm适配分段B:高165mm+双端公母榫+两端各 2x Ø3.9 对锁孔(M4x16 自攻,非对称孔位防反装)+P01-B 竖排TOP刻字"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8, - "200mm适配分段C:高149mm+底端母榫+底端 2x Ø3.9 对锁孔(非对称孔位防反装)+顶面P03安装台(4x Ø3.9 安装孔 + 1x Ø3.9 横向锁紧孔)+P01-C 竖排TOP刻字"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "底座核心承力座", lambda: p02_c, 0x475569, - "200mm适配中座:195x190mm核心承力座+48mm套筒+12mm阶梯榫座(轴向面承载)+前后三角加强筋+前后非对称防呆母榫槽+刻字"), - ("P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打", "前向防翻力臂", lambda: p02_f, 0xe11d48, - "200mm适配前臂:145x190mm扩展力臂+104mm宽跨距双公榫+底部M5预埋螺母沉槽+P02-FRONT刻字"), - ("P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打", "后向防翻力臂", lambda: p02_r, 0xd97706, - "200mm适配后臂:45x190mm扩展力臂+80mm窄跨距双公榫+底部M5预埋螺母沉槽+P02-REAR刻字"), - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563, - "轻量化板式桁架版:主干由 66mm 实心块改为双侧 6mm 桁架竖板+帐篷减重窗(斜面≥52°自支撑)+R4内角,外轮廓与旧版并集完全一致,实心 728->286cm³;底部 9mm 承力鞍座(Ø3.9 安装孔)+12mm 中位龙骨筋(自鞍座斜拉至法兰下缘)+40~45°外扩斜撑;保留 10mm 后置法兰、侧开式电机托架、Ø60.7 直通电机舱(平孔无台阶)(M1 从+Y滑入,后端 4xM3x16 锁死)、大散热窗与对流槽;无封闭空腔、免支撑"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "肩叉", lambda: p_fork(j1, j2), 0x2563eb, - "齐平插槽+ M2 前端压盖+ 立柱减重槽版:叉板背面与P03托架端面(11.5)0.0硬贴平(用户要求外观齐平),P03侧插槽独立加长3mm,M1电机可倾斜卡入,J1输出凸台贯通直连电机,J1从动法兰圆盘鼓楞已删(接缝区与P03止脱环齐径齐高R36.5/顶646.5),单实体全角度零干涉;【2026-10-02】M2 前端压盖(R35, Y155.5..161.0):电机前端盖外端面加 4xM3x8 沉孔(BCD55)夹紧,与后端安装板 4xM3x10 形成前后双向面压,消除大扭矩下电机在座孔里的倾斜;【2026-10-02】立柱减重槽(X23~35 / Y102~154 / 自Z525向上敞开):立柱Z525~635整段壁厚峰值17.5mm,J2在z=560/600的安全系数有19,改成U形槽钢后361->289cm3(-20%),抗弯的两片X向腹板正好落在弯矩拉压区(z=560截面X13~45、中性面X29,腹板占X13~23/X35~45),掏掉的正是中性层附近几乎不参与抗弯的料;槽底Z525以下保留整块实心过渡到电机座,槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X23~35 比最初的 X21~37 收窄 2mm,强度从 88% 回到 95%);【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面(全行程重扫 907s 确认 J1~J6 行程逐项不变):原来 yz_pts 里(164.5,646.5)->(155.5,526) 是一条直线,沿 X 拉成 4048mm2 平板(法向 (0,1,-0.07)、带 4° 斜度),正对电机上方,两侧被 xz_wire 的 R18 圆弧挤出去,形成「中间一块平、两边鼓」的观感。改成三次 Hermite(两端切线分别沿 Y/Z,与上下水平段 G1 连续),中部向 +Y 外凸约 3mm,弧度与两侧 R18 一致;单段圆弧无法同时满足两端切线(解出 R=9 与 R=120.5 矛盾)故用 Hermite。P04 体积 282.1->274.3cm³(弧面替代平面削掉的料);【2026-10-04 承压面让位腔改为 32.2 圆腔、悬空凸台消失且外壁连上】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, - "V3圆角+ J2前端夹紧配合:Loft外轮廓R=10mm,L型转角内部2mm倒角,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂);J2 从动法兰盘背面由 Y=157 后移至 Y=162(hub_gap=6.0,给 P04 新增的 M2 前端压盖让位,J2 直锁凸台同步加长到 Y=165.5,3xS01 输出螺钉咬合量不变);保证 J2 全角度 0 干涉;【2026-10-03】M3 前端压盖(R35, Z384.0..389.0):J3 roll 电机原来也只有后端板 4xM3x10 单面锁死(S02),大扭矩下绕 J3 轴在 Ø60.3 座孔里点头;现按 P04/P06/P07 同一配方在 M3 前端盖外端面(Z=389)加 4xM3x8(BCD55)沉孔压盖,与后端板前后双向面压,倾斜自由度归零(配方唯一差别是 roll 电机轴向 -Z,压盖朝 -Z 长,见 motor_front_clamp axis=\"Z\");代价是对侧 P06 的 J3 毂面整体后移 5.5mm 让位;【2026-10-04 承压面让位腔改为 32.2 圆腔、悬空凸台消失且外壁连上】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488, - "Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体;【2026-10-03】取消电机上方Loft过渡块(Z360~385)的拱形减重窗,恢复实心:该窗沿Y贯通,正面看是整机最显眼处的矩形黑洞(用户视觉整改),代价仅 +22cm3/+27g,强度仍为无窗基线100%;【2026-10-03】J3 毂面整体后移 5.5mm(顶端 388.5→383.0,直锁凸台 59.5→65.0):给 P05 新增的 M3 前端压盖(Z384..389)让位,留 1.0mm 动间隙——与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源,换算式同为「压盖 5.0 + 动间隙 1.0 - 原基准 0.5」;凸台同步加长,3xS01 头面台阶(s=58)/杆长/咬合量与 J3 直锁关系一律不变;【2026-10-04 承压面让位腔改为 32.2 圆腔、悬空凸台消失且外壁连上】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, - "V2 L型结构 + 顶板减重槽:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉;【2026-10-02】顶板根部Z255~280开朝上敞开的减重槽(X-3~43 / Y103~153, Y上限取153避开y_hub_in=158.5起的竖直立柱本体):顶板由实心变槽钢,288->230cm3(-20%),槽底以下保留整块实心到电机座,抗弯靠两侧各10mm的X向腹板;槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X-3~43 比最初的 X-7~47 收窄 4mm,强度从 88% 回到 96%);【2026-10-04 承压面让位腔改为 32.2 圆腔、悬空凸台消失且外壁连上】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706, - "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉;【2026-10-04 承压面让位腔改为 32.2 圆腔、悬空凸台消失且外壁连上】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626, - "V2圆角:外轮廓R=3mm,法兰面倒角2mm;【2026-10-02】打印填充由100%降到30%:该截面安全系数288是全机裕度最大的件,满填充纯属浪费(件号同步改为P09_工具法兰_螺旋体30%)"), - ] - - total_parts = len(parts_to_build) - for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): - print(f" [{idx}/{total_parts}] 正在构建 {name} ({cn})...", end="", flush=True) - t0 = time.time() - sh = fn() - dt = time.time() - t0 - vol = AB.vol_of(sh) / 1000.0 - n_solids = len(solids_of(sh)) - print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True) - add(name, cn, sh, color, note=note) - - print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True) - t0 = time.time() - for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS): - j = JS[tag] - add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af, - printed=False, note="复用 电机.STEP") - print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True) - print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True) - return JS - - -CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"] -SEP = 74.0 - - -def explode_chain(p, JS, cents, gmean): - """爆炸图偏移:立柱垂直阶梯拉开,底座前后防呆拉开,电机沿轴拉出,机械臂整体随上段分离""" - nm = p["name"] - if nm == "P01_A_立柱下段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 0.0] - if nm == "P01_B_立柱中段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 60.0] - if nm == "P01_C_立柱上段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 120.0] - if nm == "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": - return [0.0, 0.0, 0.0] - if nm == "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": - return [65.0, 0.0, 0.0] - if nm == "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": - return [-65.0, 0.0, 0.0] - if nm == "P03_肩吊臂_螺旋体35%_墙5底5_竖打": - return [0.0, 0.0, 120.0] - - grp = AB.GRP.get(p["name"], "root") - if grp == "root": - return [0.0, 0.0, 0.0] - k = CHAIN.index(grp) - off = [0.0, 0.0, 120.0 - SEP * k] - j = JS.get(grp) - if j: - w = 0.0 - if "半联轴器" in nm or "抱箍" in nm: - w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0) - elif nm.startswith("M"): - w = 62.0 - for q in range(3): - off[q] += j.d[q] * w - return off - - -if __name__ == "__main__": - JS = build() - - print("=" * 80, flush=True) - print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) - print("=" * 80, flush=True) - bad = 0 - printed_list = [p for p in PARTS if p["printed"]] - total_pr = len(printed_list) - for idx, p in enumerate(printed_list, 1): - n = len(solids_of(p["shape"])) - if n != 1: - bad += 1 - status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌" - print(f" [{idx}/{total_pr}] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) - if bad == 0: - print(f"-> 单实体检查全部通过!{total_pr}/{total_pr} 构件均为完美单一流形拓扑 ✓\n", flush=True) - else: - print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) - - AB.explode_vec = explode_chain - bom = AB.export(JS) - - # 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例, - # 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD) - try: - import screw_mesh - screw_mesh.main() - except Exception as e: - print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True) - - cl = AB.collide() - - try: - from build_viewer import rebuild_viewer - rebuild_viewer() - except Exception as e: - print("rebuild_viewer error:", e, flush=True) - - # 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉), - # 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算) - try: - sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), - "..", "scratch")) - import ws_sweep - import gen_env_mesh - t_env = time.time() - ws_sweep.main() - gen_env_mesh.main() - from build_viewer import rebuild_viewer as _rbv - _rbv() - print(f"-> 可达包络已自动注入查看器(+{time.time()-t_env:.1f}s)✓\n", flush=True) - except Exception as e: - print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):", - e, flush=True) - - print("=" * 80, flush=True) - print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) - print("=" * 80, flush=True) - sys.stdout.flush() - os._exit(0) diff --git a/.bak/arm_model.py.bak20261005c b/.bak/arm_model.py.bak20261005c deleted file mode 100644 index b4696c9..0000000 --- a/.bak/arm_model.py.bak20261005c +++ /dev/null @@ -1,1848 +0,0 @@ -# -*- coding: utf-8 -*- -"""机器人手臂深度圆角优化版 V2:彻底消除所有棱角 -针对用户反馈的三大问题区域进行深度优化: -1. 蓝色零件(P04肩叉)顶部方形突起 → 全圆角处理 -2. 绿色/青色零件(P05/P08)底部和侧面棱角 → Loft外轮廓圆角 -3. 所有连接处的尖锐过渡 → 大圆角平滑过渡 -4. 橙色零件(P06/P07)所有方形边缘 → 完整圆角系统 -""" -import math -import os -import sys -import time - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -import arm_build as AB -from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS) -from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector, - FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK, - BORE_R, CB_R, PCB_W, D2R, T, R, PARTS) -import human_layout as HL -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1, - Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6, - Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1, - X_FORK_GAP, X_BAY_EXT, - Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, - Y_M6, Y_CHEST, X_CHEST, FOOT_H) - -VERSION = os.environ.get("ARMVER", "v2") -ITER = int(os.environ.get("ARMITER", "3")) -AB.VERSION = VERSION -AB.ITER = ITER - -Y_MID = Y_AXIS -BOSS = COVER_Z + 0.2 -DISC_R = 40.0 -SLEEVE_R = 35.0 - -# 深度圆角优化参数 -CLEARANCE_GAP = 2.0 # 关节间隙 2.0mm -FILLET_TINY = 1.5 # 微小圆角(螺孔边缘) -FILLET_SMALL = 3.0 # 小圆角(功能面) -FILLET_MEDIUM = 6.0 # 中圆角(结构过渡) -FILLET_LARGE = 10.0 # 大圆角(主要轮廓) -FILLET_XLARGE = 15.0 # 超大圆角(视觉主导区域) -CHAMFER_EDGE = 2.0 # 边缘倒角 - - -def Xcyl(r, x0, x1, py, pz): - return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz)) - - -def Xplate(pts_yz, x0, x1): - prof = [(-p[1], p[0]) for p in pts_yz] - return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0)) - - -def XB(a0, a1, y0, y1, z0, z1): - return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1) - - -# ------------------------------------------------------------------ 减重(镂空)-------- -# 三个手法件(P04/P06/P07)在关节根部都留着一大块"实心过渡料":壁厚最深到 15~17.5mm, -# 而 gyroid 填充只有 25~35%,等于用整块实心去抗一个安全系数 19~122 的截面。 -# 下面两个 helper 把这块料掏掉,规则与 P03 的"帐篷形减重窗"一致: -# 1) 不留封闭空腔 —— 减重腔要么朝上敞开,要么是有进出口的窗,打印不憋气、不积料; -# 2) 斜面自支撑 —— 斜面与水平面夹角 >= 52°(与竖直面夹角 <= 38°),免支撑; -# 3) 内角 R4 圆角 —— 挖出来的洞会把圆截面变成尖角,Kt 从 ~1.2 抬到 ~2.5, -# 圆角把应力集中压回去; -# 4) 只吃"距表面 > 8mm"的厚料,四周至少留 8mm 壁 —— 受力纤维一根不碰。 - -HOLLOW_WALL = 8.0 # 减重腔与原外表面的最小间距(= 留下的最小壁厚) -HOLLOW_FILLET = 4.0 # 减重腔内角圆角,与 P03 竖板减重窗一致 - -# 三处减重腔的开度。这三个件都是绕 Y 轴弯,弯矩在 X 方向拉压,距中性面越远的料 -# 贡献越大,所以"少挖、挖窄"是恢复强度最省料的做法。 -# 数值由 scratch/tune_hollow.py 按"槽区最差安全系数 >= 无槽基线的 95%"扫出来: -# P04 档1 X23..35 Y102..154 Z>=525 -> 95% 基线,保留 69% 的减重量 -# P07 档1 X−3..43 Y103..153 Z>=255 -> 96% 基线,保留 76% -# P06 档1 抬高窗底到 Z362(不是收窄) -> 100% 基线,保留 56% -# 窗底原来压在 Z358,而该处截面安全系数最低;抬高 4mm 比横向收窄 8mm 划算得多。 -P04_CH = dict(x0=23.0, x1=35.0, y0=102.0, y1=154.0, z_floor=525.0) # 肩叉立柱 -P07_CH = dict(x0=-3.0, x1=43.0, y0=103.0, y1=153.0, z_floor=255.0) # 小臂顶板 -P06_WIN_XZ = [(3.0, 362.0), (37.0, 362.0), (37.0, 371.0), - (28.0, 374.0), (12.0, 374.0), (3.0, 371.0)] # 大臂过渡块 -P06_HOLLOW_WIN = False # 2026-10-03 起关闭:拱形窗正面看是贯通黑洞,不雅观(详见 p_upper_bone 5c) - - -def hollow_channel(x0, x1, y0, y1, z_floor, z_open, tag="", fil=HOLLOW_FILLET): - """朝上敞开的减重槽:从 z_open 往下切到 z_floor,槽底水平(朝上受支撑)、侧壁竖直。 - - 竖打(模型 Z = 打印 Z)下这个形状是零支撑的:槽底是水平面、槽壁全是竖直面, - 顶部完全敞开因而不构成封闭空腔。切完该件变成 U 形槽钢而不是实心料: - 抗弯靠两侧 X 向腹板(正好落在弯矩拉压区),中间中性层掏空几乎不损失 I。 - - x0/x1/y0/y1 用世界坐标;z_open 取一个保证贯通顶面的值。 - """ - tool = bx(x0, x1, y0, y1, z_floor, z_open) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Z").fillet(fil).val() - except Exception as e: - print(" [减重槽] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重槽] %s: X%.1f..%.1f Y%.1f..%.1f 自 Z%.1f 向上敞开 (四周留壁 >=%.0fmm)" - % (tag, x0, x1, y0, y1, z_floor, HOLLOW_WALL), flush=True) - return tool - - -def hollow_tent_window(pts_xz, y0, y1, tag="", fil=HOLLOW_FILLET): - """帐篷形减重窗:XZ 平面画轮廓,沿 Y 双向切穿。 - - 轮廓下宽上窄(拱形)——窗顶上方的料形成一个尖顶,斜面与水平夹角 >= 52° 自支撑, - 只有顶部一小段平顶需要桥接(<=20mm,与 P03 同)。轮廓在 X 方向不能吃满整个截面, - 两侧必须留墙,否则该高度上零件会被切断、失去单实体。 - """ - # XZ 工作平面的法线是 -Y,extrude 会朝 -Y 走,所以起点放在 y1 才能覆盖 y0..y1 - tool = (cq.Workplane("XZ", origin=(0, y1, 0)).polyline(pts_xz).close() - .extrude(y1 - y0).val()) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Y").fillet(fil).val() - except Exception as e: - print(" [减重窗] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重窗] %s: XZ 轮廓 %d 点, 沿 Y %.1f..%.1f 切穿 (帐篷形, 斜面自支撑)" - % (tag, len(pts_xz), y0, y1), flush=True) - return cq.Shape(tool.wrapped) - - -# 各关节"电机前端夹紧"统一配方(J2/J3/J4/J5 通用,J1 由 P03 自己那套 S04 凸台实现)。 -# 电机前端盖外端面:Y 轴关节(J2/J4/J5)在 Y_COVER=156,压盖朝 +Y 生长; -# Z 轴关节(J3,roll 电机,轴向 -Z)在 Z_R-56,压盖朝 -Z 生长(-Z 侧是从动件)。 -# 压盖背面与之 0.0 间隙面压,螺钉只提供压紧力。 -# 沉孔相位按【关节局部相位】定义(BCD55,72.2°+90k):换算到全局后 Y 轴关节是 -# 17.8°+90k(与 P03→M1 的 S04 一致),Z 轴关节是同一组数落在全局 (x,y) 平面, -# 两种轴向的物理指向完全一致 —— 全机同一套孔位约定,改一处必须同步另一处。 -CLAMP_BCD = 27.5 -CLAMP_CLOCK = (17.8, 107.8, 197.8, 287.8) # Y 轴关节的全局 (x,z) 相位 -CLAMP_CLOCK_LOCAL = tuple((360.0 - a) % 360.0 for a in CLAMP_CLOCK) # = 72.2+90k -CLAMP_ROOT = 0.5 # 根部薄环厚度:与前端定位环前端面共面,保证熔接成一体 -CLAMP_T = 5.0 # 压盖本体厚度 -CLAMP_CLR = 1.0 # 从动件让位间隙:与 Y 轴关节 hub_gap=6.0 同一个道理 -CLAMP_BACKOFF = CLAMP_T + CLAMP_CLR - CLAMP_ROOT # =5.5 从动件毂面整体后移量 - - -def motor_front_clamp(px, pz, plate_t=CLAMP_T, tag="", axis="Y", py=0.0, z_face=None): - """电机前端压盖:把"只被后端安装板单面锁死"改成前后双向面压夹紧。 - - 原设计里每个关节的电机都只有后端板 4xM3x10(S02)锁在尾端面上,前端盖完全裸露, - 大扭矩下电机绕自身轴在 Ø60.3 座孔里点头/翘起。这里在电机前端盖外端面加一枚 - 与后端板同径的 R35 压盖,用 4xM3x8(BCD55)在前端盖上再夹一次 —— 电机被前后两块 - 同心 Ø60.3 端面双向夹死,倾斜自由度归零;夹紧力全部走面压,螺钉不参与定位。 - - 两个轴向共用同一套尺寸,只换刀具体系(不重复实现任何半径/相位): - axis="Y"(J2/J4/J5):关节轴过 (px, pz),前端盖外端面在 Y_COVER,压盖朝 +Y 长; - axis="Z"(J3 roll,轴向 -Z):关节轴过 (px, py),前端盖外端面在 z_face,压盖朝 -Z 长。 - - 局部坐标(s 从前端盖外端面起算,s>0 一律"背离电机",两个轴向同号): - - 压盖 R35,s 从 -CLAMP_ROOT(与前端定位环前端面共面,保证熔接成一体)到 plate_t; - - 背面精确落在 s=0,不让根部那 0.5mm 啃到端盖 R30.15 的外圆; - - Ø37 中央让位孔(R18.5)让开从动件的直锁凸台 R17.8 与 3xS01 输出螺钉头; - - 4xM3x8:Ø3.4 通孔 + Ø6.0 沉孔深 3.0(自前端面沉到 s=2),圆柱头全沉入, - 头面压孔口台阶,中间 2.0mm 压盖本体受压。 - """ - if axis == "Y": - def C(r, s0, s1, du=0.0, dv=0.0): - return Ycyl(r, Y_COVER + s0, Y_COVER + s1, px + du, pz + dv) - elif axis == "Z": - def C(r, s0, s1, du=0.0, dv=0.0): - return cyl(r, z_face - s1, z_face - s0, px + du, py + dv) - else: - raise ValueError("motor_front_clamp: axis 只能是 Y 或 Z,收到 %r" % (axis,)) - - s0, s1 = -CLAMP_ROOT, plate_t - fp = C(35.0, s0, s1) - fp = fp.cut(C(18.5, s0 - 0.5, s1 + 0.5)) # Ø37 中央让位孔 - fp = fp.cut(C(30.15, s0 - 0.5, 0.0)) # Ø60.3 让位槽 - for deg in CLAMP_CLOCK_LOCAL: - rad = math.radians(-deg) # 全局相位 = -局部相位 - fu, fv = CLAMP_BCD * math.cos(rad), CLAMP_BCD * math.sin(rad) - fp = fp.cut(C(3.0, 2.0, s1 + 0.5, fu, fv)) # Ø6.0 沉孔 - fp = fp.cut(C(1.7, s0 - 0.5, 2.0, fu, fv)) # Ø3.4 通孔 - where = ("Y%.1f..%.1f" % (Y_COVER - CLAMP_ROOT, Y_COVER + plate_t)) if axis == "Y" \ - else ("Z%.1f..%.1f" % (z_face - plate_t, z_face + CLAMP_ROOT)) - print(" [前端压盖] %s: R35 %s, 4xM3x8 BCD55@%s (%s 轴)" - % (tag, where, "/".join("%.1f" % d for d in CLAMP_CLOCK), axis), flush=True) - return fp - - -def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0): - """优化直连法兰:增加导向倒角""" - disc = cyl(r_disc, 56.5, z_out) - boss = cyl(17.8, 54.942, 56.5) - chamfer_cone = cyl(18.5, 54.5, 54.942) - shape = shape.fuse(j.L(disc)).fuse(j.L(boss)).cut(j.L(chamfer_cone)) - shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, z_out + 40.0, u, v) - shape = shape.cut(j.L(th)).cut(j.L(cb)) - return shape - - -# ========== 躯干部分 ========== -# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ========== -X_COL = -115.0 -Y_COL = Y_AXIS # 128.0 -Z_LOCK = 463.0 # 横向防脱锁紧螺钉高度(立柱后壁 → 榫头,M4x16 自攻) - - -def p_chest(): - """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱): - 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方; - 2. 顶部接收 P03 43.4x43.4x15mm 定位榫头(44x44 R4 内腔)、4x 竖向安装孔 + 1x 横向防脱锁紧孔 - (全部 Ø3.9 直孔:M4x16 从外部直接拧入自攻成型,无过孔/无沉孔/无预埋); - 3. 底部 50x50x12mm 阶梯榫头插入 P02 榫座:环形台肩(66->50)面承载轴向力,槽壁全周承载剪切; - 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); - 5. 外棱角全部采用 R=6mm 圆角。 - """ - z_bot = 22.0 - z_top = Z_CHEST1 # 470.0 - - col = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot)) - .rect(66.0, 66.0) - .extrude(z_top - z_bot) - .edges("|Z").fillet(6.0)).val() - s = cq.Shape(col.wrapped) - - cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot + 10.0)) - .rect(44.0, 44.0) - .extrude(z_top - z_bot + 1.0) - .edges("|Z").fillet(4.0)).val() - s = s.cut(cav) - - # 底部阶梯榫头:插入 P02 榫座后环形台肩坐实,轴向力走面压而非 M5 螺纹 - tenon = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(50.0, 50.0).extrude(z_bot - 10.0) - .edges("|Z").fillet(4.0)).val() - s = s.fuse(tenon) - - # 底部 M5 夹紧孔(贯通榫头):X 向非对称 (−20/+16) 与榫座孔位配对防反装/防转 90° - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - s = s.cut(cyl(2.75, 9.99, z_bot + 15.0, xx, yy)) - # 顶部 4x 安装孔:统一 Ø3.9 直孔(M4x16 直接拧入自攻成型;只打这一个孔,无过孔/无沉孔),深 20mm - # 位于 66x66 顶环 11mm 实体壁内(内腔 22 → 外壁 33),避开 44x44 内腔与 R4 内腔圆角; - for xx in (X_COL - 27.5, X_COL + 27.5): - for yy in (Y_COL - 13.5, Y_COL + 13.5): - s = s.cut(cyl(1.95, z_top - 20.0, z_top + 0.01, xx, yy)) - # 横向防脱锁紧孔(后壁 → 榫头):Ø3.9 贯穿 11mm 后壁 + 榫头同径(M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - 34.0, X_COL - 20.0, Y_COL, Z_LOCK)) - - w_bot = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 105.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_bot) - - w_top = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 375.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_top) - return s - - -def split_p01_poka_yoke(p01): - """拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印): - 带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁, - 单边配合间隙 0.5)、以及统一 Ø3.9 直孔的分段对锁孔(M4x16 从外面直接拧入自攻成型, - 母口壁与公榫壁同径,无过孔/沉孔/预埋,前后两面对称各一颗); - 孔位相对立柱中心 −16 / +22 非对称(物理防反装防呆);本体 3D 凹刻朝向标识。 - """ - z_bot = 22.0 - z_s1 = 172.0 - z_s2 = 321.0 - z_top = 470.0 - h_lap = 16.0 - - # 非对称对锁孔位(相对立柱中心偏心 -16mm 与 +22mm,偏心差 6mm,反装无法穿入螺栓) - xx_bolts = (X_COL - 16.0, X_COL + 22.0) - - # 分段对锁孔:统一 Ø3.9 直孔(M4x16 从外面直接拧入自攻成型;母口壁与公榫壁同径, - # 没有过孔/沉孔等其它孔),前后两面对称;杆尖伸过榫壁的一段在内腔侧留 Ø4.5 让位。 - def _seam_holes(part, xx, z): - return (part.cut(Ycyl(1.95, Y_COL - 33.6, Y_COL - 21.4, xx, z)) - .cut(Ycyl(1.95, Y_COL + 21.4, Y_COL + 33.6, xx, z)) - .cut(Ycyl(2.25, Y_COL - 21.7, Y_COL - 15.5, xx, z)) - .cut(Ycyl(2.25, Y_COL + 15.5, Y_COL + 21.7, xx, z))) - - # --- P01_A (下段) --- - 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(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() - tongue1 = cq.Shape(tongue1_outer.wrapped).cut(tongue1_cav) - p01_a = p01_a_base.fuse(tongue1) - - z_bolt1 = z_s1 + h_lap / 2.0 # 180.0 - for xx in xx_bolts: - p01_a = _seam_holes(p01_a, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - - # Engrave the front (-Y) flat face, reading VERTICALLY bottom-to-top along - # the column (user request): local +X = world +Z, local +Y = world -X, so - # the trailing "▶" glyph renders pointing world-up (+Z). - front_label_plane_a = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 120.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_a = (cq.Workplane(front_label_plane_a) - .text("P01-A TOP ▶", 9.0, -0.8)).val() - p01_a = p01_a.cut(cq.Shape(label_a.wrapped)) - - # --- P01_B (中段) --- - cut_below_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s1) - 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.9)).val() - p01_b_base = p01_b_base.cut(pocket1) - - tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2)) - .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() - tongue2 = cq.Shape(tongue2_outer.wrapped).cut(tongue2_cav) - p01_b = p01_b_base.fuse(tongue2) - - z_bolt2 = z_s2 + h_lap / 2.0 # 329.0 - for xx in xx_bolts: - p01_b = _seam_holes(p01_b, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - p01_b = _seam_holes(p01_b, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_b = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 240.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_b = (cq.Workplane(front_label_plane_b) - .text("P01-B TOP ▶", 9.0, -0.8)).val() - p01_b = p01_b.cut(cq.Shape(label_b.wrapped)) - - # --- P01_C (上段) --- - 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.9)).val() - p01_c = p01_c_base.cut(pocket2) - for xx in xx_bolts: - p01_c = _seam_holes(p01_c, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_c = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 390.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_c = (cq.Workplane(front_label_plane_c) - .text("P01-C TOP ▶", 9.0, -0.8)).val() - p01_c = p01_c.cut(cq.Shape(label_c.wrapped)) - - return p01_a, p01_b, p01_c - - -def make_p02_poka_yoke(): - """将 P02 拆分为三件模块化组件(均严格 <= 195x190mm,适配 200x200mm 打印床): - 1. P02_Center (底座中核心): 195x190x70mm,完整容纳立柱套筒与前后三角斜撑筋,核心应力区无接缝; - 2. P02_Front (前向力臂): 145x190x22mm,宽跨距 104mm 双榫头 + 预埋 M5 螺母槽; - 3. P02_Rear (后向力臂): 45x190x22mm,窄跨距 80mm 双榫头(跨距差达 24mm 物理防呆,绝不混装)。 - """ - z0 = 0.0 - z1 = 22.0 - y_width = 190.0 - - f_tenon_w = 44.0 - f_tenon_y_span = 52.0 - r_tenon_w = 32.0 - r_tenon_y_span = 40.0 - - # 1. P02_Center (X = -195.0 .. 0.0, 195x190mm) - x_c_min = -195.0 - x_c_max = 0.0 - x_c_len = x_c_max - x_c_min - x_c_mid = (x_c_min + x_c_max) / 2.0 - - c_plate = (cq.Workplane("XY", origin=(x_c_mid, Y_COL, z0)) - .rect(x_c_len, y_width).extrude(z1) - .edges("|Z and X").fillet(8.0) - .edges(">Z").fillet(2.0)).val() - p02_c = cq.Shape(c_plate.wrapped) - - # Socket collar (Z: 22..70mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0).extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0).edges(">Z").chamfer(2.0)).val() - p02_c = p02_c.fuse(cq.Shape(collar_outer.wrapped)) - - # Front & Rear triangular gusset ribs - f_pts = [(-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0)] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(f_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(f_rib.wrapped)) - - r_pts = [(-155.0, z1), (-195.0, z1), (-195.0, z1 + 14.0), (-155.0, 70.0)] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(r_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(r_rib.wrapped)) - - # Front wire guide - wg_pts = [(-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5)] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(wg_pts).close().extrude(9.0, both=True)).val() - p02_c = p02_c.cut(wire_guide) - - # Column socket — stepped mortise: upper 66.6 bore for the 66 box walls, - # lower 50.6 recess (floor 0.6 low) for the 50x50 bottom tenon; the recess - # rim at z=22 is the axial seat, so column weight bears on face, not threads. - socket_upper = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(66.6, 66.6).extrude(z_collar_top - z1 + 1.0) - .edges("|Z").fillet(6.3)).val() - p02_c = p02_c.cut(socket_upper) - socket_lower = (cq.Workplane("XY", origin=(X_COL, Y_COL, 9.4)) - .rect(50.6, 50.6).extrude(z1 - 9.4) - .edges("|Z").fillet(4.3)).val() - p02_c = p02_c.cut(socket_lower) - - # Vertical M5 clamp bolts (offsets match the asymmetric column tenon holes: - # X −20/+16 blocks 180° flip, the ±18 Y span blocks 90° rotation). - # Threads engage the tenon 15mm — clamp only, axial load rides on the seat. - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - p02_c = p02_c.cut(cyl(2.75, -0.01, 10.5, xx, yy)).cut(cyl(5.0, -0.01, 6.0, xx, yy)) - - # Center clamp slots - for cy in (Y_COL - 75.0, Y_COL + 75.0): - plane = cq.Plane(origin=(x_c_mid, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_c = p02_c.cut(slot) - - p02_c = p02_c.cut((cq.Workplane("XY", origin=(X_COL, Y_COL - 70.0, -0.01)).rect(28.0, 80.0).extrude(6.0)).val()) - - # Front mortise pockets - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - front_mortise = (cq.Workplane("XY", origin=(-10.0, yy, 4.8)) - .rect(21.0, f_tenon_w + 0.8).extrude(12.6).edges("|Z and X").fillet(3.0)).val() - p02_c = p02_c.cut(rear_mortise) - p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -185.0, yy)) - - # Debossed text on P02_Center top face. The mid-plate line (y=Y_COL) is - # hidden behind the column collar and the gusset ribs, so both labels sit on - # the flat -Y side strip (y = Y_COL-68) where they stay in full view from the - # label side: REAR on the rear half, FRONT on the front half, clear of the - # clamp slot (x -101..-94) and the M5 screw counterbores. - txt_c_f = (cq.Workplane("XY", origin=(-49.0, Y_COL - 68.0, z1)).text("▲ FRONT (前向) ▲", 9.0, -0.8)).val() - txt_c_r = (cq.Workplane("XY", origin=(-146.0, Y_COL - 68.0, z1)).text("▲ REAR (后向) ▲", 9.0, -0.8)).val() - p02_c = p02_c.cut(cq.Shape(txt_c_f.wrapped)).cut(cq.Shape(txt_c_r.wrapped)) - - # 2. P02_Front (X = 0.0 .. 145.0, 145x190mm) - x_f_len = 145.0 - x_f_mid = 72.5 - f_plate = (cq.Workplane("XY", origin=(x_f_mid, Y_COL, z0)) - .rect(x_f_len, y_width).extrude(z1) - .edges("|Z and >X").fillet(16.0).edges(">Z").fillet(2.0)).val() - p02_f = cq.Shape(f_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(120.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_f = p02_f.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-9.5, yy, 5.25)) - .rect(20.0, f_tenon_w - 0.2).extrude(11.5).edges("|Z and Z").fillet(2.0)).val() - p02_r = cq.Shape(r_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(-215.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_r = p02_r.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * r_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-185.5, yy, 5.25)) - .rect(20.0, r_tenon_w - 0.2).extrude(11.5).edges("|Z and >X").fillet(2.5).edges(">X").chamfer(1.5)).val() - p02_r = p02_r.fuse(cq.Shape(tongue.wrapped)) - p02_r = p02_r.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - nut_pocket = (cq.Workplane("XY", origin=(-185.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val() - p02_r = p02_r.cut(nut_pocket) - - # Same +Y rotation as the front plate; "▼" points local -Y = world +X, - # i.e. toward the centre joint. Along +Y the ~73 mm text fits the 190 mm - # plate width instead of overhanging its 45 mm X span. - txt_r = (cq.Workplane(cq.Plane(origin=(x_r_mid, Y_COL, z1), xDir=(0, 1, 0), - normal=(0, 0, 1))) - .text("P02-REAR ▼ JOIN", 7.5, -0.8)).val() - p02_r = p02_r.cut(cq.Shape(txt_r.wrapped)) - - return p02_c, p02_f, p02_r - - -def p_base_foot(): - """P02_底脚 (重型防倾翻超长力臂底座 + 双向大截面三角加强斜撑筋): - 1. 底盘尺寸大幅扩展为 385x240x22mm (X: -240.0..+145.0, Y: 8.0..248.0); - - 前向延伸至 X = +145.0mm (在手臂垂直投影前方 91.5mm),彻底杜绝手臂前伸与抓握重物时的前倾翻; - - 后向延伸至 X = -240.0mm (在立柱后方 92.0mm),抵御大动态回程后仰力矩; - 2. 立柱插接套筒由 12mm 浅槽大幅升级为 48mm 高加固套筒 (Z: 22.0..70.0mm,外壁厚 7mm); - 3. 前向大三角加强斜撑筋 (Z: 68.0->22.0mm, X: -75.0->-15.0mm, 宽 38mm),抗弯力臂长达 60mm,留足安全间隙不干涉手臂运动; - 4. 后向大三角加强斜撑筋 (Z: 70.0->22.0mm, X: -155.0->-225.0mm, 宽 38mm),抗后仰力臂长达 70mm; - 5. 前斜撑表面设 18mm 宽平滑走线导槽,与 P01 下部走线窗口自然贯通; - 6. 6 组 M6 工业工作台紧固腰槽 (28x7mm) + 底部隐蔽走线槽; - 7. 四周 R=18mm 大圆角过渡与 2.5mm 上边缘倒角。 - """ - z0 = 0.0 - z1 = 22.0 - x_rear = -240.0 - x_front = 145.0 - x_len = x_front - x_rear # 385.0 - x_mid = (x_rear + x_front) / 2.0 # -47.5 - y_width = 240.0 - - # 1. 主底盘 (385 x 240 x 22 mm) - base_plate = (cq.Workplane("XY", origin=(x_mid, Y_COL, z0)) - .rect(x_len, y_width) - .extrude(z1) - .edges("|Z").fillet(18.0) - .edges(">Z").fillet(2.5)).val() - s = cq.Shape(base_plate.wrapped) - - # 2. 立柱外包加固套筒 (Z: 22.0..70.0 mm, 外廓 80x80mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0) - .extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0) - .edges(">Z").chamfer(2.0)).val() - s = s.fuse(cq.Shape(collar_outer.wrapped)) - - # 3. 前向抗倾翻三角加强斜撑筋 (Front Triangular Gusset Rib) - f_pts = [ - (-75.0, z1), - (-15.0, z1), - (-15.0, z1 + 4.0), - (-75.0, 68.0), - ] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(f_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(f_rib.wrapped)) - - # 4. 后向抗倾翻三角加强斜撑筋 (Rear Triangular Gusset Rib) - r_pts = [ - (-155.0, z1), - (-225.0, z1), - (-225.0, z1 + 4.0), - (-155.0, 70.0), - ] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(r_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(r_rib.wrapped)) - - # 5. 前斜撑表面走线导槽 (与 P01 前走线窗 Z=70..140mm 自然顺接) - wire_guide_pts = [ - (-75.5, z1 + 1.0), - (-14.0, z1 + 1.0), - (-14.0, z1 + 4.0), - (-75.5, 68.5), - ] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(wire_guide_pts).close() - .extrude(9.0, both=True)).val() - s = s.cut(wire_guide) - - # 6. 立柱深插槽内腔 (Pocket: 66.6 x 66.6 mm, 留 0.3mm 精密滑配间隙) - socket_box = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(66.6, 66.6) - .extrude(z_collar_top - 10.0 + 1.0) - .edges("|Z").fillet(6.3)).val() - s = s.cut(socket_box) - - # 7. 4x M5 底部垂直紧固螺栓孔与沉孔 - for xx in (X_COL - 21.0, X_COL + 21.0): - for yy in (Y_COL - 21.0, Y_COL + 21.0): - th = cyl(2.75, -0.01, 10.5, xx, yy) - cb = cyl(5.0, -0.01, 6.0, xx, yy) - s = s.cut(th).cut(cb) - - # 8. 6 组 M6 工业工作台紧固腰槽 (28 x 7 mm) - slot_positions = [ - (-215.0, Y_COL - 85.0), (-215.0, Y_COL + 85.0), # 后部两角 - (-47.5, Y_COL - 95.0), (-47.5, Y_COL + 95.0), # 中部两侧 - (120.0, Y_COL - 85.0), (120.0, Y_COL + 85.0), # 前部两角 - ] - for cx, cy in slot_positions: - plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - s = s.cut(slot) - - # 9. 底部隐蔽走线通槽 - wire_ch = (cq.Workplane("XY", origin=(X_COL, Y_COL - 80.0, -0.01)) - .rect(28.0, 100.0) - .extrude(6.0)).val() - s = s.cut(wire_ch) - return s - - -def p_girdle(j1): - """P03_肩关节安装悬臂 (轻量化板式桁架版 + 侧开式电机托架): - 1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm); - 2. 主干由 66mm 实心块改为双侧 6mm 桁架竖板 (Y = Y_ARM ± 27..33),外轮廓保持旧版 - "主干 ∪ 侧筋"并集不变 —— 只减材料不加体积,零新增干涉风险;实心 728 -> 286 cm³ (-61%); - 3. 竖板帐篷形减重窗 (顶/底斜面 ≥52°,自支撑;仅 20mm 平顶可桥接); - 4. 底部 9mm 承力鞍座 + 43.4x43.4 定位榫头 (配 P01_C 44x44 内腔,单边 0.3mm 间隙); - 鞍座底面 66x66 与立柱顶环带承压面积 2403mm² —— 竖向载荷走面压,螺栓只防上抬/防滑; - 5. 4x 安装孔 (X_COL±27.5, Y_COL±13.5):Ø3.9 直孔,M4x16 从上方直接拧入自攻成型 - (鞍座与立柱顶环同径,无过孔/沉孔/预埋),可拆装; - 6. 下腹中央龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰下缘) + 顶部横向锁紧环抱筋 (66x9x10mm, - 与两侧竖板外面齐平、顶面压在筋板脊线上,仅顶前棱一道 2mm 倒角); - 7. 侧开式电机托架 (+Y侧全开, X: -51.0..11.5,前端随 X_BAY_EXT 前延 3mm), - M1 电机可从侧方斜插卡入装配(插槽比电机长 3mm),由后端法兰 4xM3x16 锁死; - 电机舱 Ø60.7 从后端直通到前端面(2026-10-02 用户裁定"去掉台阶改成平的":无环形台阶、 - 无喇叭锥;原前置止脱锁紧法兰 4xM3 随之取消,电机轴向由后端 4xM3x16 约束); - 9. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通; - 10. Ø26mm 中央走线孔;竖板间为开放腔体(无封闭空腔),M3 螺丝刀通道直接走腔体,拧紧更顺手。 - """ - X_MOUNT = Z_R1 # -51.0 (Rear face of M1) - T_FLANGE = 10.0 # 10mm thick high-strength mounting flange - X_BACK = X_MOUNT - T_FLANGE # -61.0 - Y_ARM = Y_AXIS # 128.0 - Z_ARM = Z_RL # 610.0 - Y_P_IN = 27.0 # 桁架竖板内表面 (Y_ARM ± 27) - Y_P_OUT = 33.0 # 桁架竖板外表面 (与旧主干齐平) - T_SADDLE = 9.0 # 底部鞍座厚度 - TENON = 43.4 # 定位榫头边长(P01_C 内腔 44x44 R4,单边留 0.3mm) - WIN_R = 4.0 # 竖板减重窗内角圆角(消除内尖角应力集中,Kt 由 ~2.5 降到 ~1.2) - BOLT_DX, BOLT_DY = 27.5, 13.5 # 4x 安装孔相对立柱中心偏移:落在顶环 11mm 实体壁内 - - # 1. 主安装法兰盘 (74x74mm, 四角倒圆 R=8mm) - # 背面那圈 2mm 倒角已去掉:板料只伸进法兰 0.1mm,倒角面与板背面相交会在 - # Z≈574.9 挤出一批 2mm 短棱,正是这批短棱让整条交接内棱网络倒不出圆角; - # 同时它跨过 R8 角部圆弧,就是角上那道"卷起来"的怪倒角。 - flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM)) - .rect(74.0, 74.0) - .extrude(T_FLANGE) - .edges("|X").fillet(8.0)).val() - s = cq.Shape(flange.wrapped) - - # 2. 双侧桁架竖板 (6mm):旧"斜挑支撑主干"与"大三角加强筋"轮廓同段挤出 → 自动并集成单块侧板 - # 主干的后缘两点取在加强筋斜边的延长线上,使外轮廓顶部成为一整条直线 - # (否则后缘 (-148,550) 处有 5° 折点、留下 5mm 台阶)。 - # Z_CREST = 侧筋脊线高度,同时是顶筋 2mm 顶前倒角的下沿:倒角从 (X-71,Z_CREST) - # 斜上到 (X-69,Z_CREST+2) = 顶筋顶面,斜坡面与倒角面几乎相切,顶部读起来是一整张面。 - Z_CREST = Z_ARM + 33.0 # 643(初版 642;顶筋整体上移 3mm 让出扳手空间后跟着抬) - - RIB_SLOPE = (Z_CREST - 575.0) / ((X_BACK - 10.0) - (X_COL - 15.0)) - - def rib_line_z(x): - return Z_CREST + RIB_SLOPE * (x - (X_BACK - 10.0)) - - trunk_pts = [ - (X_COL - 33.0, Z_CHEST1), - (X_COL + 33.0, Z_CHEST1), - (X_COL + 33.0, 505.0), - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_ARM - 5.0), - (X_COL - 10.0, rib_line_z(X_COL - 10.0)), - (X_COL - 33.0, rib_line_z(X_COL - 33.0)), - ] - rib_pts = [ - (X_COL - 33.0, Z_CHEST1), # 底边两端与主干/鞍座对齐 (±33):原来收进 3mm, - (X_COL + 33.0, Z_CHEST1), # 会在鞍座顶面上留下 0.7/3mm 短棱,卡住整条底棱圆角 - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_CREST), - (X_BACK - 10.0, Z_CREST), # 脊线 (-71,643):正好落在顶筋前面与 2mm 倒角下沿的交点 - (X_COL - 15.0, 575.0), - (X_COL - 30.0, 530.0), - ] - for sign in (-1, 1): - ya = Y_ARM + sign * Y_P_IN - yb = Y_ARM + sign * Y_P_OUT - y0, y1 = min(ya, yb), max(ya, yb) - for prof in (trunk_pts, rib_pts): - s = s.fuse(cq.Shape(Yplate(prof, y0, y1).wrapped)) - - # 3. 竖板减重窗 (帐篷形:斜面 ≥52° 自支撑、仅 20mm 平顶可桥接;避开鞍座与 M5 螺母柱) - # 6 个内角全部 R4 圆角:把内尖角应力集中 (Kt≈2.5) 降到 ≈1.2,同时圆角面仍是竖直/自支撑面 - win_pts = [ - (X_COL - 24.0, 498.0), (X_COL + 23.0, 498.0), (X_COL + 25.0, 522.0), - (X_COL + 10.0, 545.0), (X_COL - 10.0, 545.0), (X_COL - 25.0, 524.0), - ] - for sign in (-1, 1): - yc = Y_ARM + sign * 0.5 * (Y_P_IN + Y_P_OUT) - tool = (cq.Workplane("XZ", origin=(0, yc, 0)).polyline(win_pts).close() - .extrude(Y_P_OUT - Y_P_IN + 1.0, both=True) - .edges("|Y").fillet(WIN_R).val()) - s = s.cut(cq.Shape(tool.wrapped)) - - # 4. 底部承力鞍座:坐实立柱顶面,把两竖板、龙骨与榫头连成整体 - s = s.fuse(bx(X_COL - 33.0, X_COL + 33.0, Y_ARM - Y_P_OUT, Y_ARM + Y_P_OUT, - Z_CHEST1, Z_CHEST1 + T_SADDLE)) - - # 5. 下腹部纵向抗弯龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰背面;交接内棱在融合后做真 3D 圆角) - keel_pts = [ - (X_COL + 33.0, Z_CHEST1), # (-82, 470) - (X_COL + 33.0, 495.0), # (-82, 495) - (X_BACK + 0.1, 573.0), # (-60.9, 573) 斜拉至法兰背面 - (X_BACK + 0.1, 594.4), # (-60.9, 594.4) 顶端贴法兰 - (X_COL + 42.1, 585.0), # (-72.9, 585) - (X_COL + 20.0, Z_CHEST1), # (-95, 470) 背面一路斜到底:原来在 (-95,480) 有个折点, - ] # 折点离鞍座顶面只 1mm,R2.5 圆角爬不上去 → 龙骨底棱整组倒不出 - s = s.fuse(cq.Shape(Yplate(keel_pts, Y_ARM - 6.0, Y_ARM + 6.0).wrapped)) - - # 7. 顶部横向锁紧环抱横筋:Y 向与两竖板外面齐平 (Y_ARM±33)、前面与筋板脊线共面 (X=-71)、 - # 顶面 Z=645 压住脊线 (Z=643) 并与 2mm 顶前倒角相切 → 顶筋与两侧脊线合成一整块。 - # 整体比初版抬高 3mm (底面 633→636):4x M3 后端沉孔 Ø6.5 跨在 Z 627.2..633.7 上, - # 底面 633 会压住孔口上沿 0.7mm、扳手让位只剩负间隙;抬到 636 后退到孔口上方 2.3mm、 - # 离螺钉轴线 5.55mm。顶面 645 不越过法兰 R8 角部圆弧在 Y=95 处的 645.9 上界。 - tie_rib = (cq.Workplane("YZ", origin=(X_BACK - 10.0, Y_ARM, Z_ARM + 30.5)) - .rect(66.0, 9.0) - .extrude(10.0) - .edges(cq.selectors.BoxSelector((X_BACK - 10.5, Y_ARM - 34.0, Z_ARM + 34.5), - (X_BACK - 9.5, Y_ARM + 34.0, Z_ARM + 35.5))) - .chamfer(2.0)).val() - s = s.fuse(cq.Shape(tie_rib.wrapped)) - - # 6.y 体-体交接凹棱:融合完成后对"凹棱"直接做真 3D 圆角。 - # 原先这里是手工沿棱 fuse 的 10 条"正方形−四分之一圆"焊珠小体(_cove),已整体删除: - # 焊珠端面是平的、悬在半空,两条互相垂直的焊珠在角上互不搭接 → 圆角"缺一块", - # 并与相邻体的棱互相穿插留 1~3mm² 碎面。真圆角在面边界自然收口、多棱相交处由 OCC - # 生成角部补片。 - from cadquery import selectors as _sel - from OCP.BRepAdaptor import BRepAdaptor_Surface - from OCP.GeomAbs import GeomAbs_Plane - from OCP.TopAbs import TopAbs_REVERSED - - def _outward(f, sh): - """平面面的外法向:先按 Orientation 修正,再用"面中心外移 1mm 是否仍在实体内"兜方向。""" - ad = BRepAdaptor_Surface(f.wrapped) - if ad.GetType() != GeomAbs_Plane: - return None - ax = ad.Plane().Axis().Direction() - n = cq.Vector(ax.X(), ax.Y(), ax.Z()) - if f.wrapped.Orientation() == TopAbs_REVERSED: - n = n * -1.0 - c = f.Center() - if cq.Solid(sh.wrapped).isInside(cq.Vector(c.x + n.x, c.y + n.y, c.z + n.z), 1e-6): - n = n * -1.0 - return n - - def _fillet_reflex(lo, hi, radii, tag, eps=0.9): - """倒出 lo~hi 区域内的凹棱(两面夹 270° 材料)。判据:从棱中点沿 (n1-n2) 外探 eps, - 落在实体内 = 凹、落在外 = 凸。区域内凹棱先整批一次倒(半径逐级降级,且倒完验拓扑: - OCC 会给出"没抛异常但面已坏"的结果),建不出时按相连棱分组逐组倒。""" - nonlocal s - - def _pick(sh): - edges = sh.Edges() - idx = {} - for i, e in enumerate(edges): - idx[e.hashCode()] = i - adj = [[] for _ in edges] - for f in sh.Faces(): - for ee in f.Edges(): - k = idx.get(ee.hashCode()) - if k is not None: - adj[k].append(f) - box = _sel.BoxSelector(lo, hi) - out = [] - for i, e in enumerate(edges): - if e.Length() <= 6.0 or len(adj[i]) != 2 or not box.filter([e]): - continue - n1 = _outward(adj[i][0], sh) - n2 = _outward(adj[i][1], sh) - if n1 is None or n2 is None: - continue - dx, dy, dz = n1.x - n2.x, n1.y - n2.y, n1.z - n2.z - ln = math.sqrt(dx * dx + dy * dy + dz * dz) - if ln < 1e-6: - continue - p = e.Center() - if cq.Solid(sh.wrapped).isInside( - cq.Vector(p.x + dx / ln * eps, p.y + dy / ln * eps, - p.z + dz / ln * eps), 1e-6): - out.append(e) - return out - - es0 = _pick(s) - if not es0: - print(" [P03 真圆角] %-14s 区域内无凹棱,跳过" % tag, flush=True) - return - - def _groups(ee): - """按共端点把凹棱连成链/网络:一组一次倒,OCC 才能在交汇处生成角部补片; - 逐条倒会在交汇处留下断口(看着就像圆角倒一半停了)。""" - pts = [e.Vertices() or [e.Center()] for e in ee] - par = list(range(len(ee))) - - def find(a): - while par[a] != a: - par[a] = par[par[a]] - a = par[a] - return a - - for i in range(len(ee)): - for j in range(i + 1, len(ee)): - if any(pi.distance(pj) < 0.05 for pi in pts[i] for pj in pts[j]): - par[find(i)] = find(j) - g = {} - for i in range(len(ee)): - g.setdefault(find(i), []).append(ee[i]) - return list(g.values()) - - def _do(edges_, rr): - """倒完立刻验拓扑:OCC 会给出"没抛异常但面已坏"的结果,必须挡下来。""" - try: - ns = cq.Shape(cq.Solid(s.wrapped).fillet(rr, edges_).wrapped) - except Exception: - return None - return ns if cq.Solid(ns.wrapped).isValid() else None - - # 先试"区域内全部凹棱一次倒"(探针实测这样能整批成型,且只重建一次, - # 不会像分批那样把同一张面反复重算坏掉);不行再按相连棱分组逐组倒。 - for rr in radii: - ns = _do(es0, rr) - if ns is not None: - s = ns - print(" [P03 真圆角] %-14s %d 条凹棱 R%.1f ✓ 整批一次成型" - % (tag, len(es0), rr), flush=True) - return - - n_ok, n_grp, left = 0, 0, len(es0) - for _ in range(24): - es = _pick(s) - if not es: - break - got = False - for g in sorted(_groups(es), key=lambda g: -sum(e.Length() for e in g)): - for rr in radii: - ns = _do(g, rr) - if ns is None: - continue - s = ns - n_ok += len(g) - n_grp += 1 - print(" [P03 真圆角] %-14s 第%d组 %d 条 R%.1f ✓" - % (tag, n_grp, len(g), rr), flush=True) - got = True - break - if got: - break - if not got: - left = len(es) - break - print(" [P03 真圆角] %-14s 共 %d 组 %d 条凹棱倒圆,剩 %d 条保持尖棱" - % (tag, n_grp, n_ok, left), flush=True) - - XR, YC, ZS = X_BACK, Y_ARM, Z_CHEST1 + T_SADDLE # -61, 128, 479 - # —— 鞍座顶面 (Z=479) 上的交接内棱:龙骨三条 + 两竖板内侧两条,一次真圆角 —— - # 半径上限 2.0:龙骨侧面 Y=122 与 M5 螺钉头外缘 Y=118.75 只隔 3.25mm, - # 原来的 R3 焊珠会顶到螺帽承压边,R2.0 留 1.25mm。 - _fillet_reflex((X_COL - 34.0, Y_ARM - 40.0, ZS - 3.0), - (X_COL + 34.0, Y_ARM + 40.0, ZS + 7.0), - (2.0, 1.5), "鞍座顶面交接") - # —— 法兰背面 (X=-61) 上的交接内棱:只倒竖板/龙骨的竖直棱 —— - # 顶筋底面那条水平棱 (Z=636) 不倒:4x M3 沉孔 Ø6.5 在 Z 627.2..633.7, - # 倒上 R2.5 会把螺钉上方 5.55mm 的扳手让位吃掉 2.5mm。 - # 上界取 631:竖板内棱被顶筋底面切成 573..636 / 636..643 两段,只倒下面长的那段 - # ——把 636..643 的 7mm 短段并进来,整批就从"一次成型"退化成逐组、且少倒 6 条。 - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, 570.0), - (X_BACK + 3.0, Y_ARM + 40.0, 631.0), - (2.5, 2.0, 1.5), "竖板/龙骨↔法兰") - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, Z_ARM + 34.5), - (X_BACK + 3.0, Y_ARM + 40.0, Z_ARM + 35.5), - (1.5, 1.0), "顶筋顶↔法兰") - - # 与立柱顶部配合的定位榫头 (43.4x43.4x15mm):配 P01_C 44x44 R4 内腔,单边 0.3mm 滑配 - boss = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 15.0)) - .rect(TENON, TENON) - .extrude(15.0) - .edges("|Z").fillet(4.2)).val() - s = s.fuse(cq.Shape(boss.wrapped)) - - # 4x 安装孔:统一 Ø3.9 直孔(M4x16 从腔体正上方拧入自攻成型;鞍座与立柱顶环同径、无沉孔) - # 孔位 (X_COL±27.5, Y_COL±13.5) 同时满足:①在立柱顶环实体壁内(内腔 22 → 外壁 33); - # ②在 P03 两竖板之间、避开中间龙骨,从腔体正上方用扳手可直接拧紧。 - for xx in (X_COL - BOLT_DX, X_COL + BOLT_DX): - for yy in (Y_COL - BOLT_DY, Y_COL + BOLT_DY): - s = s.cut(cyl(1.95, Z_CHEST1 - 0.5, Z_CHEST1 + T_SADDLE + 0.5, xx, yy)) - - # 1x 横向防脱锁紧:榫头 −X 侧向内 Ø3.9x14 直孔(与立柱后壁 Ø3.9 孔同轴,M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - TENON / 2.0 - 1.0, X_COL - TENON / 2.0 + 14.0, Y_COL, Z_LOCK)) - - # 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道) - # 孔位 = 电机尾端 4x M3 螺纹径向槽实测位(电机.STEP):BCD55、相位 39.9°+90k。 - # 旧 35.1×40.9 矩形孔距与实际槽位差 ~4.7mm(对角两颗螺钉无处咬合), - # 2026-10-02 用户指出"螺钉没有对齐"后按 STEP 实测修正。 - for dy, dz in ((21.06, 17.65), (-17.65, 21.06), (-21.06, -17.65), (17.65, -21.06)): - py = Y_ARM + dy - pz = Z_ARM + dz - th = Xcyl(1.7, X_BACK - 0.5, X_MOUNT + 4.0, py, pz) - cb = Xcyl(3.25, X_BACK - 0.5, X_BACK + 4.5, py, pz) - s = s.cut(th).cut(cb) - - # 中央走线孔 (Ø26mm) - wire_hole = Xcyl(13.0, X_COL - 40.0, X_MOUNT + 2.0, Y_ARM, Z_ARM) - s = s.cut(wire_hole) - - # 6. 前向电机全包托架 (X: -51.0..8.5+X_BAY_EXT=11.5,Ø60.7 舱孔平直贯通到端面) - # 端面随插槽加长前延 X_BAY_EXT;叉板背面(10.5+X_FORK_GAP=11.5)与端面 0.0 硬贴平 - # (用户两次要求"搞成平的";J1 转动摩擦由用户知悉,发涩时可微松 M2) - X_FRONT = 8.5 + X_BAY_EXT - R_POD = 36.5 - R_BORE = 30.35 - - cradle_outer = (cq.Workplane("YZ", origin=(X_MOUNT - 0.1, Y_ARM, Z_ARM)) - .circle(R_POD) - .extrude(X_FRONT - X_MOUNT + 0.1) - .edges(">X").chamfer(1.5)).val() - s = s.fuse(cq.Shape(cradle_outer.wrapped)) - - # 电机定子配合内腔(最终状态,2026-10-02 用户裁定"去掉台阶改成平的+保留固定电机的面"): - # Ø60.7 圆柱孔壁从前端面平直贯通到舱底(无窄颈、无台阶、无喇叭锥); - # 舱底 = 一个完整的平坦环面(X=5.06+X_BAY_EXT=8.06,与插槽末端墙共面,中开 Ø39 凸台过孔) - # ——即电机前限位面。电机轴向由后端法兰 4xM3x16 约束; - # 前端锁紧由 4 个 Ø10 局部凸台上的 M3 圆柱头螺钉承担(见下方凸台段)。 - motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.06 + X_BAY_EXT, Y_ARM, Z_ARM) - s = s.cut(motor_pocket) - - # 4x 前置 M3 锁紧螺钉凸台(BCD=55mm,17.8°/107.8°/197.8°/287.8°): - # 平直舱孔的沉头座位由 4 个 Ø10 局部凸台承载——凸台位于舱孔前段 8.06..11.5, - # 电机前端面(5.0)之前 3mm,电机斜插路径不经过;M3x8 圆柱头坐在凸台沉孔 - # 底(x=9.5)旋入电机前端面,咬合 3.5mm,兼作电机前端防转定位。 - for deg in (17.8, 107.8, 197.8, 287.8): - rad = math.radians(deg) - fy = Y_ARM + 27.5 * math.cos(rad) - fz = Z_ARM + 27.5 * math.sin(rad) - s = s.fuse(Xcyl(5.0, 5.06 + X_BAY_EXT, X_FRONT, fy, fz)) - th = Xcyl(1.6, 5.06 + X_BAY_EXT, X_FRONT, fy, fz) - cb = Xcyl(3.0, X_FRONT - 2.0, X_FRONT, fy, fz) - s = s.cut(th).cut(cb) - - # J1 输出凸台过孔 (Ø39 × 3.44——J1 凸台与 3xM2 输出螺钉的必经孔,非台阶; - # 原 6.7mm 长颈与半圆台肩已随舱底前移一并消除) - hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM) - s = s.cut(hub_clearance) - - # 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒): - # M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。 - # 轴向约束:后端法兰 4xM3(电机尾端面)单向锁死(前置止脱法兰随平直舱孔要求取消); - # 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06+X_BAY_EXT=8.06 - # (比电机前端面多 3mm 斜插余量,电机可倾斜卡入),止脱法兰环随之前移、环宽保持 3.44mm。 - s = s.cut(bx(X_MOUNT, 5.06 + X_BAY_EXT, Y_ARM, Y_ARM + 60.0, Z_ARM - 60.0, Z_ARM + 60.0)) - - # 7. 散热系统:双侧大视窗 + 顶部斜向对流排热槽 - for sign in (-1, 1): - win_plane = cq.Plane( - origin=(-24.5, Y_ARM + sign * 35.0, Z_ARM), - xDir=(1, 0, 0), - normal=(0, sign, 0) - ) - win_cut = cq.Workplane(win_plane).slot2D(34.0, 20.0).extrude(16.0, both=True).val() - s = s.cut(win_cut) - - for deg in (45, 135): - rad = math.radians(deg) - slot_plane = cq.Plane( - origin=(-24.5, Y_ARM + 33.0 * math.cos(rad), Z_ARM + 33.0 * math.sin(rad)), - xDir=(1, 0, 0), - normal=(0, math.cos(rad), math.sin(rad)) - ) - slot_cut = cq.Workplane(slot_plane).slot2D(30.0, 6.5).extrude(15.0, both=True).val() - s = s.cut(slot_cut) - - return s - - -def p_fork(j1, j2): - """P04_肩叉_螺旋体30%_墙5底5_竖打 (Shoulder Fork / L-Bracket) - 加长避让顺滑流线版: - 1. 头部与身子起点自 X=5.5 前移加长至 X=10.5+X_FORK_GAP=11.5,叉板背面与 P03 托架端面 - (X=8.5+X_BAY_EXT=11.5) 0.0 硬贴平(2026-10-02 用户两次要求"搞成平的"; - P03 侧插槽独立加长 3mm 便于 M1 电机斜插卡入); - 2. J1 从动法兰圆盘已删除(盘面在 column 前缘形成鼓楞):J1 由 boss(Ø35.6)贯通 P03 前盖中心孔直连电机输出盘;column 顶边保持 Z=646.5 与 P03 环齐高,R=18mm 大圆弧顺滑过渡; - 3. 头部转接凸台加长贯通 P03 前盖中心孔 (R=19.5mm),直连 J1 旋转法兰盘; - 4. M2 保留后端安装板(R=35mm, 带 rear_mount_holes 螺钉孔),前端定位环鼓出圆盘已删除(前端定位由 column 端面 pocket 承担),中间镂空散热; - 4b. 【2026-10-02 新增】M2 前端压盖:原方案电机只被后端安装板(S02 4xM3x10)单面锁死, - 大扭矩下电机绕 Y 轴在 Ø60.3 座孔里点头/翘起,固定不牢。现在前端盖外端面 - (Y=156.0) 再加一枚同径 R35 压盖,用 4x M3x8 在电机前端盖 BCD55 上把电机 - 从前端夹一次 —— 电机被前后两块同心 Ø60.3 端面双向夹紧,倾斜自由度归零; - 螺钉沉在压盖沉孔内不外凸,不占用 J2 轴向间隙(P05 法兰盘已相应后移); - 5. 严格满足 single-solid 流形拓扑与 360° 全范围零干涉 (COLLISION pairs = 0)。 - """ - # 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm) - rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH) - - # 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH) - s = rear_plate.fuse(front_collar) - - # 2b. 【2026-10-02 新增】M2 前端压盖(几何与理由见 motor_front_clamp)。 - # 必须在第 6/7 步所有 R30.15~30.5 圆柱刀具走完之后才熔接,否则压盖根部会被 - # front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边(见 7b)。 - fp = motor_front_clamp(X_ARM, Z_SH, tag="P04/M2") - - # 3. J1 从动法兰圆盘已删除:原 R=36.5 圆盘盘面在 column 前缘形成圆盘鼓楞, - # J1 与 P04 的连接由 boss(Ø35.6 贯通 P03 前盖中心孔 R=19.5)直连电机输出盘 - # 承担,螺钉走 boss 端面 4xB 孔,圆盘无传力功能已去除。 - - # 4. XZ 平面有机连续流线轮廓(随 X_FORK_GAP 整体前移,避让顺滑过渡): - x_b, z_b = -12.65 + X_FORK_GAP, 506.0 - x_a, z_a = 10.5 + X_FORK_GAP, 545.0 - Lz = z_a - z_b - d0 = 0.514 * Lz - d1 = 0.0 - - hermite_pts = [] - N_h = 16 - for i in range(N_h + 1): - t = i / N_h - h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0 - h10 = t**3 - 2.0 * (t**2) + t - h01 = -2.0 * (t**3) + 3.0 * (t**2) - h11 = t**3 - t**2 - xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11 - zh = z_b + t * Lz - hermite_pts.append((xh, zh)) - - dx_flare = (53.5 + X_FORK_GAP) - (44.0 + X_FORK_GAP) - dz_flare = 545.0 - 490.0 - R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) - - xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) - .moveTo(10.5 + X_FORK_GAP, 545.0) - .lineTo(10.5 + X_FORK_GAP, Z_RL + 36.5) - .lineTo(28.0 + X_FORK_GAP, Z_RL + 36.5) - .radiusArc((44.0 + X_FORK_GAP, 630.0), 18.0) - .lineTo(44.0 + X_FORK_GAP, 545.0) - .radiusArc((53.5 + X_FORK_GAP, 490.0), -R_flare) - .lineTo(18.5 + X_FORK_GAP, 490.0) - .lineTo(-16.5 + X_FORK_GAP, 490.0) - .threePointArc((-15.1 + X_FORK_GAP, 498.5), (-12.65 + X_FORK_GAP, 506.0))) - for pt in hermite_pts[1:]: - xz_wire = xz_wire.lineTo(pt[0], pt[1]) - xz_wire = xz_wire.close() - xz_shape = cq.Shape(xz_wire.extrude(100.0, both=True).val().wrapped) - - # 5. YZ 平面包络 - # 【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面。 - # 原来第 2->3 点之间是一条直线 (164.5,646.5)->(155.5,526),沿 X 方向拉成 - # 4048mm2 的平板(实测法向 (0,1,-0.07),带 4 度斜度),正对电机上方 —— - # 两侧被 xz_wire 的 R18 圆弧挤出去,形成"中间一块平、两边鼓"的观感。 - # 改法:把这段直线换成三次 Hermite 曲线,两端切线分别沿 Y(接上面 Z=646.5 - # 的顶段)与沿 Z(接下面 Y=155.5 的竖直段),保证 G1 连续无缝; - # 中部向 +Y 外凸约 3mm,弧度观感与两侧 R18 一致。 - # 为什么不用单段圆弧:A 处切线要沿 Y、B 处要沿 Z,单个圆心无法同时满足 - # (解出来 R=9 与 R=120.5 矛盾),必须用 Hermite/Bezier —— 与 P05/P06 - # 的 Loft 过渡同一套做法。 - # 注意:顶点绕序必须与原列表一致(先 y_b 后 y_a),否则多边形自交、 - # 布尔后零件会碎掉(实测会掉到 48cm3 / 3 块实体)。 - y_a = (Y_COVER - 0.5, Z_SH + 36.0) # (155.5, 526.0) 曲线终点 - y_b = (Y_AXIS + 36.5, Z_RL + 36.5) # (164.5, 646.5) 曲线起点 - _d = math.hypot(y_b[0] - y_a[0], y_b[1] - y_a[1]) - _m0y, _m1z = -0.5 * _d * 0.35, 0.5 * _d * 0.5 # 切线:起点沿 -Y,终点沿 +Z - yz_pts = [(Y_AXIS - 36.5, Z_RL + 36.5)] - for k in range(13): - t = k / 12.0 - h00 = 2 * t ** 3 - 3 * t ** 2 + 1 - h10 = t ** 3 - 2 * t ** 2 + t - h01 = -2 * t ** 3 + 3 * t ** 2 - h11 = t ** 3 - t ** 2 - yz_pts.append((h00 * y_b[0] + h10 * _m0y + h01 * y_a[0], - h00 * y_b[1] + h11 * _m1z + h01 * y_a[1])) - yz_pts += [(Y_COVER - 0.5, Z_SH), - (Y_REAR - 8.0, Z_SH), - (Y_REAR - 8.0, Z_SH + 36.0)] - yz_solid = Xplate(yz_pts, -20.0, 60.0) - - c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped) - c_int.Build() - column = cq.Shape(c_int.Shape()) - - s = s.fuse(column) - - # 6. 中部电机定子散热切除 - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH)) - - # 7. M2 接口成型 - # 承压面起点用 AB.REAR_SEAT(=0.6 台肩 + 2.0 公差),与 Joint.pocket 同源: - # P04 走的是自己那套 BORE_R 圆柱刀具(没调 Joint.pocket),这里必须同步改, - # 否则 J2 关节的电机公差位仍是旧的 0.6,与 J3~J6 不一致。 - # 【2026-10-05】承压面段加一刀 SEAT_R(=R35) 圆腔,SEAT_TOP=+1.0。 - # 取满 R35 才能让承压面与外壁齐平成**一个平面**(用户要求:把凸台、围挡 - # 一起弄平,做成和电机两头齐平的平面)。取小于 R35 必然留下一圈凸起 - # 2.6mm 的围挡环墙 —— 详见 Joint.pocket 的 docstring。 - s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - c_m2 = [cyl(BORE_R, -AB.REAR_SEAT, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6), - cyl(AB.SEAT_R, -AB.REAR_SEAT, AB.SEAT_TOP)] - s = s.cut(*[j2.L(item) for item in c_m2]) - - - # 7b. M2 前端压盖熔接(见 2b):必须在 6/7 步所有 R30.15~30.5 圆柱刀具走完之后, - # 否则压盖根部会被 front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边。 - s = s.fuse(fp) - - # 7c. 【2026-10-02 新增】立柱减重槽(见 hollow_channel)。 - # 立柱 Z525..635 整段是壁厚最深处达 17.5mm 的实心料,而 J2 在 z=560/600 的 - # 安全系数有 19。挖成朝上敞开的 U 形槽后立柱变成槽钢:抗弯的两片 X 向腹板 - # 正好落在弯矩拉压区(z=560 截面 X13..45,中性面在 X29,腹板占 X13~23/X35~45), - # 被掏掉的正是中性层附近几乎不参与抗弯的料。 - # 槽底 Z525 以下保留整块实心过渡到电机座;顶部敞开故非封闭腔、免支撑。 - # 必须在 8 步 J1 凸台/螺钉孔之前切:槽的 X23..35 范围离 J1 凸台(X<=11.5)与 - # 3xS01 沉孔(X12..14)都有富余,但先切可以保证后面那些刀具不会把槽壁打薄。 - s = s.cut(hollow_channel(P04_CH["x0"], P04_CH["x1"], P04_CH["y0"], P04_CH["y1"], - P04_CH["z_floor"], 660.0, tag="P04立柱")) - - # 8. 头部转接凸台加长(贯通 P03 前盖中心孔,跨越装配缝直连 J1 输出盘) - boss = cyl(17.8, 54.942, 61.5 + X_FORK_GAP) - s = s.fuse(j1.L(boss)) - s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R) - v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 63.0 + X_FORK_GAP, u, v) - # 沉孔台阶固定在 63.0(不随前移):M2x10 旋入电机输出毂的咬合量与改前一致; - # 螺钉头(63..65)相对叉板背面(64.5)外露 0.5mm,位于 2mm 叉缝空隙内,无干涉。 - cb = cyl(2.8, 63.0, 74.0 + 40.0 + X_FORK_GAP, u, v) - s = s.cut(j1.L(th)).cut(j1.L(cb)) - - return s - - - -def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket", hub_gap=1.0, - roll_clamp=False): - """Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08): - 1. 顶部从动法兰与前侧 Loft 自然相切顺接; - 2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉; - 3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合; - 4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。 - - hub_gap = 从动法兰盘背面与上级电机前端盖之间的轴向让位间隙。 - 默认 1.0(贴合防磨)。P05 传 6.0:J2 处 P04 新增了 M2 前端压盖 - (Y 155.5..161.0),必须让出这 6.0mm 才能装下;见 p_shoulder_roll_base。 - - roll_clamp = 本件作为 roll 电机【前端夹紧件】时为 True(J3/P05 夹 M3)。 - roll 电机(J3/J6)轴向是 -Z,前端盖外端面在 z_r-56,-Z 侧是从动件: - 压盖朝 -Z 长(Z_COVER-5..Z_COVER),从动件毂面必须整体后移 CLAMP_BACKOFF=5.5 - 让位(与 Y 轴关节 hub_gap=6.0 同一个道理,只是基准间隙 0.5 而非 1.0)。 - P08 传 False:J6 的 M6 前端压盖尚未做(从动件 P09 让位要改工具法兰直锁凸台)。 - """ - z_bot = z_r - 56.0 # roll 电机底端 (front collar) - z_plate = z_r + 8.0 # roll 电机顶端安装板 (rear plate top);见第 8 步 t_plate 说明 - y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) - y_hub_in = HL.Y_COVER + hub_gap # 157.0 / (P05) 162.0 从动法兰内侧面 - z_hub_in = y_hub_in - HL.Y_REAR # 法兰盘背面的电机局部 z(凸台长度跟着它走) - y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值) - - # 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm) - s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS) - - # 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm) - disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch) - s = s.fuse(disc) - - # 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph) - Z_top = z_pitch - 35.0 - z_start = z_bot - N = 16 - 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) - 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_mid = max(v_c, v_s + 0.02) - # 后缘两角 R2 圆角:把 y=123 凸包与 R35 柱面交汇的两条 90° 竖棱圆润化 - # (磨掉"凸起来的"尖角,不动后缘高度/位置,打印无悬挂) - FR = 2.0 - 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 + FR) - .radiusArc((35.0 - FR, -5.0), FR) - .lineTo(-35.0 + FR, -5.0) - .radiusArc((-35.0, -5.0 + FR), FR) - .lineTo(-35.0, v_s) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域) - top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(y_hub_in, Z_top) - .lineTo(y_out, Z_top) - .lineTo(y_out, z_pitch) - .lineTo(y_hub_in, z_pitch) - .close() - .extrude(35.0, both=True)) - s = s.fuse(top_fill.val()) - - # 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料, - # 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整! - cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(-999.0, z_plate) - .lineTo(y_hub_in, z_plate) - .lineTo(y_hub_in, z_pitch + 50.0) - .lineTo(-999.0, z_pitch + 50.0) - .close() - .extrude(60.0, both=True)) - s = s.cut(cut_top_solid.val()) - - # 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强 - cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0) - s = s.cut(cut_open) - - # 7. 上级电机同心圆弧避让 - clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch) - s = s.cut(clearance) - - # 8. 下方电机安装孔位与定位止口 - # t_plate=8.0:Joint.pocket 的尾端面承压面让空了 MOTOR_TOL=2.0,承压面到螺钉沉孔 - # 台阶只剩 t_plate-cb_depth-REAR_SEAT = 8-4-2.6 = 1.4mm 膜厚;给到 8 而不是原来的 - # 7,是为了不让这层膜退化成 0.4mm。7->8 让 P05/P08 的杯体外壳顶 z_plate 抬 1mm。 - s = j_roll.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j_roll.pocket(s) - - # 9. 电机定子配合空腔 - s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS)) - - # 10. 俯仰轴直连法兰结构(凸台延伸至法兰盘背面之后 2.5mm,跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, z_hub_in + 2.5) - s = s.fuse(j_pitch.L(boss)) - s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb)) - - # 11. L 型转角连接处圆角过渡(圆角 R=2.0mm): - # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中 - try: - wp_s = cq.Workplane(obj=s) - sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate)) - s = wp_s.edges(sel_l).fillet(2.0).val() - except Exception as e: - print(f"[{name}] L型转角圆角异常: {e}") - - # 12. roll 电机(M3)前端压盖:与 P04/P06/P07 的 M2/M4/M5 压盖同一套配方, - # 只是 roll 电机轴向为 -Z,压盖朝 -Z 长(见 motor_front_clamp 的 axis="Z")。 - # 熔接必须放在最后:第 6/7/8/9 步所有 R30.15~30.5 圆柱刀具都走完之后才接, - # 否则压盖根部那 0.5mm 薄环会被 front_collar_pocket / 定子孔啃掉一圈。 - if roll_clamp: - s = s.fuse(motor_front_clamp(HL.X_ARM, 0.0, tag="%s/M3" % name[:3], - axis="Z", py=HL.Y_AXIS, z_face=z_r - COVER_Z)) - - return s - - -def p_shoulder_roll_base(j2, j3, half=None): - """P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版) - - 本件在链路上有两重身份,两处让位都是为前端压盖: - 1. hub_gap=6.0(从动侧):J2 处 P04 新增了 M2 前端压盖(前端夹紧电机,消除大扭矩下的 - 倾斜),压盖占 Y 155.5..161.0,所以从动法兰盘背面必须从 Y=157 后移到 Y=162 - (留 1.0mm 动间隙)。 - 2. roll_clamp=True(前端夹紧侧):P05 自己抱着 M3(J3,-Z 轴),原来也只有后端板 - 4xM3x10 单面锁死;现在按 motor_front_clamp 的统一配方在 M3 前端盖外端面 - (Z=389)加一枚 R35 压盖(Z 384..389,4xM3x8 BCD55),与后端板前后双向面压, - 倾斜自由度归零。代价是对侧从动件 P06 的 J3 毂面整体后移 CLAMP_BACKOFF=5.5 - (Z 388.5→383.0),见 p_humerus_bone。 - P08 保持默认 —— J5 处 P07 的前端压盖已做(只需 6.0 的 hub_gap),而 J6 的 M6 - 前端压盖尚未做,故 roll_clamp 传 False。 - """ - return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, - name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - hub_gap=6.0, roll_clamp=True) - - -def p_humerus_bone(j3, j4): - """P06_大臂骨架_螺旋体25%_墙4底5_竖打 (Humerus Bone - Loft 圆弧转方有机过渡版): - 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, - 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 - 1. 借鉴 P05 大臂旋转座几何放样算法: - - 顶端结合面 (Z = 383.0mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯; - - 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面; - - 过渡区 (Z: 367.0..383.0mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变; - 2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗; - 3.【2026-10-03】J3 毂面整体后移 CLAMP_BACKOFF=5.5(顶端 388.5→383.0,直锁凸台 - 59.5→65.0):P05 新增 M3 前端压盖,占 Z 384..389,原先 388.5 的毂面只剩 0.5mm - 让位放不下压盖。配方与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源: - 压盖 5.0 + 动间隙 1.0 - 原基准 0.5 = 5.5。凸台同步加长,3xS01 输出螺钉的头面 - 台阶(局部 s=58)、杆长、咬合量与改前完全一致,J3 直锁与零干涉不受影响。 - 压盖中央 Ø37 让位孔(R18.5)也让开 R17.8 凸台,单边余 0.7mm。 - 4. 严格单实体 (solids = 1) 且与上下级旋转构件及电机实现动态 0 碰撞干涉。 - """ - # 1. M4 底部抱紧套筒与后端安装板 (R=35mm) - rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL) - front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL) - s = rear_m4.fuse(front_m4) - - # 顶端结合面与过渡高度 - # z_top 由 M3 前端盖外端面反推:压盖占 [Z_COVER-CLAMP_T, Z_COVER],再留 CLAMP_CLR - # 动间隙 -> 383.0(改前 388.5,与前端盖只有 0.5mm,放不下压盖)。 - z_cover3 = Z_R3 - COVER_Z # 389.0 M3 前端盖外端面 - z_top = z_cover3 - CLAMP_T - CLAMP_CLR # 383.0 J3 毂面(= 388.5 - CLAMP_BACKOFF) - z_bot = Z_EL + 35.0 # 367.0 - - # 2. 下部实心基座块 (Z: Z_EL..z_bot + 0.5) - lower_block = bx(X_ARM - 35.0, X_ARM + 35.0, Y_REAR - 8.0, Y_COVER - 0.5, Z_EL, z_bot + 0.5) - s = s.fuse(lower_block) - - # 3. 借鉴 P05 的 Loft 放样过渡体:圆弧变方形(20 层平滑放样,保持严格对称 4 段式截面拓扑) - N = 20 - wp = cq.Workplane("XY") - for i in range(N + 1): - t = i / float(N) - z_curr = z_bot + t * (z_top - z_bot) - st = 3.0 * (t**2) - 2.0 * (t**3) - - # 前侧:t=0 为平直面 (Y=155.5, v=27.5),t=1 渐变为与 Ø70 正切的圆弧顶点 (Y=163.0, v=35.0) - v_sf = 27.5 * (1.0 - st) + 0.05 * st - v_cf = max(27.5 * (1.0 - st) + 35.0 * st, v_sf + 0.05) - - # 后侧:从 t=0 的平直面 (Y=92.0, v=-36.0) 渐变到 t=1 的圆弧 (Y=93.0, v=-35.0) - v_sr = -36.0 * (1.0 - st) - 0.05 * st - v_cr = min(-36.0 * (1.0 - st) - 35.0 * st, v_sr - 0.05) - - w = (cq.Workplane("XY", origin=(X_ARM, Y_AXIS, z_curr)) - .moveTo(-35.0, v_sf) - .threePointArc((0.0, v_cf), (35.0, v_sf)) - .lineTo(35.0, v_sr) - .threePointArc((0.0, v_cr), (-35.0, v_sr)) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 电机中间定子散热裸露切除 (100% 裸露) - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL)) - - # 5. M4 接口成型 - s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j4.pocket(s) - - # 5b. 【2026-10-02 新增】M4 前端压盖 4xM3x8(BCD55):M4 原来只被后端板单面锁死, - # 大扭矩下会绕 J4 轴在 Ø60.3 座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 熔接放在所有 R30.15~30.5 圆柱刀具之后,理由同 p_fork 7b。P07 的 J4 从动侧 - # 已相应后移到 Y>=162(见 p_forearm_bone 的 J4_CLEAR),与压盖前端面留 1.0mm。 - s = s.fuse(motor_front_clamp(X_ARM, Z_EL, tag="P06/M4")) - - # 5c. 【2026-10-02 新增】电机上方过渡块减重窗(见 hollow_tent_window)。 - # Z360..385 这段 Loft 过渡块壁厚最深 15.8mm,是 P06 唯一一块可掏的厚料 - # (Z332..367 的下部本体已经被 Ø61 定子孔掏成 4.5mm 薄壁,没得再掏)。 - # 但它正压在 J3 毂下面,所以三条红线: - # 1) 窗顶最高只到 Z374 —— J3 的 Ø70 毂面在 Z380..388.5,窗顶留 6mm 整块 - # 实心托住毂面承压,绝不碰毂面和它后面的 3xS01 直锁螺钉; - # 2) 窗的 X 范围 3..37 —— 截面本体 X-15..55,两侧各留 18mm 竖墙, - # 该高度上零件才不会被切成两半(必须单实体); - # 3) 拱形(下宽上窄)轮廓,斜面与水平 >= 52° 自支撑,只有顶部 X12..28 的 - # 16mm 平顶需要桥接(与 P03 同量级)。 - # 窗底取 Z362 而不是更低:Z360 附近是全段安全系数最低的截面, - # 抬高窗底 4mm 比横向收窄 8mm 更划算(减重量只少 3cm3,安全系数从 92% 回到 100%)。 - # 窗沿 Y 双向切穿(Y87..172 超出本体,前后都贯通),顺带把这段的定子散热口 - # 一起打开。 - # 【2026-10-03 关闭】用户实机视点确认这个拱形窗在正面看就是一个贯通的矩形黑洞, - # 位于J4 关节正上方、整机最显眼的落脚点,视觉上像"零件破了个洞",非常不雅观。 - # 权衡后恢复实心:代价只有 161->183cm3(+22cm3 / +27g PLA),而强度本来就已经是 - # 无窗基线的 100%(这段本来就不缺强度),减重收益纯属锦上添花。 - # 保留 P06_WIN_XZ 与本段注释,需要减重时把开关改回 True 即可复原。 - if P06_HOLLOW_WIN: - s = s.cut(hollow_tent_window(P06_WIN_XZ, 80.0, 175.0, tag="P06过渡块")) - else: - print(" [减重窗] P06过渡块: 已关闭(2026-10-03 视觉整改),该段恢复实心", flush=True) - - # 6. J3 直连法兰结构(凸台延伸至 65.0 跨过结合面牢固熔接,保持单实体与零干涉) - # 凸台随 J3 毂面一起后移 CLAMP_BACKOFF=5.5(59.5→65.0):进入电机输出毂的深度 - # 仍是 s54.942..56.0 那 1.06mm,3xS01 的头面台阶(s=58)/杆长/咬合量一律不变。 - boss = cyl(17.8, 54.942, 59.5 + CLAMP_BACKOFF) - chamfer_cone = cyl(18.5, 54.5, 54.942) - s = s.fuse(j3.L(boss)).cut(j3.L(chamfer_cone)) - s = s.cut(j3.L(cyl(3.5, 54.0, 58.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 58.0 + 40.0, u, v) - s = s.cut(j3.L(th)).cut(j3.L(cb)) - - return s - - -def p_forearm_bone(j4, j5, half=None): - """P07_小臂骨架_螺旋体30%_墙5底5_竖打 L型高刚度无干涉版(深度圆角优化版) - 设计要点: - 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - - 彻底消除原全深S弯在J4轴下方向两边突出的干涉结构(两边突出的结构全部去除) - - 垂直立柱与下方 M5 电机水平座之间采用规则折角(Corner Chamfer/Gusset)刚性连接 - 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 - 3. J4 轴俯仰动态行程从受限的 ±54° 大幅释放至无死角大范围摆动(零干涉) - 4. 满足单实体 (solids=1) 与装配零碰撞。 - 5.【2026-10-02】M5 前端压盖(4xM3x8 BCD55)消除"电机只被后端板单面锁死"的倾斜隐患; - J4 从动侧整体让位:y_hub_in 由 158.5 后移到 J4_CLEAR=162.0,让出 P06 的 M4 前端压盖 - (前端面 Y=161.0),留 1.0mm 动间隙。J4 直连凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - y_out = Y_HUB + PLATE_T # 174.0 - # J4 从动侧让位基准:必须 >= P06 的 M4 前端压盖前端面(Y_COVER+5.0=161.0) + 1.0mm - J4_CLEAR = Y_COVER + 6.0 # 162.0 - y_hub_in = J4_CLEAR - 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) - rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR) - s = collar_front.fuse(rear_plate) - - # 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL) - disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL) - s = s.fuse(disc) - - # 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_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)) - s = s.fuse(roof_w.val()) - - # 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座) - vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) - .moveTo(y_hub_in, z_top_housing) - .lineTo(y_out, z_top_housing) - .lineTo(y_out, Z_EL) - .lineTo(y_hub_in, Z_EL) - .close() - .extrude(35.0, both=True)) - s = s.fuse(vert_leg.val()) - - # 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块") - # 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm) - # 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end) - # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! - # 注:顶板整条加高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_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_roof_top) - .threePointArc(p_mid_arc, (y_hub_in, z_arc_end)) - .lineTo(y_out, z_arc_end) - .lineTo(y_out, z_top_housing) - .lineTo(y_arc_start, z_top_housing) - .close() - .extrude(35.0, both=True)) - s = s.fuse(gusset_w.val()) - - # 6. 下级 P08 旋转圆盘动态避让切除(R=35.5mm,预留 0.5mm 间隙) - p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR) - s = s.cut(p08_clearance) - - # 6b. 【2026-10-02 新增】顶板根部减重槽(见 hollow_channel)。 - # 顶板在 Z255..280 一整段是壁厚最深 15mm 的实心料,而 J4 在 z=245 的安全系数 - # 高达 122、z=195 也有 24。挖成朝上敞开的 U 形槽,顶板变成槽钢: - # 槽底 Z255 以下保留整块实心到电机座,抗弯的两片 X 向腹板(X-15..-7 / X47..55) - # 留在弯矩拉压区。Y 上限取 156 而非核心的 166,是为了避开 y_hub_in=158.5 起的 - # 竖直立柱本体(立柱是 J4 从动法兰的承力柱,不能掏)。 - # 槽顶 Z280 就是顶板上表面,敞口向上,免支撑、非封闭腔。 - s = s.cut(hollow_channel(P07_CH["x0"], P07_CH["x1"], P07_CH["y0"], P07_CH["y1"], - P07_CH["z_floor"], 300.0, tag="P07顶板")) - - # 7. M5 电机安装接口与定子空腔 - s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5) - s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j5.pocket(s) - s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, HL.Y_REAR - 0.5, y_front - 6.0, X_ARM, Z_WR)) - - # 半包散热设计:-Z底部电机定子通风口 - vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0) - s = s.cut(vent_cut) - - # 7b. 【2026-10-02 新增】M5 前端压盖 4xM3x8(BCD55):M5 原来只被后端板单面锁死, - # 大扭矩下会绕 J5 轴在座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 必须在第 6 步 p08_clearance(R35.5 让位孔,Y>=156)之后熔接,否则压盖会被削掉。 - # P08 的 J5 从动侧已相应后移到 Y>=162(make_pitch_to_roll_bracket 的 hub_gap=6.0)。 - s = s.fuse(motor_front_clamp(X_ARM, Z_WR, tag="P07/M5")) - - # 8. J4 直连法兰结构(凸台延伸到法兰盘背面之后 2.0mm 跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, (y_hub_in - HL.Y_REAR) + 2.0) - s = s.fuse(j4.L(boss)) - s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j4.L(th)).cut(j4.L(cb)) - - # 9. 工艺走线通孔 - slot_z = (Z_EL + z_top_housing) / 2.0 - slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z) - s = s.cut(slot) - - # ========== 关键圆角优化 ========== - try: - # 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角) - wp_s = cq.Workplane(obj=s) - sel_rear = cq.selectors.BoxSelector( - (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).fillet(FILLET_SMALL).val() - except Exception as e: - print(f"P07 顶板后边缘圆角异常: {e}") - - return s - - -def p_wrist_bracket(j5, j6): - """P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版) - - hub_gap=6.0:J5 处 P07 新增了 M5 前端压盖(Y 155.5..161.0),从动法兰盘背面必须 - 从 Y=157 后移到 Y=162 让位;J5 直锁凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, - name="P08_腕偏转座_螺旋体25%_墙4底5_竖打", - hub_gap=6.0) - - -def p_tool_flange(j6): - """P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版 - 关键改进: - 1. 外轮廓全圆角(R=4mm) - 2. 法兰面倒角(2mm) - 3. 螺孔边缘倒角(1.5mm) - """ - z_bot = Z_HUB6 - PLATE_T - 9.0 - s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) - P09_M4 = [] - - # 4x M4螺栓孔 - # - # 分布圆半径 20 -> 22(BCD 40 -> 44):R20 与 3xS01 输出螺钉的 Ø5.6 螺丝刀通道 - # (make_direct_hub 里 cyl(2.8, 58.0, z_out+40) 在 BCD29=R14.5、相位 351.4/232.3/112.1°) - # 咬穿沉孔。0° 那个孔中心 (39.5,128) 到 351.4° 通道中心 (33.84,125.83) 只有 6.06mm, - # 而两孔半径之和是 3.5+2.8=6.30 —— 差 0.24mm,Ø7.0 沉孔壁被啃掉一块, - # 完整圆柱面(88mm²)裂成 45.7+35.4 两片,即用户截图里的月牙孔。 - # 该相位差只有 8.6°,转动 M4 孔相位无法躲开(0/90/180/270 与 45 度错开全部仍 <6.30), - # 只能加大分布圆半径:R22 时最小中心距 7.96mm,余量 1.66mm。 - # 边壁仍充足:R35 法兰外径 - (R22 + 沉孔 3.5) = 9.5mm。 - for a in (0, 90, 180, 270): - rad = 22.0 - u = rad * math.cos(a * D2R) - v = rad * math.sin(a * D2R) - # 实际切割推迟到 make_direct_hub 之后(见下)。这里只算出 4 个孔位。 - P09_M4.append((X_ARM + u, Y_AXIS + v)) - - # 定位止口 + 走线孔 - pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS) - thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS) - s = s.cut(pilot).cut(thru) - - # 直连法兰 - s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0) - - # 4x M4 孔(必须在 make_direct_hub 之后切) - # - # 直连法兰那步 fuse 了 R35 圆盘(局部 z=56.5..74 -> 全局 z=102..121 一侧), - # 会把先切的孔重新填死:分段求交显示 z=97..102 无干涉、z=102 以上整根杆都埋在料里 - # (每段 12.57 mm^3 = pi*2^2*1,全截面被埋)。 - # - # 孔径与深度同时修正:旧值 Ø3.7 比 M4 杆 Ø4.0 还细,且只到 z=105, - # 而 M4x16 头面在 z=97、杆要走到 z=113 —— 杆尾 8mm 无处可去(干涉 147.3 mm^3)。 - # Ø4.4 是 M4 标准间隙孔(ISO 273 medium ≈ Ø4.3)。 - # 孔顶要盖过 M4x16 的杆尾:头面 z=97 + 杆长 16 = z=113,故开到 Z_HUB6+4=115, - # 比杆尾多留 2mm(实测开到 112 时最后 1mm 杆尾仍撞料 37.7mm³)。 - for (fx, fy) in P09_M4: - s = s.cut(cyl(2.2, z_bot - 1.0, Z_HUB6 + 4.0, fx, fy)) - s = s.cut(cyl(3.5, z_bot - 1.0, z_bot + 4.0, fx, fy)) - - # ========== 关键圆角优化 ========== - try: - # 外轮廓圆角 - s = s.edges("|Z").fillet(FILLET_SMALL) - except: - pass - - try: - # 法兰面倒角 - s = s.edges("Z").fillet(FILLET_TINY) - except: - pass - - return s - - -# ---------------------------------------------------------------- assembly ---------------- -def build(): - print("=" * 80, flush=True) - print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True) - print("=" * 80, flush=True) - t_stage1 = time.time() - - print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True) - t0 = time.time() - motor = cq.importers.importStep(AB.SRC_STEP).val() - motor = place(motor, T(0, 0, -2.57)) - AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None - print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True) - - JS = {} - for tag, cn, rear, d, xd, holder, driven in HL.JOINTS: - JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0) - j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7)) - - PARTS[:] = [] - AB.CENTS.clear() - AB.GRP = {} - par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"} - driven = {"J1": "P04_肩叉_螺旋体30%_墙5底5_竖打", "J2": "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "J3": "P06_大臂骨架_螺旋体25%_墙4底5_竖打", - "J4": "P07_小臂骨架_螺旋体30%_墙5底5_竖打", "J5": "P08_腕偏转座_螺旋体25%_墙4底5_竖打", "J6": "P09_工具法兰_螺旋体30%_墙4底5_竖打"} - motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3", - "J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"} - for t in ("J1", "J2", "J3", "J4", "J5", "J6"): - AB.GRP[motor_of[t]] = par[t] - AB.GRP[driven[t]] = t - AB.GRP.update({ - "P01_A_立柱下段_三角形30%_墙4底5_竖打": "root", "P01_B_立柱中段_三角形30%_墙4底5_竖打": "root", "P01_C_立柱上段_三角形30%_墙4底5_竖打": "root", - "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": "root", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": "root", - "P03_肩吊臂_螺旋体35%_墙5底5_竖打": "root" - }) - AB.JAW = {} - - p01_full = p_chest() - p01_a, p01_b, p01_c = split_p01_poka_yoke(p01_full) - p02_c, p02_f, p02_r = make_p02_poka_yoke() - - parts_to_build = [ - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7, - "200mm适配分段A:高166mm+16mm阶梯公榫+顶端 2x Ø3.9 对锁孔(M4x16 自攻)+底部50x50x12榫头+P01-A 竖排TOP刻字"), - ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb, - "200mm适配分段B:高165mm+双端公母榫+两端各 2x Ø3.9 对锁孔(M4x16 自攻,非对称孔位防反装)+P01-B 竖排TOP刻字"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8, - "200mm适配分段C:高149mm+底端母榫+底端 2x Ø3.9 对锁孔(非对称孔位防反装)+顶面P03安装台(4x Ø3.9 安装孔 + 1x Ø3.9 横向锁紧孔)+P01-C 竖排TOP刻字"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "底座核心承力座", lambda: p02_c, 0x475569, - "200mm适配中座:195x190mm核心承力座+48mm套筒+12mm阶梯榫座(轴向面承载)+前后三角加强筋+前后非对称防呆母榫槽+刻字"), - ("P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打", "前向防翻力臂", lambda: p02_f, 0xe11d48, - "200mm适配前臂:145x190mm扩展力臂+104mm宽跨距双公榫+底部M5预埋螺母沉槽+P02-FRONT刻字"), - ("P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打", "后向防翻力臂", lambda: p02_r, 0xd97706, - "200mm适配后臂:45x190mm扩展力臂+80mm窄跨距双公榫+底部M5预埋螺母沉槽+P02-REAR刻字"), - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563, - "轻量化板式桁架版:主干由 66mm 实心块改为双侧 6mm 桁架竖板+帐篷减重窗(斜面≥52°自支撑)+R4内角,外轮廓与旧版并集完全一致,实心 728->286cm³;底部 9mm 承力鞍座(Ø3.9 安装孔)+12mm 中位龙骨筋(自鞍座斜拉至法兰下缘)+40~45°外扩斜撑;保留 10mm 后置法兰、侧开式电机托架、Ø60.7 直通电机舱(平孔无台阶)(M1 从+Y滑入,后端 4xM3x16 锁死)、大散热窗与对流槽;无封闭空腔、免支撑"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "肩叉", lambda: p_fork(j1, j2), 0x2563eb, - "齐平插槽+ M2 前端压盖+ 立柱减重槽版:叉板背面与P03托架端面(11.5)0.0硬贴平(用户要求外观齐平),P03侧插槽独立加长3mm,M1电机可倾斜卡入,J1输出凸台贯通直连电机,J1从动法兰圆盘鼓楞已删(接缝区与P03止脱环齐径齐高R36.5/顶646.5),单实体全角度零干涉;【2026-10-02】M2 前端压盖(R35, Y155.5..161.0):电机前端盖外端面加 4xM3x8 沉孔(BCD55)夹紧,与后端安装板 4xM3x10 形成前后双向面压,消除大扭矩下电机在座孔里的倾斜;【2026-10-02】立柱减重槽(X23~35 / Y102~154 / 自Z525向上敞开):立柱Z525~635整段壁厚峰值17.5mm,J2在z=560/600的安全系数有19,改成U形槽钢后361->289cm3(-20%),抗弯的两片X向腹板正好落在弯矩拉压区(z=560截面X13~45、中性面X29,腹板占X13~23/X35~45),掏掉的正是中性层附近几乎不参与抗弯的料;槽底Z525以下保留整块实心过渡到电机座,槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X23~35 比最初的 X21~37 收窄 2mm,强度从 88% 回到 95%);【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面(全行程重扫 907s 确认 J1~J6 行程逐项不变):原来 yz_pts 里(164.5,646.5)->(155.5,526) 是一条直线,沿 X 拉成 4048mm2 平板(法向 (0,1,-0.07)、带 4° 斜度),正对电机上方,两侧被 xz_wire 的 R18 圆弧挤出去,形成「中间一块平、两边鼓」的观感。改成三次 Hermite(两端切线分别沿 Y/Z,与上下水平段 G1 连续),中部向 +Y 外凸约 3mm,弧度与两侧 R18 一致;单段圆弧无法同时满足两端切线(解出 R=9 与 R=120.5 矛盾)故用 Hermite。P04 体积 282.1->274.3cm³(弧面替代平面削掉的料);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, - "V3圆角+ J2前端夹紧配合:Loft外轮廓R=10mm,L型转角内部2mm倒角,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂);J2 从动法兰盘背面由 Y=157 后移至 Y=162(hub_gap=6.0,给 P04 新增的 M2 前端压盖让位,J2 直锁凸台同步加长到 Y=165.5,3xS01 输出螺钉咬合量不变);保证 J2 全角度 0 干涉;【2026-10-03】M3 前端压盖(R35, Z384.0..389.0):J3 roll 电机原来也只有后端板 4xM3x10 单面锁死(S02),大扭矩下绕 J3 轴在 Ø60.3 座孔里点头;现按 P04/P06/P07 同一配方在 M3 前端盖外端面(Z=389)加 4xM3x8(BCD55)沉孔压盖,与后端板前后双向面压,倾斜自由度归零(配方唯一差别是 roll 电机轴向 -Z,压盖朝 -Z 长,见 motor_front_clamp axis=\"Z\");代价是对侧 P06 的 J3 毂面整体后移 5.5mm 让位;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488, - "Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体;【2026-10-03】取消电机上方Loft过渡块(Z360~385)的拱形减重窗,恢复实心:该窗沿Y贯通,正面看是整机最显眼处的矩形黑洞(用户视觉整改),代价仅 +22cm3/+27g,强度仍为无窗基线100%;【2026-10-03】J3 毂面整体后移 5.5mm(顶端 388.5→383.0,直锁凸台 59.5→65.0):给 P05 新增的 M3 前端压盖(Z384..389)让位,留 1.0mm 动间隙——与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源,换算式同为「压盖 5.0 + 动间隙 1.0 - 原基准 0.5」;凸台同步加长,3xS01 头面台阶(s=58)/杆长/咬合量与 J3 直锁关系一律不变;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, - "V2 L型结构 + 顶板减重槽:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉;【2026-10-02】顶板根部Z255~280开朝上敞开的减重槽(X-3~43 / Y103~153, Y上限取153避开y_hub_in=158.5起的竖直立柱本体):顶板由实心变槽钢,288->230cm3(-20%),槽底以下保留整块实心到电机座,抗弯靠两侧各10mm的X向腹板;槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X-3~43 比最初的 X-7~47 收窄 4mm,强度从 88% 回到 96%);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706, - "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626, - "V2圆角:外轮廓R=3mm,法兰面倒角2mm;【2026-10-02】打印填充由100%降到30%:该截面安全系数288是全机裕度最大的件,满填充纯属浪费(件号同步改为P09_工具法兰_螺旋体30%)"), - ] - - total_parts = len(parts_to_build) - for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): - print(f" [{idx}/{total_parts}] 正在构建 {name} ({cn})...", end="", flush=True) - t0 = time.time() - sh = fn() - dt = time.time() - t0 - vol = AB.vol_of(sh) / 1000.0 - n_solids = len(solids_of(sh)) - print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True) - add(name, cn, sh, color, note=note) - - print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True) - t0 = time.time() - for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS): - j = JS[tag] - add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af, - printed=False, note="复用 电机.STEP") - print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True) - print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True) - return JS - - -CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"] -SEP = 74.0 - - -def explode_chain(p, JS, cents, gmean): - """爆炸图偏移:立柱垂直阶梯拉开,底座前后防呆拉开,电机沿轴拉出,机械臂整体随上段分离""" - nm = p["name"] - if nm == "P01_A_立柱下段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 0.0] - if nm == "P01_B_立柱中段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 60.0] - if nm == "P01_C_立柱上段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 120.0] - if nm == "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": - return [0.0, 0.0, 0.0] - if nm == "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": - return [65.0, 0.0, 0.0] - if nm == "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": - return [-65.0, 0.0, 0.0] - if nm == "P03_肩吊臂_螺旋体35%_墙5底5_竖打": - return [0.0, 0.0, 120.0] - - grp = AB.GRP.get(p["name"], "root") - if grp == "root": - return [0.0, 0.0, 0.0] - k = CHAIN.index(grp) - off = [0.0, 0.0, 120.0 - SEP * k] - j = JS.get(grp) - if j: - w = 0.0 - if "半联轴器" in nm or "抱箍" in nm: - w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0) - elif nm.startswith("M"): - w = 62.0 - for q in range(3): - off[q] += j.d[q] * w - return off - - -if __name__ == "__main__": - JS = build() - - print("=" * 80, flush=True) - print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) - print("=" * 80, flush=True) - bad = 0 - printed_list = [p for p in PARTS if p["printed"]] - total_pr = len(printed_list) - for idx, p in enumerate(printed_list, 1): - n = len(solids_of(p["shape"])) - if n != 1: - bad += 1 - status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌" - print(f" [{idx}/{total_pr}] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) - if bad == 0: - print(f"-> 单实体检查全部通过!{total_pr}/{total_pr} 构件均为完美单一流形拓扑 ✓\n", flush=True) - else: - print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) - - AB.explode_vec = explode_chain - bom = AB.export(JS) - - # 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例, - # 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD) - try: - import screw_mesh - screw_mesh.main() - except Exception as e: - print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True) - - cl = AB.collide() - - try: - from build_viewer import rebuild_viewer - rebuild_viewer() - except Exception as e: - print("rebuild_viewer error:", e, flush=True) - - # 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉), - # 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算) - try: - sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), - "..", "scratch")) - import ws_sweep - import gen_env_mesh - t_env = time.time() - ws_sweep.main() - gen_env_mesh.main() - from build_viewer import rebuild_viewer as _rbv - _rbv() - print(f"-> 可达包络已自动注入查看器(+{time.time()-t_env:.1f}s)✓\n", flush=True) - except Exception as e: - print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):", - e, flush=True) - - print("=" * 80, flush=True) - print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) - print("=" * 80, flush=True) - sys.stdout.flush() - os._exit(0) diff --git a/.bak/arm_model.py.bak20261005e b/.bak/arm_model.py.bak20261005e deleted file mode 100644 index d78940f..0000000 --- a/.bak/arm_model.py.bak20261005e +++ /dev/null @@ -1,1886 +0,0 @@ -# -*- coding: utf-8 -*- -"""机器人手臂深度圆角优化版 V2:彻底消除所有棱角 -针对用户反馈的三大问题区域进行深度优化: -1. 蓝色零件(P04肩叉)顶部方形突起 → 全圆角处理 -2. 绿色/青色零件(P05/P08)底部和侧面棱角 → Loft外轮廓圆角 -3. 所有连接处的尖锐过渡 → 大圆角平滑过渡 -4. 橙色零件(P06/P07)所有方形边缘 → 完整圆角系统 -""" -import math -import os -import sys -import time - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -import arm_build as AB -from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS) -from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector, - FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK, - BORE_R, CB_R, PCB_W, D2R, T, R, PARTS) -import human_layout as HL -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1, - Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6, - Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1, - X_FORK_GAP, X_BAY_EXT, - Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, - Y_M6, Y_CHEST, X_CHEST, FOOT_H) - -VERSION = os.environ.get("ARMVER", "v2") -ITER = int(os.environ.get("ARMITER", "3")) -AB.VERSION = VERSION -AB.ITER = ITER - -Y_MID = Y_AXIS -BOSS = COVER_Z + 0.2 -DISC_R = 40.0 -SLEEVE_R = 35.0 - -# 深度圆角优化参数 -CLEARANCE_GAP = 2.0 # 关节间隙 2.0mm -FILLET_TINY = 1.5 # 微小圆角(螺孔边缘) -FILLET_SMALL = 3.0 # 小圆角(功能面) -FILLET_MEDIUM = 6.0 # 中圆角(结构过渡) -FILLET_LARGE = 10.0 # 大圆角(主要轮廓) -FILLET_XLARGE = 15.0 # 超大圆角(视觉主导区域) -CHAMFER_EDGE = 2.0 # 边缘倒角 - - -def Xcyl(r, x0, x1, py, pz): - return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz)) - - -def Xplate(pts_yz, x0, x1): - prof = [(-p[1], p[0]) for p in pts_yz] - return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0)) - - -def XB(a0, a1, y0, y1, z0, z1): - return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1) - - -# ------------------------------------------------------------------ 减重(镂空)-------- -# 三个手法件(P04/P06/P07)在关节根部都留着一大块"实心过渡料":壁厚最深到 15~17.5mm, -# 而 gyroid 填充只有 25~35%,等于用整块实心去抗一个安全系数 19~122 的截面。 -# 下面两个 helper 把这块料掏掉,规则与 P03 的"帐篷形减重窗"一致: -# 1) 不留封闭空腔 —— 减重腔要么朝上敞开,要么是有进出口的窗,打印不憋气、不积料; -# 2) 斜面自支撑 —— 斜面与水平面夹角 >= 52°(与竖直面夹角 <= 38°),免支撑; -# 3) 内角 R4 圆角 —— 挖出来的洞会把圆截面变成尖角,Kt 从 ~1.2 抬到 ~2.5, -# 圆角把应力集中压回去; -# 4) 只吃"距表面 > 8mm"的厚料,四周至少留 8mm 壁 —— 受力纤维一根不碰。 - -HOLLOW_WALL = 8.0 # 减重腔与原外表面的最小间距(= 留下的最小壁厚) -HOLLOW_FILLET = 4.0 # 减重腔内角圆角,与 P03 竖板减重窗一致 - -# 三处减重腔的开度。这三个件都是绕 Y 轴弯,弯矩在 X 方向拉压,距中性面越远的料 -# 贡献越大,所以"少挖、挖窄"是恢复强度最省料的做法。 -# 数值由 scratch/tune_hollow.py 按"槽区最差安全系数 >= 无槽基线的 95%"扫出来: -# P04 档1 X23..35 Y102..154 Z>=525 -> 95% 基线,保留 69% 的减重量 -# P07 档1 X−3..43 Y103..153 Z>=255 -> 96% 基线,保留 76% -# P06 档1 抬高窗底到 Z362(不是收窄) -> 100% 基线,保留 56% -# 窗底原来压在 Z358,而该处截面安全系数最低;抬高 4mm 比横向收窄 8mm 划算得多。 -P04_CH = dict(x0=23.0, x1=35.0, y0=102.0, y1=154.0, z_floor=525.0) # 肩叉立柱 -P07_CH = dict(x0=-3.0, x1=43.0, y0=103.0, y1=153.0, z_floor=255.0) # 小臂顶板 -P06_WIN_XZ = [(3.0, 362.0), (37.0, 362.0), (37.0, 371.0), - (28.0, 374.0), (12.0, 374.0), (3.0, 371.0)] # 大臂过渡块 -P06_HOLLOW_WIN = False # 2026-10-03 起关闭:拱形窗正面看是贯通黑洞,不雅观(详见 p_upper_bone 5c) - - -def hollow_channel(x0, x1, y0, y1, z_floor, z_open, tag="", fil=HOLLOW_FILLET): - """朝上敞开的减重槽:从 z_open 往下切到 z_floor,槽底水平(朝上受支撑)、侧壁竖直。 - - 竖打(模型 Z = 打印 Z)下这个形状是零支撑的:槽底是水平面、槽壁全是竖直面, - 顶部完全敞开因而不构成封闭空腔。切完该件变成 U 形槽钢而不是实心料: - 抗弯靠两侧 X 向腹板(正好落在弯矩拉压区),中间中性层掏空几乎不损失 I。 - - x0/x1/y0/y1 用世界坐标;z_open 取一个保证贯通顶面的值。 - """ - tool = bx(x0, x1, y0, y1, z_floor, z_open) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Z").fillet(fil).val() - except Exception as e: - print(" [减重槽] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重槽] %s: X%.1f..%.1f Y%.1f..%.1f 自 Z%.1f 向上敞开 (四周留壁 >=%.0fmm)" - % (tag, x0, x1, y0, y1, z_floor, HOLLOW_WALL), flush=True) - return tool - - -def hollow_tent_window(pts_xz, y0, y1, tag="", fil=HOLLOW_FILLET): - """帐篷形减重窗:XZ 平面画轮廓,沿 Y 双向切穿。 - - 轮廓下宽上窄(拱形)——窗顶上方的料形成一个尖顶,斜面与水平夹角 >= 52° 自支撑, - 只有顶部一小段平顶需要桥接(<=20mm,与 P03 同)。轮廓在 X 方向不能吃满整个截面, - 两侧必须留墙,否则该高度上零件会被切断、失去单实体。 - """ - # XZ 工作平面的法线是 -Y,extrude 会朝 -Y 走,所以起点放在 y1 才能覆盖 y0..y1 - tool = (cq.Workplane("XZ", origin=(0, y1, 0)).polyline(pts_xz).close() - .extrude(y1 - y0).val()) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Y").fillet(fil).val() - except Exception as e: - print(" [减重窗] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重窗] %s: XZ 轮廓 %d 点, 沿 Y %.1f..%.1f 切穿 (帐篷形, 斜面自支撑)" - % (tag, len(pts_xz), y0, y1), flush=True) - return cq.Shape(tool.wrapped) - - -# 各关节"电机前端夹紧"统一配方(J2/J3/J4/J5 通用,J1 由 P03 自己那套 S04 凸台实现)。 -# 电机前端盖外端面:Y 轴关节(J2/J4/J5)在 Y_COVER=156,压盖朝 +Y 生长; -# Z 轴关节(J3,roll 电机,轴向 -Z)在 Z_R-56,压盖朝 -Z 生长(-Z 侧是从动件)。 -# 压盖背面与之 0.0 间隙面压,螺钉只提供压紧力。 -# 沉孔相位按【关节局部相位】定义(BCD55,72.2°+90k):换算到全局后 Y 轴关节是 -# 17.8°+90k(与 P03→M1 的 S04 一致),Z 轴关节是同一组数落在全局 (x,y) 平面, -# 两种轴向的物理指向完全一致 —— 全机同一套孔位约定,改一处必须同步另一处。 -CLAMP_BCD = 27.5 -CLAMP_CLOCK = (17.8, 107.8, 197.8, 287.8) # Y 轴关节的全局 (x,z) 相位 -CLAMP_CLOCK_LOCAL = tuple((360.0 - a) % 360.0 for a in CLAMP_CLOCK) # = 72.2+90k -CLAMP_ROOT = 0.5 # 根部薄环厚度:与前端定位环前端面共面,保证熔接成一体 -CLAMP_T = 5.0 # 压盖本体厚度 -CLAMP_CLR = 1.0 # 从动件让位间隙:与 Y 轴关节 hub_gap=6.0 同一个道理 -CLAMP_BACKOFF = CLAMP_T + CLAMP_CLR - CLAMP_ROOT # =5.5 从动件毂面整体后移量 -# 【2026-10-05d】J3 前端"围挡"只切敞口(-Y)那一圈,背脊(+Y)侧整圈保留。 -# 围挡本体 = Z389.0..395.5 的 r30.25..35.0 前端定位环(6mm 高环墙)+ 骑在它上面的 -# R35 压盖外圈(Ø70 比电机 Ø63.6 凸出 3.2mm),把 M3 前端盖和穿过 Ø37 孔的 P06 J3 -# 凸台围在中间。 -# 为什么不整圈削到与电机齐平(R35->R31.8): -# 1. 这圈壁与第 3 步 Loft 在 X=±35 处与 Ø70 柱面**相切**,整圈切完那两片切向楔形 -# (实测 1.32mm3)会脱空成孤立碎块,破坏单实体拓扑(除非把角部一起切掉, -# 那会在背脊下端开一个 6.5mm 深的缺口); -# 2. 压盖根部只有 0.5mm 熔接带,环削到 1.55mm 宽后连接面从 2.37mm2 掉到 0.78mm2, -# 4xM3x8 的夹紧力会把压盖从本体上拉下来。 -# 所以只切敞口那一圈:背脊侧定位环继续定位电机前端盖、并作为压盖唯一且足够强的 -# 支撑/熔接面;压盖本体、Ø37 让位孔、4xM3x8 沉孔、腔口下沿 z_bot=389 全部不动。 -# 设 False 取消。 -M3_FENCE_OPEN_CUT = True - - -def motor_front_clamp(px, pz, plate_t=CLAMP_T, tag="", axis="Y", py=0.0, z_face=None): - """电机前端压盖:把"只被后端安装板单面锁死"改成前后双向面压夹紧。 - - 原设计里每个关节的电机都只有后端板 4xM3x10(S02)锁在尾端面上,前端盖完全裸露, - 大扭矩下电机绕自身轴在 Ø60.3 座孔里点头/翘起。这里在电机前端盖外端面加一枚 - 与后端板同径的 R35 压盖,用 4xM3x8(BCD55)在前端盖上再夹一次 —— 电机被前后两块 - 同心 Ø60.3 端面双向夹死,倾斜自由度归零;夹紧力全部走面压,螺钉不参与定位。 - - 两个轴向共用同一套尺寸,只换刀具体系(不重复实现任何半径/相位): - axis="Y"(J2/J4/J5):关节轴过 (px, pz),前端盖外端面在 Y_COVER,压盖朝 +Y 长; - axis="Z"(J3 roll,轴向 -Z):关节轴过 (px, py),前端盖外端面在 z_face,压盖朝 -Z 长。 - - 局部坐标(s 从前端盖外端面起算,s>0 一律"背离电机",两个轴向同号): - - 压盖 R35,s 从 -CLAMP_ROOT(与前端定位环前端面共面,保证熔接成一体)到 plate_t; - - 背面精确落在 s=0,不让根部那 0.5mm 啃到端盖 R30.15 的外圆; - - Ø37 中央让位孔(R18.5)让开从动件的直锁凸台 R17.8 与 3xS01 输出螺钉头; - - 4xM3x8:Ø3.4 通孔 + Ø6.0 沉孔深 3.0(自前端面沉到 s=2),圆柱头全沉入, - 头面压孔口台阶,中间 2.0mm 压盖本体受压。 - """ - if axis == "Y": - def C(r, s0, s1, du=0.0, dv=0.0): - return Ycyl(r, Y_COVER + s0, Y_COVER + s1, px + du, pz + dv) - elif axis == "Z": - def C(r, s0, s1, du=0.0, dv=0.0): - return cyl(r, z_face - s1, z_face - s0, px + du, py + dv) - else: - raise ValueError("motor_front_clamp: axis 只能是 Y 或 Z,收到 %r" % (axis,)) - - s0, s1 = -CLAMP_ROOT, plate_t - fp = C(35.0, s0, s1) - fp = fp.cut(C(18.5, s0 - 0.5, s1 + 0.5)) # Ø37 中央让位孔 - fp = fp.cut(C(30.15, s0 - 0.5, 0.0)) # Ø60.3 让位槽 - for deg in CLAMP_CLOCK_LOCAL: - rad = math.radians(-deg) # 全局相位 = -局部相位 - fu, fv = CLAMP_BCD * math.cos(rad), CLAMP_BCD * math.sin(rad) - fp = fp.cut(C(3.0, 2.0, s1 + 0.5, fu, fv)) # Ø6.0 沉孔 - fp = fp.cut(C(1.7, s0 - 0.5, 2.0, fu, fv)) # Ø3.4 通孔 - where = ("Y%.1f..%.1f" % (Y_COVER - CLAMP_ROOT, Y_COVER + plate_t)) if axis == "Y" \ - else ("Z%.1f..%.1f" % (z_face - plate_t, z_face + CLAMP_ROOT)) - print(" [前端压盖] %s: R35 %s, 4xM3x8 BCD55@%s (%s 轴)" - % (tag, where, "/".join("%.1f" % d for d in CLAMP_CLOCK), axis), flush=True) - return fp - - -def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0): - """优化直连法兰:增加导向倒角""" - disc = cyl(r_disc, 56.5, z_out) - boss = cyl(17.8, 54.942, 56.5) - chamfer_cone = cyl(18.5, 54.5, 54.942) - shape = shape.fuse(j.L(disc)).fuse(j.L(boss)).cut(j.L(chamfer_cone)) - shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, z_out + 40.0, u, v) - shape = shape.cut(j.L(th)).cut(j.L(cb)) - return shape - - -# ========== 躯干部分 ========== -# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ========== -X_COL = -115.0 -Y_COL = Y_AXIS # 128.0 -Z_LOCK = 463.0 # 横向防脱锁紧螺钉高度(立柱后壁 → 榫头,M4x16 自攻) - - -def p_chest(): - """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱): - 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方; - 2. 顶部接收 P03 43.4x43.4x15mm 定位榫头(44x44 R4 内腔)、4x 竖向安装孔 + 1x 横向防脱锁紧孔 - (全部 Ø3.9 直孔:M4x16 从外部直接拧入自攻成型,无过孔/无沉孔/无预埋); - 3. 底部 50x50x12mm 阶梯榫头插入 P02 榫座:环形台肩(66->50)面承载轴向力,槽壁全周承载剪切; - 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); - 5. 外棱角全部采用 R=6mm 圆角。 - """ - z_bot = 22.0 - z_top = Z_CHEST1 # 470.0 - - col = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot)) - .rect(66.0, 66.0) - .extrude(z_top - z_bot) - .edges("|Z").fillet(6.0)).val() - s = cq.Shape(col.wrapped) - - cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot + 10.0)) - .rect(44.0, 44.0) - .extrude(z_top - z_bot + 1.0) - .edges("|Z").fillet(4.0)).val() - s = s.cut(cav) - - # 底部阶梯榫头:插入 P02 榫座后环形台肩坐实,轴向力走面压而非 M5 螺纹 - tenon = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(50.0, 50.0).extrude(z_bot - 10.0) - .edges("|Z").fillet(4.0)).val() - s = s.fuse(tenon) - - # 底部 M5 夹紧孔(贯通榫头):X 向非对称 (−20/+16) 与榫座孔位配对防反装/防转 90° - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - s = s.cut(cyl(2.75, 9.99, z_bot + 15.0, xx, yy)) - # 顶部 4x 安装孔:统一 Ø3.9 直孔(M4x16 直接拧入自攻成型;只打这一个孔,无过孔/无沉孔),深 20mm - # 位于 66x66 顶环 11mm 实体壁内(内腔 22 → 外壁 33),避开 44x44 内腔与 R4 内腔圆角; - for xx in (X_COL - 27.5, X_COL + 27.5): - for yy in (Y_COL - 13.5, Y_COL + 13.5): - s = s.cut(cyl(1.95, z_top - 20.0, z_top + 0.01, xx, yy)) - # 横向防脱锁紧孔(后壁 → 榫头):Ø3.9 贯穿 11mm 后壁 + 榫头同径(M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - 34.0, X_COL - 20.0, Y_COL, Z_LOCK)) - - w_bot = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 105.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_bot) - - w_top = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 375.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_top) - return s - - -def split_p01_poka_yoke(p01): - """拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印): - 带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁, - 单边配合间隙 0.5)、以及统一 Ø3.9 直孔的分段对锁孔(M4x16 从外面直接拧入自攻成型, - 母口壁与公榫壁同径,无过孔/沉孔/预埋,前后两面对称各一颗); - 孔位相对立柱中心 −16 / +22 非对称(物理防反装防呆);本体 3D 凹刻朝向标识。 - """ - z_bot = 22.0 - z_s1 = 172.0 - z_s2 = 321.0 - z_top = 470.0 - h_lap = 16.0 - - # 非对称对锁孔位(相对立柱中心偏心 -16mm 与 +22mm,偏心差 6mm,反装无法穿入螺栓) - xx_bolts = (X_COL - 16.0, X_COL + 22.0) - - # 分段对锁孔:统一 Ø3.9 直孔(M4x16 从外面直接拧入自攻成型;母口壁与公榫壁同径, - # 没有过孔/沉孔等其它孔),前后两面对称;杆尖伸过榫壁的一段在内腔侧留 Ø4.5 让位。 - def _seam_holes(part, xx, z): - return (part.cut(Ycyl(1.95, Y_COL - 33.6, Y_COL - 21.4, xx, z)) - .cut(Ycyl(1.95, Y_COL + 21.4, Y_COL + 33.6, xx, z)) - .cut(Ycyl(2.25, Y_COL - 21.7, Y_COL - 15.5, xx, z)) - .cut(Ycyl(2.25, Y_COL + 15.5, Y_COL + 21.7, xx, z))) - - # --- P01_A (下段) --- - 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(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() - tongue1 = cq.Shape(tongue1_outer.wrapped).cut(tongue1_cav) - p01_a = p01_a_base.fuse(tongue1) - - z_bolt1 = z_s1 + h_lap / 2.0 # 180.0 - for xx in xx_bolts: - p01_a = _seam_holes(p01_a, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - - # Engrave the front (-Y) flat face, reading VERTICALLY bottom-to-top along - # the column (user request): local +X = world +Z, local +Y = world -X, so - # the trailing "▶" glyph renders pointing world-up (+Z). - front_label_plane_a = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 120.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_a = (cq.Workplane(front_label_plane_a) - .text("P01-A TOP ▶", 9.0, -0.8)).val() - p01_a = p01_a.cut(cq.Shape(label_a.wrapped)) - - # --- P01_B (中段) --- - cut_below_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s1) - 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.9)).val() - p01_b_base = p01_b_base.cut(pocket1) - - tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2)) - .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() - tongue2 = cq.Shape(tongue2_outer.wrapped).cut(tongue2_cav) - p01_b = p01_b_base.fuse(tongue2) - - z_bolt2 = z_s2 + h_lap / 2.0 # 329.0 - for xx in xx_bolts: - p01_b = _seam_holes(p01_b, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - p01_b = _seam_holes(p01_b, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_b = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 240.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_b = (cq.Workplane(front_label_plane_b) - .text("P01-B TOP ▶", 9.0, -0.8)).val() - p01_b = p01_b.cut(cq.Shape(label_b.wrapped)) - - # --- P01_C (上段) --- - 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.9)).val() - p01_c = p01_c_base.cut(pocket2) - for xx in xx_bolts: - p01_c = _seam_holes(p01_c, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_c = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 390.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_c = (cq.Workplane(front_label_plane_c) - .text("P01-C TOP ▶", 9.0, -0.8)).val() - p01_c = p01_c.cut(cq.Shape(label_c.wrapped)) - - return p01_a, p01_b, p01_c - - -def make_p02_poka_yoke(): - """将 P02 拆分为三件模块化组件(均严格 <= 195x190mm,适配 200x200mm 打印床): - 1. P02_Center (底座中核心): 195x190x70mm,完整容纳立柱套筒与前后三角斜撑筋,核心应力区无接缝; - 2. P02_Front (前向力臂): 145x190x22mm,宽跨距 104mm 双榫头 + 预埋 M5 螺母槽; - 3. P02_Rear (后向力臂): 45x190x22mm,窄跨距 80mm 双榫头(跨距差达 24mm 物理防呆,绝不混装)。 - """ - z0 = 0.0 - z1 = 22.0 - y_width = 190.0 - - f_tenon_w = 44.0 - f_tenon_y_span = 52.0 - r_tenon_w = 32.0 - r_tenon_y_span = 40.0 - - # 1. P02_Center (X = -195.0 .. 0.0, 195x190mm) - x_c_min = -195.0 - x_c_max = 0.0 - x_c_len = x_c_max - x_c_min - x_c_mid = (x_c_min + x_c_max) / 2.0 - - c_plate = (cq.Workplane("XY", origin=(x_c_mid, Y_COL, z0)) - .rect(x_c_len, y_width).extrude(z1) - .edges("|Z and X").fillet(8.0) - .edges(">Z").fillet(2.0)).val() - p02_c = cq.Shape(c_plate.wrapped) - - # Socket collar (Z: 22..70mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0).extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0).edges(">Z").chamfer(2.0)).val() - p02_c = p02_c.fuse(cq.Shape(collar_outer.wrapped)) - - # Front & Rear triangular gusset ribs - f_pts = [(-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0)] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(f_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(f_rib.wrapped)) - - r_pts = [(-155.0, z1), (-195.0, z1), (-195.0, z1 + 14.0), (-155.0, 70.0)] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(r_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(r_rib.wrapped)) - - # Front wire guide - wg_pts = [(-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5)] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(wg_pts).close().extrude(9.0, both=True)).val() - p02_c = p02_c.cut(wire_guide) - - # Column socket — stepped mortise: upper 66.6 bore for the 66 box walls, - # lower 50.6 recess (floor 0.6 low) for the 50x50 bottom tenon; the recess - # rim at z=22 is the axial seat, so column weight bears on face, not threads. - socket_upper = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(66.6, 66.6).extrude(z_collar_top - z1 + 1.0) - .edges("|Z").fillet(6.3)).val() - p02_c = p02_c.cut(socket_upper) - socket_lower = (cq.Workplane("XY", origin=(X_COL, Y_COL, 9.4)) - .rect(50.6, 50.6).extrude(z1 - 9.4) - .edges("|Z").fillet(4.3)).val() - p02_c = p02_c.cut(socket_lower) - - # Vertical M5 clamp bolts (offsets match the asymmetric column tenon holes: - # X −20/+16 blocks 180° flip, the ±18 Y span blocks 90° rotation). - # Threads engage the tenon 15mm — clamp only, axial load rides on the seat. - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - p02_c = p02_c.cut(cyl(2.75, -0.01, 10.5, xx, yy)).cut(cyl(5.0, -0.01, 6.0, xx, yy)) - - # Center clamp slots - for cy in (Y_COL - 75.0, Y_COL + 75.0): - plane = cq.Plane(origin=(x_c_mid, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_c = p02_c.cut(slot) - - p02_c = p02_c.cut((cq.Workplane("XY", origin=(X_COL, Y_COL - 70.0, -0.01)).rect(28.0, 80.0).extrude(6.0)).val()) - - # Front mortise pockets - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - front_mortise = (cq.Workplane("XY", origin=(-10.0, yy, 4.8)) - .rect(21.0, f_tenon_w + 0.8).extrude(12.6).edges("|Z and X").fillet(3.0)).val() - p02_c = p02_c.cut(rear_mortise) - p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -185.0, yy)) - - # Debossed text on P02_Center top face. The mid-plate line (y=Y_COL) is - # hidden behind the column collar and the gusset ribs, so both labels sit on - # the flat -Y side strip (y = Y_COL-68) where they stay in full view from the - # label side: REAR on the rear half, FRONT on the front half, clear of the - # clamp slot (x -101..-94) and the M5 screw counterbores. - txt_c_f = (cq.Workplane("XY", origin=(-49.0, Y_COL - 68.0, z1)).text("▲ FRONT (前向) ▲", 9.0, -0.8)).val() - txt_c_r = (cq.Workplane("XY", origin=(-146.0, Y_COL - 68.0, z1)).text("▲ REAR (后向) ▲", 9.0, -0.8)).val() - p02_c = p02_c.cut(cq.Shape(txt_c_f.wrapped)).cut(cq.Shape(txt_c_r.wrapped)) - - # 2. P02_Front (X = 0.0 .. 145.0, 145x190mm) - x_f_len = 145.0 - x_f_mid = 72.5 - f_plate = (cq.Workplane("XY", origin=(x_f_mid, Y_COL, z0)) - .rect(x_f_len, y_width).extrude(z1) - .edges("|Z and >X").fillet(16.0).edges(">Z").fillet(2.0)).val() - p02_f = cq.Shape(f_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(120.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_f = p02_f.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-9.5, yy, 5.25)) - .rect(20.0, f_tenon_w - 0.2).extrude(11.5).edges("|Z and Z").fillet(2.0)).val() - p02_r = cq.Shape(r_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(-215.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_r = p02_r.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * r_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-185.5, yy, 5.25)) - .rect(20.0, r_tenon_w - 0.2).extrude(11.5).edges("|Z and >X").fillet(2.5).edges(">X").chamfer(1.5)).val() - p02_r = p02_r.fuse(cq.Shape(tongue.wrapped)) - p02_r = p02_r.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - nut_pocket = (cq.Workplane("XY", origin=(-185.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val() - p02_r = p02_r.cut(nut_pocket) - - # Same +Y rotation as the front plate; "▼" points local -Y = world +X, - # i.e. toward the centre joint. Along +Y the ~73 mm text fits the 190 mm - # plate width instead of overhanging its 45 mm X span. - txt_r = (cq.Workplane(cq.Plane(origin=(x_r_mid, Y_COL, z1), xDir=(0, 1, 0), - normal=(0, 0, 1))) - .text("P02-REAR ▼ JOIN", 7.5, -0.8)).val() - p02_r = p02_r.cut(cq.Shape(txt_r.wrapped)) - - return p02_c, p02_f, p02_r - - -def p_base_foot(): - """P02_底脚 (重型防倾翻超长力臂底座 + 双向大截面三角加强斜撑筋): - 1. 底盘尺寸大幅扩展为 385x240x22mm (X: -240.0..+145.0, Y: 8.0..248.0); - - 前向延伸至 X = +145.0mm (在手臂垂直投影前方 91.5mm),彻底杜绝手臂前伸与抓握重物时的前倾翻; - - 后向延伸至 X = -240.0mm (在立柱后方 92.0mm),抵御大动态回程后仰力矩; - 2. 立柱插接套筒由 12mm 浅槽大幅升级为 48mm 高加固套筒 (Z: 22.0..70.0mm,外壁厚 7mm); - 3. 前向大三角加强斜撑筋 (Z: 68.0->22.0mm, X: -75.0->-15.0mm, 宽 38mm),抗弯力臂长达 60mm,留足安全间隙不干涉手臂运动; - 4. 后向大三角加强斜撑筋 (Z: 70.0->22.0mm, X: -155.0->-225.0mm, 宽 38mm),抗后仰力臂长达 70mm; - 5. 前斜撑表面设 18mm 宽平滑走线导槽,与 P01 下部走线窗口自然贯通; - 6. 6 组 M6 工业工作台紧固腰槽 (28x7mm) + 底部隐蔽走线槽; - 7. 四周 R=18mm 大圆角过渡与 2.5mm 上边缘倒角。 - """ - z0 = 0.0 - z1 = 22.0 - x_rear = -240.0 - x_front = 145.0 - x_len = x_front - x_rear # 385.0 - x_mid = (x_rear + x_front) / 2.0 # -47.5 - y_width = 240.0 - - # 1. 主底盘 (385 x 240 x 22 mm) - base_plate = (cq.Workplane("XY", origin=(x_mid, Y_COL, z0)) - .rect(x_len, y_width) - .extrude(z1) - .edges("|Z").fillet(18.0) - .edges(">Z").fillet(2.5)).val() - s = cq.Shape(base_plate.wrapped) - - # 2. 立柱外包加固套筒 (Z: 22.0..70.0 mm, 外廓 80x80mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0) - .extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0) - .edges(">Z").chamfer(2.0)).val() - s = s.fuse(cq.Shape(collar_outer.wrapped)) - - # 3. 前向抗倾翻三角加强斜撑筋 (Front Triangular Gusset Rib) - f_pts = [ - (-75.0, z1), - (-15.0, z1), - (-15.0, z1 + 4.0), - (-75.0, 68.0), - ] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(f_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(f_rib.wrapped)) - - # 4. 后向抗倾翻三角加强斜撑筋 (Rear Triangular Gusset Rib) - r_pts = [ - (-155.0, z1), - (-225.0, z1), - (-225.0, z1 + 4.0), - (-155.0, 70.0), - ] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(r_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(r_rib.wrapped)) - - # 5. 前斜撑表面走线导槽 (与 P01 前走线窗 Z=70..140mm 自然顺接) - wire_guide_pts = [ - (-75.5, z1 + 1.0), - (-14.0, z1 + 1.0), - (-14.0, z1 + 4.0), - (-75.5, 68.5), - ] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(wire_guide_pts).close() - .extrude(9.0, both=True)).val() - s = s.cut(wire_guide) - - # 6. 立柱深插槽内腔 (Pocket: 66.6 x 66.6 mm, 留 0.3mm 精密滑配间隙) - socket_box = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(66.6, 66.6) - .extrude(z_collar_top - 10.0 + 1.0) - .edges("|Z").fillet(6.3)).val() - s = s.cut(socket_box) - - # 7. 4x M5 底部垂直紧固螺栓孔与沉孔 - for xx in (X_COL - 21.0, X_COL + 21.0): - for yy in (Y_COL - 21.0, Y_COL + 21.0): - th = cyl(2.75, -0.01, 10.5, xx, yy) - cb = cyl(5.0, -0.01, 6.0, xx, yy) - s = s.cut(th).cut(cb) - - # 8. 6 组 M6 工业工作台紧固腰槽 (28 x 7 mm) - slot_positions = [ - (-215.0, Y_COL - 85.0), (-215.0, Y_COL + 85.0), # 后部两角 - (-47.5, Y_COL - 95.0), (-47.5, Y_COL + 95.0), # 中部两侧 - (120.0, Y_COL - 85.0), (120.0, Y_COL + 85.0), # 前部两角 - ] - for cx, cy in slot_positions: - plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - s = s.cut(slot) - - # 9. 底部隐蔽走线通槽 - wire_ch = (cq.Workplane("XY", origin=(X_COL, Y_COL - 80.0, -0.01)) - .rect(28.0, 100.0) - .extrude(6.0)).val() - s = s.cut(wire_ch) - return s - - -def p_girdle(j1): - """P03_肩关节安装悬臂 (轻量化板式桁架版 + 侧开式电机托架): - 1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm); - 2. 主干由 66mm 实心块改为双侧 6mm 桁架竖板 (Y = Y_ARM ± 27..33),外轮廓保持旧版 - "主干 ∪ 侧筋"并集不变 —— 只减材料不加体积,零新增干涉风险;实心 728 -> 286 cm³ (-61%); - 3. 竖板帐篷形减重窗 (顶/底斜面 ≥52°,自支撑;仅 20mm 平顶可桥接); - 4. 底部 9mm 承力鞍座 + 43.4x43.4 定位榫头 (配 P01_C 44x44 内腔,单边 0.3mm 间隙); - 鞍座底面 66x66 与立柱顶环带承压面积 2403mm² —— 竖向载荷走面压,螺栓只防上抬/防滑; - 5. 4x 安装孔 (X_COL±27.5, Y_COL±13.5):Ø3.9 直孔,M4x16 从上方直接拧入自攻成型 - (鞍座与立柱顶环同径,无过孔/沉孔/预埋),可拆装; - 6. 下腹中央龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰下缘) + 顶部横向锁紧环抱筋 (66x9x10mm, - 与两侧竖板外面齐平、顶面压在筋板脊线上,仅顶前棱一道 2mm 倒角); - 7. 侧开式电机托架 (+Y侧全开, X: -51.0..11.5,前端随 X_BAY_EXT 前延 3mm), - M1 电机可从侧方斜插卡入装配(插槽比电机长 3mm),由后端法兰 4xM3x16 锁死; - 电机舱 Ø60.7 从后端直通到前端面(2026-10-02 用户裁定"去掉台阶改成平的":无环形台阶、 - 无喇叭锥;原前置止脱锁紧法兰 4xM3 随之取消,电机轴向由后端 4xM3x16 约束); - 9. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通; - 10. Ø26mm 中央走线孔;竖板间为开放腔体(无封闭空腔),M3 螺丝刀通道直接走腔体,拧紧更顺手。 - """ - X_MOUNT = Z_R1 # -51.0 (Rear face of M1) - T_FLANGE = 10.0 # 10mm thick high-strength mounting flange - X_BACK = X_MOUNT - T_FLANGE # -61.0 - Y_ARM = Y_AXIS # 128.0 - Z_ARM = Z_RL # 610.0 - Y_P_IN = 27.0 # 桁架竖板内表面 (Y_ARM ± 27) - Y_P_OUT = 33.0 # 桁架竖板外表面 (与旧主干齐平) - T_SADDLE = 9.0 # 底部鞍座厚度 - TENON = 43.4 # 定位榫头边长(P01_C 内腔 44x44 R4,单边留 0.3mm) - WIN_R = 4.0 # 竖板减重窗内角圆角(消除内尖角应力集中,Kt 由 ~2.5 降到 ~1.2) - BOLT_DX, BOLT_DY = 27.5, 13.5 # 4x 安装孔相对立柱中心偏移:落在顶环 11mm 实体壁内 - - # 1. 主安装法兰盘 (74x74mm, 四角倒圆 R=8mm) - # 背面那圈 2mm 倒角已去掉:板料只伸进法兰 0.1mm,倒角面与板背面相交会在 - # Z≈574.9 挤出一批 2mm 短棱,正是这批短棱让整条交接内棱网络倒不出圆角; - # 同时它跨过 R8 角部圆弧,就是角上那道"卷起来"的怪倒角。 - flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM)) - .rect(74.0, 74.0) - .extrude(T_FLANGE) - .edges("|X").fillet(8.0)).val() - s = cq.Shape(flange.wrapped) - - # 2. 双侧桁架竖板 (6mm):旧"斜挑支撑主干"与"大三角加强筋"轮廓同段挤出 → 自动并集成单块侧板 - # 主干的后缘两点取在加强筋斜边的延长线上,使外轮廓顶部成为一整条直线 - # (否则后缘 (-148,550) 处有 5° 折点、留下 5mm 台阶)。 - # Z_CREST = 侧筋脊线高度,同时是顶筋 2mm 顶前倒角的下沿:倒角从 (X-71,Z_CREST) - # 斜上到 (X-69,Z_CREST+2) = 顶筋顶面,斜坡面与倒角面几乎相切,顶部读起来是一整张面。 - Z_CREST = Z_ARM + 33.0 # 643(初版 642;顶筋整体上移 3mm 让出扳手空间后跟着抬) - - RIB_SLOPE = (Z_CREST - 575.0) / ((X_BACK - 10.0) - (X_COL - 15.0)) - - def rib_line_z(x): - return Z_CREST + RIB_SLOPE * (x - (X_BACK - 10.0)) - - trunk_pts = [ - (X_COL - 33.0, Z_CHEST1), - (X_COL + 33.0, Z_CHEST1), - (X_COL + 33.0, 505.0), - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_ARM - 5.0), - (X_COL - 10.0, rib_line_z(X_COL - 10.0)), - (X_COL - 33.0, rib_line_z(X_COL - 33.0)), - ] - rib_pts = [ - (X_COL - 33.0, Z_CHEST1), # 底边两端与主干/鞍座对齐 (±33):原来收进 3mm, - (X_COL + 33.0, Z_CHEST1), # 会在鞍座顶面上留下 0.7/3mm 短棱,卡住整条底棱圆角 - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_CREST), - (X_BACK - 10.0, Z_CREST), # 脊线 (-71,643):正好落在顶筋前面与 2mm 倒角下沿的交点 - (X_COL - 15.0, 575.0), - (X_COL - 30.0, 530.0), - ] - for sign in (-1, 1): - ya = Y_ARM + sign * Y_P_IN - yb = Y_ARM + sign * Y_P_OUT - y0, y1 = min(ya, yb), max(ya, yb) - for prof in (trunk_pts, rib_pts): - s = s.fuse(cq.Shape(Yplate(prof, y0, y1).wrapped)) - - # 3. 竖板减重窗 (帐篷形:斜面 ≥52° 自支撑、仅 20mm 平顶可桥接;避开鞍座与 M5 螺母柱) - # 6 个内角全部 R4 圆角:把内尖角应力集中 (Kt≈2.5) 降到 ≈1.2,同时圆角面仍是竖直/自支撑面 - win_pts = [ - (X_COL - 24.0, 498.0), (X_COL + 23.0, 498.0), (X_COL + 25.0, 522.0), - (X_COL + 10.0, 545.0), (X_COL - 10.0, 545.0), (X_COL - 25.0, 524.0), - ] - for sign in (-1, 1): - yc = Y_ARM + sign * 0.5 * (Y_P_IN + Y_P_OUT) - tool = (cq.Workplane("XZ", origin=(0, yc, 0)).polyline(win_pts).close() - .extrude(Y_P_OUT - Y_P_IN + 1.0, both=True) - .edges("|Y").fillet(WIN_R).val()) - s = s.cut(cq.Shape(tool.wrapped)) - - # 4. 底部承力鞍座:坐实立柱顶面,把两竖板、龙骨与榫头连成整体 - s = s.fuse(bx(X_COL - 33.0, X_COL + 33.0, Y_ARM - Y_P_OUT, Y_ARM + Y_P_OUT, - Z_CHEST1, Z_CHEST1 + T_SADDLE)) - - # 5. 下腹部纵向抗弯龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰背面;交接内棱在融合后做真 3D 圆角) - keel_pts = [ - (X_COL + 33.0, Z_CHEST1), # (-82, 470) - (X_COL + 33.0, 495.0), # (-82, 495) - (X_BACK + 0.1, 573.0), # (-60.9, 573) 斜拉至法兰背面 - (X_BACK + 0.1, 594.4), # (-60.9, 594.4) 顶端贴法兰 - (X_COL + 42.1, 585.0), # (-72.9, 585) - (X_COL + 20.0, Z_CHEST1), # (-95, 470) 背面一路斜到底:原来在 (-95,480) 有个折点, - ] # 折点离鞍座顶面只 1mm,R2.5 圆角爬不上去 → 龙骨底棱整组倒不出 - s = s.fuse(cq.Shape(Yplate(keel_pts, Y_ARM - 6.0, Y_ARM + 6.0).wrapped)) - - # 7. 顶部横向锁紧环抱横筋:Y 向与两竖板外面齐平 (Y_ARM±33)、前面与筋板脊线共面 (X=-71)、 - # 顶面 Z=645 压住脊线 (Z=643) 并与 2mm 顶前倒角相切 → 顶筋与两侧脊线合成一整块。 - # 整体比初版抬高 3mm (底面 633→636):4x M3 后端沉孔 Ø6.5 跨在 Z 627.2..633.7 上, - # 底面 633 会压住孔口上沿 0.7mm、扳手让位只剩负间隙;抬到 636 后退到孔口上方 2.3mm、 - # 离螺钉轴线 5.55mm。顶面 645 不越过法兰 R8 角部圆弧在 Y=95 处的 645.9 上界。 - tie_rib = (cq.Workplane("YZ", origin=(X_BACK - 10.0, Y_ARM, Z_ARM + 30.5)) - .rect(66.0, 9.0) - .extrude(10.0) - .edges(cq.selectors.BoxSelector((X_BACK - 10.5, Y_ARM - 34.0, Z_ARM + 34.5), - (X_BACK - 9.5, Y_ARM + 34.0, Z_ARM + 35.5))) - .chamfer(2.0)).val() - s = s.fuse(cq.Shape(tie_rib.wrapped)) - - # 6.y 体-体交接凹棱:融合完成后对"凹棱"直接做真 3D 圆角。 - # 原先这里是手工沿棱 fuse 的 10 条"正方形−四分之一圆"焊珠小体(_cove),已整体删除: - # 焊珠端面是平的、悬在半空,两条互相垂直的焊珠在角上互不搭接 → 圆角"缺一块", - # 并与相邻体的棱互相穿插留 1~3mm² 碎面。真圆角在面边界自然收口、多棱相交处由 OCC - # 生成角部补片。 - from cadquery import selectors as _sel - from OCP.BRepAdaptor import BRepAdaptor_Surface - from OCP.GeomAbs import GeomAbs_Plane - from OCP.TopAbs import TopAbs_REVERSED - - def _outward(f, sh): - """平面面的外法向:先按 Orientation 修正,再用"面中心外移 1mm 是否仍在实体内"兜方向。""" - ad = BRepAdaptor_Surface(f.wrapped) - if ad.GetType() != GeomAbs_Plane: - return None - ax = ad.Plane().Axis().Direction() - n = cq.Vector(ax.X(), ax.Y(), ax.Z()) - if f.wrapped.Orientation() == TopAbs_REVERSED: - n = n * -1.0 - c = f.Center() - if cq.Solid(sh.wrapped).isInside(cq.Vector(c.x + n.x, c.y + n.y, c.z + n.z), 1e-6): - n = n * -1.0 - return n - - def _fillet_reflex(lo, hi, radii, tag, eps=0.9): - """倒出 lo~hi 区域内的凹棱(两面夹 270° 材料)。判据:从棱中点沿 (n1-n2) 外探 eps, - 落在实体内 = 凹、落在外 = 凸。区域内凹棱先整批一次倒(半径逐级降级,且倒完验拓扑: - OCC 会给出"没抛异常但面已坏"的结果),建不出时按相连棱分组逐组倒。""" - nonlocal s - - def _pick(sh): - edges = sh.Edges() - idx = {} - for i, e in enumerate(edges): - idx[e.hashCode()] = i - adj = [[] for _ in edges] - for f in sh.Faces(): - for ee in f.Edges(): - k = idx.get(ee.hashCode()) - if k is not None: - adj[k].append(f) - box = _sel.BoxSelector(lo, hi) - out = [] - for i, e in enumerate(edges): - if e.Length() <= 6.0 or len(adj[i]) != 2 or not box.filter([e]): - continue - n1 = _outward(adj[i][0], sh) - n2 = _outward(adj[i][1], sh) - if n1 is None or n2 is None: - continue - dx, dy, dz = n1.x - n2.x, n1.y - n2.y, n1.z - n2.z - ln = math.sqrt(dx * dx + dy * dy + dz * dz) - if ln < 1e-6: - continue - p = e.Center() - if cq.Solid(sh.wrapped).isInside( - cq.Vector(p.x + dx / ln * eps, p.y + dy / ln * eps, - p.z + dz / ln * eps), 1e-6): - out.append(e) - return out - - es0 = _pick(s) - if not es0: - print(" [P03 真圆角] %-14s 区域内无凹棱,跳过" % tag, flush=True) - return - - def _groups(ee): - """按共端点把凹棱连成链/网络:一组一次倒,OCC 才能在交汇处生成角部补片; - 逐条倒会在交汇处留下断口(看着就像圆角倒一半停了)。""" - pts = [e.Vertices() or [e.Center()] for e in ee] - par = list(range(len(ee))) - - def find(a): - while par[a] != a: - par[a] = par[par[a]] - a = par[a] - return a - - for i in range(len(ee)): - for j in range(i + 1, len(ee)): - if any(pi.distance(pj) < 0.05 for pi in pts[i] for pj in pts[j]): - par[find(i)] = find(j) - g = {} - for i in range(len(ee)): - g.setdefault(find(i), []).append(ee[i]) - return list(g.values()) - - def _do(edges_, rr): - """倒完立刻验拓扑:OCC 会给出"没抛异常但面已坏"的结果,必须挡下来。""" - try: - ns = cq.Shape(cq.Solid(s.wrapped).fillet(rr, edges_).wrapped) - except Exception: - return None - return ns if cq.Solid(ns.wrapped).isValid() else None - - # 先试"区域内全部凹棱一次倒"(探针实测这样能整批成型,且只重建一次, - # 不会像分批那样把同一张面反复重算坏掉);不行再按相连棱分组逐组倒。 - for rr in radii: - ns = _do(es0, rr) - if ns is not None: - s = ns - print(" [P03 真圆角] %-14s %d 条凹棱 R%.1f ✓ 整批一次成型" - % (tag, len(es0), rr), flush=True) - return - - n_ok, n_grp, left = 0, 0, len(es0) - for _ in range(24): - es = _pick(s) - if not es: - break - got = False - for g in sorted(_groups(es), key=lambda g: -sum(e.Length() for e in g)): - for rr in radii: - ns = _do(g, rr) - if ns is None: - continue - s = ns - n_ok += len(g) - n_grp += 1 - print(" [P03 真圆角] %-14s 第%d组 %d 条 R%.1f ✓" - % (tag, n_grp, len(g), rr), flush=True) - got = True - break - if got: - break - if not got: - left = len(es) - break - print(" [P03 真圆角] %-14s 共 %d 组 %d 条凹棱倒圆,剩 %d 条保持尖棱" - % (tag, n_grp, n_ok, left), flush=True) - - XR, YC, ZS = X_BACK, Y_ARM, Z_CHEST1 + T_SADDLE # -61, 128, 479 - # —— 鞍座顶面 (Z=479) 上的交接内棱:龙骨三条 + 两竖板内侧两条,一次真圆角 —— - # 半径上限 2.0:龙骨侧面 Y=122 与 M5 螺钉头外缘 Y=118.75 只隔 3.25mm, - # 原来的 R3 焊珠会顶到螺帽承压边,R2.0 留 1.25mm。 - _fillet_reflex((X_COL - 34.0, Y_ARM - 40.0, ZS - 3.0), - (X_COL + 34.0, Y_ARM + 40.0, ZS + 7.0), - (2.0, 1.5), "鞍座顶面交接") - # —— 法兰背面 (X=-61) 上的交接内棱:只倒竖板/龙骨的竖直棱 —— - # 顶筋底面那条水平棱 (Z=636) 不倒:4x M3 沉孔 Ø6.5 在 Z 627.2..633.7, - # 倒上 R2.5 会把螺钉上方 5.55mm 的扳手让位吃掉 2.5mm。 - # 上界取 631:竖板内棱被顶筋底面切成 573..636 / 636..643 两段,只倒下面长的那段 - # ——把 636..643 的 7mm 短段并进来,整批就从"一次成型"退化成逐组、且少倒 6 条。 - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, 570.0), - (X_BACK + 3.0, Y_ARM + 40.0, 631.0), - (2.5, 2.0, 1.5), "竖板/龙骨↔法兰") - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, Z_ARM + 34.5), - (X_BACK + 3.0, Y_ARM + 40.0, Z_ARM + 35.5), - (1.5, 1.0), "顶筋顶↔法兰") - - # 与立柱顶部配合的定位榫头 (43.4x43.4x15mm):配 P01_C 44x44 R4 内腔,单边 0.3mm 滑配 - boss = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 15.0)) - .rect(TENON, TENON) - .extrude(15.0) - .edges("|Z").fillet(4.2)).val() - s = s.fuse(cq.Shape(boss.wrapped)) - - # 4x 安装孔:统一 Ø3.9 直孔(M4x16 从腔体正上方拧入自攻成型;鞍座与立柱顶环同径、无沉孔) - # 孔位 (X_COL±27.5, Y_COL±13.5) 同时满足:①在立柱顶环实体壁内(内腔 22 → 外壁 33); - # ②在 P03 两竖板之间、避开中间龙骨,从腔体正上方用扳手可直接拧紧。 - for xx in (X_COL - BOLT_DX, X_COL + BOLT_DX): - for yy in (Y_COL - BOLT_DY, Y_COL + BOLT_DY): - s = s.cut(cyl(1.95, Z_CHEST1 - 0.5, Z_CHEST1 + T_SADDLE + 0.5, xx, yy)) - - # 1x 横向防脱锁紧:榫头 −X 侧向内 Ø3.9x14 直孔(与立柱后壁 Ø3.9 孔同轴,M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - TENON / 2.0 - 1.0, X_COL - TENON / 2.0 + 14.0, Y_COL, Z_LOCK)) - - # 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道) - # 孔位 = 电机尾端 4x M3 螺纹径向槽实测位(电机.STEP):BCD55、相位 39.9°+90k。 - # 旧 35.1×40.9 矩形孔距与实际槽位差 ~4.7mm(对角两颗螺钉无处咬合), - # 2026-10-02 用户指出"螺钉没有对齐"后按 STEP 实测修正。 - for dy, dz in ((21.06, 17.65), (-17.65, 21.06), (-21.06, -17.65), (17.65, -21.06)): - py = Y_ARM + dy - pz = Z_ARM + dz - th = Xcyl(1.7, X_BACK - 0.5, X_MOUNT + 4.0, py, pz) - cb = Xcyl(3.25, X_BACK - 0.5, X_BACK + 4.5, py, pz) - s = s.cut(th).cut(cb) - - # 中央走线孔 (Ø26mm) - wire_hole = Xcyl(13.0, X_COL - 40.0, X_MOUNT + 2.0, Y_ARM, Z_ARM) - s = s.cut(wire_hole) - - # 6. 前向电机全包托架 (X: -51.0..8.5+X_BAY_EXT=11.5,Ø60.7 舱孔平直贯通到端面) - # 端面随插槽加长前延 X_BAY_EXT;叉板背面(10.5+X_FORK_GAP=11.5)与端面 0.0 硬贴平 - # (用户两次要求"搞成平的";J1 转动摩擦由用户知悉,发涩时可微松 M2) - X_FRONT = 8.5 + X_BAY_EXT - R_POD = 36.5 - R_BORE = 30.35 - - cradle_outer = (cq.Workplane("YZ", origin=(X_MOUNT - 0.1, Y_ARM, Z_ARM)) - .circle(R_POD) - .extrude(X_FRONT - X_MOUNT + 0.1) - .edges(">X").chamfer(1.5)).val() - s = s.fuse(cq.Shape(cradle_outer.wrapped)) - - # 电机定子配合内腔(最终状态,2026-10-02 用户裁定"去掉台阶改成平的+保留固定电机的面"): - # Ø60.7 圆柱孔壁从前端面平直贯通到舱底(无窄颈、无台阶、无喇叭锥); - # 舱底 = 一个完整的平坦环面(X=5.06+X_BAY_EXT=8.06,与插槽末端墙共面,中开 Ø39 凸台过孔) - # ——即电机前限位面。电机轴向由后端法兰 4xM3x16 约束; - # 前端锁紧由 4 个 Ø10 局部凸台上的 M3 圆柱头螺钉承担(见下方凸台段)。 - motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.06 + X_BAY_EXT, Y_ARM, Z_ARM) - s = s.cut(motor_pocket) - - # 4x 前置 M3 锁紧螺钉凸台(BCD=55mm,17.8°/107.8°/197.8°/287.8°): - # 平直舱孔的沉头座位由 4 个 Ø10 局部凸台承载——凸台位于舱孔前段 8.06..11.5, - # 电机前端面(5.0)之前 3mm,电机斜插路径不经过;M3x8 圆柱头坐在凸台沉孔 - # 底(x=9.5)旋入电机前端面,咬合 3.5mm,兼作电机前端防转定位。 - for deg in (17.8, 107.8, 197.8, 287.8): - rad = math.radians(deg) - fy = Y_ARM + 27.5 * math.cos(rad) - fz = Z_ARM + 27.5 * math.sin(rad) - s = s.fuse(Xcyl(5.0, 5.06 + X_BAY_EXT, X_FRONT, fy, fz)) - th = Xcyl(1.6, 5.06 + X_BAY_EXT, X_FRONT, fy, fz) - cb = Xcyl(3.0, X_FRONT - 2.0, X_FRONT, fy, fz) - s = s.cut(th).cut(cb) - - # J1 输出凸台过孔 (Ø39 × 3.44——J1 凸台与 3xM2 输出螺钉的必经孔,非台阶; - # 原 6.7mm 长颈与半圆台肩已随舱底前移一并消除) - hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM) - s = s.cut(hub_clearance) - - # 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒): - # M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。 - # 轴向约束:后端法兰 4xM3(电机尾端面)单向锁死(前置止脱法兰随平直舱孔要求取消); - # 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06+X_BAY_EXT=8.06 - # (比电机前端面多 3mm 斜插余量,电机可倾斜卡入),止脱法兰环随之前移、环宽保持 3.44mm。 - s = s.cut(bx(X_MOUNT, 5.06 + X_BAY_EXT, Y_ARM, Y_ARM + 60.0, Z_ARM - 60.0, Z_ARM + 60.0)) - - # 7. 散热系统:双侧大视窗 + 顶部斜向对流排热槽 - for sign in (-1, 1): - win_plane = cq.Plane( - origin=(-24.5, Y_ARM + sign * 35.0, Z_ARM), - xDir=(1, 0, 0), - normal=(0, sign, 0) - ) - win_cut = cq.Workplane(win_plane).slot2D(34.0, 20.0).extrude(16.0, both=True).val() - s = s.cut(win_cut) - - for deg in (45, 135): - rad = math.radians(deg) - slot_plane = cq.Plane( - origin=(-24.5, Y_ARM + 33.0 * math.cos(rad), Z_ARM + 33.0 * math.sin(rad)), - xDir=(1, 0, 0), - normal=(0, math.cos(rad), math.sin(rad)) - ) - slot_cut = cq.Workplane(slot_plane).slot2D(30.0, 6.5).extrude(15.0, both=True).val() - s = s.cut(slot_cut) - - return s - - -def p_fork(j1, j2): - """P04_肩叉_螺旋体30%_墙5底5_竖打 (Shoulder Fork / L-Bracket) - 加长避让顺滑流线版: - 1. 头部与身子起点自 X=5.5 前移加长至 X=10.5+X_FORK_GAP=11.5,叉板背面与 P03 托架端面 - (X=8.5+X_BAY_EXT=11.5) 0.0 硬贴平(2026-10-02 用户两次要求"搞成平的"; - P03 侧插槽独立加长 3mm 便于 M1 电机斜插卡入); - 2. J1 从动法兰圆盘已删除(盘面在 column 前缘形成鼓楞):J1 由 boss(Ø35.6)贯通 P03 前盖中心孔直连电机输出盘;column 顶边保持 Z=646.5 与 P03 环齐高,R=18mm 大圆弧顺滑过渡; - 3. 头部转接凸台加长贯通 P03 前盖中心孔 (R=19.5mm),直连 J1 旋转法兰盘; - 4. M2 保留后端安装板(R=35mm, 带 rear_mount_holes 螺钉孔),前端定位环鼓出圆盘已删除(前端定位由 column 端面 pocket 承担),中间镂空散热; - 4b. 【2026-10-02 新增】M2 前端压盖:原方案电机只被后端安装板(S02 4xM3x10)单面锁死, - 大扭矩下电机绕 Y 轴在 Ø60.3 座孔里点头/翘起,固定不牢。现在前端盖外端面 - (Y=156.0) 再加一枚同径 R35 压盖,用 4x M3x8 在电机前端盖 BCD55 上把电机 - 从前端夹一次 —— 电机被前后两块同心 Ø60.3 端面双向夹紧,倾斜自由度归零; - 螺钉沉在压盖沉孔内不外凸,不占用 J2 轴向间隙(P05 法兰盘已相应后移); - 5. 严格满足 single-solid 流形拓扑与 360° 全范围零干涉 (COLLISION pairs = 0)。 - """ - # 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm) - rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH) - - # 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH) - s = rear_plate.fuse(front_collar) - - # 2b. 【2026-10-02 新增】M2 前端压盖(几何与理由见 motor_front_clamp)。 - # 必须在第 6/7 步所有 R30.15~30.5 圆柱刀具走完之后才熔接,否则压盖根部会被 - # front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边(见 7b)。 - fp = motor_front_clamp(X_ARM, Z_SH, tag="P04/M2") - - # 3. J1 从动法兰圆盘已删除:原 R=36.5 圆盘盘面在 column 前缘形成圆盘鼓楞, - # J1 与 P04 的连接由 boss(Ø35.6 贯通 P03 前盖中心孔 R=19.5)直连电机输出盘 - # 承担,螺钉走 boss 端面 4xB 孔,圆盘无传力功能已去除。 - - # 4. XZ 平面有机连续流线轮廓(随 X_FORK_GAP 整体前移,避让顺滑过渡): - x_b, z_b = -12.65 + X_FORK_GAP, 506.0 - x_a, z_a = 10.5 + X_FORK_GAP, 545.0 - Lz = z_a - z_b - d0 = 0.514 * Lz - d1 = 0.0 - - hermite_pts = [] - N_h = 16 - for i in range(N_h + 1): - t = i / N_h - h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0 - h10 = t**3 - 2.0 * (t**2) + t - h01 = -2.0 * (t**3) + 3.0 * (t**2) - h11 = t**3 - t**2 - xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11 - zh = z_b + t * Lz - hermite_pts.append((xh, zh)) - - dx_flare = (53.5 + X_FORK_GAP) - (44.0 + X_FORK_GAP) - dz_flare = 545.0 - 490.0 - R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) - - xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) - .moveTo(10.5 + X_FORK_GAP, 545.0) - .lineTo(10.5 + X_FORK_GAP, Z_RL + 36.5) - .lineTo(28.0 + X_FORK_GAP, Z_RL + 36.5) - .radiusArc((44.0 + X_FORK_GAP, 630.0), 18.0) - .lineTo(44.0 + X_FORK_GAP, 545.0) - .radiusArc((53.5 + X_FORK_GAP, 490.0), -R_flare) - .lineTo(18.5 + X_FORK_GAP, 490.0) - .lineTo(-16.5 + X_FORK_GAP, 490.0) - .threePointArc((-15.1 + X_FORK_GAP, 498.5), (-12.65 + X_FORK_GAP, 506.0))) - for pt in hermite_pts[1:]: - xz_wire = xz_wire.lineTo(pt[0], pt[1]) - xz_wire = xz_wire.close() - xz_shape = cq.Shape(xz_wire.extrude(100.0, both=True).val().wrapped) - - # 5. YZ 平面包络 - # 【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面。 - # 原来第 2->3 点之间是一条直线 (164.5,646.5)->(155.5,526),沿 X 方向拉成 - # 4048mm2 的平板(实测法向 (0,1,-0.07),带 4 度斜度),正对电机上方 —— - # 两侧被 xz_wire 的 R18 圆弧挤出去,形成"中间一块平、两边鼓"的观感。 - # 改法:把这段直线换成三次 Hermite 曲线,两端切线分别沿 Y(接上面 Z=646.5 - # 的顶段)与沿 Z(接下面 Y=155.5 的竖直段),保证 G1 连续无缝; - # 中部向 +Y 外凸约 3mm,弧度观感与两侧 R18 一致。 - # 为什么不用单段圆弧:A 处切线要沿 Y、B 处要沿 Z,单个圆心无法同时满足 - # (解出来 R=9 与 R=120.5 矛盾),必须用 Hermite/Bezier —— 与 P05/P06 - # 的 Loft 过渡同一套做法。 - # 注意:顶点绕序必须与原列表一致(先 y_b 后 y_a),否则多边形自交、 - # 布尔后零件会碎掉(实测会掉到 48cm3 / 3 块实体)。 - y_a = (Y_COVER - 0.5, Z_SH + 36.0) # (155.5, 526.0) 曲线终点 - y_b = (Y_AXIS + 36.5, Z_RL + 36.5) # (164.5, 646.5) 曲线起点 - _d = math.hypot(y_b[0] - y_a[0], y_b[1] - y_a[1]) - _m0y, _m1z = -0.5 * _d * 0.35, 0.5 * _d * 0.5 # 切线:起点沿 -Y,终点沿 +Z - yz_pts = [(Y_AXIS - 36.5, Z_RL + 36.5)] - for k in range(13): - t = k / 12.0 - h00 = 2 * t ** 3 - 3 * t ** 2 + 1 - h10 = t ** 3 - 2 * t ** 2 + t - h01 = -2 * t ** 3 + 3 * t ** 2 - h11 = t ** 3 - t ** 2 - yz_pts.append((h00 * y_b[0] + h10 * _m0y + h01 * y_a[0], - h00 * y_b[1] + h11 * _m1z + h01 * y_a[1])) - yz_pts += [(Y_COVER - 0.5, Z_SH), - (Y_REAR - 8.0, Z_SH), - (Y_REAR - 8.0, Z_SH + 36.0)] - yz_solid = Xplate(yz_pts, -20.0, 60.0) - - c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped) - c_int.Build() - column = cq.Shape(c_int.Shape()) - - s = s.fuse(column) - - # 6. 中部电机定子散热切除 - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH)) - - # 7. M2 接口成型 - # 承压面起点用 AB.REAR_SEAT(=0.6 台肩 + 2.0 公差),与 Joint.pocket 同源: - # P04 走的是自己那套 BORE_R 圆柱刀具(没调 Joint.pocket),这里必须同步改, - # 否则 J2 关节的电机公差位仍是旧的 0.6,与 J3~J6 不一致。 - # 【2026-10-05】承压面段加一刀 SEAT_R(=R35) 圆腔,SEAT_TOP=+1.0。 - # 取满 R35 才能让承压面与外壁齐平成**一个平面**(用户要求:把凸台、围挡 - # 一起弄平,做成和电机两头齐平的平面)。取小于 R35 必然留下一圈凸起 - # 2.6mm 的围挡环墙 —— 详见 Joint.pocket 的 docstring。 - s = j2.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - c_m2 = [cyl(BORE_R, -AB.REAR_SEAT, COVER_Z - 3.5), cyl(CB_R, COVER_Z - 3.5, COVER_Z + 0.6), - cyl(AB.SEAT_R, -AB.REAR_SEAT, AB.SEAT_TOP)] - s = s.cut(*[j2.L(item) for item in c_m2]) - - - # 7b. M2 前端压盖熔接(见 2b):必须在 6/7 步所有 R30.15~30.5 圆柱刀具走完之后, - # 否则压盖根部会被 front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边。 - s = s.fuse(fp) - - # 7c. 【2026-10-02 新增】立柱减重槽(见 hollow_channel)。 - # 立柱 Z525..635 整段是壁厚最深处达 17.5mm 的实心料,而 J2 在 z=560/600 的 - # 安全系数有 19。挖成朝上敞开的 U 形槽后立柱变成槽钢:抗弯的两片 X 向腹板 - # 正好落在弯矩拉压区(z=560 截面 X13..45,中性面在 X29,腹板占 X13~23/X35~45), - # 被掏掉的正是中性层附近几乎不参与抗弯的料。 - # 槽底 Z525 以下保留整块实心过渡到电机座;顶部敞开故非封闭腔、免支撑。 - # 必须在 8 步 J1 凸台/螺钉孔之前切:槽的 X23..35 范围离 J1 凸台(X<=11.5)与 - # 3xS01 沉孔(X12..14)都有富余,但先切可以保证后面那些刀具不会把槽壁打薄。 - s = s.cut(hollow_channel(P04_CH["x0"], P04_CH["x1"], P04_CH["y0"], P04_CH["y1"], - P04_CH["z_floor"], 660.0, tag="P04立柱")) - - # 8. 头部转接凸台加长(贯通 P03 前盖中心孔,跨越装配缝直连 J1 输出盘) - boss = cyl(17.8, 54.942, 61.5 + X_FORK_GAP) - s = s.fuse(j1.L(boss)) - s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M['m2_bcd'] / 2.0 * math.cos(a * AB.D2R) - v = AB.M['m2_bcd'] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 63.0 + X_FORK_GAP, u, v) - # 沉孔台阶固定在 63.0(不随前移):M2x10 旋入电机输出毂的咬合量与改前一致; - # 螺钉头(63..65)相对叉板背面(64.5)外露 0.5mm,位于 2mm 叉缝空隙内,无干涉。 - cb = cyl(2.8, 63.0, 74.0 + 40.0 + X_FORK_GAP, u, v) - s = s.cut(j1.L(th)).cut(j1.L(cb)) - - return s - - - -def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket", hub_gap=1.0, - roll_clamp=False): - """Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08): - 1. 顶部从动法兰与前侧 Loft 自然相切顺接; - 2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉; - 3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合; - 4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。 - - hub_gap = 从动法兰盘背面与上级电机前端盖之间的轴向让位间隙。 - 默认 1.0(贴合防磨)。P05 传 6.0:J2 处 P04 新增了 M2 前端压盖 - (Y 155.5..161.0),必须让出这 6.0mm 才能装下;见 p_shoulder_roll_base。 - - roll_clamp = 本件作为 roll 电机【前端夹紧件】时为 True(J3/P05 夹 M3)。 - roll 电机(J3/J6)轴向是 -Z,前端盖外端面在 z_r-56,-Z 侧是从动件: - 压盖朝 -Z 长(Z_COVER-5..Z_COVER),从动件毂面必须整体后移 CLAMP_BACKOFF=5.5 - 让位(与 Y 轴关节 hub_gap=6.0 同一个道理,只是基准间隙 0.5 而非 1.0)。 - P08 传 False:J6 的 M6 前端压盖尚未做(从动件 P09 让位要改工具法兰直锁凸台)。 - """ - z_bot = z_r - 56.0 # roll 电机底端 (front collar) - z_plate = z_r + 8.0 # roll 电机顶端安装板 (rear plate top);见第 8 步 t_plate 说明 - y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) - y_hub_in = HL.Y_COVER + hub_gap # 157.0 / (P05) 162.0 从动法兰内侧面 - z_hub_in = y_hub_in - HL.Y_REAR # 法兰盘背面的电机局部 z(凸台长度跟着它走) - y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值) - - # 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm) - s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS) - - # 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm) - disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch) - s = s.fuse(disc) - - # 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph) - Z_top = z_pitch - 35.0 - z_start = z_bot - N = 16 - 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) - 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_mid = max(v_c, v_s + 0.02) - # 后缘两角 R2 圆角:把 y=123 凸包与 R35 柱面交汇的两条 90° 竖棱圆润化 - # (磨掉"凸起来的"尖角,不动后缘高度/位置,打印无悬挂) - FR = 2.0 - 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 + FR) - .radiusArc((35.0 - FR, -5.0), FR) - .lineTo(-35.0 + FR, -5.0) - .radiusArc((-35.0, -5.0 + FR), FR) - .lineTo(-35.0, v_s) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域) - top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(y_hub_in, Z_top) - .lineTo(y_out, Z_top) - .lineTo(y_out, z_pitch) - .lineTo(y_hub_in, z_pitch) - .close() - .extrude(35.0, both=True)) - s = s.fuse(top_fill.val()) - - # 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料, - # 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整! - cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(-999.0, z_plate) - .lineTo(y_hub_in, z_plate) - .lineTo(y_hub_in, z_pitch + 50.0) - .lineTo(-999.0, z_pitch + 50.0) - .close() - .extrude(60.0, both=True)) - s = s.cut(cut_top_solid.val()) - - # 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强 - cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0) - s = s.cut(cut_open) - - # 7. 上级电机同心圆弧避让 - clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch) - s = s.cut(clearance) - - # 8. 下方电机安装孔位与定位止口 - # t_plate=8.0:Joint.pocket 的尾端面承压面让空了 MOTOR_TOL=2.0,承压面到螺钉沉孔 - # 台阶只剩 t_plate-cb_depth-REAR_SEAT = 8-4-2.6 = 1.4mm 膜厚;给到 8 而不是原来的 - # 7,是为了不让这层膜退化成 0.4mm。7->8 让 P05/P08 的杯体外壳顶 z_plate 抬 1mm。 - s = j_roll.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j_roll.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j_roll.pocket(s) - - # 9. 电机定子配合空腔 - s = s.cut(cyl(30.5, z_r - 49.5, z_r - 0.5, HL.X_ARM, HL.Y_AXIS)) - - # 10. 俯仰轴直连法兰结构(凸台延伸至法兰盘背面之后 2.5mm,跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, z_hub_in + 2.5) - s = s.fuse(j_pitch.L(boss)) - s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j_pitch.L(th)).cut(j_pitch.L(cb)) - - # 11. L 型转角连接处圆角过渡(圆角 R=2.0mm): - # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中 - try: - wp_s = cq.Workplane(obj=s) - sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate)) - s = wp_s.edges(sel_l).fillet(2.0).val() - except Exception as e: - print(f"[{name}] L型转角圆角异常: {e}") - - # 12. roll 电机(M3)前端压盖:与 P04/P06/P07 的 M2/M4/M5 压盖同一套配方, - # 只是 roll 电机轴向为 -Z,压盖朝 -Z 长(见 motor_front_clamp 的 axis="Z")。 - # 熔接必须放在最后:第 6/7/8/9 步所有 R30.15~30.5 圆柱刀具都走完之后才接, - # 否则压盖根部那 0.5mm 薄环会被 front_collar_pocket / 定子孔啃掉一圈。 - if roll_clamp: - s = s.fuse(motor_front_clamp(HL.X_ARM, 0.0, tag="%s/M3" % name[:3], - axis="Z", py=HL.Y_AXIS, z_face=z_r - COVER_Z)) - - # 13.【2026-10-05d】只切"围挡"那一圈:敞口(-Y)侧的前端定位环 + 压盖根部薄环。 - # 用户 2026-10-05 指出这圈环墙把 M3 前端盖和 P06 的 J3 凸台围在中间;我第一版 - # 把整个前端(定位环 + 压盖 + 腔口下沿 389->395)都切了,电机前端没了支撑面 —— - # 过度。现在只切敞口这一圈,Y 界限与第 6 步 cut_open 完全一致 - # (Y_AXIS-40 .. Y_AXIS+3),保证 -Y 半圈从 Z=395.5 一路开到压盖背面 Z=389; - # 背脊(+Y)侧整圈定位环保留: - # - 继续定位 M3 前端盖(内孔 r30.25,前端盖 Ø60.3 -> 单边 0.10mm); - # - 继续当 R35 压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带,本次改动 - # 完全不碰)→ M3 仍是后端板 4xM3x10 + 前端压盖 4xM3x8 双向面压夹紧; - # - 压盖本体 / Ø37 让位孔 / 4xM3x8 沉孔 / 腔口下沿 z_bot=389 全部不动, - # 压盖敞口侧的端面从 Z=389.5 降到 389 与背面齐平,视觉上不再凸出。 - # 为什么不能整圈削到与电机齐平(R35->R31.8,见 M3_FENCE_OPEN_CUT 处的说明): - # ① 这圈壁与第 3 步 Loft 在 X=±35 处与 Ø70 柱面**相切**,整圈切完那两片切向楔形 - # (实测 1.32mm3)会脱空成孤立碎块,破坏单实体拓扑; - # ② 压盖根部只有 0.5mm 熔接带,环削到 1.55mm 宽后连接面从 2.37mm2 掉到 0.78mm2, - # 4xM3x8 的夹紧力会把压盖从本体上拉下来。 - # 附带效果:盒切把 Loft 在 X=±35 与 Ø70 柱面相切的那两片切向楔形一起带走 - # (楔形位于 Y_AXIS 附近 < Y_AXIS+3),不会留下孤立碎块 -> 单实体拓扑保持。 - if roll_clamp and M3_FENCE_OPEN_CUT: - s = s.cut(bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, - z_bot - 0.1, z_r - COVER_Z + 6.6)) # Z 388.9 .. 395.6 - print(" [围挡] %s/M3: 只切敞口侧那一圈(-Y:定位环 Z389~395.5 + 压盖根部薄环);" - "背脊侧整圈保留作压盖支撑,压盖本体/沉孔/腔口下沿不变" % name[:3], flush=True) - - return s - - -def p_shoulder_roll_base(j2, j3, half=None): - """P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版) - - 本件在链路上有两重身份,两处让位都是为前端压盖: - 1. hub_gap=6.0(从动侧):J2 处 P04 新增了 M2 前端压盖(前端夹紧电机,消除大扭矩下的 - 倾斜),压盖占 Y 155.5..161.0,所以从动法兰盘背面必须从 Y=157 后移到 Y=162 - (留 1.0mm 动间隙)。 - 2. roll_clamp=True(前端夹紧侧):P05 自己抱着 M3(J3,-Z 轴),原来也只有后端板 - 4xM3x10 单面锁死;现在按 motor_front_clamp 的统一配方在 M3 前端盖外端面 - (Z=389)加一枚 R35 压盖(Z 384..389,4xM3x8 BCD55),与后端板前后双向面压, - 倾斜自由度归零。代价是对侧从动件 P06 的 J3 毂面整体后移 CLAMP_BACKOFF=5.5 - (Z 388.5→383.0),见 p_humerus_bone。 - P08 保持默认 —— J5 处 P07 的前端压盖已做(只需 6.0 的 hub_gap),而 J6 的 M6 - 前端压盖尚未做,故 roll_clamp 传 False。 - """ - return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, - name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - hub_gap=6.0, roll_clamp=True) - - -def p_humerus_bone(j3, j4): - """P06_大臂骨架_螺旋体25%_墙4底5_竖打 (Humerus Bone - Loft 圆弧转方有机过渡版): - 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, - 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 - 1. 借鉴 P05 大臂旋转座几何放样算法: - - 顶端结合面 (Z = 383.0mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯; - - 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面; - - 过渡区 (Z: 367.0..383.0mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变; - 2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗; - 3.【2026-10-03】J3 毂面整体后移 CLAMP_BACKOFF=5.5(顶端 388.5→383.0,直锁凸台 - 59.5→65.0):P05 新增 M3 前端压盖,占 Z 384..389,原先 388.5 的毂面只剩 0.5mm - 让位放不下压盖。配方与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源: - 压盖 5.0 + 动间隙 1.0 - 原基准 0.5 = 5.5。凸台同步加长,3xS01 输出螺钉的头面 - 台阶(局部 s=58)、杆长、咬合量与改前完全一致,J3 直锁与零干涉不受影响。 - 压盖中央 Ø37 让位孔(R18.5)也让开 R17.8 凸台,单边余 0.7mm。 - 4. 严格单实体 (solids = 1) 且与上下级旋转构件及电机实现动态 0 碰撞干涉。 - """ - # 1. M4 底部抱紧套筒与后端安装板 (R=35mm) - rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL) - front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL) - s = rear_m4.fuse(front_m4) - - # 顶端结合面与过渡高度 - # z_top 由 M3 前端盖外端面反推:压盖占 [Z_COVER-CLAMP_T, Z_COVER],再留 CLAMP_CLR - # 动间隙 -> 383.0(改前 388.5,与前端盖只有 0.5mm,放不下压盖)。 - z_cover3 = Z_R3 - COVER_Z # 389.0 M3 前端盖外端面 - z_top = z_cover3 - CLAMP_T - CLAMP_CLR # 383.0 J3 毂面(= 388.5 - CLAMP_BACKOFF) - z_bot = Z_EL + 35.0 # 367.0 - - # 2. 下部实心基座块 (Z: Z_EL..z_bot + 0.5) - lower_block = bx(X_ARM - 35.0, X_ARM + 35.0, Y_REAR - 8.0, Y_COVER - 0.5, Z_EL, z_bot + 0.5) - s = s.fuse(lower_block) - - # 3. 借鉴 P05 的 Loft 放样过渡体:圆弧变方形(20 层平滑放样,保持严格对称 4 段式截面拓扑) - N = 20 - wp = cq.Workplane("XY") - for i in range(N + 1): - t = i / float(N) - z_curr = z_bot + t * (z_top - z_bot) - st = 3.0 * (t**2) - 2.0 * (t**3) - - # 前侧:t=0 为平直面 (Y=155.5, v=27.5),t=1 渐变为与 Ø70 正切的圆弧顶点 (Y=163.0, v=35.0) - v_sf = 27.5 * (1.0 - st) + 0.05 * st - v_cf = max(27.5 * (1.0 - st) + 35.0 * st, v_sf + 0.05) - - # 后侧:从 t=0 的平直面 (Y=92.0, v=-36.0) 渐变到 t=1 的圆弧 (Y=93.0, v=-35.0) - v_sr = -36.0 * (1.0 - st) - 0.05 * st - v_cr = min(-36.0 * (1.0 - st) - 35.0 * st, v_sr - 0.05) - - w = (cq.Workplane("XY", origin=(X_ARM, Y_AXIS, z_curr)) - .moveTo(-35.0, v_sf) - .threePointArc((0.0, v_cf), (35.0, v_sf)) - .lineTo(35.0, v_sr) - .threePointArc((0.0, v_cr), (-35.0, v_sr)) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 电机中间定子散热裸露切除 (100% 裸露) - s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_EL)) - - # 5. M4 接口成型 - s = j4.front_collar_pocket(s, r_bore=30.25, z0=50.0, z1=56.5) - s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j4.pocket(s) - - # 5b. 【2026-10-02 新增】M4 前端压盖 4xM3x8(BCD55):M4 原来只被后端板单面锁死, - # 大扭矩下会绕 J4 轴在 Ø60.3 座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 熔接放在所有 R30.15~30.5 圆柱刀具之后,理由同 p_fork 7b。P07 的 J4 从动侧 - # 已相应后移到 Y>=162(见 p_forearm_bone 的 J4_CLEAR),与压盖前端面留 1.0mm。 - s = s.fuse(motor_front_clamp(X_ARM, Z_EL, tag="P06/M4")) - - # 5c. 【2026-10-02 新增】电机上方过渡块减重窗(见 hollow_tent_window)。 - # Z360..385 这段 Loft 过渡块壁厚最深 15.8mm,是 P06 唯一一块可掏的厚料 - # (Z332..367 的下部本体已经被 Ø61 定子孔掏成 4.5mm 薄壁,没得再掏)。 - # 但它正压在 J3 毂下面,所以三条红线: - # 1) 窗顶最高只到 Z374 —— J3 的 Ø70 毂面在 Z380..388.5,窗顶留 6mm 整块 - # 实心托住毂面承压,绝不碰毂面和它后面的 3xS01 直锁螺钉; - # 2) 窗的 X 范围 3..37 —— 截面本体 X-15..55,两侧各留 18mm 竖墙, - # 该高度上零件才不会被切成两半(必须单实体); - # 3) 拱形(下宽上窄)轮廓,斜面与水平 >= 52° 自支撑,只有顶部 X12..28 的 - # 16mm 平顶需要桥接(与 P03 同量级)。 - # 窗底取 Z362 而不是更低:Z360 附近是全段安全系数最低的截面, - # 抬高窗底 4mm 比横向收窄 8mm 更划算(减重量只少 3cm3,安全系数从 92% 回到 100%)。 - # 窗沿 Y 双向切穿(Y87..172 超出本体,前后都贯通),顺带把这段的定子散热口 - # 一起打开。 - # 【2026-10-03 关闭】用户实机视点确认这个拱形窗在正面看就是一个贯通的矩形黑洞, - # 位于J4 关节正上方、整机最显眼的落脚点,视觉上像"零件破了个洞",非常不雅观。 - # 权衡后恢复实心:代价只有 161->183cm3(+22cm3 / +27g PLA),而强度本来就已经是 - # 无窗基线的 100%(这段本来就不缺强度),减重收益纯属锦上添花。 - # 保留 P06_WIN_XZ 与本段注释,需要减重时把开关改回 True 即可复原。 - if P06_HOLLOW_WIN: - s = s.cut(hollow_tent_window(P06_WIN_XZ, 80.0, 175.0, tag="P06过渡块")) - else: - print(" [减重窗] P06过渡块: 已关闭(2026-10-03 视觉整改),该段恢复实心", flush=True) - - # 6. J3 直连法兰结构(凸台延伸至 65.0 跨过结合面牢固熔接,保持单实体与零干涉) - # 凸台随 J3 毂面一起后移 CLAMP_BACKOFF=5.5(59.5→65.0):进入电机输出毂的深度 - # 仍是 s54.942..56.0 那 1.06mm,3xS01 的头面台阶(s=58)/杆长/咬合量一律不变。 - boss = cyl(17.8, 54.942, 59.5 + CLAMP_BACKOFF) - chamfer_cone = cyl(18.5, 54.5, 54.942) - s = s.fuse(j3.L(boss)).cut(j3.L(chamfer_cone)) - s = s.cut(j3.L(cyl(3.5, 54.0, 58.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 58.0 + 40.0, u, v) - s = s.cut(j3.L(th)).cut(j3.L(cb)) - - return s - - -def p_forearm_bone(j4, j5, half=None): - """P07_小臂骨架_螺旋体30%_墙5底5_竖打 L型高刚度无干涉版(深度圆角优化版) - 设计要点: - 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - - 彻底消除原全深S弯在J4轴下方向两边突出的干涉结构(两边突出的结构全部去除) - - 垂直立柱与下方 M5 电机水平座之间采用规则折角(Corner Chamfer/Gusset)刚性连接 - 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 - 3. J4 轴俯仰动态行程从受限的 ±54° 大幅释放至无死角大范围摆动(零干涉) - 4. 满足单实体 (solids=1) 与装配零碰撞。 - 5.【2026-10-02】M5 前端压盖(4xM3x8 BCD55)消除"电机只被后端板单面锁死"的倾斜隐患; - J4 从动侧整体让位:y_hub_in 由 158.5 后移到 J4_CLEAR=162.0,让出 P06 的 M4 前端压盖 - (前端面 Y=161.0),留 1.0mm 动间隙。J4 直连凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - y_out = Y_HUB + PLATE_T # 174.0 - # J4 从动侧让位基准:必须 >= P06 的 M4 前端压盖前端面(Y_COVER+5.0=161.0) + 1.0mm - J4_CLEAR = Y_COVER + 6.0 # 162.0 - y_hub_in = J4_CLEAR - 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) - rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR) - s = collar_front.fuse(rear_plate) - - # 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL) - disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL) - s = s.fuse(disc) - - # 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_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)) - s = s.fuse(roof_w.val()) - - # 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座) - vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) - .moveTo(y_hub_in, z_top_housing) - .lineTo(y_out, z_top_housing) - .lineTo(y_out, Z_EL) - .lineTo(y_hub_in, Z_EL) - .close() - .extrude(35.0, both=True)) - s = s.fuse(vert_leg.val()) - - # 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块") - # 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm) - # 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end) - # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! - # 注:顶板整条加高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_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_roof_top) - .threePointArc(p_mid_arc, (y_hub_in, z_arc_end)) - .lineTo(y_out, z_arc_end) - .lineTo(y_out, z_top_housing) - .lineTo(y_arc_start, z_top_housing) - .close() - .extrude(35.0, both=True)) - s = s.fuse(gusset_w.val()) - - # 6. 下级 P08 旋转圆盘动态避让切除(R=35.5mm,预留 0.5mm 间隙) - p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR) - s = s.cut(p08_clearance) - - # 6b. 【2026-10-02 新增】顶板根部减重槽(见 hollow_channel)。 - # 顶板在 Z255..280 一整段是壁厚最深 15mm 的实心料,而 J4 在 z=245 的安全系数 - # 高达 122、z=195 也有 24。挖成朝上敞开的 U 形槽,顶板变成槽钢: - # 槽底 Z255 以下保留整块实心到电机座,抗弯的两片 X 向腹板(X-15..-7 / X47..55) - # 留在弯矩拉压区。Y 上限取 156 而非核心的 166,是为了避开 y_hub_in=158.5 起的 - # 竖直立柱本体(立柱是 J4 从动法兰的承力柱,不能掏)。 - # 槽顶 Z280 就是顶板上表面,敞口向上,免支撑、非封闭腔。 - s = s.cut(hollow_channel(P07_CH["x0"], P07_CH["x1"], P07_CH["y0"], P07_CH["y1"], - P07_CH["z_floor"], 300.0, tag="P07顶板")) - - # 7. M5 电机安装接口与定子空腔 - s = j5.front_collar_pocket(s, r_bore=30.25 + CLEARANCE_GAP, z0=50.0, z1=56.5) - s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j5.pocket(s) - s = s.cut(Ycyl(30.5 + CLEARANCE_GAP, HL.Y_REAR - 0.5, y_front - 6.0, X_ARM, Z_WR)) - - # 半包散热设计:-Z底部电机定子通风口 - vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0) - s = s.cut(vent_cut) - - # 7b. 【2026-10-02 新增】M5 前端压盖 4xM3x8(BCD55):M5 原来只被后端板单面锁死, - # 大扭矩下会绕 J5 轴在座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 必须在第 6 步 p08_clearance(R35.5 让位孔,Y>=156)之后熔接,否则压盖会被削掉。 - # P08 的 J5 从动侧已相应后移到 Y>=162(make_pitch_to_roll_bracket 的 hub_gap=6.0)。 - s = s.fuse(motor_front_clamp(X_ARM, Z_WR, tag="P07/M5")) - - # 8. J4 直连法兰结构(凸台延伸到法兰盘背面之后 2.0mm 跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, (y_hub_in - HL.Y_REAR) + 2.0) - s = s.fuse(j4.L(boss)) - s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0))) - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - th = cyl(1.4, 54.0, 58.0, u, v) - cb = cyl(2.8, 58.0, 74.0 + 40.0, u, v) - s = s.cut(j4.L(th)).cut(j4.L(cb)) - - # 9. 工艺走线通孔 - slot_z = (Z_EL + z_top_housing) / 2.0 - slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z) - s = s.cut(slot) - - # ========== 关键圆角优化 ========== - try: - # 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角) - wp_s = cq.Workplane(obj=s) - sel_rear = cq.selectors.BoxSelector( - (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).fillet(FILLET_SMALL).val() - except Exception as e: - print(f"P07 顶板后边缘圆角异常: {e}") - - return s - - -def p_wrist_bracket(j5, j6): - """P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版) - - hub_gap=6.0:J5 处 P07 新增了 M5 前端压盖(Y 155.5..161.0),从动法兰盘背面必须 - 从 Y=157 后移到 Y=162 让位;J5 直锁凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, - name="P08_腕偏转座_螺旋体25%_墙4底5_竖打", - hub_gap=6.0) - - -def p_tool_flange(j6): - """P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版 - 关键改进: - 1. 外轮廓全圆角(R=4mm) - 2. 法兰面倒角(2mm) - 3. 螺孔边缘倒角(1.5mm) - """ - z_bot = Z_HUB6 - PLATE_T - 9.0 - s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) - P09_M4 = [] - - # 4x M4螺栓孔 - # - # 分布圆半径 20 -> 22(BCD 40 -> 44):R20 与 3xS01 输出螺钉的 Ø5.6 螺丝刀通道 - # (make_direct_hub 里 cyl(2.8, 58.0, z_out+40) 在 BCD29=R14.5、相位 351.4/232.3/112.1°) - # 咬穿沉孔。0° 那个孔中心 (39.5,128) 到 351.4° 通道中心 (33.84,125.83) 只有 6.06mm, - # 而两孔半径之和是 3.5+2.8=6.30 —— 差 0.24mm,Ø7.0 沉孔壁被啃掉一块, - # 完整圆柱面(88mm²)裂成 45.7+35.4 两片,即用户截图里的月牙孔。 - # 该相位差只有 8.6°,转动 M4 孔相位无法躲开(0/90/180/270 与 45 度错开全部仍 <6.30), - # 只能加大分布圆半径:R22 时最小中心距 7.96mm,余量 1.66mm。 - # 边壁仍充足:R35 法兰外径 - (R22 + 沉孔 3.5) = 9.5mm。 - for a in (0, 90, 180, 270): - rad = 22.0 - u = rad * math.cos(a * D2R) - v = rad * math.sin(a * D2R) - # 实际切割推迟到 make_direct_hub 之后(见下)。这里只算出 4 个孔位。 - P09_M4.append((X_ARM + u, Y_AXIS + v)) - - # 定位止口 + 走线孔 - pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS) - thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS) - s = s.cut(pilot).cut(thru) - - # 直连法兰 - s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0) - - # 4x M4 孔(必须在 make_direct_hub 之后切) - # - # 直连法兰那步 fuse 了 R35 圆盘(局部 z=56.5..74 -> 全局 z=102..121 一侧), - # 会把先切的孔重新填死:分段求交显示 z=97..102 无干涉、z=102 以上整根杆都埋在料里 - # (每段 12.57 mm^3 = pi*2^2*1,全截面被埋)。 - # - # 孔径与深度同时修正:旧值 Ø3.7 比 M4 杆 Ø4.0 还细,且只到 z=105, - # 而 M4x16 头面在 z=97、杆要走到 z=113 —— 杆尾 8mm 无处可去(干涉 147.3 mm^3)。 - # Ø4.4 是 M4 标准间隙孔(ISO 273 medium ≈ Ø4.3)。 - # 孔顶要盖过 M4x16 的杆尾:头面 z=97 + 杆长 16 = z=113,故开到 Z_HUB6+4=115, - # 比杆尾多留 2mm(实测开到 112 时最后 1mm 杆尾仍撞料 37.7mm³)。 - for (fx, fy) in P09_M4: - s = s.cut(cyl(2.2, z_bot - 1.0, Z_HUB6 + 4.0, fx, fy)) - s = s.cut(cyl(3.5, z_bot - 1.0, z_bot + 4.0, fx, fy)) - - # ========== 关键圆角优化 ========== - try: - # 外轮廓圆角 - s = s.edges("|Z").fillet(FILLET_SMALL) - except: - pass - - try: - # 法兰面倒角 - s = s.edges("Z").fillet(FILLET_TINY) - except: - pass - - return s - - -# ---------------------------------------------------------------- assembly ---------------- -def build(): - print("=" * 80, flush=True) - print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True) - print("=" * 80, flush=True) - t_stage1 = time.time() - - print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True) - t0 = time.time() - motor = cq.importers.importStep(AB.SRC_STEP).val() - motor = place(motor, T(0, 0, -2.57)) - AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None - print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True) - - JS = {} - for tag, cn, rear, d, xd, holder, driven in HL.JOINTS: - JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0) - j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7)) - - PARTS[:] = [] - AB.CENTS.clear() - AB.GRP = {} - par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"} - driven = {"J1": "P04_肩叉_螺旋体30%_墙5底5_竖打", "J2": "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "J3": "P06_大臂骨架_螺旋体25%_墙4底5_竖打", - "J4": "P07_小臂骨架_螺旋体30%_墙5底5_竖打", "J5": "P08_腕偏转座_螺旋体25%_墙4底5_竖打", "J6": "P09_工具法兰_螺旋体30%_墙4底5_竖打"} - motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3", - "J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"} - for t in ("J1", "J2", "J3", "J4", "J5", "J6"): - AB.GRP[motor_of[t]] = par[t] - AB.GRP[driven[t]] = t - AB.GRP.update({ - "P01_A_立柱下段_三角形30%_墙4底5_竖打": "root", "P01_B_立柱中段_三角形30%_墙4底5_竖打": "root", "P01_C_立柱上段_三角形30%_墙4底5_竖打": "root", - "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": "root", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": "root", - "P03_肩吊臂_螺旋体35%_墙5底5_竖打": "root" - }) - AB.JAW = {} - - p01_full = p_chest() - p01_a, p01_b, p01_c = split_p01_poka_yoke(p01_full) - p02_c, p02_f, p02_r = make_p02_poka_yoke() - - parts_to_build = [ - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7, - "200mm适配分段A:高166mm+16mm阶梯公榫+顶端 2x Ø3.9 对锁孔(M4x16 自攻)+底部50x50x12榫头+P01-A 竖排TOP刻字"), - ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb, - "200mm适配分段B:高165mm+双端公母榫+两端各 2x Ø3.9 对锁孔(M4x16 自攻,非对称孔位防反装)+P01-B 竖排TOP刻字"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8, - "200mm适配分段C:高149mm+底端母榫+底端 2x Ø3.9 对锁孔(非对称孔位防反装)+顶面P03安装台(4x Ø3.9 安装孔 + 1x Ø3.9 横向锁紧孔)+P01-C 竖排TOP刻字"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "底座核心承力座", lambda: p02_c, 0x475569, - "200mm适配中座:195x190mm核心承力座+48mm套筒+12mm阶梯榫座(轴向面承载)+前后三角加强筋+前后非对称防呆母榫槽+刻字"), - ("P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打", "前向防翻力臂", lambda: p02_f, 0xe11d48, - "200mm适配前臂:145x190mm扩展力臂+104mm宽跨距双公榫+底部M5预埋螺母沉槽+P02-FRONT刻字"), - ("P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打", "后向防翻力臂", lambda: p02_r, 0xd97706, - "200mm适配后臂:45x190mm扩展力臂+80mm窄跨距双公榫+底部M5预埋螺母沉槽+P02-REAR刻字"), - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563, - "轻量化板式桁架版:主干由 66mm 实心块改为双侧 6mm 桁架竖板+帐篷减重窗(斜面≥52°自支撑)+R4内角,外轮廓与旧版并集完全一致,实心 728->286cm³;底部 9mm 承力鞍座(Ø3.9 安装孔)+12mm 中位龙骨筋(自鞍座斜拉至法兰下缘)+40~45°外扩斜撑;保留 10mm 后置法兰、侧开式电机托架、Ø60.7 直通电机舱(平孔无台阶)(M1 从+Y滑入,后端 4xM3x16 锁死)、大散热窗与对流槽;无封闭空腔、免支撑"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "肩叉", lambda: p_fork(j1, j2), 0x2563eb, - "齐平插槽+ M2 前端压盖+ 立柱减重槽版:叉板背面与P03托架端面(11.5)0.0硬贴平(用户要求外观齐平),P03侧插槽独立加长3mm,M1电机可倾斜卡入,J1输出凸台贯通直连电机,J1从动法兰圆盘鼓楞已删(接缝区与P03止脱环齐径齐高R36.5/顶646.5),单实体全角度零干涉;【2026-10-02】M2 前端压盖(R35, Y155.5..161.0):电机前端盖外端面加 4xM3x8 沉孔(BCD55)夹紧,与后端安装板 4xM3x10 形成前后双向面压,消除大扭矩下电机在座孔里的倾斜;【2026-10-02】立柱减重槽(X23~35 / Y102~154 / 自Z525向上敞开):立柱Z525~635整段壁厚峰值17.5mm,J2在z=560/600的安全系数有19,改成U形槽钢后361->289cm3(-20%),抗弯的两片X向腹板正好落在弯矩拉压区(z=560截面X13~45、中性面X29,腹板占X13~23/X35~45),掏掉的正是中性层附近几乎不参与抗弯的料;槽底Z525以下保留整块实心过渡到电机座,槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X23~35 比最初的 X21~37 收窄 2mm,强度从 88% 回到 95%);【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面(全行程重扫 907s 确认 J1~J6 行程逐项不变):原来 yz_pts 里(164.5,646.5)->(155.5,526) 是一条直线,沿 X 拉成 4048mm2 平板(法向 (0,1,-0.07)、带 4° 斜度),正对电机上方,两侧被 xz_wire 的 R18 圆弧挤出去,形成「中间一块平、两边鼓」的观感。改成三次 Hermite(两端切线分别沿 Y/Z,与上下水平段 G1 连续),中部向 +Y 外凸约 3mm,弧度与两侧 R18 一致;单段圆弧无法同时满足两端切线(解出 R=9 与 R=120.5 矛盾)故用 Hermite。P04 体积 282.1->274.3cm³(弧面替代平面削掉的料);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, - "V3圆角+ J2前端夹紧配合:Loft外轮廓R=10mm,L型转角内部2mm倒角,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂);J2 从动法兰盘背面由 Y=157 后移至 Y=162(hub_gap=6.0,给 P04 新增的 M2 前端压盖让位,J2 直锁凸台同步加长到 Y=165.5,3xS01 输出螺钉咬合量不变);保证 J2 全角度 0 干涉;【2026-10-05d 只切敞口那一圈围挡】用户指出 M3 电机前端(另一头)也有一圈围挡:Z389.0~395.5 的 r30.25~35 前端定位环(6mm 高环墙) + 骑在它上面的 R35 压盖外圈(Ø70 比电机 Ø63.6 凸 3.2mm),把 M3 前端盖连同穿过 Ø37 孔的 P06 J3 凸台围在中间。第一版把整个前端(定位环+压盖+腔口下沿 389->395)都切了属过度(电机前端失去支撑面、M3 退回单面锁死),已回退;现只切敞口(-Y)那一圈(盒切,Y 界限与 cut_open 一致),-Y 半圈从 Z=395.5 一路开到压盖背面 Z=389,背脊(+Y)侧整圈保留作前端盖定位与压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带);压盖本体/Ø37 孔/4xM3x8 沉孔/腔口下沿 389.0 全部不动,P05 只少 3.2cm3(154.8->151.6),仍单实体。不整圈削到 R31.8 的原因:那圈壁与 Loft 在 X=±35 处与 Ø70 柱面相切,整圈切完会脱空出 1.32mm3 碎块破坏单实体;且压盖根部熔接面会从 2.37mm2 缩到 0.78mm2,4xM3x8 夹紧力会把压盖拉下来。开关 M3_FENCE_OPEN_CUT。【2026-10-03】M3 前端压盖(R35, Z384.0..389.0):J3 roll 电机原来也只有后端板 4xM3x10 单面锁死(S02),大扭矩下绕 J3 轴在 Ø60.3 座孔里点头;现按 P04/P06/P07 同一配方在 M3 前端盖外端面(Z=389)加 4xM3x8(BCD55)沉孔压盖,与后端板前后双向面压,倾斜自由度归零(配方唯一差别是 roll 电机轴向 -Z,压盖朝 -Z 长,见 motor_front_clamp axis=\"Z\");代价是对侧 P06 的 J3 毂面整体后移 5.5mm 让位;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488, - "Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体;【2026-10-03】取消电机上方Loft过渡块(Z360~385)的拱形减重窗,恢复实心:该窗沿Y贯通,正面看是整机最显眼处的矩形黑洞(用户视觉整改),代价仅 +22cm3/+27g,强度仍为无窗基线100%;【2026-10-03】J3 毂面整体后移 5.5mm(顶端 388.5→383.0,直锁凸台 59.5→65.0):给 P05 新增的 M3 前端压盖(Z384..389)让位,留 1.0mm 动间隙——与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源,换算式同为「压盖 5.0 + 动间隙 1.0 - 原基准 0.5」;凸台同步加长,3xS01 头面台阶(s=58)/杆长/咬合量与 J3 直锁关系一律不变;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, - "V2 L型结构 + 顶板减重槽:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉;【2026-10-02】顶板根部Z255~280开朝上敞开的减重槽(X-3~43 / Y103~153, Y上限取153避开y_hub_in=158.5起的竖直立柱本体):顶板由实心变槽钢,288->230cm3(-20%),槽底以下保留整块实心到电机座,抗弯靠两侧各10mm的X向腹板;槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X-3~43 比最初的 X-7~47 收窄 4mm,强度从 88% 回到 96%);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706, - "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626, - "V2圆角:外轮廓R=3mm,法兰面倒角2mm;【2026-10-02】打印填充由100%降到30%:该截面安全系数288是全机裕度最大的件,满填充纯属浪费(件号同步改为P09_工具法兰_螺旋体30%)"), - ] - - total_parts = len(parts_to_build) - for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): - print(f" [{idx}/{total_parts}] 正在构建 {name} ({cn})...", end="", flush=True) - t0 = time.time() - sh = fn() - dt = time.time() - t0 - vol = AB.vol_of(sh) / 1000.0 - n_solids = len(solids_of(sh)) - print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True) - add(name, cn, sh, color, note=note) - - print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True) - t0 = time.time() - for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS): - j = JS[tag] - add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af, - printed=False, note="复用 电机.STEP") - print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True) - print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True) - return JS - - -CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"] -SEP = 74.0 - - -def explode_chain(p, JS, cents, gmean): - """爆炸图偏移:立柱垂直阶梯拉开,底座前后防呆拉开,电机沿轴拉出,机械臂整体随上段分离""" - nm = p["name"] - if nm == "P01_A_立柱下段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 0.0] - if nm == "P01_B_立柱中段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 60.0] - if nm == "P01_C_立柱上段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 120.0] - if nm == "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": - return [0.0, 0.0, 0.0] - if nm == "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": - return [65.0, 0.0, 0.0] - if nm == "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": - return [-65.0, 0.0, 0.0] - if nm == "P03_肩吊臂_螺旋体35%_墙5底5_竖打": - return [0.0, 0.0, 120.0] - - grp = AB.GRP.get(p["name"], "root") - if grp == "root": - return [0.0, 0.0, 0.0] - k = CHAIN.index(grp) - off = [0.0, 0.0, 120.0 - SEP * k] - j = JS.get(grp) - if j: - w = 0.0 - if "半联轴器" in nm or "抱箍" in nm: - w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0) - elif nm.startswith("M"): - w = 62.0 - for q in range(3): - off[q] += j.d[q] * w - return off - - -if __name__ == "__main__": - JS = build() - - print("=" * 80, flush=True) - print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) - print("=" * 80, flush=True) - bad = 0 - printed_list = [p for p in PARTS if p["printed"]] - total_pr = len(printed_list) - for idx, p in enumerate(printed_list, 1): - n = len(solids_of(p["shape"])) - if n != 1: - bad += 1 - status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌" - print(f" [{idx}/{total_pr}] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) - if bad == 0: - print(f"-> 单实体检查全部通过!{total_pr}/{total_pr} 构件均为完美单一流形拓扑 ✓\n", flush=True) - else: - print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) - - AB.explode_vec = explode_chain - bom = AB.export(JS) - - # 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例, - # 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD) - try: - import screw_mesh - screw_mesh.main() - except Exception as e: - print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True) - - cl = AB.collide() - - try: - from build_viewer import rebuild_viewer - rebuild_viewer() - except Exception as e: - print("rebuild_viewer error:", e, flush=True) - - # 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉), - # 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算) - try: - sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), - "..", "scratch")) - import ws_sweep - import gen_env_mesh - t_env = time.time() - ws_sweep.main() - gen_env_mesh.main() - from build_viewer import rebuild_viewer as _rbv - _rbv() - print(f"-> 可达包络已自动注入查看器(+{time.time()-t_env:.1f}s)✓\n", flush=True) - except Exception as e: - print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):", - e, flush=True) - - print("=" * 80, flush=True) - print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) - print("=" * 80, flush=True) - sys.stdout.flush() - os._exit(0) diff --git a/.bak/arm_model.py.bak20261007 b/.bak/arm_model.py.bak20261007 deleted file mode 100644 index 5074cba..0000000 --- a/.bak/arm_model.py.bak20261007 +++ /dev/null @@ -1,2049 +0,0 @@ -# -*- coding: utf-8 -*- -"""机器人手臂深度圆角优化版 V2:彻底消除所有棱角 -针对用户反馈的三大问题区域进行深度优化: -1. 蓝色零件(P04肩叉)顶部方形突起 → 全圆角处理 -2. 绿色/青色零件(P05/P08)底部和侧面棱角 → Loft外轮廓圆角 -3. 所有连接处的尖锐过渡 → 大圆角平滑过渡 -4. 橙色零件(P06/P07)所有方形边缘 → 完整圆角系统 -""" -import math -import os -import sys -import time - -sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) -import cadquery as cq -import arm_build as AB -from armcore import (M, C, cyl, box, ring, place, trs, bb, bbox_str, solids_of, OUT, WEB, DOCS) -from arm_build import (Joint, add, bx, Ycyl, Ybox, Yplate, prism, dogbone, sector, - FLANGE_Z, COVER_Z, HUB_FACE_Z, CLAMP_SPAN, HUB_M2_CLOCK, - BORE_R, CB_R, PCB_W, D2R, T, R, PARTS) -import human_layout as HL -from OCP.BRepAlgoAPI import BRepAlgoAPI_Common -from human_layout import (HUB, PLATE_T, R_MOT, Z_TIP, Z_WR, Z_EI, Z_SH, Z_RL, Z_R1, - Z_R3, Z_HUB3, Z_EL, Z_R6, Z_HUB6, - Z_R4, Z_CAP, Z_M6, Z_CHEST0, Z_CHEST1, X_J1_HUB, X_ARM, L1, - X_FORK_GAP, X_BAY_EXT, - Y_AXIS, Y_HUB, Y_REAR, Y_COVER, Y_H, Y_R_IN, Y_R_OUT, - Y_M6, Y_CHEST, X_CHEST, FOOT_H) - -VERSION = os.environ.get("ARMVER", "v2") -ITER = int(os.environ.get("ARMITER", "3")) -AB.VERSION = VERSION -AB.ITER = ITER - -Y_MID = Y_AXIS -BOSS = COVER_Z + 0.2 -DISC_R = 40.0 -SLEEVE_R = 35.0 - -# 深度圆角优化参数 -CLEARANCE_GAP = 2.0 # 关节间隙 2.0mm -FILLET_TINY = 1.5 # 微小圆角(螺孔边缘) -FILLET_SMALL = 3.0 # 小圆角(功能面) -FILLET_MEDIUM = 6.0 # 中圆角(结构过渡) -FILLET_LARGE = 10.0 # 大圆角(主要轮廓) -FILLET_XLARGE = 15.0 # 超大圆角(视觉主导区域) -CHAMFER_EDGE = 2.0 # 边缘倒角 - - -def Xcyl(r, x0, x1, py, pz): - return place(place(cyl(r, 0.0, x1 - x0, 0.0, 0.0), R(90, 0, 1, 0)), T(x0, py, pz)) - - -def Xplate(pts_yz, x0, x1): - prof = [(-p[1], p[0]) for p in pts_yz] - return place(place(prism(prof, 0.0, x1 - x0), R(90, 0, 1, 0)), T(x0, 0.0, 0.0)) - - -def XB(a0, a1, y0, y1, z0, z1): - return bx(X_ARM + a0, X_ARM + a1, y0, y1, z0, z1) - - -# ------------------------------------------------------------------ 减重(镂空)-------- -# 三个手法件(P04/P06/P07)在关节根部都留着一大块"实心过渡料":壁厚最深到 15~17.5mm, -# 而 gyroid 填充只有 25~35%,等于用整块实心去抗一个安全系数 19~122 的截面。 -# 下面两个 helper 把这块料掏掉,规则与 P03 的"帐篷形减重窗"一致: -# 1) 不留封闭空腔 —— 减重腔要么朝上敞开,要么是有进出口的窗,打印不憋气、不积料; -# 2) 斜面自支撑 —— 斜面与水平面夹角 >= 52°(与竖直面夹角 <= 38°),免支撑; -# 3) 内角 R4 圆角 —— 挖出来的洞会把圆截面变成尖角,Kt 从 ~1.2 抬到 ~2.5, -# 圆角把应力集中压回去; -# 4) 只吃"距表面 > 8mm"的厚料,四周至少留 8mm 壁 —— 受力纤维一根不碰。 - -HOLLOW_WALL = 8.0 # 减重腔与原外表面的最小间距(= 留下的最小壁厚) -HOLLOW_FILLET = 4.0 # 减重腔内角圆角,与 P03 竖板减重窗一致 - -# 三处减重腔的开度。这三个件都是绕 Y 轴弯,弯矩在 X 方向拉压,距中性面越远的料 -# 贡献越大,所以"少挖、挖窄"是恢复强度最省料的做法。 -# 数值由 scratch/tune_hollow.py 按"槽区最差安全系数 >= 无槽基线的 95%"扫出来: -# P04 档1 X23..35 Y102..154 Z>=525 -> 95% 基线,保留 69% 的减重量 -# P07 档1 X−3..43 Y103..153 Z>=255 -> 96% 基线,保留 76% -# P06 档1 抬高窗底到 Z362(不是收窄) -> 100% 基线,保留 56% -# 窗底原来压在 Z358,而该处截面安全系数最低;抬高 4mm 比横向收窄 8mm 划算得多。 -P04_CH = dict(x0=23.0, x1=35.0, y0=102.0, y1=154.0, z_floor=525.0) # 肩叉立柱 -P07_CH = dict(x0=-3.0, x1=43.0, y0=103.0, y1=153.0, z_floor=255.0) # 小臂顶板 -P06_WIN_XZ = [(3.0, 362.0), (37.0, 362.0), (37.0, 371.0), - (28.0, 374.0), (12.0, 374.0), (3.0, 371.0)] # 大臂过渡块 -P06_HOLLOW_WIN = False # 2026-10-03 起关闭:拱形窗正面看是贯通黑洞,不雅观(详见 p_upper_bone 5c) - - -def hollow_channel(x0, x1, y0, y1, z_floor, z_open, tag="", fil=HOLLOW_FILLET): - """朝上敞开的减重槽:从 z_open 往下切到 z_floor,槽底水平(朝上受支撑)、侧壁竖直。 - - 竖打(模型 Z = 打印 Z)下这个形状是零支撑的:槽底是水平面、槽壁全是竖直面, - 顶部完全敞开因而不构成封闭空腔。切完该件变成 U 形槽钢而不是实心料: - 抗弯靠两侧 X 向腹板(正好落在弯矩拉压区),中间中性层掏空几乎不损失 I。 - - x0/x1/y0/y1 用世界坐标;z_open 取一个保证贯通顶面的值。 - """ - tool = bx(x0, x1, y0, y1, z_floor, z_open) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Z").fillet(fil).val() - except Exception as e: - print(" [减重槽] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重槽] %s: X%.1f..%.1f Y%.1f..%.1f 自 Z%.1f 向上敞开 (四周留壁 >=%.0fmm)" - % (tag, x0, x1, y0, y1, z_floor, HOLLOW_WALL), flush=True) - return tool - - -def hollow_tent_window(pts_xz, y0, y1, tag="", fil=HOLLOW_FILLET): - """帐篷形减重窗:XZ 平面画轮廓,沿 Y 双向切穿。 - - 轮廓下宽上窄(拱形)——窗顶上方的料形成一个尖顶,斜面与水平夹角 >= 52° 自支撑, - 只有顶部一小段平顶需要桥接(<=20mm,与 P03 同)。轮廓在 X 方向不能吃满整个截面, - 两侧必须留墙,否则该高度上零件会被切断、失去单实体。 - """ - # XZ 工作平面的法线是 -Y,extrude 会朝 -Y 走,所以起点放在 y1 才能覆盖 y0..y1 - tool = (cq.Workplane("XZ", origin=(0, y1, 0)).polyline(pts_xz).close() - .extrude(y1 - y0).val()) - if fil > 0: - try: - tool = cq.Workplane(obj=tool).edges("|Y").fillet(fil).val() - except Exception as e: - print(" [减重窗] %s 内角圆角跳过: %s" % (tag, e), flush=True) - print(" [减重窗] %s: XZ 轮廓 %d 点, 沿 Y %.1f..%.1f 切穿 (帐篷形, 斜面自支撑)" - % (tag, len(pts_xz), y0, y1), flush=True) - return cq.Shape(tool.wrapped) - - -# 各关节"电机前端夹紧"统一配方(J2/J3/J4/J5 通用,J1 由 P03 自己那套 S04 凸台实现)。 -# 电机前端盖外端面:Y 轴关节(J2/J4/J5)在 Y_COVER=156,压盖朝 +Y 生长; -# Z 轴关节(J3,roll 电机,轴向 -Z)在 Z_R-56,压盖朝 -Z 生长(-Z 侧是从动件)。 -# 压盖背面与之 0.0 间隙面压,螺钉只提供压紧力。 -# 沉孔相位按【关节局部相位】定义(BCD55,72.2°+90k):换算到全局后 Y 轴关节是 -# 17.8°+90k(与 P03→M1 的 S04 一致),Z 轴关节是同一组数落在全局 (x,y) 平面, -# 两种轴向的物理指向完全一致 —— 全机同一套孔位约定,改一处必须同步另一处。 -CLAMP_BCD = 27.5 -CLAMP_CLOCK = (17.8, 107.8, 197.8, 287.8) # Y 轴关节的全局 (x,z) 相位 -CLAMP_CLOCK_LOCAL = tuple((360.0 - a) % 360.0 for a in CLAMP_CLOCK) # = 72.2+90k -CLAMP_ROOT = 0.5 # 根部薄环厚度:与前端定位环前端面共面,保证熔接成一体 -CLAMP_T = 5.0 # 压盖本体厚度 -CLAMP_CLR = 1.0 # 从动件让位间隙:与 Y 轴关节 hub_gap=6.0 同一个道理 -CLAMP_BACKOFF = CLAMP_T + CLAMP_CLR - CLAMP_ROOT # =5.5 从动件毂面整体后移量 -# 【2026-10-05e】"电机前端围挡"统一整改开关,作用于全机 5 处电机前端(J2/J3/J4/J5/J6)。 -# 围挡本体 = 各零件前端的 **前端定位环**(`front_collar_pocket` 把座孔从 R30.15 扩到 -# R30.25 时留下的那圈 r30.25..35、6mm 高的环墙)。它比电机前端盖 Ø60.3 凸出 4.75mm, -# 把电机前端盖整圈围住;J2/J3/J4/J5 上还骑着一枚 R35 前端压盖(Ø70 再凸 3.2mm)。 -# 用户 2026-10-05 先在 J3/M3 尾部指出,2026-10-05d 只切敞口那一圈解决;本次把同一刀 -# 推广到其余四处(J2/M2、J4/M4、J5/M5、J6/M6),几何完全同构。 -# 为什么不整圈削到与电机齐平(R35 -> R31.8): -# 1. 那圈壁与本体在两个方位与外壁**相切**,整圈切完那两片切向楔形会脱空成孤立碎块 -# (实测 1.32mm3),破坏单实体拓扑; -# 2. 压盖根部只有 0.5mm 熔接带,环削窄后连接面从 2.37mm2 掉到 0.78mm2, -# 4xM3x8 的夹紧力会把压盖从本体上拉下来。 -# 所以只切**敞口那一半**:背脊侧定位环继续定位电机前端盖、并作为压盖唯一且足够强的 -# 支撑/熔接面;压盖本体、Ø37 让位孔、4xM3x8 沉孔、腔口下沿全部不动。 -# 切口位置见 cut_motor_fence()。设 False 取消(恢复原来的整圈围挡)。 -FENCE_OPEN_CUT = True - - -def cut_motor_fence(shape, j, tag, name3, z0=49.4, z1=56.1): - """【2026-10-05e】只切掉电机前端围挡(前端定位环)在**敞口那一半**的环墙。 - - 与 2026-10-05d 给 P05/M3 那一刀完全同源 —— 当时那记全局盒切 - (X_ARM±40, Y_AXIS-40..Y_AXIS+3, Z388.9..395.6) 换算到 J3 局部系正好是 - x±40 / y -3..+40 / z 49.4..56.1。本函数把它从"P05 专用全局盒"泛化成 - "按关节局部坐标切半个环",J2/J4/J5/J6 于是同一刀通用。 - - 局部坐标约定(见 trs/place):z 沿电机轴、指向电机前端盖(外端面在 z=COVER_Z=56), - x/y 在垂直于轴的平面内。**局部 +y 一律是半包敞口侧** —— 实测 5 处电机舱的 - 本体(环外 r=37/40 有料处 = 背脊侧)方位角全部落在局部角 180..360 = 局部 -y 侧: - J2/M2 背脊 240..315 J3/M3 背脊 225..315 J4/M4 背脊 210..330 - J5/M5 背脊 210..330 J6/M6 背脊 195..345 - 故敞口方向无需逐件配置,统一取局部 +y。 - - 为什么一对 z 参数能覆盖全机:5 处电机的前端定位环都恰好落在**局部 z 49.5..56** - (Y 轴电机前端环 Y150..156 -> z50..56;roll 电机 Z395.5..389 -> z49.5..56)。 - - 被切掉的只有 r30.25..35 那一圈环墙在敞口侧的部分;压盖本体(4xM3x8 沉孔段)、 - Ø37 让位孔、腔口下沿、背脊侧整圈定位环全部不动。 - 切口边界取在局部 y=-3.0 而不是 0:环壁在局部 y=-3 处仍与背脊侧实体连通, - 切出来的是真正的缺口而不是零厚度缝,单实体拓扑天然保持 - (P05 实测切完 solids=1;2026-10-05c 整圈削 R31.8 那版会掉出 1.32mm3 碎块)。 - """ - if not FENCE_OPEN_CUT: - return shape - shape = shape.cut(j.L(bx(-40.0, 40.0, -3.0, 40.0, z0, z1))) - print(" [围挡] %s: 只切敞口侧那一圈前端定位环(局部 y -3..+40, z %.1f..%.1f);" - "背脊侧整圈保留定位前端盖并支撑压盖,压盖本体/沉孔/腔口下沿不变" - % (name3, z0, z1), flush=True) - return shape - - -def motor_front_clamp(px, pz, plate_t=CLAMP_T, tag="", axis="Y", py=0.0, z_face=None): - """电机前端压盖:把"只被后端安装板单面锁死"改成前后双向面压夹紧。 - - 原设计里每个关节的电机都只有后端板 4xM3x10(S02)锁在尾端面上,前端盖完全裸露, - 大扭矩下电机绕自身轴在 Ø60.3 座孔里点头/翘起。这里在电机前端盖外端面加一枚 - 与后端板同径的 R35 压盖,用 4xM3x8(BCD55)在前端盖上再夹一次 —— 电机被前后两块 - 同心 Ø60.3 端面双向夹死,倾斜自由度归零;夹紧力全部走面压,螺钉不参与定位。 - - 两个轴向共用同一套尺寸,只换刀具体系(不重复实现任何半径/相位): - axis="Y"(J2/J4/J5):关节轴过 (px, pz),前端盖外端面在 Y_COVER,压盖朝 +Y 长; - axis="Z"(J3 roll,轴向 -Z):关节轴过 (px, py),前端盖外端面在 z_face,压盖朝 -Z 长。 - - 局部坐标(s 从前端盖外端面起算,s>0 一律"背离电机",两个轴向同号): - - 压盖 R35,s 从 -CLAMP_ROOT(与前端定位环前端面共面,保证熔接成一体)到 plate_t; - - 背面精确落在 s=0,不让根部那 0.5mm 啃到端盖 R30.15 的外圆; - - Ø37 中央让位孔(R18.5)让开从动件的直锁凸台 R17.8 与 3xS01 输出螺钉头; - - 4xM3x8:Ø3.4 通孔 + Ø6.0 沉孔深 3.0(自前端面沉到 s=2),圆柱头全沉入, - 头面压孔口台阶,中间 2.0mm 压盖本体受压。 - """ - if axis == "Y": - def C(r, s0, s1, du=0.0, dv=0.0): - return Ycyl(r, Y_COVER + s0, Y_COVER + s1, px + du, pz + dv) - elif axis == "Z": - def C(r, s0, s1, du=0.0, dv=0.0): - return cyl(r, z_face - s1, z_face - s0, px + du, py + dv) - else: - raise ValueError("motor_front_clamp: axis 只能是 Y 或 Z,收到 %r" % (axis,)) - - s0, s1 = -CLAMP_ROOT, plate_t - fp = C(35.0, s0, s1) - fp = fp.cut(C(18.5, s0 - 0.5, s1 + 0.5)) # Ø37 中央让位孔 - # 【2026-10-03h 接缝整改】根部让位槽原来切 R30.15,比腔壁 BAY_R=30.50 小 0.35, - # 于是压盖根部那圈 r30.15..35 的定位唇内壁比腔壁**内凸 0.35**,从腔里看就是 - # 腔口一圈 0.5mm 宽、0.35mm 深的环槽(P04/P05/P06/P07 各有一圈,实测台阶面 - # 31.2mm2 + R30.15 环带 44.4mm2,四件完全相同)。这刀是腔还是 R30.15/R30.25 - # 那个年代留下的:当时必须让到电机实测外圆,压盖根部才啃不到电机。 - # 现在主腔已统一到 BAY_R,电机实测 R30.15,让位槽取 BAY_R 即可: - # 定位唇内壁与腔壁同一根正圆,两张圆柱面首尾相接,中间不再有任何台阶, - # 对电机的单边间隙仍是 0.35(唇内径 Ø61.0 > 电机 Ø60.3)。 - fp = fp.cut(C(AB.BAY_R, s0 - 0.5, 0.0)) # Ø61.0 根部让位槽(= 腔径) - for deg in CLAMP_CLOCK_LOCAL: - rad = math.radians(-deg) # 全局相位 = -局部相位 - fu, fv = CLAMP_BCD * math.cos(rad), CLAMP_BCD * math.sin(rad) - fp = fp.cut(C(3.0, 2.0, s1 + 0.5, fu, fv)) # Ø6.0 沉孔 - fp = fp.cut(C(1.7, s0 - 0.5, 2.0, fu, fv)) # Ø3.4 通孔 - where = ("Y%.1f..%.1f" % (Y_COVER - CLAMP_ROOT, Y_COVER + plate_t)) if axis == "Y" \ - else ("Z%.1f..%.1f" % (z_face - plate_t, z_face + CLAMP_ROOT)) - print(" [前端压盖] %s: R35 %s, 4xM3x8 BCD55@%s (%s 轴)" - % (tag, where, "/".join("%.1f" % d for d in CLAMP_CLOCK), axis), flush=True) - return fp - - -def hub_m2_holes(shape, j, cb_z1, th_z1=58.0, th_z0=54.0, cb_z0=58.0, - th_r=1.4, cb_r=2.8): - """在从动毂上开 4x M2 直锁螺钉孔:Ø2.8 通孔 + Ø5.6 沉孔,BCD 见 AB.M["m2_bcd"]。 - - 全机 5 处(J1 叉板 / J2+J3 直连毂 / J4+J5 直连毂)原本各抄一份同样的循环, - 只有沉孔顶 z 与销孔顶 z 两处随件而异 —— 抄一次错一处就是一处真 bug。 - 现在统一走这里,各调用点只传自己那两个 z。 - - cb_z0 默认 58.0 = th_z1,台阶因此落在通孔顶端;P03 叉板那一处例外 - (沉孔台阶固定在 63.0 不随凸台前移,见 p_fork 的注释),故两个 z 都可传。 - """ - for a in AB.HUB_M2_CLOCK: - u = AB.M["m2_bcd"] / 2.0 * math.cos(a * AB.D2R) - v = AB.M["m2_bcd"] / 2.0 * math.sin(a * AB.D2R) - shape = shape.cut(j.L(cyl(th_r, th_z0, th_z1, u, v))) - shape = shape.cut(j.L(cyl(cb_r, cb_z0, cb_z1, u, v))) - return shape - - -def make_direct_hub(shape, j, r_disc=35.0, z_out=74.0, backoff=0.0): - """优化直连法兰:增加导向倒角 - - backoff = 毂面沿电机轴**远离电机**的让位量(2026-10-03 新增,目前只有 P09 用)。 - - 场景:J6/M6 补上前端压盖后(见 p_wrist_bracket 的 roll_clamp=True),压盖占 - 局部 z 55.5..61.0(= 全局 Z 120.5..115),而毂面原来在局部 56.5,两者直接打架。 - 按 P06 给 J3 让位的同一套办法,把毂面整体后移 CLAMP_BACKOFF=5.5: - 毂面 56.5 -> 62.0,压盖前端面在 61.0,中间正好留 CLAMP_CLR=1.0。 - - 只动"外端"坐标,不动"电机端"坐标,否则咬合量就变了: - 直锁凸台起点 54.942 不动、长度 54.942..56.5 -> ..62.0 加长 5.5, - 进入电机输出毂的深度与改前完全一致(与 p_humerus_bone 的 J3 处理同源); - 导向锥 54.5..54.942 不动; - 3xS01 输出螺钉让位通道与 4xM2 直锁沉孔的**内**端(54.0 / 58.0)不动, - 只把外端(z_out+30 / z_out+40)跟着放长。 - """ - disc = cyl(r_disc, 56.5 + backoff, z_out + backoff) - boss = cyl(17.8, 54.942, 56.5 + backoff) - chamfer_cone = cyl(18.5, 54.5, 54.942) - shape = shape.fuse(j.L(disc)).fuse(j.L(boss)).cut(j.L(chamfer_cone)) - shape = shape.cut(j.L(cyl(3.5, 54.0, z_out + 30.0 + backoff))) - return hub_m2_holes(shape, j, cb_z1=z_out + 40.0 + backoff) - - -# ========== 躯干部分 ========== -# ========== 躯干/测试台架部分 (后置防倾翻高刚性机架) ========== -X_COL = -115.0 -Y_COL = Y_AXIS # 128.0 -Z_LOCK = 463.0 # 横向防脱锁紧螺钉高度(立柱后壁 → 榫头,M4x16 自攻) - - -def p_chest(): - """P01_胸腔支撑立柱 (High-Rigidity Box Mast Tower - 后置台架立柱): - 1. 66x66mm 封闭中空厚壁箱型柱(壁厚 11mm,内腔 44x44),中心在 (-115.0, 128.0),位于手臂正后方; - 2. 顶部接收 P03 43.4x43.4x15mm 定位榫头(44x44 R4 内腔)、4x 竖向安装孔 + 1x 横向防脱锁紧孔 - (全部 Ø3.9 直孔:M4x16 从外部直接拧入自攻成型,无过孔/无沉孔/无预埋); - 3. 底部 50x50x12mm 阶梯榫头插入 P02 榫座:环形台肩(66->50)面承载轴向力,槽壁全周承载剪切; - 4. 前向开设两组大圆角走线工艺窗口(Z=60~150mm 与 Z=330~420mm); - 5. 外棱角全部采用 R=6mm 圆角。 - """ - z_bot = 22.0 - z_top = Z_CHEST1 # 470.0 - - col = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot)) - .rect(66.0, 66.0) - .extrude(z_top - z_bot) - .edges("|Z").fillet(6.0)).val() - s = cq.Shape(col.wrapped) - - cav = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_bot + 10.0)) - .rect(44.0, 44.0) - .extrude(z_top - z_bot + 1.0) - .edges("|Z").fillet(4.0)).val() - s = s.cut(cav) - - # 底部阶梯榫头:插入 P02 榫座后环形台肩坐实,轴向力走面压而非 M5 螺纹 - tenon = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(50.0, 50.0).extrude(z_bot - 10.0) - .edges("|Z").fillet(4.0)).val() - s = s.fuse(tenon) - - # 底部 M5 夹紧孔(贯通榫头):X 向非对称 (−20/+16) 与榫座孔位配对防反装/防转 90° - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - s = s.cut(cyl(2.75, 9.99, z_bot + 15.0, xx, yy)) - # 顶部 4x 安装孔:统一 Ø3.9 直孔(M4x16 直接拧入自攻成型;只打这一个孔,无过孔/无沉孔),深 20mm - # 位于 66x66 顶环 11mm 实体壁内(内腔 22 → 外壁 33),避开 44x44 内腔与 R4 内腔圆角; - for xx in (X_COL - 27.5, X_COL + 27.5): - for yy in (Y_COL - 13.5, Y_COL + 13.5): - s = s.cut(cyl(1.95, z_top - 20.0, z_top + 0.01, xx, yy)) - # 横向防脱锁紧孔(后壁 → 榫头):Ø3.9 贯穿 11mm 后壁 + 榫头同径(M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - 34.0, X_COL - 20.0, Y_COL, Z_LOCK)) - - w_bot = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 105.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_bot) - - w_top = (cq.Workplane("YZ", origin=(X_COL + 33.5, Y_COL, 375.0)) - .rect(34.0, 70.0) - .extrude(-20.0)).val() - s = s.cut(w_top) - return s - - -def split_p01_poka_yoke(p01): - """拆分 P01 为 A/B/C 三段(严格适配 200x200mm 打印机,竖直免支撑打印): - 带 16mm 阶梯互锁榫卯(外套筒壁 3.6 + 公榫壁 6.9,内腔收窄 51->44 换取双厚壁, - 单边配合间隙 0.5)、以及统一 Ø3.9 直孔的分段对锁孔(M4x16 从外面直接拧入自攻成型, - 母口壁与公榫壁同径,无过孔/沉孔/预埋,前后两面对称各一颗); - 孔位相对立柱中心 −16 / +22 非对称(物理防反装防呆);本体 3D 凹刻朝向标识。 - """ - z_bot = 22.0 - z_s1 = 172.0 - z_s2 = 321.0 - z_top = 470.0 - h_lap = 16.0 - - # 非对称对锁孔位(相对立柱中心偏心 -16mm 与 +22mm,偏心差 6mm,反装无法穿入螺栓) - xx_bolts = (X_COL - 16.0, X_COL + 22.0) - - # 分段对锁孔:统一 Ø3.9 直孔(M4x16 从外面直接拧入自攻成型;母口壁与公榫壁同径, - # 没有过孔/沉孔等其它孔),前后两面对称;杆尖伸过榫壁的一段在内腔侧留 Ø4.5 让位。 - def _seam_holes(part, xx, z): - return (part.cut(Ycyl(1.95, Y_COL - 33.6, Y_COL - 21.4, xx, z)) - .cut(Ycyl(1.95, Y_COL + 21.4, Y_COL + 33.6, xx, z)) - .cut(Ycyl(2.25, Y_COL - 21.7, Y_COL - 15.5, xx, z)) - .cut(Ycyl(2.25, Y_COL + 15.5, Y_COL + 21.7, xx, z))) - - # --- P01_A (下段) --- - 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(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() - tongue1 = cq.Shape(tongue1_outer.wrapped).cut(tongue1_cav) - p01_a = p01_a_base.fuse(tongue1) - - z_bolt1 = z_s1 + h_lap / 2.0 # 180.0 - for xx in xx_bolts: - p01_a = _seam_holes(p01_a, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - - # Engrave the front (-Y) flat face, reading VERTICALLY bottom-to-top along - # the column (user request): local +X = world +Z, local +Y = world -X, so - # the trailing "▶" glyph renders pointing world-up (+Z). - front_label_plane_a = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 120.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_a = (cq.Workplane(front_label_plane_a) - .text("P01-A TOP ▶", 9.0, -0.8)).val() - p01_a = p01_a.cut(cq.Shape(label_a.wrapped)) - - # --- P01_B (中段) --- - cut_below_s1 = bx(X_COL - 50.0, X_COL + 50.0, Y_COL - 50.0, Y_COL + 50.0, z_bot - 14.0, z_s1) - 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.9)).val() - p01_b_base = p01_b_base.cut(pocket1) - - tongue2_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z_s2)) - .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() - tongue2 = cq.Shape(tongue2_outer.wrapped).cut(tongue2_cav) - p01_b = p01_b_base.fuse(tongue2) - - z_bolt2 = z_s2 + h_lap / 2.0 # 329.0 - for xx in xx_bolts: - p01_b = _seam_holes(p01_b, xx, z_bolt1) # A-B 缝对锁孔(Ø3.9,前后两面) - p01_b = _seam_holes(p01_b, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_b = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 240.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_b = (cq.Workplane(front_label_plane_b) - .text("P01-B TOP ▶", 9.0, -0.8)).val() - p01_b = p01_b.cut(cq.Shape(label_b.wrapped)) - - # --- P01_C (上段) --- - 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.9)).val() - p01_c = p01_c_base.cut(pocket2) - for xx in xx_bolts: - p01_c = _seam_holes(p01_c, xx, z_bolt2) # B-C 缝对锁孔(Ø3.9,前后两面) - - front_label_plane_c = cq.Plane( - origin=(X_COL, Y_COL - 33.0, 390.0), - xDir=(0, 0, 1), normal=(0, -1, 0), - ) - label_c = (cq.Workplane(front_label_plane_c) - .text("P01-C TOP ▶", 9.0, -0.8)).val() - p01_c = p01_c.cut(cq.Shape(label_c.wrapped)) - - return p01_a, p01_b, p01_c - - -def make_p02_poka_yoke(): - """将 P02 拆分为三件模块化组件(均严格 <= 195x190mm,适配 200x200mm 打印床): - 1. P02_Center (底座中核心): 195x190x70mm,完整容纳立柱套筒与前后三角斜撑筋,核心应力区无接缝; - 2. P02_Front (前向力臂): 145x190x22mm,宽跨距 104mm 双榫头 + 预埋 M5 螺母槽; - 3. P02_Rear (后向力臂): 45x190x22mm,窄跨距 80mm 双榫头(跨距差达 24mm 物理防呆,绝不混装)。 - """ - z0 = 0.0 - z1 = 22.0 - y_width = 190.0 - - f_tenon_w = 44.0 - f_tenon_y_span = 52.0 - r_tenon_w = 32.0 - r_tenon_y_span = 40.0 - - # 1. P02_Center (X = -195.0 .. 0.0, 195x190mm) - x_c_min = -195.0 - x_c_max = 0.0 - x_c_len = x_c_max - x_c_min - x_c_mid = (x_c_min + x_c_max) / 2.0 - - c_plate = (cq.Workplane("XY", origin=(x_c_mid, Y_COL, z0)) - .rect(x_c_len, y_width).extrude(z1) - .edges("|Z and X").fillet(8.0) - .edges(">Z").fillet(2.0)).val() - p02_c = cq.Shape(c_plate.wrapped) - - # Socket collar (Z: 22..70mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0).extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0).edges(">Z").chamfer(2.0)).val() - p02_c = p02_c.fuse(cq.Shape(collar_outer.wrapped)) - - # Front & Rear triangular gusset ribs - f_pts = [(-75.0, z1), (-15.0, z1), (-15.0, z1 + 4.0), (-75.0, 68.0)] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(f_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(f_rib.wrapped)) - - r_pts = [(-155.0, z1), (-195.0, z1), (-195.0, z1 + 14.0), (-155.0, 70.0)] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(r_pts).close().extrude(19.0, both=True)).val() - p02_c = p02_c.fuse(cq.Shape(r_rib.wrapped)) - - # Front wire guide - wg_pts = [(-75.5, z1 + 1.0), (-14.0, z1 + 1.0), (-14.0, z1 + 4.0), (-75.5, 68.5)] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)).polyline(wg_pts).close().extrude(9.0, both=True)).val() - p02_c = p02_c.cut(wire_guide) - - # Column socket — stepped mortise: upper 66.6 bore for the 66 box walls, - # lower 50.6 recess (floor 0.6 low) for the 50x50 bottom tenon; the recess - # rim at z=22 is the axial seat, so column weight bears on face, not threads. - socket_upper = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(66.6, 66.6).extrude(z_collar_top - z1 + 1.0) - .edges("|Z").fillet(6.3)).val() - p02_c = p02_c.cut(socket_upper) - socket_lower = (cq.Workplane("XY", origin=(X_COL, Y_COL, 9.4)) - .rect(50.6, 50.6).extrude(z1 - 9.4) - .edges("|Z").fillet(4.3)).val() - p02_c = p02_c.cut(socket_lower) - - # Vertical M5 clamp bolts (offsets match the asymmetric column tenon holes: - # X −20/+16 blocks 180° flip, the ±18 Y span blocks 90° rotation). - # Threads engage the tenon 15mm — clamp only, axial load rides on the seat. - for xx in (X_COL - 20.0, X_COL + 16.0): - for yy in (Y_COL - 18.0, Y_COL + 18.0): - p02_c = p02_c.cut(cyl(2.75, -0.01, 10.5, xx, yy)).cut(cyl(5.0, -0.01, 6.0, xx, yy)) - - # Center clamp slots - for cy in (Y_COL - 75.0, Y_COL + 75.0): - plane = cq.Plane(origin=(x_c_mid, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_c = p02_c.cut(slot) - - p02_c = p02_c.cut((cq.Workplane("XY", origin=(X_COL, Y_COL - 70.0, -0.01)).rect(28.0, 80.0).extrude(6.0)).val()) - - # Front mortise pockets - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - front_mortise = (cq.Workplane("XY", origin=(-10.0, yy, 4.8)) - .rect(21.0, f_tenon_w + 0.8).extrude(12.6).edges("|Z and X").fillet(3.0)).val() - p02_c = p02_c.cut(rear_mortise) - p02_c = p02_c.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - p02_c = p02_c.cut(cyl(5.0, z1 - 5.0, z1 + 1.0, -185.0, yy)) - - # Debossed text on P02_Center top face. The mid-plate line (y=Y_COL) is - # hidden behind the column collar and the gusset ribs, so both labels sit on - # the flat -Y side strip (y = Y_COL-68) where they stay in full view from the - # label side: REAR on the rear half, FRONT on the front half, clear of the - # clamp slot (x -101..-94) and the M5 screw counterbores. - txt_c_f = (cq.Workplane("XY", origin=(-49.0, Y_COL - 68.0, z1)).text("▲ FRONT (前向) ▲", 9.0, -0.8)).val() - txt_c_r = (cq.Workplane("XY", origin=(-146.0, Y_COL - 68.0, z1)).text("▲ REAR (后向) ▲", 9.0, -0.8)).val() - p02_c = p02_c.cut(cq.Shape(txt_c_f.wrapped)).cut(cq.Shape(txt_c_r.wrapped)) - - # 2. P02_Front (X = 0.0 .. 145.0, 145x190mm) - x_f_len = 145.0 - x_f_mid = 72.5 - f_plate = (cq.Workplane("XY", origin=(x_f_mid, Y_COL, z0)) - .rect(x_f_len, y_width).extrude(z1) - .edges("|Z and >X").fillet(16.0).edges(">Z").fillet(2.0)).val() - p02_f = cq.Shape(f_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(120.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_f = p02_f.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * f_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-9.5, yy, 5.25)) - .rect(20.0, f_tenon_w - 0.2).extrude(11.5).edges("|Z and Z").fillet(2.0)).val() - p02_r = cq.Shape(r_plate.wrapped) - - for cy in (Y_COL - 70.0, Y_COL + 70.0): - plane = cq.Plane(origin=(-215.0, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - p02_r = p02_r.cut(slot) - - for sign in (-1, 1): - yy = Y_COL + sign * r_tenon_y_span - tongue = (cq.Workplane("XY", origin=(-185.5, yy, 5.25)) - .rect(20.0, r_tenon_w - 0.2).extrude(11.5).edges("|Z and >X").fillet(2.5).edges(">X").chamfer(1.5)).val() - p02_r = p02_r.fuse(cq.Shape(tongue.wrapped)) - p02_r = p02_r.cut(cyl(2.75, -0.01, z1 + 1.0, -185.0, yy)) - nut_pocket = (cq.Workplane("XY", origin=(-185.0, yy, -0.01)).polygon(6, 9.2).extrude(5.0)).val() - p02_r = p02_r.cut(nut_pocket) - - # Same +Y rotation as the front plate; "▼" points local -Y = world +X, - # i.e. toward the centre joint. Along +Y the ~73 mm text fits the 190 mm - # plate width instead of overhanging its 45 mm X span. - txt_r = (cq.Workplane(cq.Plane(origin=(x_r_mid, Y_COL, z1), xDir=(0, 1, 0), - normal=(0, 0, 1))) - .text("P02-REAR ▼ JOIN", 7.5, -0.8)).val() - p02_r = p02_r.cut(cq.Shape(txt_r.wrapped)) - - return p02_c, p02_f, p02_r - - -def p_base_foot(): - """P02_底脚 (重型防倾翻超长力臂底座 + 双向大截面三角加强斜撑筋): - 1. 底盘尺寸大幅扩展为 385x240x22mm (X: -240.0..+145.0, Y: 8.0..248.0); - - 前向延伸至 X = +145.0mm (在手臂垂直投影前方 91.5mm),彻底杜绝手臂前伸与抓握重物时的前倾翻; - - 后向延伸至 X = -240.0mm (在立柱后方 92.0mm),抵御大动态回程后仰力矩; - 2. 立柱插接套筒由 12mm 浅槽大幅升级为 48mm 高加固套筒 (Z: 22.0..70.0mm,外壁厚 7mm); - 3. 前向大三角加强斜撑筋 (Z: 68.0->22.0mm, X: -75.0->-15.0mm, 宽 38mm),抗弯力臂长达 60mm,留足安全间隙不干涉手臂运动; - 4. 后向大三角加强斜撑筋 (Z: 70.0->22.0mm, X: -155.0->-225.0mm, 宽 38mm),抗后仰力臂长达 70mm; - 5. 前斜撑表面设 18mm 宽平滑走线导槽,与 P01 下部走线窗口自然贯通; - 6. 6 组 M6 工业工作台紧固腰槽 (28x7mm) + 底部隐蔽走线槽; - 7. 四周 R=18mm 大圆角过渡与 2.5mm 上边缘倒角。 - """ - z0 = 0.0 - z1 = 22.0 - x_rear = -240.0 - x_front = 145.0 - x_len = x_front - x_rear # 385.0 - x_mid = (x_rear + x_front) / 2.0 # -47.5 - y_width = 240.0 - - # 1. 主底盘 (385 x 240 x 22 mm) - base_plate = (cq.Workplane("XY", origin=(x_mid, Y_COL, z0)) - .rect(x_len, y_width) - .extrude(z1) - .edges("|Z").fillet(18.0) - .edges(">Z").fillet(2.5)).val() - s = cq.Shape(base_plate.wrapped) - - # 2. 立柱外包加固套筒 (Z: 22.0..70.0 mm, 外廓 80x80mm) - z_collar_top = 70.0 - collar_outer = (cq.Workplane("XY", origin=(X_COL, Y_COL, z1)) - .rect(80.0, 80.0) - .extrude(z_collar_top - z1) - .edges("|Z").fillet(8.0) - .edges(">Z").chamfer(2.0)).val() - s = s.fuse(cq.Shape(collar_outer.wrapped)) - - # 3. 前向抗倾翻三角加强斜撑筋 (Front Triangular Gusset Rib) - f_pts = [ - (-75.0, z1), - (-15.0, z1), - (-15.0, z1 + 4.0), - (-75.0, 68.0), - ] - f_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(f_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(f_rib.wrapped)) - - # 4. 后向抗倾翻三角加强斜撑筋 (Rear Triangular Gusset Rib) - r_pts = [ - (-155.0, z1), - (-225.0, z1), - (-225.0, z1 + 4.0), - (-155.0, 70.0), - ] - r_rib = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(r_pts).close() - .extrude(19.0, both=True)).val() - s = s.fuse(cq.Shape(r_rib.wrapped)) - - # 5. 前斜撑表面走线导槽 (与 P01 前走线窗 Z=70..140mm 自然顺接) - wire_guide_pts = [ - (-75.5, z1 + 1.0), - (-14.0, z1 + 1.0), - (-14.0, z1 + 4.0), - (-75.5, 68.5), - ] - wire_guide = (cq.Workplane("XZ", origin=(0, Y_COL, 0)) - .polyline(wire_guide_pts).close() - .extrude(9.0, both=True)).val() - s = s.cut(wire_guide) - - # 6. 立柱深插槽内腔 (Pocket: 66.6 x 66.6 mm, 留 0.3mm 精密滑配间隙) - socket_box = (cq.Workplane("XY", origin=(X_COL, Y_COL, 10.0)) - .rect(66.6, 66.6) - .extrude(z_collar_top - 10.0 + 1.0) - .edges("|Z").fillet(6.3)).val() - s = s.cut(socket_box) - - # 7. 4x M5 底部垂直紧固螺栓孔与沉孔 - for xx in (X_COL - 21.0, X_COL + 21.0): - for yy in (Y_COL - 21.0, Y_COL + 21.0): - th = cyl(2.75, -0.01, 10.5, xx, yy) - cb = cyl(5.0, -0.01, 6.0, xx, yy) - s = s.cut(th).cut(cb) - - # 8. 6 组 M6 工业工作台紧固腰槽 (28 x 7 mm) - slot_positions = [ - (-215.0, Y_COL - 85.0), (-215.0, Y_COL + 85.0), # 后部两角 - (-47.5, Y_COL - 95.0), (-47.5, Y_COL + 95.0), # 中部两侧 - (120.0, Y_COL - 85.0), (120.0, Y_COL + 85.0), # 前部两角 - ] - for cx, cy in slot_positions: - plane = cq.Plane(origin=(cx, cy, -0.01), xDir=(0, 1, 0), normal=(0, 0, 1)) - slot = cq.Workplane(plane).slot2D(28.0, 7.0).extrude(z1 + 1.0).val() - s = s.cut(slot) - - # 9. 底部隐蔽走线通槽 - wire_ch = (cq.Workplane("XY", origin=(X_COL, Y_COL - 80.0, -0.01)) - .rect(28.0, 100.0) - .extrude(6.0)).val() - s = s.cut(wire_ch) - return s - - -def p_girdle(j1): - """P03_肩关节安装悬臂 (轻量化板式桁架版 + 侧开式电机托架): - 1. 10mm 后端重载法兰盘 (X: -61.0..-51.0, 74x74mm); - 2. 主干由 66mm 实心块改为双侧 6mm 桁架竖板 (Y = Y_ARM ± 27..33),外轮廓保持旧版 - "主干 ∪ 侧筋"并集不变 —— 只减材料不加体积,零新增干涉风险;实心 728 -> 286 cm³ (-61%); - 3. 竖板帐篷形减重窗 (顶/底斜面 ≥52°,自支撑;仅 20mm 平顶可桥接); - 4. 底部 9mm 承力鞍座 + 43.4x43.4 定位榫头 (配 P01_C 44x44 内腔,单边 0.3mm 间隙); - 鞍座底面 66x66 与立柱顶环带承压面积 2403mm² —— 竖向载荷走面压,螺栓只防上抬/防滑; - 5. 4x 安装孔 (X_COL±27.5, Y_COL±13.5):Ø3.9 直孔,M4x16 从上方直接拧入自攻成型 - (鞍座与立柱顶环同径,无过孔/沉孔/预埋),可拆装; - 6. 下腹中央龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰下缘) + 顶部横向锁紧环抱筋 (66x9x10mm, - 与两侧竖板外面齐平、顶面压在筋板脊线上,仅顶前棱一道 2mm 倒角); - 7. 侧开式电机托架 (+Y侧全开, X: -51.0..11.5,前端随 X_BAY_EXT 前延 3mm), - M1 电机可从侧方斜插卡入装配(插槽比电机长 3mm),由后端法兰 4xM3x16 锁死; - 电机舱 Ø60.7 从后端直通到前端面(2026-10-02 用户裁定"去掉台阶改成平的":无环形台阶、 - 无喇叭锥;原前置止脱锁紧法兰 4xM3 随之取消,电机轴向由后端 4xM3x16 约束); - 9. 两侧大面积散热舷窗 (34x20mm) + 顶部双斜向对流排热槽 (30x6.5mm),确保散热畅通; - 10. Ø26mm 中央走线孔;竖板间为开放腔体(无封闭空腔),M3 螺丝刀通道直接走腔体,拧紧更顺手。 - """ - X_MOUNT = Z_R1 # -51.0 (Rear face of M1) - T_FLANGE = 10.0 # 10mm thick high-strength mounting flange - X_BACK = X_MOUNT - T_FLANGE # -61.0 - Y_ARM = Y_AXIS # 128.0 - Z_ARM = Z_RL # 610.0 - Y_P_IN = 27.0 # 桁架竖板内表面 (Y_ARM ± 27) - Y_P_OUT = 33.0 # 桁架竖板外表面 (与旧主干齐平) - T_SADDLE = 9.0 # 底部鞍座厚度 - TENON = 43.4 # 定位榫头边长(P01_C 内腔 44x44 R4,单边留 0.3mm) - WIN_R = 4.0 # 竖板减重窗内角圆角(消除内尖角应力集中,Kt 由 ~2.5 降到 ~1.2) - BOLT_DX, BOLT_DY = 27.5, 13.5 # 4x 安装孔相对立柱中心偏移:落在顶环 11mm 实体壁内 - - # 1. 主安装法兰盘 (74x74mm, 四角倒圆 R=8mm) - # 背面那圈 2mm 倒角已去掉:板料只伸进法兰 0.1mm,倒角面与板背面相交会在 - # Z≈574.9 挤出一批 2mm 短棱,正是这批短棱让整条交接内棱网络倒不出圆角; - # 同时它跨过 R8 角部圆弧,就是角上那道"卷起来"的怪倒角。 - flange = (cq.Workplane("YZ", origin=(X_BACK, Y_ARM, Z_ARM)) - .rect(74.0, 74.0) - .extrude(T_FLANGE) - .edges("|X").fillet(8.0)).val() - s = cq.Shape(flange.wrapped) - - # 2. 双侧桁架竖板 (6mm):旧"斜挑支撑主干"与"大三角加强筋"轮廓同段挤出 → 自动并集成单块侧板 - # 主干的后缘两点取在加强筋斜边的延长线上,使外轮廓顶部成为一整条直线 - # (否则后缘 (-148,550) 处有 5° 折点、留下 5mm 台阶)。 - # Z_CREST = 侧筋脊线高度,同时是顶筋 2mm 顶前倒角的下沿:倒角从 (X-71,Z_CREST) - # 斜上到 (X-69,Z_CREST+2) = 顶筋顶面,斜坡面与倒角面几乎相切,顶部读起来是一整张面。 - Z_CREST = Z_ARM + 33.0 # 643(初版 642;顶筋整体上移 3mm 让出扳手空间后跟着抬) - - RIB_SLOPE = (Z_CREST - 575.0) / ((X_BACK - 10.0) - (X_COL - 15.0)) - - def rib_line_z(x): - return Z_CREST + RIB_SLOPE * (x - (X_BACK - 10.0)) - - trunk_pts = [ - (X_COL - 33.0, Z_CHEST1), - (X_COL + 33.0, Z_CHEST1), - (X_COL + 33.0, 505.0), - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_ARM - 5.0), - (X_COL - 10.0, rib_line_z(X_COL - 10.0)), - (X_COL - 33.0, rib_line_z(X_COL - 33.0)), - ] - rib_pts = [ - (X_COL - 33.0, Z_CHEST1), # 底边两端与主干/鞍座对齐 (±33):原来收进 3mm, - (X_COL + 33.0, Z_CHEST1), # 会在鞍座顶面上留下 0.7/3mm 短棱,卡住整条底棱圆角 - (X_BACK + 0.1, 565.0), - (X_BACK + 0.1, Z_CREST), - (X_BACK - 10.0, Z_CREST), # 脊线 (-71,643):正好落在顶筋前面与 2mm 倒角下沿的交点 - (X_COL - 15.0, 575.0), - (X_COL - 30.0, 530.0), - ] - for sign in (-1, 1): - ya = Y_ARM + sign * Y_P_IN - yb = Y_ARM + sign * Y_P_OUT - y0, y1 = min(ya, yb), max(ya, yb) - for prof in (trunk_pts, rib_pts): - s = s.fuse(cq.Shape(Yplate(prof, y0, y1).wrapped)) - - # 3. 竖板减重窗 (帐篷形:斜面 ≥52° 自支撑、仅 20mm 平顶可桥接;避开鞍座与 M5 螺母柱) - # 6 个内角全部 R4 圆角:把内尖角应力集中 (Kt≈2.5) 降到 ≈1.2,同时圆角面仍是竖直/自支撑面 - win_pts = [ - (X_COL - 24.0, 498.0), (X_COL + 23.0, 498.0), (X_COL + 25.0, 522.0), - (X_COL + 10.0, 545.0), (X_COL - 10.0, 545.0), (X_COL - 25.0, 524.0), - ] - for sign in (-1, 1): - yc = Y_ARM + sign * 0.5 * (Y_P_IN + Y_P_OUT) - tool = (cq.Workplane("XZ", origin=(0, yc, 0)).polyline(win_pts).close() - .extrude(Y_P_OUT - Y_P_IN + 1.0, both=True) - .edges("|Y").fillet(WIN_R).val()) - s = s.cut(cq.Shape(tool.wrapped)) - - # 4. 底部承力鞍座:坐实立柱顶面,把两竖板、龙骨与榫头连成整体 - s = s.fuse(bx(X_COL - 33.0, X_COL + 33.0, Y_ARM - Y_P_OUT, Y_ARM + Y_P_OUT, - Z_CHEST1, Z_CHEST1 + T_SADDLE)) - - # 5. 下腹部纵向抗弯龙骨筋 (12mm 中位腹板,自鞍座斜拉至法兰背面;交接内棱在融合后做真 3D 圆角) - keel_pts = [ - (X_COL + 33.0, Z_CHEST1), # (-82, 470) - (X_COL + 33.0, 495.0), # (-82, 495) - (X_BACK + 0.1, 573.0), # (-60.9, 573) 斜拉至法兰背面 - (X_BACK + 0.1, 594.4), # (-60.9, 594.4) 顶端贴法兰 - (X_COL + 42.1, 585.0), # (-72.9, 585) - (X_COL + 20.0, Z_CHEST1), # (-95, 470) 背面一路斜到底:原来在 (-95,480) 有个折点, - ] # 折点离鞍座顶面只 1mm,R2.5 圆角爬不上去 → 龙骨底棱整组倒不出 - s = s.fuse(cq.Shape(Yplate(keel_pts, Y_ARM - 6.0, Y_ARM + 6.0).wrapped)) - - # 7. 顶部横向锁紧环抱横筋:Y 向与两竖板外面齐平 (Y_ARM±33)、前面与筋板脊线共面 (X=-71)、 - # 顶面 Z=645 压住脊线 (Z=643) 并与 2mm 顶前倒角相切 → 顶筋与两侧脊线合成一整块。 - # 整体比初版抬高 3mm (底面 633→636):4x M3 后端沉孔 Ø6.5 跨在 Z 627.2..633.7 上, - # 底面 633 会压住孔口上沿 0.7mm、扳手让位只剩负间隙;抬到 636 后退到孔口上方 2.3mm、 - # 离螺钉轴线 5.55mm。顶面 645 不越过法兰 R8 角部圆弧在 Y=95 处的 645.9 上界。 - tie_rib = (cq.Workplane("YZ", origin=(X_BACK - 10.0, Y_ARM, Z_ARM + 30.5)) - .rect(66.0, 9.0) - .extrude(10.0) - .edges(cq.selectors.BoxSelector((X_BACK - 10.5, Y_ARM - 34.0, Z_ARM + 34.5), - (X_BACK - 9.5, Y_ARM + 34.0, Z_ARM + 35.5))) - .chamfer(2.0)).val() - s = s.fuse(cq.Shape(tie_rib.wrapped)) - - # 6.y 体-体交接凹棱:融合完成后对"凹棱"直接做真 3D 圆角。 - # 原先这里是手工沿棱 fuse 的 10 条"正方形−四分之一圆"焊珠小体(_cove),已整体删除: - # 焊珠端面是平的、悬在半空,两条互相垂直的焊珠在角上互不搭接 → 圆角"缺一块", - # 并与相邻体的棱互相穿插留 1~3mm² 碎面。真圆角在面边界自然收口、多棱相交处由 OCC - # 生成角部补片。 - from cadquery import selectors as _sel - from OCP.BRepAdaptor import BRepAdaptor_Surface - from OCP.GeomAbs import GeomAbs_Plane - from OCP.TopAbs import TopAbs_REVERSED - - def _outward(f, sh): - """平面面的外法向:先按 Orientation 修正,再用"面中心外移 1mm 是否仍在实体内"兜方向。""" - ad = BRepAdaptor_Surface(f.wrapped) - if ad.GetType() != GeomAbs_Plane: - return None - ax = ad.Plane().Axis().Direction() - n = cq.Vector(ax.X(), ax.Y(), ax.Z()) - if f.wrapped.Orientation() == TopAbs_REVERSED: - n = n * -1.0 - c = f.Center() - if cq.Solid(sh.wrapped).isInside(cq.Vector(c.x + n.x, c.y + n.y, c.z + n.z), 1e-6): - n = n * -1.0 - return n - - def _fillet_reflex(lo, hi, radii, tag, eps=0.9): - """倒出 lo~hi 区域内的凹棱(两面夹 270° 材料)。判据:从棱中点沿 (n1-n2) 外探 eps, - 落在实体内 = 凹、落在外 = 凸。区域内凹棱先整批一次倒(半径逐级降级,且倒完验拓扑: - OCC 会给出"没抛异常但面已坏"的结果),建不出时按相连棱分组逐组倒。""" - nonlocal s - - def _pick(sh): - edges = sh.Edges() - idx = {} - for i, e in enumerate(edges): - idx[e.hashCode()] = i - adj = [[] for _ in edges] - for f in sh.Faces(): - for ee in f.Edges(): - k = idx.get(ee.hashCode()) - if k is not None: - adj[k].append(f) - box = _sel.BoxSelector(lo, hi) - out = [] - for i, e in enumerate(edges): - if e.Length() <= 6.0 or len(adj[i]) != 2 or not box.filter([e]): - continue - n1 = _outward(adj[i][0], sh) - n2 = _outward(adj[i][1], sh) - if n1 is None or n2 is None: - continue - dx, dy, dz = n1.x - n2.x, n1.y - n2.y, n1.z - n2.z - ln = math.sqrt(dx * dx + dy * dy + dz * dz) - if ln < 1e-6: - continue - p = e.Center() - if cq.Solid(sh.wrapped).isInside( - cq.Vector(p.x + dx / ln * eps, p.y + dy / ln * eps, - p.z + dz / ln * eps), 1e-6): - out.append(e) - return out - - es0 = _pick(s) - if not es0: - print(" [P03 真圆角] %-14s 区域内无凹棱,跳过" % tag, flush=True) - return - - def _groups(ee): - """按共端点把凹棱连成链/网络:一组一次倒,OCC 才能在交汇处生成角部补片; - 逐条倒会在交汇处留下断口(看着就像圆角倒一半停了)。""" - pts = [e.Vertices() or [e.Center()] for e in ee] - par = list(range(len(ee))) - - def find(a): - while par[a] != a: - par[a] = par[par[a]] - a = par[a] - return a - - for i in range(len(ee)): - for j in range(i + 1, len(ee)): - if any(pi.distance(pj) < 0.05 for pi in pts[i] for pj in pts[j]): - par[find(i)] = find(j) - g = {} - for i in range(len(ee)): - g.setdefault(find(i), []).append(ee[i]) - return list(g.values()) - - def _do(edges_, rr): - """倒完立刻验拓扑:OCC 会给出"没抛异常但面已坏"的结果,必须挡下来。""" - try: - ns = cq.Shape(cq.Solid(s.wrapped).fillet(rr, edges_).wrapped) - except Exception: - return None - return ns if cq.Solid(ns.wrapped).isValid() else None - - # 先试"区域内全部凹棱一次倒"(探针实测这样能整批成型,且只重建一次, - # 不会像分批那样把同一张面反复重算坏掉);不行再按相连棱分组逐组倒。 - for rr in radii: - ns = _do(es0, rr) - if ns is not None: - s = ns - print(" [P03 真圆角] %-14s %d 条凹棱 R%.1f ✓ 整批一次成型" - % (tag, len(es0), rr), flush=True) - return - - n_ok, n_grp, left = 0, 0, len(es0) - for _ in range(24): - es = _pick(s) - if not es: - break - got = False - for g in sorted(_groups(es), key=lambda g: -sum(e.Length() for e in g)): - for rr in radii: - ns = _do(g, rr) - if ns is None: - continue - s = ns - n_ok += len(g) - n_grp += 1 - print(" [P03 真圆角] %-14s 第%d组 %d 条 R%.1f ✓" - % (tag, n_grp, len(g), rr), flush=True) - got = True - break - if got: - break - if not got: - left = len(es) - break - print(" [P03 真圆角] %-14s 共 %d 组 %d 条凹棱倒圆,剩 %d 条保持尖棱" - % (tag, n_grp, n_ok, left), flush=True) - - XR, YC, ZS = X_BACK, Y_ARM, Z_CHEST1 + T_SADDLE # -61, 128, 479 - # —— 鞍座顶面 (Z=479) 上的交接内棱:龙骨三条 + 两竖板内侧两条,一次真圆角 —— - # 半径上限 2.0:龙骨侧面 Y=122 与 M5 螺钉头外缘 Y=118.75 只隔 3.25mm, - # 原来的 R3 焊珠会顶到螺帽承压边,R2.0 留 1.25mm。 - _fillet_reflex((X_COL - 34.0, Y_ARM - 40.0, ZS - 3.0), - (X_COL + 34.0, Y_ARM + 40.0, ZS + 7.0), - (2.0, 1.5), "鞍座顶面交接") - # —— 法兰背面 (X=-61) 上的交接内棱:只倒竖板/龙骨的竖直棱 —— - # 顶筋底面那条水平棱 (Z=636) 不倒:4x M3 沉孔 Ø6.5 在 Z 627.2..633.7, - # 倒上 R2.5 会把螺钉上方 5.55mm 的扳手让位吃掉 2.5mm。 - # 上界取 631:竖板内棱被顶筋底面切成 573..636 / 636..643 两段,只倒下面长的那段 - # ——把 636..643 的 7mm 短段并进来,整批就从"一次成型"退化成逐组、且少倒 6 条。 - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, 570.0), - (X_BACK + 3.0, Y_ARM + 40.0, 631.0), - (2.5, 2.0, 1.5), "竖板/龙骨↔法兰") - _fillet_reflex((X_BACK - 3.0, Y_ARM - 40.0, Z_ARM + 34.5), - (X_BACK + 3.0, Y_ARM + 40.0, Z_ARM + 35.5), - (1.5, 1.0), "顶筋顶↔法兰") - - # 与立柱顶部配合的定位榫头 (43.4x43.4x15mm):配 P01_C 44x44 R4 内腔,单边 0.3mm 滑配 - boss = (cq.Workplane("XY", origin=(X_COL, Y_COL, Z_CHEST1 - 15.0)) - .rect(TENON, TENON) - .extrude(15.0) - .edges("|Z").fillet(4.2)).val() - s = s.fuse(cq.Shape(boss.wrapped)) - - # 4x 安装孔:统一 Ø3.9 直孔(M4x16 从腔体正上方拧入自攻成型;鞍座与立柱顶环同径、无沉孔) - # 孔位 (X_COL±27.5, Y_COL±13.5) 同时满足:①在立柱顶环实体壁内(内腔 22 → 外壁 33); - # ②在 P03 两竖板之间、避开中间龙骨,从腔体正上方用扳手可直接拧紧。 - for xx in (X_COL - BOLT_DX, X_COL + BOLT_DX): - for yy in (Y_COL - BOLT_DY, Y_COL + BOLT_DY): - s = s.cut(cyl(1.95, Z_CHEST1 - 0.5, Z_CHEST1 + T_SADDLE + 0.5, xx, yy)) - - # 1x 横向防脱锁紧:榫头 −X 侧向内 Ø3.9x14 直孔(与立柱后壁 Ø3.9 孔同轴,M4x16 拧入自攻) - s = s.cut(Xcyl(1.95, X_COL - TENON / 2.0 - 1.0, X_COL - TENON / 2.0 + 14.0, Y_COL, Z_LOCK)) - - # 4x M3 连接螺孔(螺丝刀通道直接走两竖板之间的开放腔体,无需再钻 Ø8.5 直通隧道) - # 孔位 = 电机尾端 4x M3 螺纹径向槽实测位(电机.STEP):BCD55、相位 39.9°+90k。 - # 旧 35.1×40.9 矩形孔距与实际槽位差 ~4.7mm(对角两颗螺钉无处咬合), - # 2026-10-02 用户指出"螺钉没有对齐"后按 STEP 实测修正。 - for dy, dz in ((21.06, 17.65), (-17.65, 21.06), (-21.06, -17.65), (17.65, -21.06)): - py = Y_ARM + dy - pz = Z_ARM + dz - th = Xcyl(1.7, X_BACK - 0.5, X_MOUNT + 4.0, py, pz) - cb = Xcyl(3.25, X_BACK - 0.5, X_BACK + 4.5, py, pz) - s = s.cut(th).cut(cb) - - # 中央走线孔 (Ø26mm) - wire_hole = Xcyl(13.0, X_COL - 40.0, X_MOUNT + 2.0, Y_ARM, Z_ARM) - s = s.cut(wire_hole) - - # 6. 前向电机全包托架 (X: -51.0..8.5+X_BAY_EXT=11.5,Ø60.7 舱孔平直贯通到端面) - # 端面随插槽加长前延 X_BAY_EXT;叉板背面(10.5+X_FORK_GAP=11.5)与端面 0.0 硬贴平 - # (用户两次要求"搞成平的";J1 转动摩擦由用户知悉,发涩时可微松 M2) - X_FRONT = 8.5 + X_BAY_EXT - R_POD = 36.5 - R_BORE = 30.35 - - cradle_outer = (cq.Workplane("YZ", origin=(X_MOUNT - 0.1, Y_ARM, Z_ARM)) - .circle(R_POD) - .extrude(X_FRONT - X_MOUNT + 0.1) - .edges(">X").chamfer(1.5)).val() - s = s.fuse(cq.Shape(cradle_outer.wrapped)) - - # 电机定子配合内腔(最终状态,2026-10-02 用户裁定"去掉台阶改成平的+保留固定电机的面"): - # Ø60.7 圆柱孔壁从前端面平直贯通到舱底(无窄颈、无台阶、无喇叭锥); - # 舱底 = 一个完整的平坦环面(X=5.06+X_BAY_EXT=8.06,与插槽末端墙共面,中开 Ø39 凸台过孔) - # ——即电机前限位面。电机轴向由后端法兰 4xM3x16 约束; - # 前端锁紧由 4 个 Ø10 局部凸台上的 M3 圆柱头螺钉承担(见下方凸台段)。 - motor_pocket = Xcyl(R_BORE, X_MOUNT - 0.2, 5.06 + X_BAY_EXT, Y_ARM, Z_ARM) - s = s.cut(motor_pocket) - - # 4x 前置 M3 锁紧螺钉凸台(BCD=55mm,17.8°/107.8°/197.8°/287.8°): - # 平直舱孔的沉头座位由 4 个 Ø10 局部凸台承载——凸台位于舱孔前段 8.06..11.5, - # 电机前端面(5.0)之前 3mm,电机斜插路径不经过;M3x8 圆柱头坐在凸台沉孔 - # 底(x=9.5)旋入电机前端面,咬合 3.5mm,兼作电机前端防转定位。 - for deg in (17.8, 107.8, 197.8, 287.8): - rad = math.radians(deg) - fy = Y_ARM + 27.5 * math.cos(rad) - fz = Z_ARM + 27.5 * math.sin(rad) - s = s.fuse(Xcyl(5.0, 5.06 + X_BAY_EXT, X_FRONT, fy, fz)) - th = Xcyl(1.6, 5.06 + X_BAY_EXT, X_FRONT, fy, fz) - cb = Xcyl(3.0, X_FRONT - 2.0, X_FRONT, fy, fz) - s = s.cut(th).cut(cb) - - # J1 输出凸台过孔 (Ø39 × 3.44——J1 凸台与 3xM2 输出螺钉的必经孔,非台阶; - # 原 6.7mm 长颈与半圆台肩已随舱底前移一并消除) - hub_clearance = Xcyl(19.5, 4.8, X_FRONT + 0.5, Y_ARM, Z_ARM) - s = s.cut(hub_clearance) - - # 8. 电机托架 +Y 侧全开(侧开式半包,替代原封闭全包筒): - # M1 电机可从 +Y 侧方直线滑入托架完成装配——原封闭筒电机物理上无法装入。 - # 轴向约束:后端法兰 4xM3(电机尾端面)单向锁死(前置止脱法兰随平直舱孔要求取消); - # 径向约束:Ø60.7 精密滑配托架底 + 重力贴底。开口前端至 5.06+X_BAY_EXT=8.06 - # (比电机前端面多 3mm 斜插余量,电机可倾斜卡入),止脱法兰环随之前移、环宽保持 3.44mm。 - s = s.cut(bx(X_MOUNT, 5.06 + X_BAY_EXT, Y_ARM, Y_ARM + 60.0, Z_ARM - 60.0, Z_ARM + 60.0)) - - # 7. 散热系统:双侧大视窗 + 顶部斜向对流排热槽 - for sign in (-1, 1): - win_plane = cq.Plane( - origin=(-24.5, Y_ARM + sign * 35.0, Z_ARM), - xDir=(1, 0, 0), - normal=(0, sign, 0) - ) - win_cut = cq.Workplane(win_plane).slot2D(34.0, 20.0).extrude(16.0, both=True).val() - s = s.cut(win_cut) - - for deg in (45, 135): - rad = math.radians(deg) - slot_plane = cq.Plane( - origin=(-24.5, Y_ARM + 33.0 * math.cos(rad), Z_ARM + 33.0 * math.sin(rad)), - xDir=(1, 0, 0), - normal=(0, math.cos(rad), math.sin(rad)) - ) - slot_cut = cq.Workplane(slot_plane).slot2D(30.0, 6.5).extrude(15.0, both=True).val() - s = s.cut(slot_cut) - - return s - - -def p_fork(j1, j2): - """P04_肩叉_螺旋体30%_墙5底5_竖打 (Shoulder Fork / L-Bracket) - 加长避让顺滑流线版: - 1. 头部与身子起点自 X=5.5 前移加长至 X=10.5+X_FORK_GAP=11.5,叉板背面与 P03 托架端面 - (X=8.5+X_BAY_EXT=11.5) 0.0 硬贴平(2026-10-02 用户两次要求"搞成平的"; - P03 侧插槽独立加长 3mm 便于 M1 电机斜插卡入); - 2. J1 从动法兰圆盘已删除(盘面在 column 前缘形成鼓楞):J1 由 boss(Ø35.6)贯通 P03 前盖中心孔直连电机输出盘;column 顶边保持 Z=646.5 与 P03 环齐高,R=18mm 大圆弧顺滑过渡; - 3. 头部转接凸台加长贯通 P03 前盖中心孔 (R=19.5mm),直连 J1 旋转法兰盘; - 4. M2 保留后端安装板(R=35mm, 带 rear_mount_holes 螺钉孔),前端定位环鼓出圆盘已删除(前端定位由 column 端面 pocket 承担),中间镂空散热; - 4b. 【2026-10-02 新增】M2 前端压盖:原方案电机只被后端安装板(S02 4xM3x10)单面锁死, - 大扭矩下电机绕 Y 轴在 Ø60.3 座孔里点头/翘起,固定不牢。现在前端盖外端面 - (Y=156.0) 再加一枚同径 R35 压盖,用 4x M3x8 在电机前端盖 BCD55 上把电机 - 从前端夹一次 —— 电机被前后两块同心 Ø60.3 端面双向夹紧,倾斜自由度归零; - 螺钉沉在压盖沉孔内不外凸,不占用 J2 轴向间隙(P05 法兰盘已相应后移); - 5. 严格满足 single-solid 流形拓扑与 360° 全范围零干涉 (COLLISION pairs = 0)。 - """ - # 1. M2 后端安装法兰盘 (Y: 92..100, R=35mm) - rear_plate = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_SH) - - # 2. M2 前端定位环 (Y: 149.5..155.5, R=35mm) - front_collar = Ycyl(35.0, Y_COVER - 6.5, Y_COVER - 0.5, X_ARM, Z_SH) - s = rear_plate.fuse(front_collar) - - # 2b. 【2026-10-02 新增】M2 前端压盖(几何与理由见 motor_front_clamp)。 - # 必须在第 6/7 步所有 R30.15~30.5 圆柱刀具走完之后才熔接,否则压盖根部会被 - # front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边(见 7b)。 - fp = motor_front_clamp(X_ARM, Z_SH, tag="P04/M2") - - # 3. J1 从动法兰圆盘已删除:原 R=36.5 圆盘盘面在 column 前缘形成圆盘鼓楞, - # J1 与 P04 的连接由 boss(Ø35.6 贯通 P03 前盖中心孔 R=19.5)直连电机输出盘 - # 承担,螺钉走 boss 端面 4xB 孔,圆盘无传力功能已去除。 - - # 4. XZ 平面有机连续流线轮廓(随 X_FORK_GAP 整体前移,避让顺滑过渡): - x_b, z_b = -12.65 + X_FORK_GAP, 506.0 - x_a, z_a = 10.5 + X_FORK_GAP, 545.0 - Lz = z_a - z_b - d0 = 0.514 * Lz - d1 = 0.0 - - hermite_pts = [] - N_h = 16 - for i in range(N_h + 1): - t = i / N_h - h00 = 2.0 * (t**3) - 3.0 * (t**2) + 1.0 - h10 = t**3 - 2.0 * (t**2) + t - h01 = -2.0 * (t**3) + 3.0 * (t**2) - h11 = t**3 - t**2 - xh = x_b * h00 + d0 * h10 + x_a * h01 + d1 * h11 - zh = z_b + t * Lz - hermite_pts.append((xh, zh)) - - dx_flare = (53.5 + X_FORK_GAP) - (44.0 + X_FORK_GAP) - dz_flare = 545.0 - 490.0 - R_flare = (dx_flare**2 + dz_flare**2) / (2.0 * dx_flare) - - xz_wire = (cq.Workplane("XZ", origin=(0, Y_AXIS, 0)) - .moveTo(10.5 + X_FORK_GAP, 545.0) - .lineTo(10.5 + X_FORK_GAP, Z_RL + 36.5) - .lineTo(28.0 + X_FORK_GAP, Z_RL + 36.5) - .radiusArc((44.0 + X_FORK_GAP, 630.0), 18.0) - .lineTo(44.0 + X_FORK_GAP, 545.0) - .radiusArc((53.5 + X_FORK_GAP, 490.0), -R_flare) - .lineTo(18.5 + X_FORK_GAP, 490.0) - .lineTo(-16.5 + X_FORK_GAP, 490.0) - .threePointArc((-15.1 + X_FORK_GAP, 498.5), (-12.65 + X_FORK_GAP, 506.0))) - for pt in hermite_pts[1:]: - xz_wire = xz_wire.lineTo(pt[0], pt[1]) - xz_wire = xz_wire.close() - xz_shape = cq.Shape(xz_wire.extrude(100.0, both=True).val().wrapped) - - # 5. YZ 平面包络 - # 【2026-10-07 按用户手绘红线改成"R3 圆角 + 竖直前壁",消掉顶面前沿那颗尖】 - # 来历:2026-10-04 那版把前脸做成与顶面**相切**的 Hermite 弧面,但这条曲线 - # 只有 9.0mm 的 Y 预算(164.5->155.5),却要吸收 120.5mm 的落差,于是 90° 的 - # 转弯全挤在起点:实测离开顶面 0.28mm 落差就转到 45°,起始曲率半径 0.571mm。 - # 后果是顶面前沿 Y=164.5 成了**全件最外凸点**,往下前脸立刻后退, - # 在 Y=164.32 处顶面与前脸之间的料厚只剩 0.027mm —— 就是用户截图圈的那颗尖。 - # 2026-10-05 的"起点加密采样"把第一段 57° 折线(那其实是一层意外的钝化)抹平, - # 反而让它变成真正的刀刃。两次都只碰了前缘**一条**棱, - # 顶面左缘(X=11.5, 73mm)与后缘(Y=91.5, 17.5mm)仍是 89° 直棱,所以没解决。 - # 现在的形状:顶面 -> 前侧 R3 圆角 -> 竖直前壁 Y=164.5 一路直落到 Z=566 - # -> Hermite(两端切线均沿 -Z,G1) 顺收到 (155.5, 526); - # 顶面**后缘**同样倒 R3(与前缘对称,用户 2026-10-07 追加要求), - # 弧心落在背面那条斜线上(背面从 (91.5,646.5) 斜到 (92.0,526),斜 0.24°, - # 切点 Y 必须按该斜线取,否则弧端会露出 0.01mm 台阶)。 - # crest 不再是独立的最外凸点(整条前壁同为 Y=164.5),尖由此从根上消失; - # 全件 Y 最大值仍是 164.5,与 P03 止脱环 R36.5/顶646.5 的齐平关系不变。 - # 后缘圆角只减料:体积 296.24 -> 296.20 cm3,Y 最小值 91.50 -> 91.51。 - # 代价(实测):P04 体积 276.2 -> 296.2 cm3 (+20.0),因为填掉了原来那个内凹腔; - # 且 J1 扫掠半径由 crest 的 hypot(36.5,36.5)=51.6 增到前壁 (Y164.5,Z566) 的 - # hypot(36.5,44.0)=57.2 (+5.6mm) —— 全行程碰撞/行程必须重扫才算完成。 - # 注意:顶点绕序必须与原列表一致(先顶面、再前壁、再竖直段),否则多边形自交、 - # 布尔后零件会碎掉(实测会掉到 48cm3 / 3 块实体)。 - y_a = (Y_COVER - 0.5, Z_SH + 36.0) # (155.5, 526.0) 顺收终点 - y_b = (Y_AXIS + 36.5, Z_RL + 36.5) # (164.5, 646.5) 顶面前沿 = 前壁所在 Y - R_TIP = 3.0 # 顶面 -> 竖直前壁 的圆角半径 - R_BACK = 3.0 # 顶面 -> 背面 的圆角半径(与前缘对称) - Z_WALL_END = 566.0 # 竖直前壁落到此处,再往里顺收 - _c = (y_b[0] - R_TIP, y_b[1] - R_TIP) # 前侧圆角心 (161.5, 643.5) - # 背面那条线是斜的:(91.5, 646.5) -> (92.0, 526.0),斜 0.24°。 - # 后侧圆角必须切在这条线上,否则弧端点会露出 0.01mm 的台阶。 - _zb0, _zb1 = Z_RL + 36.5, Z_SH # 背面上下端 Z - _yb0, _yb1 = Y_AXIS - 36.5, Y_REAR - 8.0 # 背面上下端 Y - _z_ab = _zb0 - R_BACK # 弧与背面的切点高度 643.5 - _y_ab = _yb0 + (_zb0 - _z_ab) / (_zb0 - _zb1) * (_yb1 - _yb0) - _cb = (_y_ab + R_BACK, _z_ab) # 后侧圆角心 - yz_pts = [(_cb[0], _zb0), # 顶面后缘改从圆角起点开始 - (_c[0], y_b[1])] # 顶面走到前侧圆角起点 Y=161.5 - for _k in range(1, 31): # R3 圆角:90° -> 0°,切线由水平转竖直 - _a = math.radians(90.0 - 90.0 * _k / 30.0) - yz_pts.append((_c[0] + R_TIP * math.cos(_a), _c[1] + R_TIP * math.sin(_a))) - yz_pts.append((y_b[0], Z_WALL_END)) # 竖直前壁 Y=164.5 直落(直线段) - _m = 0.5 * math.hypot(y_b[0] - y_a[0], Z_WALL_END - y_a[1]) * 0.5 - for _k in range(1, 121): # 底部顺收 Hermite:两端切线都沿 -Z - t = _k / 120.0 - h00 = 2 * t ** 3 - 3 * t ** 2 + 1 - h10 = t ** 3 - 2 * t ** 2 + t - h01 = -2 * t ** 3 + 3 * t ** 2 - h11 = t ** 3 - t ** 2 - yz_pts.append((h00 * y_b[0] + h01 * y_a[0], - h00 * Z_WALL_END + h01 * y_a[1] - _m * (h10 + h11))) - yz_pts += [(Y_COVER - 0.5, Z_SH), - (Y_REAR - 8.0, Z_SH), - (Y_REAR - 8.0, Z_SH + 36.0)] - for _k in range(0, 30): # 后侧 R3 圆角:180° -> 90° - _a = math.radians(180.0 - 90.0 * _k / 30.0) - yz_pts.append((_cb[0] + R_BACK * math.cos(_a), _cb[1] + R_BACK * math.sin(_a))) - yz_solid = Xplate(yz_pts, -20.0, 60.0) - - c_int = BRepAlgoAPI_Common(yz_solid.wrapped, xz_shape.wrapped) - c_int.Build() - column = cq.Shape(c_int.Shape()) - - s = s.fuse(column) - - # 6. 中部电机定子散热切除 - # 【2026-10-05f】这刀原本是独立存在的(挖 R30.5 让定子裸露)。现在主腔已经 - # 统一到 BAY_R=30.50 并从承压面一路挖到前端,这刀完全落在主腔范围内 —— - # 留着不改也不会多挖,但它是「同一段腔被两个不同半径各挖一次」的残留, - # 正是接缝的来源,删掉以免后人以为散热口另有深意。 - # 通风靠 cut_open(第 6b 步的敞口盒切)实现,不依赖这刀。 - # s = s.cut(Ycyl(30.5, Y_REAR + 0.5, Y_COVER - 6.5, X_ARM, Z_SH)) - - # 7. M2 接口成型 - # 【2026-10-05f】与 Joint.pocket 同步:主腔统一为单一 BAY_R 圆柱, - # 从承压面一路挖到前端盖外,内壁不再有 R35/R30.15/R30.25/R30.50 四段台阶。 - # (承压面段不再单独挖 SEAT_R:BAY_R 从 -REAR_SEAT 起就覆盖了它, - # 承压面本身仍是 R35 外壁围出的完整平圆盘。) - s = j2.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = s.cut(j2.L(cyl(AB.BAY_R, -AB.REAR_SEAT, COVER_Z + 0.6))) - s = s.cut(j2.L(cyl(AB.SEAT_R, -AB.REAR_SEAT, AB.SEAT_TOP))) - - - # 7b. M2 前端压盖熔接(见 2b):必须在 6/7 步所有 R30.15~30.5 圆柱刀具走完之后, - # 否则压盖根部会被 front_collar_pocket / c_m2 啃掉一圈 0.1mm 薄边。 - s = s.fuse(fp) - - # 7b2.【2026-10-05e】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。 - # 围挡 = front_collar_pocket 把座孔扩到 R30.25 后留下的那圈 r30.25..35、6mm 高 - # 环墙(Y149.5..155.5),比电机前端盖 Ø60.3 凸 4.75mm,把前端盖整圈围住; - # 上面还骑着 R35 压盖(Ø70 再凸 3.2mm)。切敞口(局部 +y = 全局 -Z)那半圈后, - # 背脊(局部 -y = 全局 +Z)侧整圈保留:继续定位前端盖、并作为压盖唯一且足够强 - # 的支撑面(4.75mm 宽 x 0.5mm 熔接带,本次完全不碰)→ M2 仍是 - # 后端板 4xM3x10 + 前端压盖 4xM3x8 双向面压;压盖本体/Ø37 孔/4xM3x8 沉孔/ - # 腔口下沿 Y155.5 全部不动。 - s = cut_motor_fence(s, j2, "P04/M2", "P04") - - # 7c. 【2026-10-02 新增】立柱减重槽(见 hollow_channel)。 - # 立柱 Z525..635 整段是壁厚最深处达 17.5mm 的实心料,而 J2 在 z=560/600 的 - # 安全系数有 19。挖成朝上敞开的 U 形槽后立柱变成槽钢:抗弯的两片 X 向腹板 - # 正好落在弯矩拉压区(z=560 截面 X13..45,中性面在 X29,腹板占 X13~23/X35~45), - # 被掏掉的正是中性层附近几乎不参与抗弯的料。 - # 槽底 Z525 以下保留整块实心过渡到电机座;顶部敞开故非封闭腔、免支撑。 - # 必须在 8 步 J1 凸台/螺钉孔之前切:槽的 X23..35 范围离 J1 凸台(X<=11.5)与 - # 3xS01 沉孔(X12..14)都有富余,但先切可以保证后面那些刀具不会把槽壁打薄。 - # 7c2.【2026-10-05 槽右壁跟随外轮廓,去掉右上角两段尖楔】 - # x1=35 是按立柱中段定的(Z525~630,X11.5~45 全宽都在,中性面 X29, - # 两片腹板 X13~23 / X35~45 各 10mm)。可 Z630 以上零件被 R18 圆角收窄: - # 顶面只剩 X11.5~29,再往右是圆角带 X29~45。 - # 原先一刀切到底,槽就横着切进圆角带;又因为槽在 Y=102/154 收尾, - # 圆角带被切成两段悬空的楔料(一头尖一头宽),从右上角看就是"一小段尖的" - # —— 它悬在槽口上方 1.7mm(Z644.76~646.5)没有支撑,尖头正好落在收尾处。 - # 改法:**上段跟着外轮廓收窄**,Z>630 只切到 X29(顶面右边缘), - # 圆角带 X29~45 一整条保持连续,中/左/右三段料都不再悬空, - # 尖楔和新台阶一起消失(先前只在 Z644.8 削平顶面,等于把尖顶削成一条 - # 新台阶,尖头本身还在,更难看 —— 已回退)。 - # 下段 X23..35 原样保留,腹板位置和厚度都不动,强度结论不受影响。 - # 分界 Z=630 取自 xz_wire:R18 圆弧正是从 (X45,Z630) 起往左上收。 - Z_TAPER = 630.0 # = 圆角起点 Z,槽右壁在此处收窄 - X_TOP_EDGE = 29.0 # = 顶面右边缘(圆角带上沿),槽不许越过 - s = s.cut(hollow_channel(P04_CH["x0"], P04_CH["x1"], P04_CH["y0"], P04_CH["y1"], - P04_CH["z_floor"], Z_TAPER, tag="P04立柱下段")) - s = s.cut(hollow_channel(P04_CH["x0"], X_TOP_EDGE, P04_CH["y0"], P04_CH["y1"], - Z_TAPER, 660.0, tag="P04立柱上段(随外轮廓收窄)", fil=0.0)) - # 上段 fil=0:槽宽只剩 6mm(X23~29),R4 内角圆角在 6mm 宽的槽上两侧 - # 叠加会退化成零面积而失败(实测报 BRep_API: command not done)。 - # 这里本来就是直壁窄槽,不倒角也符合"跟随外轮廓"的意图。 - - # 8. 头部转接凸台加长(贯通 P03 前盖中心孔,跨越装配缝直连 J1 输出盘) - boss = cyl(17.8, 54.942, 61.5 + X_FORK_GAP) - s = s.fuse(j1.L(boss)) - s = s.cut(j1.L(cyl(3.5, 54.0, 74.0 + 30.0))) - # 沉孔台阶固定在 63.0(不随前移):M2x10 旋入电机输出毂的咬合量与改前一致; - # 螺钉头(63..65)相对叉板背面(64.5)外露 0.5mm,位于 2mm 叉缝空隙内,无干涉。 - s = hub_m2_holes(s, j1, cb_z1=74.0 + 40.0 + X_FORK_GAP, - th_z1=63.0 + X_FORK_GAP, cb_z0=63.0) - - return s - - - -def make_pitch_to_roll_bracket(j_pitch, j_roll, z_pitch, z_r, name="bracket", hub_gap=1.0, - roll_clamp=False, fillet_r=1.5): - """Half-wrapped (半包) 双曲面有机流线型 Pitch-to-Roll 结构座 (P05 & P08): - 1. 顶部从动法兰与前侧 Loft 自然相切顺接; - 2. 严格清除 z_plate 顶面以上与上级旋转件贴面的多余材料 (cut_top_solid),保证关节大范围摆动动态 0 干涉; - 3. 下方电机实行半包散热设计,-Y侧敞开,+Y侧背脊与圆柱一体化融合; - 4. 严格满足 single-solid 流形拓扑与零干涉 (COLLISION pairs = 0)。 - - hub_gap = 从动法兰盘背面与上级电机前端盖之间的轴向让位间隙。 - 默认 1.0(贴合防磨)。P05 传 6.0:J2 处 P04 新增了 M2 前端压盖 - (Y 155.5..161.0),必须让出这 6.0mm 才能装下;见 p_shoulder_roll_base。 - - roll_clamp = 本件作为 roll 电机【前端夹紧件】时为 True(J3/P05 夹 M3)。 - roll 电机(J3/J6)轴向是 -Z,前端盖外端面在 z_r-56,-Z 侧是从动件: - 压盖朝 -Z 长(Z_COVER-5..Z_COVER),从动件毂面必须整体后移 CLAMP_BACKOFF=5.5 - 让位(与 Y 轴关节 hub_gap=6.0 同一个道理,只是基准间隙 0.5 而非 1.0)。 - P08 自 2026-10-03 起也传 True:M6 前端压盖补齐,对侧 P09 让位 CLAMP_BACKOFF - (见 make_direct_hub 的 backoff),五处电机腔至此全部是「后端板 + 前端压盖」 - 双向面压,配方同源。 - - fillet_r = 立柱顶面与竖直臂交汇处的 L 型内转角过渡圆角半径(mm,默认 1.5)。 - 半径调小为 1.5mm 确保在 OCC 中稳定边倒圆成型(小于临界高度 2.0mm,彻底避免算法异常), - 同时不生成多余外凸台阶,平滑消除 90° 锐角应力集中。 - """ - z_bot = z_r - 56.0 # roll 电机底端 (front collar) - z_plate = z_r + 8.0 # roll 电机顶端安装板 (rear plate top);见第 8 步 t_plate 说明 - y_out = HL.Y_HUB + HL.PLATE_T # 174.0 (俯仰直连法兰外侧面) - y_hub_in = HL.Y_COVER + hub_gap # 157.0 / (P05) 162.0 从动法兰内侧面 - z_hub_in = y_hub_in - HL.Y_REAR # 法兰盘背面的电机局部 z(凸台长度跟着它走) - y_cyl_max = HL.Y_AXIS + 35.0 # 163.0 (下方电机外轮廓极值) - - # 1. 基础立柱:Ø70mm 实心圆柱 (Z: z_bot .. z_plate, R=35mm) - s = cyl(35.0, z_bot, z_plate, HL.X_ARM, HL.Y_AXIS) - - # 2. 俯仰轴直连法兰盘:全圆柱实体 (Y: y_hub_in .. y_out, R=35mm) - disc = Ycyl(35.0, y_hub_in, y_out, HL.X_ARM, z_pitch) - s = s.fuse(disc) - - # 3. 前侧从底端 Ø70mm 圆柱到顶端法兰的有机相切平滑渐变过渡体 (Loft Morph) - Z_top = z_pitch - 35.0 - z_start = z_bot - N = 16 - 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) - 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_mid = max(v_c, v_s + 0.02) - # 后缘两角 R2 圆角:把 y=123 凸包与 R35 柱面交汇的两条 90° 竖棱圆润化 - # (磨掉"凸起来的"尖角,不动后缘高度/位置,打印无悬挂) - FR = 2.0 - 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 + FR) - .radiusArc((35.0 - FR, -5.0), FR) - .lineTo(-35.0 + FR, -5.0) - .radiusArc((-35.0, -5.0 + FR), FR) - .lineTo(-35.0, v_s) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 顶部填充(仅保留 y >= y_hub_in 与法兰盘对齐的部分,避免侵入上级关节旋转扫掠区域) - top_fill = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(y_hub_in, Z_top) - .lineTo(y_out, Z_top) - .lineTo(y_out, z_pitch) - .lineTo(y_hub_in, z_pitch) - .close() - .extrude(35.0, both=True)) - s = s.fuse(top_fill.val()) - - # 5. 旋转清让切除:彻底清除 z >= z_plate 且 y <= y_hub_in 范围内的多余材料, - # 消除导致与上游旋转件(P04/P07)严重干涉的立壁与尖角,保持顶面平整规整! - cut_top_solid = (cq.Workplane("YZ", origin=(HL.X_ARM, 0, 0)) - .moveTo(-999.0, z_plate) - .lineTo(y_hub_in, z_plate) - .lineTo(y_hub_in, z_pitch + 50.0) - .lineTo(-999.0, z_pitch + 50.0) - .close() - .extrude(60.0, both=True)) - s = s.cut(cut_top_solid.val()) - - # 6. 电机半包处理 (Half-Enclosure):-Y侧敞开散热,+Y侧背脊壳体加强 - cut_open = bx(HL.X_ARM - 40.0, HL.X_ARM + 40.0, HL.Y_AXIS - 40.0, HL.Y_AXIS + 3.0, z_bot + 6.0, z_r - 1.0) - s = s.cut(cut_open) - - # 7. 上级电机同心圆弧避让 - clearance = Ycyl(35.5, HL.Y_AXIS - 40.0, HL.Y_COVER + 0.1, HL.X_ARM, z_pitch) - s = s.cut(clearance) - - # 8. 下方电机安装孔位与定位止口 - # t_plate=8.0:Joint.pocket 的尾端面承压面让空了 MOTOR_TOL=1.0,承压面到螺钉沉孔 - # 台阶为 t_plate-cb_depth-REAR_SEAT = 8-4-1.6 = 2.4mm 膜厚(结构强度大幅提升)。 - s = j_roll.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - # 【2026-10-03i 接缝整改】front_collar_pocket 已删。 - # 它切的是 cyl(BAY_R, z0=50.0, z1=56.5),而紧接着的 Joint.pocket 切的是 - # cyl(BAY_R, -REAR_SEAT, COVER_Z+front_pad) —— **同一根圆柱、同一个半径**, - # 前者完全是后者的子集。两次半径相同的圆柱布尔相减,等于让 OCC 在腔壁上 - # 留下一条 s=50.0 的零宽棱:实测 P05 的 R30.500 腔壁被切成 5090.5 + 493.9 - # 两张面(-2.6..50.0 与 50.0..55.5),P06 同样在 50.0 处裂开。着色时那条 - # 棱就是腔里横穿的一道线。P04/P08 本来就没调它,腔壁是完整一张面。 - s = j_roll.pocket(s) - - # 9. 电机定子配合空腔 - # 【2026-10-05f】原 cyl(30.5, ...) 定子配合空腔已删,同上:已被主腔 BAY_R 覆盖。 - - # 10. 俯仰轴直连法兰结构(凸台延伸至法兰盘背面之后 2.5mm,跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, z_hub_in + 2.5) - s = s.fuse(j_pitch.L(boss)) - s = s.cut(j_pitch.L(cyl(3.5, 54.0, 74.0 + 30.0))) - s = hub_m2_holes(s, j_pitch, cb_z1=74.0 + 40.0) - - # 11. L 型转角连接处圆角过渡(圆角半径调小为 1.5mm): - # 平滑消除立柱顶面与竖直臂交汇处的 90° 尖角与应力集中, - # 半径调小至 1.5mm 确保在 OCC 中稳定成型(小于临界高度 2.0mm,彻底避免算法异常), - # 局部圆角不切入两侧轮廓,完全避免任何外凸台阶,流线自然过渡。 - if fillet_r > 0: - try: - wp_s = cq.Workplane(obj=s) - sel_l = cq.NearestToPointSelector((HL.X_ARM, y_hub_in, z_plate)) - s = wp_s.edges(sel_l).fillet(fillet_r).val() - except Exception as e: - print(f"[{name}] L型转角圆角异常: {e}") - - # 12. roll 电机(M3)前端压盖:与 P04/P06/P07 的 M2/M4/M5 压盖同一套配方, - # 只是 roll 电机轴向为 -Z,压盖朝 -Z 长(见 motor_front_clamp 的 axis="Z")。 - # 熔接必须放在最后:第 6/7/8/9 步所有 R30.15~30.5 圆柱刀具都走完之后才接, - # 否则压盖根部那 0.5mm 薄环会被 front_collar_pocket / 定子孔啃掉一圈。 - if roll_clamp: - # tag 里的电机号从 j_roll.jid 取(P05->M3 / P08->M6),别再写死 M3 —— - # 写死的话 P08 的构建日志会谎报自己夹的是 M3,排查时容易被带偏。 - s = s.fuse(motor_front_clamp(HL.X_ARM, 0.0, - tag="%s/M%d" % (name[:3], int(j_roll.jid[1])), - axis="Z", py=HL.Y_AXIS, z_face=z_r - COVER_Z)) - - # 13.【2026-10-05d】只切"围挡"那一圈:敞口(-Y)侧的前端定位环 + 压盖根部薄环。 - # 用户 2026-10-05 指出这圈环墙把 M3 前端盖和 P06 的 J3 凸台围在中间;我第一版 - # 把整个前端(定位环 + 压盖 + 腔口下沿 389->395)都切了,电机前端没了支撑面 —— - # 过度。现在只切敞口这一圈,Y 界限与第 6 步 cut_open 完全一致 - # (Y_AXIS-40 .. Y_AXIS+3),保证 -Y 半圈从 Z=395.5 一路开到压盖背面 Z=389; - # 背脊(+Y)侧整圈定位环保留: - # - 继续定位 M3 前端盖(内孔 r30.25,前端盖 Ø60.3 -> 单边 0.10mm); - # - 继续当 R35 压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带,本次改动 - # 完全不碰)→ M3 仍是后端板 4xM3x10 + 前端压盖 4xM3x8 双向面压夹紧; - # - 压盖本体 / Ø37 让位孔 / 4xM3x8 沉孔 / 腔口下沿 z_bot=389 全部不动, - # 压盖敞口侧的端面从 Z=389.5 降到 389 与背面齐平,视觉上不再凸出。 - # 为什么不能整圈削到与电机齐平(R35->R31.8,见 FENCE_OPEN_CUT 处的说明): - # ① 这圈壁与第 3 步 Loft 在 X=±35 处与 Ø70 柱面**相切**,整圈切完那两片切向楔形 - # (实测 1.32mm3)会脱空成孤立碎块,破坏单实体拓扑; - # ② 压盖根部只有 0.5mm 熔接带,环削到 1.55mm 宽后连接面从 2.37mm2 掉到 0.78mm2, - # 4xM3x8 的夹紧力会把压盖从本体上拉下来。 - # 附带效果:盒切把 Loft 在 X=±35 与 Ø70 柱面相切的那两片切向楔形一起带走 - # (楔形位于 Y_AXIS 附近 < Y_AXIS+3),不会留下孤立碎块 -> 单实体拓扑保持。 - # 13.【2026-10-05e】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。 - # P05(M3)/P08(M6) 现在都有前端压盖、环上骑着压盖 —— 两者都只切敞口(局部 +y) - # 那半圈定位环,背脊侧整圈保留,继续定位电机前端盖,并给压盖当 4.5mm x 0.5mm - # 的熔接带支撑面。 - if roll_clamp or FENCE_OPEN_CUT: - s = cut_motor_fence(s, j_roll, "%s/M3" % name[:3], name[:3]) - - return s - - -def p_shoulder_roll_base(j2, j3, half=None): - """P05_大臂旋转座_螺旋体30%_墙4底5_竖打(深度圆角优化版) - - 本件在链路上有两重身份,两处让位都是为前端压盖: - 1. hub_gap=6.0(从动侧):J2 处 P04 新增了 M2 前端压盖(前端夹紧电机,消除大扭矩下的 - 倾斜),压盖占 Y 155.5..161.0,所以从动法兰盘背面必须从 Y=157 后移到 Y=162 - (留 1.0mm 动间隙)。 - 2. roll_clamp=True(前端夹紧侧):P05 自己抱着 M3(J3,-Z 轴),原来也只有后端板 - 4xM3x10 单面锁死;现在按 motor_front_clamp 的统一配方在 M3 前端盖外端面 - (Z=389)加一枚 R35 压盖(Z 384..389,4xM3x8 BCD55),与后端板前后双向面压, - 倾斜自由度归零。代价是对侧从动件 P06 的 J3 毂面整体后移 CLAMP_BACKOFF=5.5 - (Z 388.5→383.0),见 p_humerus_bone。 - P08 保持默认 —— J5 处 P07 的前端压盖已做(只需 6.0 的 hub_gap),而 J6 的 M6 - 前端压盖尚未做,故 roll_clamp 传 False。 - """ - return make_pitch_to_roll_bracket(j2, j3, Z_SH, Z_R3, - name="P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - hub_gap=6.0, roll_clamp=True) - - -def p_humerus_bone(j3, j4): - """P06_大臂骨架_螺旋体25%_墙4底5_竖打 (Humerus Bone - Loft 圆弧转方有机过渡版): - 受 J3 (大臂自转, -Z轴, hub@Z_HUB3=380) 驱动, - 向下延伸构成大臂主体骨架,底端抱紧 M4 (肘部俯仰电机, +Y轴, 轴线位于 Z_EL=332.0)。 - 1. 借鉴 P05 大臂旋转座几何放样算法: - - 顶端结合面 (Z = 383.0mm):与 J3 轴径严格相切的 Ø70mm 正圆形轮廓,彻底消除原先外突的方形水平阶梯; - - 底端结合面 (Z = 367.0mm):平滑展开融入 M4 电机套筒的 70x63.5mm 方形截面; - - 过渡区 (Z: 367.0..383.0mm):采用 Hermite s(t) = 3t² - 2t³ 驱动 20 层放样切片连续渐变; - 2. 下部抱紧套筒与安装板 (R=35mm),电机定子 100% 裸露散热开窗; - 3.【2026-10-03】J3 毂面整体后移 CLAMP_BACKOFF=5.5(顶端 388.5→383.0,直锁凸台 - 59.5→65.0):P05 新增 M3 前端压盖,占 Z 384..389,原先 388.5 的毂面只剩 0.5mm - 让位放不下压盖。配方与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源: - 压盖 5.0 + 动间隙 1.0 - 原基准 0.5 = 5.5。凸台同步加长,3xS01 输出螺钉的头面 - 台阶(局部 s=58)、杆长、咬合量与改前完全一致,J3 直锁与零干涉不受影响。 - 压盖中央 Ø37 让位孔(R18.5)也让开 R17.8 凸台,单边余 0.7mm。 - 4. 严格单实体 (solids = 1) 且与上下级旋转构件及电机实现动态 0 碰撞干涉。 - """ - # 1. M4 底部抱紧套筒与后端安装板 (R=35mm) - rear_m4 = Ycyl(35.0, Y_REAR - 8.0, Y_REAR, X_ARM, Z_EL) - front_m4 = Ycyl(35.0, Y_COVER - 6.0, Y_COVER - 0.5, X_ARM, Z_EL) - s = rear_m4.fuse(front_m4) - - # 顶端结合面与过渡高度 - # z_top 由 M3 前端盖外端面反推:压盖占 [Z_COVER-CLAMP_T, Z_COVER],再留 CLAMP_CLR - # 动间隙 -> 383.0(改前 388.5,与前端盖只有 0.5mm,放不下压盖)。 - z_cover3 = Z_R3 - COVER_Z # 389.0 M3 前端盖外端面 - z_top = z_cover3 - CLAMP_T - CLAMP_CLR # 383.0 J3 毂面(= 388.5 - CLAMP_BACKOFF) - z_bot = Z_EL + 35.0 # 367.0 - - # 2. 下部实心基座块 (Z: Z_EL..z_bot + 0.5) - lower_block = bx(X_ARM - 35.0, X_ARM + 35.0, Y_REAR - 8.0, Y_COVER - 0.5, Z_EL, z_bot + 0.5) - s = s.fuse(lower_block) - - # 3. 借鉴 P05 的 Loft 放样过渡体:圆弧变方形(20 层平滑放样,保持严格对称 4 段式截面拓扑) - N = 20 - wp = cq.Workplane("XY") - for i in range(N + 1): - t = i / float(N) - z_curr = z_bot + t * (z_top - z_bot) - st = 3.0 * (t**2) - 2.0 * (t**3) - - # 前侧:t=0 为平直面 (Y=155.5, v=27.5),t=1 渐变为与 Ø70 正切的圆弧顶点 (Y=163.0, v=35.0) - v_sf = 27.5 * (1.0 - st) + 0.05 * st - v_cf = max(27.5 * (1.0 - st) + 35.0 * st, v_sf + 0.05) - - # 后侧:从 t=0 的平直面 (Y=92.0, v=-36.0) 渐变到 t=1 的圆弧 (Y=93.0, v=-35.0) - v_sr = -36.0 * (1.0 - st) - 0.05 * st - v_cr = min(-36.0 * (1.0 - st) - 35.0 * st, v_sr - 0.05) - - w = (cq.Workplane("XY", origin=(X_ARM, Y_AXIS, z_curr)) - .moveTo(-35.0, v_sf) - .threePointArc((0.0, v_cf), (35.0, v_sf)) - .lineTo(35.0, v_sr) - .threePointArc((0.0, v_cr), (-35.0, v_sr)) - .close()) - wp = wp.add(w) - - loft = wp.toPending().loft() - s = s.fuse(loft.val()) - - # 4. 电机中间定子散热裸露切除 (100% 裸露) - # 【2026-10-05f】原 Ycyl(30.5, ...) 定子散热切除已删:主腔统一 BAY_R=30.50 - # 且从承压面挖到前端,这刀完全落在主腔内,属同段腔被两个半径各挖一次的残留。 - # 通风由敞口盒切实现,不依赖它。 - - # 5. M4 接口成型 - # 【2026-10-03i 接缝整改】front_collar_pocket 已删,理由同 p_shoulder_roll_base: - # 它与 Joint.pocket 切的是同一根 BAY_R 圆柱,是后者的子集,两次同半径布尔 - # 相减会在腔壁上留下 s=50.0 的零宽棱(本件实测 R30.500 裂成 5168.4 + 493.9 两张面)。 - s = j4.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j4.pocket(s) - - # 5b. 【2026-10-02 新增】M4 前端压盖 4xM3x8(BCD55):M4 原来只被后端板单面锁死, - # 大扭矩下会绕 J4 轴在 Ø60.3 座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 熔接放在所有 R30.15~30.5 圆柱刀具之后,理由同 p_fork 7b。P07 的 J4 从动侧 - # 已相应后移到 Y>=162(见 p_forearm_bone 的 J4_CLEAR),与压盖前端面留 1.0mm。 - s = s.fuse(motor_front_clamp(X_ARM, Z_EL, tag="P06/M4")) - - # 5b2.【2026-10-05e】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。 - # 配方与 P04/P07 的 M2/M5 完全同源(同一套 front_collar_pocket + motor_front_clamp), - # 局部 z 50..56 的 r30.25..35 环墙切掉敞口(局部 +y = 全局 -Z)那半圈;背脊侧整圈 - # 保留,继续定位 M4 前端盖并支撑压盖 → M4 前后双向面压不变。 - s = cut_motor_fence(s, j4, "P06/M4", "P06") - - # 5c. 【2026-10-02 新增】电机上方过渡块减重窗(见 hollow_tent_window)。 - # Z360..385 这段 Loft 过渡块壁厚最深 15.8mm,是 P06 唯一一块可掏的厚料 - # (Z332..367 的下部本体已经被 Ø61 定子孔掏成 4.5mm 薄壁,没得再掏)。 - # 但它正压在 J3 毂下面,所以三条红线: - # 1) 窗顶最高只到 Z374 —— J3 的 Ø70 毂面在 Z380..388.5,窗顶留 6mm 整块 - # 实心托住毂面承压,绝不碰毂面和它后面的 3xS01 直锁螺钉; - # 2) 窗的 X 范围 3..37 —— 截面本体 X-15..55,两侧各留 18mm 竖墙, - # 该高度上零件才不会被切成两半(必须单实体); - # 3) 拱形(下宽上窄)轮廓,斜面与水平 >= 52° 自支撑,只有顶部 X12..28 的 - # 16mm 平顶需要桥接(与 P03 同量级)。 - # 窗底取 Z362 而不是更低:Z360 附近是全段安全系数最低的截面, - # 抬高窗底 4mm 比横向收窄 8mm 更划算(减重量只少 3cm3,安全系数从 92% 回到 100%)。 - # 窗沿 Y 双向切穿(Y87..172 超出本体,前后都贯通),顺带把这段的定子散热口 - # 一起打开。 - # 【2026-10-03 关闭】用户实机视点确认这个拱形窗在正面看就是一个贯通的矩形黑洞, - # 位于J4 关节正上方、整机最显眼的落脚点,视觉上像"零件破了个洞",非常不雅观。 - # 权衡后恢复实心:代价只有 161->183cm3(+22cm3 / +27g PLA),而强度本来就已经是 - # 无窗基线的 100%(这段本来就不缺强度),减重收益纯属锦上添花。 - # 保留 P06_WIN_XZ 与本段注释,需要减重时把开关改回 True 即可复原。 - if P06_HOLLOW_WIN: - s = s.cut(hollow_tent_window(P06_WIN_XZ, 80.0, 175.0, tag="P06过渡块")) - else: - print(" [减重窗] P06过渡块: 已关闭(2026-10-03 视觉整改),该段恢复实心", flush=True) - - # 6. J3 直连法兰结构(凸台延伸至 65.0 跨过结合面牢固熔接,保持单实体与零干涉) - # 凸台随 J3 毂面一起后移 CLAMP_BACKOFF=5.5(59.5→65.0):进入电机输出毂的深度 - # 仍是 s54.942..56.0 那 1.06mm,3xS01 的头面台阶(s=58)/杆长/咬合量一律不变。 - boss = cyl(17.8, 54.942, 59.5 + CLAMP_BACKOFF) - chamfer_cone = cyl(18.5, 54.5, 54.942) - s = s.fuse(j3.L(boss)).cut(j3.L(chamfer_cone)) - s = s.cut(j3.L(cyl(3.5, 54.0, 58.0 + 30.0))) - s = hub_m2_holes(s, j3, cb_z1=58.0 + 40.0) - - return s - - -def p_forearm_bone(j4, j5, half=None): - """P07_小臂骨架_螺旋体30%_墙5底5_竖打 L型高刚度无干涉版(深度圆角优化版) - 设计要点: - 1. 改为与 P05/P08 统一的 L 型结构(L-bracket): - - +Y侧垂直背脊立柱(Y: y_hub_in..y_out),避开上级大臂骨架 P06(Y <= 155.5) - - 彻底消除原全深S弯在J4轴下方向两边突出的干涉结构(两边突出的结构全部去除) - - 垂直立柱与下方 M5 电机水平座之间采用规则折角(Corner Chamfer/Gusset)刚性连接 - 2. 下方 M5 电机实行半包散热结构,底部与内腔开窗散热 - 3. J4 轴俯仰动态行程从受限的 ±54° 大幅释放至无死角大范围摆动(零干涉) - 4. 满足单实体 (solids=1) 与装配零碰撞。 - 5.【2026-10-02】M5 前端压盖(4xM3x8 BCD55)消除"电机只被后端板单面锁死"的倾斜隐患; - J4 从动侧整体让位:y_hub_in 由 158.5 后移到 J4_CLEAR=162.0,让出 P06 的 M4 前端压盖 - (前端面 Y=161.0),留 1.0mm 动间隙。J4 直连凸台同步加长,3xS01 输出螺钉咬合量不变。 - """ - y_out = Y_HUB + PLATE_T # 174.0 - # J4 从动侧让位基准:必须 >= P06 的 M4 前端压盖前端面(Y_COVER+5.0=161.0) + 1.0mm - J4_CLEAR = Y_COVER + 6.0 # 162.0 - y_hub_in = J4_CLEAR - 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) - rear_plate = Ycyl(35.0, HL.Y_REAR - 8.0, HL.Y_REAR, X_ARM, Z_WR) - s = collar_front.fuse(rear_plate) - - # 2. J4 顶部从动法兰盘 (Y: y_hub_in..y_out, R=35mm, Z: Z_EL) - disc = Ycyl(35.0, y_hub_in, y_out, X_ARM, Z_EL) - s = s.fuse(disc) - - # 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_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)) - s = s.fuse(roof_w.val()) - - # 4. L型垂直立柱臂 (+Y侧,Y: y_hub_in..y_out,从 Z_EL 顶端延伸至下方电机座) - vert_leg = (cq.Workplane("YZ", origin=(X_ARM, 0, 0)) - .moveTo(y_hub_in, z_top_housing) - .lineTo(y_out, z_top_housing) - .lineTo(y_out, Z_EL) - .lineTo(y_hub_in, Z_EL) - .close() - .extrude(35.0, both=True)) - s = s.fuse(vert_leg.val()) - - # 5. L型内侧圆弧加强过渡(Circular Arc Gusset,用户要求"改成圆弧而不是折角,消掉悬空小块") - # 彻底取代直角折角与直斜线折角,采用与顶面和立柱双相切的平滑内凹大圆弧 (R=28.0mm) - # 起点在顶板平整面 (y_arc_start, z_roof_top),精确相切至立柱内侧面 (y_hub_in, z_arc_end) - # 严控全域 Z >= z_top_housing,彻底消除侵入下方旋转副的悬空小方块与多余突起! - # 注:顶板整条加高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_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_roof_top) - .threePointArc(p_mid_arc, (y_hub_in, z_arc_end)) - .lineTo(y_out, z_arc_end) - .lineTo(y_out, z_top_housing) - .lineTo(y_arc_start, z_top_housing) - .close() - .extrude(35.0, both=True)) - s = s.fuse(gusset_w.val()) - - # 6. 下级 P08 旋转圆盘动态避让切除(R=35.5mm,预留 0.5mm 间隙) - p08_clearance = Ycyl(35.5, 156.0, y_out + 2.0, X_ARM, Z_WR) - s = s.cut(p08_clearance) - - # 6b. 【2026-10-02 新增】顶板根部减重槽(见 hollow_channel)。 - # 顶板在 Z255..280 一整段是壁厚最深 15mm 的实心料,而 J4 在 z=245 的安全系数 - # 高达 122、z=195 也有 24。挖成朝上敞开的 U 形槽,顶板变成槽钢: - # 槽底 Z255 以下保留整块实心到电机座,抗弯的两片 X 向腹板(X-15..-7 / X47..55) - # 留在弯矩拉压区。Y 上限取 156 而非核心的 166,是为了避开 y_hub_in=158.5 起的 - # 竖直立柱本体(立柱是 J4 从动法兰的承力柱,不能掏)。 - # 槽顶 Z280 就是顶板上表面,敞口向上,免支撑、非封闭腔。 - s = s.cut(hollow_channel(P07_CH["x0"], P07_CH["x1"], P07_CH["y0"], P07_CH["y1"], - P07_CH["z_floor"], 300.0, tag="P07顶板")) - - # 7. M5 电机安装接口与定子空腔 - # 【2026-10-05f 接缝整改】本件原先是全机唯一给腔径加 CLEARANCE_GAP(2.0) 的地方: - # front_collar_pocket(r_bore=32.25) + 一刀 Ycyl(32.5, Y_REAR-0.5, y_front-6)。 - # 结果内壁是 35 -> 32.5 -> 30.2 -> 32.3 -> 30.2,比兄弟件多两道台阶 - # (实测 scratch/bay_step_profile.py:z=5.5 处 32.5、z=49.5 处掉到 30.2、 - # z=50.25 又回到 32.3)。 - # 这 2.0mm 是「关节间隙」,但关节间隙是**轴向**让位(见 hub_gap / CLAMP_CLR), - # 径向把腔径撑大 2mm 只会让电机在腔里晃,且与 P05/P06/P08 不一致。 - # 现统一走 BAY_R(30.50),与兄弟件同径,接缝归零。 - # 【2026-10-03i】front_collar_pocket 随之删除:它切 cyl(BAY_R, 50.0..56.5), - # 是 Joint.pocket 那根 cyl(BAY_R, -2.6..61.6) 的子集,同半径重复相减 - # 只会在腔壁上多出一条 s=50.0 的零宽棱。 - s = j5.rear_mount_holes(s, t_plate=8.0, hole_d=2.8, cb_d=5.5, cb_depth=4.0) - s = j5.pocket(s) - # 【2026-10-05f】原 Ycyl(30.5+CLEARANCE_GAP=32.5, ...) 整刀删除: - # 它既撑大了腔径(接缝主因),又已被主腔 BAY_R 完全覆盖(纯冗余)。 - - # 半包散热设计:-Z底部电机定子通风口 - vent_cut = bx(X_ARM - 40.0, X_ARM + 40.0, y_rear + 8.0, y_front - 6.0, Z_WR - 40.0, Z_WR - 25.0) - s = s.cut(vent_cut) - - # 7b. 【2026-10-02 新增】M5 前端压盖 4xM3x8(BCD55):M5 原来只被后端板单面锁死, - # 大扭矩下会绕 J5 轴在座孔里点头。现在前端也面压夹一次(见 motor_front_clamp)。 - # 必须在第 6 步 p08_clearance(R35.5 让位孔,Y>=156)之后熔接,否则压盖会被削掉。 - # P08 的 J5 从动侧已相应后移到 Y>=162(make_pitch_to_roll_bracket 的 hub_gap=6.0)。 - s = s.fuse(motor_front_clamp(X_ARM, Z_WR, tag="P07/M5")) - - # 7c2.【2026-10-05e】删掉前端"围挡"在敞口侧的那半圈环墙(见 FENCE_OPEN_CUT)。 - # 与 P04/P06 同源。注意本件的 front_collar_pocket 用了 r_bore=30.25+CLEARANCE_GAP - # =32.25(2.0mm 关节间隙),环壁因此更薄(r32.25..35),切法与配方完全一致。 - s = cut_motor_fence(s, j5, "P07/M5", "P07") - - # 8. J4 直连法兰结构(凸台延伸到法兰盘背面之后 2.0mm 跨过间隙与法兰盘牢固熔接, - # 保持单实体)。凸台长度跟着 y_hub_in 走:法兰盘后移时凸台同步加长, - # 3xS01 输出螺钉的头面台阶(局部 z=58)、杆长、咬合量与改前完全一致。 - boss = cyl(17.8, 54.942, (y_hub_in - HL.Y_REAR) + 2.0) - s = s.fuse(j4.L(boss)) - s = s.cut(j4.L(cyl(3.5, 54.0, 74.0 + 30.0))) - s = hub_m2_holes(s, j4, cb_z1=74.0 + 30.0) - - # 9. 工艺走线通孔 - slot_z = (Z_EL + z_top_housing) / 2.0 - slot = Ycyl(7.0, y_hub_in - 2.0, y_out + 2.0, X_ARM, slot_z) - s = s.cut(slot) - - # ========== 关键圆角优化 ========== - try: - # 后侧顶板外边缘圆角(平滑消除水平顶板与后部垂直端面交汇处的 90° 尖角,用户要求倒角改圆角) - wp_s = cq.Workplane(obj=s) - sel_rear = cq.selectors.BoxSelector( - (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).fillet(FILLET_SMALL).val() - except Exception as e: - print(f"P07 顶板后边缘圆角异常: {e}") - - return s - - -def p_wrist_bracket(j5, j6): - """P08_腕偏转座_螺旋体25%_墙4底5_竖打(深度圆角优化版) - - hub_gap=6.0:J5 处 P07 新增了 M5 前端压盖(Y 155.5..161.0),从动法兰盘背面必须 - 从 Y=157 后移到 Y=162 让位;J5 直锁凸台同步加长,3xS01 输出螺钉咬合量不变。 - - roll_clamp=True【2026-10-03】:M6 补上前端压盖,与 P04/P05/P06/P07 的 - M2/M3/M4/M5 同一套配方(R35、4xM3x8 BCD55 @17.8°+90k、Ø6.0 沉孔、Ø61.0 根部 - 让位槽),轴向 -Z,压盖占全局 Z 120.5..115。此前本件是五处电机腔里**唯一** - 只被后端板 4xM3x10 单面锁死的,电机大扭矩下会在 Ø60.3 座孔里点头;对侧 - P09 让位 CLAMP_BACKOFF=5.5(见 make_direct_hub 的 backoff),工具端点不动。 - """ - return make_pitch_to_roll_bracket(j5, j6, Z_WR, Z_R6, - name="P08_腕偏转座_螺旋体25%_墙4底5_竖打", - hub_gap=6.0, roll_clamp=True) - - -def p_tool_flange(j6): - """P09_工具法兰_螺旋体30%_墙4底5_竖打 深度圆角优化版 - 关键改进: - 1. 外轮廓全圆角(R=4mm) - 2. 法兰面倒角(2mm) - 3. 螺孔边缘倒角(1.5mm) - """ - z_bot = Z_HUB6 - PLATE_T - 9.0 - s = cyl(35.0, z_bot, Z_HUB6, X_ARM, Y_AXIS) - P09_M4 = [] - - # 4x M4螺栓孔 - # - # 分布圆半径 20 -> 22(BCD 40 -> 44):R20 与 3xS01 输出螺钉的 Ø5.6 螺丝刀通道 - # (make_direct_hub 里 cyl(2.8, 58.0, z_out+40) 在 BCD29=R14.5、相位 351.4/232.3/112.1°) - # 咬穿沉孔。0° 那个孔中心 (39.5,128) 到 351.4° 通道中心 (33.84,125.83) 只有 6.06mm, - # 而两孔半径之和是 3.5+2.8=6.30 —— 差 0.24mm,Ø7.0 沉孔壁被啃掉一块, - # 完整圆柱面(88mm²)裂成 45.7+35.4 两片,即用户截图里的月牙孔。 - # 该相位差只有 8.6°,转动 M4 孔相位无法躲开(0/90/180/270 与 45 度错开全部仍 <6.30), - # 只能加大分布圆半径:R22 时最小中心距 7.96mm,余量 1.66mm。 - # 边壁仍充足:R35 法兰外径 - (R22 + 沉孔 3.5) = 9.5mm。 - for a in (0, 90, 180, 270): - rad = 22.0 - u = rad * math.cos(a * D2R) - v = rad * math.sin(a * D2R) - # 实际切割推迟到 make_direct_hub 之后(见下)。这里只算出 4 个孔位。 - P09_M4.append((X_ARM + u, Y_AXIS + v)) - - # 定位止口 + 走线孔 - pilot = cyl(10.0, z_bot - 1.0, z_bot + 2.5, X_ARM, Y_AXIS) - thru = cyl(5.0, z_bot - 1.0, Z_HUB6 + 1.0, X_ARM, Y_AXIS) - s = s.cut(pilot).cut(thru) - - # 直连法兰 - # 【2026-10-03】M6 补上前端压盖后,J6 毂面必须让位 CLAMP_BACKOFF=5.5 - # (详见 make_direct_hub 的 backoff)。P09 的法兰本体 z_bot/Z_HUB6 一个字没动, - # 让位的只是伸进 J6 腔里的那圈毂面 —— 圆盘下移后仍与法兰本体在局部 65..79.5 - # 大面积交叠,熔接不受影响;工具端点 Z_TIP=93 也随之不变。 - s = make_direct_hub(s, j6, r_disc=35.0, z_out=74.0, backoff=CLAMP_BACKOFF) - - # 4x M4 孔(必须在 make_direct_hub 之后切) - # - # 直连法兰那步 fuse 了 R35 圆盘(局部 z=56.5..74 -> 全局 z=102..121 一侧), - # 会把先切的孔重新填死:分段求交显示 z=97..102 无干涉、z=102 以上整根杆都埋在料里 - # (每段 12.57 mm^3 = pi*2^2*1,全截面被埋)。 - # - # 孔径与深度同时修正:旧值 Ø3.7 比 M4 杆 Ø4.0 还细,且只到 z=105, - # 而 M4x16 头面在 z=97、杆要走到 z=113 —— 杆尾 8mm 无处可去(干涉 147.3 mm^3)。 - # Ø4.4 是 M4 标准间隙孔(ISO 273 medium ≈ Ø4.3)。 - # 孔顶要盖过 M4x16 的杆尾:头面 z=97 + 杆长 16 = z=113,故开到 Z_HUB6+4=115, - # 比杆尾多留 2mm(实测开到 112 时最后 1mm 杆尾仍撞料 37.7mm³)。 - for (fx, fy) in P09_M4: - s = s.cut(cyl(2.2, z_bot - 1.0, Z_HUB6 + 4.0, fx, fy)) - s = s.cut(cyl(3.5, z_bot - 1.0, z_bot + 4.0, fx, fy)) - - # ========== 关键圆角优化 ========== - try: - # 外轮廓圆角 - s = s.edges("|Z").fillet(FILLET_SMALL) - except: - pass - - try: - # 法兰面倒角 - s = s.edges("Z").fillet(FILLET_TINY) - except: - pass - - return s - - -# ---------------------------------------------------------------- assembly ---------------- -def build(): - print("=" * 80, flush=True) - print("【第一阶段 1/5】开始生成手臂各级构件 3D 几何实体模型...", flush=True) - print("=" * 80, flush=True) - t_stage1 = time.time() - - print(" [0/9] 载入电机标准件模组 (STEP)...", end="", flush=True) - t0 = time.time() - motor = cq.importers.importStep(AB.SRC_STEP).val() - motor = place(motor, T(0, 0, -2.57)) - AB.MOTOR, AB.HALF, AB.CLAMP = motor, None, None - print(f" 完成 ({time.time()-t0:.2f}s) ✓", flush=True) - - JS = {} - for tag, cn, rear, d, xd, holder, driven in HL.JOINTS: - JS[tag] = Joint(tag, cn, rear, d, xd, lug_phase=90.0 if tag == "J1" else 0.0) - j1, j2, j3, j4, j5, j6 = (JS["J%d" % k] for k in range(1, 7)) - - PARTS[:] = [] - AB.CENTS.clear() - AB.GRP = {} - par = {"J1": "root", "J2": "J1", "J3": "J2", "J4": "J3", "J5": "J4", "J6": "J5"} - driven = {"J1": "P04_肩叉_螺旋体30%_墙5底5_竖打", "J2": "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "J3": "P06_大臂骨架_螺旋体25%_墙4底5_竖打", - "J4": "P07_小臂骨架_螺旋体30%_墙5底5_竖打", "J5": "P08_腕偏转座_螺旋体25%_墙4底5_竖打", "J6": "P09_工具法兰_螺旋体30%_墙4底5_竖打"} - motor_of = {"J1": "M1_J1", "J2": "M2_J2", "J3": "M3_J3", - "J4": "M4_J4", "J5": "M5_J5", "J6": "M6_J6"} - for t in ("J1", "J2", "J3", "J4", "J5", "J6"): - AB.GRP[motor_of[t]] = par[t] - AB.GRP[driven[t]] = t - AB.GRP.update({ - "P01_A_立柱下段_三角形30%_墙4底5_竖打": "root", "P01_B_立柱中段_三角形30%_墙4底5_竖打": "root", "P01_C_立柱上段_三角形30%_墙4底5_竖打": "root", - "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": "root", "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": "root", "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": "root", - "P03_肩吊臂_螺旋体35%_墙5底5_竖打": "root" - }) - AB.JAW = {} - - p01_full = p_chest() - p01_a, p01_b, p01_c = split_p01_poka_yoke(p01_full) - p02_c, p02_f, p02_r = make_p02_poka_yoke() - - parts_to_build = [ - ("P01_A_立柱下段_三角形30%_墙4底5_竖打", "立柱下段", lambda: p01_a, 0x0284c7, - "200mm适配分段A:高166mm+16mm阶梯公榫+顶端 2x Ø3.9 对锁孔(M4x16 自攻)+底部50x50x12榫头+P01-A 竖排TOP刻字"), - ("P01_B_立柱中段_三角形30%_墙4底5_竖打", "立柱中段", lambda: p01_b, 0x2563eb, - "200mm适配分段B:高165mm+双端公母榫+两端各 2x Ø3.9 对锁孔(M4x16 自攻,非对称孔位防反装)+P01-B 竖排TOP刻字"), - ("P01_C_立柱上段_三角形30%_墙4底5_竖打", "立柱上段", lambda: p01_c, 0x1d4ed8, - "200mm适配分段C:高149mm+底端母榫+底端 2x Ø3.9 对锁孔(非对称孔位防反装)+顶面P03安装台(4x Ø3.9 安装孔 + 1x Ø3.9 横向锁紧孔)+P01-C 竖排TOP刻字"), - ("P02_Center_底座中核心_螺旋体30%_墙5底6_平打", "底座核心承力座", lambda: p02_c, 0x475569, - "200mm适配中座:195x190mm核心承力座+48mm套筒+12mm阶梯榫座(轴向面承载)+前后三角加强筋+前后非对称防呆母榫槽+刻字"), - ("P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打", "前向防翻力臂", lambda: p02_f, 0xe11d48, - "200mm适配前臂:145x190mm扩展力臂+104mm宽跨距双公榫+底部M5预埋螺母沉槽+P02-FRONT刻字"), - ("P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打", "后向防翻力臂", lambda: p02_r, 0xd97706, - "200mm适配后臂:45x190mm扩展力臂+80mm窄跨距双公榫+底部M5预埋螺母沉槽+P02-REAR刻字"), - ("P03_肩吊臂_螺旋体35%_墙5底5_竖打", "肩关节安装悬臂", lambda: p_girdle(j1), 0x4b5563, - "轻量化板式桁架版:主干由 66mm 实心块改为双侧 6mm 桁架竖板+帐篷减重窗(斜面≥52°自支撑)+R4内角,外轮廓与旧版并集完全一致,实心 728->286cm³;底部 9mm 承力鞍座(Ø3.9 安装孔)+12mm 中位龙骨筋(自鞍座斜拉至法兰下缘)+40~45°外扩斜撑;保留 10mm 后置法兰、侧开式电机托架、Ø60.7 直通电机舱(平孔无台阶)(M1 从+Y滑入,后端 4xM3x16 锁死)、大散热窗与对流槽;无封闭空腔、免支撑"), - ("P04_肩叉_螺旋体30%_墙5底5_竖打", "肩叉", lambda: p_fork(j1, j2), 0x2563eb, - "齐平插槽+ M2 前端压盖+ 立柱减重槽版:叉板背面与P03托架端面(11.5)0.0硬贴平(用户要求外观齐平),P03侧插槽独立加长3mm,M1电机可倾斜卡入,J1输出凸台贯通直连电机,J1从动法兰圆盘鼓楞已删(接缝区与P03止脱环齐径齐高R36.5/顶646.5),单实体全角度零干涉;【2026-10-02】M2 前端压盖(R35, Y155.5..161.0):电机前端盖外端面加 4xM3x8 沉孔(BCD55)夹紧,与后端安装板 4xM3x10 形成前后双向面压,消除大扭矩下电机在座孔里的倾斜;【2026-10-02】立柱减重槽(X23~35 / Y102~154 / 自Z525向上敞开):立柱Z525~635整段壁厚峰值17.5mm,J2在z=560/600的安全系数有19,改成U形槽钢后361->289cm3(-20%),抗弯的两片X向腹板正好落在弯矩拉压区(z=560截面X13~45、中性面X29,腹板占X13~23/X35~45),掏掉的正是中性层附近几乎不参与抗弯的料;槽底Z525以下保留整块实心过渡到电机座,槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X23~35 比最初的 X21~37 收窄 2mm,强度从 88% 回到 95%);【2026-10-04 视觉整改】电机上方那块 4048mm2 的大平面改成 Hermite 弧面(全行程重扫 907s 确认 J1~J6 行程逐项不变):原来 yz_pts 里(164.5,646.5)->(155.5,526) 是一条直线,沿 X 拉成 4048mm2 平板(法向 (0,1,-0.07)、带 4° 斜度),正对电机上方,两侧被 xz_wire 的 R18 圆弧挤出去,形成「中间一块平、两边鼓」的观感。改成三次 Hermite(两端切线分别沿 Y/Z,与上下水平段 G1 连续),中部向 +Y 外凸约 3mm,弧度与两侧 R18 一致;单段圆弧无法同时满足两端切线(解出 R=9 与 R=120.5 矛盾)故用 Hermite。P04 体积 282.1->274.3cm³(弧面替代平面削掉的料);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P05_大臂旋转座_螺旋体30%_墙4底5_竖打", "大臂旋转座", lambda: p_shoulder_roll_base(j2, j3, None), 0x059669, - "V3圆角+ J2前端夹紧配合:Loft外轮廓R=10mm,后缘凸包两角90°竖棱磨R2圆角(结构/后缘高度不变,打印无悬挂);【2026-10-07】L型内转角R1.5精细圆角(将圆角半径精调至1.5mm,完全杜绝算法异常与外凸台阶,平滑过渡并消除直角应力集中,与螺钉沉孔/运动间隙保持充裕余量);J2 从动法兰盘背面由 Y=157 后移至 Y=162(hub_gap=6.0,给 P04 新增的 M2 前端压盖让位,J2 直锁凸台同步加长到 Y=165.5,3xS01 输出螺钉咬合量不变);保证 J2 全角度 0 干涉;【2026-10-05d 只切敞口那一圈围挡】用户指出 M3 电机前端(另一头)也有一圈围挡:Z389.0~395.5 的 r30.25~35 前端定位环(6mm 高环墙) + 骑在它上面的 R35 压盖外圈(Ø70 比电机 Ø63.6 凸 3.2mm),把 M3 前端盖连同穿过 Ø37 孔的 P06 J3 凸台围在中间。第一版把整个前端(定位环+压盖+腔口下沿 389->395)都切了属过度(电机前端失去支撑面、M3 退回单面锁死),已回退;现只切敞口(-Y)那一圈(盒切,Y 界限与 cut_open 一致),-Y 半圈从 Z=395.5 一路开到压盖背面 Z=389,背脊(+Y)侧整圈保留作前端盖定位与压盖唯一且足够强的支撑面(4.75mm 宽 x 0.5mm 熔接带);压盖本体/Ø37 孔/4xM3x8 沉孔/腔口下沿 389.0 全部不动,P05 只少 3.2cm3(154.8->151.6),仍单实体。不整圈削到 R31.8 的原因:那圈壁与 Loft 在 X=±35 处与 Ø70 柱面相切,整圈切完会脱空出 1.32mm3 碎块破坏单实体;且压盖根部熔接面会从 2.37mm2 缩到 0.78mm2,4xM3x8 夹紧力会把压盖拉下来。开关 FENCE_OPEN_CUT(2026-10-05e 起为全机 5 处共用)。【2026-10-03】M3 前端压盖(R35, Z384.0..389.0):J3 roll 电机原来也只有后端板 4xM3x10 单面锁死(S02),大扭矩下绕 J3 轴在 Ø60.3 座孔里点头;现按 P04/P06/P07 同一配方在 M3 前端盖外端面(Z=389)加 4xM3x8(BCD55)沉孔压盖,与后端板前后双向面压,倾斜自由度归零(配方唯一差别是 roll 电机轴向 -Z,压盖朝 -Z 长,见 motor_front_clamp axis=\"Z\");代价是对侧 P06 的 J3 毂面整体后移 5.5mm 让位;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P06_大臂骨架_螺旋体25%_墙4底5_竖打", "大臂骨架", lambda: p_humerus_bone(j3, j4), 0x0d9488, - "Loft有机过渡版:借鉴P05圆弧变方形放样算法,顶端Ø70正圆与J3平齐无台阶,底端渐变融入M4抱紧套筒与壳体;【2026-10-03】取消电机上方Loft过渡块(Z360~385)的拱形减重窗,恢复实心:该窗沿Y贯通,正面看是整机最显眼处的矩形黑洞(用户视觉整改),代价仅 +22cm3/+27g,强度仍为无窗基线100%;【2026-10-03】J3 毂面整体后移 5.5mm(顶端 388.5→383.0,直锁凸台 59.5→65.0):给 P05 新增的 M3 前端压盖(Z384..389)让位,留 1.0mm 动间隙——与 Y 轴三处(J2/J4/J5 令从动法兰盘 Y157→162)完全同源,换算式同为「压盖 5.0 + 动间隙 1.0 - 原基准 0.5」;凸台同步加长,3xS01 头面台阶(s=58)/杆长/咬合量与 J3 直锁关系一律不变;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P07_小臂骨架_螺旋体30%_墙5底5_竖打", "小臂骨架", lambda: p_forearm_bone(j4, j5, None), 0x7c3aed, - "V2 L型结构 + 顶板减重槽:+Y侧背脊立柱避让大臂骨架,过渡区采用与顶板和立柱双相切的R=28mm平滑内凹大圆弧,彻底消除直斜线折角与悬空阶梯小块,顶板前后外缘2mm倒角改R3圆角,顶板整条加高19mm(z261->280)加强连接且gusset弧R28->R14重定相切融入,J4圆盘外沿与立柱外棱不倒圆保持原始尖角,下方M5电机半包散热,俯仰大范围自由运动零干涉;【2026-10-02】顶板根部Z255~280开朝上敞开的减重槽(X-3~43 / Y103~153, Y上限取153避开y_hub_in=158.5起的竖直立柱本体):顶板由实心变槽钢,288->230cm3(-20%),槽底以下保留整块实心到电机座,抗弯靠两侧各10mm的X向腹板;槽底水平朝上、侧壁竖直、顶部敞开故零支撑且非封闭腔;开度按「减重区最差安全系数 >= 无槽基线 95%」选定(X-3~43 比最初的 X-7~47 收窄 4mm,强度从 88% 回到 96%);【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P08_腕偏转座_螺旋体25%_墙4底5_竖打", "腕部转接座", lambda: p_wrist_bracket(j5, j6), 0xd97706, - "V2圆角:Loft外轮廓R=10mm,L型转角内部2mm倒角,彻底清除立壁保证J5全角度0干涉;【2026-10-05 承压面让位腔改为 R35 圆腔、凸台与围挡一并消除】电机尾端垫片位(REAR_SEAT=2.6)用圆腔掏出承压面。**刀半径必须等于外壁 R35**(arm_build.SEAT_R):取小于 R35(曾试 32.2 = 64.4 方腔内切圆)时,刀半径与外壁之间那圈 2.8mm 环墙完整保留,而承压面只挖到刀半径,于是该环墙相对承压面【凸起】并把电机尾端面围住 —— 这就是用户截图里的一圈围挡(P08 实测 1536.9mm3)。它不是残留,而是 SEAT_R+1.0 杯体连接,无整圈通槽)。沉孔 z 区间(-8..-4)与圆腔(-2.6..+1.0)不重叠,S02 完好。连带:承压环由「方腔内切 32.2 − 沉孔外缘 30.25 = 1.95mm 窄环」变为「完整 R35 圆环 4.75mm」,受压面积 383->974mm²(2.5 倍)。【2026-10-04 承压面让位腔改为 32.2 圆腔】电机尾端垫片位(REAR_SEAT=2.6)之前用 64.4 **方**腔掏出来,方腔壁以外 z=-2.6..0 那圈料相对新承压面凸起 2.6mm,打印时是悬空薄壁(用户反馈:难打印、需支撑、公差差)。现承压面段改用**SEAT_R=32.2 圆腔**(= 64.4 方腔的内切圆半径,z=-2.6..0 与方腔平接)把整圈挖到承压面高度,凸台零残留;z=0 以上 PCBA 腔仍保持 64.4 方(PCB 是方的必须方腔)。**关键:刀半径必须取 32.2 而不是 R35** —— 中间试过 R35,结果把 r=32.2..35 的连接壁整圈切断:实测 P04 在 z=-2.6..+1.0 只剩 240~320° 方位有料,其余 360° 全掏空,后端法兰与杯壁之间出现 3.6mm 宽通槽 + 225° 一片 0.5mm 孤立薄片(用户截图:肩叉顶部「没连上」)。取 32.2 后 r=32.2..35 这圈 2.8mm 厚壁完整保留,360° 连续,既无凸台又与上方臂身连成一体(实测 5/5 件连接壁整圈连续、单实体)"), - ("P09_工具法兰_螺旋体30%_墙4底5_竖打", "末端工具法兰", lambda: p_tool_flange(j6), 0xdc2626, - "V2圆角:外轮廓R=3mm,法兰面倒角2mm;【2026-10-02】打印填充由100%降到30%:该截面安全系数288是全机裕度最大的件,满填充纯属浪费(件号同步改为P09_工具法兰_螺旋体30%)"), - ] - - total_parts = len(parts_to_build) - for idx, (name, cn, fn, color, note) in enumerate(parts_to_build, 1): - print(f" [{idx}/{total_parts}] 正在构建 {name} ({cn})...", end="", flush=True) - t0 = time.time() - sh = fn() - dt = time.time() - t0 - vol = AB.vol_of(sh) / 1000.0 - n_solids = len(solids_of(sh)) - print(f" 完成 ✓ ({dt:.2f}s | 体积: {vol:.1f} cm³ | 实体数: {n_solids})", flush=True) - add(name, cn, sh, color, note=note) - - print(" [装配] 正在将 M1~M6 电机定位至各关节坐标位姿...", end="", flush=True) - t0 = time.time() - for k, (tag, cn, rear, d, xd, holder, driven_name) in enumerate(HL.JOINTS): - j = JS[tag] - add("M%d_%s" % (k + 1, tag), "%s 电机模组" % tag, place(motor, j.t), 0x9ca3af, - printed=False, note="复用 电机.STEP") - print(f" 完成 ✓ ({time.time()-t0:.2f}s)", flush=True) - print(f"-> 构件模型全部生成完成,总耗时: {time.time()-t_stage1:.2f}s\n", flush=True) - return JS - - -CHAIN = ["root", "J1", "J2", "J3", "J4", "J5", "J6"] -SEP = 74.0 - - -def explode_chain(p, JS, cents, gmean): - """爆炸图偏移:立柱垂直阶梯拉开,底座前后防呆拉开,电机沿轴拉出,机械臂整体随上段分离""" - nm = p["name"] - if nm == "P01_A_立柱下段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 0.0] - if nm == "P01_B_立柱中段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 60.0] - if nm == "P01_C_立柱上段_三角形30%_墙4底5_竖打": - return [0.0, 0.0, 120.0] - if nm == "P02_Center_底座中核心_螺旋体30%_墙5底6_平打": - return [0.0, 0.0, 0.0] - if nm == "P02_Front_前向防翻力臂_螺旋体30%_墙4底5_平打": - return [65.0, 0.0, 0.0] - if nm == "P02_Rear_后向防翻力臂_螺旋体30%_墙4底5_平打": - return [-65.0, 0.0, 0.0] - if nm == "P03_肩吊臂_螺旋体35%_墙5底5_竖打": - return [0.0, 0.0, 120.0] - - grp = AB.GRP.get(p["name"], "root") - if grp == "root": - return [0.0, 0.0, 0.0] - k = CHAIN.index(grp) - off = [0.0, 0.0, 120.0 - SEP * k] - j = JS.get(grp) - if j: - w = 0.0 - if "半联轴器" in nm or "抱箍" in nm: - w = 34.0 * (1.0 if ("A" in nm[-1:]) else -1.0) - elif nm.startswith("M"): - w = 62.0 - for q in range(3): - off[q] += j.d[q] * w - return off - - -if __name__ == "__main__": - JS = build() - - print("=" * 80, flush=True) - print("【单实体拓扑完整性校验】验证所有打印构件流形实体状态...", flush=True) - print("=" * 80, flush=True) - bad = 0 - printed_list = [p for p in PARTS if p["printed"]] - total_pr = len(printed_list) - for idx, p in enumerate(printed_list, 1): - n = len(solids_of(p["shape"])) - if n != 1: - bad += 1 - status = "单实体 ✓" if n == 1 else f"异常 ({n} 个实体) ❌" - print(f" [{idx}/{total_pr}] {p['name']:<14} : 体积 {p['vol']/1000.0:6.1f} cm³ | {status}", flush=True) - if bad == 0: - print(f"-> 单实体检查全部通过!{total_pr}/{total_pr} 构件均为完美单一流形拓扑 ✓\n", flush=True) - else: - print(f"-> 发现 {bad} 个非单实体构件!\n", flush=True) - - AB.explode_vec = explode_chain - bom = AB.export(JS) - - # 紧固件(螺钉)实例注入:把 docs/screws.json 的 72 颗螺钉按真实孔位还原成三维实例, - # 追加进 web/mesh_arm.json 的 parts(查看器可点 BOM 螺丝行高亮定位;纯数据,不动 CAD) - try: - import screw_mesh - screw_mesh.main() - except Exception as e: - print("螺钉实例注入失败(不影响主流程,可手动跑 src/screw_mesh.py):", e, flush=True) - - cl = AB.collide() - - try: - from build_viewer import rebuild_viewer - rebuild_viewer() - except Exception as e: - print("rebuild_viewer error:", e, flush=True) - - # 可达包络自动注入:AB.export 刚重写过 mesh_arm.json(包络件会被冲掉), - # 这里重跑 FK 采样 + 包络网格注入,再重打包一次查看器(约 3~4s,纯数据无 CAD 计算) - try: - sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), - "..", "scratch")) - import ws_sweep - import gen_env_mesh - t_env = time.time() - ws_sweep.main() - gen_env_mesh.main() - from build_viewer import rebuild_viewer as _rbv - _rbv() - print(f"-> 可达包络已自动注入查看器(+{time.time()-t_env:.1f}s)✓\n", flush=True) - except Exception as e: - print("可达包络自动注入失败(不影响主流程,修复后手动跑 scratch/gen_env_mesh.py + src/build_viewer.py):", - e, flush=True) - - print("=" * 80, flush=True) - print("✅ 机器人手臂编译、导出与查看器发布全流程完成!", flush=True) - print("=" * 80, flush=True) - sys.stdout.flush() - os._exit(0) diff --git a/.bak/travel.json.bak20261005c b/.bak/travel.json.bak20261005c deleted file mode 100644 index 02abeee..0000000 --- a/.bak/travel.json.bak20261005c +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "axis": "J1", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J2", - "lo": -105.0, - "hi": 9.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +9° / 反向 -105°,总行程 114°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J3", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J4", - "lo": -111.0, - "hi": 16.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +16° / 反向 -111°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J5", - "lo": -93.0, - "hi": 34.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +34° / 反向 -93°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J6", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - } -] \ No newline at end of file diff --git a/.bak/travel.json.bak20261005e b/.bak/travel.json.bak20261005e deleted file mode 100644 index 02abeee..0000000 --- a/.bak/travel.json.bak20261005e +++ /dev/null @@ -1,50 +0,0 @@ -[ - { - "axis": "J1", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J2", - "lo": -105.0, - "hi": 9.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +9° / 反向 -105°,总行程 114°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J3", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J4", - "lo": -111.0, - "hi": 16.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +16° / 反向 -111°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J5", - "lo": -93.0, - "hi": 34.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +34° / 反向 -93°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J6", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - } -] \ No newline at end of file diff --git a/.bak/travel_detail.json.bak20261005c b/.bak/travel_detail.json.bak20261005c deleted file mode 100644 index 52f4ab4..0000000 --- a/.bak/travel_detail.json.bak20261005c +++ /dev/null @@ -1,1298 +0,0 @@ -{ - "generated": "2026-10-03 21:54:33", - "vol_min": 2.0, - "step": 2.0, - "combo_step": 6.0, - "workers": 12, - "fixture_included": true, - "singles": { - "J1": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - }, - "J2": { - "lo": -106.25, - "hi": 10.38, - "jams": { - "12.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P08_腕偏转座_螺旋体25%_墙4底5_竖打", - 2557.1, - [ - -52.9, - 137.7, - 149.2 - ] - ] - ], - "-108.0": [ - [ - "P04_肩叉_螺旋体30%_墙5底5_竖打", - "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - 175.1, - [ - 46.8, - 141.0, - 532.9 - ] - ] - ] - } - }, - "J3": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - }, - "J4": { - "lo": -112.62, - "hi": 17.88, - "jams": { - "18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ], - "-114.0": [ - [ - "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - "P07_小臂骨架_螺旋体30%_墙5底5_竖打", - 30.3, - [ - 53.6, - 128.0, - 384.9 - ] - ] - ] - } - }, - "J5": { - "lo": -94.75, - "hi": 35.75, - "jams": { - "36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.7 - ] - ] - ], - "-96.0": [ - [ - "P07_小臂骨架_螺旋体30%_墙5底5_竖打", - "P08_腕偏转座_螺旋体25%_墙4底5_竖打", - 144.8, - [ - 55.5, - 139.7, - 257.0 - ] - ] - ] - } - }, - "J6": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - } - }, - "combos": [ - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 10.375, - "new": 5.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "lo", - "old": -106.25, - "new": -103.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 17.875, - "new": 11.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "lo", - "old": -112.625, - "new": -109.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 35.75, - "new": 29.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "lo", - "old": -94.75, - "new": -91.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J2", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J3", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J4", - "direction": "hi", - "old": 11.0, - "new": 5.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J5", - "direction": "hi", - "old": 29.0, - "new": 23.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J6", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J1", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J3", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J4", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J5", - "direction": "hi", - "old": 23.0, - "new": 17.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J6", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J4", - "direction": "lo", - "old": -109.0, - "new": -18.625, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 17.0, - "new": 11.0, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.6 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J5", - "direction": "lo", - "old": -91.0, - "new": -36.625, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.6 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 167.0, - "new": 161.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J1", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 186.7, - [ - -83.0, - 104.7, - 120.8 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J4", - "direction": "lo", - "old": -18.625, - "new": -17.875, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 186.7, - [ - -83.0, - 104.7, - 120.8 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J5", - "direction": "hi", - "old": 11.0, - "new": 5.0, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 2.5, - [ - -82.0, - 108.0, - 138.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J6", - "direction": "hi", - "old": 161.0, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J1", - "direction": "hi", - "old": 167.0, - "new": 161.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J3", - "direction": "hi", - "old": 167.0, - "new": 157.625, - "jams": { - "162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 104.8, - 117.4 - ] - ] - ], - "-162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 151.2, - 117.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J3", - "direction": "lo", - "old": -180.0, - "new": -159.625, - "jams": { - "162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 104.8, - 117.4 - ] - ] - ], - "-162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 151.2, - 117.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J5", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J6", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J1", - "direction": "hi", - "old": 161.0, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "hi", - "old": 157.625, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "lo", - "old": -159.625, - "new": -157.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "lo", - "old": -17.875, - "new": -13.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J6", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J1", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J3", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J3", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J6", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J1", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J3", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J6", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J1", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J6", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J1", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J6", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J1", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J3", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J6", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J1", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J6", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J1", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J3", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - } - ], - "final": [ - { - "axis": "J1", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J2", - "lo": -105.0, - "hi": 9.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +9° / 反向 -105°,总行程 114°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J3", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J4", - "lo": -111.0, - "hi": 16.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +16° / 反向 -111°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J5", - "lo": -93.0, - "hi": 34.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +34° / 反向 -93°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J6", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - } - ] -} \ No newline at end of file diff --git a/.bak/travel_detail.json.bak20261005e b/.bak/travel_detail.json.bak20261005e deleted file mode 100644 index f6757c8..0000000 --- a/.bak/travel_detail.json.bak20261005e +++ /dev/null @@ -1,1298 +0,0 @@ -{ - "generated": "2026-10-04 01:04:09", - "vol_min": 2.0, - "step": 2.0, - "combo_step": 6.0, - "workers": 12, - "fixture_included": true, - "singles": { - "J1": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - }, - "J2": { - "lo": -106.25, - "hi": 10.38, - "jams": { - "12.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P08_腕偏转座_螺旋体25%_墙4底5_竖打", - 2557.1, - [ - -52.9, - 137.7, - 149.2 - ] - ] - ], - "-108.0": [ - [ - "P04_肩叉_螺旋体30%_墙5底5_竖打", - "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - 175.1, - [ - 46.8, - 141.0, - 532.9 - ] - ] - ] - } - }, - "J3": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - }, - "J4": { - "lo": -112.62, - "hi": 17.88, - "jams": { - "18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ], - "-114.0": [ - [ - "P05_大臂旋转座_螺旋体30%_墙4底5_竖打", - "P07_小臂骨架_螺旋体30%_墙5底5_竖打", - 30.3, - [ - 53.6, - 128.0, - 384.9 - ] - ] - ] - } - }, - "J5": { - "lo": -94.75, - "hi": 35.75, - "jams": { - "36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.7 - ] - ] - ], - "-96.0": [ - [ - "P07_小臂骨架_螺旋体30%_墙5底5_竖打", - "P08_腕偏转座_螺旋体25%_墙4底5_竖打", - 144.8, - [ - 55.5, - 139.7, - 257.0 - ] - ] - ] - } - }, - "J6": { - "lo": -180.0, - "hi": 180.0, - "jams": {} - } - }, - "combos": [ - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 10.375, - "new": 5.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "lo", - "old": -106.25, - "new": -103.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 17.875, - "new": 11.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "lo", - "old": -112.625, - "new": -109.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 35.75, - "new": 29.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "lo", - "old": -94.75, - "new": -91.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J2", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J3", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J4", - "direction": "hi", - "old": 11.0, - "new": 5.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J5", - "direction": "hi", - "old": 29.0, - "new": 23.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J1", - "hold_at": 180.0, - "swept": "J6", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J1", - "direction": "hi", - "old": 180.0, - "new": 179.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J3", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J4", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J5", - "direction": "hi", - "old": 23.0, - "new": 17.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J2", - "hold_at": -103.0, - "swept": "J6", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 179.0, - "new": 173.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J4", - "direction": "lo", - "old": -109.0, - "new": -18.625, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 6.2, - [ - -82.0, - 128.0, - 116.1 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 17.0, - "new": 11.0, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.6 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J5", - "direction": "lo", - "old": -91.0, - "new": -36.625, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 11.4, - [ - -82.2, - 128.0, - 140.6 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 167.0, - "new": 161.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J1", - "direction": "hi", - "old": 173.0, - "new": 167.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 186.7, - [ - -83.0, - 104.7, - 120.8 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J4", - "direction": "lo", - "old": -18.625, - "new": -17.875, - "jams": { - "-18.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 186.7, - [ - -83.0, - 104.7, - 120.8 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J5", - "direction": "hi", - "old": 11.0, - "new": 5.0, - "jams": { - "-36.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 2.5, - [ - -82.0, - 108.0, - 138.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J3", - "hold_at": 167.0, - "swept": "J6", - "direction": "hi", - "old": 161.0, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J1", - "direction": "hi", - "old": 167.0, - "new": 161.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J3", - "direction": "hi", - "old": 167.0, - "new": 157.625, - "jams": { - "162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 104.8, - 117.4 - ] - ] - ], - "-162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 151.2, - 117.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J3", - "direction": "lo", - "old": -180.0, - "new": -159.625, - "jams": { - "162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 104.8, - 117.4 - ] - ] - ], - "-162.0": [ - [ - "P01_A_立柱下段_三角形30%_墙4底5_竖打", - "P09_工具法兰_螺旋体30%_墙4底5_竖打", - 52.0, - [ - -82.4, - 151.2, - 117.4 - ] - ] - ] - } - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J5", - "direction": "hi", - "old": 5.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J4", - "hold_at": -17.875, - "swept": "J6", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J1", - "direction": "hi", - "old": 161.0, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "hi", - "old": 157.625, - "new": 155.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "lo", - "old": -159.625, - "new": -157.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "lo", - "old": -17.875, - "new": -13.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J5", - "hold_at": -36.625, - "swept": "J6", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 155.0, - "new": 149.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J1", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J3", - "direction": "hi", - "old": 149.0, - "new": 143.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 0, - "hold": "J6", - "hold_at": 143.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": -180.0, - "swept": "J6", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J3", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J1", - "hold_at": 143.0, - "swept": "J6", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J1", - "direction": "hi", - "old": 143.0, - "new": 137.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J3", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J2", - "hold_at": -103.0, - "swept": "J6", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J1", - "direction": "hi", - "old": 137.0, - "new": 131.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": -157.0, - "swept": "J6", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J1", - "direction": "hi", - "old": 131.0, - "new": 125.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J3", - "hold_at": 125.0, - "swept": "J6", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J1", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J3", - "direction": "hi", - "old": 125.0, - "new": 119.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J4", - "hold_at": -13.0, - "swept": "J6", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J1", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J3", - "direction": "hi", - "old": 119.0, - "new": 113.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J5", - "hold_at": -36.625, - "swept": "J6", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J1", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J3", - "direction": "hi", - "old": 113.0, - "new": 107.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": -180.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J1", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J2", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J3", - "direction": "hi", - "old": 107.0, - "new": 101.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J4", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - }, - { - "pass_": 1, - "hold": "J6", - "hold_at": 101.0, - "swept": "J5", - "direction": "hi", - "old": 0.0, - "new": 0.0, - "jams": {} - } - ], - "final": [ - { - "axis": "J1", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J2", - "lo": -105.0, - "hi": 9.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +9° / 反向 -105°,总行程 114°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J3", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - }, - { - "axis": "J4", - "lo": -111.0, - "hi": 16.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +16° / 反向 -111°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J5", - "lo": -93.0, - "hi": 34.0, - "continuous": false, - "zero": 0.0, - "note": "正向 +34° / 反向 -93°,总行程 127°(全对全动态扫描验证,含相邻件与电机)" - }, - { - "axis": "J6", - "lo": -180.0, - "hi": 180.0, - "continuous": true, - "zero": 0.0, - "note": "整周无干涉,可 360° 连续旋转(全对全动态扫描验证)" - } - ] -} \ No newline at end of file diff --git a/.gitignore b/.gitignore index c08068d..d71377e 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,7 @@ __pycache__/ *.pyc -# 构建产物(可用 src/ 下的脚本重新生成) +# 构建产物(可用 python -m arm.* 重新生成) out/ # 临时调试脚本与截图 @@ -44,3 +44,13 @@ docs/shots/m5_guide_step1.png docs/shots/m6_guide_step5.png docs/shots/m7_sheet_blueprint.png docs/shots/m8_sheet_dark.png + +# 其它编辑器的本地配置/会话残留(不是工程产物,也不该跟着版本走) +.trae/ +.workbuddy/ +.zcode/ +.zcodeignore + +# 定位图/ 只是"给用户看的核验图落点",一次性、会反复清空; +# 关卡比对的权威物是 tests/baselines/(md5 名单 + flaky 参考像素),不在这里。 +定位图/ diff --git a/.trae/documents/姿态估计算法评估与优化方案.md b/.trae/documents/姿态估计算法评估与优化方案.md deleted file mode 100644 index ba68ff6..0000000 --- a/.trae/documents/姿态估计算法评估与优化方案.md +++ /dev/null @@ -1,204 +0,0 @@ -# 姿态估计算法评估与优化 —— 实施计划 - -> 交付物(用户已确认):**仅一份中文评估报告**,不改动求解器代码。 -> 报告落盘位置:`docs/姿态估计算法评估报告.md` - ---- - -## 1. 摘要(Summary) - -本任务对机械臂的「姿态估计算法」——即 [arm-ik-solver.js](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js) 的 6 轴逆运动学(IK)姿态求解器及其查看器集成 [ik-glue.js](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js)(用户已确认该文件即「姿态估计算法」)——做一次全面评估。 - -需要解决的核心问题:**在多个关键位姿(近工作空间边界、折叠/奇异构型、贴近关节限位处)无法达到预定姿态目标;且当目标姿态确实不可达时,返回的「最近位姿」明显劣于肉眼可见的更优姿态。** - -评估已在本阶段(Plan 模式,只读)实测复现并定位到 4 类缺陷(见 §2.3),并额外回答了两个关键设计问题:**现状是「先点位、后角度」的位置优先架构**(§2.4),以及**位置与姿态在远目标上真正不可兼得、必须提供可切换优先级**(§2.4 实测取舍表)。本计划定义报告的**结构、数据来源、可复现实验命令、优化策略与验证口径**。执行阶段只产出一份报告 md,不新增任何脚本文件(实验以 `node -e` 内联执行,命令内嵌报告附录)。 - ---- - -## 2. 现状分析(Current State Analysis) - -### 2.1 代码与数据资产 - -| 角色 | 文件 | 说明 | -| :--- | :--- | :--- | -| 姿态求解器(主) | [arm-ik-solver.js](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js) | UMD,`make(cfg)` 返回 `solve/solveTo/solveWarm/solveDyn/closest/closest6/branch/fk/orient/costOf` 等([导出清单 L1355-1360](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L1355-L1360)) | -| 查看器集成 | [ik-glue.js](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js) | 姿态 UI、目标拖动、`solveTo` 调用([L379](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js#L379)、[L399](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js#L399))、结果渲染与徽标判定([renderResult L318-342](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js#L318-L342)) | -| 关节表(几何真值) | [mesh_arm.json](file:///g:/Robot_v2/机器人手臂/web/mesh_arm.json) | `joints[] = {id, rear, d}`,FK/IK 运动学常量来源 | -| 关节限位 | [travel.json](file:///g:/Robot_v2/机器人手臂/docs/travel.json) | 本报告测量口径:J1/J3/J6 连续(±180),J2∈[-106,9],J4∈[-111,16],J5∈[-95,34](2026-09-29 起该表退为零位扫描参考:滑块改电机行程 ±360°,可行域改相邻件实测区间) | -| 既有自检 | [pose_ik_check.js](file:///g:/Robot_v2/机器人手臂/scratch/pose_ik_check.js) | 7 组 node 自检(往返、位置回归、姿态重构、交互口径、兜底、耗时) | -| 既有性能基准 | [bench_ik_split.js](file:///g:/Robot_v2/机器人手臂/scratch/bench_ik_split.js) / [bench_ik_split.json](file:///g:/Robot_v2/机器人手臂/scratch/bench_ik_split.json) | 单次求解耗时与「位置/姿态」占比拆分 | -| 历史优化实验 | `scratch/ikopt/`(`solver_scanbase.js` 等) | 根枚举/窗口/同族门等过往迭代产物,报告需说明其结论 | - -### 2.2 现有算法骨架(报告需准确描述) - -1. **位置闭式 + 二维冗余扫描**:以 (θ4,θ5) 为冗余参数全域扫描,θ1..θ3 由 [branch() L429-463](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L429-L463) 闭式给出(最多 4 支:θ3=±σ、±(π−σ)),位置天然精确到 1e-13 mm。 -2. **按行裁剪**:[rowSpans L644-654](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L644-L654) 用探测步 `hp=max(h,2)°` 找每 θ4 行的可行 θ5 段,段内再按 h 精扫。 -3. **姿态任务**:工具轴指向 = 目标轴,是 (θ4,θ5) 上的 2 方程 2 未知量,解为**孤立点**;[poseTools L486-545](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L486-L545) 提供残差/牛顿原语;[§4.1b 根枚举 L697-761](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L697-L761) 逐行做变号括号 + 牛顿收口;滚转由 [rollClose L549-555](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L549-L555) 闭式补 θ6。 -4. **接受门**:`POSETOL=2.0°`([L581](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L581))——`scoreBranch` 对 `oerr>POSETOL` 的候选**直接返回 null 丢弃**([L596-598](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L596-L598))。 -5. **不可达兜底**:[closest6 L1054-1169](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L1054-L1169) 为 6 维阻尼最小二乘(DLS),160 次确定性拟随机重启 × 160 迭代;[solveTo L1172-1192](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L1172-L1192) 在 `solve()` 失败时调用它。 -6. **实时快路**:[solveWarm L1200-1265](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L1200-L1265),沿上一帧解同支续解,失败回落全量。 - -### 2.3 已实测缺陷(本阶段只读复现,数据可直接写入报告) - -**缺陷 A — 覆盖漏洞:目标姿态精确可达,求解器却静默返回 ~1–2° 残差** -- 全空间随机可达位形,`solve(step=0.25)` 返回 `oerr>0.05°` 的比例:**2.5%(3/120)**,量级 `oerr∈[1.24, 1.95]°`(真值应为 0)。 -- 其中可明确归因于「覆盖漏洞」(细扫 step=0.05 能到 ≤0.05°、即确有大步长漏解)的部分,小样本估计 **0.3%–1.3%**(300 组样本:1/300;80 组样本:1/80);其余为真·近奇异构型(细扫同样到不了 ≤0.05°,属物理不可达的刀尖构型)。**执行阶段用更大样本(D1/D4)收敛该比例。** -- 典型案例 `T=[329.3,253.4,750.0]`:真解 (θ4,θ5)=(-16.45,-51.97),`branch()` 在该点给出 **位置误差 5.7e-14 mm、轴误差 0.000°** 的精确解;但 `solve(step=0.25)` 返回 oerr=1.909°。 -- **步长敏感(非分辨率问题)**:同一目标 `oerr` 随 `step` 非单调跳变——`0.25°→1.60`、`0.15°→0.045`、`0.10°→1.14`、`0.05°→0.013`;用 `solveTo({warm:真解})` 则得到 **oerr=0.0000**。证明确切解存在,是**根枚举/牛顿收口的完备性缺口**。 -- 工作区实测:前伸工作区 (20,210,300)±90mm 失败率 **1.7%(1/60)**;查看器默认区 (150,128,260)±60mm 为 **0%(0/60)**——缺口集中在工作空间外围/极端构型。 - -**缺陷 B — 不可达兜底 closest6 严重次优(「肉眼可见更优姿态」的直接来源)** -以「位置精确解中姿态误差的全局最小值」为真最优(对 (θ4,θ5) 网格 0.05° 枚举 `branch()` 全部在限位内的支),目标姿态在可达姿态基础上外推 20°: - -| 案例 | 目标 TCP | 兜底返回 | 真最优姿态差 | 劣化 | -| :--- | :--- | ---: | ---: | ---: | -| 1 | (40,455,216) | 位置 0.4mm / 姿态 **74.0°** | **11.04°** | **63.0°** | -| 2 | (196,122,136) | 位置 0.5mm / 姿态 **130.0°** | **17.26°** | **112.8°** | -| 3 | (-141,-18,1030) | 位置 0.0mm / 姿态 **167.2°** | **89.09°** | **78.1°** | - -**缺陷 C — 姿态不可达时把「位置精确解候选池」整体丢弃,导致兜底被便宜解严格支配** -- `T=[20,400,300]`、`R=poseMat(25,95,10)`:位置完全可达(无姿态 `solve` 返回 err=2.5e-14,0.5° 格上有 **11920** 个位置精确解),但姿态目标不可达时 `scoreBranch` 因 `oerr>POSETOL` 把全部候选置 null,`solve()` 返回 `ok=false`、候选池为空,被迫走 [closest6](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L1054-L1169) 的 6 维随机 DLS。 -- 同例对照(实测):位置精确解集内最小姿态差 = **121.05°**;`closest6` 返回 **136.7° / 位置 0.0mm**。即 `closest6` 在**位置与姿态两个指标上同时劣于**一个用现有闭式扫描就能廉价得到的解 —— **被严格支配(Pareto-dominated)**。 -- 后果:`位置精确 + 姿态尽力` 这条最自然的路径没有实现;兜底既不最优、又更慢(159–224ms vs 扫描 ~100ms)。 - -**缺陷 D — 性能超出既定预算** -- [bench_ik_split.json](file:///g:/Robot_v2/机器人手臂/scratch/bench_ik_split.json) 摘要:开姿态单次 **中位 107ms / p95 242ms / 最大 299ms**(位置基座中位 73.6ms,姿态增量中位 37.2ms 占 33%,escalating 触发率 2.5%)。 -- 本阶段在工作区实测均耗时 112–157ms;不可达兜底 159–224ms。 -- 均高于用户设定的 **≤100ms** 目标,且兜底路径(random-restart DLS)是主要浪费点。 - -### 2.4 求解范式评估:位置优先 vs 末端姿态反推(用户提出,报告中作为备选简要说明) - -**用户提问**:能否用已指定的末端角度,从末端逐级反推回各关节? - -**几何事实(以 [mesh_arm.json](file:///g:/Robot_v2/机器人手臂/web/mesh_arm.json) 为准)**: - -| 关节 | rear | d | 类型 | -| :--- | :--- | :--- | :--- | -| J1 | (-51,128,610) | (1,0,0) | X 抬举 | -| J2 | (18.5,100,490) | (0,1,0) | Y 俯仰 | -| J3 | (18.5,128,445) | (0,0,-1) | Z 自转 | -| J4 | (18.5,100,332) | (0,**1**,0) | Y 俯仰(肘) | -| J5 | (18.5,100,222) | (0,**1**,0) | Y 俯仰(腕) | -| J6 | (18.5,128,176) | (0,0,**-1**) | Z 工具自转 | - -- **J4 ∥ J5**(均 +Y,相距 110mm)⇒ 平行轴永不相交 ⇒ **非球形腕**,经典"腕心解耦 + θ6→θ5→θ4 逐级闭式反推"不成立。(J5⊥J6 在 (18.5,128,222) 相交,仅构成 2 轴腕。) -- 但存在可用的**降阶结构**:因 J4∥J5,`R4·R5 = R_y(θ4+θ5)`(代码 [L406-411](file:///g:/Robot_v2/机器人手臂/web/arm-ik-solver.js#L406-L411) 已用 `SAMEAX` 利用该性质),故 - - ``` - R = Rx(θ1)·Ry(θ2)·Rz(−θ3)·Ry(θ4+θ5)·Rz(−θ6) - ``` - - ⇒ **工具轴只由 (θ1,θ2,θ3,θ4+θ5) 决定,θ6 是纯工具滚转**;"用末端姿态驱动求解"可行,但需按此降阶链求解(3 方程 / 5 参数 → 2 参数族),而非逐级反推。 - -**位置与姿态在远目标上"真的不可兼得"(实测,报告须收录)**: - -| 目标 | 优先级 | 位置误差 | 姿态差 | -| :--- | :--- | ---: | ---: | -| T=(20,400,300), R=poseMat(25,95,10) | 位置优先(保点) | **0.0 mm** | 121.05° | -| 同上 | 姿态优先(保角度) | 127.6 mm | **0.22°** | -| T=(196,122,136), R=poseMat(25,95,10) | 位置优先 | **0.0 mm** | 116.39° | -| 同上 | 姿态优先 | 233.6 mm | **0.45°** | - -⇒ 结论:二者是**真实的取舍**,不存在"都能兼顾"的解;算法必须提供**可切换的优先级**。 - -### 2.5 误差来源定位(报告核心结论) - -- **R1 根枚举不完备**:姿态精确解在 (θ4,θ5) 平面是一条一维曲线,只在少数孤立点穿过 `POSETOL` 窄带;当该曲线经过 θ3≈±90° 的**折叠点**(`branch()` 的 4 支两两重合、位置雅可比奇异)、或贴着关节限位/可行段边缘时,变号括号检测失效、`match()` 的「θ1..θ3 最近」同支判据在折叠处换支,牛顿收口失败。 -- **R2 接受门把「不可达」与「漏解」混为一谈**:`oerr>POSETOL` 一律丢弃 ⇒ 无法区分「确实不可达」与「可达但没找到」,导致 A 类静默近失 + C 类候选池清空。 -- **R3 兜底目标函数与真实目标不一致**:`closest6` 用固定的 20mm/rad 把姿态折进位置残差做联合 DLS,既不保证「位置优先、姿态最小」,也依赖随机重启初值,落点常是劣质局部极值。 -- **R4 行裁剪探测步过粗**:`hp=max(h,2)°` 且按单一连续段假设(注释称仅在 5 个目标上验证过),窄于 2° 的可行段会被整段漏掉 ⇒ 整行/整区无候选。 - ---- - -## 3. 交付物与报告大纲(Proposed Changes) - -**唯一变更**:新建 `docs/姿态估计算法评估报告.md`(中文)。 - -报告章节(每章明确数据来源,均为本阶段或执行阶段 `node -e` 实测可得): - -1. **概述与结论摘要**(TL;DR 表格:缺陷 / 影响 / 根因 / 建议 / 预期收益) -2. **算法现状描述**——§2.2 的骨架,附关键函数行号链接与数据流图 -3. **求解范式评估(位置优先 vs 末端姿态反推)**——§2.4: - - 确认现状为「先点位、后角度」(位置被强制精确、姿态是带 2° 门的软约束) - - 关节轴几何表 + `J4∥J5 ⇒ 非球形腕` 的判定;说明「逐级闭式反推」不成立的**原因**(备选简要说明,约半页,含降阶链 `Rx·Ry·Rz·Ry(θ4+θ5)·Rz` 的结论式) - - 位置/姿态**不可兼得**的实测取舍表(§2.4),给出「必须提供可切换优先级」的结论 -4. **评估方法**——指标定义: - - 姿态残差 `oerr`(`rotErrDeg`,度) - - 位置残差 `err`(mm) - - 覆盖漏洞率 = 「真解 oerr≤0.05° 而解算 oerr>0.05°」占比 - - 步长敏感度 = 同目标在 step∈{0.05,0.1,0.15,0.25,0.5} 的 oerr 波动 - - 兜底劣化 = `closest6` 姿态差 −「位置精确解集内最优姿态差」 - - 单次耗时中位/p95(开/关姿态、可达/兜底) -5. **性能分析**——引用 bench_ik_split.json 的耗时构成 + 工作区实测;给出「位置/姿态/兜底」三段占比 -6. **误差来源定位**——§2.5 的 R1–R4,逐条给出**可复现证据**(目标坐标、真解、实测值) -7. **优化策略(设计方案,不在本轮落地)**——按收益/成本排序给出可实施建议: - - **S0(架构)双优先级求解范式**:仿 [ik-glue.js](file:///g:/Robot_v2/机器人手臂/web/ik-glue.js) 现有姿态模式的 UI 开关,新增「优先级」选择——**位置优先 / 姿态优先 / 折中**;三种模式下共用同一闭式支枚举结果,只切换目标函数与选解规则。给出各模式的数学表述: - - 位置优先:`min oerr s.t. ‖fk(q)−T‖=0`(在位置精确解集上最小化姿态误差) - - 姿态优先:`min ‖fk(q)−T‖ s.t. oerr=0`(在姿态精确解集上最小化位置误差;利用降阶链参数化) - - 折中:`min ‖fk(q)−T‖² + k·oerr²`,k 可调 - - 预期:位置优先模式把 B 类劣化从 63–113° 压到接近真最优;姿态优先模式提供 oerr≤0.05° 的「保角度」解(代价是位置误差,如实上报) - - **S1(根因修复)覆盖率完备化**:把 (θ4,θ5) 上的姿态方程看作一维解曲线,用**预测-校正 continuation 跟踪**(以解析/数值切向推进、跨越折叠点不换支),或等价地做**分支索引对齐的多起点牛顿 + 折叠点/限位角种子**;预期把 A 类覆盖漏洞降到 <0.2%,且不加粗扫密度 ⇒ 耗时基本不变(≤100ms)。 - - **S2(关键收益)姿态不可达时不再清空候选池**:去掉 `POSETOL` 硬门对候选池的**清空**语义,改为保留并按 `oerr` 排序入池(门只用于「达标」判定,不用于「丢弃」);预期消除 C 类 Pareto 支配,且兜底不再需要。 - - **S3 兜底加速**:以 S0/S2 的闭式候选为种子替代 160 次随机重启,仅在位置也越界时才退回受限 DLS;预期兜底耗时从 ~160–220ms 降到 ≤100ms。 - - **S4 可观测性**:UI 明确区分「已到达 / 位置到达·姿态最近 X° / 姿态到达·位置最近 Y mm / 均不可达」四种语义,避免把漏解或取舍结果当成功。 - - 每条策略给出:改动点(函数+行号)、原理、预期指标、代价与风险、与 ≤100ms 预算的关系。 -8. **验证方案与验收口径**——见 §5。 -9. **附录**:可复现实验命令(`node -e` 全文内联,不落脚本文件)+ 基准数据表 + 术语表。 - -> 说明:报告只做「评估 + 策略设计」,不含代码修改。若后续要落地 S0–S3,需另起一次实施任务。 - ---- - -## 4. 假设与决策(Assumptions & Decisions) - -| 项 | 决策 | 依据 | -| :--- | :--- | :--- | -| 交付物 | 仅评估报告 md,**不改代码** | 用户选择「仅评估报告」 | -| 「姿态估计算法」定义 | = `web/arm-ik-solver.js` 的 IK 姿态求解 + `ik-glue.js` 集成 | 全仓检索无 IMU/卡尔曼/姿态估计类算法;仓库内「姿态」相关实现仅此一处 | -| 性能预算 | 以 **≤100ms** 为优化目标约束(报告据此评估各策略) | 用户选择「保持 ≤100ms」 | -| 失败场景 | A(可达却漏解)与 B/C(不可达最近位姿)**都覆盖** | 用户选择「两者都做」 | -| 实验脚本 | 不新增文件,全部 `node -e` 内联并在报告附录给出原文 | 遵守「仅评估报告」与「不新建文件」 | -| **求解优先级** | 报告须设计**双优先级机制(位置优先 / 姿态优先 / 折中),界面可切换**;不可兼得时两种解都给出 | 用户选择「两者都做、界面可切换」 | -| **末端姿态反推** | 报告仅作**备选简要说明**(说明非球形腕故不能逐级闭式反推 + 降阶链结论),不展开公式级设计 | 用户选择「只作备选简要说明」 | -| 关节表/限位 | 以 `web/mesh_arm.json` + `docs/travel.json` 为准,tcp=[18.5,128,93] | 与 `pose_ik_check.js`、`bench_ik_split.js` 一致 | -| 真最优基准 | 「位置精确解集内姿态误差最小」的网格枚举(0.05° 分辨率,θ6 另行闭式最优) | 独立于被测求解器,避免自证 | -| 随机性 | 固定 LCG 种子的确定性采样,报告数据可复现 | 与求解器内部自检策略一致 | - ---- - -## 5. 验证方案(报告第 8 章内容) - -**基准数据集**(执行阶段固定种子生成,规模与命令写入附录): -- D1 全空间随机可达位形 ≥200 组(覆盖极端/边界构型) -- D2 前伸工作区 (20,210,300)±90mm ≥60 组、查看器默认区 (150,128,260)±60mm ≥60 组 -- D3 姿态外推集(外推 3/8/20°)≥30 组 → 测兜底劣化 -- D4 退化构型集(θ3≈±90°、临近限位)≥30 组 → 测覆盖漏洞 -- D5 越界目标集 → 测兜底耗时与质量 -- D6 **双优先级取舍集**:位置与姿态不可兼得的远目标 ≥20 组 → 测「位置优先」与「姿态优先」两解各自的误差与真实性 - -**指标与验收口径(现状基线 → 目标)**: - -| 指标 | 现状实测 | 目标 | -| :--- | :--- | :--- | -| D1/D4 覆盖漏洞率(真解≤0.05° 却返回>0.05°) | 2.5% | ≤0.2% | -| D1 最大静默姿态残差 | 1.95° | ≤0.05° | -| 步长敏感度(同目标 oerr 波动) | 0→1.86° | ≤0.05° | -| D3/D5 兜底相对真最优劣化(姿态优先口径) | 63–113° | ≤2° | -| D6 位置优先解:位置误差 / 姿态差 | 0.0mm / 121.05° | 位置=0;姿态=真最优(劣化 ≤0.5°) | -| D6 姿态优先解:姿态差 / 位置误差 | 0.22° / 127.6mm | 姿态 ≤0.05°;位置如实上报 | -| 开姿态单次耗时 p95 | 242ms | ≤100ms | -| 兜底耗时 | 159–224ms | ≤100ms | -| 姿态残差 P95(可达集) | ≤2.05°(接受限) | ≤0.05° | - -**沿用既有自检**:`node scratch/pose_ik_check.js` 的 7 项(含往返一致性、位置回归、姿态重构、roll→θ6 口径、6D 兜底、耗时对比)作为回归门,报告中给出其现状通过情况与目标。 - -**报告自检**:报告内每张数据表须与附录命令重跑结果一致;所有结论必须能由附录命令复现。 - ---- - -## 6. 执行步骤(待批准后按序执行) - -1. 复跑并固化基线数据(D1–D6 + bench + 自检),生成报告用数据表。 -2. 撰写 `docs/姿态估计算法评估报告.md` 正文(§3 大纲 1–9 章)。 -3. 内嵌可复现 `node -e` 命令与原始输出,形成附录。 -4. 复核报告数据与结论一致性(重跑附录命令抽查),交付。 diff --git a/.trae/documents/查看器3D性能优化方案.md b/.trae/documents/查看器3D性能优化方案.md deleted file mode 100644 index 7820309..0000000 --- a/.trae/documents/查看器3D性能优化方案.md +++ /dev/null @@ -1,258 +0,0 @@ -# 机器人手臂3D查看器 · 性能优化方案 - -目标文件:`G:\Robot_v2\机器人手臂\机器人手臂3D查看器.html`(15.31 MB / 35986 行) -用户要求:先做完整备份(带版本标识)→ 优化 → 验证功能完好 → 记录优化项与性能数据。 - ---- - -## 一、现状分析(实测,非猜测) - -### 1.1 文件结构 - -| 位置 | 内容 | -| --- | --- | -| L1~L420 | HTML + CSS + 界面骨架(左栏/IK 控制条/舞台/页脚/BOM 抽屉) | -| L421~L428 | `