"""Thin floor/tunnel/sill construction around retained reference mounts; no fitted body survey."""
from pathlib import Path
import json,math
import cadquery as cq
from front_wing_geometry import side_x,absolute_tessellation
from rear_cabin_layout import layout
from body_rail_junction import trim as trim_rail_body,metadata as rail_body_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
FORMER_SECTIONS=[(.38,.34,.79),(.05,.25,.72),(-.38,.17,.66),(-1.18,.12,.62)]
# Estimated rear flare around the existing 165 mm banjo comparison, not body data.
SECTIONS=[(.38,.34,.79),(.05,.25,.72),(-.38,.17,.66),(-.85,.18,.6365),(-1.18,.22,.62),(-1.20,.22,.62)]
LAYOUT=layout(BODY)
parts=[];nodes={}
# Shared visible arch/footwell boundaries replace the inherited long box.
# Sections and stations are construction estimates, not fitted sill data.
SILL_REAR=BODY['body_side_reconstruction']['estimated_profile_stations_body_units']['rear_arch_front']
SILL_FRONT=.35;OUTER_FRONT=.23;SILL_BOTTOM=.30;SILL_TOP=.50;RETURN_OUTER=.71
HANGERS=json.loads((OUT/'rear-spring-hanger-reference.json').read_text())
def V(p):return cq.Vector(*(v*S for v in p))
def prism(points,delta):
 ps=list(map(V,points));return cq.Solid.extrudeLinear(cq.Wire.makePolygon(ps+[ps[0]]),[],cq.Vector(*delta))
def box(w,h,d,p):return cq.Workplane('XY').box(w*S,h*S,d*S).translate(V(p).toTuple()).val()
def add(name,shape,color=(.23,.30,.24)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color))
def section(z,w,h,inside=False):
 y=.35*S-(2 if inside else 0);rx=w*S-(T if inside else 0);ry=(h-.35)*S-(T-2 if inside else 0)
 pts=[cq.Vector(rx*math.cos(i*math.pi/48),y+ry*math.sin(i*math.pi/48),z*S) for i in range(49)]
 return cq.Wire.makePolygon(pts+[pts[0]])
outer=cq.Solid.makeLoft([section(*s) for s in SECTIONS],ruled=True)
inner=cq.Solid.makeLoft([section(*s,inside=True) for s in SECTIONS],ruled=True)
tunnel=outer.cut(inner)
# Existing gaiter footprint only guides a declared inspection mouth, not a seal fit.
lever_mouth=cq.Workplane('XZ',origin=(0,.90*S,-.32*S)).ellipse(.08*S,.10*S).extrude(.40*S).val()
tube_relief=cq.Solid.makeCylinder(33,.60*S,V([-.30,.39,-1.20]),cq.Vector(1,0,0))
tunnel=tunnel.cut(lever_mouth).cut(tube_relief)
GEAR_CONTROLS=json.loads((OUT/'gearbox-controls-reference.json').read_text())['nodes']
for fixing in GEAR_CONTROLS['body_fixings']:
 p=cq.Vector(*fixing['center_body_mm']);axis=cq.Vector(*fixing['axis_body']);r=fixing['bore_radius_mm']
 tunnel=tunnel.cut(cq.Solid.makeCylinder(r,40,p-axis*15,axis))
 assert tunnel.intersect(cq.Solid.makeCylinder(r-.2,40,p-axis*15,axis)).Volume()<1e-5
 assert tunnel.intersect(cq.Solid.makeCylinder(1,50,p+cq.Vector(8,-15,0),axis)).Volume()>1

# Two shared rear-cradle/tunnel axes are construction maps from the body register.
REAR_SUPPORT=BODY['rear_power_support_reference']['estimated_nodes']
for fixing in REAR_SUPPORT.values():
 p=cq.Vector(*fixing['tunnel_fixing_axis_body_mm']);axis=cq.Vector(*fixing['tunnel_fixing_axis_outward']);r=fixing['tunnel_bore_radius_mm']
 tunnel=tunnel.cut(cq.Solid.makeCylinder(r,50,p-axis*20,axis))
 assert tunnel.intersect(cq.Solid.makeCylinder(r-.2,50,p-axis*20,axis)).Volume()<1e-5
 assert tunnel.intersect(cq.Solid.makeCylinder(1,50,p+cq.Vector(0,0,8)-axis*20,axis)).Volume()>1

add('cabin_hollow_tapered_tunnel_with_declared_lever_mouth_reference',tunnel)
def width(z):
 for (a,w,h),(b,v,q) in zip(SECTIONS,SECTIONS[1:]):
  if b<=z<=a:return w+(v-w)*(z-a)/(b-a)
 raise ValueError(z)
