"""Native bonnet main/safety closure comparison;no operating lock or release."""
from pathlib import Path
import json,math,re
import cadquery as cq
from bonnet_surface import height_units
from front_clip_layout import rigid_z,panel_z
from front_wing_geometry import absolute_tessellation
ROOT=Path(__file__).resolve().parent;S=2032/2.4;X,Y,Z=cq.Vector(1,0,0),cq.Vector(0,1,0),cq.Vector(0,0,1)
parts=[];roles={};nodes={}
def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p).val()
def cyl(r,l,p,a=Y):return cq.Solid.makeCylinder(r,l,cq.Vector(*p),a)
def ring(ro,ri,l,p,a=Y):return cyl(ro,l,p,a).cut(cyl(ri,l+2,(cq.Vector(*p)-a).toTuple(),a))
def put(role,n,s,color=(.48,.51,.45)):
 assert s.isValid()and all(v.Volume()>0 for v in s.Solids()),n
 parts.append((n,s,color));roles.setdefault(role,[]).append(n);return s
def helix(radius,wire,start,axis,length,turns):
 path=cq.Wire.makeHelix(length/turns,length,radius,center=start,dir=axis)
 edge=path.Edges()[0];p=edge.startPoint();a=edge.tangentAt(0)
 return cq.Workplane(cq.Plane(origin=p,normal=a)).circle(wire).sweep(path,isFrenet=True).val()
def rod(a,b,r):
 a,b=cq.Vector(*a),cq.Vector(*b);d=b-a;return cq.Solid.makeCylinder(r,d.Length,a,d.normalized())
