"""Fully 3D-printable linear actuator: dovetail rail + sliding carriage + printable lead screw + printed nut
+ end brackets + coupler, driven by our Feetech STS3215 (mounted in holder_fillet). Travel along Z, screw at
Y=20. Every piece is a watertight printable STL. -> linear_rail.urdf"""
import numpy as np, trimesh
from shapely.geometry import Point, Polygon
def box(x0,x1,y0,y1,z0,z1):
    b=trimesh.creation.box(extents=[x1-x0,y1-y0,z1-z0]); b.apply_translation([(x0+x1)/2,(y0+y1)/2,(z0+z1)/2]); return b
def cylZ(x,y,r,z0,z1):
    c=trimesh.creation.cylinder(radius=r,height=z1-z0,sections=48); c.apply_translation([x,y,(z0+z1)/2]); return c
def cylY(x,z,r,y0,y1):
    c=trimesh.creation.cylinder(radius=r,height=y1-y0,sections=32); c.apply_transform(trimesh.transformations.rotation_matrix(np.pi/2,[1,0,0])); c.apply_translation([x,(y0+y1)/2,z]); return c
def extr(poly,z0,z1):
    m=trimesh.creation.extrude_polygon(poly,z1-z0); m.apply_translation([0,0,z0]); return m
def U(p): return trimesh.boolean.union(p,engine="manifold")
def D(a,b): return trimesh.boolean.difference([a,b],engine="manifold")
def screw(length,rcore,rib,pitch):
    turns=length/pitch; n=int(turns*44); tt=np.linspace(0,turns*2*np.pi,n); zz=np.linspace(1.5,length-1.5,n)
    path=np.c_[rcore*np.cos(tt),rcore*np.sin(tt),zz]
    rr=trimesh.creation.sweep_polygon(Point(0,0).buffer(rib,resolution=6),path)
    c=trimesh.creation.cylinder(radius=rcore,height=length,sections=48); c.apply_translation([0,0,length/2])
    return U([c,rr])
YS=20.0; PITCH=8.0
# --- RAIL (dovetail, along Z) ---
rp=Polygon([(-18,0),(18,0),(18,6),(10,6),(13,14),(-13,14),(-10,6),(-18,6)])
rail=extr(rp,-80,80)
for z in (-70,-35,0,35,70): rail=D(rail,cylY(0,z,2.6,-1,7))   # M5 base mount holes
rail.export("rail.stl")
# --- CARRIAGE (dovetail groove + nut pocket + top mount) ---
carr=box(-20,20,6.5,30,-25,25)
gp=Polygon([(10.4,4),(13.4,14.4),(-13.4,14.4),(-10.4,4)])     # ridge + 0.4 clearance, open bottom
carr=D(carr,extr(gp,-30,30))
carr=D(carr,cylZ(0,YS,8.3,-26,26))                            # nut pocket bore (nut presses in)
for x in (-14,14):                                            # top mount holes (M3) for payload
    carr=D(carr,cylY(x,0,1.7,24,31))
carr.export("carriage.stl")
# --- LEAD SCREW (8mm pitch) + D-flat coupler end ---
sc=screw(150,3.2,1.7,PITCH); sc.apply_translation([0,0,-75])  # Z[-75,75]
cend=D(cylZ(0,0,4,-88,-75),box(2.2,6,-6,6,-88,-75))           # D-flat shaft for coupler
lead=U([sc,cend]); lead.apply_translation([0,YS,0]); lead.export("leadscrew.stl")
# --- PRINTED NUT (internal thread = clearance screw subtracted) + flange ---
nut=U([cylZ(0,0,8,-12,12),cylZ(0,0,11,10,12)])               # body + flange
nut=D(nut,screw(26,3.55,2.05,PITCH).apply_translation([0,0,-13]))
nut.apply_translation([0,YS,0]); nut.export("leadnut.stl")
# --- MOTOR END (holds screw coupler bearing; holder bolts behind) + IDLER END ---
me=box(-20,20,0,34,-86,-80); me=D(me,cylZ(0,YS,4.3,-88,-79))
for x in (-14,14): me=D(me,cylY(x,-83,2.2,-1,7)) if False else me
me.export("motor_end.stl")
ie=box(-20,20,0,34,80,86); ie=D(ie,cylZ(0,YS,4.3,79,88)); ie.export("idler_end.stl")
# --- COUPLER (servo horn side <-> screw D-shaft side) ---
cp=cylZ(0,YS,7,-74,-60); cp=D(cp,box(-2.9,2.9,YS-6,YS+6,-74,-66))  # D socket for screw
cp=D(cp,cylZ(0,YS,3,-64,-60))                                       # horn side bore
cp.export("coupler.stl")
for n,m in [("rail",rail),("carriage",carr),("leadscrew",lead),("leadnut",nut),("motor_end",me),("idler_end",ie),("coupler",cp)]:
    print(f"{n}: wt={m.is_watertight} bodies={len(m.split(only_watertight=False))}")
