"""Open cage, toothless crown wheel and side-bearing/cap comparison."""
from pathlib import Path
import json,math
import cadquery as cq
OUT=Path(__file__).parent;B=json.loads((OUT.parent/'body-data.json').read_text());R=B['differential_support_reference'];S=2032/2.4
X=cq.Vector(1,0,0);Y=cq.Vector(0,1,0);Z=cq.Vector(0,0,1);O=cq.Vector(0,.39*S,-1.2*S);parts=[];nodes={};contacts={}
def cyl(r,l,p,a=X):return cq.Solid.makeCylinder(r,l,p,a)
def ring(ro,ri,l,p,a=X):return cyl(ro,l,p,a).cut(cyl(ri,l+2,p-a,a))
def box(w,h,d,p):return cq.Workplane('XY').box(w,h,d).translate(p.toTuple()).val()
def comp(xs):return cq.Compound.makeCompound(xs)
def hexagon(ro,l,p,a):return cq.Workplane(cq.Plane(origin=p,normal=a)).polygon(6,2*ro).extrude(l).val()
def put(n,s,c=(.42,.47,.40)):
 assert s.isValid() and s.Volume()>0,n
 parts.append((n,s.translate(O),c));return s
def point(p):return list((p+O).toTuple())
def bore(s,p,r,a,l=60):return s.cut(cyl(r,l,p-a*l/2,a))
cage=ring(65,48,100,cq.Vector(-50,0,0))
for sign in [-1,1]:
 cage=cage.cut(box(70,100,70,cq.Vector(0,sign*65,0))).cut(box(70,70,100,cq.Vector(0,0,sign*65)))
 end=ring(50,36.5,18,cq.Vector(sign*50,0,0),X*sign)
 end=end.fuse(ring(50,14,2,cq.Vector(sign*68,0,0),X*sign)).fuse(ring(17.5,14,17.1,cq.Vector(sign*70,0,0),X*sign))
 cage=cage.fuse(end)
cage=cage.fuse(ring(74,36.5,5,cq.Vector(62,0,0)))
wheel=ring(112,50.2,8,cq.Vector(67,0,0)).fuse(cq.Solid.makeCone(125,130,13,cq.Vector(67,0,0),X).cut(cyl(110,15,cq.Vector(66,0,0))))
axes=[cq.Vector(62,60*math.cos(i*math.pi/3),60*math.sin(i*math.pi/3)) for i in range(6)]
for p in axes:
 cage=bore(cage,p,4.5,X,30);wheel=bore(wheel,p,4.5,X,30)
assert len(cage.Solids())==1 and len(wheel.Solids())==1
put('differential_open_cage_end_walls_bored_mount_and_hollow_journals',cage)
put('differential_toothless_crown_wheel_boundary_without_ratio',wheel)
bolts=[];tabs=[]
for p in axes:bolts.append(cyl(4,14,p-X).fuse(hexagon(7,6,p-X*7,X)))
for i in [0,2,4]:
 a,b=axes[i],axes[i+1];lo=i*math.pi/3;hi=(i+1)*math.pi/3
 def yz(r,t):return (r*math.cos(t),r*math.sin(t))
 tab=cq.Workplane('YZ',origin=(61,0,0)).moveTo(*yz(67,lo)).threePointArc(yz(67,(lo+hi)/2),yz(67,hi)).lineTo(*yz(53,hi)).threePointArc(yz(53,(lo+hi)/2),yz(53,lo)).close().extrude(1).val()
 for p in [a,b]:tab=tab.fuse(cyl(7,1,p-X));tab=bore(tab,p,4.5,X,8)
 tabs.append(tab)
