"""Early cable/compensator comparison. No fitted dimensions or removal paths."""
from pathlib import Path
import json, math
import cadquery as cq
OUT=Path(__file__).parent
BODY=json.loads((OUT.parent/'body-data.json').read_text())
S=BODY['published_reference_dimensions_mm']['wheelbase']/2.4
parts=[]; joints={}; X=cq.Vector(1,0,0); Y=cq.Vector(0,1,0); Z=cq.Vector(0,0,1)
def V(p): return cq.Vector(*(a*S for a in p))
def box(w,h,d,c): return cq.Workplane('XY').box(w,h,d).translate(c.toTuple()).val()
def ring(ro,ri,l,c,axis):
 return cq.Solid.makeCylinder(ro,l,c-axis*l/2,axis).cut(cq.Solid.makeCylinder(ri,l+2,c-axis*(l/2+1),axis))
def rod(a,b,r):
 v=b-a; return cq.Solid.makeCylinder(r,v.Length,a,v.normalized())
def link(a,b,width,thickness,axis):
 plane=cq.Plane(origin=(a+b)*.5,xDir=(b-a).normalized(),normal=axis)
 return cq.Workplane(plane).rect((b-a).Length,width).extrude(thickness/2,both=True).val().fuse(ring(width/2,.01,thickness,a,axis)).fuse(ring(width/2,.01,thickness,b,axis))
def bore(shape,p,r,axis,length=50):
 return shape.cut(cq.Solid.makeCylinder(r,length,p-axis*length/2,axis))
def add(name,shape,color=(.44,.46,.42)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color));return shape
def path(points,r):
 # Joined straight envelopes, not a manufactured bend-radius specification.
 return cq.Compound.makeCompound([rod(a,b,r) for a,b in zip(points,points[1:])])
def clevis(center,axis,along,hole_r,inner_gap=5,ear_t=3):
 # All geometry in mm; each bored ear shares the explicit pin axis.
 es=[]
 for sign in [-1,1]:
  c=center+axis*sign*(inner_gap/2+ear_t/2)
  e=link(c,c+along*16,14,ear_t,axis)
  es.append(bore(e,center,hole_r,axis))
 return cq.Compound.makeCompound(es)
def pin(p,r,axis,length):
 return cq.Compound.makeCompound([cq.Solid.makeCylinder(r,length,p-axis*length/2,axis),cq.Solid.makeCylinder(r+2,2,p-axis*(length/2+2),axis)])

# Right-side tunnel control. Side, profile, pose and every mounting station are
# construction choices. Three feet stop at a declared body-interface boundary;
# no invented factory holes are cut in the existing tunnel/floor.
control=V([-.205,.505,-.65]);control_cable=V([-.205,.455,-.71])
mount=box(3,94,100,control+X*14)
mount=bore(mount,control,11,X)
mount_holes=[]
for dy,dz in [(-32,-30),(-32,30),(32,0)]:
 p=control+cq.Vector(14,dy,dz);mount=bore(mount,p,4.5,X);mount_holes.append(list(p.toTuple()))
 # Feet extend inward to an unverified body attachment, not a welded fit.
 mount=mount.fuse(box(22,12,12,p+X*12.5));mount=bore(mount,p,4.5,X,80)
add('handbrake_tunnel_mount_bracket_reference',mount,(.26,.31,.26))
grip_end=V([-.205,.58,-.36]);arm=link(control,grip_end,20,5,X)
arm=arm.fuse(link(control,control_cable,18,5,X));arm=bore(arm,control,10.5,X);arm=bore(arm,control_cable,4.2,X)
grip_start=grip_end+(control-grip_end).normalized()*82
grip=rod(grip_start,grip_end,13)
button=rod(grip_end,grip_end+(grip_end-control).normalized()*6,7)
# Smooth ratchet-sector cue; tooth/pawl engagement intentionally unresolved.
sector=ring(38,20,4,control+X*6,X).cut(box(100,100,100,control+cq.Vector(6,-50,0)))
add('handbrake_control_arm_grip_button_and_smooth_sector_reference',cq.Compound.makeCompound([arm,grip,button,sector]))
add('handbrake_control_spindle_and_retainer_envelopes',pin(control,10,X,40))

# Rear support uses a declared rear-of-axle construction envelope around the
# existing tube. Its two fixing envelopes are not an actual axle drilling map.
pivot=V([.28,.42,-1.43]);output_y=.45*S
left_joint=cq.Vector(pivot.x,output_y,pivot.z+18)
right_joint=cq.Vector(pivot.x,output_y,pivot.z-18)
input_joint=pivot+cq.Vector(-35,-20,0)
abutment=input_joint+cq.Vector(0,0,65)
support=box(56,3,100,pivot+cq.Vector(0,-26,30))
support=bore(support,pivot,9,Y)
# Forward mounting flange meets the old tube surface at Z=-1.20-32/S.
tube_rear=-1.20*S-32
front_z=tube_rear-1.5
web=box(56,48,3,cq.Vector(pivot.x,.39*S,front_z))
support_fixings=[]
for dx in [-16,16]:
 p=cq.Vector(pivot.x+dx,.39*S,front_z)
 web=bore(web,p,4.5,Z)
 support_fixings.extend([cq.Solid.makeCylinder(4,3,p-Z*1.5,Z),cq.Solid.makeCylinder(7,4,p-Z*5.5,Z)])
