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.4
- License
- unset
- 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,
};
}
}