"""Estimated static clamping stack; no fitted clutch dimensions or release path.

Factory1500 p79 establishes GCC196/GCP230, GRB207 and six146176/LWN305
fixings. General specification gives7¼in. Existing source flywheel/crank/bolt
and dowel stations are drawing datums. The new pressure plate, cover depth,
facings, diaphragm and drive straps are construction geometry, not selected
parts, loaded spring state, wear limits or fitted removal clearance.
"""
from pathlib import Path
import json,math,hashlib
import cadquery as cq
from front_wing_geometry import absolute_tessellation
from rear_engine_interface_layout import RING_ID
OUT=Path(__file__).parent
B=json.loads((OUT.parent/'body-data.json').read_text());S=2032/2.4
EP=B['native_engine_placement'];CORE=OUT/EP['source_url'].split('/')[-1].split('?')[0]
CORE_STEP=CORE.with_suffix('.step')
engine=cq.Assembly.load(str(CORE_STEP))
Y=-225.;Z=cq.Vector(0,0,1);parts=[];shapes={}
# All dimensions below except the nominal7¼in OD and catalog quantities are
# estimates. Keep old flywheel/rear crank and four crank-fastener geometry.
OD=184.15;F=-208.;FACING_THICKNESS=1.5;CORE_THICKNESS=2.;FACING_INNER=52.
RECESS_RADIUS=50.;PRESSURE_FRONT=F-2*FACING_THICKNESS-CORE_THICKNESS
PRESSURE_BACK=PRESSURE_FRONT-7.;DIAPHRAGM_INNER_Z=-248.
DIAPHRAGM_OUTER_Z=-225.;DIAPHRAGM_THICKNESS=1.2
FIXING_R=101.;FIXING_AXES=[(FIXING_R*math.cos(math.radians(a)),Y+FIXING_R*math.sin(math.radians(a))) for a in range(0,360,60)]
DOWEL_AXES=[(FIXING_R*math.cos(math.radians(a)),Y+FIXING_R*math.sin(math.radians(a))) for a in [30,150,270]]
def point(x,y,z):return cq.Vector(x,Y+y,z)
def cyl(r,length,x=0,y=0,z=0):return cq.Solid.makeCylinder(r,length,point(x,y,z),Z)
def ring(ro,ri,length,z):return cyl(ro,length,z=z).cut(cyl(ri,length+2,z=z-1))
def block(w,h,d,x,y,z):return cq.Workplane('XY').box(w,h,d).translate(point(x,y,z)).val()
def turn(s,a):return s.rotate((0,Y,0),(0,Y,1),a)
def radial(r,a,z,t=0):
 a=math.radians(a);return point(r*math.cos(a)-t*math.sin(a),r*math.sin(a)+t*math.cos(a),z)
def revolved(points):return cq.Workplane('XZ').polyline(points).close().revolve(360,(0,0),(0,1)).val().translate((0,Y,0))
def add(name,shape,color):
 assert shape.isValid() and shape.Volume()>0,name
 shapes[name]=shape;parts.append((name,shape,color));print(name,flush=True)
def native(name):return engine.objects[name].obj
flywheel=native('flywheel_catalog_topology_provisional').fuse(ring(88,RECESS_RADIUS,3,F)).clean()
for x,y in FIXING_AXES:flywheel=flywheel.cut(cyl(3.5,14,x,y-Y,F-1))
assert len(flywheel.Solids())==1
add('clutch_flywheel_retained_envelope_revised_central_recess_and_six_cover_bores_reference',flywheel,(.43,.45,.43))
# Preserve the old hub/recognition damper roles, with a larger continuous steel
# core and an explicit carrier web. The union is a static assembly envelope,
# not a welded hub or a torsional damper simulation.
old=native('clutch_disc_184mm_20_splines_catalog_reference_provisional')
core=ring(OD/2-1,32,CORE_THICKNESS,F-FACING_THICKNESS-CORE_THICKNESS)
hub_and_roles=[]
for solid in old.Solids():
 bb=solid.BoundingBox()
 if bb.xmax-bb.xmin>150:continue  # replace only the old undersized outer core
 hub_and_roles.append(solid.translate((0,0,1.5)))
