pixalynx/edge-connector

Configures USB-A, Micro-USB, and USB-C PCB-edge connectors with exposed copper contacts and automatically shaped, positioned board tongues.

Version
1.0.0
License
GPL-3.0-only
Stars
0

UsbEdgeConnectorBoard.tsx

import type { ComponentProps } from "react"
import { UsbAEdgeConnector, usbAEdgeConnectorGeometry, type UsbAEdgeConnectorProps } from "./UsbAEdgeConnector"
import { UsbCEdgeConnector, usbCEdgeConnectorGeometry, type UsbCEdgeConnectorProps } from "./UsbCEdgeConnector"
import { MicroUsbEdgeConnector, microUsbEdgeConnectorGeometry, type MicroUsbEdgeConnectorProps } from "./MicroUsbEdgeConnector"

export type UsbEdgeConnectorKind = "usb-a" | "usb-c" | "micro-usb"
export type UsbBoardEdge = "top" | "right" | "bottom" | "left"

type PositionedProps = "pcbX" | "pcbY" | "pcbRotation" | "pcbPositionAnchor" | "pcbRelativeTo" | "pcbLayout"
type BoardOptions = Omit<
  ComponentProps<"board">,
  PositionedProps | "width" | "height" | "outline" | "thickness" |
  "outlineOffsetX" | "outlineOffsetY" | "boardAnchorPosition" |
  "boardAnchorAlignment" | "anchorAlignment" | "borderRadius"
> & {
  /** Overall board bounds in millimetres, including the tongue. */
  width: number
  height: number
  edge?: UsbBoardEdge
  /** Millimetres along the edge: positive is +X on top/bottom, +Y on left/right. */
  offset?: number
}

export type UsbEdgeConnectorBoardProps = BoardOptions & (
  | { connector: "usb-a"; connectorProps: Omit<UsbAEdgeConnectorProps, PositionedProps> }
  | { connector: "usb-c"; connectorProps: Omit<UsbCEdgeConnectorProps, PositionedProps> }
  | { connector: "micro-usb"; connectorProps: Omit<MicroUsbEdgeConnectorProps, PositionedProps> }
)

const geometries = {
  "usb-a": usbAEdgeConnectorGeometry,
  "usb-c": usbCEdgeConnectorGeometry,
  "micro-usb": microUsbEdgeConnectorGeometry,
}

/** Internal layout calculation shared by the wrapper and its geometry tests. */
export function getUsbEdgeBoardLayout({ connector, width, height, edge = "top", offset = 0 }: {
  connector: UsbEdgeConnectorKind
  width: number
  height: number
  edge?: UsbBoardEdge
  offset?: number
}) {
  if (![width, height, offset].every(Number.isFinite) || width <= 0 || height <= 0) {
    throw new Error("USB edge board width and height must be positive millimetres; offset must be finite.")
  }
  const geometry = geometries[connector]
  if (!geometry) throw new Error(`Unknown USB edge connector: ${connector}`)
  const rotations = { top: 0, left: 90, bottom: 180, right: 270 } as const
  const rotation = rotations[edge]
  if (rotation === undefined) throw new Error(`Unknown board edge: ${edge}`)
  const sideways = edge === "left" || edge === "right"
  const localWidth = sideways ? height : width
  const localHeight = sideways ? width : height
  const localOffset = edge === "bottom" || edge === "right" ? -offset : offset
  const connectorY = localHeight / 2 - geometry.tipY
  const profile = geometry.edgeProfile.map(({ x, y }) => ({ x: x + localOffset, y: y + connectorY }))
  if (profile.some(p => Math.abs(p.x) > localWidth / 2 - 0.2)) {
    throw new Error("Connector profile does not fit this edge. Increase the board size or reduce offset (0.2 mm shoulder margin required).")
  }
  if (Math.min(...profile.map(p => p.y)) <= -localHeight / 2 + 0.2) {
    throw new Error("Board is too short for the connector tongue and reliefs. Increase the board size.")
  }
  const rotate = ({ x, y }: { x: number; y: number }) => {
    switch (edge) {
      case "top": return { x, y }
      case "left": return { x: -y, y: x }
      case "bottom": return { x: -x, y: -y }
      case "right": return { x: y, y: -x }
    }
  }
  const position = rotate({ x: localOffset, y: connectorY })
  return {
    outline: [
      { x: -localWidth / 2, y: -localHeight / 2 },
      { x: -localWidth / 2, y: profile[0].y },
      ...profile,
      { x: localWidth / 2, y: profile[profile.length - 1].y },
      { x: localWidth / 2, y: -localHeight / 2 },
    ].map(rotate),
    thickness: geometry.boardThickness,
    minBoardEdgeClearance: connector === "usb-c" ? 0.15 : 0.2,
    placement: { pcbX: position.x, pcbY: position.y, pcbRotation: rotation },
  }
}

/** A rectangular carrier with one automatically shaped and placed USB edge connector. */
export function UsbEdgeConnectorBoard(props: UsbEdgeConnectorBoardProps) {
  const { connector, connectorProps, width, height, edge, offset, children, ...boardProps } = props
  const layout = getUsbEdgeBoardLayout({ connector, width, height, edge, offset })
  const contact = connector === "usb-a"
    ? <UsbAEdgeConnector {...connectorProps} {...layout.placement} />
    : connector === "usb-c"
      ? <UsbCEdgeConnector {...connectorProps} {...layout.placement} />
      : <MicroUsbEdgeConnector {...connectorProps} {...layout.placement} />
  return (
    <board
      minBoardEdgeClearance={layout.minBoardEdgeClearance}
      {...boardProps}
      width={width}
      height={height}
      outline={layout.outline}
      thickness={layout.thickness}
    >
      {contact}
      {children}
    </board>
  )
}