z=rigid_z(1.93)*S;top=(height_units(0,rigid_z(1.93))-.018)*S;tip=.815*S
# Keep inherited main pin axis and lower tip;the native section is a declared
# pointed-pin/thimble/coil comparison rather than a constant browser cylinder.
mount=box(.12*S,1.5,.08*S,(0,top-.75,rigid_z(1.90)*S))
mount=mount.cut(cyl(6.8,8,(0,top-4,z)))
put('bonnet-lock-pin','bonnet_main_pin_carried_mount_plate_comparison',mount)
profile=[(0,tip),(9,tip+7),(9,tip+12),(6.5,tip+15),(6.5,top),(0,top)]
pin=cq.Workplane(cq.Plane(origin=(0,0,z),xDir=X,normal=Z)).polyline(profile).close().revolve(360,(0,0),(0,1)).val()
put('bonnet-lock-pin','bonnet_main_pointed_pin_and_retention_shoulder_comparison',pin)
thimble=ring(.027*S,.012*S,1.5,(0,top-9,z)).fuse(cq.Solid.makeCone(.027*S,.020*S,6,cq.Vector(0,top-9,z),-Y).cut(cyl(.012*S,9,(0,top-16,z))))
put('bonnet-lock-pin','bonnet_main_separate_flared_thimble_comparison',thimble.clean())
spring_bottom=.855*S;spring_top=top-18
put('bonnet-lock-pin','bonnet_main_five_turn_unloaded_pin_spring_comparison',helix(.021*S,1.,(0,spring_bottom,z),Y,spring_top-spring_bottom,5))
nodes['main']=dict(axis_xz_body_mm=[0,z],mount_top_y_mm=top,pin_tip_y_mm=tip,spring_axis_y_bounds_mm=[spring_bottom,spring_top],spring_wire_radius_mm=1.,spring_mean_radius_mm=.021*S)
# Old cup/mouth and catch-plane datums stay. A plain formed receiver and
# separate keyhole catch do not simulate capture of the pointed pin.
body_y=.837*S;cup_bottom=.803*S
plate=box(.13*S,1.5,.14*S,(0,body_y-.75,z)).cut(cyl(.035*S,8,(0,body_y-4,z)))
cup=ring(.035*S,.030*S,.034*S,(0,cup_bottom,z))
put('bonnet-lock-body','bonnet_main_open_locating_cup_and_flange_comparison',plate.fuse(cup).clean())
catch_y=.803*S;catch=box(78,2,52,(12,catch_y,z)).cut(cyl(8,8,(0,catch_y-4,z)))
catch=catch.cut(box(40,8,12,(20,catch_y,z)))
put('bonnet-lock-body','bonnet_main_separate_keyhole_catch_plate_comparison',catch)
put('bonnet-lock-body','bonnet_main_unloaded_catch_return_spring_comparison',helix(3.2,.7,(12,catch_y+4,z+43),X,24,6))
nodes['main']['body_mouth_center_body_mm']=[0,body_y,z];nodes['main']['catch_middle_y_mm']=catch_y
nodes['main']['mount_plate_bore_radius_mm']=6.8
nodes['main']['unregistered_return_spring_center_z_mm']=z+43
# Shared positive-X native landing-panel slot corrects the former reflected
# browser safety pair on negativeX. This is canonical source registration,not
# proof of actual-car handedness or a measured hook/slot map.
sx=.10*S;sz=rigid_z(1.98)*S;st=(height_units(.10,rigid_z(1.90))-.018)*S;py=st-.018*S;pz=rigid_z(1.91)*S
put('bonnet-safety-hook','bonnet_safety_carried_pivot_mount_plate_comparison',box(.06*S,1.5,.065*S,(sx,st-.75,rigid_z(1.90)*S)))
outline=[(py+8,pz-7),(.810*S+9,sz-6),(.810*S+9,sz-27),(.810*S-2,sz-27),(.810*S-8,sz-19),(.810*S-8,sz+8),(.810*S+3,sz+9),(py-8,pz+7)]
wire=cq.Workplane('YZ',origin=(sx-1,0,0)).polyline(outline).close().val();wire=wire.fillet2D(2,wire.Vertices())
hook=cq.Solid.extrudeLinear(wire,[],X*2).fuse(ring(9.3,5.1,10,(sx-5,py,pz),X)).cut(cyl(5.1,20,(sx-10,py,pz),X)).clean()
put('bonnet-safety-hook','bonnet_safety_curved_hook_and_bored_pivot_comparison',hook)
put('bonnet-safety-hook','bonnet_safety_pivot_pin_and_plain_spacer_comparison',cq.Compound.makeCompound([cyl(4.9,24,(sx-12,py,pz),X),ring(8,5.2,2,(sx+5.2,py,pz),X)]))
put('bonnet-safety-hook','bonnet_safety_unloaded_pivot_coil_comparison',helix(7.2,.65,(sx-11,py,pz),X,5,3))
bracket=box(.08*S,2,.07*S,(sx,body_y-1,sz)).cut(box(.04*S,10,.05*S,(sx,body_y,sz)))
put('bonnet-safety-bracket','bonnet_safety_body_slotted_strike_plate_comparison',bracket)
nodes['safety']=dict(slot_center_xz_body_mm=[sx,sz],prior_browser_reflected_x_mm=-sx,reference_x_correction_mm=2*sx,pivot_center_body_mm=[sx,py,pz],carried_mount_top_y_mm=st,body_bracket_top_y_mm=body_y,slot_width_depth_mm=[.04*S,.05*S],fitted_side_verified=False,unloaded_pivot_spring_axis_x_bounds_mm=[sx-11,sx-6])
# Preserve every inherited release-route station after canonical X reflection
# and the existing front-layout transform. Neither remote trace nor support
# clips/body passage is supplied by the owner views.
legacy=[[-.48,.78,.03],[-.50,.83,.22],[-.56,.75,.65],[-.58,.73,1.20],[-.50,.80,1.72],[-.12,.805,1.93],[-.06,.805,1.93]]
route=[cq.Vector(-x*S,y*S,(rigid_z(z_)if z_>=1.9 else panel_z(z_))*S)for x,y,z_ in legacy]
path=cq.Wire.assembleEdges([cq.Edge.makeSpline(route)]);plane=cq.Plane(origin=route[0],normal=path.Edges()[0].tangentAt(0))
sheath=cq.Workplane(plane).circle(.0035*S).circle(.0035*S-1.2).sweep(path,isFrenet=True).val()
put('bonnet-release-cable','bonnet_release_hollow_sheath_untraced_route_comparison',sheath,(.10,.13,.11))
knob=cq.Solid.makeSphere(.017*S,cq.Vector(.48*S,.78*S,.005*S))
knob=knob.fuse(rod((.48*S,.78*S,.005*S),route[0].toTuple(),1.5))
put('bonnet-release-cable','bonnet_release_pull_knob_and_stem_comparison',knob)
put('bonnet-release-cable','bonnet_release_assumed_footwell_mount_plate_comparison',box(.055*S,.04*S,2,(.48*S,.78*S,.03*S)).cut(cyl(3.4,10,(.48*S,.78*S,.03*S-5),Z)))
end=route[-1];put('bonnet-release-cable','bonnet_release_open_end_ferrule_comparison',ring(5.08,3.1,14,(end-X*14).toTuple(),X))
nodes['cable']=dict(retained_route_stations_body_mm=[list(p.toTuple())for p in route],sheath_outer_inner_radii_mm=[.0035*S,.0035*S-1.2],open_catch_boundary_body_mm=list(end.toTuple()),route_trace_verified=False)
limits=('Factory PDF247/248 distinguishes main pin14G2444,thimble4G3676,pin spring4G1588,locating cupAHA5543,catch plate4G3035,return spring4G2494,release cableAHA5653 and independent safety hook/bracket roles. '
        'Owner6789/6853/6856supports installed pin/spring/front-panel context,not fitted part identity,dimensions,catch engagement or a complete cable trace. '
        'All sections,spring wire/turn counts,pin/undercut profiles,catch keyhole,hook curve,pivot map,plate gauges,route smoothing and mounting shapes are construction estimates. '
        'Main axis,carried heights,body/catch planes and cable route stations retain existing drawing datums. '
        'The safety pair moves from reflected browserX=-84.6667 to the existing native slotX=+84.6667mm;this is a source-coordinate correction,not actual-car handedness evidence. '
        'The pin plate has a6.8mm radius internal comparison bore;independent bonnet/body receivers stay unchanged. Spring end anchors remain unregistered. No fitted fastener map,thread,lock state,adjustment,preload,spring force,cable tension or hidden attachment is inferred. '
        'Cable sheath/ends are hollow comparisons;inner-wire routing,remote clamp,clips and body crossing remain untraced. '
        'Bonnet-carried groups follow the existing illustrative four-bar pose;body lock/bracket/cable stay fixed. '
        'This does not establish catch release,secondary-hook operation,collision clearance,safe support,service order or verified replacement/removal.')
