Files
Robot_Arm/arm/cad/helpers.py
T

174 lines
5.4 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""CAD 放置与几何 helper:刚体变换、求交/包围盒、基础体、网格化与 STEP 读取。
实测接口数值在 arm/constants.py,仓库路径在 arm/paths.py——本模块不再持有它们。
"""
import math
import cadquery as cq
from OCP.gp import gp_Trsf, gp_Vec, gp_XYZ, gp_Mat, gp_Pnt, gp_Dir, gp_Ax1, gp_Ax2, gp_Quaternion
from OCP.BRepBuilderAPI import BRepBuilderAPI_Transform
from OCP.Bnd import Bnd_Box
from OCP.BRepBndLib import BRepBndLib
from OCP.BRepMesh import BRepMesh_IncrementalMesh
from OCP.BRep import BRep_Tool
from OCP.TopExp import TopExp_Explorer
from OCP.TopAbs import TopAbs_FACE, TopAbs_REVERSED, TopAbs_SOLID
from OCP.TopoDS import TopoDS
V = cq.Vector
# ---------------- geometry helpers --------------------------------------------------------------------
def _nrm(v):
m = v.Magnitude()
return gp_Vec(v.X() / m, v.Y() / m, v.Z() / m)
def _dot(a, b):
return a.X() * b.X() + a.Y() * b.Y() + a.Z() * b.Z()
def _perp(e, z):
d = _dot(e, z)
return gp_Vec(e.X() - d * z.X(), e.Y() - d * z.Y(), e.Z() - d * z.Z())
def trs(pos=(0, 0, 0), xdir=(1, 0, 0), zdir=(0, 0, 1)):
"""Local->global rigid transform: local +Z -> zdir, local +X -> xdir, y = z x x.
Uses SetValues (row-major 3x4) because gp_Quaternion / SetCols overloads in this
OCP build do not map axes the way the docs suggest - verified against known points."""
z = _nrm(gp_Vec(*zdir))
x = _perp(gp_Vec(*xdir), z)
if x.Magnitude() < 1e-9:
x = _perp(gp_Vec(0, 0, 1), z)
x = _nrm(x)
y = z.Crossed(x)
t = gp_Trsf()
t.SetValues(x.X(), y.X(), z.X(), pos[0],
x.Y(), y.Y(), z.Y(), pos[1],
x.Z(), y.Z(), z.Z(), pos[2])
return t
def map_pt(t, p):
v = cq.Vertex.makeVertex(*p)
w = cq.Shape(BRepBuilderAPI_Transform(v.wrapped, t, True).Shape())
q = w.Vertices()[0].Center()
return (q.x, q.y, q.z)
def place(shape, t):
if shape is None:
return None
return cq.Shape(BRepBuilderAPI_Transform(shape.wrapped, t, True).Shape())
def bb(shape):
b = Bnd_Box()
BRepBndLib.Add_s(shape.wrapped, b, True)
lo, hi = b.CornerMin(), b.CornerMax()
return (lo.X(), lo.Y(), lo.Z()), (hi.X(), hi.Y(), hi.Z())
def bbox_str(shape):
a, b = bb(shape)
return "[%.1f %.1f %.1f .. %.1f %.1f %.1f] (%.1fx%.1fx%.1f)" % (
a[0], a[1], a[2], b[0], b[1], b[2], b[0] - a[0], b[1] - a[1], b[2] - a[2])
def cyl(r, z0, z1, x=0.0, y=0.0):
return cq.Solid.makeCylinder(r, z1 - z0, V(x, y, z0), V(0, 0, 1))
def ring(r0, r1, z0, z1):
return cyl(r1, z0, z1).cut(cyl(r0, z0 - 0.001, z1 + 0.001))
def box(x0, x1, y0, y1, z0, z1):
return cq.Solid.makeBox(x1 - x0, y1 - y0, z1 - z0, V(x0, y0, z0))
def rect_pocket(cx, cy, w, h, z0, z1, rot=0.0):
s = box(cx - w / 2.0, cx + w / 2.0, cy - h / 2.0, cy + h / 2.0, z0 - 0.001, z1 + 0.001)
if rot:
s = s.rotated(V(0, 0, 0), V(0, 0, 1), rot)
return s
def holes(shape, pts, d, z0, z1):
"""Through/ blind cylindrical cuts at the given (x, y) points."""
cut = [cyl(d / 2.0, z0, z1, p[0], p[1]) for p in pts]
return shape.cut(*cut) if cut else shape
def slot_holes(shape, pts, d, cb_d=None, cb_z0=None, cb_z1=None, z0=-1, z1=1):
cuts = [cyl(d / 2.0, z0, z1, p[0], p[1]) for p in pts]
if cb_d and cb_z0 is not None:
cuts += [cyl(cb_d / 2.0, cb_z0, cb_z1, p[0], p[1]) for p in pts]
return shape.cut(*cuts)
def polygon_pts(r, n, phase=0.0):
return [(r * math.cos(math.radians(phase + 360.0 * k / n)),
r * math.sin(math.radians(phase + 360.0 * k / n))) for k in range(n)]
def convex_prism(pts2d, z0, z1):
"""Solid from an ordered 2D outline (assumed convex or simple)."""
wp = cq.Workplane("XY").workplane(centerSection=(0, 0, z0))
w2 = [(p[0], p[1]) for p in pts2d]
r = (cq.Workplane("XY", origin=(0, 0, z0))
.polyline(w2 + [w2[0]]).close().extrude(z1 - z0, combine=False))
return r.val()
def fillet_all_edges(shape, r):
try:
return shape.edges().fillet(r).val() if isinstance(shape, cq.Workplane) else \
cq.Workplane(obj=shape).edges().fillet(r).val()
except Exception:
return shape
def mesh_of(shape, defl=0.08):
BRepMesh_IncrementalMesh(shape.wrapped, defl, False, 0.55, True)
verts, tris, base = [], [], 0
e = TopExp_Explorer(shape.wrapped, TopAbs_FACE)
while e.More():
cur = e.Current()
f = TopoDS.Face_s(cur)
tri = BRep_Tool.Triangulation_s(f, cur.Location())
if tri is not None:
trsf = cur.Location().Transformation()
for i in range(1, tri.NbNodes() + 1):
p = tri.Node(i).Transformed(trsf)
verts.append((round(p.X(), 2), round(p.Y(), 2), round(p.Z(), 2)))
rev = f.Orientation() == TopAbs_REVERSED
for i in range(1, tri.NbTriangles() + 1):
t = tri.Triangle(i)
a, b, c = t.Value(1) - 1, t.Value(2) - 1, t.Value(3) - 1
tris.append((base + a, base + c, base + b) if rev else (base + a, base + b, base + c))
base += tri.NbNodes()
e.Next()
return verts, tris
def solids_of(shape):
out = []
e = TopExp_Explorer(shape.wrapped, TopAbs_SOLID)
while e.More():
out.append(TopoDS.Solid_s(e.Current()))
e.Next()
return [cq.Shape(s) for s in out]
def read_step(p):
return cq.importers.importStep(p)
def norm_angle(a):
return (a + 180.0) % 360.0 - 180.0