"""Observed electric-pump exterior and local joints; circuit/identity unknown."""
from pathlib import Path
import json,math
import cadquery as cq
import build_engine as E
from observed_pump_mount_layout import BODY_AXIS_MM, BORE_RADIUS_MM, BASE_BELOW_BARREL_CENTER_MM, metadata as mount_metadata
OUT=Path(__file__).parent
BODY=json.loads((OUT.parent/'body-data.json').read_text());S=BODY['published_reference_dimensions_mm']['wheelbase']/2.4
FRAME=BODY['native_engine_placement'];T=[b*S-a for a,b in zip(FRAME['native_anchor_mm'],FRAME['body_anchor_units'])]
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);parts=[]
def V(p):return cq.Vector(*p)
def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p.toTuple()).val()
def ring(ro,ri,l,p,axis=Z):return cq.Solid.makeCylinder(ro,l,p,axis).cut(cq.Solid.makeCylinder(ri,l+2,p-axis,axis))
def add(name,shape,color=(.53,.47,.30)):
 assert shape.isValid() and shape.Volume()>0,name
 parts.append((name,shape,color));return shape
def curve_shell(points,ro,ri):
 edge=cq.Edge.makeBezier(list(map(V,points)));path=cq.Workplane(obj=cq.Wire.assembleEdges([edge]));plane=cq.Plane(origin=V(points[0]),normal=edge.tangentAt(0))
 return cq.Workplane(plane).circle(ro).circle(ri).sweep(path,isFrenet=True).val()
def source_point(body_key):return V([v*S-t for v,t in zip(BODY['fuel_elc_connection_reference']['nodes'][body_key],T)])
old_x,old_y,old_z=E.FUEL_PUMP_CENTER
# Shared body ledge axis minus unchanged engine/body placement. This changes
# the independent whole-car pump source; the older engine-only export stays.
foot_base=V([b-t for b,t in zip(BODY_AXIS_MM,T)])
x=foot_base.x-54;y=foot_base.y+BASE_BELOW_BARREL_CENTER_MM;z=foot_base.z
pose_shift=V([x-old_x,y-old_y,z-old_z]);rear=z-50;front=z+50
# Reuse recognition geometry exactly; no fictional pumping internals are added.
barrel=E.make_electric_fuel_pump_barrel().translate(pose_shift);label=E.make_electric_fuel_pump_label_band().translate(pose_shift);bands=E.make_electric_fuel_pump_red_bands().translate(pose_shift)
add('observed_electric_pump_retained_barrel_reference',barrel,(.57,.48,.27))
add('observed_electric_pump_retained_label_reference',label,(.89,.85,.72))
add('observed_electric_pump_retained_red_bands_reference',bands,(.68,.19,.16))
# Photo supports a curved strap and outboard foot. Retain the ring section,
# add a folded drop web, and put the flat foot below the full ring instead of
# burying its lower edge in the ledge. Fold shape/gauge/levels remain estimates.
mount_parts=E.make_electric_fuel_pump_mount().Solids();assert len(mount_parts)==3
strap=mount_parts[0].translate(pose_shift)
foot_center=foot_base
foot=box(43,5,37,V([x+48,foot_base.y+2.5,z]))
anchor_a=(E.P['electric_fuel_pump_radius']+2)/math.sqrt(2);anchor_b=(E.P['electric_fuel_pump_radius']+4)/math.sqrt(2)
web=cq.Workplane('XY',origin=(x,y,z-13)).polyline([(anchor_a,-anchor_a),(anchor_b,-anchor_b),(38,-44.5),(48,-44.5),(48,-47.5),(35,-47.5)]).close().extrude(26).val()
tool=cq.Solid.makeCylinder(BORE_RADIUS_MM,30,foot_center-Y*10,Y)
foot=foot.cut(tool)
mount=strap.fuse(web).fuse(foot).clean()
assert mount.isValid() and len(mount.Solids())==1
add('observed_electric_pump_strap_and_bored_mount_foot_reference',mount)
lug=ring(10,BORE_RADIUS_MM,1.5,foot_center+Y*5,Y)
fix=cq.Solid.makeCylinder(5,11.5,foot_center-Y*5,Y).fuse(cq.Solid.makeCylinder(8,4,foot_center+Y*6.5,Y))
add('observed_electric_pump_mount_fixing_and_ring_lug_envelopes',cq.Compound.makeCompound([fix,lug]),(.45,.48,.43))

