"""Thin bonnet/formed inner frame at an unregistered closed display pose."""
from cowl_apron_layout import BONNET_REAR_UNITS,REAR_FRAME_SHIFT_UNITS,metadata as apron_metadata
from front_upper_profile import metadata as upper_profile_metadata
from pathlib import Path
import json
import cadquery as cq
from bonnet_surface import height_units,surface_metadata
from front_clip_layout import panel_z,metadata as clip_metadata
from front_wing_geometry import absolute_tessellation
OUT=Path(__file__).parent;B=json.loads((OUT.parent/'body-data.json').read_text());R=B['bonnet_structure_reference'];S=2032/2.4
assert R['estimated_transverse_crown_units']==surface_metadata(S)['estimated_transverse_crown_units']
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);parts=[];contacts={};nodes={}
def H(x,z):
 x,z=x/S,z/S
 return S*height_units(x,z)
def cyl(r,l,p,a=Y):return cq.Solid.makeCylinder(r,l,p,a)
def ring(ro,ri,l,p,a=Y):return cyl(ro,l,p,a).cut(cyl(ri,l+2,p-a,a))
def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p.toTuple()).val()
def form(x0,x1,z0,z1,offset,depth=1,flat_bottom=None,n=7):
 # Projected Y section, not a normal-offset gauge or a measured stamping.
 wires=[]
 for j in range(n):
  z=panel_z((z0+(z1-z0)*j/(n-1))/S)*S;xs=[x0+(x1-x0)*i/16 for i in range(17)]
  top=[cq.Vector(x,H(x,z)-offset,z) for x in xs]
  bottom=[cq.Vector(x,flat_bottom if flat_bottom is not None else H(x,z)-offset-depth,z) for x in xs]
  edges=[cq.Edge.makeSpline(top),cq.Edge.makeLine(top[-1],bottom[-1]),cq.Edge.makeSpline(bottom[::-1]),cq.Edge.makeLine(bottom[0],top[0])]
  wires.append(cq.Wire.assembleEdges(edges))
 return cq.Solid.makeLoft(wires)
def put(name,s,color=(.28,.36,.25)):
 assert s.isValid() and s.Volume()>0,name
 parts.append((name,s,color));return s
g=R['estimated_frame_vertical_gauge_mm'];lower=R['retained_lower_mount_offset_mm'];skin_g=R['estimated_skin_vertical_gauge_mm']
skin=put('bonnet_thin_curved_skin_revised_transverse_crown_reference',form(-.67*S,.67*S,BONNET_REAR_UNITS*S,1.94*S,0,skin_g,n=19),(.40,.48,.34))
for sign,label in [(1,'left'),(-1,'right')]:
 x0,x1=sorted([sign*.63*S,sign*.67*S]);a,b=sorted([sign*(.67*S-g),sign*.67*S])
 side=form(x0,x1,BONNET_REAR_UNITS*S,1.94*S,lower-g,g,n=19).fuse(form(a,b,BONNET_REAR_UNITS*S,1.94*S,skin_g,lower-skin_g,n=19))
 put('bonnet_'+label+'_formed_side_edge_channel',side.clean())
def crossrail(z0,z1):
 floor=form(-.65*S,.65*S,z0,z1,lower-g,g)
 for a,b in [(z0,z0+g),(z1-g,z1)]:floor=floor.fuse(form(-.65*S,.65*S,a,b,skin_g,lower-skin_g))
 return floor
rear=crossrail((.17+REAR_FRAME_SHIFT_UNITS)*S,(.36+REAR_FRAME_SHIFT_UNITS)*S);front=crossrail(1.80*S,1.94*S);fixings=[]
slot_centers=[]
for x,w in [(-.43*S,140),(-.10*S,90),(.10*S,90),(.43*S,140)]:
 p=cq.Vector(x,H(x,(.20+REAR_FRAME_SHIFT_UNITS)*S)-lower,(.20+REAR_FRAME_SHIFT_UNITS)*S);tool=cq.Workplane('XZ',origin=(x,1100,(.20+REAR_FRAME_SHIFT_UNITS)*S)).slot2D(w,22).extrude(500).val();rear=rear.cut(tool);slot_centers.append(list(p.toTuple()))
