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/attachments.ts
import { Euler, Matrix4, Vector3, MathUtils } from "three"
import type { CadComponent, CircuitJson } from "circuit-json"
import { dimensions as d } from "./dimensions"
export type Point = { x: number; y: number; z: number }
export type Pose = { position: Point; rotation: Point }
export type Port = {
position: Point
direction: Point
kind: "fixed_wire" | "bullet_male" | "bullet_female"
diameter: number
}
export type Part = { name: string; pose: Pose; ports: Record<string, Port> }
export const phases = ["U", "V", "W"] as const
/** CAD-local mm to right-handed circuit-world mm (+X right, +Y top, +Z above).
* Paired with core renderAssemblyCadModel: intrinsic XYZ = Rx * Ry * Rz.
* Points acquire translation; directions acquire rotation only.
*/
export function poseMatrix(pose: Pose) {
const r = pose.rotation
return new Matrix4().makeRotationFromEuler(new Euler(
MathUtils.degToRad(r.x), MathUtils.degToRad(r.y), MathUtils.degToRad(r.z), "XYZ",
)).setPosition(pose.position.x, pose.position.y, pose.position.z)
}
export const point = (v: Vector3): Point => ({ x: v.x, y: v.y, z: v.z })
export const vector = (p: Point) => new Vector3(p.x, p.y, p.z)
/** Resolve a named, component-owned local interface in circuit-world mm. */
export function matingPoint(part: Part, portName: string, insertion = 0) {
const port = part.ports[portName]
if (!port) throw new Error(`No ${portName} connection on ${part.name}`)
const matrix = poseMatrix(part.pose)
const direction = vector(port.direction).transformDirection(matrix)
const position = vector(port.position).applyMatrix4(matrix).addScaledVector(direction, -insertion)
return { position: point(position), direction: point(direction) }
}
export const defaultMotorPose: Pose = {
position: { x: d.layout.motor[0], y: d.layout.motor[1], z: d.layout.motor[2] },
rotation: { x: 0, y: 0, z: 0 },
}
export function motorPart(pose: Pose = defaultMotorPose): Part {
return {
name: "MOTOR_ENVELOPE", pose,
ports: Object.fromEntries(phases.map((phase, i) => [phase, {
position: { x: (i - 1) * 3, y: -d.motor.bodyDiameter / 2 + 1, z: 4 },
direction: { x: 0, y: -1, z: 0 }, kind: "fixed_wire", diameter: d.illustrativeLeads.wireDiameter,
} satisfies Port])),
}
}
// Supplier OBJ-local measurements, not PCB/world positions. These stay with the
// exact C7437324 model; its imported origin, rotation and position come from PCB CAD.
// The raised circular lip is at +X (13.25 mm); -X is the rear face.
export const socketLocalMouth = { x: 13.25, y: 0, z: 7.5 }
export function socketPart(cad: CadComponent, phase: number): Part {
const origin = cad.model_origin_position ?? { x: 0, y: 0, z: 0 }
const scale = cad.model_unit_to_mm_scale_factor ?? 1
return { name: `J${10 + phase}`, pose: { position: cad.position, rotation: cad.rotation ?? { x: 0, y: 0, z: 0 } }, ports: {
mouth: {
position: point(vector(socketLocalMouth).sub(vector(origin)).multiplyScalar(scale)),
direction: { x: 1, y: 0, z: 0 }, kind: "bullet_female", diameter: d.socket.nominalBore,
},
} }
}
export function selectSocketCad(json: CircuitJson): CadComponent[] {
return phases.map((_, i) => {
const source = json.find(p => p.type === "source_component" && p.name === `J${10 + i}`)
const cad = json.find((p): p is CadComponent => p.type === "cad_component" &&
!!source && "source_component_id" in source && p.source_component_id === source.source_component_id)
if (!cad?.model_obj_url?.includes("C7437324")) throw new Error(`Review the imported model for J${10 + i}`)
return cad
})
}
export const adapterShoulderZ = d.adapter.illustrativeMaleLength - d.adapter.illustrativeBoardInsertion
export const adapterMouthZ = adapterShoulderZ + d.adapter.illustrativeSleeveLength
/** Adapter-local +Z points out of the socket towards the motor; origin is the
* PCB socket mouth. Reuse the socket CAD rotation, then turn local +Z to +X.
* This seats the adapter from the current PCB geometry, including rotations.
*/
export function adapterPart(socket: Part, phase: number): Part {
const mouth = matingPoint(socket, "mouth")
const orientation = poseMatrix(socket.pose).multiply(new Matrix4().makeRotationY(Math.PI / 2))
const r = new Euler().setFromRotationMatrix(orientation, "XYZ")
return { name: `RIGID_ADAPTER_${phases[phase]}`, pose: {
position: mouth.position,
rotation: { x: MathUtils.radToDeg(r.x), y: MathUtils.radToDeg(r.y), z: MathUtils.radToDeg(r.z) },
}, ports: {
board: { position: { x: 0, y: 0, z: -d.adapter.illustrativeBoardInsertion },
direction: { x: 0, y: 0, z: -1 }, kind: "bullet_male", diameter: d.adapter.maleNominalDiameter },
motor: { position: { x: 0, y: 0, z: adapterMouthZ },
direction: { x: 0, y: 0, z: 1 }, kind: "bullet_female", diameter: d.adapter.femaleNominalDiameter },
} }
}
export function assemblyParts(socketCad: CadComponent[], motorPose = defaultMotorPose) {
if (socketCad.length !== 3) throw new Error("The motor assembly needs three PCB phase sockets")
const sockets = socketCad.map(socketPart)
return { motor: motorPart(motorPose), sockets, adapters: sockets.map(adapterPart) }
}