support=cq.Compound.makeCompound([support,web,box(3,42,abs(pivot.z-front_z),cq.Vector(pivot.x+26,.39*S,(pivot.z+front_z)/2)),box(3,42,abs(pivot.z-front_z),cq.Vector(pivot.x-26,.39*S,(pivot.z+front_z)/2))])
# Separate abutment strip carries the outer cable sleeve.
strip=box(40,36,3,abutment)
strip=bore(strip,abutment,5.5,Z)
support=cq.Compound.makeCompound([support,strip,box(3,20,65,pivot+cq.Vector(-26,-26,32.5))])
add('handbrake_axle_support_and_cable_abutment_reference',support,(.29,.33,.28))
carrier=ring(13,8.5,8,pivot+cq.Vector(0,-26,0),Y)
carrier=carrier.fuse(cq.Solid.makeCylinder(8,65,pivot-Y*28,Y))
add('handbrake_balance_pivot_carrier_and_felt_envelopes',cq.Compound.makeCompound([carrier,ring(18,8.5,2,pivot-Y*20,Y)]))
upper=link(left_joint,right_joint,18,4,Y)
for j in [left_joint,right_joint]:upper=bore(upper,j,2.7,Y)
upper=bore(upper,pivot,8.5,Y,100)
lower=link(pivot-Y*20,input_joint,18,4,Y);lower=bore(lower,pivot,8.5,Y);lower=bore(lower,input_joint,3.5,Y)
spindle=ring(12,8.5,output_y-(pivot.y-20),cq.Vector(pivot.x,(output_y+pivot.y-20)/2,pivot.z),Y)
add('handbrake_balance_lever_two_output_eyes_and_input_arm_reference',cq.Compound.makeCompound([upper,lower,spindle]))

# Inner cable in three spans. An EXPLICIT gap at the body boundary prevents
# depicting an unsurveyed floor/tunnel crossing as a verified continuous route.
boundary_out=V([-.205,.405,-.78]);boundary_in=V([-.13,.32,-.80])
route=[boundary_in,V([-.13,.24,-.92]),V([.10,.16,-1.10]),V([.23,.16,-1.34]),abutment+Z*25,abutment]
outer=path(route,5)
inner_tools=path(route,2)
outer=outer.cut(inner_tools)
# End sleeves are open on the inner-cable line; actual adjuster thread omitted.
end_axis=(route[-1]-route[-2]).normalized()
outer=cq.Compound.makeCompound([outer,ring(8,2.2,18,abutment-end_axis*9,end_axis)])
add('handbrake_outer_conduit_and_rear_adjuster_sleeve_reference',outer,(.12,.15,.14))
inner=cq.Compound.makeCompound([path([control_cable,boundary_out],1.5),path(route,1.5),rod(abutment,input_joint,1.5),clevis(control_cable,X,(boundary_out-control_cable).normalized(),4.2),clevis(input_joint,Y,Z,3.5)])
add('handbrake_inner_cable_and_two_clevises_with_unresolved_body_crossing',inner)

fixings=[pin(control_cable,3.9,X,20),pin(input_joint,3.175,Y,18)]
rod_nodes={};brake_nodes={}
for side,label,start in [(1,'left',left_joint),(-1,'right',right_joint)]:
 end=V([side*BODY['published_reference_dimensions_mm']['rear_track_steel_wheels']/2/S-side*55/S,.39+43/S,-1.20-45/S])
 # Rear-of-banjo dogleg is an estimate. It avoids the static sphere comparison,
 # but is not a factory rod profile or suspension-travel envelope.
 route_rod=[start,cq.Vector(side*.34*S,output_y,pivot.z),end]
 shaft=path([route_rod[0]+(route_rod[1]-route_rod[0]).normalized()*9,route_rod[1],route_rod[-1]+(route_rod[-2]-route_rod[-1]).normalized()*12],3)
 shaft=cq.Compound.makeCompound([shaft,ring(8,2.7,4,start,Y),clevis(end,Y,(route_rod[-2]-end).normalized(),3.5)])
 add('handbrake_'+label+'_cross_rod_bored_inner_eye_and_outer_fork_reference',shaft)
 # Preserve former lever endpoints and boot/pivot cues; add the missing bore.
 a=V([side*(BODY['published_reference_dimensions_mm']['rear_track_steel_wheels']/2-30)/S,.39+70/S,-1.20-23/S])
 lever=link(a,end,12,3,Y);lever=bore(lever,end,3.5,Y)
 pivot_cue=rod(a-X*7,a+X*7,5)
 boot=box(10,20,25,V([side*(BODY['published_reference_dimensions_mm']['rear_track_steel_wheels']/2-23)/S,.39+70/S,-1.20-23/S]))
 add('handbrake_'+label+'_rear_brake_input_lever_boot_and_bored_rod_eye_reference',cq.Compound.makeCompound([lever,pivot_cue,boot]),(.31,.33,.30))
 fixings.extend([pin(start,2.38125,Y,16),pin(end,3.175,Y,20)])
 joints[label]=dict(inner_center_mm=list(start.toTuple()),outer_center_mm=list(end.toTuple()),inner_bore_radius_mm=2.7,outer_bore_radius_mm=3.5,pin_axis=[0,1,0],rod_path_mm=[list(p.toTuple()) for p in route_rod])
 # Construction passage checks exclude deliberately inserted pins.
 assert shaft.intersect(cq.Solid.makeCylinder(2.5,30,start-Y*15,Y)).Volume()<1e-5
 assert shaft.intersect(cq.Solid.makeCylinder(3.3,30,end-Y*15,Y)).Volume()<1e-5
 assert lever.intersect(cq.Solid.makeCylinder(3.3,30,end-Y*15,Y)).Volume()<1e-5
