abse/museair-usb

USB-C–powered ESP32-C3 controller board with AP63203 buck conversion, AP2112K 3.3V regulation, I²C SEN62 sensor interface, status LED, buttons, UART/I²C headers, and extensive decoupling.

Version
0.5.7
License
unset
Stars
0

scripts/prepare-horizontal-assembly.py

from pathlib import Path
import os,trimesh,json,base64,numpy as np
r=Path(__file__).resolve().parent.parent;h=Path(os.environ.get('MUSEAIR_HORIZONTAL_OUT',str(r/'mechanical/horizontal')))
for target,names in [('enclosure-base',['case-base','feet','gaskets','mounting-inserts']),('enclosure-lid',['case-lid','enclosure-hardware'])]:
 s=trimesh.Scene()
 for name in names:
  sc=trimesh.load(h/'models'/f'{name}.glb',force='scene')
  for n in sc.graph.nodes_geometry:
   tf,g=sc.graph[n];m=sc.geometry[g].copy();m.apply_transform(tf);s.add_geometry(m,node_name=f'{name}:{n}')
 s.export(h/'models'/f'{target}.glb')
keys=['pcb','sensor','carrier','ties','pcb-hardware','holder-hardware','sensor-cable','usb-cable','enclosure-base','enclosure-lid','display','display-glass','display-flex','speaker','speaker-cone','speaker-gasket','speaker-factory-lead','microphone-gasket','ask-button','bivar-vlp-350-f']
for name in keys:
 if name=='bivar-vlp-350-f':
  assert (r/'assembly-assets/bivar-vlp-350-f.ts').exists(), 'Run add-flat-light-pipe.py first'
  continue
 if name=='pcb':
  assert (r/'assembly-assets/pcb.gltf').exists(), 'Run package-native-assembly-pcb.py first'
  continue
 s=trimesh.load(h/'models'/f'{"pcb" if name=="pcb-board" else name}.glb',force='scene')
 for m in s.geometry.values():
  triangles=m.triangles.copy();m.merge_vertices(merge_tex=False,merge_norm=False);assert np.allclose(triangles,m.triangles,atol=1e-7,rtol=0)
 raw=s.export(file_type='glb');(r/'assembly-assets'/f'{name}.glb').write_bytes(raw);(r/'assembly-assets'/f'{name}.ts').write_text('export default '+json.dumps('data:model/gltf-binary;base64,'+base64.b64encode(raw).decode())+'\n')
labels=['PCB_COMPLETE','SEN62_UNDER_PCB','SENSOR_HOLDER','SENSOR_RETENTION','PCB_INTERNAL_SCREWS','HOLDER_INTERNAL_SCREWS','GH6_SENSOR_HARNESS','USB_POWER_CABLE','ENCLOSURE_BASE','REMOVABLE_TOP_LID','BUYDISPLAY_PANEL','DISPLAY_GLASS','DISPLAY_FLEX','ENCLOSURE_SPEAKER','SPEAKER_CONE','SPEAKER_RIM_GASKET','PICOBLADE_FACTORY_LEAD','LID_MICROPHONE_GASKET','ASK_BUTTON_CAP','BIVAR_VLP_350_F_LIGHT_PIPE']
s="import { assembly } from 'tscircuit'\nimport React from 'react'\n"+'\n'.join(f'import model{i} from "./assembly-assets/{k}"' for i,k in enumerate(keys))+'\n\n// Base, lid and ASK cap have independent CAD IDs for viewer Hide/Show.\n// Mounting geometry and cable audit: mechanical/HORIZONTAL-ASSEMBLY.md.\nexport default ({showBase=true,showLid=true,showButton=true,showTies=false}:{showBase?:boolean,showLid?:boolean,showButton?:boolean,showTies?:boolean}={}) => <assembly.device name="MuseAir top-service assembly"><group name="Assembly" subcircuit>\n'
for i,(k,label) in enumerate(zip(keys,labels)):
 mpn=' manufacturerPartNumber="VLP-350-F"' if k=='bivar-vlp-350-f' else ' manufacturerPartNumber="CMS-28588N-L152A"' if k=='speaker' else ' manufacturerPartNumber="MUSEAIR-SPK-ADAPTER-01"' if k=='speaker-adapter' else ''
 modelprop='gltfUrl' if k=='pcb' else 'glbUrl'
 chip=f'<chip{mpn} footprint={{<footprint/>}} name="{label}" cadModel={{{{{modelprop}:model{i},rotationOffset:{{x:0,y:0,z:0}},positionOffset:{{x:0,y:0,z:0}},modelOriginPosition:{{x:0,y:0,z:0}}}}}}/>'
 s+='  '+('{showBase && '+chip+'}' if k=='enclosure-base' else '{showLid && '+chip+'}' if k in ['enclosure-lid','microphone-gasket'] else '{showButton && '+chip+'}' if k=='ask-button' else '{showTies && '+chip+'}' if k=='ties' else chip)+'\n'
s+='</group></assembly.device>\n';(r/'assembly.circuit.tsx').write_text(s)
print('One complete PCB model plus 18 mechanical groups and optional ties')