for side,label in [(1,'left'),(-1,'right')]:
 edge=[(z,width(z)) for z in [-1.20,-1.18,-.85,-.38,.05,.35]]
 outline=[(side*edge[0][1],.35,-1.20),(side*LAYOUT['floor_rear_outer_x_abs_units'],.35,-1.20),(side*LAYOUT['floor_rear_outer_x_abs_units'],.35,LAYOUT['wheelhouse_front_z_units']),(side*.66,.35,LAYOUT['floor_notch_front_z_units']),(side*.66,.35,.35),(side*edge[-1][1],.35,.35)]
 outline += [(side*w,.35,z) for z,w in edge[-2:0:-1]]
 floor=prism(outline,[0,-T,0]);holes=[]
 for x,y,z in BODY['native_seat_support_placement']['floor_stations_units']:
  if x*side>0:
   floor=floor.cut(cq.Solid.makeCylinder(BODY['native_seat_support_placement']['floor_aperture_radius_units']*S,T+4,V([x,.35,z])+cq.Vector(0,-T-2,0),cq.Vector(0,1,0)));holes.append([x,.35,z])
 h=HANGERS['nodes'][label];cx,cy,cz=h['eye_axis_center_units'];half=h['floor_pocket_half_width_units']
 floor=floor.cut(box(2*half,.03,2*half,[cx,.35,cz]))
 assert floor.intersect(box(.11,.02,.11,[cx,.35,cz])).Volume()<1e-5
 assert floor.intersect(cq.Solid.makeCylinder(1,10,V([cx,.34,cz])+cq.Vector(side*(half*S+4),0,0),cq.Vector(0,1,0))).Volume()>1
 floor=trim_rail_body(floor)
 add('cabin_'+label+'_floor_with_retained_seat_bores_reference',floor)
 # The old X=.66 solid box becomes a web with outward top/bottom returns.
 web=prism([[side*.66,y,z] for z,y in [(SILL_REAR,SILL_BOTTOM),(SILL_FRONT,SILL_BOTTOM),(SILL_FRONT,SILL_TOP),(SILL_REAR,SILL_TOP)]],[side*T,0,0])
 top=prism([[side*x,SILL_TOP,z] for x,z in [(.66,SILL_REAR),(RETURN_OUTER,SILL_REAR),(RETURN_OUTER,SILL_FRONT),(.66,SILL_FRONT)]],[0,-T,0])
 bottom=prism([[side*x,SILL_BOTTOM,z] for x,z in [(.66,SILL_REAR),(RETURN_OUTER,SILL_REAR),(RETURN_OUTER,SILL_FRONT),(.66,SILL_FRONT)]],[0,T,0])
 add('cabin_'+label+'_open_inner_sill_web_and_returns_reference',cq.Compound.makeCompound([web,top,bottom]))
 # Native outer skin/returns close the reference section against the inner
 # return edges. Gauge is projected X/Y construction, not a formed-sheet map.
 outer_path=[(RETURN_OUTER*S,SILL_TOP*S)]+[(side_x(y,.13)*S,y*S)for y in [SILL_TOP-(SILL_TOP-SILL_BOTTOM)*i/32 for i in range(33)]]+[(RETURN_OUTER*S,SILL_BOTTOM*S),(RETURN_OUTER*S,SILL_BOTTOM*S+T)]
 inner_path=[(side_x(y,.13)*S-T,y*S)for y in [SILL_BOTTOM+T/S+(SILL_TOP-SILL_BOTTOM-2*T/S)*i/32 for i in range(33)]]+[(RETURN_OUTER*S,SILL_TOP*S-T)]
 pts=[cq.Vector(side*x,y,SILL_REAR*S)for x,y in outer_path+inner_path]
 outer_sill=cq.Solid.extrudeLinear(cq.Wire.makePolygon(pts+[pts[0]]),[],cq.Vector(0,0,(OUTER_FRONT-SILL_REAR)*S))
 add('cabin_'+label+'_formed_outer_sill_skin_and_returns_reference',outer_sill,(.055,.065,.060))

 for x,y,z in holes:
  assert floor.intersect(cq.Solid.makeCylinder(2.8,10,V([x,.34,z]),cq.Vector(0,1,0))).Volume()<1e-5
  assert floor.intersect(cq.Solid.makeCylinder(1,10,V([x,.34,z])+cq.Vector(side*7,0,0),cq.Vector(0,1,0))).Volume()>1
 nodes[label]=dict(floor_y_units=.35,floor_outer_x_units=side*.66,floor_rear_outer_x_units=side*LAYOUT['floor_rear_outer_x_abs_units'],former_floor_outer_x_units=side*.70,estimated_front_spring_floor_pocket=dict(center_xz_units=[cx,cz],half_width_units=half,basis='Shared body-box footprint only; not a fitted factory opening or cutting template.'),inner_edge_zw_units=edge,seat_bores_units=holes,sill_web_x_units=side*.66,sill_return_outer_x_units=side*RETURN_OUTER,sill_y_bounds_units=[SILL_BOTTOM,SILL_TOP],sill_z_bounds_units=[SILL_REAR,SILL_FRONT])