add('handbrake_six_shared_clevis_pins_and_two_support_fixing_envelopes',cq.Compound.makeCompound(fixings+support_fixings))

# Real source-solid checks against the retained static axle reference. These
# establish only drawing consistency, never installed or moving clearance.
axle=cq.importers.importStep(str(OUT/'rear-axle-reference.step')).val().translate(V([0,.39,-1.20]))
for name,shape,_ in parts:
 if 'cross_rod_' in name or 'outer_conduit_' in name:
  assert shape.intersect(axle).Volume()<1e-4,('static axle conflict',name)

limits='All installed stations, profiles, gauges, cable/rod lengths, bends, lever pose, spans, holes, fits, supports and clearances are estimated. No direct owner dimensions or fitted identities applied. The early 2A7308/ATA7320/2A7058/2A7057 topology and disc-wheel BTA498/BTA497 references are catalog comparisons; later CHA638/CHA634 cables are not combined with this architecture. Factory AHA6406 versus North American CHA567 control change at GAN6-166304 remains unresolved for this car. The two inner-cable segments end at an explicit unresolved body-crossing gap; existing body panels have not been drilled or certified to accommodate it. The conduit is joined straight annular envelopes, not a manufacturing bend-radius, tension or travel model. Rear rod doglegs clear the existing static banjo comparison only, not the moving axle/body or a factory profile. Rear external lever endpoints and boot/pivot cues are retained reference stations; their new eyes and pin axes are estimates, not verified handed lever shapes. Smooth ratchet sector, spindle, sleeve and fixing envelopes omit teeth, pawl engagement, threads, split pins and loaded retention. No equalization, leverage ratio, adjustment, stroke, brake operation, lifting/support method or verified removal path is simulated. Inspection separation is illustrative and breaks reference connections.'
meta=dict(units='mm',source_frame='Canonical body +X vehicle-left, +Y up, +Z front; millimetres.',factory_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=168',earlier_workshop_source='https://spridgetguru.com/AKD1775/AKD1775.pdf#page=126',workshop_scope='Earlier factory manual M.3 / Fig M.4 supports mechanical architecture only; not a 1500 procedure. Its non-adjustable factory rods remain distinct from modern adjustable supplier replacements.',owner_photos=['IMG_6341'],owner_dimensions_applied=False,basis='Factory p168 distinguishes tunnel bracket/control, early Bowden cable, axle support, separate carrier/balance lever and disc-/wire-wheel rods; later cable branches start GAN6-182001. Earlier factory Fig M.4 corroborates the axle abutment and compensator architecture. IMG_6341 gives uncalibrated cabin context, not a control identity or hidden route survey.',catalog_comparison_pin_diameters_mm=dict(control=7.9375,balance_input=6.35,rod_inner=4.7625,rod_outer=6.35),nodes=dict(control_pivot_mm=list(control.toTuple()),control_cable_eye_mm=list(control_cable.toTuple()),control_mount_bores_mm=mount_holes,balance_pivot_mm=list(pivot.toTuple()),balance_input_mm=list(input_joint.toTuple()),cable_abutment_mm=list(abutment.toTuple()),unresolved_body_crossing_mm=[list(boundary_out.toTuple()),list(boundary_in.toTuple())],conduit_centerline_mm=[list(p.toTuple()) for p in route],rear_joints=joints),limits=limits)
assembly=cq.Assembly(name='estimated_early_handbrake_control_and_compensator');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=shape.tessellate(.5,.15)
 meshes.append(dict(name=name,color=color,vertices=[round(v,4) for p in vs for v in p.toTuple()],triangles=[i for t in ts for i in t]))
assembly.export(str(OUT/'handbrake-reference.step'))
meta['parts']=meshes
(OUT/'handbrake-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'handbrake-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,four_rod_eye_passages=True,conduit_and_rods_clear_static_axle_reference=True,body_crossing_explicitly_unresolved=True)))
