"""Rear gearbox isolation and body attachment comparison, not fitted support."""
from pathlib import Path
import json,math
import cadquery as cq
from gearbox_reference_layout import REAR_SUPPORT_CENTERS_LOCAL_MM
OUT=Path(__file__).parent;B=json.loads((OUT.parent/'body-data.json').read_text());R=B['rear_power_support_reference'];S=2032/2.4
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);parts=[];contacts={};nodes=R['estimated_nodes']
for label,center in REAR_SUPPORT_CENTERS_LOCAL_MM.items():
 assert (cq.Vector(*nodes[label]['rubber_center_body_mm'])-cq.Vector(center[0],center[1]+.39*S,center[2])).Length<1e-8
def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p.toTuple()).val()
def cyl(r,l,p,a):return cq.Solid.makeCylinder(r,l,p,a)
def ring(ro,ri,l,p,a):return cyl(ro,l,p,a).cut(cyl(ri,l+2,p-a,a))
def comp(xs):return cq.Compound.makeCompound(xs)
def put(n,s,c=(.28,.33,.28)):
 assert s.isValid() and s.Volume()>0,n
 parts.append((n,s,c));return s
def hexagon(ro,ri,l,p,a):return cq.Workplane(cq.Plane(origin=p,normal=a)).polygon(6,2*ro).extrude(l).val().cut(cyl(ri,l+2,p-a,a))
def bore(s,p,r,a,length=60):return s.cut(cyl(r,length,p-a*length/2,a))
z=-.16*S;g=R['estimated_crossmember_gauge_mm'];base_y=.13*S;bottom=.07*S;floor_under=.35*S-1.5
# Retain the old low crossmember envelope, replace solid slabs with a hollow
# section and open raised end channels. Top returns meet the floor underside
# as a construction choice: old top .345*S -> floor underside (+2.7333 mm).
beam=box(1.24*S,base_y-bottom,.20*S,cq.Vector(0,(base_y+bottom)/2,z)).cut(box(1.24*S+2,base_y-bottom-2*g,.20*S-2*g,cq.Vector(0,(base_y+bottom)/2,z)))
for sign in [-1,1]:
 cx=sign*.56*S;outer=sign*.62*S;lo=base_y-g;high=floor_under-g
 riser=[box(g,high-lo,.20*S,cq.Vector(outer-sign*g/2,(lo+high)/2,z))]
 for dz in [-.10*S+g/2,.10*S-g/2]:riser.append(box(.12*S,high-lo,g,cq.Vector(cx,(lo+high)/2,z+dz)))
 riser.append(box(.20*S,g,.24*S,cq.Vector(cx,floor_under-g/2,z)))
 for s in riser:beam=beam.fuse(s)
for n in nodes.values():beam=bore(beam,cq.Vector(*n['base_fixing_axis_body_mm']),n['base_bore_radius_mm'],Y,140)
beam=put('rear_power_hollow_crossmember_and_open_floor_returns',beam.clean())
# Thin connected cradle: base, two outer webs, inclined rubber faces and two
# narrow tunnel-side ears. Body stations remain the existing drawing frame.
t=R['estimated_fold_gauge_mm'];cradle=box(.40*S,t,.18*S,cq.Vector(0,base_y+t/2,z));faces={}
for sign,label in [(1,'left'),(-1,'right')]:
 n=nodes[label];c=cq.Vector(*n['rubber_center_body_mm']);a=cq.Vector(*n['rubber_axis_outward']);tangent=cq.Vector(sign,1,0).normalized()
 q=c+a*(.029*S+t/2);faces[label]=q
 # Inner face meets the retained outboard bonded-face datum; this replaces
 # the former 15.24 mm slab with a 3 mm plate and shifts its middle plane.
 face=cq.Workplane(cq.Plane(origin=q,xDir=tangent,normal=a)).rect(.11313708499*S,.18*S).extrude(t/2,both=True).val()
 face=bore(face,q,4.5,a);outer_x=q.x+sign*(.11313708499*S/2/math.sqrt(2))
 web=box(t,188,.18*S,cq.Vector(outer_x,base_y+188/2,z))
 # Ear outer face follows the retained tunnel inner-surface comparison only
 # at its one fixing station. Curvature/loaded attachment is not registered.
 bp=cq.Vector(*n['tunnel_fixing_axis_body_mm']);ear_x=bp.x-sign*t/2
 ear=box(t,95,36,cq.Vector(ear_x,287.5,bp.z));ear=bore(ear,bp,4.5,X)
 bridge=box(abs(ear_x-outer_x)+t,t,36,cq.Vector((ear_x+outer_x)/2,287.5,bp.z))
 cradle=cradle.fuse(face).fuse(web).fuse(ear).fuse(bridge)
 cradle=bore(cradle,cq.Vector(*n['base_fixing_axis_body_mm']),5.2,Y,30)
 # The inclined face intersects the ear web, keeping it one source solid.
 n['cradle_face_axis_body_mm']=list(q.toTuple());n['rubber_retained_half_length_mm']=.021*S;n['bonded_face_outer_station_mm']=.029*S
 n['cradle_face_middle_plane_shift_outward_mm']=(.029*S+t/2)-(.02*S*math.sqrt(2))
