"""Rear damper/strap supports and rubber-bumper floor beams; estimated geometry."""
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
SKIN=BODY['boot_compartment_reference']['estimated_floor_and_lid_skin_offset_mm'];T=1.5
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);parts=[];nodes={};reliefs=[]
def V(p):return cq.Vector(*(v*S for v in p))
def box(w,h,d,c):return cq.Workplane('XY').box(w,h,d).translate(c.toTuple()).val()
def prism(ps,delta):return cq.Solid.extrudeLinear(cq.Wire.makePolygon(ps+[ps[0]]),[],delta)
def ring(ro,ri,l,p,axis):return cq.Solid.makeCylinder(ro,l,p,axis).cut(cq.Solid.makeCylinder(ri,l+2,p-axis,axis))
def add(name,shape,color=(.27,.34,.28)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color));return shape
def floor_y(z):
 return (.64 if z>=-1.32 else .49 if z<=-1.58 else .49+.15*(z+1.58)/.26)*S-SKIN
def capsule(a,b,r,t,axis):
 plane=cq.Plane(origin=(a+b)*.5,xDir=(b-a).normalized(),normal=axis)
 s=cq.Workplane(plane).rect((b-a).Length,2*r).extrude(t/2,both=True).val()
 return s.fuse(cq.Solid.makeCylinder(r,t,a-axis*t/2,axis)).fuse(cq.Solid.makeCylinder(r,t,b-axis*t/2,axis))
