"""Owner-visible front anti-roll architecture; nominal bar section is comparison only."""
from pathlib import Path
import json,math
import cadquery as cq
OUT=Path(__file__).parent;S=json.loads((OUT.parent/'body-data.json').read_text())['published_reference_dimensions_mm']['wheelbase']/2.4
R=(9/16*25.4)/2;parts=[];nodes={}
FULCRUM=json.loads((OUT/'front-fulcrum-reference.json').read_text())
def V(p):return cq.Vector(*(v*S for v in p))
def box(w,h,d,p):return cq.Workplane('XY').box(w*S,h*S,d*S).translate(V(p).toTuple()).val()
def cyl(r,l,p,axis=(1,0,0)):return cq.Solid.makeCylinder(r*S,l*S,V(p),cq.Vector(*axis))
def ring(ro,ri,l,p,axis=(1,0,0)):return cyl(ro,l,p,axis).cut(cyl(ri,l+.002,(V(p)-cq.Vector(*axis)*.001*S).multiply(1/S).toTuple(),axis))
def rod(a,b,r):
 a,b=V(a),V(b);d=b-a;return cq.Solid.makeCylinder(r*S,d.Length,a,d.normalized())
def add(name,shape,color=(.24,.27,.24)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color))
# Tangent bends replace spline interpolation; no rate or loaded twist is modeled.
for side,label in [(1,'left'),(-1,'right')]:
 a=FULCRUM['nodes'][label]['front_antiroll_attachment'];origin=cq.Vector(*a['plane_origin_mm']);t=cq.Vector(*a['tangent']);n=cq.Vector(*a['outward_normal'])
 upper=origin+t*a['upper_stud_hole_uv_mm'][0]+cq.Vector(0,a['upper_stud_hole_uv_mm'][1],0)+n*a['upper_ball_normal_offset_mm']
 lower=cq.Vector(upper.x,a['lower_ball_y_units']*S,upper.z);eye=lower+n*a['bar_eye_normal_offset_from_lower_ball_mm']
 nodes[label]=dict(attachment=a,frame_bearing_center_units=[side*.36,.23,1.54],upper_link_joint_units=list(upper.multiply(1/S).toTuple()),lower_link_joint_units=list(lower.multiply(1/S).toTuple()),bar_eye_units=list(eye.multiply(1/S).toTuple()),bar_eye_axis=list(n.toTuple()),wishbone_bracket_holes_units=[[v/S for v in h] for h in a['fixing_centers_mm']])
ends={k:V(v['bar_eye_units'])+cq.Vector(0,.018*S,0) for k,v in nodes.items()}
stations=[list(ends['right'].multiply(1/S).toTuple()),[-.50,.23,1.54],[.50,.23,1.54],list(ends['left'].multiply(1/S).toTuple())]
pts=list(map(V,stations));edges=[];prev=pts[0]
for a,b,c in zip(pts,pts[1:],pts[2:]):
 u=(b-a).normalized();v=(c-b).normalized();dot=u.dot(v);angle=math.acos(dot);radius=.035*S;d=radius*math.tan(angle/2);assert d<.49*min((b-a).Length,(c-b).Length)
 p=b-u*d;q=b+v*d;center=p+(v-u*dot).normalized()*radius;mid=center+((p-center).normalized()+(q-center).normalized()).normalized()*radius
 edges.extend([cq.Edge.makeLine(prev,p),cq.Edge.makeThreePointArc(p,mid,q)]);prev=q
edges.append(cq.Edge.makeLine(prev,pts[-1]));wire=cq.Wire.assembleEdges(edges)
bar=cq.Workplane(cq.Plane(origin=pts[0],normal=(pts[1]-pts[0]).normalized())).circle(R).sweep(wire,isFrenet=True).val()
eyes=[]
for label in ['left','right']:
 node=nodes[label];n=cq.Vector(*node['bar_eye_axis']);c=V(node['bar_eye_units'])
 eye=cq.Solid.makeCylinder(.020*S,.010*S,c-n*.005*S,n).cut(cq.Solid.makeCylinder(.0055*S,.020*S,c-n*.010*S,n));eyes.append(eye)
bar=cq.Compound.makeCompound([bar,*eyes]);add('antiroll_solid_bar_and_end_eye_comparison',bar)
bushes=[];clamps=[];frame_fix=[];stops=[]
for side,label in [(1,'left'),(-1,'right')]:
 x=side*.36
 bush=box(.050,.050,.050,[x,.23,1.54]).cut(cyl((R+.4)/S,.060,[x-.03,.23,1.54]))
 bushes.append(bush)
 clamp=[box(.060,.003,.060,[x,.2035,1.54])]
 for sign in [-1,1]:
  clamp.append(box(.060,.050,.003,[x,.23,1.54+sign*.0285]))
  z=1.54+sign*.041
  flange=box(.060,.003,.025,[x,.2535,z]).cut(cyl(.0035,.008,[x,.249,z],(0,1,0)));clamp.append(flange)
  frame_fix.extend([cyl(.003,.028,[x,.242,z],(0,1,0)),cyl(.007,.005,[x,.242,z],(0,1,0))])
 clamps.extend(clamp)
 for ax in [side*.3225,side*.3975]:
  collar=ring(.014,(R+.25)/S,.012,[ax-.006,.23,1.54]).cut(box(.018,.015,.004,[ax,.214,1.54]))
  stops.append(collar)

