"""Native welded rear quarters from shared owner-photo reference construction.

No fitted pressing, normal material gauge, weld/seam map or repair template.
Outer contour follows the existing viewer; inner returns are explicit estimates.
"""
from pathlib import Path
import json,math
import cadquery as cq
from rear_profile_layout import outer_y,reference
from front_wing_geometry import absolute_tessellation
OUT=Path(__file__).parent;S=2032/2.4;BODY=json.loads((OUT.parent/'body-data.json').read_text());G=BODY['body_side_reconstruction']['estimated_projected_skin_offset_mm'];X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1)
parts=[]
def side_x(y,z):
 t=max(0,min(1,(y-.39)/.64));tail=max(0,(-z-1.65)/.37);nose=max(0,(z-1.65)/.38)
 return .75+.012*math.sin(math.pi*t)-.024*t**5-.025*(tail*tail+nose*nose)
def top(z):return outer_y(z) if z<=-.86 else 1.005+(.99-1.005)*(z+.86)/.09
profile=reference();a,y0=profile['rear_transition_zy_units'][0];b,y1=profile['rear_transition_zy_units'][-1]
# Dense parametric samples retain the near-vertical rear transition.
stations=[a+(b-a)*(i/32)**2 for i in range(33)]
knots=[p[0] for p in profile['upper_profile_zy_units']]+[-.77]
for lo,hi in zip(knots,knots[1:]):
 count=max(3,math.ceil((hi-lo)*S/18));stations.extend(lo+(hi-lo)*i/count for i in range(1,count+1))
stations=sorted(set(stations+[-1.58,-1.46,-.94,-.82,-.86]))
def side_wire(z):
 yt=top(z);ys=[.39+(yt-.39)*i/20 for i in range(21)]
 out=[cq.Vector(side_x(y,z)*S,y*S,z*S) for y in ys];inside=[p-X*G for p in out]
 return cq.Wire.assembleEdges([cq.Edge.makeSpline(out),cq.Edge.makeLine(out[-1],inside[-1]),cq.Edge.makeSpline(list(reversed(inside))),cq.Edge.makeLine(inside[0],out[0])])
skin=cq.Solid.makeLoft([side_wire(z) for z in stations],ruled=True)
# Square-topped rear wheel opening: same straight/quadratic contour as viewer.
def P(z,y):return cq.Vector(1000,y*S,z*S)
def line(a,b):return cq.Edge.makeLine(P(*a),P(*b))
points=[(-1.58,.30),(-1.58,.63),(-1.46,.75),(-.94,.75),(-.82,.63),(-.82,.30)]
edges=[line(points[0],points[1]),cq.Edge.makeBezier([P(-1.58,.63),P(-1.58,.75),P(-1.46,.75)]),line(points[2],points[3]),cq.Edge.makeBezier([P(-.94,.75),P(-.82,.75),P(-.82,.63)]),line(points[4],points[5]),line(points[5],points[0])]
arch=cq.Solid.extrudeLinear(cq.Wire.assembleEdges(edges),[],X*-2000);skin=skin.cut(arch)
# Unmeasured20mm inward upper return and existing .68-unit jamb boundary.
return_wires=[]
for z in stations:
 yt=top(z)*S;x=side_x(top(z),z)*S
 ps=[cq.Vector(xx,yy,z*S) for xx,yy in [(x,yt),(x-20,yt),(x-20,yt-G),(x,yt-G)]]
 return_wires.append(cq.Wire.makePolygon(ps+[ps[0]]))
upper=cq.Solid.makeLoft(return_wires,ruled=True)
z=-.77;ys=[.39+(.99-.39)*i/30 for i in range(31)];outside=[cq.Vector(side_x(y,z)*S,y*S,z*S) for y in ys]
inside=[cq.Vector(.68*S,y*S,z*S) for y in ys]
jamb_wire=cq.Wire.assembleEdges([cq.Edge.makeSpline(outside),cq.Edge.makeLine(outside[-1],inside[-1]),cq.Edge.makeLine(inside[-1],inside[0]),cq.Edge.makeLine(inside[0],outside[0])])
jamb=cq.Solid.extrudeLinear(jamb_wire,[],Z*-G)
quarter=skin.fuse(upper,jamb).clean();assert quarter.isValid() and len(quarter.Solids())==1 and quarter.Volume(1e-9)>0
for sign,side in [(1,'left'),(-1,'right')]:
 shape=quarter if sign>0 else quarter.mirror('YZ');assert shape.isValid() and len(shape.Solids())==1
 parts.append(('rear_'+side+'_welded_quarter_with_estimated_upper_and_jamb_returns',shape,(.25,.31,.18)))
limits='Outer shoulder uses the existing conditional IMG_6329 camera/profile register, not a body survey. Wheel-opening quadratic contour, lower/front stations and camber function are retained viewer construction; source tessellation and ruled loft interpolation differ within recorded sampling limits. The1.2mm projected-X skin offset is not normal measured material gauge. New20mm inward upper return and B-post return to the inherited0.68-unit X boundary are estimates, not factory flange dimensions, weld ownership/seam schedule, load path, repair or cutting template. Both quarters repeat an assumed symmetry. All corner radii, stamping, hems, edge seats, drain/rust/repair condition and hidden reinforcement remain unresolved. These are welded body references, not detachable wings or verified replacement/removal paths. Existing deck, floor, inner wheelhouses, body posts, tank/lid/lamp sources and installed datums are not moved to make contact disappear. Source intersections are reconstruction relationships, not fitted clearance, support or strength.'
meta=dict(units='mm',source_frame='Canonical body mm,+X vehicle-left,+Y up,+Z front.',owner_photos=['IMG_6329','IMG_6341'],owner_dimensions_applied=False,manufacturer_source='https://www.bmh-ltd.com/vehicle-parts/mg-midget-parts/mg-midget-skin-panels/',factory_source='https://www.spridgets.de/downloads/Parts-1977.pdf#page=231',manufacturer_configuration='Rubber-bumper rear-wing CZJ590 RH/CZJ591 LH architecture comparison, not fitted pressing identity.',basis='Convert the shared owner-photo rear-quarter profile into closed native curved skins with separate declared upper/jamb construction returns, retaining wheel openings and surrounding source datums.',rear_profile_reconstruction=profile,estimated_projected_skin_offset_mm=G,estimated_upper_return_width_mm=20,estimated_jamb_inner_x_mm=.68*S,stations_z_body_units=stations,limits=limits)
a=cq.Assembly(name='unregistered_welded_rear_quarter_references');meshes=[]
for name,shape,color in parts:
 a.add(shape,name=name,color=cq.Color(*color));vs,ts=absolute_tessellation(shape,.10,.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]))
a.export(str(OUT/'rear-quarter-reference.step'));meta['parts']=meshes;(OUT/'rear-quarter-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
r=cq.importers.importStep(str(OUT/'rear-quarter-reference.step')).val();assert r.isValid() and len(r.Solids())==2 and all(s.Volume(1e-9)>0 for s in r.Solids());print(json.dumps(dict(groups=2,valid_connected_quarters=2,station_count=len(stations),projected_gauge_mm=G,upper_return_mm=20)))