for sign,label in [(1,'left'),(-1,'right')]:
 cx=sign*.53*S;cz=(.30+REAR_FRAME_SHIFT_UNITS)*S;y=H(cx,cz)-lower;w=.09*S;d=.11*S
 # A hollow formed land bridges the flat legacy hinge leaf to the curved
 # skin/frame comparison. It is not a fitted pressing or weld construction.
 rear=rear.cut(box(w,200,d,cq.Vector(cx,y,cz)))
 cup=form(cx-w/2,cx+w/2,cz-d/2,cz+d/2,skin_g,flat_bottom=y)
 cavity=form(cx-w/2+g,cx+w/2-g,cz-d/2+g,cz+d/2-g,-1,flat_bottom=y+g)
 rear=rear.fuse(cup.cut(cavity))
 axes=[]
 for dz in R['estimated_hinge_hole_z_offsets_mm']:
  p=cq.Vector(cx-sign*R['estimated_hinge_hole_inward_offset_mm'],y,cz+dz);axes.append(list(p.toTuple()))
  rear=rear.fuse(ring(8,3.6,4,p+Y*g));rear=rear.cut(cyl(3.6,30,p-Y*5))
  headface=p-Y*3;length=R['supplier_hinge_screw_comparison_mm']['underhead_length']
  shank=cyl(3.175,length,headface);head=cq.Workplane(cq.Plane(origin=headface-Y*4.5,normal=Y)).polygon(6,12.8).extrude(4.5).val()
  fixings.extend([shank.fuse(head),ring(7,3.6,1.5,p-Y*3)])
 nodes[label]=dict(legacy_leaf_center_body_mm=[cx,y,cz],hinge_fixing_axes_body_mm=axes,hole_radius_mm=3.6,legacy_hinge_axis_center_body_mm=[cx,1.04*S,.13*S])
assert len(rear.Solids())==1 and len(front.Solids())==1
put('bonnet_rear_slotted_formed_rail_and_hollow_hinge_lands',rear.clean())
put('bonnet_front_formed_closure_rail_reference',front.clean())
put('bonnet_four_unthreaded_hinge_screw_and_washer_sets',cq.Compound.makeCompound(fixings),(.54,.57,.49))
# Retain the original canonical right-side upper stay endpoint. The body-side
# parked span is unchanged and still hides during illustrative bonnet opening.
p=cq.Vector(-.61*S,H(.61*S,.80*S)-lower,.80*S)
tab=form(-.65*S,-.57*S,.765*S,.835*S,lower-g,g)
eye=box(3,26,32,p+Y*3);eye=eye.cut(cyl(6.4,10,p-X*5,X));tab=tab.fuse(eye).cut(cyl(6.4,100,p-X*50,X))
assert len(tab.Solids())==1
put('bonnet_carried_stay_attachment_with_open_pin_eye',tab.clean())
pin=cyl(.007*S,35,p-X*17.5,X).fuse(cyl(8,2.5,p-X*20,X));put('bonnet_carried_stay_pin_envelope',pin,(.54,.57,.49))
for n in nodes.values():
 for q in n['hinge_fixing_axes_body_mm']:assert rear.intersect(cyl(3.4,25,cq.Vector(*q)-Y*5)).Volume()<1e-5
assert tab.intersect(cyl(6.2,12,p-X*6,X)).Volume()<1e-5
# A limited closed-pose check against current intake/heater and engine roof references. The
# existing engine/body registration remains estimated, even with zero contact.
sampled=[]
roof_sources=[('midget-1500-measured-core.step','native_engine_placement',{'rocker_cover_photo_scaled'}),('intake-mounting-reference.step','native_intake_mounting_placement',{'air_cleaner_housing_photo_reference_provisional','air_cleaner_inlet_chamber_photo_reference_provisional','carb_damper_cap_photo_reference_provisional'}),('heater-assembly-reference.step','native_heater_assembly_placement',{'heater_case_and_necks_reference','heater_blower_motor_and_plate_reference'})]
for filename,key,wanted in roof_sources:
 E=B[key];T=cq.Vector(*[v*S-a for a,v in zip(E['native_anchor_mm'],E['body_anchor_units'])])
 source=cq.Assembly.importStep(str(OUT/filename))
 for name,obj in source.objects.items():
  if name not in wanted or obj.obj is None:continue
  shape=obj.obj.val() if isinstance(obj.obj,cq.Workplane) else obj.obj;shape=shape.translate(T);sampled.append(name)
  for pn,ps,_ in parts:
   ba,bb=ps.BoundingBox(),shape.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
   volume=round(ps.intersect(shape).Volume(),4)
   if volume>1e-4:contacts[pn+'__'+name]=volume
