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))