carrier=ring(44,24,2,-215.5)
disc=core.fuse(carrier,*hub_and_roles).clean()
assert len(disc.Solids())==1
add('clutch_driven_plate_connected_core_hub_and_smooth_damper_roles_reference',disc,(.43,.45,.43))
facings=cq.Compound.makeCompound([ring(OD/2,FACING_INNER,FACING_THICKNESS,F-FACING_THICKNESS),ring(OD/2,FACING_INNER,FACING_THICKNESS,PRESSURE_FRONT)])
add('clutch_pair_nominal_diameter_unloaded_friction_facings_reference',facings,(.22,.19,.15))
# The source plane touch is a constructed clamping stack, not compressed parts.
def spring_z(r):return DIAPHRAGM_INNER_Z+(r-24)*(DIAPHRAGM_OUTER_Z-DIAPHRAGM_INNER_Z)/60
pressure=ring(94,48,PRESSURE_FRONT-PRESSURE_BACK,PRESSURE_BACK)
load_rim=revolved([(75,PRESSURE_BACK),(86,PRESSURE_BACK),(86,spring_z(84)+DIAPHRAGM_THICKNESS),(84,spring_z(84)+DIAPHRAGM_THICKNESS),(75,spring_z(75)+DIAPHRAGM_THICKNESS)])
pressure=pressure.fuse(load_rim)
for a in [0,120,240]:pressure=pressure.fuse(turn(block(18,14,5,98,0,(PRESSURE_FRONT+PRESSURE_BACK)/2),a))
pressure=pressure.clean();assert len(pressure.Solids())==1
add('clutch_pressure_plate_annulus_load_rim_and_three_drive_lug_roles_reference',pressure,(.49,.50,.47))
# Eighteen visual fingers retain the former reference count; actual spring,
# fulcrum, strap quantity and loaded cone/contacts are not identified.
spring=revolved([(24,DIAPHRAGM_INNER_Z),(84,DIAPHRAGM_OUTER_Z),(84,DIAPHRAGM_OUTER_Z+DIAPHRAGM_THICKNESS),(24,DIAPHRAGM_INNER_Z+DIAPHRAGM_THICKNESS)])
for a in range(0,360,20):spring=spring.cut(turn(block(42,1.8,40,43,0,-235),a))
spring=spring.clean();assert len(spring.Solids())==1
wire_z=spring_z(70)-1.2*math.sqrt(1+((DIAPHRAGM_OUTER_Z-DIAPHRAGM_INNER_Z)/60)**2)
support=cq.Solid.makeTorus(70,1.2,point(0,0,wire_z),Z)
add('clutch_conical_diaphragm_fingers_and_annular_support_role_reference',cq.Compound.makeCompound([spring,support]),(.39,.41,.38))
back=wire_z-1.2
cover=revolved([(107,F-2),(101,-212),(98,-224),(79,back-2),(68,back-2),(68,back),(78,back),(96,-222.8),(99,-211.4),(105,F),(107,F)])
cover=cover.fuse(ring(107,94,2,F-2))
for a in range(0,360,60):
 p=radial(FIXING_R,a,F-2);ear=cq.Solid.makeCylinder(9,2,p,Z).cut(cyl(94,4,z=F-3));cover=cover.fuse(ear)
for a in [0,120,240]:cover=cover.cut(turn(block(40,18,10,102,0,-218),a))
for x,y in FIXING_AXES:cover=cover.cut(cyl(3.5,6,x,y-Y,F-4))
for x,y in DOWEL_AXES:cover=cover.cut(cyl(3.3,16,x,y-Y,F-12))
cover=cover.clean();assert len(cover.Solids())==1
add('clutch_pressed_hollow_cover_six_ears_three_windows_and_shared_bores_reference',cover,(.46,.48,.45))
screws=[]
for x,y in FIXING_AXES:
 shank=cyl(3.175,14,x,y-Y,F-3)
 head=cq.Workplane('XY').center(x,y).polygon(6,11).extrude(4).translate((0,0,F-7)).val()
 washer=cq.Solid.makeCylinder(6.5,1,cq.Vector(x,y,F-3),Z).cut(cq.Solid.makeCylinder(3.5,3,cq.Vector(x,y,F-4),Z))
 screws.extend([shank.fuse(head),washer])
add('clutch_six_unthreaded_cover_screw_and_unloaded_washer_roles_reference',cq.Compound.makeCompound(screws),(.62,.64,.57))
straps=[]
for a in [0,120,240]:
 p=radial(99,a,PRESSURE_FRONT,-4);q=radial(103,a,F-2,15)
 tangent=cq.Vector(-math.sin(math.radians(a)),math.cos(math.radians(a)),0)*3
 w=cq.Wire.makePolygon([p-tangent,q-tangent,q+tangent,p+tangent,p-tangent]);straps.append(cq.Solid.extrudeLinear(w,[],Z*.8))