put('differential_six_unthreaded_crown_wheel_bolt_envelopes',comp(bolts),(.57,.60,.52))
put('differential_three_flat_paired_crown_wheel_lock_tabs',comp(tabs),(.58,.60,.52))
bearings=[];caps=[];pack=[]
for sign,label in [(1,'left'),(-1,'right')]:
 low,high=R['bearing_x_abs_mm'];p=cq.Vector(sign*low,0,0)
 bearing=ring(36,17.5,17,p,X*sign);bearings.append(bearing)
 put('differential_'+label+'_independent_angular_contact_bearing_boundary',bearing,(.59,.62,.54))
 x=R['cap_nodes'][label]['x_local_mm'];cap=ring(44,36,17,cq.Vector(x-8.5,0,0)).intersect(box(200,200,90,cq.Vector(x,0,-45)))
 for y in [-43,43]:cap=cap.fuse(box(17,20,8,cq.Vector(x,y,-4)))
 cap=cap.cut(cyl(36,19,cq.Vector(x-9.5,0,0)))
 cap_axes=[cq.Vector(*a) for a in R['cap_nodes'][label]['stud_axes_local_mm']]
 for a in cap_axes:cap=bore(cap,a,5.2,Z,40)
 assert len(cap.Solids())==1
 caps.append((label,cap));pack.append(ring(34,17.7,.1,cq.Vector(sign*70,0,0),X*sign))
 nodes[label]=dict(bearing_axis_body=[sign,0,0],bearing_bounds_body_mm=[point(cq.Vector(sign*v,0,0)) for v in [low,high]],bearing_center_body_mm=point(cq.Vector(x,0,0)),cap_stud_axes_body_mm=[point(a) for a in cap_axes],halfshaft_inner_boundary_body_mm=point(cq.Vector(sign*40,0,0)),side_gear_boundary_bounds_body_mm=[point(cq.Vector(sign*v,0,0)) for v in [40,68]])
 for s in [cage,bearing]:assert s.intersect(cyl(11.2,210,cq.Vector(-105,0,0))).Volume()<1e-5
 assert cage.intersect(bearing).Volume()<1e-5 and cap.intersect(bearing).Volume()<1e-5
for label,cap in caps:put('differential_'+label+'_rear_half_bearing_cap_with_shared_stud_bores',cap)
hardware=[]
for n in R['cap_nodes'].values():
 for v in n['stud_axes_local_mm']:
  p=cq.Vector(*v);hardware.append(cyl(4.5,32,p-Z*24,Z))
  hardware.append(ring(10,5.2,2,p-Z*10,Z))
  hardware.append(hexagon(8.5,8,p-Z*18,Z).cut(cyl(4.8,10,p-Z*19,Z)))
put('differential_four_cap_stud_nut_and_unloaded_washer_sets',comp(hardware),(.57,.60,.52))
put('differential_two_unselected_side_bearing_packing_envelopes',comp(pack),(.66,.67,.55))
for p in axes:
 for s in [cage,wheel,comp(tabs)]:assert s.intersect(cyl(4.3,24,p-X*5)).Volume()<1e-5
# Comparison includes the retained shaft/gear boundaries and pinion head.
# No tooth engagement, hypoid offset, bearing load or removal path is inferred.
neighbors=[]
for file,offset in [('rear-axle-reference.step',O),('rear-hub-reference.step',cq.Vector()),('pinion-support-reference.step',cq.Vector())]:
 a=cq.Assembly.importStep(str(OUT/file))
 for name,obj in a.objects.items():
  if obj.obj is None:continue
  s=obj.obj.val() if isinstance(obj.obj,cq.Workplane) else obj.obj
  if isinstance(s,cq.Shape):neighbors.append((name,s.translate(offset)))
carrier=next(s for n,s in neighbors if n=='differential_carrier_reference')
for n in R['cap_nodes'].values():
 for v in n['stud_axes_local_mm']:
  q=cq.Vector(*v)+O;assert carrier.intersect(cyl(5,40,q-Z*20,Z)).Volume()<1e-5
for s in bearings:assert s.translate(O).intersect(carrier).Volume()<1e-5
for name,a,_ in parts:
 for other,b in neighbors:
  ba,bb=a.BoundingBox(),b.BoundingBox()
  if not all(getattr(ba,k+'min')<=getattr(bb,k+'max') and getattr(bb,k+'min')<=getattr(ba,k+'max') for k in ['x','y','z']):continue
  v=round(a.intersect(b).Volume(),4)
  if v>1e-4:contacts[name+'__'+other]=v