assert len(cradle.Solids())==1
cradle=put('rear_power_connected_folded_cradle_inclined_faces_and_tunnel_ears',cradle.clean())
for sign,label in [(1,'left'),(-1,'right')]:
 n=nodes[label];c=cq.Vector(*n['rubber_center_body_mm']);a=cq.Vector(*n['rubber_axis_outward']);q=cq.Vector(*n['cradle_face_axis_body_mm'])
 rubber=put('rear_power_'+label+'_unloaded_rubber_body_comparison',cyl(.034*S,.042*S,c-a*.021*S,a),(.13,.17,.14))
 plates=[];studs=[]
 for end in [-1,1]:
  plates.append(cyl(.036*S,.008*S,c+a*end*.025*S-a*.004*S,a))
  studs.append(cyl(3.96875,.030*S,c+a*end*.025*S,a*end))
 metal=comp(plates)
 assert rubber.intersect(metal).Volume()<1e-5
 assert cradle.intersect(metal).Volume()<1e-5
 put('rear_power_'+label+'_two_bonded_metal_face_envelopes',metal,(.48,.51,.45))
 assert comp(studs).intersect(cyl(3,30,c-a*15,a)).Volume()<1e-5
 put('rear_power_'+label+'_two_separate_opposing_stud_envelopes',comp(studs),(.58,.60,.52))
 outer=q+a*t/2
 washer=ring(9,4.5,2,outer,a);nut=hexagon(8,4.3,6,outer+a*2,a)
 put('rear_power_'+label+'_cradle_washer_and_unthreaded_nut',comp([washer,nut]),(.55,.57,.49))
 assert cradle.intersect(cyl(4.2,40,q-a*20,a)).Volume()<1e-5
bolts=[];washers=[]
for label,n in nodes.items():
 p=cq.Vector(*n['base_fixing_axis_body_mm']);headface=cq.Vector(p.x,bottom-3.5,z)
 bolt=cyl(4.7625,R['catalog_long_bolt_underhead_length_mm'],headface,Y).fuse(hexagon(8.5,.01,8,headface-Y*8,Y))
 bolts.append(bolt);washers.extend([ring(10,5.2,2,cq.Vector(p.x,bottom-2,z),Y),ring(9,5.2,1.5,headface,Y)])
 assert beam.intersect(cyl(5,140,p-Y*70,Y)).Volume()<1e-5
 assert cradle.intersect(cyl(5,25,p-Y*10,Y)).Volume()<1e-5
put('rear_power_two_unthreaded_long_crossmember_bolt_envelopes',comp(bolts),(.58,.60,.52))
put('rear_power_long_bolt_plain_and_unloaded_lock_washers',comp(washers),(.54,.56,.48))
sidebolts=[];sidewashers=[]
for label,n in nodes.items():
 p=cq.Vector(*n['tunnel_fixing_axis_body_mm']);a=cq.Vector(*n['tunnel_fixing_axis_outward'])
 # Head at the cabin side; true access and thread ownership remain unresolved.
 bearing=p+a*5
 sidebolts.append(cyl(3.96875,19.05,bearing-a*19.05,a).fuse(hexagon(7,.01,5,bearing,a)))
 sidewashers.append(ring(10,4.5,2,bearing-a*2,a))
 assert cradle.intersect(cyl(4.3,40,p-a*20,a)).Volume()<1e-5
