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

import React from "react"
import { expect, test } from "bun:test"
import { Circuit } from "@tscircuit/core"
import { loadJscadPlan } from "circuit-json-to-gltf"
import { parseCableString } from "@tscircuit/cableprinter"
import { createCableMeshes } from "jscad-electronics/cables"
import Study from "./index"
import { motorPlan, adapterContactGeometry } from "./models"
import { selectSocketCad, poseMatrix, vector } from "./attachments"

const pcb = await Bun.file(new URL("../dist/index/circuit.json", import.meta.url)).json()
const socketCad = selectSocketCad(pcb)

const bounds = (points: number[][]) => ({
  min: [0,1,2].map(i=>Math.min(...points.map(p=>p[i]))),
  max: [0,1,2].map(i=>Math.max(...points.map(p=>p[i]))),
})
const meshPoints = (mesh: {positions:number[]}) => Array.from({length:mesh.positions.length/3},(_,i)=>mesh.positions.slice(i*3,i*3+3))

// Expectations use real compiled socket transforms, native emitted cable meshes
// and rigid contact geometry, rather than just comparing placement constants.
test("native motor plugs seat in rigid adapters on the real PCB socket axes", async () => {
  const names = new Map(pcb.filter((p:any)=>p.type==="source_component").map((p:any)=>[p.source_component_id,p.name]))
  const circuit = new Circuit(); circuit.add(<Study includeBoard={false} socketCad={socketCad}/>); await circuit.renderUntilSettled()
  const json=circuit.getCircuitJson(),cables=json.filter(x=>x.type==="cad_cable")
  const motor=json.filter(x=>x.type==="cad_component").find(x=>
    json.some((p:any)=>p.type==="source_component" && p.name==="MOTOR_ENVELOPE" && p.source_component_id===x.source_component_id))!
  for (let phase=0;phase<3;phase++) {
    const socket = pcb.find((p:any)=>p.type==="cad_component" && names.get(p.source_component_id)===`J${10+phase}`)
    const origin=socket.model_origin_position, a=socket.rotation.z*Math.PI/180
    const x=13.25-origin.x,y=-origin.y,z=7.5-origin.z
    const mouth=[socket.position.x+x*Math.cos(a)-y*Math.sin(a),socket.position.y+x*Math.sin(a)+y*Math.cos(a),socket.position.z+z]
    const cable=cables.find(c=>c.name===`MOTOR_LEAD_${"UVW"[phase]}`)!
    const definition=parseCableString(cable.cableprinter_string)
    expect(definition.connectorA.kind).toBe("wire_end")
    expect(definition.connectorB.kind).toBe("bullet_male")
    expect("diameter" in definition.connectorB && definition.connectorB.diameter).toBe(3.5)
    const meshes=createCableMeshes({definition,path:cable.path.map(p=>[p.x,p.y,p.z])})
    expect(meshes.some(m=>m.name.startsWith("A-"))).toBe(false)
    expect(meshes.some(m=>m.name==="jacket")).toBe(true)
    const plug=bounds(meshPoints(meshes.find(m=>m.name==="B-bullet-pin")!))
    const adapterSource=json.filter(p=>p.type==="source_component").find(p=>p.name===`RIGID_ADAPTER_${"UVW"[phase]}`)!
    const adapterCad=json.filter(p=>p.type==="cad_component").find(p=>p.source_component_id===adapterSource.source_component_id)!
    const matrix=poseMatrix({position:adapterCad.position,rotation:adapterCad.rotation ?? {x:0,y:0,z:0}})
    const transform=(points:number[][])=>points.map(([x,y,z])=>vector({x,y,z}).applyMatrix4(matrix).toArray())
    const femaleGeometry=adapterContactGeometry("female")
    const femalePoints=transform(femaleGeometry.points)
    const female=bounds(femalePoints)
    const male=bounds(transform(adapterContactGeometry("male").points))
    expect((male.min[0]+male.max[0])/2).toBeCloseTo(mouth[0],4)
    expect((male.min[2]+male.max[2])/2).toBeCloseTo(mouth[2],4)
    expect(male.min[1]).toBeCloseTo(mouth[1]-8,4)
    expect(male.max[1]).toBeCloseTo(mouth[1]+4,4)
    expect((plug.min[0]+plug.max[0])/2).toBeCloseTo((female.min[0]+female.max[0])/2,4)
    expect((plug.min[2]+plug.max[2])/2).toBeCloseTo((female.min[2]+female.max[2])/2,4)
    expect(plug.min[1]).toBeCloseTo(female.min[1],4)
    expect(plug.max[1]).toBeCloseTo(female.max[1],4)
    expect(femalePoints.some(([vx,vy,vz])=>Math.abs(Math.hypot(vx-mouth[0],vz-mouth[2])-1.75)<1e-4)).toBe(true)
    const start=cable.path[0]
    expect(Math.hypot(start.x-motor.position.x,start.y-motor.position.y)).toBeLessThan(14)
    expect(start.z).toBeGreaterThan(0)
    expect(start.z).toBeLessThan(24)
    const obj=await Bun.file(new URL("dist/assets/rigid-adapter.obj",import.meta.url)).text()
    const body=bounds(obj.split("\n").filter(l=>l.startsWith("v ")).map(l=>l.split(" ").slice(1).map(Number)))
    expect(body.max[2]-body.min[2]).toBeCloseTo(26,4) // local +Z; 12 mm pin + 14 mm body
  }
})

test("motor envelope retains published dimensions", () => {
  const b=loadJscadPlan(motorPlan()).boundingBox
  expect(b.max.x-b.min.x).toBeCloseTo(28,5)
  expect(b.max.z-b.min.z).toBeCloseTo(28,5)
  expect(b.max.y-b.min.y).toBeCloseTo(38,5)
})

test("only original motor leads are cables and adapters are rigid CAD owners", async () => {
  const circuit=new Circuit();circuit.add(<Study includeBoard={false} socketCad={socketCad}/>);await circuit.renderUntilSettled()
  const json=circuit.getCircuitJson()
  expect(json.filter(x=>x.type.endsWith("_error"))).toHaveLength(0)
  expect(json.filter(x=>x.type==="cad_cable")).toHaveLength(3)
  expect(json.filter(x=>["pcb_board","pcb_component","pcb_trace","pcb_via","source_trace"].includes(x.type))).toHaveLength(0)
  const owners=json.filter(x=>x.type==="source_component")
  const motor=owners.find(x=>x.name==="MOTOR_ENVELOPE")!
  expect(owners.filter(x=>x.name?.startsWith("RIGID_ADAPTER_"))).toHaveLength(3)
  expect(owners.some(x=>x.name?.startsWith("MOTOR_ORIGINAL_LEAD_")||x.name?.startsWith("MOTOR_EXISTING_PLUG_"))).toBe(false)
  for(const cable of json.filter(x=>x.type==="cad_cable")) {
    expect(cable.name).toMatch(/^MOTOR_LEAD_[UVW]$/)
    expect(cable.from_source_component_id).toBe(motor.source_component_id)
    expect(owners.find(x=>x.source_component_id===cable.to_source_component_id)?.name).toMatch(/^RIGID_ADAPTER_[UVW]$/)
  }
})