Files
Robot_Arm/src/travel_sweep.py
T

163 lines
5.6 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""Compute each axis's real free travel by sweeping the assembled solids.
For joint Jk the moving set is every part whose kinematic group is Jk or a descendant
of it, rotated about the joint axis; the static set is everything else. A pair can only
jam if the analytically rotated bounding boxes overlap, so the sweep is
box prune -> boolean intersection volume -> jam
and the first angle whose overlap exceeds VOL_MIN is the hard stop. Where a whole turn
stays clear the axis is reported continuous, which is what the viewer's slider needs.
The jaw slides (P10/P11) are excluded: they are constrained along their rail, not by the
tree, and their own crash limit is the crank travel.
Run: python travel_sweep.py [J1 J3 ...] -> docs/travel.json
"""
import json
import math
import os
import sys
import time
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
sys.stdout.reconfigure(encoding="utf-8")
except Exception:
pass
import cadquery as cq
from armcore import WEB, DOCS, OUT, place
from arm_build import R, bb, common_vol
from OCP.gp import gp_Pnt
VOL_MIN = 2.0 # mm3 of interpenetration that counts as a jam
COARSE = 3.0 # deg, scan step
FINE = 0.25 # deg, bisect resolution
TREE = ["root", "J1", "J2", "J3", "J4", "J5", "J6"]
def load():
meta = json.load(open(WEB + "/mesh_arm.json", encoding="utf-8"))
joints = {j["id"]: j for j in meta["joints"]}
parts = {}
for p in meta["parts"]:
if p["jaw"]:
continue
f = "%s/parts_step/%s.step" % (OUT, p["name"])
if not os.path.exists(f):
print(" missing %s" % f)
continue
parts[p["name"]] = (cq.importers.importStep(f).val(), p["grp"])
return joints, parts
def corners(lo, hi):
return [(x, y, z) for x in (lo[0], hi[0]) for y in (lo[1], hi[1]) for z in (lo[2], hi[2])]
def rot_box(c8, ang, axis, pt):
t = R(ang, axis[0], axis[1], axis[2], pt[0], pt[1], pt[2])
ps = []
for c in c8:
q = gp_Pnt(*c).Transformed(t)
ps.append((q.X(), q.Y(), q.Z()))
return ([min(p[i] for p in ps) for i in range(3)], [max(p[i] for p in ps) for i in range(3)])
def overlap(mov, sta, ang, axis, pt):
"""Interpenetration volume (mm3) with the moving set rotated by ang about the axis.
The rotation must reach the GEOMETRY, not just the prune box - transforming only the
boxes leaves every boolean at the assembled pose, which reports any axis as clear."""
t = R(ang, axis[0], axis[1], axis[2], pt[0], pt[1], pt[2])
boxes = {n: rot_box(m["c8"], ang, axis, pt) for n, m in mov.items()}
cands = []
for sn, s in sta.items():
for mn, b1 in boxes.items():
b2 = s["box"]
if any(b1[1][k] < b2[0][k] + 0.05 or b2[1][k] < b1[0][k] - 0.05 for k in range(3)):
continue
cands.append((mn, sn))
if not cands:
return 0.0
rot = {}
for mn in {m for m, _ in cands}:
rot[mn] = place(mov[mn]["shape"], t)
tot = 0.0
for mn, sn in cands:
v = common_vol(rot[mn], sta[sn]["shape"])
if v > VOL_MIN:
tot += v
return tot
def refine(mov, sta, axis, pt, a_free, a_jam):
while abs(a_jam - a_free) > FINE:
mid = 0.5 * (a_free + a_jam)
if overlap(mov, sta, mid, axis, pt) > VOL_MIN:
a_jam = mid
else:
a_free = mid
return a_free
def sweep_one(jid, joints, parts):
j = joints[jid]
k = TREE.index(jid)
down = set(TREE[k:])
axis, pt = j["d"], j["rear"]
mov = {n: dict(shape=s, box=bb(s), c8=corners(*bb(s)))
for n, (s, g) in parts.items() if g in down}
sta = {n: dict(shape=s, box=bb(s)) for n, (s, g) in parts.items() if g not in down}
zero = overlap(mov, sta, 0.0, axis, pt)
full = True
a = 0.0
while a < 360.0 - 1e-6:
if overlap(mov, sta, a, axis, pt) > VOL_MIN:
full = False
break
a += COARSE
if full:
return dict(axis=jid, lo=-180.0, hi=180.0, continuous=True, zero=round(zero, 1),
note="整周无干涉,可 360° 连续旋转")
pos = _limit(mov, sta, axis, pt, +1)
neg = _limit(mov, sta, axis, pt, -1)
hi = min(pos, 180.0)
lo = max(neg, -180.0)
return dict(axis=jid, lo=round(lo, 1), hi=round(hi, 1), continuous=False,
zero=round(zero, 1),
note="正向 +%.0f° / 反向 %.0f°,总行程 %.0f°" % (hi, lo, hi - lo))
def _limit(mov, sta, axis, pt, sign):
"""How far the joint turns in `sign` direction before something interpenetrates."""
step = COARSE
a_free = 0.0
while step < 360.0:
ang = sign * step
if overlap(mov, sta, ang % 360.0, axis, pt) > VOL_MIN:
return refine(mov, sta, axis, pt, a_free, ang)
a_free = ang
step += COARSE
return 180.0 * sign
def main():
t0 = time.time()
joints, parts = load()
print("loaded %d rigid parts, groups=%s" % (len(parts), sorted({g for _, g in parts.values()})))
want = sys.argv[1:] or [k for k in ("J1", "J2", "J3", "J4", "J5", "J6") if k in joints]
rows = []
for jid in want:
r = sweep_one(jid, joints, parts)
rows.append(r)
print("%-3s lo=%7.1f hi=%7.1f continuous=%-5s zero=%6.1f mm3 %-34s [%.0fs]"
% (jid, r["lo"], r["hi"], r["continuous"], r["zero"], r["note"],
time.time() - t0), flush=True)
json.dump(rows, open(DOCS + "/travel.json", "w", encoding="utf-8"),
ensure_ascii=False, indent=1)
print("wrote docs/travel.json (%.0fs)" % (time.time() - t0))
if __name__ == "__main__":
main()