"""Observed manifold-side round unit; early Federal EGR candidate only."""
from pathlib import Path
import json,math
import cadquery as cq
import build_engine as E
OUT=Path(__file__).parent
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);A=-X;parts=[]
def V(p):return cq.Vector(*p)
def ring(ro,ri,length,p,axis):return cq.Solid.makeCylinder(ro,length,p,axis).cut(cq.Solid.makeCylinder(ri,length+2,p-axis,axis))
def add(name,shape,color=(.39,.32,.24)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color));return shape
def hex_ring(p,axis,diameter,length,bore):
 plane=cq.Plane(origin=p,xDir=(0,0,1),normal=axis)
 return cq.Workplane(plane).polygon(6,diameter).extrude(length).val().cut(cq.Solid.makeCylinder(bore,length+2,p-axis,axis))
seat=V([-140,-20,75]);body_end=seat+A*24;actuator=seat+A*58
pipe_port=V([-160,-6,75]);banjo=V([-125,68,28.5]);vacuum_mouth=actuator+A*19
# New seat and station are estimates. Existing source castings are neither
# drilled nor reposed to make this candidate identification appear fitted.
body=ring(13,5,24,seat,A)
body=body.fuse(ring(8,3.5,14,pipe_port-Y*4,Y))
body=body.cut(cq.Solid.makeCylinder(3.5,30,V([-160,-22,75]),Y)).cut(cq.Solid.makeCylinder(5.3,14,pipe_port,Y))
add('manifold_round_unit_external_body_and_bored_pipe_port_comparison',body)
lock=hex_ring(seat+A*2,A,34,6,13.4)
add('manifold_round_unit_smooth_mount_locknut_comparison',lock,(.45,.40,.30))
# Open cage, not a hidden diaphragm/spring mechanism. Four supports are an
# estimated recognition count; neither photograph proves their full pattern.
cage=[ring(25,5,2,body_end,A),ring(28,23,2,actuator+X*2,A)]
for i in range(4):
 angle=2*math.pi*i/4;offset=V([0,23*math.cos(angle),23*math.sin(angle)])
 p=body_end+offset;length=(actuator+X*2-body_end).Length
 cage.append(cq.Solid.makeCylinder(2,length,p,A))
cage.append(cq.Solid.makeCylinder(2.5,31,body_end,A))
add('manifold_round_unit_open_support_cage_and_stem_recognition',cq.Compound.makeCompound(cage))
cover=cq.Solid.makeCone(29,22,13,actuator,A).cut(cq.Solid.makeCone(27,20,12,actuator+X*.5,A))
cover=cover.fuse(ring(32,27,2,actuator,A))
# Opaque cover end, without an invented diaphragm, return spring or valve seat.
cover=cover.fuse(cq.Solid.makeCylinder(22,1.5,actuator+A*12,A))
cover=cover.cut(cq.Solid.makeCylinder(1.5,18,actuator,A))
add('manifold_round_unit_shallow_cover_and_rolled_rim_reference',cover)
spigot=ring(3,1.5,10,actuator+A*9,A)
add('manifold_round_unit_open_small_hose_spigot_reference',spigot,(.50,.45,.32))
vacuum_end=V([-250,15,25]);controls=[list(vacuum_mouth.toTuple()),[-232,-20,75],[-250,-5,25],list(vacuum_end.toTuple())]
edge=cq.Edge.makeBezier(list(map(V,controls)));path=cq.Workplane(obj=cq.Wire.assembleEdges([edge]));plane=cq.Plane(origin=vacuum_mouth,normal=edge.tangentAt(0))
small_hose=cq.Workplane(plane).circle(3.2).circle(1.8).sweep(path,isFrenet=True).val()
add('manifold_round_unit_local_control_hose_untraced_boundary',small_hose,(.12,.15,.13))

# Photo supports a rigid pipe/union at the unit. Factory p65 supplies the
# candidate banjo/washer/nut/olive topology; the hidden manifold joint and
# complete route are NOT identified as fitted by joining estimated geometry.
pipe_controls=[list((pipe_port+Y*14).toTuple()),[-160,35,75],[-125,68,65],list(banjo.toTuple())]
edge=cq.Edge.makeBezier(list(map(V,pipe_controls)));path=cq.Workplane(obj=cq.Wire.assembleEdges([edge]));plane=cq.Plane(origin=V(pipe_controls[0]),normal=edge.tangentAt(0))
outer=cq.Workplane(plane).circle(5).sweep(path,isFrenet=True).val().fuse(cq.Solid.makeCylinder(5,14,pipe_port,Y))
void=cq.Workplane(plane).circle(3.5).sweep(path,isFrenet=True).val().fuse(cq.Solid.makeCylinder(3.5,14,pipe_port,Y))
eye=cq.Solid.makeCylinder(11,8,banjo-Y*4,Y)
pipe=outer.fuse(eye).cut(void.fuse(cq.Solid.makeCylinder(4.5,12,banjo-Y*6,Y)))
add('manifold_round_unit_rigid_pipe_and_bored_banjo_eye_comparison',pipe,(.48,.40,.27))
nut=hex_ring(pipe_port-Y*2,Y,22,7,5.3)
olive=ring(6.8,5.2,2,pipe_port+Y*4,Y)
add('manifold_round_unit_pipe_union_nut_and_olive_envelopes',cq.Compound.makeCompound([nut,olive]),(.49,.46,.36))
bolt=cq.Solid.makeCylinder(4,15,banjo-Y*6,Y)
bolt=bolt.fuse(hex_ring(banjo+Y*5,Y,18.5,5,0.01))
# No cross-drilling/thread helix or functioning banjo bolt is inferred.
add('manifold_round_unit_smooth_banjo_bolt_envelope',bolt,(.52,.52,.45))
washers=[ring(10,4.6,1,banjo+Y*v,Y) for v in [-5,4]]
add('manifold_round_unit_two_banjo_washer_comparisons',cq.Compound.makeCompound(washers),(.53,.48,.33))