# Port A: visible right-angle brass fitting. Exterior and passages are estimated
# around the old source rear station; an elbow is not an assigned inlet/outlet.
a=V([x,y,rear-23]);up=a+Y*24
elbow=box(20,20,20,a)
elbow=elbow.fuse(cq.Solid.makeCylinder(7,23,a,Z)).fuse(cq.Solid.makeCylinder(5.5,24,a,Y))
elbow=elbow.cut(cq.Solid.makeCylinder(3.5,35,a-Z*2,Z)).cut(cq.Solid.makeCylinder(3.5,30,a-Y*2,Y))
# The two bores meet within the block. Threads/hex standard remain unresolved.
add('observed_electric_pump_bored_right_angle_fitting_a_reference',elbow)

# Photo supports an inline metal connection. Correct only the old estimated
# adjacent-device X/Y offset (-3,-6 mm); no owner dimension is overwritten.
device_shift=pose_shift+cq.Vector(-3,-6,0)
old_device=E.make_fuel_line_adjacent_canister().Solids();assert len(old_device)==3
device_body,device_lip,old_device_outlet=old_device
device_body=device_body.translate(device_shift);device_lip=device_lip.translate(device_shift)
add('observed_flow_marked_device_retained_body_with_declared_axis_alignment',cq.Compound.makeCompound([device_body,device_lip]),(.47,.50,.44))
union=ring(10,3.5,38,V([x,y,front]))
union=union.fuse(ring(14,3.5,12,V([x,y,front+5])))
add('observed_pump_to_flow_device_open_metal_union_reference',union)
out_base=front+38+55;out_tip=out_base+15
outlet=ring(9,3.5,10,V([x,y,out_base]))
outlet=outlet.fuse(ring(5.5,3.5,14,V([x,y,out_base+1])))
add('observed_flow_device_bored_outer_fitting_b_reference',outlet,(.47,.50,.44))

end_a=source_point('pump_hose_a_open');end_b=source_point('canister_hose_b_open')
# Stop at the two prior remote boundaries. These are drawing endpoints, not
# carburetor/tank identities or a completed fuel circuit.
# Untraced bend controls follow the revised upper-body surroundings. Endpoints
# and hose section stay; these chosen rises are not an owner route measurement.
a_controls=[up.toTuple(),(x,y+60,a.z),(end_a.x,end_a.y+240,end_a.z),end_a.toTuple()]
b_start=V([x,y,out_tip]);b_controls=[b_start.toTuple(),(x,y,out_base+35),(end_b.x+50,end_b.y+300,end_b.z),end_b.toTuple()]
hose_a=curve_shell(a_controls,6.5,5.7).fuse(ring(6.5,5.7,9,a+Y*15,Y))
# Simple tangent circular bends keep this estimated local route robust in STEP.
# They preserve the untraced endpoint, without implying an observed full route.
r1,r2=45.,30.;p1=V([x-r1,y,b_start.z+r1]);p2=V([end_b.x+r2,y,p1.z]);p3=V([end_b.x,y-r2,p1.z])
edges=[cq.Edge.makeThreePointArc(b_start,V([x-r1+r1/math.sqrt(2),y,b_start.z+r1/math.sqrt(2)]),p1),cq.Edge.makeLine(p1,p2),cq.Edge.makeThreePointArc(p2,V([p2.x-r2/math.sqrt(2),y-r2+r2/math.sqrt(2),p1.z]),p3)]
drop=p3.y-end_b.y;inset=p3.z-end_b.z;last_r=(drop*drop+inset*inset)/(2*inset);angle=math.atan2(drop,last_r-inset)
mid=V([end_b.x,p3.y-last_r*math.sin(angle/2),p3.z-last_r+last_r*math.cos(angle/2)])
edges.append(cq.Edge.makeThreePointArc(p3,mid,end_b))
path=cq.Workplane(obj=cq.Wire.assembleEdges(edges));hose_b=cq.Workplane(cq.Plane(origin=b_start,normal=Z)).circle(6.5).circle(5.7).sweep(path,isFrenet=True).val().fuse(ring(6.5,5.7,5,V([x,y,out_base+10]),Z))
b_arc_nodes=[list(v.toTuple()) for v in [b_start,p1,p2,p3,end_b]]
add('observed_pump_local_hollow_hose_a_untraced_boundary',hose_a,(.12,.15,.13))
add('observed_flow_device_local_hollow_hose_b_untraced_boundary',hose_b,(.12,.15,.13))
clamps=[]
for center,axis in [(a+Y*21,Y),(V([x,y,out_base+11]),Z)]:
 clamps += [ring(8,6.6,6,center-axis*3,axis),box(10,5,7,center+X*8)]
