gokul/usb3-sd-hub

Engineering draft: four-layer, computer-powered USB 3 hub with one USB-C host socket, two USB-C and two USB-A accessory sockets, and a full-size SD slot. Board and three schematic previews are available. Routing and production checks are incomplete; not fabrication ready.

Version
0.1.5
License
MIT
Stars
0

design/CompactPairRouter.ts

import { AutoroutingPipelineSolver9_PreloadedTraceGraph } from "@tscircuit/capacity-autorouter";

type RoutingProblem = ConstructorParameters<
	typeof AutoroutingPipelineSolver9_PreloadedTraceGraph
>[0];
type RoutingOptions = ConstructorParameters<
	typeof AutoroutingPipelineSolver9_PreloadedTraceGraph
>[1];

// Signals are routed on top/bottom copper; the two inner ground references
// remain available for return currents. Through-via barrels occupy all four
// copper layers. Each two-wire phase uses a bounded search region. Copper obstacles and
// electrical constraints remain in the input; the output cannot leave this
// region. Obstacles wholly outside that region plus a 1 mm safety band cannot
// meet any in-bounds trace or via, and are excluded from the local search only.
// The remaining board connections use the full board and all its obstacles.
export class CompactPairRouter extends AutoroutingPipelineSolver9_PreloadedTraceGraph {
	private readonly boardBounds: RoutingProblem["bounds"];
	private readonly boardLayerCount: number;
	constructor(srj: RoutingProblem, opts?: RoutingOptions) {
		const pairPoints = srj.connections.flatMap(
			(connection) => connection.pointsToConnect,
		);
		const isSinglePair =
			srj.connections.length === 2 && srj.differentialPairs?.length === 1;
		const marginMm = 6;
		const bounds =
			isSinglePair && pairPoints.length === 4
				? {
						minX: Math.max(
							srj.bounds.minX,
							Math.min(...pairPoints.map((point) => point.x)) - marginMm,
						),
						maxX: Math.min(
							srj.bounds.maxX,
							Math.max(...pairPoints.map((point) => point.x)) + marginMm,
						),
						minY: Math.max(
							srj.bounds.minY,
							Math.min(...pairPoints.map((point) => point.y)) - marginMm,
						),
						maxY: Math.min(
							srj.bounds.maxY,
							Math.max(...pairPoints.map((point) => point.y)) + marginMm,
						),
					}
				: srj.bounds;
		const outerLayers = ["top", "bottom"];
		super(
			{
				...srj,
				bounds,
				layerCount: 2,
				allowBlindAndBuriedVias: false,
				obstacles: srj.obstacles
					.filter((obstacle) => {
						if (!isSinglePair) return true;
						const obstacleRadius =
							Math.hypot(obstacle.width / 2, obstacle.height / 2) + 1;
						return (
							obstacle.center.x + obstacleRadius >= bounds.minX &&
							obstacle.center.x - obstacleRadius <= bounds.maxX &&
							obstacle.center.y + obstacleRadius >= bounds.minY &&
							obstacle.center.y - obstacleRadius <= bounds.maxY
						);
					})
					.map((obstacle) => ({
						...obstacle,
						layers: obstacle.layers.filter((layer) =>
							outerLayers.includes(layer),
						),
					}))
					.filter((obstacle) => obstacle.layers.length > 0),
				connections: srj.connections.map((connection) => ({
					...connection,
					pointsToConnect: connection.pointsToConnect.map((point) =>
						point.layers
							? {
									...point,
									layers: point.layers.filter((layer) =>
										outerLayers.includes(layer),
									),
								}
							: point,
					),
				})),
			},
			opts,
		);
		this.boardBounds = srj.bounds;
		this.boardLayerCount = srj.layerCount;
	}
	override getOutputSimplifiedPcbTraces() {
		return super
			.getOutputSimplifiedPcbTraces()
			.map((trace) => ({
				...trace,
				route: trace.route.map((point) =>
					point.route_type === "via"
						? { ...point, layers: ["top", "inner1", "inner2", "bottom"] }
						: point,
				),
			}));
	}
	override getOutputSimpleRouteJson() {
		return {
			...super.getOutputSimpleRouteJson(),
			bounds: this.boardBounds,
			layerCount: this.boardLayerCount,
		};
	}
}