limits='No new owner dimensions or fitted bonnet/hinge/stay identity applied. IMG6856 supports rear pressed/slotted framework and hinge context; supplier CZJ380 underside supports four rear access apertures, perimeter/front reinforcement and no middle tubular crossbar. That modern comparison is not identification of this bonnet or a fitted slot/hole map. The transverse crown and source side-height curve stay; forward longitudinal stations follow the shorter-front construction. X width, front closure and stay station stay. The apron revision changes the rear seam/framework and rigid hinge placement; the old virtual display pivot is historical. The skin/frame/carried mounting heights follow the new construction, not fitted coordinates. The old second surface15.24 mm below the top and round tube braces are replaced by a1 mm projected-Y skin and1.2 mm projected-Y formed-frame gauges; these are construction estimates, not measured or normal-offset material thicknesses. Rear/front floors retain the old lower mount offset15.24 mm only as a reference datum. All sections, folds, apertures, hollow hinge lands, captive-boss envelopes, holes, screws/washers, stay eye/pin and assembly stations are estimated. Four bonnet-side hinge screw roles use an independent1/4 inch x5/8 supplier comparison, not fitted HZS405 identity, thread/engagement, head standard or tightening. The old carried hinge leaves receive two matching reference holes each; body-side leaves/bulkhead joints and the actual NA multi-link mechanism remain unresolved. The native upper stay eye retains the old right-side X/Z station and follows the revised surface height, without fitted AHA9800 identification, locking, travel, gas assistance or safe load. The parked body-side span still hides while opening; anchors are not joined by a fabricated operating extension. The viewer solves the estimated four-bar closure for carried parts; this is not verified NA hinge kinematics, support release, latch sequence or removal trajectory. Main lock/safety references and their landing-panel openings translate with the front module; forward buffers and cable bends follow the shorter layout. Bay equipment stays in its prior unregistered frame. Limited closed-pose contacts against named roof references do not prove engine/body registration, operating or removal clearance. No loaded hinge/stay strength, weld/hem/adhesive specification, sound-pad material, fitted paint, skin stiffness, actual aperture/template or verified replacement path. Separation/opening is illustrative.'
meta=dict(units='mm',source_frame='Canonical body mm +X vehicle-left +Y up +Z front; closed display pose.',factory_sources=R['factory_sources'],supplier_source=R['supplier_source'],supplier_image=R['supplier_image'],owner_photos=R['owner_photos'],owner_dimensions_applied=False,basis='Existing owner rear-bay/front-closure context and factory bonnet/hinge/stay roles; supplier CZJ380 underside supplies the four-slot/frame comparison. Outer profile and all new thicknesses, mounting maps and installed relationships remain unmeasured estimates.',registration_status=R['registration_status'],nodes=nodes,rear_slot_centers_body_mm=slot_centers,upper_stay_axis_center_body_mm=list(p.toTuple()),upper_stay_axis_body=[1,0,0],virtual_pivot_body_mm=[v*S for v in R['virtual_pivot_body_units']],display_open_angle_radians=.82,estimated_skin_vertical_gauge_mm=skin_g,estimated_frame_vertical_gauge_mm=g,removed_uniform_lower_surface_offset_mm=15.24,sampled_current_roof_parts=sampled,sampled_closed_pose_contacts_mm3=contacts,limits=limits)
meta['cowl_apron_reconstruction']=apron_metadata(S)
meta['basis']+=' The bonnet rear trims to the broader apron reference; rear slotted rail, hinge lands and four fixing roles move forward with it, following the existing height surface. Front closure, stay, screen and engine/body datums stay.'
meta['limits']+=' '+meta['cowl_apron_reconstruction']['limits']
meta['surface_reconstruction']=surface_metadata(S)
meta['front_clip_reconstruction']=clip_metadata(S)
a=cq.Assembly(name='unregistered_thin_bonnet_formed_framework_and_carried_mounts');meshes=[]
for name,s,c in parts:
 a.add(s,name=name,color=cq.Color(*c));vs,ts=absolute_tessellation(s,.05,.12);meshes.append(dict(name=name,color=c,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/'bonnet-structure-reference.step'));meta['parts']=meshes;meta['front_upper_profile_reference']=upper_profile_metadata(S)
meta['basis']+=' The shared longitudinal front-upper correction lowers the middle contour, with zero correction at sourceZ=.13/1.99. That earlier upper-profile stage remains; current apron metadata supersedes rear framework, hinge placement and wiper-row stations.'
meta['limits']+=' The0.10-unit peak is an owner-photo-led construction estimate, not a measured height. Rear/nose interfaces, axles, marker, engine/body pose, hinge pivots and wiper/screen datums remain drawing references. Better photographic agreement is not fitted panel registration, support/locking strength, full-car collision clearance or a verified removal path.'
(OUT/'bonnet-structure-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n');restored=cq.importers.importStep(str(OUT/'bonnet-structure-reference.step')).val();assert restored.isValid();print(json.dumps(dict(meshes=len(parts),solids=len(restored.Solids()),step_valid=True,hinge_and_stay_bores_open=True,formed_rails_connected=True,sampled_current_roof_parts=sampled,closed_pose_contacts=contacts)))
