Devesh36/key-finder
A compact BLE key finder for keys, bags and remotes: replaceable CR2032, piezo alert, status LED and pair button in a 38 mm black keyring enclosure. Includes routed PCB, enclosed/exploded assembly and BOM. Firmware/app and physical qualification remain separate.
- Version
- 0.1.2
- License
- unset
- Stars
- 0
mechanics.tsx
import { Fragment } from 'react'
import { jscad } from 'tscircuit'
import { wordmarkContours } from './wordmark'
// Millimetres in PCB coordinates. PCB surfaces ±0.5; enclosure -5.2 to +3.2.
// Black comes from geometry color, so exports retain it across render backends.
function Cylinder({radius,height,center,segments=64}:{radius:number,height:number,center:[number,number,number],segments?:number}){
const points=Array.from({length:segments},(_,i)=>[radius*Math.cos(i*2*Math.PI/segments),radius*Math.sin(i*2*Math.PI/segments)] as [number,number])
return <jscad.translate offset={[center[0],center[1],center[2]-height/2]}><jscad.extrudeLinear height={height}><jscad.polygon points={points}/></jscad.extrudeLinear></jscad.translate>
}
const black='#1e2228'
function EyeCut(){return <Cylinder radius={2.2} height={30} center={[0,14.2,0]} segments={64}/>}
export function Base(){return <jscad.colorize color={black}><jscad.subtract>
<jscad.union>
<jscad.subtract>
<jscad.hull>
{[0,1,2,3,4,5,6].map(i=>{const a=i*Math.PI/12;return <Fragment key={i}><Cylinder radius={18+Math.sin(a)} height={0.04} center={[0,0,-4.18-Math.cos(a)]} segments={128}/></Fragment>})}
<Cylinder radius={19} height={6.2} center={[0,0,-1.1]} segments={128}/>
</jscad.hull>
<Cylinder radius={17.8} height={10} center={[0,0,0.5]} segments={96}/>
{/* Small pry slot at the seam for replacing the cell. */}
<jscad.cuboid size={[3,3,0.65]} center={[18.5,0,1.8]}/>
</jscad.subtract>
<Cylinder radius={3.1} height={7.2} center={[0,14.2,-1.6]} segments={64}/>
{[-1,1].flatMap(x=>[-1,1].map(y=><Fragment key={`${x},${y}`}>
<jscad.cuboid size={[1.1,1.6,3.9]} center={[x*16.9,y*3,-2.45]}/>
</Fragment>))}
</jscad.union>
<EyeCut/>
</jscad.subtract></jscad.colorize>}
export function Lid(){return <jscad.colorize color={black}><jscad.subtract>
<jscad.union>
<jscad.hull>
{[0,1,2,3,4,5,6].map(i=>{const a=i*Math.PI/12;return <Fragment key={i}><Cylinder radius={18+Math.cos(a)} height={0.04} center={[0,0,2.18+Math.sin(a)]} segments={128}/></Fragment>})}
</jscad.hull>
{/* Short registration skirt; friction fit must be tuned on the target printer. */}
<jscad.subtract>
<Cylinder radius={17.6} height={0.6} center={[0,0,1.8]} segments={96}/>
<Cylinder radius={17.15} height={1} center={[0,0,1.8]} segments={96}/>
<Cylinder radius={3.35} height={2} center={[0,14.2,1.8]} segments={64}/>
</jscad.subtract>
{/* Rim support only: do not glue the vibrating centre of the brass disc. */}
<jscad.subtract>
<Cylinder radius={7.7} height={0.3} center={[-2,1,1.95]} segments={64}/>
<Cylinder radius={7.1} height={1} center={[-2,1,1.95]} segments={64}/>
</jscad.subtract>
</jscad.union>
<EyeCut/>
<Cylinder radius={2.4} height={5} center={[10,4,2]} segments={48}/>
<Cylinder radius={0.65} height={5} center={[10,-1,2]} segments={32}/>
{/* Subtle three-slot sound grille. */}
{[-1,0,1].map(i=><Fragment key={i}><jscad.hull>
{[-1,1].map(s=><Fragment key={s}><Cylinder radius={0.3} height={5} center={[-2+s*2,1+i*1.3,2]} segments={24}/></Fragment>)}
</jscad.hull></Fragment>)}
</jscad.subtract></jscad.colorize>}
export function Button(){return <jscad.colorize color='#30343a'><jscad.union>
<jscad.hull>
<Cylinder radius={2.18} height={0.75} center={[10,4,2.475]} segments={64}/>
<Cylinder radius={1.98} height={0.10} center={[10,4,3.10]} segments={64}/>
</jscad.hull>
<Cylinder radius={2.75} height={0.25} center={[10,4,1.975]} segments={48}/>
<Cylinder radius={0.65} height={0.76} center={[10,4,1.47]} segments={32}/>
</jscad.union></jscad.colorize>}
export function Lens(){return <jscad.colorize color='#982d35'><jscad.union>
<Cylinder radius={0.55} height={1.4} center={[10,-1,2.45]} segments={32}/>
<Cylinder radius={0.9} height={0.2} center={[10,-1,1.85]} segments={32}/>
</jscad.union></jscad.colorize>}
export function Cell(){return <jscad.colorize color='#bfc4ca'><Cylinder radius={10} height={3.2} center={[0,0,-2.45]} segments={96}/></jscad.colorize>}
export function Piezo(){return <jscad.union>
<jscad.colorize color='#b59b52'><Cylinder radius={7.5} height={0.10} center={[-2,1,1.75]} segments={72}/></jscad.colorize>
<jscad.colorize color='#e2e2d7'><Cylinder radius={4.5} height={0.08} center={[-2,1,1.66]} segments={64}/></jscad.colorize>
</jscad.union>}
export function Wires(){return <jscad.union>
{[-1,1].map((s,i)=><Fragment key={s}><jscad.colorize color={i?'#d9d9d3':'#ad3932'}>
<jscad.hull><jscad.sphere radius={0.18} center={[-7+s*1.5,2,0.75]}/><jscad.sphere radius={0.18} center={[-7+s*1.5,3.6,1.3]}/></jscad.hull>
<jscad.hull><jscad.sphere radius={0.18} center={[-7+s*1.5,3.6,1.3]}/><jscad.sphere radius={0.18} center={[-5+s*1.5,3.6,1.6]}/></jscad.hull>
</jscad.colorize></Fragment>)}
</jscad.union>}
// A thin nonconductive blue print on the flat front surface.
export function Wordmark(){return <jscad.colorize color="#3388ff"><jscad.translate offset={[0,0,3.2]}><jscad.extrudeLinear height={0.06}><jscad.union>
{wordmarkContours.map((points,i)=><jscad.polygon key={i} points={points}/>)}
</jscad.union></jscad.extrudeLinear></jscad.translate></jscad.colorize>}