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/export-pcb-mount-parts.py

from pathlib import Path
import trimesh, json, hashlib, base64
from trimesh.visual.material import PBRMaterial
from mount_mesh_utils import printable_mesh
root=Path(__file__).resolve().parent.parent
def parts(filename):
    s=trimesh.load(root/f'mechanical/{filename}.glb',force='scene')
    for node in s.graph.nodes_geometry:
        T,name=s.graph[node];mesh=s.geometry[name].copy();mesh.apply_transform(T)
        # Imported visual meshes split vertices at material/normal seams.
        # Weld coincident vertices for watertight fabrication solids only.
        mesh.merge_vertices(merge_tex=True,merge_norm=True)
        assert mesh.is_watertight and mesh.volume>0, name
        yield name,mesh
case_parts=list(parts('optical-enclosure'))
# The fixed motor shelf traps J3/J8 during vertical PCB installation. Its
# insulating support becomes a removable shim backed by the PCB stiffener.
shell=[mesh for name,mesh in case_parts if name=='ORIGINAL_SIZE_CASE_WITH_SPLIT_OPTICAL_TUNNEL']
shim=next(mesh for name,mesh in case_parts if name=='Revised wall-supported motor shelf')
clip=trimesh.creation.box(extents=[5.6,9,1],transform=trimesh.transformations.translation_matrix([-14.85,-8,-6.75]))
shim=trimesh.boolean.intersection([shim,clip],engine='manifold')
assert shim.is_volume
shim.visual=trimesh.visual.TextureVisuals(material=PBRMaterial(baseColorFactor=[175,150,90,255],roughnessFactor=.7))
s=trimesh.Scene();s.add_geometry(shim,geom_name='REMOVABLE_0_2MM_MOTOR_SUPPORT_SHIM',node_name='REMOVABLE_0_2MM_MOTOR_SUPPORT_SHIM')
data=s.export(file_type='glb');(root/'mechanical/motor-support-shim.glb').write_bytes(data)
(root/'mechanical/motor-support-shim.ts').write_text('export default "data:model/gltf-binary;base64,'+base64.b64encode(data).decode()+'"\n')
supports=[mesh for _,mesh in parts('pcb-supports')]
mounted=trimesh.boolean.union(shell+supports,engine='manifold',check_volume=True)
assert mounted.is_watertight
assert mounted.body_count==1, mounted.body_count
original_shell=max(shell,key=lambda mesh:mesh.volume)
assert abs(mounted.bounds-original_shell.bounds).max()<0.000003
mounted=printable_mesh(mounted)
mounted.export(root/'mechanical/mounted-enclosure.stl')
# Main assembly uses the same finished printable case/post solid as the STL.
mounted.visual=trimesh.visual.TextureVisuals(material=PBRMaterial(baseColorFactor=[90,105,125,255],roughnessFactor=.65))
scene=trimesh.Scene();scene.add_geometry(mounted,geom_name='PRINTED_CASE_WITH_INTEGRAL_POSTS',node_name='PRINTED_CASE_WITH_INTEGRAL_POSTS')
binary=scene.export(file_type='glb');(root/'mechanical/mounted-enclosure.glb').write_bytes(binary)
(root/'mechanical/mounted-enclosure.ts').write_text('export default "data:model/gltf-binary;base64,'+base64.b64encode(binary).decode()+'"\n')
# Original rolled rim obstructs the driver. Split the real case into a body
# and removable top bezel, with a reserved 0.1mm adhesive seam. Exterior bounds
# and floor/windows are preserved. Bond compatibility/strength need a prototype.
def region(z0,z1):
    return trimesh.creation.box(extents=[60,70,z1-z0],transform=trimesh.transformations.translation_matrix([0,0,(z0+z1)/2]))
split_parts=[]
for name,z0,z1,color in [('case-body',-12,1.95,[90,105,125,255]),('case-bezel',2.05,8,[90,105,125,255]),('bezel-bondline',1.95,2.05,[25,28,32,255])]:
    part=trimesh.boolean.intersection([mounted,region(z0,z1)],engine='manifold');part=printable_mesh(part)
    assert part.is_volume and part.body_count==1,name
    if name!='bezel-bondline':part.export(root/f'mechanical/{name}.stl');split_parts.append('mechanical/'+name+'.stl')
    part.visual=trimesh.visual.TextureVisuals(material=PBRMaterial(baseColorFactor=color,roughnessFactor=.65))
    s=trimesh.Scene();s.add_geometry(part,geom_name=name,node_name=name)
    data=s.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')
cradle=trimesh.boolean.union([m for _,m in parts('pcb-cradle')],engine='manifold',check_volume=True)
assert cradle.is_watertight and cradle.body_count==1
cradle=printable_mesh(cradle)
cradle.export(root/'mechanical/pcb-mounting-cradle.stl')
outputs=[]
retainers=list(parts('pcb-retainers'))
for end in ['FRONT']:
    for side in ['LEFT','RIGHT']:
        label=end+'_'+side
        combined=trimesh.boolean.union([m for n,m in retainers if n.startswith(label)],engine='manifold',check_volume=True)
        assert combined.is_watertight and combined.body_count==1,label
        filename=f'mechanical/retainer-{label.lower().replace("_","-")}.stl'
        combined=printable_mesh(combined)
        combined.export(root/filename);outputs.append(filename)
report={'status':'PASS','mountedCaseWatertight':mounted.is_watertight,'mountedCaseSolidCount':mounted.body_count,'caseBodySTL':'mechanical/case-body.stl','caseBezelSTL':'mechanical/case-bezel.stl','bezelJointMM':{'bodyTop':1.95,'bezelBottom':2.05,'reservedBondline':.1},'combinedCaseIsAssemblyReference':True,
    'originalExteriorBoundsPreserved':True,'mountedCaseBoundsMM':mounted.bounds.tolist(),
    'cradleSTL':'mechanical/pcb-mounting-cradle.stl','cradleWatertight':True,'cradleSolidCount':1,'enclosureSTL':'mechanical/mounted-enclosure.stl','retainerSTLs':outputs,
    'scope':'Boolean union of the optically revised shell with the aligned side-button slot and the integrated support features, plus two separately printable front retainers. Rear retainers are integrated into the cradle. The screws are purchased M1.6 countersunk fasteners, not printed parts.'}
extras=[]
for f in ['mechanical/button-actuator.stl','mechanical/led-window.stl','mechanical/photodiode-window.stl']:
    mesh=trimesh.load(root/f);assert mesh.is_watertight and mesh.body_count==1 and mesh.volume>0
    extras.append(f)
report['extraActuatorAndOpticalInsertSTLs']=extras
for path in [report['enclosureSTL'],report['cradleSTL'],*outputs,*split_parts]:
    emitted=trimesh.load(root/path);assert emitted.is_volume and emitted.body_count==1,path
report['emittedSTLsWatertight']=True
report['sha256']={f:hashlib.sha256((root/f).read_bytes()).hexdigest() for f in [report['enclosureSTL'],report['cradleSTL'],*outputs,*extras,*split_parts]}
optical_path=root/'reports/optical-tunnel.json'
if optical_path.exists():
    optical=json.loads(optical_path.read_text());optical['enclosureSha256']=report['sha256'][report['enclosureSTL']]
    optical_path.write_text(json.dumps(optical,indent=2)+'\n')
(root/'reports/pcb-mount-print-files.json').write_text(json.dumps(report,indent=2)+'\n')
print(json.dumps(report,indent=2))