"""Owner-visible parked hood/frame; topology reference, no folding mechanism."""
from pathlib import Path
import json,math
import cadquery as cq
from rear_profile_layout import outer_y
OUT=Path(__file__).parent
S=json.loads((OUT.parent/'body-data.json').read_text())['published_reference_dimensions_mm']['wheelbase']/2.4
parts=[]
def V(p):return cq.Vector(*(v*S for v 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 cyl(r,l,p,axis=(1,0,0)):return cq.Solid.makeCylinder(r*S,l*S,V(p),cq.Vector(*axis))
def rod(a,b,r):
 a,b=V(a),V(b);d=b-a;return cq.Solid.makeCylinder(r*S,d.Length,a,d.normalized())
def add(name,shape,color=(.12,.13,.12)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color))
def tube(points,ro,ri):
 pts=[V(p) for p in points];path=cq.Wire.assembleEdges([cq.Edge.makeSpline(pts)])
 return cq.Workplane(cq.Plane(origin=pts[0],normal=pts[1]-pts[0])).circle(ro*S).circle(ri*S).sweep(path,isFrenet=True).val()
# IMG_6341 supports a standing header above the exposed stack/headrests,
# not a unique angle/height. This is a qualitative construction pose.
HEADER_OLD=[0,1.13,-1.34];HEADER_NEW=[0,1.38,-1.30];HEADER_ANGLE=-65.
def parked_header(shape):
 return shape.translate((-V(HEADER_OLD)).toTuple()).rotate((0,0,0),(1,0,0),HEADER_ANGLE).translate(V(HEADER_NEW).toTuple())
def deck(x,z):
 y=outer_y(z);t=max(0,min(1,(y-.39)/.64));tail=max(0,(-z-1.65)/.37)
 w=.75+.012*math.sin(math.pi*t)-.024*t**5-.025*tail**2
 return y+.03*(1-(x/w)**2)
mounts=[];nodes={}
for side,label in [(1,'left'),(-1,'right')]:
 x=side*.65
 packing=box(.006,.07,.15,[x,.90,-.88]);plate=box(.006,.07,.15,[x-side*.007,.90,-.88])
 for z in [-.93,-.88,-.83]:
  cutter=cyl(.004,.024,[x-.012,.90,z]);packing=packing.cut(cutter);plate=plate.cut(cutter)
  mounts.append([x,.90,z])
 add('hood_'+label+'_base_and_packing_comparison',cq.Compound.makeCompound([packing,plate]),(.31,.32,.28))
 a=[side*.64,.90,-.88];b=[side*.64,1.035,-1.24];c=[side*.62,1.075,-1.405];d=[side*.58,HEADER_NEW[1],HEADER_NEW[2]]
 nodes[label+'_body_pivot']=a;nodes[label+'_rear_pivot']=c
 arms=[rod(a,b,.008),rod(b,c,.008),rod(c,d,.008),rod(b,d,.006)]
 for p in [a,b,c,d]:arms.append(cyl(.012,.018,[p[0]-.009,p[1],p[2]]))
 add('hood_'+label+'_parked_side_link_comparison',cq.Compound.makeCompound(arms))
for label,z,y in [('rear',-1.405,1.075),('forward',-1.27,1.045)]:
 pts=[[-.62,y,z],[-.46,y+.026,z-.012],[0,y+.042,z-.018],[.46,y+.026,z-.012],[.62,y,z]]
 add('hood_'+label+'_parked_bow_comparison',tube(pts,.009,.0065))
header=box(1.18,.055,.12,[0,1.13,-1.34]).cut(box(1.16,.05,.10,[0,1.117,-1.34]))
add('hood_parked_header_rail_comparison',parked_header(header))
retainer=box(1.12,.006,.027,[0,1.157,-1.345]);seal=box(1.12,.014,.02,[0,1.166,-1.345])
add('hood_header_seal_and_retainer_comparison',parked_header(cq.Compound.makeCompound([retainer,seal])),(.17,.18,.15))
for side,label in [(1,'left'),(-1,'right')]:
 x=side*.45
 base=box(.065,.006,.036,[x,1.106,-1.34]);lever=box(.022,.012,.065,[x,1.091,-1.34]);hook=box(.023,.018,.006,[x,1.092,-1.374])
 add('hood_'+label+'_parked_catch_comparison',parked_header(cq.Compound.makeCompound([base,lever,hook])),(.47,.48,.44))
# Folded sheet envelope, not a textile gauge, cutting pattern or window model.
fold=[(1.06,-1.23),(1.115,-1.28),(1.065,-1.33),(1.12,-1.39),(1.08,-1.44),(1.18,-1.38),(1.28,-1.345),(1.30,-1.31)]
# A stroked section gives the return fold a finite rounded envelope without
# a self-crossing offset polygon. The8mm boundary is not a textile gauge.
strips=[]
for (ya,za),(yb,zb) in zip(fold,fold[1:]):
 dy,dz=yb-ya,zb-za;l=math.hypot(dy,dz);ny,nz=-dz/l*.004,dy/l*.004
 profile=[(ya+ny,za+nz),(yb+ny,zb+nz),(yb-ny,zb-nz),(ya-ny,za-nz)]
 strips.append(cq.Workplane('YZ').polyline([(y*S,z*S) for y,z in profile]).close().extrude(1.17*S).translate((-.585*S,0,0)).val())
for y,z in fold:strips.append(cyl(.004,1.17,[-.585,y,z]))
fabric=strips[0].fuse(*strips[1:]).clean()
add('hood_folded_fabric_envelope_reference',fabric,(.075,.08,.075))
# Factory rear retainer is body-carried. Place its existing width/depth on
# the fixed shroud ahead of the lid, not over the moving panel. Mid-band
# station, projected thickness and0.5mm unloaded display gap are estimates.
retainer_z=(-1.16-1.362)/2;retainer_half_depth=.0125;gap=.5/S
sections=[]
for i in range(49):
 x=-.59+1.18*i/48
 pts=[cq.Vector(x*S,(deck(x,z)+gap+dy)*S,z*S) for z,dy in [(retainer_z-retainer_half_depth,0),(retainer_z+retainer_half_depth,0),(retainer_z+retainer_half_depth,.010),(retainer_z-retainer_half_depth,.010)]]
 sections.append(cq.Wire.makePolygon(pts+[pts[0]]))
rear_bar=cq.Solid.makeLoft(sections,ruled=True);fix=[];rear_axes=[]
for i in range(7):
 x=-.54+i*.18;y=deck(x,retainer_z)+gap
 rear_bar=rear_bar.cut(cyl(.003,.025,[x,y-.005,retainer_z],(0,1,0)))
 fix.append(cyl(.0025,.025,[x,y-.005,retainer_z],(0,1,0)))
 rear_axes.append([x,y,retainer_z])
add('hood_rear_body_retainer_comparison',cq.Compound.makeCompound([rear_bar,*fix]),(.33,.34,.30))
fast=[]
for x,y,z in mounts:
 side=1 if x>0 else -1
 fast.extend([cyl(.0035,.035,[x-side*.0175,y,z],(side,0,0)),cyl(.007,.004,[x-side*.0215,y,z],(side,0,0))])
add('hood_six_frame_body_fastener_comparison',cq.Compound.makeCompound(fast),(.42,.43,.39))
assembly=cq.Assembly(name='static_owner_parked_folded_hood_reference');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=shape.tessellate(.7,.2)
 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/'folded-hood-reference.step'))
