AnasSarkiz/magnetic-shutter-remote-r8

A USB‑C rechargeable battery-powered ESP32‑C3 wireless controller with Li‑ion charging, protected 3.3‑V buck‑boost regulation, thermal/safety monitoring, status LEDs, buttons, programming UART, crystal clock, and 2.4‑GHz antenna matching network.

Version
0.4.0-prototype
License
unset
Stars
0

scripts/render-product.ts

import {
	loadGLTFWithResourcesFromURL,
	createSceneFromGLTF,
	renderSceneFromGLTF,
	encodePNG,
} from "poppygl";
import { createProductParts } from "../product/geometry";
import { z } from "zod";
const name = z
	.enum(["product.assembly", "product.exploded", "enclosure"])
	.parse(Bun.argv[2]);
const view = z
	.enum(["normal", "phone-facing"])
	.default("normal")
	.parse(Bun.argv[3]);
if (view === "phone-facing" && name === "product.exploded")
	throw new Error("Phone-facing view uses the closed assembly");
const glb = await Bun.file(`dist/${name}/3d.glb`).arrayBuffer();
// Presentation materials on exact native geometry; never write an edited GLB.
const { gltf, resources } = await loadGLTFWithResourcesFromURL(
	`data:model/gltf-binary;base64,${Buffer.from(glb).toString("base64")}`,
);
const document = z
	.object({
		nodes: z.array(
			z
				.object({ name: z.string().optional(), mesh: z.number().optional() })
				.passthrough(),
		),
		meshes: z.array(
			z
				.object({
					primitives: z.array(
						z.object({ material: z.number().optional() }).passthrough(),
					),
				})
				.passthrough(),
		),
		materials: z.array(z.unknown()),
	})
	.passthrough()
	.parse(gltf);
const parts = createProductParts();
for (const node of document.nodes) {
	const part = parts.find((part) => part.name === node.name);
	if (!part || node.mesh === undefined) continue;
	if (part.plan.type !== "colorize")
		throw new Error("Product part is missing its presentation material");
	const rgb = z
		.tuple([z.number(), z.number(), z.number()])
		.parse(part.plan.color);
	const materialIndex = document.materials.length;
	document.materials.push({
		name: `${part.name} presentation`,
		pbrMetallicRoughness: {
			baseColorFactor: [...rgb, part.referenceOnly ? 0.45 : 1],
			roughnessFactor: 0.85,
			metallicFactor: 0,
		},
		alphaMode: part.referenceOnly ? "BLEND" : "OPAQUE",
	});
	for (const primitive of document.meshes[node.mesh].primitives)
		primitive.material = materialIndex;
}
const scene = createSceneFromGLTF(document, resources);
const result = renderSceneFromGLTF(scene, {
	width: 1400,
	height: 1100,
	backgroundColor: "#f0f2f5",
	ambient: 0.5,
	camPos:
		view === "phone-facing"
			? [-70, -210, 95]
			: name === "product.exploded"
				? [175, 210, 145]
				: [135, 210, 80],
	lookAt: [14, name === "product.exploded" ? 50 : 17, -13],
	up: name === "product.exploded" ? "y+" : "z+",
	fov: name === "product.exploded" ? 40 : 34,
});
const png = await encodePNG(result.bitmap);
await Bun.write(
	`product/${view === "phone-facing" ? "phone-facing" : name === "product.exploded" ? "exploded" : "closed"}.png`,
	png,
);
if (view === "normal") await Bun.write(`dist/${name}/3d.png`, png);
console.log(`Rendered ${name} with an explicit whole-product camera`);