put('rear_power_two_tunnel_side_screw_envelopes',comp(sidebolts),(.58,.60,.52))
put('rear_power_two_tunnel_side_washer_envelopes',comp(sidewashers),(.54,.56,.48))
# Limited source contact sampling: installed-frame neighbors, never a physical
# clearance result. Engine/transmission and all their stations are untouched.
neighbors=[]
for filename,offset in [('cabin-understructure-reference.step',(0,0,0)),('transmission-reference.step',(0,.39*S,0)),('underfloor-exhaust-reference.step',(0,0,0))]:
 source=cq.Assembly.importStep(str(OUT/filename))
 for name,obj in source.objects.items():
  if obj.obj is None:continue
  s=obj.obj.val() if isinstance(obj.obj,cq.Workplane) else obj.obj
  if not isinstance(s,cq.Shape):continue
  neighbors.append((name,s.translate(offset)))
for name,a,_ in parts:
 for other,b in neighbors:
  ba,bb=a.BoundingBox(),b.BoundingBox()
  if not all(getattr(ba,k+'min')<=getattr(bb,k+'max') and getattr(bb,k+'min')<=getattr(ba,k+'max') for k in ['x','y','z']):continue
  v=round(a.intersect(b).Volume(),4)
  if v>1e-4:contacts[name+'__'+other]=v
extension=next(s for n,s in neighbors if n=='gearbox_extension_reference')
for name,s,_ in parts:
 assert extension.intersect(s).Volume()<1e-5,('extension/support reconstruction overlap',name)
limits='No direct owner rear-support dimensions or fitted rubber/restraint identity applied. Factory CHA266, paired122689/CHA615 and body fixing roles remain comparisons; donor25147 side photographs supply unscaled compact rubber/extension-pad architecture. Rubber centers move fromX±169.3333 to estimatedX±80 mm; localY=-50.8/Z=-135.4667 mm, rubber radius/incline and existing base/tunnel body fixing axes remain drawing datums. New pad positions,64x68 mm faces,40 mm root extrusion andR4.5 plain bores do not establish fitted casting sections, threads, engagement or loaded seating. Metal end plates meet the two rubber faces without modeled bond or compression. Opposing studs remain separate smooth envelopes. Cradle base narrows to338.6667 mm; moved web/face planes and provisional return bridges reach the retained tunnel-side ears. All profiles, gauges, locations and body registration remain estimates, not a drilling/repair map. Crossmember/floor returns, cabin and exhaust geometry stay. Supplier BH60628188.9 mm under-head length is a comparison, not fitted hardware, torque or engagement. Nuts/washers are unloaded and all threads absent. North American CHA463/487/488 and UKC5254 remain unselected roles. Static cabin/floor/exhaust source contacts are reconstruction discrepancies, not actual-car interference or operating clearance. No safe jack/support point, lifting height, strength, mount travel, release order or verified replacement/removal path. General separation is illustrative.'
meta=dict(units='mm',source_frame='Canonical body mm +X vehicle-left +Y up +Z front.',factory_source=R['factory_source'],supplier_sources=R['supplier_sources']+['https://www.rwautoclassics.com/viewitem/?id=25147'],owner_evidence_scope=R['owner_evidence_scope'],owner_dimensions_applied=False,basis='Factory 1500 rear mounting bracket, paired isolators and body fixing roles, with unscaled donor photographs showing compact inclined rubber/extension-pad architecture. Revised rubber centers/cradle profiles and shared pad bores are construction estimates; old body fixing axes remain unregistered.',registration_status=R['registration_status'],proportion_revision=R['proportion_revision'],nodes=nodes,sampled_source_contacts_mm3=contacts,limits=limits)
a=cq.Assembly(name='unregistered_rear_gearbox_isolation_and_body_attachment');meshes=[]
for name,s,color in parts:
 a.add(s,name=name,color=cq.Color(*color));vs,ts=s.tessellate(.5,.16);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]))
a.export(str(OUT/'rear-power-support-reference.step'));meta['parts']=meshes;(OUT/'rear-power-support-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n');restored=cq.importers.importStep(str(OUT/'rear-power-support-reference.step')).val();assert restored.isValid()
assert all(s.isValid() and s.Volume(1e-9)>0 for s in restored.Solids())
assert len(restored.Solids())==sum(len(s.Solids()) for _,s,_ in parts)
assert math.isclose(restored.Volume(1e-9),sum(s.Volume(1e-9) for _,s,_ in parts),rel_tol=1e-7)
print(json.dumps(dict(meshes=len(parts),solids=len(restored.Solids()),step_valid=True,cradle_solid_count=len(cradle.Solids()),shared_base_and_tunnel_ear_passages=True,extension_interfaces_nonintersecting=True,rubber_end_faces_nonintersecting=True,source_contacts=contacts)))
