abse/smartwatch-flex-top-only

Three-panel flex smartwatch with two parallel R3.9 bends, all components flat-top, low-profile JST connector harnesses, split heartbeat optical windows and screw-retained PCB mounting. Modeled 39 × 50.5 × 14.5mm prototype; 100% routing, zero native DRC and shorts. Hardware qualification pending.

Version
0.2.20
License
unset
Stars
0

scripts/verify-oled-clock-invariants.py

"""Compare the candidate to released v0.2.17, allowing one reviewed trace edit."""
from pathlib import Path
from collections import Counter
import hashlib, json

root = Path(__file__).resolve().parent.parent
baseline = root/'baseline-v0217'
trace_id = 'source_net_57_grid11170_material_0'
read = lambda p: json.loads(p.read_text())
sha = lambda p: hashlib.sha256(p.read_bytes()).hexdigest()
before_cache = read(baseline/'cache.json')
after_cache = read(root/'routing/final/cache.json')
assert before_cache.keys() == after_cache.keys()
for key in before_cache:
    if key != 'traces':
        assert before_cache[key] == after_cache[key], key
changed = []
for before, after in zip(before_cache['traces'], after_cache['traces'], strict=True):
    if before == after:
        continue
    changed.append(after['pcb_trace_id'])
    assert {k:v for k,v in before.items() if k != 'route'} == {k:v for k,v in after.items() if k != 'route'}
    assert before['route'][0] == after['route'][0]
    assert before['route'][-1] == after['route'][-1]
    assert [p for p in before['route'] if p['route_type'] == 'via'] == [p for p in after['route'] if p['route_type'] == 'via']
    assert all(p['route_type'] == 'wire' and p['layer'] == 'bottom' and p['width'] == .127 for p in after['route'])
assert changed == [trace_id], changed

before = read(baseline/'index.circuit.json')
after = read(root/'dist/index/circuit.json')
types = {e['type'] for e in before+after}
for kind in types - {'pcb_trace', 'pcb_copper_pour', 'pcb_trace_style_warning'}:
    assert [e for e in before if e['type'] == kind] == [e for e in after if e['type'] == kind], kind
assert [e for e in before if e['type'] == 'pcb_trace' and e['pcb_trace_id'] != trace_id] == [e for e in after if e['type'] == 'pcb_trace' and e['pcb_trace_id'] != trace_id]
old_pours = {e['pcb_copper_pour_id']:e for e in before if e['type'] == 'pcb_copper_pour'}
new_pours = {e['pcb_copper_pour_id']:e for e in after if e['type'] == 'pcb_copper_pour'}
assert old_pours.keys() == new_pours.keys()
changed_pours = []
for ident, old in old_pours.items():
    new = new_pours[ident]
    assert {k:v for k,v in old.items() if k != 'brep_shape'} == {k:v for k,v in new.items() if k != 'brep_shape'}
    if old != new:
        assert old['layer'] == 'bottom'
        changed_pours.append(ident)
assert not any('error' in e['type'] or 'warning' in e['type'] for e in after)
vias = [e for e in after if e['type'] == 'pcb_via']
assert len(vias) == 623
assert all(abs(e['hole_diameter']-.3) < 1e-9 and abs(e['outer_diameter']-.45) < 1e-9 for e in vias)
assert before_cache['input']['minViaHoleDiameter'] == after_cache['input']['minViaHoleDiameter'] == .3
assert before_cache['input']['minViaPadDiameter'] == after_cache['input']['minViaPadDiameter'] == .45
files = read(baseline/'source-mechanical-sha256.json')
derived = {'mechanical/upright-pcb.glb','mechanical/upright-pcb.ts','mechanical/upright-watch.glb'}
for p, expected in files.items():
    if p == 'index.tsx':
        original=(baseline/'index.tsx').read_text()
        assert sha(baseline/'index.tsx') == expected
        assert (root/p).read_text() == original.replace("'./routing/final/cache.json'", "'./routing/final/cache-data'")
        continue
    if p in derived or p.startswith('mechanical/site-previews/'):
        continue  # Fresh copper textures and thumbnails are bound to current circuits.
    assert sha(root/p) == expected, p
report = dict(status='PASS', baseline='Published v0.2.17', changedTraceIds=changed,
              changedBottomPours=changed_pours, nativeWarningsBefore=dict(Counter(e['type'] for e in before if 'warning' in e['type'])),
              nativeWarningsAfter={}, nativeErrors=0, nativeVias=623,
              holeDiameterMM=.3, padDiameterMM=.45,
              allRoutingInputsAndRulesIdentical=True, allViasAndEndpointsIdentical=True,
              padsComponentsBendsStiffenersAndAssemblyIdentical=True,
              unchangedSourceAndMechanicalFiles=sum(p not in derived and not p.startswith('mechanical/site-previews/') for p in files),
              refreshedDerivedModels=sorted(derived),
              sourceSha256=sha(root/'dist/index/circuit.json'),
              cacheSha256=sha(root/'routing/final/cache.json'))
(root/'reports/oled-clock-style/invariants.json').write_text(json.dumps(report, indent=2)+'\n')
print(json.dumps(report, indent=2))