From e58a6272b094199a1c944bedf0841aaa6ca64c6b Mon Sep 17 00:00:00 2001 From: seveibar Date: Tue, 3 Sep 2024 20:05:06 -0700 Subject: [PATCH] Autorouter same-net detection improvements --- .../base-components/NormalComponent.ts | 13 +- lib/components/primitive-components/Trace.ts | 131 +++++++++++------- .../findPossibleTraceLayerCombinations.ts | 2 +- lib/utils/autorouting/mergeRoutes.ts | 1 + lib/utils/components/createNetsFromProps.ts | 15 ++ tests/__snapshots__/example1.snap.svg | 2 +- .../__snapshots__/trace-pcb.snap.svg | 2 +- 7 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 lib/utils/components/createNetsFromProps.ts diff --git a/lib/components/base-components/NormalComponent.ts b/lib/components/base-components/NormalComponent.ts index 5c2811c..0bcf015 100644 --- a/lib/components/base-components/NormalComponent.ts +++ b/lib/components/base-components/NormalComponent.ts @@ -17,6 +17,7 @@ import { import { getPortFromHints } from "lib/utils/getPortFromHints" import { createComponentsFromSoup } from "lib/utils/createComponentsFromSoup" import { Net } from "../primitive-components/Net" +import { createNetsFromProps } from "lib/utils/components/createNetsFromProps" export type PortMap = { [K in T]: Port @@ -309,17 +310,7 @@ export class NormalComponent< } _createNetsFromProps(propsWithConnections: (string | undefined | null)[]) { - for (const prop of propsWithConnections) { - if (typeof prop === "string" && prop.startsWith("net.")) { - if (!this.getSubcircuit().selectOne(prop)) { - this.getSubcircuit().add( - new Net({ - name: prop.split(".")[1], - }), - ) - } - } - } + createNetsFromProps(this, propsWithConnections) } /** diff --git a/lib/components/primitive-components/Trace.ts b/lib/components/primitive-components/Trace.ts index 760a620..d1c43f7 100644 --- a/lib/components/primitive-components/Trace.ts +++ b/lib/components/primitive-components/Trace.ts @@ -22,12 +22,16 @@ import type { import { computeObstacleBounds } from "lib/utils/autorouting/computeObstacleBounds" import { projectPointInDirection } from "lib/utils/projectPointInDirection" import type { TraceHint } from "./TraceHint" -import { findPossibleTraceLayerCombinations } from "lib/utils/autorouting/findPossibleTraceLayerCombinations" +import { + findPossibleTraceLayerCombinations, + type CandidateTraceLayerCombination, +} from "lib/utils/autorouting/findPossibleTraceLayerCombinations" import { pairs } from "lib/utils/pairs" import { mergeRoutes } from "lib/utils/autorouting/mergeRoutes" import type { Net } from "./Net" import { getClosest } from "lib/utils/getClosest" import { z } from "zod" +import { createNetsFromProps } from "lib/utils/components/createNetsFromProps" type PcbRouteObjective = | RouteHintPoint @@ -144,20 +148,47 @@ export class Trace extends PrimitiveComponent { } } - _findConnectedNets(): Array<{ selector: string; net: Net }> { - const nets = this.getTracePathNetSelectors().map((selector) => ({ - selector, - net: this.getSubcircuit().selectOne(selector, { type: "net" }) as Net, - })) + _findConnectedNets(): { + nets: Net[] + netsWithSelectors: Array<{ selector: string; net: Net }> + } { + const netsWithSelectors = this.getTracePathNetSelectors().map( + (selector) => ({ + selector, + net: this.getSubcircuit().selectOne(selector, { type: "net" }) as Net, + }), + ) - const undefinedNets = nets.filter((n) => !n.net) + const undefinedNets = netsWithSelectors.filter((n) => !n.net) if (undefinedNets.length > 0) { this.renderError( `Could not find net for selector "${undefinedNets[0].selector}" inside ${this}`, ) } - return nets + return { netsWithSelectors, nets: netsWithSelectors.map((n) => n.net) } + } + + /** + * Get all the traces that are connected in any degree to this trace, this is + * used during autorouting to routes to pass through traces connected to the + * same net. + */ + _getAllTracesConnectedToSameNet(): Trace[] { + const traces = this.getSubcircuit().selectAll("trace") as Trace[] + + const myNets = this._findConnectedNets().nets + const myPorts = this._findConnectedPorts().ports ?? [] + + return traces.filter((t) => { + if (t === this) return false + const tNets = t._findConnectedNets().nets + const tPorts = t._findConnectedPorts().ports ?? [] + return ( + tNets.some((n) => myNets.includes(n)) || + tPorts.some((p) => myPorts.includes(p)) + ) + }) } /** @@ -175,10 +206,14 @@ export class Trace extends PrimitiveComponent { * Determine if a trace is explicitly connected to a net (not via a port) */ _isExplicitlyConnectedToNet(net: Net) { - const nets = this._findConnectedNets().map((n) => n.net) + const nets = this._findConnectedNets().nets return nets.includes(net) } + doInitialCreateNetsFromProps(): void { + createNetsFromProps(this, this.getTracePathNetSelectors()) + } + doInitialSourceTraceRender(): void { const { db } = this.project! const { _parsedProps: props, parent } = this @@ -192,11 +227,11 @@ export class Trace extends PrimitiveComponent { this._findConnectedPorts() if (!allPortsFound) return - const nets = this._findConnectedNets() + const nets = this._findConnectedNets().nets const trace = db.source_trace.insert({ connected_source_port_ids: ports.map((p) => p.port.source_port_id!), - connected_source_net_ids: nets.map((n) => n.net.source_net_id!), + connected_source_net_ids: nets.map((n) => n.source_net_id!), }) this.source_trace_id = trace.source_trace_id @@ -213,7 +248,7 @@ export class Trace extends PrimitiveComponent { if (!allPortsFound) return - const nets = this._findConnectedNets() + const nets = this._findConnectedNets().netsWithSelectors if (ports.length === 0 && nets.length === 2) { // Find the two optimal points to connect the two nets @@ -254,7 +289,8 @@ export class Trace extends PrimitiveComponent { elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || - elm.type === "pcb_port", + elm.type === "pcb_port" || + elm.type === "source_trace", ) const source_trace = db.source_trace.get(this.source_trace_id!)! @@ -296,44 +332,24 @@ export class Trace extends PrimitiveComponent { return } - if (pcbRouteHints.length === 0) { - const { solution } = autoroute( - pcbElements.concat([ - { - ...source_trace, - // manually override b/c some of the ports may be connected via nets - // so they don't appear properly in the source_trace, we don't need - // to do this if the algorithm correctly looks at connected_source_net_ids - connected_source_port_ids: ports.map((p) => p.source_port_id!), - } as SourceTrace, - ]), - ) - // TODO for some reason, the solution gets duplicated inside ijump-astar - const inputPcbTrace = solution[0] - - if (!inputPcbTrace) { - // TODO render error indicating we could not find a route - console.log( - `Failed to find route from ${ports[0]} to ${ports[1]} (TODO render error!)`, - ) - return - } - const pcb_trace = db.pcb_trace.insert(inputPcbTrace as any) + let orderedRouteObjectives: PcbRouteObjective[] = [] - this.pcb_trace_id = pcb_trace.pcb_trace_id - this._portsRoutedOnPcb = ports - return + if (pcbRouteHints.length === 0) { + orderedRouteObjectives = [ + portToObjective(ports[0]), + portToObjective(ports[1]), + ] + } else { + // When we have hints, we have to order the hints then route between each + // terminal of the trace and the hints + // TODO order based on proximity to ports + orderedRouteObjectives = [ + portToObjective(ports[0]), + ...pcbRouteHints, + portToObjective(ports[1]), + ] } - // When we have hints, we have to order the hints then route between each - // terminal of the trace and the hints - // TODO order based on proximity to ports - const orderedRouteObjectives: PcbRouteObjective[] = [ - portToObjective(ports[0]), - ...pcbRouteHints, - portToObjective(ports[1]), - ] - // Hints can indicate where there should be a via, but the layer is allowed // to be unspecified, therefore we need to find possible layer combinations // to go to each hint and still route to the start and end points @@ -356,6 +372,19 @@ export class Trace extends PrimitiveComponent { this.source_trace_id!, ) + const allConnectedTraceIds = this._getAllTracesConnectedToSameNet().map( + (t) => t.source_trace_id, + ) + for (const obstacle of obstacles) { + if ( + obstacle.connectedTo.some((connection) => + allConnectedTraceIds.includes(connection), + ) + ) { + obstacle.connectedTo.push(this.source_trace_id!) + } + } + // TODO explore all candidate layer combinations if one fails const candidateLayerSelections = candidateLayerCombinations[0].layer_path @@ -377,6 +406,7 @@ export class Trace extends PrimitiveComponent { for (const [a, b] of pairs(orderedRoutePoints)) { const BOUNDS_MARGIN = 2 //mm const ijump = new IJumpAutorouter({ + OBSTACLE_MARGIN: 0.3, input: { obstacles, connections: [ @@ -396,8 +426,8 @@ export class Trace extends PrimitiveComponent { }) const traces = ijump.solveAndMapToTraces() if (traces.length === 0) { - this.renderError( - `Could not find a route between ${a.x}, ${a.y} and ${b.x}, ${b.y}`, + console.log( + `Could not find a route between ${a} and ${b} for ${this} (TODO render error)`, ) return } @@ -410,6 +440,7 @@ export class Trace extends PrimitiveComponent { route: mergeRoutes(routes), source_trace_id: this.source_trace_id!, }) + this._portsRoutedOnPcb = ports this.pcb_trace_id = pcb_trace.pcb_trace_id } diff --git a/lib/utils/autorouting/findPossibleTraceLayerCombinations.ts b/lib/utils/autorouting/findPossibleTraceLayerCombinations.ts index 23cfd35..b9ba6cb 100644 --- a/lib/utils/autorouting/findPossibleTraceLayerCombinations.ts +++ b/lib/utils/autorouting/findPossibleTraceLayerCombinations.ts @@ -13,7 +13,7 @@ const LAYER_SELECTION_PREFERENCE = ["top", "bottom", "inner1", "inner2"] * top -> bottom -> bottom -> top * bottom -> top -> top -> bottom */ -interface CandidateTraceLayerCombination { +export interface CandidateTraceLayerCombination { layer_path: string[] } diff --git a/lib/utils/autorouting/mergeRoutes.ts b/lib/utils/autorouting/mergeRoutes.ts index 476f5cf..a39f271 100644 --- a/lib/utils/autorouting/mergeRoutes.ts +++ b/lib/utils/autorouting/mergeRoutes.ts @@ -11,6 +11,7 @@ function pdist(a: any, b: any) { * reverse the next route and append it to the previous route. */ export const mergeRoutes = (routes: PCBTrace["route"][]) => { + if (routes.length === 1) return routes[0] // routes = routes.filter((route) => route.length > 0) if (routes.some((r) => r.length === 0)) { throw new Error("Cannot merge routes with zero length") diff --git a/lib/utils/components/createNetsFromProps.ts b/lib/utils/components/createNetsFromProps.ts new file mode 100644 index 0000000..7e31b32 --- /dev/null +++ b/lib/utils/components/createNetsFromProps.ts @@ -0,0 +1,15 @@ +import type { PrimitiveComponent } from "lib/components/base-components/PrimitiveComponent" +import { Net } from "lib/components/primitive-components/Net" + +export const createNetsFromProps = ( + component: PrimitiveComponent, + props: (string | undefined | null)[], +) => { + for (const prop of props) { + if (typeof prop === "string" && prop.startsWith("net.")) { + if (!component.getSubcircuit().selectOne(prop)) { + component.getSubcircuit().add(new Net({ name: prop.split("net.")[1] })) + } + } + } +} diff --git a/tests/__snapshots__/example1.snap.svg b/tests/__snapshots__/example1.snap.svg index 063d97a..ded54b1 100644 --- a/tests/__snapshots__/example1.snap.svg +++ b/tests/__snapshots__/example1.snap.svg @@ -8,4 +8,4 @@ .pcb-silkscreen { fill: none; } .pcb-silkscreen-top { stroke: #f2eda1; } .pcb-silkscreen-bottom { stroke: #f2eda1; } - \ No newline at end of file + \ No newline at end of file diff --git a/tests/components/primitive-components/__snapshots__/trace-pcb.snap.svg b/tests/components/primitive-components/__snapshots__/trace-pcb.snap.svg index d5435f7..d492472 100644 --- a/tests/components/primitive-components/__snapshots__/trace-pcb.snap.svg +++ b/tests/components/primitive-components/__snapshots__/trace-pcb.snap.svg @@ -8,4 +8,4 @@ .pcb-silkscreen { fill: none; } .pcb-silkscreen-top { stroke: #f2eda1; } .pcb-silkscreen-bottom { stroke: #f2eda1; } - \ No newline at end of file + \ No newline at end of file