add('observed_pump_and_flow_device_two_local_clamp_envelopes',cq.Compound.makeCompound(clamps),(.56,.58,.51))

# Local posts/grommets and lead recognition, with no invented circuit polarity,
# fuse, switch, earth quality or hidden harness destination.
posts=[];lead_starts=[]
for dx in [-13,13]:
 p=V([x+dx,y,rear-5]);posts += [ring(4.5,2,5,p,-Z),cq.Solid.makeCylinder(1.5,7,p-Z*5,-Z)];lead_starts.append(p-Z*12)
add('observed_electric_pump_two_terminal_and_grommet_references',cq.Compound.makeCompound(posts),(.15,.18,.16))
leads=[]
for index,start in enumerate(lead_starts):
 end=V([x+30+index*8,y+12,z-10+index*15])
 controls=[start.toTuple(),(start.x,y-15,rear-35),(end.x,y-15,end.z),end.toTuple()]
 edge=cq.Edge.makeBezier(list(map(V,controls)));plane=cq.Plane(origin=start,normal=edge.tangentAt(0))
 leads.append(cq.Workplane(plane).circle(1.6).sweep(cq.Workplane(obj=cq.Wire.assembleEdges([edge])),isFrenet=True).val())
add('observed_electric_pump_two_local_lead_envelopes_unassigned_circuit',cq.Compound.makeCompound(leads),(.39,.28,.14))

