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

tests/cad/pcb-model-storage.test.ts

import { expect, test } from "bun:test";
import { Document, NodeIO, type Primitive } from "@gltf-transform/core";
import { indexPcbModel } from "../../scripts/index-pcb-model";
import { partitionPcbModels } from "../../scripts/partition-pcb-models";

function expandedAttributes(primitive: Primitive) {
	const indices = primitive.getIndices()?.getArray();
	if (!indices) throw new Error("Indexed triangles required");
	return Object.fromEntries(
		primitive.listSemantics().map((semantic) => {
			const accessor = primitive.getAttribute(semantic);
			const attribute = accessor?.getArray();
			if (!attribute || !accessor) throw new Error("Missing encoded attribute");
			const size = accessor.getElementSize();
			return [
				semantic,
				Array.from(indices).flatMap((index) =>
					Array.from(attribute.slice(index * size, (index + 1) * size)),
				),
			];
		}),
	);
}

test("lossless indexing preserves hard normals, colours and triangle order", async () => {
	const document = new Document();
	const buffer = document.createBuffer();
	const positions = [
		0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0,
		1, 0,
	];
	const normals = [
		0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0,
		0, 1,
	];
	const colours = [
		1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0,
		0, 1,
	];
	const primitive = document.createPrimitive();
	for (const [semantic, attribute] of [
		["POSITION", positions],
		["NORMAL", normals],
		["COLOR_0", colours],
	] as const)
		primitive.setAttribute(
			semantic,
			document
				.createAccessor()
				.setBuffer(buffer)
				.setType("VEC3")
				.setArray(new Float32Array(attribute)),
		);
	primitive.setIndices(
		document
			.createAccessor()
			.setBuffer(buffer)
			.setType("SCALAR")
			.setArray(new Uint16Array([0, 1, 2, 3, 4, 5, 6, 7, 8])),
	);
	const node = document
		.createNode("ActualPart")
		.setMesh(document.createMesh("ActualPart").addPrimitive(primitive));
	document.createScene().addChild(node);
	const expected = expandedAttributes(primitive);
	const io = new NodeIO();
	const source = await io.writeBinary(document);
	const indexed = await indexPcbModel(new Uint8Array(source).buffer);
	const packed = new DataView(indexed);
	const manifest = JSON.parse(
		new TextDecoder().decode(
			new Uint8Array(indexed, 20, packed.getUint32(12, true)),
		),
	);
	expect(
		manifest.bufferViews.every(
			// Separate float VEC3 attributes may explicitly declare their packed12-byte stride.
			(view: { byteStride?: number }) =>
				view.byteStride === undefined || view.byteStride === 12,
		),
	).toBe(true);
	const decoded = await io.readBinary(new Uint8Array(indexed));
	const actual = decoded.getRoot().listMeshes()[0].listPrimitives()[0];
	expect(actual.getAttribute("POSITION")?.getCount()).toBe(8);
	expect(actual.getIndices()?.getCount()).toBe(9);
	expect(expandedAttributes(actual)).toEqual(expected);
});

test("measured packing respects byte and count limits while retaining63 identities", () => {
	const models = Array.from({ length: 63 }, (_, index) => ({
		cadComponentId: `cad_component_${index}`,
		bytes: index === 0 ? 1_800_000 : index < 20 ? 100_000 : 30_000,
	}));
	const boardBytes = 350_000;
	expect(
		boardBytes +
			models.slice(0, 9).reduce((sum, model) => sum + model.bytes, 0),
	).toBeGreaterThan(2_400_000);
	const partitions = partitionPcbModels({
		models,
		boardBytes,
		fragmentCount: 7,
		maxModelsPerFragment: 9,
		maxFragmentBytes: 2_400_000,
	});
	expect(new Set(partitions.flat()).size).toBe(63);
	expect(partitions.flat().sort()).toEqual(
		models.map((model) => model.cadComponentId).sort(),
	);
	for (const [index, partition] of partitions.entries()) {
		expect(partition.length).toBeLessThanOrEqual(9);
		const bytes = partition.reduce(
			(sum, id) =>
				sum + models.find((model) => model.cadComponentId === id)!.bytes,
			index === 0 ? boardBytes : 0,
		);
		expect(bytes).toBeLessThanOrEqual(2_400_000);
	}
});