add('antiroll_frame_bearing_pair_comparison',cq.Compound.makeCompound(bushes),(.13,.15,.13))
add('antiroll_frame_strap_pair_comparison',cq.Compound.makeCompound(clamps))
add('antiroll_four_split_stop_collar_comparison',cq.Compound.makeCompound(stops))
add('antiroll_four_frame_fixing_envelopes',cq.Compound.makeCompound(frame_fix),(.46,.47,.42))
wish_fix=[]
for side,label in [(1,'left'),(-1,'right')]:
 node=nodes[label];upper=V(node['upper_link_joint_units']);lower=V(node['lower_link_joint_units']);n=cq.Vector(*node['bar_eye_axis'])
 link=[rod(node['lower_link_joint_units'],node['upper_link_joint_units'],.006)]
 for c,direction in [(upper,-1),(lower,1)]:
  ball=cq.Solid.makeSphere(.016*S,c)
  stud=cq.Solid.makeCylinder(3.8,34,c-n*direction,n*direction)
  link.extend([ball,stud])
 add('antiroll_'+label+'_two_joint_link_comparison',cq.Compound.makeCompound(link))
 a=node['attachment'];origin=cq.Vector(*a['plane_origin_mm']);t=cq.Vector(*a['tangent']);up=cq.Vector(0,1,0)
 back=origin+n*a['plate_back_normal_offset_mm'];poly=[back+t*u+up*v for u,v in a['plate_outline_uv_mm']]
 wire=cq.Wire.makePolygon(poly+[poly[0]]);plate=cq.Solid.extrudeLinear(wire,[],n*a['plate_thickness_mm'])
 for h in a['fixing_centers_mm']:
  c=cq.Vector(*h);tool=cq.Solid.makeCylinder(a['fixing_bore_radius_mm'],20,c-n*10,n);plate=plate.cut(tool)
  wish_fix.extend([cq.Solid.makeCylinder(3.2,12,c-n*8,n),cq.Solid.makeCylinder(6.5,4,c+n*3.4,n)])
  assert plate.intersect(cq.Solid.makeCylinder(4,18,c-n*9,n)).Volume()<1e-5
 stud=origin+t*a['upper_stud_hole_uv_mm'][0]+up*a['upper_stud_hole_uv_mm'][1]
 plate=plate.cut(cq.Solid.makeCylinder(a['upper_stud_hole_radius_mm'],20,stud-n*10,n))
 add('antiroll_'+label+'_wishbone_attachment_comparison',plate)
 assert plate.intersect(cq.Solid.makeCylinder(5,18,stud-n*9,n)).Volume()<1e-5
 c=V(node['bar_eye_units']);assert bar.intersect(cq.Solid.makeCylinder(4,24,c-n*12,n)).Volume()<1e-5
add('antiroll_six_wishbone_fixing_envelopes',cq.Compound.makeCompound(wish_fix),(.46,.47,.42))
assembly=cq.Assembly(name='photo_supported_front_antiroll_architecture');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=shape.tessellate(.6,.15)
 meshes.append(dict(name=name,color=color,vertices=[round(v,4) for p in vs for v in p.toTuple()],triangles=[i for tri in ts for i in tri]))
assembly.export(str(OUT/'antiroll-reference.step'))
limits='Owner photos establish visible bar/clamp/link presence, not calibrated diameter, part number, material, rubber condition or loaded geometry. The 9/16-inch (14.2875 mm) solid shaft is an AHA7013 catalog comparison, not this car’s measured bar. All bends, eyes, bearing/strap/collar sections, link/joint shapes, mounting holes/stations, fasteners and fitted relationships are estimates. Left/right replication is a construction assumption. Frame-side stations and four source frame mesh records are unchanged. IMG_6914 corrects the prior horizontal bracket to a flat forward-wall plate. The wishbone front wall is extended to an estimated 40 mm depth and bored at three declared reference stations; link/stud axes and bar ends follow this local construction. Its flat outline, thickness, hole pattern, web depth, fore/aft stations, handed replication and all local running allowances are estimates, not direct dimensions, a fitted drilling pattern or proof of a contact/load path. No torsion stiffness, rubber compression, articulation, steering/suspension travel, interference, support load, safe release sequence, torque or verified removal path. Separate display offsets are illustrative.'
meta=dict(bracket_comparison_source='https://rimmerbros.com/Item--i-AHA7028',attachment_correction='Old horizontal pattern over the outer-joint gap is superseded. Owner IMG_6914 and factory p144 establish a forward-wall flat plate; new positions/sections remain estimates.',units='mm',source_frame='Canonical body +X vehicle-left, +Y up, +Z front in mm.',owner_photos=['IMG_6914','IMG_6919','IMG_6910'],owner_evidence='IMG_6914 shows the bent transverse bar, frame clamp, vertical link and flat plate on the wishbone forward-facing wall; IMG_6919 adds front underside relationships. These supplied views were reconciled before adding the previously absent assembly.',factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=144','https://www.spridgets.de/downloads/Parts-1977.pdf#page=317'],catalog_source='https://www.mgocspares.co.uk/product/aha7013/anti-roll-bar-mid-9-16',basis='Factory p144 separates AHA7013 bar, AHA7011/12 handed links, AHA7028 brackets, AHH6541 bearings, 1B7356 straps, four stops and fixing counts. Its optional/UK-standard note is not applied as a North American fitment claim. Owner photo establishes presence on this car; exact variants remain unknown.',comparison_diameter_mm=R*2,diameter_basis='9/16-inch AHA7013 catalog comparison only; not direct owner measurement.',estimated_path_corner_stations_units=stations,estimated_bend_radius_mm=.035*S,nodes=nodes,limits=limits,parts=meshes)
(OUT/'antiroll-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'antiroll-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,bar_eye_and_bracket_bores_open=True,owner_visible_presence=True,catalog_diameter_only=R*2,forward_wall_attachment=True)))