# Keep the old rear bar's span and Z station; move its upper construction face
# down to the underside of the new floor rather than through it.
top_y=.35-T/S
cross_top=prism([[x,top_y,z] for x,z in [(-.61,-1.18),(.61,-1.18),(.61,-1.06),(-.61,-1.06)]],[0,-T,0])
cross_sides=[prism([[x,y,z] for x,y in [(-.61,.31),(.61,.31),(.61,top_y),(-.61,top_y)]],[0,0,T if z==-1.18 else -T]) for z in [-1.18,-1.06]]
cross_mouth=box(.44,.20,.20,[0,.33,-1.12])
cross=cq.Compound.makeCompound([p.cut(cross_mouth) for p in [cross_top,*cross_sides]]);add('cabin_rear_floor_formed_crossmember_reference',cross)
heel_outline=[[-.66,.35],[.66,.35],[.66,.39],[.47,.39],[.47,.64],[-.47,.64],[-.47,.39],[-.66,.39]]
heel=prism([[x,y,-1.16] for x,y in heel_outline],[0,0,-T]).cut(outer)
add('cabin_lower_heelboard_with_tunnel_mouth_reference',heel)
# Real geometric tests of constructed passages; not installed clearances.
assert tunnel.intersect(box(.04,.02,1.50,[0,.35,-.40])).Volume()<1e-5
assert tunnel.intersect(cq.Solid.makeCylinder(4,.40*S,V([0,.50,-.32]),cq.Vector(0,1,0))).Volume()<1e-5
assert cross.intersect(box(.50,.015,.05,[0,.32,-1.12])).Volume()<1e-5
assembly=cq.Assembly(name='estimated_cabin_floor_sill_and_tunnel_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 '_floor_with_retained_seat_bores_'in name else 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/'cabin-understructure-reference.step'))
limits='No direct owner dimensions or fitted panel identities are applied. Manufacturer floor/sill/heelboard architecture does not identify actual repair parts or installed contours. All gauges, open sill section, floor outer-edge correction, tunnel seam, lever mouth, rear crossmember and heelboard profiles/joins are estimates. Existing eight seat-floor stations remain unverified hole-map comparisons; no fastener specification or strength is established. Front/mid tunnel sections retain previous drawing choices. A declared rear flare replaces the former .12-unit rear half-width with .22 units around the existing 165 mm banjo comparison; a short extension reaches the floor endpoint. This is an estimated construction correction, not owner body dimensions or fitted clearance. The rear crossmember has a declared .44-unit central mouth, not a verified chassis section. Two open-rear tunnel side reliefs use a 33 mm radius around the existing 32 mm-radius axle-tube comparison at body Y.39/Z-1.20. The 1 mm radial construction allowance is not fitted clearance, a factory cutout or suspension travel allowance. The 1.5 mm radial shell construction is not a uniform normal sheet offset. Four new body bores follow the separate curved gearbox-gaiter retainer construction map, not a fitted factory pattern or drilling/repair template. Lever-mouth ellipse follows the old gaiter region only; no seal fit, shift travel, operating clearance or removal opening is verified. Front spring-hanger floor pockets follow the separate estimated body-box footprint; no actual opening or load-bearing repair geometry is established. Actual floor pressings, drains, plugs, jacking-point apertures, reinforcements, weld/lap order, loaded contacts, corrosion and repaired seams are unresolved. The rear floor crossmember is not identified as manufacturer 2A5478; independent rear-power support now adds two estimated cradle/tunnel-side bores at the shared register axes. That earlier rear-support revision changed only the tunnel mesh and retained six other cabin meshes; the later sill revision below supersedes that pass-specific count. Hole size/axes and attachment are estimates, not factory drilling dimensions or verified support. All other tunnel/floor contours and gaiter maps stay unchanged. No body/jacking/restraint strength, alignment, gearbox fit, suspension travel or safe cutting/welding/removal procedure. All welded references stay fixed during inspection.'
meta=dict(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/',owner_photos=['IMG_6341','IMG_6919'],basis='Manufacturer separates floor halves/full floor, inner sills, centre crossmember and heelboard skins/reinforcements. These support panel roles, not fitted dimensions. Owner photos give cabin/underside context without hidden-section measurements. Prior floor/tunnel stations and seat fixing references are reconciled before changing geometry.',owner_dimensions_applied=False,estimated_gauge_mm=T,former_tunnel_sections_zwh_units=FORMER_SECTIONS,estimated_current_tunnel_sections_zwh_units=SECTIONS,estimated_rear_tube_relief=dict(center_yz_units=[.39,-1.20],radius_mm=33,existing_tube_comparison_radius_mm=32,limit='Static drawing relief only; not fitted clearance, factory opening or travel envelope.'),rear_power_support_body_fixings=REAR_SUPPORT,gearbox_gaiter_body_fixings=GEAR_CONTROLS['body_fixings'],estimated_lever_mouth_units=dict(center_xz=[0,-.32],radii_xz=[.08,.10]),rear_floor_crossmember=dict(x_bounds_units=[-.61,.61],z_bounds_units=[-1.18,-1.06],lower_y_units=.31,upper_y_units=top_y,former_upper_y_units=.37,upper_face_correction_mm=(top_y-.37)*S,estimated_central_mouth_width_units=.44,basis='Declared interface correction to floor underside, not an owner measurement or fitted packing/clearance.'),heelboard_front_z_units=-1.16,heelboard_top_y_units=.64,nodes=nodes,limits=limits,parts=meshes)
meta['sill_section_revision']=dict(former_inner_sill_y_bounds_units=[.25,.50],former_inner_sill_z_bounds_units=[-1.255,.395],current_sill_y_bounds_units=[SILL_BOTTOM,SILL_TOP],current_sill_z_bounds_units=[SILL_REAR,SILL_FRONT],outer_sill_z_bounds_units=[SILL_REAR,OUTER_FRONT],inner_web_x_abs_units=.66,shared_return_edge_x_abs_units=RETURN_OUTER,estimated_projected_outer_gauge_mm=T,estimated_inner_gauge_mm=T,outer_section_basis='Retained visible side camber and outer sill Y bounds. Both rear ends stop at the existing rear-arch front; the inner front reaches the footwell front plane. The outer front retains its formerZ=.23 boundary, leaving the lower wing/footwell front closure unresolved. Paired returns shareX=.71 over their common span. These are coherent construction boundaries, not calibrated body dimensions or fitted welds.',owner_photos=['IMG_6329','IMG_6341'],unselected_manufacturer_outer_sills=['CZJ694 RH','CZJ695 LH'],unselected_manufacturer_inner_sills=['CZJ176 RH','CZJ177 LH'],identity_limit='Manufacturer distinguishes1975-on twin-jacking-point outer sills from earlier variants. Actual apertures/reinforcements and installed replacement history remain unknown; no jack receiver, support rating or cutting template is inferred.',remaining_gap='Existing rear floor/wheelhouse/seat registration remains independent. No floor is trimmed or seat-hole moved to force agreement.')
meta['basis']+=' The paired native sill skins/returns now share a bounded construction section with the inner webs, rather than ending at the old standalone browser skin. Rear wheel-opening and front footwell boundaries replace the overlong inner-sill box.'
meta['basis']+=' BMH lists1975-on twin-jacking-point CZJ694/695 outer and CZJ176/177 inner sill comparisons; none identifies this car’s fitted panels or supports a modeled jack receiver.'
meta['limits']+=' Sill revision supersedes former lowerY=.25/rearZ=-1.255/frontZ=.395 with estimatedY=.30 and Z=-.82..+.35. Native outer skins retain the old visibleY=.30..50 and side camber, with estimated1.5mm projected sections. Rear/front end closures, drains, jack points, actual weld/lap ownership, structural strength, replacement cuts and wheel travel remain unknown. Outer front stays atZ=.23; lower-wing/footwell closure betweenZ=.23 and+.35 remains unresolved. Ends stay open for inspection; a coherent static section does not establish fitted box strength or a complete load path. Floor/tunnel/seat sources remain held even where independent rear registration conflicts persist.'
meta['rear_cabin_placement_revision']=LAYOUT
meta['limits']+=' Rear floor corners now step to the retained inner wheelhouse plane; all eight floor fixing axes follow the shared photo-led forward seat placement instead of the former unmeasured stations. This supersedes earlier claims that the former rear floor outline and seat axes stay. No fitted panel profile, hole pattern, weld or clearance is verified.'
meta['body_rail_reconstruction']=rail_body_metadata()
meta['limits']+=' '+rail_body_metadata()['limits']
(OUT/'cabin-understructure-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'cabin-understructure-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,eight_seat_bores_with_adjacent_material=True,tunnel_open_bottom_and_lever_mouth=True,rear_crossmember_open_section=True,rear_flare_is_unmeasured_construction=True,front_spring_floor_pockets_open_with_adjacent_material=True)))
