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/vendor/core-mating-points.patch
--- /dev/null
+++ b/lib/components/primitive-components/assembly-cable-mating-props.ts
@@ -0,0 +1,32 @@
+import { assemblyCableProps } from "@tscircuit/props"
+import { z } from "zod"
+
+const point3 = z.object({
+ x: z.number().finite(),
+ y: z.number().finite(),
+ z: z.number().finite(),
+})
+
+/** Explicit cable mating point in right-handed circuit-world millimetres:
+ * +X right, +Y top, +Z above. Position is a point; direction is the outward
+ * direction toward the cable wire. This is already world space: no hidden
+ * translation, board offset or model rotation is applied by the resolver.
+ */
+export const assemblyCableMatingPoint = z.object({
+ position: point3,
+ direction: point3.refine(
+ (direction) => Math.hypot(direction.x, direction.y, direction.z) > 1e-9,
+ { message: "Cable mating direction must be nonzero" },
+ ),
+ pin1Position: point3.optional(),
+})
+
+// Optional extension preserves the published selector/model API and its defaults.
+export const assemblyCableMatingProps = assemblyCableProps.extend({
+ fromMatingPoint: assemblyCableMatingPoint.optional(),
+ toMatingPoint: assemblyCableMatingPoint.optional(),
+})
+export type AssemblyCableMatingPoint = z.infer<typeof assemblyCableMatingPoint>
+export type AssemblyCableMatingPropsInput = z.input<
+ typeof assemblyCableMatingProps
+>
--- a/lib/components/primitive-components/AssemblyCable.ts
+++ b/lib/components/primitive-components/AssemblyCable.ts
@@ -1,15 +1,18 @@
import type { CadCable } from "circuit-json"
-import { assemblyCableProps } from "@tscircuit/props"
+import { assemblyCableMatingProps } from "./assembly-cable-mating-props"
import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
import { resolveAssemblyCable } from "./resolve-assembly-cable"
export class AssemblyCable extends PrimitiveComponent<
- typeof assemblyCableProps
+ typeof assemblyCableMatingProps
> {
private cadCableId?: CadCable["cad_cable_id"]
get config() {
- return { componentName: "AssemblyCable", zodProps: assemblyCableProps }
+ return {
+ componentName: "AssemblyCable",
+ zodProps: assemblyCableMatingProps,
+ }
}
doInitialCadModelRender(): void {
--- a/lib/components/primitive-components/resolve-assembly-cable.ts
+++ b/lib/components/primitive-components/resolve-assembly-cable.ts
@@ -26,11 +26,13 @@
cable,
cable._parsedProps.from,
explicitDefinition?.connectorA,
+ cable._parsedProps.fromMatingPoint,
)
const to = resolveAssemblyCableEndpoint(
cable,
cable._parsedProps.to,
explicitDefinition?.connectorB,
+ cable._parsedProps.toMatingPoint,
)
const fromDefinition =
explicitDefinition ?? getCableDefinition(from.cableInput!)
--- a/lib/components/primitive-components/resolve-assembly-cable-endpoint.ts
+++ b/lib/components/primitive-components/resolve-assembly-cable-endpoint.ts
@@ -1,3 +1,4 @@
+import type { AssemblyCableMatingPoint } from "./assembly-cable-mating-props"
import type { CableConnector, CableInput } from "@tscircuit/cableprinter"
import { getJstMotorConnector } from "@tscircuit/modelprinter"
import type { Point3, SourceComponentBase } from "circuit-json"
@@ -36,8 +37,37 @@
cable: AssemblyCable,
selector: string,
cableConnector?: CableConnector,
+ matingPoint?: AssemblyCableMatingPoint,
): AssemblyCableEndpoint {
const scoped = getComponentsInAssemblyScope(cable)
+ if (matingPoint) {
+ if (!cableConnector)
+ throw new Error(
+ `assembly.cable "${cable.name}" requires an explicit model when a mating point is provided`,
+ )
+ const matches = selectAll(
+ preprocessSelector(selector, cable),
+ getAssemblyScope(cable),
+ {
+ adapter: cssSelectPrimitiveComponentAdapter,
+ },
+ ).filter((part) => scoped.includes(part))
+ if (matches.length !== 1 || !matches[0]?.source_component_id)
+ throw new Error(
+ `assembly.cable "${cable.name}" endpoint "${selector}" must match exactly one rendered source component`,
+ )
+ const direction = vec3.normalize(vec3.create(), [
+ matingPoint.direction.x,
+ matingPoint.direction.y,
+ matingPoint.direction.z,
+ ])
+ return {
+ sourceComponentId: matches[0].source_component_id,
+ position: matingPoint.position,
+ pin1Position: matingPoint.pin1Position,
+ direction: { x: direction[0], y: direction[1], z: direction[2] },
+ }
+ }
const motorReference = selector.match(/^(.+)\.wireside$/)
if (motorReference) {
const motors = scoped.filter(
--- a/lib/namespaced-elements/assembly.ts
+++ b/lib/namespaced-elements/assembly.ts
@@ -1,5 +1,5 @@
+import type { AssemblyCableMatingPropsInput } from "../components/primitive-components/assembly-cable-mating-props"
import type {
- AssemblyCablePropsInput,
AssemblyDevicePropsInput,
AssemblyMotorPropsInput,
AssemblyPrintedPartPropsInput,
@@ -26,7 +26,7 @@
extends AssemblySubassemblyPropsInput {}
export type AssemblyCadAssemblyJsxProps = AssemblySubassemblyJsxProps
-export interface AssemblyCableJsxProps extends AssemblyCablePropsInput {}
+export interface AssemblyCableJsxProps extends AssemblyCableMatingPropsInput {}
export const assembly = {
cable: createNamespacedElement<AssemblyCableJsxProps>("assembly.cable"),
--- /dev/null
+++ b/tests/assembly/assembly-cable-mating-points.test.tsx
@@ -0,0 +1,72 @@
+import { expect, test } from "bun:test"
+import "lib/register-catalogue"
+import { assembly } from "lib"
+import { RootCircuit } from "lib/RootCircuit"
+import { expectAssemblySnapshot } from "./fixtures/expect-assembly-snapshot"
+
+test("explicit mating points connect custom CAD owners without a dummy PCB", async () => {
+ const circuit = new RootCircuit()
+ circuit.add(
+ <assembly.device>
+ <assembly.subassembly
+ name="MOTOR"
+ cadModel={{
+ jscad: { type: "cuboid", size: [28, 24, 24] },
+ positionOffset: { x: 0, y: 88, z: 12 },
+ }}
+ />
+ <assembly.subassembly
+ name="CONTROLLER"
+ cadModel={{
+ jscad: { type: "cuboid", size: [40, 20, 1.6] },
+ positionOffset: { x: 0, y: 10, z: 0 },
+ }}
+ />
+ <assembly.cable
+ name="MOTOR_LEAD"
+ from=".MOTOR"
+ to=".CONTROLLER"
+ model="adaptercable_a(bullet1_d3.5mm_gfemale)_b(bullet1_d4mm_gmale)"
+ fromMatingPoint={{
+ position: { x: 0, y: 76, z: 8.3 },
+ direction: { x: 0, y: -5, z: 0 },
+ }}
+ toMatingPoint={{
+ position: { x: 0, y: 20, z: 8.3 },
+ direction: { x: 0, y: 1, z: 0 },
+ }}
+ />
+ </assembly.device>,
+ )
+ await circuit.renderUntilSettled()
+ const cable = circuit.db.cad_cable.list()[0]!
+ const owners = circuit.db.source_component.list()
+ expect(cable.from_source_component_id).toBe(
+ owners.find((p) => p.name === "MOTOR")!.source_component_id,
+ )
+ expect(cable.to_source_component_id).toBe(
+ owners.find((p) => p.name === "CONTROLLER")!.source_component_id,
+ )
+ expect(circuit.db.pcb_board.list()).toHaveLength(0)
+ expect(circuit.db.pcb_component.list()).toHaveLength(0)
+ expect(cable.path[0]!.y).toBeCloseTo(63.75, 5)
+ expect(cable.path.at(-1)!.y).toBeCloseTo(34, 5)
+ expect(cable.path.every((p) => Math.abs(p.x) < 1e-6)).toBe(true)
+ expect(cable.path.every((p) => p.z >= 8.3 - 1e-5)).toBe(true)
+ await expectAssemblySnapshot(import.meta.path, {
+ title: "Explicit world-space mating points on imported/custom CAD",
+ font: "alphabet",
+ panels: [
+ {
+ title: "3.5 mm female to 4 mm male",
+ code: '<assembly.cable from=".MOTOR" to=".CONTROLLER"\n model="adaptercable_a(bullet1_d3.5mm_gfemale)_b(bullet1_d4mm_gmale)"\n fromMatingPoint={motorFace}\n toMatingPoint={socketTip} />',
+ annotation:
+ "Native cable; actual source owners; no fake board or connector",
+ circuit,
+ renderOptions: {
+ poppygl: { camPos: [90, 90, 150], lookAt: [0, 8, 48], fov: 45 },
+ },
+ },
+ ],
+ })
+})
--- /dev/null
+++ b/tests/assembly/assembly-cable-mating-validation.test.tsx
@@ -0,0 +1,40 @@
+import { expect, test } from "bun:test"
+import "lib/register-catalogue"
+import { assembly } from "lib"
+import { RootCircuit } from "lib/RootCircuit"
+
+test("mating points reject zero directions, missing owners, and a missing model", async () => {
+ for (const invalid of [
+ "zero_direction",
+ "missing_owner",
+ "missing_model",
+ ] as const) {
+ const circuit = new RootCircuit()
+ circuit.add(
+ <assembly.device>
+ <assembly.subassembly name="MOTOR" model="nema17" />
+ <assembly.subassembly name="CONTROLLER" model="nema17" />
+ <assembly.cable
+ name="CABLE"
+ from={invalid === "missing_owner" ? ".MISSING" : ".MOTOR"}
+ to=".CONTROLLER"
+ model={
+ invalid === "missing_model"
+ ? undefined
+ : "adaptercable_a(bullet1_d3.5mm_gfemale)_b(bullet1_d4mm_gmale)"
+ }
+ fromMatingPoint={{
+ position: { x: 0, y: 70, z: 8 },
+ direction: { x: 0, y: invalid === "zero_direction" ? 0 : -1, z: 0 },
+ }}
+ toMatingPoint={{
+ position: { x: 0, y: 20, z: 8 },
+ direction: { x: 0, y: 1, z: 0 },
+ }}
+ />
+ </assembly.device>,
+ )
+ await expect(circuit.renderUntilSettled()).rejects.toThrow()
+ expect(circuit.db.cad_cable.list()).toHaveLength(0)
+ }
+})