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/attachment-motion.test.tsx

import React from "react"
import { expect, test } from "bun:test"
import { Circuit } from "@tscircuit/core"
import { convertCircuitJsonToGltf } from "circuit-json-to-gltf"
import { Euler, Matrix4, Vector3 } from "three"
import Study from "./index"
import { selectSocketCad } from "./attachments"

// Inspect the final exported GLB, not an assertion against the placement helper.
// Renderer frame is (-circuitX, circuitZ, circuitY), millimetres, +Y up.
function glbVertices(data: ArrayBuffer, label: string) {
  const view = new DataView(data)
  const jsonLength = view.getUint32(12, true)
  const gltf = JSON.parse(new TextDecoder().decode(new Uint8Array(data, 20, jsonLength)))
  const binStart = 20 + jsonLength + 8
  const node = gltf.nodes.find((n: any) => n.name === label)
  if (!node) throw new Error(`Missing exported mesh ${label}`)
  const vertices: Vector3[] = []
  for (const primitive of gltf.meshes[node.mesh].primitives) {
    const accessor = gltf.accessors[primitive.attributes.POSITION]
    expect(accessor.componentType).toBe(5126)
    const buffer = gltf.bufferViews[accessor.bufferView]
    const offset = binStart + (buffer.byteOffset ?? 0) + (accessor.byteOffset ?? 0)
    for (let i = 0; i < accessor.count; i++) {
      const start = offset + i * (buffer.byteStride ?? 12)
      vertices.push(new Vector3(...[0, 4, 8].map(b => view.getFloat32(start + b, true)) as [number,number,number])
        .add(new Vector3(...(node.translation ?? [0,0,0]))))
    }
  }
  return vertices
}
const extent = (vertices: Vector3[], direction: Vector3) => {
  const distances = vertices.map(p => p.dot(direction))
  return { min: Math.min(...distances), max: Math.max(...distances) }
}
const sceneVector = (p: Vector3) => new Vector3(-p.x, p.z, p.y)

test("exported plugs stay seated when PCB sockets and the motor move and rotate", async () => {
  const pcb = await Bun.file(new URL("../dist/index/circuit.json", import.meta.url)).json()
  // Quarter-turns, bottom-side CAD turns, plus a non-cardinal 3D motor pose.
  for (const bottom of [false, true]) for (const angle of [0, 90, 180, 270]) {
    const sockets = structuredClone(selectSocketCad(pcb))
    sockets[0].position = { x: 43, y: -19, z: bottom ? -4 : 6 }
    sockets[0].rotation = { x: 0, y: bottom ? 180 : 0, z: angle }
    sockets[0].layer = bottom ? "bottom" : "top"
    const motorPose = { position: { x: -31, y: 104, z: 12 }, rotation: { x: 17, y: -11, z: 29 } }
    const c = new Circuit()
    c.add(<Study socketCad={sockets} motorPose={motorPose} includeBoard={false} />)
    await c.renderUntilSettled()
    const json = c.getCircuitJson()
    expect(json.filter(p => p.type.endsWith("_error"))).toHaveLength(0)
    const glb = await convertCircuitJsonToGltf(json, { format: "glb", drawFauxBoard: false, includeModels: true }) as ArrayBuffer
    const adapter = glbVertices(glb, "RIGID_ADAPTER_U")
    const pin = glbVertices(glb, "MOTOR_LEAD_U / B-bullet-pin")
    const r = new Matrix4().makeRotationFromEuler(new Euler(0, bottom ? Math.PI : 0, angle * Math.PI / 180, "XYZ"))
    const axis = sceneVector(new Vector3(1, 0, 0).transformDirection(r))
    const a = extent(adapter, axis), p = extent(pin, axis)
    expect(p.max).toBeCloseTo(a.max, 4)
    expect(p.min).toBeCloseTo(a.max - 7, 4)
    for (const localCross of [new Vector3(0,1,0), new Vector3(0,0,1)]) {
      const cross = sceneVector(localCross.transformDirection(r))
      const a = extent(adapter, cross), p = extent(pin, cross)
      expect((p.min+p.max)/2).toBeCloseTo((a.min+a.max)/2, 4)
    }
    // The cable begins at the moved/rotated motor-local U wire exit, not the
    // old absolute position. This is also the center of its first jacket ring.
    const motorRotation = new Matrix4().makeRotationFromEuler(new Euler(17*Math.PI/180,-11*Math.PI/180,29*Math.PI/180,"XYZ"))
    const expectedExit = new Vector3(-3,-13,4).applyMatrix4(motorRotation).add(new Vector3(-31,104,12))
    const cable = json.filter(p => p.type === "cad_cable").find(p => p.name === "MOTOR_LEAD_U")!
    expect(new Vector3(cable.path[0].x,cable.path[0].y,cable.path[0].z).distanceTo(expectedExit)).toBeLessThan(1e-5)
    const jacket = glbVertices(glb, "MOTOR_LEAD_U / jacket")
    expect(jacket.some(v => Math.abs(v.distanceTo(sceneVector(expectedExit))-1.2)<1e-4)).toBe(true)
  }
}, 60000)