"""Footwell and hinge-pillar relationships; all fitted panel sections are unmeasured."""
from pathlib import Path
import json
import cadquery as cq
from front_wing_geometry import side_x,absolute_tessellation
from body_rail_junction import trim as trim_rail_body,metadata as rail_body_metadata
from footwell_tunnel_junction import outer_envelope,closing as tunnel_closing,metadata as tunnel_junction_metadata
OUT=Path(__file__).parent;BODY=json.loads((OUT.parent/'body-data.json').read_text());S=BODY['published_reference_dimensions_mm']['wheelbase']/2.4;T=1.5
parts=[];nodes={}
def V(p):return cq.Vector(*(x*S for x 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 prism(points,delta):
 pts=list(map(V,points));wire=cq.Wire.makePolygon(pts+[pts[0]])
 return cq.Solid.extrudeLinear(wire,[],cq.Vector(*delta))
def add(name,shape,color=(.23,.30,.24)):
 if name=='front_bulkhead_with_retained_apertures_and_footwell_mouths_reference' or '_footwell_front_and_top_'in name or '_inner_toeboard_'in name:shape=trim_rail_body(shape)
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color))
# Same main outline and rear plane as the former zero-thickness body reference.
former_outline=[[-.67,.35],[-.34,.35],[-.34,.66],[0,.76],[.34,.66],[.34,.35],[.67,.35],[.67,1.04],[-.67,1.04]]
outline=[[-.67,.35],[.67,.35],[.67,1.04],[-.67,1.04]]
tunnel_envelope=outer_envelope()
bulk=prism([[x,y,.13] for x,y in outline],[0,0,T]).cut(tunnel_envelope)
for side in [-1,1]:bulk=bulk.cut(box(.30,.37,.012,[side*.51,.535,.13]))
# Canonical +X vehicle-left; this preserves the previous LHD aperture.
bulk=bulk.cut(box(.32,.24,.012,[.50,.84,.13]))
wp=BODY['native_wiper_drive_placement']['bulkhead_crossing_units'];wr=BODY['native_wiper_drive_placement']['bulkhead_hole_radius_units']
cp=BODY['native_fascia_placement']['column_body_center_units'];cr=BODY['native_fascia_placement']['column_body_opening_radii_units']
for center,radius in [(wp,wr),([.28,.98,.13],.009)]:bulk=bulk.cut(cq.Solid.makeCylinder(radius*S,T+4,V([center[0],center[1],.13])-cq.Vector(0,0,2),cq.Vector(0,0,1)))
ellipse=cq.Workplane('XY',origin=(cp[0]*S,cp[1]*S,.13*S-2)).ellipse(cr[0]*S,cr[1]*S).extrude(T+4).val();bulk=bulk.cut(ellipse)
add('front_bulkhead_with_retained_apertures_and_footwell_mouths_reference',bulk)
for side,label in [(1,'left'),(-1,'right')]:
 def p(x,y,z):return [side*x,y,z]
 # Front wall and roof replace the old diagonal toe-board sheet.
 front=prism([p(x,y,.35) for x,y in [(.36,.35),(.66,.35),(.66,.72),(.36,.72)]],[0,0,-T])
 roof=prism([p(x,.72,z) for x,z in [(.36,.13),(.66,.13),(.66,.35),(.36,.35)]],[0,-T,0])
 if side>0:
  # Factory p163 identifies the body gasket and8 base fixing roles. This
  # projected mating construction uses the EXISTING unmeasured gasket,
  # without calibrating body height, support strength or a fitted hole map.
  pedal=cq.Assembly.importStep(str(OUT/'pedal-support-reference.step'))
  gasket=pedal.objects['pedal_uncompressed_body_gasket_comparison'].obj
  if isinstance(gasket,cq.Workplane):gasket=gasket.val()
  lower=cq.Workplane(obj=gasket).faces('<Y').val();gb=gasket.BoundingBox()
  platform=cq.Solid.extrudeLinear(lower.outerWire(),lower.innerWires(),cq.Vector(0,-T,0))
  mouth=max(lower.innerWires(),key=lambda w:cq.Face.makeFromWires(w).Area())
  mouth_tool=cq.Solid.extrudeLinear(mouth.translate((0,-40,0)),[],cq.Vector(0,80,0))
  roof=roof.cut(mouth_tool)
  y0=.72*S-T;y1=gb.ymin;z0=.13*S;z1=gb.zmax
  def body_sheet(ps,delta):return cq.Solid.extrudeLinear(cq.Wire.makePolygon([cq.Vector(*p)for p in ps+[ps[0]]]),[],cq.Vector(*delta))
  step=body_sheet([(gb.xmin,y0,z1),(gb.xmax,y0,z1),(gb.xmax,y1,z1),(gb.xmin,y1,z1)],(0,0,-T))
  skirts=[body_sheet([(x,y0,z0),(x,y0,z1),(x,y1,z1),(x,y1,z0)],(dx,0,0))for x,dx in [(gb.xmin,T),(gb.xmax,-T)]]
  shell=front.fuse(roof,platform,step,*skirts).clean()
  assert shell.isValid()and len(shell.Solids())==1
  pedal_plinth=dict(factory_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=163',basis='Body mounting/gasket architecture only. Project the retained native body-gasket footprint, central mouth and8 estimated base axes onto an unmeasured body platform, joined by an estimated front step/side returns to the old roof.',owner_dimensions_applied=False,estimated_platform_top_y_mm=gb.ymin,estimated_platform_gauge_mm=T,retained_roof_top_y_mm=.72*S,estimated_step_height_mm=gb.ymin-.72*S,projected_gasket_bounds_body_mm=dict(x=[gb.xmin,gb.xmax],z=[gb.zmin,gb.zmax]),base_fixing_axes_body_mm=json.loads((OUT/'pedal-support-reference.json').read_text())['base_fixing_axes_body_mm'],body_receiver_status='Shared reference bores only; actual body receiver threads/captive fittings and welded reinforcement unknown.',geometry_limits='Rearward platform outline/extent, section, body ownership and joint to the existing roof are construction hypotheses. Existing gasket/base axes are themselves unmeasured. No fitted panel dimensions, support strength, seam/weld map or repair/drilling template is established.')
 else:shell=cq.Compound.makeCompound([front,roof])
 add('front_'+label+'_footwell_front_and_top_comparison',shell)
 outer=prism([p(.66,y,z) for z,y in [(.13,.35),(.35,.35),(.35,.72),(.13,.72)]],[side*T,0,0])
 jamb_join=prism([p(x,y,.23) for x,y in [(.66,.35),(.68,.35),(.68,.72),(.66,.72)]],[0,0,T])
 add('front_'+label+'_footwell_outer_side_and_jamb_return_reference',cq.Compound.makeCompound([outer,jamb_join]))
 # Reference shelves/collar/lip replace the disconnected flat inner wall.
 add('front_'+label+'_inner_toeboard_and_floor_return_reference',tunnel_closing(side,tunnel_envelope))
 # Match the declared body-hinge leaves (5.08 mm thick), not an owner survey.
 # Inner reinforcement follows the outer camber inboard; the former flat
 # X=.7525 wall protruded through the visible wing.12mm is an estimated
 # construction inset, not a measured clearance or pressed section.
 pillar_recess=12.
 def outer_x(y):return side_x(y,.153)-pillar_recess/S
 ys=[.35+(1.005-.35)*i/32 for i in range(33)]
 channel_face=prism([p(.68,.35,.153)]+[p(outer_x(y),y,.153)for y in ys]+[p(.68,1.005,.153)],[0,0,T])
 hinge_centers=[]
 for y in [.62,.91]:
  for dy in [-.018,.018]:
   c=p(.70,y+dy,.153);hinge_centers.append(c)
   hole=cq.Solid.makeCylinder(.003*S,T+4,V(c)-cq.Vector(0,0,2),cq.Vector(0,0,1));channel_face=channel_face.cut(hole)
 inner_return=prism([p(.68,y,z)for z,y in [(.153,.35),(.23,.35),(.23,1.005),(.153,1.005)]],[side*T,0,0])
 wires=[]
 for y in ys:
  q=[V(p(x,y,z))for x,z in [(outer_x(y),.153),(outer_x(y),.23),(outer_x(y)-T/S,.23),(outer_x(y)-T/S,.153)]]
  wires.append(cq.Wire.makePolygon(q+[q[0]]))
 outer_return=cq.Solid.makeLoft(wires,ruled=True)
 returns=[inner_return,outer_return]
 foot=prism([p(x,.35,z) for x,z in [(.68,.153),(outer_x(.35),.153),(outer_x(.35),.23),(.68,.23)]],[0,T,0])
 pillar=cq.Compound.makeCompound([channel_face,*returns,foot]);add('front_'+label+'_inner_hinge_pillar_channel_and_lower_foot_reference',pillar)
 # Fill the lateral difference at the existing rear wheelhouse boundary.
 closing=prism([p(x,y,.35) for x,y in [(.66,.35),(.69,.35),(.69,.58),(.66,.58)]],[0,0,T])
 topreturn=prism([p(x,.58,z) for x,z in [(.66,.35),(.69,.35),(.69,.37),(.66,.37)]],[0,-T,0])
 add('front_'+label+'_rear_wheelhouse_to_footwell_closing_comparison',cq.Compound.makeCompound([closing,topreturn]))
 for c in hinge_centers:
  tool=cq.Solid.makeCylinder(.0025*S,10,V(c)-cq.Vector(0,0,4),cq.Vector(0,0,1));assert pillar.intersect(tool).Volume()<1e-5
  nearby=V(c)+cq.Vector(side*6,0,-4);assert pillar.intersect(cq.Solid.makeCylinder(1,10,nearby,cq.Vector(0,0,1))).Volume()>1
 # Static mouth only: no physical service/foot/pedal travel is inferred.
 for y in [.45,.65]:assert bulk.intersect(cq.Solid.makeCylinder(2,10,V(p(.51,y,.13))-cq.Vector(0,0,4),cq.Vector(0,0,1))).Volume()<1e-5
 nodes[label]=dict(front_plane_z_units=.35,roof_y_units=.72,rear_bulkhead_z_units=.13,inboard_x_units=side*.36,outboard_x_units=side*.66,footwell_y_bounds_units=[.35,.72],footwell_z_bounds_units=[.13,.35],hinge_backing_plane_z_units=.153,pillar_outer_profile_yx_units=[[y,side*outer_x(y)]for y in ys],estimated_pillar_recess_mm=pillar_recess,pillar_x_bounds_units=[side*.68,side*max(outer_x(y)for y in ys)],pillar_y_bounds_units=[.35,1.005],pillar_z_bounds_units=[.153,.23],hinge_fixing_centers_units=hinge_centers,hinge_bore_radius_units=.003,wheelhouse_closing_x_units=[side*.66,side*.69],wheelhouse_rear_plane_z_units=.35,wheelhouse_rear_top_y_units=.58)
assembly=cq.Assembly(name='estimated_footwell_and_inner_hinge_pillar_relationships');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=absolute_tessellation(shape,.15,.15)if name=='front_bulkhead_with_retained_apertures_and_footwell_mouths_reference' or '_footwell_front_and_top_'in name or '_inner_toeboard_'in name else 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/'front-footwell-reference.step'))
limits='No direct owner dimensions or fitted panel identities are applied. Manufacturer panel architecture and repair-panel roles do not supply installed sections or dimensions. Every front/top/side/inner panel profile, 1.5 mm gauge, fold, closure, hinge channel, mouth, joint and metric station is estimated. Bulkhead outline/rear plane and LHD pedal, wiper and wire apertures retain their drawing datums; the column aperture now follows the revised shared estimated wheel/column axis via the fascia placement register; new footwell mouths join below, without a verified pedal or access envelope. Hinge backing bores match the existing estimated body-leaf stations only, not an owner measurement, fastener specification, fitted captive-nut map or drilling template. Caged nuts/check-strap stiffener/courtesy-switch details, actual weld/lap order, local repair history, corrosion and loaded contacts are unresolved. No strength, body alignment, door operating fit, pedal travel, wiring/pipe clearance or verified cutting/welding/removal procedure. All body sections stay fixed during inspection separation.'
meta=dict(factory_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=234',unselected_factory_footwell_branches=['AHA8284 RH','AHA8285 LH','AHA8860 LH for collapsible column'],units='mm',source_frame='Canonical body +X vehicle-left, +Y up, +Z front, mm.',manufacturer_source='https://www.bmh-ltd.com/vehicle-parts/mg-midget-parts/mg-midget-front-centre-body/',manufacturer_repair_catalog='https://www.bmh-ltd.com/wp-content/uploads/2026/04/BMH-Product-Catalogue-March26-Spreads-116pp-April26.pdf',owner_photos=['IMG_6341','IMG_6855','IMG_6891'],owner_dimensions_applied=False,basis='Factory p234 distinguishes AHA8284/8285 and AHA8860 collapsible-column footwell branches without a fitted selection. BMH rubber-bumper CZJ731 RH / CZJ575 LH front/top footwell comparisons remain distinct from earlier CZJ170/171. AHA5620/5622 outer sides and 2A5550/5551 inner toeboards establish separate panel roles. MS18/MS21/MS20 repair-panel listings distinguish inner hinge-pillar/support from outer skins without identifying fitted repair pieces or check-strap variant. Owner photos supply doorway/rear-bay context, not hidden structure measurements.',estimated_gauge_mm=T,retained_bulkhead_outline_xy_units=outline,retained_wiper_aperture=dict(center_units=wp,radius_units=wr),retained_column_aperture=dict(center_units=cp,radii_units=cr),retained_wire_aperture=dict(center_units=[.28,.98,.13],radius_units=.009),retained_lhd_pedal_aperture_xy_bounds_units=[.34,.66,.72,.96],nodes=nodes,limits=limits+' The left front/top body now includes a rearward mounting platform projected from the existing pedal body gasket, with an estimated step and side returns to the old roof. The old roof is opened beneath the projected central mouth. All8 reference bores share the unmeasured base map. No fitted receiver, hidden reinforcement, weld ownership or safe pedal-box removal/support is established; column/pedal conflicts remain held.',parts=meshes)
meta['pedal_mount_body_construction']=pedal_plinth
meta['wing_door_junction_revision']=dict(estimated_pillar_recess_mm=12.,basis='Inner hinge-pillar outer channel follows retained side camber12mm inboard rather than projecting through the wing/door outer skin. Backing Z plane, hinge axes/bores, inner wall and footwell stations remain. Owner IMG6341 supports concealed pillar/outer-skin ownership, not hidden dimensions.',limits='Reconstructed channel profile/inset, gauge and joins are estimates; no fitted section, welds, strength, swept door clearance or removal path.')
meta['limits']+=' Inner pillar outer profile now follows the held side camber with a12mm construction recess; this replaces the former constantX=.7525 web. Fixing centers stay; hidden fitted section and reinforcement remain unknown.'
meta['tunnel_junction_reconstruction']=tunnel_junction_metadata()
meta['former_bulkhead_central_notch_outline_xy_units']=former_outline
meta['limits']+=' '+tunnel_junction_metadata()['limits']
meta['body_rail_reconstruction']=rail_body_metadata()
meta['limits']+=' '+rail_body_metadata()['limits']
(OUT/'front-footwell-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'front-footwell-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,eight_hinge_backing_bores_with_adjacent_material=True,footwell_rear_mouths_open=True)))