for side,label in [(1,'left'),(-1,'right')]:
 # Formed vertical support replaces a pad above the old damper. Its top follows
 # the retained floor step; all sections and stations are construction choices.
 px=side*.41*S;zs=[-1.68,-1.58,-1.32,-1.16]
 top=[cq.Vector(px,floor_y(z),z*S) for z in zs]
 bottom=[V([side*.41,.455,-1.16]),V([side*.41,.455,-1.20]),V([side*.41,.41,-1.45]),V([side*.41,.41,-1.68])]
 web=prism(top+bottom,X*side*3)
 returns=[]
 for a,b in zip(top,top[1:]):returns.append(prism([a,b,b+X*side*.035*S,a+X*side*.035*S],-Y*T))
 bottom_return=prism([V([side*.41,.41,-1.68]),V([side*.41,.41,-1.45]),V([side*.43,.41,-1.45]),V([side*.43,.41,-1.68])],Y*T)
 panel=cq.Compound.makeCompound([web,*returns,bottom_return])
 holes=[V([side*.41,.455,z]) for z in [-1.66,-1.46]]
 strap=V([side*.36,.49,-1.20]);strap_axis_on_panel=cq.Vector(px,strap.y,strap.z)
 for p,r in [(p,5.5) for p in holes]+[(strap_axis_on_panel,9)]:panel=panel.cut(cq.Solid.makeCylinder(r,30,p-X*15,X))
 add('rear_'+label+'_formed_damper_and_upper_strap_body_panel_comparison',panel)
 # New ears meet the existing damper-box face without changing its body, arm,
 # shaft or link station. This is not a copied Armstrong casting or bolt map.
 ear_t=.008*S;ear_x=px+side*(3+ear_t/2);ears=[];fixings=[]
 for p in holes:
  a=cq.Vector(ear_x,p.y,p.z);toward=.04*S if p.z<-1.56*S else -.04*S;b=a+Z*toward
  ear=capsule(a,b,14,ear_t,X).cut(cq.Solid.makeCylinder(5.5,30,p-X*15,X));ears.append(ear)
  start=p;out=p+X*side*(3+ear_t)
  fixings.extend([cq.Solid.makeCylinder(4.75,3+ear_t+9,start,X*side),cq.Solid.makeCylinder(8,4,start-X*side*4,X*side),ring(9,5,1,out,X*side),ring(8.5,5,6,out+X*side,X*side)])
 add('rear_'+label+'_damper_two_bored_mount_ear_envelopes',cq.Compound.makeCompound(ears),(.36,.38,.34))
 add('rear_'+label+'_two_damper_body_fixing_envelopes',cq.Compound.makeCompound(fixings),(.50,.52,.46))
 # Retain upper strap center and old drawing pin radius, extend only the
 # estimated outer terminal to pass the new panel. Threads remain unmodeled.
 pin_r=.010*S;start=strap-X*side*.05*S;length=.10*S+15
 pin=cq.Solid.makeCylinder(pin_r,length,start,X*side)
 head=cq.Solid.makeCylinder(13,4,start-X*side*4,X*side)
 washer=ring(13,8.7,2,strap_axis_on_panel+X*side*3,X*side)
 nut=ring(12,8.7,7,strap_axis_on_panel+X*side*5,X*side)
 add('rear_'+label+'_upper_strap_pin_and_smooth_retainer_envelopes',cq.Compound.makeCompound([pin,head,washer,nut]),(.48,.50,.44))

 # Factory GAN6 CZJ644/645 beam roles, not an invented detachable chassis rail.
 # The generic open channel sits above the low boot floor and ends against
 # the existing rear-panel/stud reference. Endplate includes a lower drop.
 cx=side*.43*S;rear=-2.02*S;end=-1.58*S;width=.11*S;height=.12*S;skin=T
 base_y=.49*S
 base=box(width,skin,end-rear,cq.Vector(cx,base_y+skin/2,(rear+end)/2))
 sides=[box(skin,height,end-rear,cq.Vector(cx+sign*(width/2-skin/2),base_y+height/2,(rear+end)/2)) for sign in [-1,1]]
 rear_plane=rear+SKIN
 endplate=box(.11*S,.18*S,3,cq.Vector(cx,.52*S,rear_plane+1.5))
 beam_holes=[V(p) for p in BODY['native_rear_bumper_placement']['body_holes_units'] if p[0]*side>0]
 bore=BODY['native_rear_bumper_placement']['body_hole_radius_units']*S
 for p in beam_holes:endplate=endplate.cut(cq.Solid.makeCylinder(bore,16,cq.Vector(p.x,p.y,rear-2),Z))
 # Open rear T relief: narrow lower-stud passage plus a short wide endplate
 # crossing. These construction cuts are not factory floor apertures.
 slot_r=8;depth=40;wide_depth=SKIN+3+1
 slot=box(2*slot_r,140,depth,cq.Vector(cx,base_y,rear+depth/2))
 end_slot=box(.11*S,140,wide_depth,cq.Vector(cx,base_y,rear+wide_depth/2))
 base=base.cut(slot).cut(end_slot)
 beam=cq.Compound.makeCompound([base,*sides,endplate])
 add('rear_'+label+'_rubber_bumper_open_floor_beam_and_bored_endplate_comparison',beam)
 for p in holes:
  gauge=cq.Solid.makeCylinder(5.2,30,p-X*15,X)
  assert panel.intersect(gauge).Volume()<1e-5
  assert all(ear.intersect(gauge).Volume()<1e-5 for ear in ears)
 assert panel.intersect(cq.Solid.makeCylinder(8.8,40,strap_axis_on_panel-X*20,X)).Volume()<1e-5
 for p in beam_holes:assert endplate.intersect(cq.Solid.makeCylinder(bore-.2,12,cq.Vector(p.x,p.y,rear-1),Z)).Volume()<1e-5
 reliefs.append(dict(side=label,center_x_units=side*.43,rear_z_units=-2.02,narrow_half_width_mm=slot_r,narrow_depth_mm=depth,wide_half_width_units=.055,wide_depth_mm=wide_depth,basis='Construction relief around retained lower bumper stud/endplate, not a fitted factory opening or cutting map.'))
 nodes[label]=dict(support_panel_x_units=side*.41,estimated_top_floor_stations_zy_mm=[[z*S,floor_y(z)] for z in zs],damper_fixing_centers_mm=[list(p.toTuple()) for p in holes],damper_bore_radius_mm=5.5,upper_strap_center_units=[side*.36,.49,-1.20],upper_strap_panel_axis_mm=list(strap_axis_on_panel.toTuple()),strap_panel_bore_radius_mm=9,retained_upper_strap_pin_radius_mm=pin_r,damper_ear_thickness_mm=ear_t,beam_center_x_units=side*.43,beam_y_bounds_units=[.49,.61],beam_z_bounds_units=[-2.02,-1.58],beam_body_fixing_centers_mm=[list(p.toTuple()) for p in beam_holes],beam_body_bore_radius_mm=bore,beam_rear_plate_z_bounds_mm=[rear_plane,rear_plane+3])