assert pipe.intersect(cq.Solid.makeCylinder(4.3,14,banjo-Y*7,Y)).Volume()<1e-5
joint=body.intersect(pipe);jbox=joint.BoundingBox() if joint.Volume()>1e-3 else None
assert joint.Volume()<1e-3,('body/pipe',joint.Volume(),{k:getattr(jbox,k) for k in ['xmin','xmax','ymin','ymax','zmin','zmax']} if jbox else None)
assert spigot.intersect(cq.Solid.makeCylinder(1.3,12,actuator+A*8,A)).Volume()<1e-5
assert nut.intersect(cq.Solid.makeCylinder(5.1,10,pipe_port-Y*3,Y)).Volume()<1e-5
neighbors={'carburetor':E.make_carburetor(),'cleaner':E.make_air_cleaner_housing(),'inlet_manifold':E.make_inlet_manifold(),'exhaust_manifold':E.make_exhaust_manifold()}
contacts={name:{key:round(shape.intersect(other).Volume(),4) for key,other in neighbors.items()} for name,shape,_ in parts if name in ['manifold_round_unit_external_body_and_bored_pipe_port_comparison','manifold_round_unit_shallow_cover_and_rolled_rim_reference','manifold_round_unit_rigid_pipe_and_bored_banjo_eye_comparison']}
limits='Observed round unit is a candidate early Federal EGR arrangement, not verified TKC1470 identity or proof of present operating state. No direct owner dimensions are applied. All diameters, sections, cage count, axis/tilt, casting seat, fitted pose, pipe route, banjo station, bore, thread and fastener geometry are estimates. IMG_6887 supports the round cover/cage/small hose; IMG_7042 supports local rigid pipe/union and casting context. Hidden pipe/manifold endpoint is a factory-topology comparison, not a traced fitted connection. The small control hose ends at an unassigned boundary without a carburetor, purge or vacuum assignment. Existing source manifolds/carburetor/cleaner and their alignment conflict remain unchanged; no port or mounting seat is cut to manufacture fit. Source overlaps are reported as reconstruction contacts, not fitted interference or clearance. Cover is hollow exterior recognition with opaque end; actual diaphragm/spring, pintle/seat, calibrated orifices, gas passage network and operating travel are absent. Smooth bolt/nut/olive/washer envelopes do not establish threads, sealing, torque or service tools. Later TKC3306/TKC3320 and UKC7722 branches remain unselected. No function test, thermal clearance, safe release order, verified pipe extraction or component replacement/removal path. Separation is illustrative and breaks comparison connections.'
meta=dict(units='mm',source_frame='Native engine +X vehicle-left, +Y up, +Z front; mm. Same estimated body transform as maintained engine.',owner_photos=['IMG_6887','IMG_7042'],factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=65','https://www.spridgets.de/downloads/Parts-1977.pdf#page=69'],owner_dimensions_applied=False,identity_status='observed manifold-side round unit; early Federal EGR candidate, fitted number/function unverified',basis='Factory p65 USA/Canada-to-1977 TKC1470/160108 valve/locknut and UKC1813 rigid pipe with UKC1846 banjo bolt, two UKC2284 washers and UKC1413/TL11 union parts provide comparison topology. Existing owner views support cover/cage, local small hose, rigid pipe and union, without dimensions or hidden manifold registration.',nodes=dict(estimated_valve_seat_mm=list(seat.toTuple()),estimated_axis=list(A.toTuple()),estimated_actuator_center_mm=list(actuator.toTuple()),small_hose_mouth_mm=list(vacuum_mouth.toTuple()),untraced_small_hose_end_mm=list(vacuum_end.toTuple()),estimated_pipe_port_mm=list(pipe_port.toTuple()),estimated_banjo_eye_mm=list(banjo.toTuple()),banjo_axis=[0,1,0],banjo_eye_bore_radius_mm=4.5,pipe_union_bore_radius_mm=5.3,pipe_controls_mm=pipe_controls,small_hose_controls_mm=controls),source_neighbor_overlaps_mm3=contacts,limits=limits)
assembly=cq.Assembly(name='observed_manifold_round_unit_early_federal_comparison');meshes=[]
for name,shape,color in parts:
 assembly.add(shape,name=name,color=cq.Color(*color));vs,ts=shape.tessellate(.4,.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]))
assembly.export(str(OUT/'manifold-diaphragm-reference.step'));meta['parts']=meshes
(OUT/'manifold-diaphragm-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'manifold-diaphragm-reference.step')).val();assert restored.isValid()
print(json.dumps(dict(groups=len(parts),solids=len(restored.Solids()),step_valid=True,external_banjo_union_and_small_spigot_passages=True,source_neighbor_overlaps_mm3=contacts)))
