MustafaMulla29/rp2040-bldc-motor-controller-new
73×72 mm 4-layer USB‑C PD (5/9/15/20 V) RP2040 controller PCB with protected power switching, MCT8329A sensorless BLDC three-phase MOSFET bridge, USB data/programming, onboard controls, and three motor outputs feeding a 2212 motor through rigid bullet adapters.
- Version
- 0.3.100
- License
- unset
- Stars
- 0
assembly/viewer.ts
import * as THREE from "three"
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js"
import { OrbitControls } from "three/addons/controls/OrbitControls.js"
const host = document.querySelector<HTMLElement>("#scene")!
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setPixelRatio(Math.min(devicePixelRatio, 2))
renderer.setClearColor(0xedf0f3)
renderer.outputColorSpace = THREE.SRGBColorSpace
host.prepend(renderer.domElement)
const scene = new THREE.Scene()
scene.add(new THREE.HemisphereLight(0xffffff, 0x6d7988, 1.8))
const light = new THREE.DirectionalLight(0xffffff, 1.7)
light.position.set(70, 150, 120);scene.add(light)
const camera = new THREE.PerspectiveCamera(36, 1, 0.1, 2000)
const controls = new OrbitControls(camera, renderer.domElement)
controls.enableDamping = true
const toWorld = (x: number, y: number, z: number) => new THREE.Vector3(-x, z, y)
const labels: { element: HTMLElement; point: THREE.Vector3 }[] = []
function label(text: string, point: THREE.Vector3) {
const element = document.createElement("div")
element.className = "label";element.textContent = text;host.append(element)
labels.push({ element, point })
}
function frame(target: THREE.Vector3, offset: THREE.Vector3) {
controls.target.copy(target);camera.position.copy(target).add(offset);controls.update()
}
let activeView = "all"
const study = new THREE.Group();scene.add(study)
function wholeStudy() {
const box = new THREE.Box3().setFromObject(study)
if (box.isEmpty()) return
const center = box.getCenter(new THREE.Vector3())
const direction = new THREE.Vector3(0.45, 0.7, 0.9).normalize()
const right = new THREE.Vector3().crossVectors(camera.up, direction).normalize()
const up = new THREE.Vector3().crossVectors(direction, right).normalize()
const tanV = Math.tan(THREE.MathUtils.degToRad(camera.fov / 2)), tanH = tanV * camera.aspect
let distance = 0
for(const x of [box.min.x,box.max.x]) for(const y of [box.min.y,box.max.y]) for(const z of [box.min.z,box.max.z]) {
const relative = new THREE.Vector3(x,y,z).sub(center), depth=relative.dot(direction)
distance=Math.max(distance,Math.abs(relative.dot(right))/tanH+depth,Math.abs(relative.dot(up))/tanV+depth)
}
frame(center,direction.multiplyScalar(distance*1.18))
}
document.querySelector("#all")!.addEventListener("click", () => { activeView = "all";wholeStudy();host.scrollIntoView() })
function detailView() { frame(toWorld(8.7, 62, 8.3), new THREE.Vector3(35, 65, 110).multiplyScalar(1/Math.min(1,camera.aspect))) }
document.querySelector("#detail")!.addEventListener("click", () => { activeView = "detail";detailView();host.scrollIntoView() })
document.querySelector("#top")!.addEventListener("click", () => { activeView = "top";frame(toWorld(-3.5, 9.5, 0), new THREE.Vector3(0, 180 / Math.min(1, camera.aspect), 0.1));host.scrollIntoView() })
document.querySelector("#bottom")!.addEventListener("click", () => { activeView = "bottom";frame(toWorld(0, 0, 0), new THREE.Vector3(0, -195 / Math.min(1, camera.aspect), 0.1));host.scrollIntoView() })
document.querySelector("#knob")!.addEventListener("click", () => { activeView = "knob";frame(toWorld(-27, 36, 5), new THREE.Vector3(28, 35, 38));host.scrollIntoView() })
document.querySelector("#buttons")!.addEventListener("click", () => { activeView = "buttons";frame(toWorld(-14, 35.5, 2), new THREE.Vector3(20, 65, -55));host.scrollIntoView() })
document.querySelector("#arm")!.addEventListener("click", () => { activeView = "arm";frame(toWorld(0, -24, 2), new THREE.Vector3(15, 36, -26));host.scrollIntoView() })
document.querySelector("#power")!.addEventListener("click", () => { activeView = "power";frame(toWorld(28, -15, 3), new THREE.Vector3(28, 45, -32));host.scrollIntoView() })
frame(toWorld(-5, 30, 0), new THREE.Vector3(100, 150, 185))
const loader = new GLTFLoader()
Promise.all([loader.loadAsync("/dist/assets/compact-board.glb"), loader.loadAsync("/dist/results/mechanical.glb")]).then(([pcb, mechanical]) => {
pcb.scene.position.set(0,0,0)
study.add(pcb.scene, mechanical.scene)
label("Configurable PD input", toWorld(-80,15,8))
label("Computer USB DATA", toWorld(-80,-25,8))
label("73 × 72 mm · components on top", toWorld(-15, -8, 5))
label("Motor envelope · published dimensions", toWorld(12, 127, 40))
label("Native motor leads · assumed length", toWorld(24, 107, 9))
label("Three side-entry sockets on top", toWorld(8.7, 36, 14))
label("Rigid adapters · ends seated", toWorld(8.7, 58, 8.3))
document.querySelector("#loading")!.textContent = "Loaded · 5/9/15/20 V USB-PD · USB DATA · motor · 3 rigid adapters"
wholeStudy()
;(window as any).assemblyLoaded = true
}).catch(error => {
document.querySelector("#errors")!.textContent = String(error)
document.querySelector("#loading")!.textContent = "Model failed to load"
})
new ResizeObserver(() => {
const w = host.clientWidth, h = host.clientHeight
renderer.setSize(w, h);camera.aspect = w / h;camera.updateProjectionMatrix()
if (activeView === "all") wholeStudy()
if (activeView === "detail") detailView()
}).observe(host)
renderer.setAnimationLoop(() => {
controls.update()
for (const { element, point } of labels) {
const p = point.clone().project(camera)
const enabled = (document.querySelector("#labels") as HTMLInputElement).checked
element.hidden = !enabled || p.z < -1 || p.z > 1 || Math.abs(p.x) > 1 || Math.abs(p.y) > 1
element.style.left = `${Math.max(90, Math.min(host.clientWidth - 90, (p.x + 1) / 2 * host.clientWidth))}px`
element.style.top = `${(1 - p.y) / 2 * host.clientHeight}px`
}
renderer.render(scene, camera)
})