add('clutch_three_unloaded_pressure_plate_to_cover_drive_strap_roles_reference',cq.Compound.makeCompound(straps),(.46,.48,.45))
# Genuine geometric checks: contact annuli, fixed source datums and material
# around open fixing bores. None validates preload, support or working motion.
for x,y in FIXING_AXES:
 for s in [flywheel,cover]:assert s.intersect(cyl(3.3,16,x,y-Y,F-3)).Volume()<1e-6
for r,z in [(55,F+.2),(90,F+.2),(55,PRESSURE_FRONT-.2),(90,PRESSURE_FRONT-.2)]:
 s=flywheel if z>F else pressure
 assert s.isInside(point(r,0,z),1e-6)
assert flywheel.intersect(facings).Volume()<1e-5
assert pressure.intersect(facings).Volume()<1e-5
assert cover.intersect(disc).Volume()<1e-5
assert cover.intersect(facings).Volume()<1e-5
assert cover.intersect(pressure).Volume()<1e-5
assert spring.intersect(pressure).Volume()<1e-5
assert spring.intersect(cover).Volume()<1e-5
assert cover.intersect(native('clutch_three_locating_dowels_catalog_reference_provisional')).Volume()<1e-5
# Keep the release mechanism fixed. Record its remaining gap to the new spring.
CP=B['native_clutch_release_placement'];T=[EP['body_anchor_units'][i]*S-EP['native_anchor_mm'][i] for i in range(3)]
release=cq.Assembly.load(str(OUT/'clutch-release-reference.step'))
bearing=release.objects['clutch_separate_annular_release_bearing_envelope'].obj
bearing_front=bearing.BoundingBox().zmax+CP['body_anchor_units'][2]*S-CP['native_anchor_mm'][2]
contacts={};housing=release.objects['clutch_hollow_housing_integral_boss_ears_and_pivot_saddles_comparison'].obj
for name,shape,color in parts:
 posed=shape.translate((T[0],T[1]-.39*S,T[2]));volume=posed.intersect(housing).Volume()
 if volume>1e-4:contacts[name]=round(volume,4)
