"""Horizontal front-mount seats from owner/factory architecture; unregistered.

Canonical body mm. Existing mount centers, frame-foot axes and engine/body
transform stay. No fitted dimensions, loaded rubber or safe support is inferred.
"""
from pathlib import Path
import json,math
import cadquery as cq
import build_engine as engine
from front_wing_geometry import absolute_tessellation
OUT=Path(__file__).parent
BODY=json.loads((OUT.parent/'body-data.json').read_text())
S=BODY['published_reference_dimensions_mm']['wheelbase']/2.4
E=BODY['native_engine_placement'];T=[b*S-a for a,b in zip(E['native_anchor_mm'],E['body_anchor_units'])]
core=engine.make_front_engine_plate().translate(T)
core_face=cq.Workplane(obj=core).faces('<Z').val()
core_outline=cq.Solid.extrudeLinear(core_face.outerWire(),[],cq.Vector(0,0,3))
parts=[];wings=[];datums={};shapes={}
Y=cq.Vector(0,1,0);X=cq.Vector(1,0,0)
G=3.;R=6.;STATION=1.15*S;CY=.53*S;CX=.36*S
BODY_SEAT=.486*S;ENGINE_SEAT=.574*S;BASE_TOP=.397*S

def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p).val()
def cyl(r,l,p,axis=Y):return cq.Solid.makeCylinder(r,l,cq.Vector(*p),axis)
def ring(ro,ri,l,p):return cyl(ro,l,p).cut(cyl(ri,l+2,(p[0],p[1]-1,p[2])))
def add(name,shape,color=(.32,.34,.30)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color));shapes[name]=shape

def rotate_mount(shape,side):
 # Former inner single-fixing face becomes the upper engine-side face;
 # former paired-stud/access face becomes the lower body-side face.
 return shape.rotate((side*CX,CY,STATION),(side*CX,CY,STATION+1),-side*90)

for side,label in [(1,'left'),(-1,'right')]:
 cx=side*CX;holes=[(side*.34*S,STATION-.085*S),(side*.38*S,STATION-.085*S),(cx,STATION+.085*S)]
 base=box(.20*S,G,.25*S,[cx,BASE_TOP-G/2,STATION])
 for x,z in holes:base=base.cut(cyl(.0055*S,G+4,[x,BASE_TOP-G-2,z]))
 top=box(.13*S,G,.23*S,[cx,BODY_SEAT-G/2,STATION])
 top=top.cut(cyl(.023*S,G+4,[cx,BODY_SEAT-G-2,STATION]))
 for dz in [-.075*S,.075*S]:top=top.cut(cyl(.007*S,G+4,[cx,BODY_SEAT-G-2,STATION+dz]))
 webs=[]
 for dz in ([-.095*S,.095*S]if side==1 else [0.]):
  poly=[(side*.27*S,BASE_TOP),(side*.45*S,BASE_TOP),(side*.425*S,BODY_SEAT-G),(side*.295*S,BODY_SEAT-G)]
  web=cq.Workplane('XY',origin=(0,0,STATION+dz-G/2)).polyline(poly).close().extrude(G).val()
  webs.append(web)
 bracket=base.fuse(top,*webs).clean()
 assert len(bracket.Solids())==1
 add(f'front_{label}_frame_bracket_comparison',bracket)
 # Retain the former comparison's exact rubber/face envelopes, rotated as one
 # stack around its held center. These dimensions are not owner measurements.
 access=cyl(.023*S,.110*S,[cx-.055*S,CY,STATION],X)
 rubber=box(.064*S,.11*S,.20*S,[cx,CY,STATION]).cut(access)
 faces=[]
 for x in [.322,.398]:
  metal=box(.012*S,.13*S,.23*S,[side*x*S,CY,STATION])
  for dz in ([0.]if x==.322 else [-.075,.075]):metal=metal.cut(cyl((.010 if x==.322 else .007)*S,.018*S,[side*x*S-.009*S,CY,STATION+dz*S],X))
  if x==.398:metal=metal.cut(access)
  faces.append(metal)
 mount=rotate_mount(cq.Compound.makeCompound([rubber,*faces]),side)
 add(f'front_{label}_rubber_and_bonded_face_comparison',mount,(.17,.18,.16))
 # The whole front plate receives a lateral web and a ninety-degree fold
 # into a horizontal foot. Joining/outline/gauges are construction estimates.
 inboard=(168. if side==1 else 105.)-4
 outer=(.36+.065)*S;foot_inner=(.36-.065)*S
 z0=T[2]+182.;bend_z=z0+R+G;bend_y=ENGINE_SEAT+G+R
 web=cq.Workplane('XY',origin=(0,0,z0)).polyline([(inboard,bend_y),(outer,bend_y),(foot_inner,.64*S),(inboard,.64*S)]).close().extrude(G).val()
 bend=cq.Solid.makeCylinder(R+G,outer-foot_inner,cq.Vector(foot_inner,bend_y,bend_z),X).cut(cq.Solid.makeCylinder(R,outer-foot_inner+2,cq.Vector(foot_inner-1,bend_y,bend_z),X))
 quadrant=box(outer-foot_inner+2,R+G,R+G,[(outer+foot_inner)/2,bend_y-(R+G)/2,bend_z-(R+G)/2])
 bend=bend.intersect(quadrant)
 foot_front=STATION+.12*S
 foot=box(outer-foot_inner,G,foot_front-bend_z,[(outer+foot_inner)/2,ENGINE_SEAT+G/2,(foot_front+bend_z)/2])
 wing=web.fuse(bend,foot).clean()
 if side<0:wing=wing.mirror('YZ')
 wing=wing.cut(cyl(.010*S,30,[cx,ENGINE_SEAT-10,STATION])).cut(core_outline).clean()
 assert wing.isValid() and wing.Volume()>0
 wings.append(wing)
 fix=[]
 for x,z in holes:
  fix.extend([cyl(.0045*S,.038*S,[x,.365*S,z]),cyl(.010*S,.006*S,[x,BASE_TOP,z])])
 # Paired body studs/nuts and single engine-foot bolt now share vertical axes.
 for dz in [-.075*S,.075*S]:
  fix.append(cyl(.0055*S,.040*S,[cx,.492*S,STATION+dz],-Y))
  fix.append(ring(.012*S,.0062*S,.010*S,[cx,BODY_SEAT-G-.010*S,STATION+dz]))
 top_face=ENGINE_SEAT+G
 fix.extend([cyl(.0075*S,.035*S,[cx,top_face,STATION],-Y),cyl(.013*S,.008*S,[cx,top_face,STATION])])
 add(f'front_{label}_separate_fixing_envelopes',cq.Compound.makeCompound(fix),(.51,.52,.47))
 datums[label]=dict(held_mount_center_body_mm=[cx,CY,STATION],mount_fixing_axis=[0,1,0],frame_foot_hole_axes_xz_body_mm=holes,frame_foot_top_y_body_mm=BASE_TOP,body_shelf_bearing_plane_y_body_mm=BODY_SEAT,engine_foot_bearing_plane_y_body_mm=ENGINE_SEAT,engine_foot_gauge_mm=G,engine_foot_fixing_center_body_mm=[cx,top_face,STATION],paired_body_fixing_axes_xz_body_mm=[[cx,STATION-.075*S],[cx,STATION+.075*S]],estimated_bend_inside_radius_mm=R,source_mount_rotation_degrees=-side*90)
 assert mount.intersect(cyl(.0067*S,.16*S,[cx,CY-.08*S,STATION])).Volume()<1e-5
 # Prove the shelf and engine foot passages locally, not access through the car.
 assert top.intersect(cyl(.022*S,G+4,[cx,BODY_SEAT-G-2,STATION])).Volume()<1e-5