limits='Parked recognition pose only. All sections, link lengths, pivot centers/counts, packing/stud patterns, header orientation, fabric folds/thickness, retainer stations and body coordinates are estimates. Catalog six frame-body screws and seven rear-retainer screws are not an owner count or drilling map. Side links/bows are inspection subdivisions of the frame, not identified separately replaceable factory parts. No raised hood, compatible kinematic linkage, folding sweep, cloth/window deformation, catch engagement, seal compression, tension, loaded support, boot/seat clearance or verified replacement/removal path. Body mount and rear-retainer registration remain unresolved. No CHA93 stowage cover or tonneau is added over the photo-visible exposed stack.'
meta=dict(units='mm',factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=252','https://www.spridgets.de/downloads/Parts-1977.pdf#page=253','https://www.spridgets.de/downloads/Parts-1977.pdf#page=254'],owner_photos=['IMG_6341','IMG_6329'],basis='IMG_6341 directly supports exposed folded fabric, header/catch-like hardware and transverse parked stack behind the seats; hidden mounts and exact parts remain unknown. Factory AHA9051 frame, two AHA8295 packing pieces, six frame/body screws, separate header/seal/catches and AHA8185 rear retainer support topology only.',limits=limits,body_mount_holes_units=mounts,parked_nodes_units=nodes,parts=meshes)
meta['parked_pose_revision']=dict(owner_photo_basis='IMG_6341: exposed folded fabric, header/catch-like hardware above the stack and headrests; exact pose/identity not resolved.',header_old_center_body_units=HEADER_OLD,header_center_body_units=HEADER_NEW,header_rotation_x_degrees=HEADER_ANGLE,header_pose_estimated=True,frame_side_lower_pivots_held=True,lower_fabric_fold_centerline_held=True,folded_fabric_centerline_yz_units=fold,rear_retainer_z_body_units=retainer_z,rear_retainer_station_basis='Unmeasured midpoint of retained fixed shroud band between rear cockpit boundary-1.16 and lid front-1.362; topology construction, not a survey.',rear_retainer_axes_body_units=rear_axes,retainer_projected_y_thickness_mm=.010*S,retainer_unloaded_display_gap_mm=.5,retainer_body_receivers_created=False,factory_retainer='AHA8185',catalog_retainer_body_screws=7)
meta['basis']+=' IMG_6341 corrects the former flat header to a standing estimated parked pose; catches/seal follow its rigid transform. Exposed folded fabric and side links connect the parked stack to that pose. Rear retainer follows the shared estimated shroud crown ahead of the lid.'
meta['limits']+=' Header-65-degree rotation, center, linkage upper points and fabric drape are qualitative photo estimates, not fitted camera reconstruction or compatible folding kinematics. Rear-retainer mid-shroud station, curved section,0.5mm unloaded gap and seven fixing axes are construction choices. No body receivers are fabricated. Zero lid contact establishes only source topology, not fitted closure, attachment seating, access or removal clearance.'
(OUT/'folded-hood-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'folded-hood-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,parked_only=True,catalog_frame_body_screws=len(mounts))))
