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/prepare-optical-tunnel.py
"""Build an aligned, split optical tunnel; preserve the watch exterior bounds."""
from pathlib import Path
import base64, hashlib, json
import numpy as np
import trimesh
from trimesh.visual.material import PBRMaterial
root=Path(__file__).resolve().parent.parent
cx=-0.02509520000035
cy=-8.99159126281738
floor=-9.42
inside=-9.02
top=-8.56 # leaves 0.15 mm to nominal 0.91 mm supplied optical face
def box(extents,center):
return trimesh.creation.box(extents=extents,transform=trimesh.transformations.translation_matrix(center))
def rounded_rect(width,height,r,z0,z1,x,y):
from shapely.geometry import box as rect
outline=rect(-width/2+r,-height/2+r,width/2-r,height/2-r).buffer(r,quad_segs=12)
m=trimesh.creation.extrude_polygon(outline,z1-z0,engine='earcut')
m.apply_translation([x,y,z0]);return m
def finish(m,color,alpha=False):
m.visual=trimesh.visual.TextureVisuals(material=PBRMaterial(baseColorFactor=color,
roughnessFactor=.35,alphaMode='BLEND' if alpha else 'OPAQUE',doubleSided=True))
return m
def export(name,scene):
data=scene.export(file_type='glb');(root/f'mechanical/{name}.glb').write_bytes(data)
(root/f'mechanical/{name}.ts').write_text('export default "data:model/gltf-binary;base64,'+base64.b64encode(data).decode()+'"\n')
original=trimesh.load(root/'mechanical/revised-enclosure.glb',force='scene')
shell=max(original.geometry.values(),key=lambda m:len(m.vertices)).copy()
shell.merge_vertices(merge_tex=True,merge_norm=True)
assert shell.is_watertight
old_bounds=shell.bounds.copy()
# Main-panel placement moved the side switch relative to the old case aperture.
# Restore that old opening with the matching opposite wall, preserving both
# the rolled exterior and the cavity profile, then cut the aligned new slot.
mirror=shell.copy();mirror.apply_scale([-1,1,1])
old_button_patch=trimesh.boolean.intersection([mirror,box([3,6.2,2.8],[18.5,-2.9,1.2])],engine='manifold')
shell=trimesh.boolean.union([shell,old_button_patch],engine='manifold')
switch_scene=trimesh.load(root/'mechanical/upright-pcb.glb',force='scene')
switch_meshes=[]
for n in switch_scene.graph.nodes_geometry:
if n=='SW1' or n.startswith('SW1_'):
T,k=switch_scene.graph[n];m=switch_scene.geometry[k].copy();m.apply_transform(T);switch_meshes.append(m)
head=max(switch_meshes,key=lambda m:m.bounds[1,0]);head_x=float(head.bounds[1,0]);head_y=float(head.bounds.mean(0)[1]);head_z=float(head.bounds.mean(0)[2])
def side_pill(y,z,length,height,x0,x1):
m=rounded_rect(length,height,height/2-.00001,x0,x1,0,0)
m.apply_transform(np.array([[0,0,1,0],[1,0,0,y],[0,1,0,z],[0,0,0,1]]));return m
button_slot=side_pill(head_y,head_z,5.05,1.5,17.4,20)
shell=trimesh.boolean.difference([shell,button_slot],engine='manifold')
cap=side_pill(head_y,head_z,4.8,1.2,18.2,20)
cap=trimesh.boolean.intersection([cap,mirror.convex_hull],engine='manifold')
flange=side_pill(head_y,head_z,6.0,2.1,17.95,18.2)
stem=trimesh.creation.cylinder(radius=.425,height=18.2-(head_x+.01),sections=48)
stem.apply_transform(trimesh.geometry.align_vectors([0,0,1],[1,0,0]));stem.apply_translation([(18.2+head_x+.01)/2,head_y,head_z])
actuator=trimesh.boolean.union([cap,flange,stem],engine='manifold')
assert actuator.is_watertight and actuator.body_count==1
actuator_scene=trimesh.Scene();actuator_scene.add_geometry(finish(actuator,[38,42,48,255]),geom_name='FLUSH_CAP_AND_RETAINING_FLANGE_ALIGNED_TO_SW1')
export('button-actuator',actuator_scene);actuator.export(root/'mechanical/button-actuator.stl')
# Fill the obsolete off-centre window within the original flat rear floor.
plug=box([3.9,6.9,.4],[-14.25,.153,-9.22])
shell=trimesh.boolean.union([shell,plug],engine='manifold')
# Package already has a 0.4 mm optical divider at local X=-0.75 mm.
# Retain a matching divider all the way to the skin-facing windows.
channels=[('LED',cx-2.175,2.45),('PHOTODIODE',cx+1.425,3.65)]
frame=rounded_rect(7.9,4.3,.35,floor+.15,top,cx,cy)
joined=trimesh.boolean.union([shell,frame],engine='manifold')
cuts=[];windows=trimesh.Scene();spec=[]
for name,x,width in channels:
opening=rounded_rect(width,3.1,.2,floor-.1,top+.1,x,cy)
cuts.append(opening)
# Two individually bonded 0.2 mm transparent inserts, flush with the back.
# The black septum stays between the inserts; no continuous glass bridge.
lens=rounded_rect(width-.1,3.0,.15,floor,floor+.2,x,cy)
windows.add_geometry(finish(lens,[75,145,165,120],True),geom_name=name+'_0_2_MM_WINDOW')
spec.append({'name':name,'centerXYMM':[x,cy],'clearApertureMM':[width,3.1],
'insertMM':[width-.1,3,.2],'nominalBondGapPerSideMM':.05})
case=trimesh.boolean.difference([joined,*cuts],engine='manifold')
assert case.is_watertight and case.body_count==1
assert np.max(np.abs(case.bounds-old_bounds))<.000003
opaque=trimesh.Scene();opaque.add_geometry(finish(case,[25,29,35,255]),geom_name='ORIGINAL_SIZE_CASE_WITH_SPLIT_OPTICAL_TUNNEL')
for n,g in original.geometry.items():
if len(g.vertices)<1000:
fixture=g.copy();fixture.merge_vertices(merge_tex=True,merge_norm=True)
if n=='Revised wall-supported motor shelf':
# The shifted ferrite needs 0.15mm side clearance. The motor's
# factory adhesive remains supported over most of its circle.
fixture=trimesh.boolean.intersection([fixture,box([17.95,100,100],[-21.025,0,0])],engine='manifold')
fixture.visual=g.visual
opaque.add_geometry(fixture,geom_name=n)
export('optical-enclosure',opaque)
guide=opaque.copy()
for g in guide.geometry.values():
mat=g.visual.material.copy();color=mat.baseColorFactor.copy();color[-1]=35;mat.baseColorFactor=color;mat.alphaMode='BLEND';g.visual.material=mat
export('optical-enclosure-guide',guide)
export('optical-windows',windows)
seal=rounded_rect(7.0,3.5,.1,top,-8.41002,cx,cy)
seal_cuts=[rounded_rect(width,3.1,.2,top-.1,-8.3,x,cy) for name,x,width in channels]
seal=trimesh.boolean.difference([seal,*seal_cuts],engine='manifold')
seal_scene=trimesh.Scene();seal_scene.add_geometry(finish(seal,[8,9,10,255]),geom_name='COMPRESSED_BLACK_OPTICAL_PERIMETER_AND_DIVIDER_GASKET')
export('optical-light-seal',seal_scene)
# Fabrication model includes the previously validated integrated mounting posts.
posts=trimesh.load(root/'mechanical/pcb-supports.glb',force='scene')
pieces=[case]
for node in posts.graph.nodes_geometry:
T,n=posts.graph[node];m=posts.geometry[n].copy();m.apply_transform(T);m.merge_vertices(merge_tex=True,merge_norm=True);pieces.append(m)
fixtures=[g.copy() for g in opaque.geometry.values() if len(g.vertices)<1000]
for g in fixtures:g.merge_vertices(merge_tex=True,merge_norm=True)
mounted=trimesh.boolean.union(pieces+fixtures,engine='manifold')
assert mounted.is_watertight and mounted.body_count==1
mounted.export(root/'mechanical/mounted-enclosure.stl')
for name,x,width in channels:
lens=rounded_rect(width-.1,3,.15,floor,floor+.2,x,cy)
lens.export(root/f'mechanical/{name.lower()}-window.stl')
report={'status':'PASS','version':'0.2.6','units':'mm','sensorPackageCenterXYMM':[cx,cy],
'originalEnclosureBoundsMM':old_bounds.tolist(),'newEnclosureBoundsMM':case.bounds.tolist(),
'originalExteriorDimensionsPreserved':True,'obsoleteWindowClosed':True,'motorShelfMaximumXMM':-12.05,'shelfToFerriteSideGapMM':.15,'opticalTunnelTopZMM':top,
'sideButtonAlignedToNativeSW1':{'headCenterMM':[head_x,head_y,head_z],'restGapMM':.01,'plannedActuationTravelMM':.20,'capSizeYZMM':[4.8,1.2],'slotSizeYZMM':[5.05,1.5],'retainingFlangeSizeYZMM':[6,2.1]},
'nominalOpticalFaceZMM':-8.41,'nominalSensorToTunnelGapMM':.15,
'maximumSensorHeightExtraMM':.09,'minimumHeightToleranceGapMM':.06,
'tunnelToWindowDepthMM':top-(floor+.2),'channels':spec,'blackDividerWidthMM':.55,
'opticalLightSealInstalledThicknessMM':-8.41002-top,'opticalLightSealFreeThicknessTargetMM':.30,
'opticalLightSealCompressionRangePercent':[13.3,80],
'enclosureWatertight':True,'enclosureSolidCount':mounted.body_count,
'enclosureSha256':hashlib.sha256((root/'mechanical/mounted-enclosure.stl').read_bytes()).hexdigest(),
'qualification':'Dimensioned prototype. Select a black compliant optical seal qualified for the listed compression range; CAD shows its installed nominal shape. Two inserts require optical-grade material transparent at all sensor wavelengths and opaque adhesive in the 0.05 mm perimeter bond gaps. No waterproof rating or optical performance claim; assembly tolerances and actual skin signal need testing.'}
(root/'reports/optical-tunnel.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report,indent=2))