plate=core.fuse(*wings).clean()
assert plate.isValid() and len(plate.Solids())==1
assert math.isclose(plate.Volume(),core.Volume()+sum(w.Volume()for w in wings),rel_tol=1e-7)
assert core.cut(plate).Volume()<1e-6
assert plate.intersect(core_outline).cut(core).Volume()<1e-5
for side in [-1,1]:assert plate.intersect(cyl(.008*S,15,[side*CX,ENGINE_SEAT-5,STATION])).Volume()<1e-5
add('front_engine_plate_with_integral_bent_mounting_tabs_comparison',plate,(.38,.40,.38))
assembly=cq.Assembly(name='unregistered_horizontal_front_mount_support_chain');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=absolute_tessellation(shape,.05,.12)
 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/'front-power-support-reference.step'))
limits='No direct owner dimensions, selected CHA280/565 or215372/TKC1830 identity, exact installed angles or loaded rubber shape. Reused rubber/face dimensions rotate as reference envelopes;3mm bracket/foot gauges,6mm bend radii, web outlines, bore maps, plain fixings and every station remain estimates. Source bearing-plane coincidence is construction, not fitted support, compression, thread engagement or strength. The opposite-side form follows factory architecture without an owner survey. Original engine front plate and engine/body frame stay. No body/frame hole or receiver is invented. Actual filter/alternator/rack/column clearances, torque, supported lift, tool access, NA restraints/cable/tie-bar and verified release/removal paths remain unresolved. UK12.45.01/.03 are documentary comparisons, not a complete applicable North American procedure. Separating shapes is illustrative.'
meta=dict(units='mm',source_frame='Canonical body +X vehicle-left,+Y up,+Z front;mm.',owner_photos=['IMG_7041'],owner_dimensions_applied=False,factory_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=72',factory_front_plate_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=61',procedure_source='https://www.spridgets.de/downloads/Midget%201500%20WH%20Teil%203.pdf#page=9',supplier_reference=dict(part='CHA565',source='https://rimmerbros.com/Item--i-CHA565',scope='Access-face and single/paired fixing topology only, not fitted identity or dimensions.'),basis='IMG7041 shows the visible front mounting plates/rubber beneath the alternator beside the filter and steering shaft. Factory p72 and UK mounting illustration show the vertical fixing stack through an engine foot and body-bracket shelf. The former sideways X-axis stack is withdrawn. Mount centers, frame-foot hole axes and engine/body transform stay; rubber/face envelopes rotate to vertical axes. New shelf/web and integral folded-foot construction connects that orientation to the retained central engine plate and body-frame foot roles.',limits=limits,source_engine_transform_mm=T,construction_datums=datums,orientation_revision=dict(baseline_commit='d81cf8d2',former_mount_fixing_axes='Body X, sideways',current_mount_fixing_axes='Body Y, vertical reference',held_mount_centers=True,held_frame_foot_axes_and_upper_plane=True,original_core_plate_material_retained=True,core_engine_export_unchanged=True,body_frame_receivers_created=False,estimated_bracket_and_plate_foot_gauge_mm=G,estimated_inside_bend_radius_mm=R),parts=meshes)
(OUT/'front-power-support-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'front-power-support-reference.step')).val();assert restored.isValid()
assert all(s.Volume()>0 for s in restored.Solids())
assert math.isclose(restored.Volume(),sum(s.Volume()for _,s,_ in parts),rel_tol=1e-7)
print(json.dumps(dict(groups=len(parts),valid_positive_solids=len(restored.Solids()),horizontal_seats_and_vertical_fixing_axes=True,connected_plate_and_brackets=True,central_reference_passages_open=True,original_core_plate_material_retained=True,datums=datums)))
