"""Bounded wishbone pressing comparison; dimensions and fitted identity unknown."""
from pathlib import Path
import copy, hashlib, json
import cadquery as cq

ROOT = Path(__file__).resolve().parent
SCALE = 2032 / 2.4
SOURCE = 'front-fulcrum-profile-reference'
OUTPUT = 'wishbone-shell-reference'
data = json.loads((ROOT / (SOURCE + '.json')).read_text())
assembly = cq.Assembly.importStep(str(ROOT / (SOURCE + '.step')))
native = {n: o.obj for n, o in assembly.objects.items() if o.obj is not None}
changes = {}
sections = {}
for side, sign in [('left', 1), ('right', -1)]:
    name = 'front_' + side + '_pressed_wishbone_and_eye_reference'
    old = native[name]
    # The old compound consists of overlapping pan, eyes, fork and wall.
    shape = old.Solids()[0]
    for solid in old.Solids()[1:]:
        shape = shape.fuse(solid)
    plane = cq.Plane(origin=(0, .28 * SCALE, 0), xDir=(1, 0, 0), normal=(0, -1, 0))
    gauge = .003 * SCALE
    center = cq.Vector(.45 * SCALE, .28 * SCALE, 1.20 * SCALE)
    # A smaller selected opening leaves a continuous land behind the inner
    # recess. It remains larger than the separate 104.648mm coil comparison.
    annulus = cq.Solid.makeCylinder(.095 * SCALE + .5, gauge, center, (0, -1, 0))
    annulus = annulus.cut(cq.Solid.makeCylinder(60, gauge + 2, center + cq.Vector(0, 1, 0), (0, -1, 0)))
    boundary = [(278, 870), (285, 910), (292, 960), (298, 1016), (292, 1072), (285, 1122), (278, 1162)]
    curve = cq.Edge.makeSpline([cq.Vector(x, 0, z) for x, z in boundary])
    samples = [curve.positionAt(i / 64) for i in range(65)]
    # Planar facets avoid unstable STEP exchange of the offset spline surface.
    points = [cq.Vector(-100, .28 * SCALE, 870)] + [cq.Vector(p.x, .28 * SCALE, p.z) for p in samples] + [cq.Vector(-100, .28 * SCALE, 1162)]
    wire = cq.Wire.makePolygon(points + [points[0]])
    cut = cq.Solid.extrudeLinear(wire, [], cq.Vector(0, -45, 0))
    # An offset ribbon forms the downward inner return on the same curve.
    outer_wire = wire.offset2D(gauge)[0]
    ribbon = cq.Solid.extrudeLinear(outer_wire, [], cq.Vector(0, -.025 * SCALE, 0)).cut(cut)
    outline = [(.34, .98), (.40, .98), (.61, 1.14), (.61, 1.26), (.40, 1.42), (.34, 1.42)]
    envelope = cq.Workplane(plane).polyline([(x * SCALE, z * SCALE) for x, z in outline]).close().extrude(.025 * SCALE).val()
    ribbon = ribbon.intersect(envelope)
    if sign < 0:
        annulus, cut, ribbon = [p.mirror('YZ') for p in [annulus, cut, ribbon]]
    shape = shape.fuse(annulus).fuse(ribbon).cut(cut).clean()
    # Preserve all eight pre-existing seat fixing axes and nominal passages.
    for point in data['spring_seats'][side]['estimated_mount_centers_mm']:
        tool = cq.Solid.makeCylinder(4.3, 60, cq.Vector(*point) + cq.Vector(0, 20, 0), (0, -1, 0))
        shape = shape.cut(tool)
    shape = shape.clean()
    assert shape.isValid() and len(shape.Solids()) == 1, name
    changes[name] = shape
    sections[side] = dict(selected_aperture_radius_mm=60, selected_sheet_gauge_mm=gauge,
                          selected_inner_return_depth_mm=.025 * SCALE,
                          selected_recess_boundary_xz_mm=[[sign*x,z] for x,z in boundary],
                          previous_compound_solids=len(old.Solids()), connected_solids=1)

limits = ('AHA7029 replacement photographs support the open inner recess, spring aperture, formed returns and connected arm architecture only. '
          'The 60mm aperture radius, 64-segment recess and 2.54mm projected gauge are selected estimates, not measured part dimensions. '
          'The selected recess depth keeps its downward return outside the retained 80mm seat disk in this construction frame; this is not fitted clearance. The old aperture radius80.4333mm is superseded; its 12.7mm inner land could not accommodate the photo-supported recess independently. '
          'Spring/seat, all fulcrum and anti-roll axes, eye/fork sections and neighboring assemblies remain inherited unregistered references. '
          'Mirroring is a construction assumption. No formed-sheet development, actual weld map, material strength, fatigue, loaded spring restraint, articulation or verified replacement path.')
revision = dict(evidence='wishbone-shell-evidence.json', owner_dimensions_applied=False, sections=sections, limits=limits,
                source_sha256={ext:hashlib.sha256((ROOT/(SOURCE+ext)).read_bytes()).hexdigest() for ext in ['.step','.json']})
result = copy.deepcopy(data)
out = cq.Assembly(name='wishbone_shell_comparison')
for part in result['parts']:
    name = part['name']
    shape = changes.get(name, native[name])
    out.add(shape, name=name, color=cq.Color(*part['color']))
    if name in changes:
        vertices, triangles = shape.tessellate(.4, .12)
        part['vertices'] = [round(v,4) for p in vertices for v in p.toTuple()]
        part['triangles'] = [v for t in triangles for v in t]
result['wishbone_shell_revision'] = revision
result['limits'] += ' ' + limits
out.export(str(ROOT/(OUTPUT+'.step')))
p = ROOT/(OUTPUT+'.step')
p.write_text('\n'.join(line.rstrip() for line in p.read_text().splitlines())+'\n')
(ROOT/(OUTPUT+'.json')).write_text(json.dumps(result,separators=(',',':'))+'\n')
print(json.dumps(dict(groups=len(result['parts']), changed=list(changes), sections=sections)))