# Tests concern external construction only. The retained pump/device bodies
# remain opaque, so no continuous internal flow path is implied.
assert foot.intersect(tool).Volume()<1e-5
assert elbow.intersect(cq.Solid.makeCylinder(3.2,15,a,Y)).Volume()<1e-5
assert union.intersect(cq.Solid.makeCylinder(3.2,38,V([x,y,front]),Z)).Volume()<1e-5
assert outlet.intersect(cq.Solid.makeCylinder(3.2,15,V([x,y,out_base]),Z)).Volume()<1e-5
assert hose_a.intersect(elbow).Volume()<1e-3
assert hose_b.intersect(outlet).Volume()<1e-3
limits='No direct owner dimensions, fitted pump/device identities, pressure/delivery specification, polarity or remote destinations are applied. IMG_6898 supports an electric-pump label, local right-angle fitting, metal connection to the FLOW-marked device, two clamped hose ends, terminals/leads and mount foot. Device function and flow direction are unresolved. All dimensions, hidden sections, bores, threads, hose walls, route curves, clamp settings, axes, clocking and body contacts are estimates. Barrel/label/bands retain their source dimensions under a declared rigid translation. A connected curved strap, estimated folded drop web and lower flat foot replace the overlapping two-solid mount whose ring extends below the foot base. Shared estimated body roof/foot/fixing/receiver axes are construction, not an owner mounting survey. The engine-only core retains its older accessory pose; this independent whole-car reference supersedes that pump pose in the combined viewer. Adjacent-device exterior retains its original source shape with the declared assembly translation plus the earlier -3/-6/0 mm correction to align its estimated axis to the pump; this is a drawing correction, not a measured installed transform. Device outlet and pump fittings are new exterior comparisons. Pump/device internals remain opaque; no pumping mechanism, filter media, continuous internal fluid path or electrical circuit is reconstructed. Two remote hose endpoints retain their prior source coordinates and remain untraced; local Bezier/circular-bend routes are re-estimated to reach the translated sockets, not traced or clearance-approved routes; no tank-feed-to-pump or pump-to-carburetor join is invented. Wiring ends are local recognition boundaries without polarity, safe supply or earth claims. One mount lug does not identify an electrical earth connection. Factory 1500 mechanical-pump variants remain documentary comparisons; no mechanical pump presence, deletion, blanking, bypass or series arrangement is asserted. No fuel compatibility, sealing, support strength, loaded/thermal clearance, test pressure, release order or verified replacement/removal path. Inspection separation is illustrative.'
limits+=' Hose-A upper Bezier controls now use60mm rise above the barrel and240mm above the retained remote endpoint instead of100/300mm. These are provisional construction curves under revised surroundings, not traced routing or a clearance-approved replacement hose.'
meta=dict(units='mm',source_frame='Maintained native engine frame +X vehicle-left, +Y up, +Z front; same estimated body transform, with independent pump/foot pose revision.',owner_photos=['IMG_6898','IMG_6855','IMG_6856','IMG_7034'],factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=92','https://www.spridgets.de/downloads/Parts-1977.pdf#page=95'],owner_dimensions_applied=False,mount_reference=mount_metadata(),assembly_translation_mm=list(pose_shift.toTuple()),superseded_pump_center_mm=[old_x,old_y,old_z],basis='Owner IMG_6898 supports foot/fastener on painted ledge and local pump/fitting/device/hose/lead topology. The whole-car pump assembly now uses a shared estimated ledge/receiver axis and connected dropped foot; all placement and new bracket geometry remain unregistered estimates. Newer carburetor photos supply intake context but do not trace the missing remote hose destinations. Factory p95 lists AC Delco mechanical pump and distinct USA/Canada feeds; p92 GAN6 main/intermediate pipe architecture does not identify this owner modification.',nodes=dict(pump_center_mm=[x,y,z],mount_axis_center_mm=list(foot_center.toTuple()),mount_axis=[0,1,0],mount_bore_radius_mm=5.4,fitting_a_corner_mm=list(a.toTuple()),fitting_a_hose_socket_mm=list(up.toTuple()),union_start_mm=[x,y,front],union_end_mm=[x,y,front+38],fitting_b_base_mm=[x,y,out_base],fitting_b_hose_socket_mm=list(b_start.toTuple()),untraced_a_mm=list(end_a.toTuple()),untraced_b_mm=list(end_b.toTuple()),hose_a_bezier_controls_mm=a_controls,hose_b_superseded_bezier_controls_mm=b_controls,hose_b_estimated_tangent_arc_nodes_mm=b_arc_nodes,hose_b_estimated_bend_radii_mm=[r1,r2,last_r],adjacent_device_old_axis_xy_mm=[old_x+3,old_y+6],adjacent_device_shifted_pre_alignment_axis_xy_mm=[x+3,y+6],adjacent_device_current_axis_xy_mm=[x,y],adjacent_device_axis_correction_mm=[-3,-6,0],estimated_assembly_translation_mm=list(pose_shift.toTuple())),limits=limits)
assembly=cq.Assembly(name='observed_electric_pump_local_external_relationships');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/'observed-fuel-pump-reference.step'));meta['parts']=meshes
(OUT/'observed-fuel-pump-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'observed-fuel-pump-reference.step')).val();assert restored.isValid() and all(v.isValid() and v.Volume(1e-9)>0 for v in restored.Solids())
assert math.isclose(restored.Volume(1e-9),sum(v.Volume(1e-9) for _,v,_ in parts),rel_tol=1e-7)
print(json.dumps(dict(source_parts=len(parts),selectable_groups=12,solids=len(restored.Solids()),step_valid=True,step_volume_roundtrip=True,connected_mount=True,external_passages_open=True,mount_bore_open=True,local_hose_socket_overlap_absent=True,remote_boundaries_retained=True)))