v=round(wheel.intersect(comp(hardware)).Volume(),4)
if v>1e-4:contacts['crown_wheel__cap_hardware_reference']=v
limits='No new owner dimensions or fitted ratio/cage/bearing/cap/gear/shaft identity applied. Factory pp146/147 establish cage, two side bearings, four cap studs/nuts, six crown-wheel bolts/three paired locking tabs, selected packing and matched gearset roles. SKF7207 BEP 35x72x17 mm is an independent angular-contact boundary comparison, not an approved2K5943/fitted replacement, orientation or loaded setting. Bearing race/ball/contact geometry is absent. All cage windows/end walls/journals, crown-wheel smooth bevel, bolt maps, saddle/arm/cap profiles, gauges, stud/nut/washer and installed stations are estimates. The positive-X crown-wheel side is a construction choice, not a verified fitted handed arrangement. No gear teeth or ratio is selected; the pinion/crown-wheel working mesh and planet/cross-pin internals remain unresolved and are not forced into contact. Crown wheel retains factory six-bolt/three-tab roles on an estimated120 mm PCD; locking tabs are flat/unloaded without bent retention or thread engagement. Four cap bores/fasteners share estimated axes with integral front saddles/arms added to the retained carrier. Only carrier and two side-gear boundary meshes change in the axle source; five others remain identical, including all pinion support interfaces. Halfshaft tips extend inward50 mm, from+/-90 to+/-40, and toothless gear boundaries move from+/-90..120 to+/-40..68, trimming their drawing length2 mm for the cage end-wall reference. These are unmeasured construction corrections, not actual shaft length, gear profile or spline engagement. Only two shaft meshes change in the hub source; thirty others remain identical. Original nominal track, axle tube ends, hub/drum stacks, source pinion pose and propshaft mating/fixing maps remain unchanged. Displayed0.1 mm packing is an unselected construction separator, not a factory offered shim, fitted stack, end float or preload. Factory packing sizes remain separate inch references. Source contacts are reconstruction discrepancies or overlapping source divisions, not actual-car damage or fitted interferences. No support strength, matched-cap identity/orientation, gear contact/backlash, angular-bearing direction/preload, lubrication, torque, safe release or verified differential/shaft/cap replacement path. Inspection separation is illustrative.'
meta=dict(units='mm',source_frame='Canonical body mm +X vehicle-left +Y up +Z front.',factory_sources=R['factory_sources'],manufacturer_source=R['manufacturer_source'],manufacturer_boundary_comparison_mm=R['bearing_comparison_mm'],owner_dimensions_applied=False,basis='Factory cage/crown-wheel/side-bearing/cap/fixing/packing roles, plus independent manufacturer bearing boundary. Existing owner cabin/underbody evidence does not calibrate hidden axle internals; all new installed geometry and connections remain construction estimates.',registration_status=R['registration_status'],nodes=nodes,crown_fixing_axes_body_mm=[point(p) for p in axes],factory_packing_sizes_inch=R['factory_packing_sizes_inch'],display_packing_thickness_mm=.1,packing_limit=R['packing_limit'],sampled_source_contacts_mm3=contacts,limits=limits)
a=cq.Assembly(name='unregistered_cage_crown_wheel_and_side_supports');meshes=[]
for name,s,c in parts:
 a.add(s,name=name,color=cq.Color(*c));vs,ts=s.tessellate(.5,.16);meshes.append(dict(name=name,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]))
a.export(str(OUT/'differential-support-reference.step'));meta['parts']=meshes;(OUT/'differential-support-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n');restored=cq.importers.importStep(str(OUT/'differential-support-reference.step')).val();assert restored.isValid();print(json.dumps(dict(meshes=len(parts),solids=len(restored.Solids()),step_valid=True,cage_and_caps_connected=True,shared_crown_and_cap_bores=True,open_halfshaft_passages=True,source_contacts=contacts)))
