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]$/)
}
})