transmission=cq.Assembly.load(str(OUT/'transmission-reference.step'))
shaft_bounds=transmission.objects['gearbox_input_shaft_reference'].obj.BoundingBox()
pilot_bounds=native('pilot_bushing_supplier_dimensions_provisional').BoundingBox()
pilot_interval=[pilot_bounds.zmin+T[2],pilot_bounds.zmax+T[2]]
input_hub_reference=dict(hub_visual_circular_bore_radius_mm=12.7,retained_visual_hub_slot_count=20,smooth_main_input_radius_mm=.012*S,hub_role_native_z_bounds_mm=[-224.5,-205.5],input_end_body_z_mm=shaft_bounds.zmax,pilot_bush_body_z_bounds_mm=pilot_interval,source_input_pilot_axial_overlap_mm=max(0,min(shaft_bounds.zmax,pilot_interval[1])-max(shaft_bounds.zmax-15,pilot_interval[0])),limits='Hub circular/scalloped bore and smooth input are unmeasured recognition envelopes, not mating spline profiles or torque engagement. Twenty visual slots retain a catalog comparison count, not fitted identity. Pilot overlap is an interval calculation under unchanged drawing transforms, not verified insertion, bearing length, clearance or operation.')
limits='Factory roles and nominal7¼in diameter are references, not fitted part identification. All recesses, profiles, axial planes, wall/facing/core gauges, hub and damper geometry, spring count/cone/support, strap count/shape, fixing maps and screw lengths/heads/washers are estimates; the smooth shank uses the separately attributed1/4UNF catalog nominal diameter. The static union of hub/carrier/damper recognition geometry does not prescribe actual welded ownership or simulate torsional damping. The two unloaded facing annuli share drawing contact planes with flywheel and pressure plate; no compressed thickness, friction, clamp force, spring travel, heat/wear limit or working torque path is validated. The cover pressure plate/spring/strap breakdown explains internal roles; it is not a verified serviceable repair kit or dismantling sequence. Crank/clutch axial planes and gearbox/fork/slave/carrier/bearing placements stay. Flywheel support radius follows the separately attributed ring-bore comparison; plate/housing/starter envelopes are revised together under a conditional replacement-pitch scale. Remaining bearing/spring gap and input/hub registration remain reference discrepancies, not measured free play, running fit or permission to extend a carrier or shaft. No spline profile, release stroke, loaded diaphragm geometry, flywheel resurfacing, thread/engagement, tightening, support, extraction clearance or verified replacement/removal path. Inspection separation is illustrative.'
meta=dict(units='mm',source_frame='Existing engine-native mm, crank axis[0,-225],+Z front.',owner_dimensions_applied=False,factory_sources=['https://www.spridgets.de/downloads/Parts-1977.pdf#page=61','https://www.spridgets.de/downloads/Parts-1977.pdf#page=79','https://www.spridgets.de/downloads/Midget%201500%20WH%20Teil%201.pdf#page=14'],supplier_sources=['https://bpnorthwest.com/mg/clutch-disc-spitfire-75-to-80-mg-midget-75-to-79/','https://www.mgocspares.co.uk/parts/mg/midget/clutch-gearbox-axle/clutch/clutch-system?page=1'],factory_roles={'cover':'GCC196','driven_plate':'GCP230','release_bearing':'GRB207','cover_screws':'146176','screw_quantity':6,'washer_quantity':6,'nominal_disc_diameter_mm':OD},basis='Factory1500 plate supports a separate driven plate and complete diaphragm-cover assembly, with six cover fixings. Existing source flywheel/crank/bolt/dowel axes remain drawing datums. A smaller central recess restores a continuous annular friction land; unloaded facing/core/pressure planes now share an explicit static clamping stack. Thin domed cover, separate pressure plate, conical fingers/support and strap roles replace the flat solid cover cue. Depths and internal geometry are construction estimates; source family/catalog counts do not identify fitted components.',engine_source_sha256=hashlib.sha256(CORE.read_bytes()).hexdigest(),engine_source_step_sha256=hashlib.sha256(CORE_STEP.read_bytes()).hexdigest(),estimated_geometry={'retained_flywheel_rear_plane_native_z_mm':F,'previous_central_recess_radius_mm':88,'central_recess_radius_mm':RECESS_RADIUS,'disc_diameter_mm':OD,'flywheel_support_diameter_mm':RING_ID,'facing_inner_radius_mm':FACING_INNER,'facing_thickness_each_mm':FACING_THICKNESS,'core_thickness_mm':CORE_THICKNESS,'pressure_plate_front_back_native_z_mm':[PRESSURE_FRONT,PRESSURE_BACK],'diaphragm_inner_outer_native_z_mm':[DIAPHRAGM_INNER_Z,DIAPHRAGM_OUTER_Z],'unthreaded_screw_nominal_catalog_diameter_mm':6.35,'screw_thread_catalog_basis':'MGOC146176,1/4UNF; head/length/plain bores/washers remain construction estimates.','cover_fixing_axes_native_xy_mm':FIXING_AXES,'retained_cover_dowel_axes_native_xy_mm':DOWEL_AXES,'estimated_dowel_passage_native_z_bounds_mm':[F-12,F+4]},remaining_registration={'unchanged_engine_to_body_translation_mm':T,'unchanged_release_bearing_front_body_z_mm':bearing_front,'new_diaphragm_rear_body_z_mm':DIAPHRAGM_INNER_Z+T[2],'bearing_to_diaphragm_drawing_gap_mm':DIAPHRAGM_INNER_Z+T[2]-bearing_front,'source_pack_housing_contacts_mm3':contacts,'input_hub_reference':input_hub_reference,'limits':'Neither assembly is translated to close the remaining gap. Its size is a source-model discrepancy, not fitted release travel or free play.'},limits=limits)
a=cq.Assembly(name='estimated_1500_static_clutch_clamping_stack');meshes=[]
for name,shape,color in parts:
 a.add(shape,name=name,color=cq.Color(*color));vs,ts=absolute_tessellation(shape,.06,.12);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/'clutch-pack-reference.step'));meta['parts']=meshes;(OUT/'clutch-pack-reference.json').write_text(json.dumps(meta,separators=(',',':'))+'\n')
restored=cq.importers.importStep(str(OUT/'clutch-pack-reference.step')).val();assert restored.isValid() and all(s.isValid() and s.Volume()>0 for s in restored.Solids())
print(json.dumps({'groups':len(parts),'solids':len(restored.Solids()),'step_valid':True,'facing_contact_annuli_and_fixing_bores':True,'remaining_registration':meta['remaining_registration']}))
