project
Adom Custom Electronic Load
Public Made by Adomby adom
Two-layer digitally controlled electronic-load prototype. Compact R11 is under active electrical, cooling and probe-access review; not a manufacturing release.
← Commit history
Track overnight board completion, native DFT repair and viewer performance evidence
3 files changed
+61
docs/completion-plan.md+7@@ -208,3 +208,10 @@ Latest continuation: AIFlow finish and export checks pass; delivery/video remain ### Side-fan mechanical candidate — 13 October 2026 User requested side fan, stronger carrier and Q1 heatsink. Published mechanical/side-fan-r1: fan 16 mm outside PCB, reinforced connected carrier with through-bolted clamps and 6 mm gussets, nominal ATS 50 x 50 x 25 mm sink with insulated Q1 contact. Geometry screening has no unexpected component-body intersections; carrier is one valid solid. New cooling path reopens thermal acceptance; physical strength, retention, airflow and full probe sequence validation remain open. Board32 and native PCB unchanged. Separate probing-preview-sidefan study reports 14 existing measurement entries and five entries relying on three proposed pads (not fabricated/routed). Three-pad routing/native DRC and complete plan/collision checks remain required. README, hero and three-page datasheet updated. Clean 30.27 s native CAD review clip retained; first overlapping-model take rejected.+++### Overnight continuation — 13 October 2026++User explicitly requested continuing until the board is finished. AIFlow returned to routing. The side-fan redesign proposal failed native copper/courtyard checks; it is rejected pending correction. Earlier routed DFT candidate plus a GND stitch at (142.85,71.76), 0.8/0.4 mm, passes native electrical DRC with zero opens; silkscreen cleanup and library-aware checks continue on sidefan-dft-r6. Adoption, schematic parity, updated 3D, current/thermal review and probing checks are not yet passed. The previous passive cooler acceptance cannot be used for the new sink.++Hero finish/layer metadata corrected; rendering batched from 23568 to 370 primitives with exact indexed triangle/normal and node/material checks. The optimized hero is published and visually checked. Shared viewer hidden/offscreen pause fix is proposed in https://wiki.adom.inc/adom/adom-3d-viewer/prs/1 (six visibility checks plus 111 existing browser checks pass; source candidate only, not deployed). Performance issue https://wiki.adom.inc/adom/adom-3d-viewer/issues/10 tracks the user freeze report; no measured freeze causality or GPU improvement claimed.
tools/check_glb_batching.pyadded+24@@ -0,0 +1,24 @@+import sys,json,hashlib+from pathlib import Path+import numpy as np+sys.path.insert(0,'tools');from glb_shape_helpers import read_glb++def sig(path):+ g,b=read_glb(Path(path))+ def read(i):+ a=g['accessors'][i];v=g['bufferViews'][a['bufferView']];dt={5126:'<f4',5125:'<u4',5123:'<u2',5122:'<i2',5121:'u1',5120:'i1'}[a['componentType']];n={'SCALAR':1,'VEC3':3}[a['type']];size=np.dtype(dt).itemsize;x=np.ndarray((a['count'],n),dtype=dt,buffer=b,offset=v.get('byteOffset',0)+a.get('byteOffset',0),strides=(v.get('byteStride',size*n),size)).copy()+ if a.get('normalized'):x=np.maximum(x.astype(np.float32)/np.iinfo(x.dtype).max,-1)+ return x+ rows=[]+ for m in g['meshes']:+ groups={}+ for p in m['primitives']:+ key=(p.get('material'),tuple(sorted(p['attributes'])));h=groups.setdefault(key,hashlib.sha256());ix=read(p['indices']).reshape(-1)+ # canonical interleaved per-index attributes, independent of primitive boundaries+ a=np.concatenate([read(p['attributes'][k])[ix].astype('<f4') for k in sorted(p['attributes'])],axis=1);h.update(a.tobytes())+ rows.append({str(k):h.hexdigest() for k,h in groups.items()})+ animations=[{'channels':a['channels'],'data':[(read(s['input']).tolist(),read(s['output']).tolist()) for s in a['samplers']]} for a in []]+ return g,rows+x,a=sig('/tmp/load-unbatched.glb');y,b=sig('mechanical/side-fan-r1/side-fan-assembly.glb');assert a==b;assert x['nodes']==y['nodes'];assert x['materials']==y['materials'];assert [a['channels'] for a in x['animations']]==[a['channels'] for a in y['animations']]+report={'geometryAndNormalsExact':True,'nodesLayersTransformsExact':True,'materialsExact':True,'animationTargetsExact':True,'beforePrimitives':23568,'afterPrimitives':370,'scope':'Render batching only; no hardware qualification'}+Path('mechanical/side-fan-r1/batching-check.json').write_text(json.dumps(report,indent=2)+'\n');print(report)
tools/repair_sidefan_silkscreen.pyadded+30@@ -0,0 +1,30 @@+from pathlib import Path+import sys,copy,uuid,math,shutil+from shapely.geometry import Point,LineString+from shapely.ops import unary_union+import sexp+R=Path(__file__).resolve().parents[1];D=R/'probing-flow/design-candidate';b=sexp.parse((D/'sidefan-dft-r4.kicad_pcb').read_text());S=sexp.Sym+# Remove one native-reported dead-end 3V3 stub; renewed connectivity check required.+b[:]=[v for v in b if not(isinstance(v,list)and sexp.first(v,'uuid')==['uuid','f7904a8b-fcc5-4ae6-bf0b-ff7b8ba843ee'])]+# Clip obsolete medium-pin outline graphics outside the enlarged corner contact markings.+keep=[];mask=unary_union([Point(100,100).buffer(3.5),Point(164,100).buffer(3.5)])+for v in b:+ if isinstance(v,list)and v[0]=='gr_line'and sexp.first(v,'layer')[1]in ['F.SilkS','B.SilkS']:+ a=list(map(float,sexp.first(v,'start')[1:3]));c=list(map(float,sexp.first(v,'end')[1:3]));line=LineString([a,c]);clipped=line.difference(mask)+ if clipped.equals(line):keep.append(v);continue+ for seg in ([clipped]if clipped.geom_type=='LineString'else getattr(clipped,'geoms',[])):+ if seg.length<.25:continue+ n=copy.deepcopy(v);sexp.first(n,'start')[1:3]=list(seg.coords[0]);sexp.first(n,'end')[1:3]=list(seg.coords[-1]);sexp.first(n,'uuid')[1]=str(uuid.uuid4());keep.append(n)+ else:keep.append(v)+b=keep+# Explicit reference relocation; all labels preserved, no hidden silkscreen exemptions.+xy={'TP3':(98.2,84.7),'TP7':(98.2,90.3),'TP9':(105.8,85.2),'TP14':(122.4,102.4),'TP16':(146.4,101.9),'J8':(164.2,48.0)}+for f in sexp.find(b,'footprint'):+ ref=next(p[2]for p in sexp.find(f,'property')if p[1]=='Reference')+ if ref not in xy:continue+ a=sexp.first(f,'at');x,y=map(float,a[1:3]);ang=math.radians(float(a[3])if len(a)>3 else 0);dx,dy=xy[ref][0]-x,xy[ref][1]-y+ prop=next(p for p in sexp.find(f,'property')if p[1]=='Reference');sexp.first(prop,'at')[1:3]=[dx*math.cos(ang)-dy*math.sin(ang),dx*math.sin(ang)+dy*math.cos(ang)]+for v in sexp.find(b,'gr_text'):+ if v[1]=='GND' and float(sexp.first(v,'at')[1])<103 and float(sexp.first(v,'at')[2])>96:sexp.first(v,'at')[1:3]=[100,95.2]+(D/'sidefan-dft-r5.kicad_pcb').write_text(sexp.dump(b)+'\n')+for ext in ['kicad_pro','kicad_dru']:shutil.copy2(D/f'sidefan-dft-r4.{ext}',D/f'sidefan-dft-r5.{ext}')