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);
}
});