anil08607/Smartwatch

A 46 mm smartwatch PCB built around an nRF52810 BLE MCU, with USB‑C PD/Li‑ion charging, buck‑boost power, LCD, accelerometer, RTC, buttons, haptic motor, crystals, antenna, and SWD programming connector.

Version
1.0.14
License
unset
Stars
0

mechanical/viewer-entry.js

import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
const canvas = document.querySelector("canvas"),
	renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
const scene = new THREE.Scene(),
	camera = new THREE.PerspectiveCamera(35, 1, 5, 1000);
camera.up.set(0, 0, 1);
camera.position.set(185, -220, 210);
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 0, 5);
controls.enableDamping = true;
scene.add(new THREE.HemisphereLight(0xffffff, 0x596270, 3));
const light = new THREE.DirectionalLight(0xffffff, 4);
light.position.set(50, -50, 100);
scene.add(light);
let model,
	manifest,
	amount = 0;
const loader = new GLTFLoader();
Promise.all([
	loader.loadAsync("complete-assembly.glb"),
	fetch("assembly-manifest.json").then((r) => r.json()),
])
	.then(([g, m]) => {
		manifest = m;
		model = g.scene;
		scene.add(model);
		document.querySelector("#parts").textContent =
			m.parts.length + " assembly solids";
		document.querySelector("#status").textContent = "Model loaded";
		update();
		model.traverse((n) => {
			if (n.isMesh) {
				if (n.material.transparent) {
					n.material.depthWrite = false;
					n.renderOrder = 10;
				}
				n.userData.start = n.position.z;
				n.userData.part = m.parts.find((p) => p.id === n.name);
			}
		});
	})
	.catch((e) => (document.querySelector("#status").textContent = e.message));
function update() {
	if (!model) return;
	model.traverse((n) => {
		if (n.isMesh && n.userData.part) {
			const p = n.userData.part;
			n.position.z = n.userData.start + p.explode * amount;
			n.visible = !(
				document.querySelector("#inside").checked &&
				[
					"case-base",
					"bezel",
					"lens",
					"strap--30",
					"strap-30",
					"strap-buckle",
					"buckle-tang",
					"strap-keeper",
				].includes(p.id)
			);
		}
	});
}
document.querySelector("#explode").oninput = (e) => {
	amount = Number(e.target.value);
	update();
};
document.querySelector("#inside").onchange = update;
document.querySelector("#detail").onclick = () => {
	camera.position.set(85, -95, 85);
	controls.target.set(0, 0, 5);
};
document.querySelector("#reset").onclick = () => {
	camera.position.set(185, -220, 210);
	controls.target.set(0, 0, 5);
};
document.querySelector("#usb-side").onclick = () => {
	camera.position.set(-115, -30, 35);
	controls.target.set(0, 0, 1);
};
document.querySelector("#button-side").onclick = () => {
	camera.position.set(115, 0, 8);
	controls.target.set(0, 0, 0.5);
};
function frame() {
	const r = canvas.getBoundingClientRect();
	if (
		canvas.width !== Math.round(r.width * renderer.getPixelRatio()) ||
		canvas.height !== Math.round(r.height * renderer.getPixelRatio())
	) {
		renderer.setSize(r.width, r.height, false);
		camera.aspect = r.width / r.height;
		camera.updateProjectionMatrix();
	}
	controls.update();
	renderer.render(scene, camera);
	requestAnimationFrame(frame);
}
frame();

const raycaster = new THREE.Raycaster();
canvas.addEventListener("click", (event) => {
	if (!model) return;
	const r = canvas.getBoundingClientRect();
	raycaster.setFromCamera(
		new THREE.Vector2(
			((event.clientX - r.left) / r.width) * 2 - 1,
			(-(event.clientY - r.top) / r.height) * 2 + 1,
		),
		camera,
	);
	const hit = raycaster
		.intersectObjects(model.children, true)
		.find((h) => h.object.visible);
	const part = hit?.object.userData.part;
	document.querySelector("#selection").textContent = part
		? `${part.id}: ${part.stage}`
		: "";
});