Devesh36/orbit-turntable
ORBIT is a USB-C-powered display turntable for photographing jewellery, models and small products. Four-layer ESP32-C3 / ULN2003 PCB, geared stepper, OLED, push encoder, home switch and buzzer, with an enclosed and exploded assembly. Hardware prototype; firmware and physical qualification remain separate.
- Version
- 0.1.1
- License
- unset
- Stars
- 0
scripts/export-mechanics.tsx
import {mkdirSync,writeFileSync} from 'node:fs'
import type {ReactNode} from 'react'
import modeling from '@jscad/modeling'
import type {Geom3} from '@jscad/modeling/src/geometries/types'
import {executeJscadOperations} from 'jscad-planner'
import {renderToJscadPlan} from 'jscad-fiber/headless'
import {jscad} from 'tscircuit'
import {outline} from '../index.circuit'
import {Base,Lid,Platter,Coupler,Knob,Motor,Bearing,Display,Screen,Lens} from '../mechanics'
const kernel=modeling as unknown as Parameters<typeof executeJscadOperations>[0]
const solid=(node:ReactNode)=>executeJscadOperations(kernel,renderToJscadPlan(node) as Parameters<typeof executeJscadOperations>[1]) as Geom3
const bodies={base:solid(<Base/>),lid:solid(<Lid/>),platter:solid(<Platter/>),coupler:solid(<Coupler/>),knob:solid(<Knob/>),motor:solid(<Motor/>),bearing:solid(<Bearing/>),display:solid(<Display/>),screen:solid(<Screen/>),lens:solid(<Lens/>),pcb:solid(<jscad.translate offset={[0,-31,-0.8]}><jscad.extrudeLinear height={1.6}><jscad.polygon points={outline.map(p=>[p.x,p.y])}/></jscad.extrudeLinear></jscad.translate>)}
mkdirSync('mechanical',{recursive:true})
const generalize=modeling.modifiers.generalize as unknown as (options:{snap:boolean,triangulate:boolean},shape:Geom3)=>Geom3
for(const name of ['base','lid','platter','coupler','knob'] as const){
const original=bodies[name];const bounds=modeling.measurements.measureBoundingBox(original);const volume=modeling.measurements.measureVolume(original)
if(!(volume>0))throw Error(`${name} has no solid volume`)
const shape=generalize({snap:true,triangulate:true},modeling.transforms.translate([0,0,-bounds[0][2]],original))
const triangles=modeling.geometries.geom3.toPolygons(shape).flatMap(p=>p.vertices.slice(2).map((v,i)=>[p.vertices[0],p.vertices[i+1],v]))
const bytes=new Uint8Array(84+50*triangles.length);bytes.set(new TextEncoder().encode(`ORBIT ${name} / mm / fit prototype`));const d=new DataView(bytes.buffer);d.setUint32(80,triangles.length,true);let pos=84
for(const [a,b,c] of triangles){const u=b.map((q,i)=>q-a[i]),v=c.map((q,i)=>q-a[i]);const n=[u[1]*v[2]-u[2]*v[1],u[2]*v[0]-u[0]*v[2],u[0]*v[1]-u[1]*v[0]];const len=Math.hypot(...n)||1;for(const q of [...n.map(x=>x/len),...a,...b,...c]){d.setFloat32(pos,q,true);pos+=4}d.setUint16(pos,0,true);pos+=2}
writeFileSync(`mechanical/${name}.stl`,bytes);console.log(`${name}: ${volume.toFixed(1)} mm³; ${triangles.length} triangles`)
}
const pairs:[keyof typeof bodies,keyof typeof bodies][]=[['base','pcb'],['base','motor'],['base','lid'],['lid','motor'],['lid','bearing'],['lid','pcb'],['motor','coupler'],['bearing','coupler'],['lid','screen'],['platter','lid'],['platter','screen'],['lid','knob'],['lid','lens']]
const overlaps=[]
for(const [a,b] of pairs){const volume=Math.abs(modeling.measurements.measureVolume(modeling.booleans.intersect(bodies[a],bodies[b])));if(volume>0.05)overlaps.push({a,b,volume})}
console.log(JSON.stringify({checkedPairs:pairs.length,overlaps,scope:'Selected envelope/solid checks. Manufacturer CAD tolerances, clips, wiring and moving clearances require physical qualification.'},null,2));process.exitCode=overlaps.length?1:0
