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/audit-cabled-assembly.py
"""Check actual world-space component meshes and the unchanged case envelope."""
from pathlib import Path
import json,hashlib,itertools,base64,io
import numpy as np,trimesh
from mount_mesh_utils import numerical_face_contact
root=Path(__file__).resolve().parent.parent
hardware=json.loads((root/'mechanical/mount-hardware.json').read_text())
parts=[]
def load(f,shift=(0,0,0)):
file=root/f'mechanical/{f}.glb'
if file.exists():s=trimesh.load(file,force='scene')
else:
data=base64.b64decode((root/f'mechanical/{f}.ts').read_text().split('base64,')[1].split('"')[0])
s=trimesh.load(io.BytesIO(data),file_type='glb',force='scene')
for n in s.graph.nodes_geometry:
T,k=s.graph[n];m=s.geometry[k].copy();m.apply_transform(T);m.apply_translation(shift)
parts.append((f+'/'+n,m))
for f in ['upright-pcb','case-body','case-bezel','bezel-bondline','motor-support-shim','optical-windows','optical-light-seal','pcb-cradle','pcb-retainers','pcb-mount-screws','assembly-display','relocated-motor','temperature-probes','motor-inline-connector','button-actuator']:load(f)
load('battery-envelope',(3,-.19159126281738,-3.6));load('ferrite-envelope',(0,-8.99159126281738,-6.78))
# The printed case body already contains all four integral posts.
failures=[];contacts=[];pairs=0
for (a,x),(b,y) in itertools.combinations(parts,2):
# Internal shapes of a supplied model are one manufactured part.
if a.split('/')[0]==b.split('/')[0]:continue
# Integrated supports intentionally enter and are united with the shell.
if {a.split('/')[0],b.split('/')[0]}=={'pcb-supports','optical-enclosure'}:continue
# PCB internal collisions need a separate folded-panel check; skip only
# native model internals here, retaining every PCB-to-peripheral check.
gap=np.maximum(x.bounds[0]-y.bounds[1],y.bounds[0]-x.bounds[1])
if np.any(gap>=-.000005):continue
cm=trimesh.collision.CollisionManager();cm.add_object(a,x);pairs+=1
_,_,data=cm.in_collision_single(y,return_names=True,return_data=True)
if data and ({a.split('/')[0],b.split('/')[0]}=={'pcb-retainers','pcb-mount-screws'} or ('case-body/' in a+b and 'pcb-cradle/' in a+b) or ('motor-support-shim/' in a+b and 'upright-pcb/' in a+b)) and numerical_face_contact(x,y):continue
for c in data:
if c.depth<=.000005:continue
intentional=('pcb-mount-screws/' in a+b and 'case-body/' in a+b and any(np.linalg.norm(np.array(c.point)[:2]-[m['x'],m['y']])<=.800005 for m in hardware['mounts']) and -5.150005<=c.point[2]<=-1.349995)
record={'a':a,'b':b,'depthMM':float(c.depth),'pointMM':c.point.tolist(),'intentionalThreadEngagement':intentional}
contacts.append(record)
if not intentional:failures.append(record)
allv=np.vstack([m.vertices for _,m in parts]);bounds=[allv.min(0).tolist(),allv.max(0).tolist()];dims=(allv.max(0)-allv.min(0)).tolist()
assert dims[0]<=39.00001 and dims[1]<=50.50001 and dims[2]<=14.50001,dims
report={'status':'PASS' if not failures else 'FAIL','meshCount':len(parts),'overlappingAabbPairsCheckedWithFCL':pairs,'boundsMM':bounds,'modeledOutsideDimensionsMM':dims,'maximumThicknessMM':14.5,'failures':failures,'method':'Exact FCL triangles against all loose assembly bodies and populated PCB; disjoint AABBs prove exclusion. Only local purchased screw thread engagement into pilot bores is intentional. Imported mating-face alarms require independent Boolean overlap below 1e-6mm3 and every overlap vertex within 5um of both surfaces. Cable clearances and optical paths have independent reports.','qualification':'Nominal modeled envelope only. Actual display populated-flex/factory-fold dimensions are absent from its supplier drawing; do not release physical tooling before verifying that flex and tolerance stack.'}
(root/'reports/cabled-assembly.json').write_text(json.dumps(report,indent=2)+'\n');print(json.dumps({k:v for k,v in report.items() if k!='failures'},indent=2));print('First failures:',json.dumps(failures[:15],indent=2));assert not failures