2026-09-26 18:05:21 +08:00
|
|
|
# -*- coding: utf-8 -*-
|
2026-10-15 03:31:20 +08:00
|
|
|
"""CAD 放置与几何 helper:刚体变换、求交/包围盒、基础体、网格化与 STEP 读取。
|
2026-09-26 18:05:21 +08:00
|
|
|
|
2026-10-15 03:31:20 +08:00
|
|
|
实测接口数值在 arm/constants.py,仓库路径在 arm/paths.py——本模块不再持有它们。
|
2026-09-26 18:05:21 +08:00
|
|
|
"""
|
|
|
|
|
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
|
2026-10-15 02:23:55 +08:00
|
|
|
|
2026-09-26 18:05:21 +08:00
|
|
|
|
2026-10-04 13:16:32 +08:00
|
|
|
|
2026-09-26 18:05:21 +08:00
|
|
|
# ---------------- 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
|