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)
+  }
+})