meta=dict(units='mm',source_frame='Canonical body millimeters,+X vehicle-left,+Y up,+Z front.',owner_dimensions_applied=False,owner_photos=['IMG_6789','IMG_6853','IMG_6856'],factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=247','https://www.spridgets.de/downloads/Parts-1977.pdf#page=248'],basis='Native static main/safety bonnet-closure and hollow release-sheath roles replace coarse browser primitives;estimated closed poses and route stations stay except explicit native-slot handedness correction.',roles=roles,nodes=nodes,limits=limits)
meta['basis']='Static main and safety bonnet-closure parts and a hollow release sheath replace the earlier browser references. Estimated closed poses and route stations stay, apart from the explicit correction to match the native landing-panel slot.'
meta['limits']=re.sub(r'([,;])(?=[A-Za-z])',r'\1 ',meta['limits'])
out=cq.Assembly(name='bonnet_main_safety_and_release_reference');meshes=[]
for n,s,c in parts:
 out.add(s,name=n,color=cq.Color(*c));vs,ts=absolute_tessellation(s,.005,.08)if('spring'in n or 'coil'in n)else absolute_tessellation(s,.03,.10)if('hollow_sheath'in n or 'pull_knob'in n)else absolute_tessellation(s,.15,.16);meshes.append(dict(name=n,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]))
out.export(str(ROOT/'bonnet-closure-reference.step'));meta['parts']=meshes;(ROOT/'bonnet-closure-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
r=cq.importers.importStep(str(ROOT/'bonnet-closure-reference.step')).val();assert r.isValid()and all(s.Volume()>0 for s in r.Solids())
print(json.dumps(dict(meshes=len(meshes),solids=len(r.Solids()),roles=len(roles),step_valid=True)))