limits='No direct owner dimensions or fitted panel identities are applied. Manufacturer AHA7879/80 damper mount and factory/Mfr CZJ644/645 rear bumper-beam roles are comparisons, not copies or verified repairs. Every profile, gauge, return, axis, bolt map, ear, foot, beam channel/endplate, join and body pose is estimated. Existing damper body/arm/link, upper strap center, axle/spring stations and bumper stud axes are retained drawing datums. Damper ears and four smooth fixing envelopes add shared construction bores outside the old damper box, not an actual casting or bolt map. Upper strap pin retains its old estimated radius/center but gains an outer extension/retainers to pass the new panel; this does not establish fitted hardware, loaded slack, travel or retention. Rear beams occupy a generic open channel above the retained low floor. Their estimated rear endplates follow existing bumper stud axes; two rear-open T reliefs in beam floor/floor panel and narrow matching local-doubler reliefs accommodate the retained low stud reference. These are declared reconstruction corrections, not observed factory openings, fabrication/repair templates or fitted clearance allowances. The old bumper/floor datum conflict is not evidence about the car. Body panels are welded references and remain fixed; added ears share damper display movement, other hardware separates illustratively. No continuous proven load path, weld/lap sequence, impact/jacking/towing/support strength, corrosion/repair state, actual fastener/thread, clamp preload, torque, tool access, suspension travel or verified replacement/cutting/welding/removal path. Bump-stop attachment/strike face and fitted body/axle registration remain unresolved.'
meta=dict(units='mm',source_frame='Canonical body +X vehicle-left, +Y up, +Z front; mm.',factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=151','https://www.spridgets.de/downloads/Parts-1977.pdf#page=233'],manufacturer_sources=['https://www.bmh-ltd.com/vehicle-parts/mg-midget-parts/mg-midget-front-centre-body/','https://www.bmh-ltd.com/vehicle-parts/mg-midget-parts/mg-midget-rear-body/'],supplier_photo='https://rimmerbros.com/Item--i-AHA7880',owner_photos=['IMG_6329','IMG_6341','IMG_6919'],owner_dimensions_applied=False,basis='Factory p151 separates two damper/body bolts per side; p233 GAN6 rear luggage-floor assembly separates CZJ644/645 bumper mounting beams. Manufacturer F25 AHA7879/80 applies to semi-elliptic cars, distinct from quarter-elliptic AHA7306/07. Supplier AHA7880 photo corroborates formed web, paired damper-hole role and low attachment; existing upper strap drawing station is retained as an estimated relationship, not fitted identification. Owner side/cabin/front-underside photos do not survey hidden rear supports.',nodes=nodes,estimated_floor_reliefs=reliefs,limits=limits)
assembly=cq.Assembly(name='estimated_rear_damper_strap_and_bumper_floor_supports');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 t in ts for i in t]))
assembly.export(str(OUT/'rear-frame-reference.step'));meta['parts']=meshes
(OUT/'rear-frame-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'rear-frame-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(source_parts=len(parts),solids=len(restored.Solids()),step_valid=True,four_shared_damper_passages=True,two_upper_strap_panel_passages=True,four_beam_endplate_passages=True)))
