@tscircuit/core 0.0.23 → 0.0.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -50,14 +50,14 @@ export abstract class PrimitiveComponent<
50
50
  externallyAddedAliases: string[]
51
51
 
52
52
  /**
53
- * An opaque group is a self-contained subcircuit. All the selectors inside
54
- * an opaque group are relative to the group. You can have multiple opaque
55
- * groups and their selectors will not interact with each other (even if the
53
+ * An subcircuit is self-contained. All the selectors inside
54
+ * a subcircuit are relative to the subcircuit group. You can have multiple
55
+ * subcircuits and their selectors will not interact with each other (even if the
56
56
  * components share the same names) unless you explicitly break out some ports
57
57
  */
58
- get isOpaqueGroup() {
58
+ get isSubcircuit() {
59
59
  return (
60
- Boolean(this.props.opaque) ||
60
+ Boolean(this.props.subcircuit) ||
61
61
  // Implied opaque group for top-level group
62
62
  (this.lowercaseComponentName === "group" &&
63
63
  this?.parent?.props?.name === "$root")
@@ -212,15 +212,15 @@ export abstract class PrimitiveComponent<
212
212
  component.shouldBeRemoved = true
213
213
  }
214
214
 
215
- getOpaqueGroupSelector(): string {
215
+ getSubcircuitSelector(): string {
216
216
  const name = this._parsedProps.name
217
217
  const endPart = name
218
218
  ? `${this.lowercaseComponentName}.${name}`
219
219
  : this.lowercaseComponentName
220
220
 
221
221
  if (!this.parent) return endPart
222
- if (this.parent.isOpaqueGroup) return endPart
223
- return `${this.parent.getOpaqueGroupSelector()} > ${endPart}`
222
+ if (this.parent.isSubcircuit) return endPart
223
+ return `${this.parent.getSubcircuitSelector()} > ${endPart}`
224
224
  }
225
225
 
226
226
  getFullPathSelector(): string {
@@ -266,9 +266,9 @@ export abstract class PrimitiveComponent<
266
266
  return false
267
267
  }
268
268
 
269
- getOpaqueGroup(): PrimitiveComponent {
270
- if (this.isOpaqueGroup) return this
271
- const group = this.parent?.getOpaqueGroup()
269
+ getSubcircuit(): PrimitiveComponent {
270
+ if (this.isSubcircuit) return this
271
+ const group = this.parent?.getSubcircuit()
272
272
  if (!group)
273
273
  throw new Error("Component is not inside an opaque group (no board?)")
274
274
  return group
@@ -349,6 +349,9 @@ export abstract class PrimitiveComponent<
349
349
  if (parent?.props?.name && props?.name) {
350
350
  return `<${cname}#${this._renderId}(.${parent?.props.name}>.${props?.name}) />`
351
351
  }
352
+ if (props?.from && props?.to) {
353
+ return `<${cname}#${this._renderId}(from:${props.from} to:${props?.to}) />`
354
+ }
352
355
  if (props?.name) {
353
356
  return `<${cname}#${this._renderId} name=".${props?.name}" />`
354
357
  }
@@ -3,6 +3,7 @@ import { Component, createElement, type ReactElement } from "react"
3
3
 
4
4
  export const orderedRenderPhases = [
5
5
  "ReactSubtreesRender", // probably going to be removed b/c subtrees should render instantly
6
+ "CreateNetsFromProps",
6
7
  "CreateTracesFromProps",
7
8
  "SourceRender",
8
9
  "SourceParentAttachment",
@@ -19,6 +20,7 @@ export const orderedRenderPhases = [
19
20
  "PcbParentAttachment",
20
21
  "PcbLayout",
21
22
  "PcbTraceRender",
23
+ "PcbRouteNetIslands",
22
24
  "CadModelRender",
23
25
  "PcbAnalysis",
24
26
  ] as const
@@ -2,11 +2,12 @@ import { boardProps } from "@tscircuit/props"
2
2
  import type { z } from "zod"
3
3
  import { NormalComponent } from "../base-components/NormalComponent"
4
4
  import { identity, type Matrix } from "transformation-matrix"
5
+ import { Group } from "../primitive-components/Group"
5
6
 
6
- export class Board extends NormalComponent<typeof boardProps> {
7
+ export class Board extends Group<typeof boardProps> {
7
8
  pcb_board_id: string | null = null
8
9
 
9
- get isOpaqueGroup() {
10
+ get isSubcircuit() {
10
11
  return true
11
12
  }
12
13
 
@@ -29,17 +29,24 @@ export class Capacitor extends NormalComponent<
29
29
  pin1 = this.portMap.pin1
30
30
  pin2 = this.portMap.pin2
31
31
 
32
+ doInitialCreateNetsFromProps() {
33
+ this._createNetsFromProps([
34
+ this.props.decouplingFor,
35
+ this.props.decouplingTo,
36
+ ])
37
+ }
38
+
32
39
  doInitialCreateTracesFromProps() {
33
40
  if (this.props.decouplingFor && this.props.decouplingTo) {
34
41
  this.add(
35
42
  new Trace({
36
- from: `${this.getOpaqueGroupSelector()} > port.1`,
43
+ from: `${this.getSubcircuitSelector()} > port.1`,
37
44
  to: this.props.decouplingFor,
38
45
  }),
39
46
  )
40
47
  this.add(
41
48
  new Trace({
42
- from: `${this.getOpaqueGroupSelector()} > port.2`,
49
+ from: `${this.getSubcircuitSelector()} > port.2`,
43
50
  to: this.props.decouplingTo,
44
51
  }),
45
52
  )
@@ -4,6 +4,7 @@ import { NormalComponent } from "../base-components/NormalComponent"
4
4
  import type { SourceSimpleResistorInput } from "@tscircuit/soup"
5
5
  import { z } from "zod"
6
6
  import { Trace } from "../primitive-components/Trace"
7
+ import { Net } from "../primitive-components/Net"
7
8
 
8
9
  export class Resistor extends NormalComponent<
9
10
  typeof resistorProps,
@@ -20,22 +21,27 @@ export class Resistor extends NormalComponent<
20
21
  pin1 = this.portMap.pin1
21
22
  pin2 = this.portMap.pin2
22
23
 
24
+ doInitialCreateNetsFromProps() {
25
+ this._createNetsFromProps([this.props.pullupFor, this.props.pullupTo])
26
+ }
27
+
23
28
  doInitialCreateTracesFromProps() {
24
29
  if (this.props.pullupFor && this.props.pullupTo) {
25
30
  this.add(
26
31
  new Trace({
27
- from: `${this.getOpaqueGroupSelector()} > port.1`,
32
+ from: `${this.getSubcircuitSelector()} > port.1`,
28
33
  to: this.props.pullupFor,
29
34
  }),
30
35
  )
31
36
  this.add(
32
37
  new Trace({
33
- from: `${this.getOpaqueGroupSelector()} > port.2`,
38
+ from: `${this.getSubcircuitSelector()} > port.2`,
34
39
  to: this.props.pullupTo,
35
40
  }),
36
41
  )
37
42
  }
38
43
  }
44
+
39
45
  doInitialSourceRender() {
40
46
  const { db } = this.project!
41
47
  const { _parsedProps: props } = this
@@ -1,11 +1,15 @@
1
1
  import { groupProps } from "@tscircuit/props"
2
2
  import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
3
3
  import { compose, identity } from "transformation-matrix"
4
+ import { z } from "zod"
5
+ import { NormalComponent } from "../base-components/NormalComponent"
4
6
 
5
- export class Group extends PrimitiveComponent<typeof groupProps> {
7
+ export class Group<
8
+ Props extends z.ZodType<any, any, any> = typeof groupProps,
9
+ > extends NormalComponent<Props> {
6
10
  get config() {
7
11
  return {
8
- zodProps: groupProps,
12
+ zodProps: groupProps as unknown as Props,
9
13
  }
10
14
  }
11
15
  }
@@ -1,12 +1,171 @@
1
1
  import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
2
2
  import { z } from "zod"
3
+ import type { Port } from "./Port"
4
+ import type { Trace } from "./Trace"
5
+ import { pairs } from "lib/utils/pairs"
6
+ import type { AnySoupElement, SourceTrace } from "@tscircuit/soup"
7
+ import { autoroute } from "@tscircuit/infgrid-ijump-astar"
3
8
 
4
9
  export const netProps = z.object({
5
10
  name: z.string(),
6
11
  })
7
12
 
8
13
  export class Net extends PrimitiveComponent<typeof netProps> {
14
+ source_net_id?: string
15
+
9
16
  getPortSelector() {
10
17
  return `net.${this.props.name}`
11
18
  }
19
+
20
+ doInitialSourceComponentRender(): void {
21
+ const { db } = this.project!
22
+ const { _parsedProps: props } = this
23
+
24
+ const net = db.source_net.insert({
25
+ name: props.name,
26
+ member_source_group_ids: [],
27
+ })
28
+
29
+ this.source_net_id = net.source_net_id
30
+ }
31
+
32
+ /**
33
+ * Get all ports connected to this net.
34
+ *
35
+ * TODO currently we're not checking for indirect connections (traces that are
36
+ * connected to other traces that are in turn connected to the net)
37
+ */
38
+ getAllConnectedPorts(): Port[] {
39
+ const allPorts = this.getSubcircuit().selectAll("port") as Port[]
40
+ const connectedPorts: Port[] = []
41
+
42
+ for (const port of allPorts) {
43
+ const traces = port._getDirectlyConnectedTraces()
44
+
45
+ for (const trace of traces) {
46
+ if (trace._isExplicitlyConnectedToNet(this)) {
47
+ connectedPorts.push(port)
48
+ break
49
+ }
50
+ }
51
+ }
52
+
53
+ return connectedPorts
54
+ }
55
+
56
+ /**
57
+ * Get all traces that are directly connected to this net, i.e. they list
58
+ * this net in their path, from, or to props
59
+ */
60
+ _getAllDirectlyConnectedTraces(): Trace[] {
61
+ const allTraces = this.getSubcircuit().selectAll("trace") as Trace[]
62
+ const connectedTraces: Trace[] = []
63
+
64
+ for (const trace of allTraces) {
65
+ if (trace._isExplicitlyConnectedToNet(this)) {
66
+ connectedTraces.push(trace)
67
+ }
68
+ }
69
+
70
+ return connectedTraces
71
+ }
72
+
73
+ /**
74
+ * Add PCB Traces to connect net islands together. A net island is a set of
75
+ * ports that are connected to each other. If a there are multiple net islands
76
+ * that means that the net is not fully connected and we need to add traces
77
+ * such that the nets are fully connected
78
+ */
79
+ doInitialPcbRouteNetIslands(): void {
80
+ const { db } = this.project!
81
+ const { _parsedProps: props } = this
82
+
83
+ const traces = this._getAllDirectlyConnectedTraces().filter(
84
+ (trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0,
85
+ )
86
+
87
+ const islands: Array<{ ports: Port[]; traces: Trace[] }> = []
88
+
89
+ for (const trace of traces) {
90
+ const tracePorts = trace._portsRoutedOnPcb
91
+ const traceIsland = islands.find((island) =>
92
+ tracePorts.some((port) => island.ports.includes(port)),
93
+ )
94
+ if (!traceIsland) {
95
+ islands.push({ ports: [...tracePorts], traces: [trace] })
96
+ continue
97
+ }
98
+ traceIsland.traces.push(trace)
99
+ traceIsland.ports.push(...tracePorts)
100
+ }
101
+
102
+ if (islands.length === 0) {
103
+ return
104
+ }
105
+
106
+ // Connect islands together by looking at each pair of islands and adding
107
+ // a trace between them
108
+ const islandPairs = pairs(islands)
109
+ for (const [A, B] of islandPairs) {
110
+ // Find two closest ports on the island
111
+ const Apositions: Array<{ x: number; y: number }> = A.ports.map((port) =>
112
+ port.getGlobalPcbPosition(),
113
+ )
114
+ const Bpositions: Array<{ x: number; y: number }> = B.ports.map((port) =>
115
+ port.getGlobalPcbPosition(),
116
+ )
117
+
118
+ let closestDist = Infinity
119
+ let closestPair: [number, number] = [-1, -1]
120
+ for (let i = 0; i < Apositions.length; i++) {
121
+ const Apos = Apositions[i]
122
+ for (let j = 0; j < Bpositions.length; j++) {
123
+ const Bpos = Bpositions[j]
124
+ const dist = Math.sqrt(
125
+ (Apos.x - Bpos.x) ** 2 + (Apos.y - Bpos.y) ** 2,
126
+ )
127
+ if (dist < closestDist) {
128
+ closestDist = dist
129
+ closestPair = [i, j]
130
+ }
131
+ }
132
+ }
133
+
134
+ const Aport = A.ports[closestPair[0]]
135
+ const Bport = B.ports[closestPair[1]]
136
+
137
+ const pcbElements: AnySoupElement[] = db
138
+ .toArray()
139
+ .filter(
140
+ (elm) =>
141
+ elm.type === "pcb_smtpad" ||
142
+ elm.type === "pcb_trace" ||
143
+ elm.type === "pcb_plated_hole" ||
144
+ elm.type === "pcb_hole" ||
145
+ elm.type === "source_port" ||
146
+ elm.type === "pcb_port",
147
+ )
148
+
149
+ const { solution } = autoroute(
150
+ pcbElements.concat([
151
+ {
152
+ type: "source_trace",
153
+ source_trace_id: "__net_trace_tmp",
154
+ connected_source_port_ids: [
155
+ Aport.source_port_id!,
156
+ Bport.source_port_id!,
157
+ ],
158
+ } as SourceTrace,
159
+ ]),
160
+ )
161
+
162
+ const trace = solution[0]
163
+ if (!trace) {
164
+ this.renderError("Failed to route net islands")
165
+ return
166
+ }
167
+
168
+ db.pcb_trace.insert(trace as any)
169
+ }
170
+ }
12
171
  }
@@ -4,6 +4,7 @@ import { z } from "zod"
4
4
  import { getRelativeDirection } from "lib/utils/get-relative-direction"
5
5
  import { symbols, type SchSymbol } from "schematic-symbols"
6
6
  import { applyToPoint, compose, translate } from "transformation-matrix"
7
+ import type { Trace } from "./Trace"
7
8
 
8
9
  export const portProps = z.object({
9
10
  name: z.string().optional(),
@@ -90,8 +91,8 @@ export class Port extends PrimitiveComponent<typeof portProps> {
90
91
  return this.isMatchingAnyOf(port.getNameAndAliases())
91
92
  }
92
93
  getPortSelector() {
94
+ // TODO this.parent.getSubcircuitSelector() >
93
95
  return `.${this.parent?.props.name} > port.${this.props.name}`
94
- // return `#${this.props.id}`
95
96
  }
96
97
  getAvailablePcbLayers(): string[] {
97
98
  return Array.from(
@@ -99,6 +100,21 @@ export class Port extends PrimitiveComponent<typeof portProps> {
99
100
  )
100
101
  }
101
102
 
103
+ /**
104
+ * Return traces that are explicitly connected to this port (not via a net)
105
+ */
106
+ _getDirectlyConnectedTraces(): Trace[] {
107
+ const allSubcircuitTraces = this.getSubcircuit().selectAll(
108
+ "trace",
109
+ ) as Trace[]
110
+
111
+ const connectedTraces = allSubcircuitTraces.filter((trace) =>
112
+ trace._isExplicitlyConnectedToPort(this),
113
+ )
114
+
115
+ return connectedTraces
116
+ }
117
+
102
118
  doInitialSourceRender(): void {
103
119
  const { db } = this.project!
104
120
  const { _parsedProps: props } = this
@@ -12,6 +12,7 @@ import type {
12
12
  PCBTrace,
13
13
  RouteHintPoint,
14
14
  SchematicTrace,
15
+ SourceTrace,
15
16
  } from "@tscircuit/soup"
16
17
  import type {
17
18
  Obstacle,
@@ -24,6 +25,9 @@ import type { TraceHint } from "./TraceHint"
24
25
  import { findPossibleTraceLayerCombinations } from "lib/utils/autorouting/findPossibleTraceLayerCombinations"
25
26
  import { pairs } from "lib/utils/pairs"
26
27
  import { mergeRoutes } from "lib/utils/autorouting/mergeRoutes"
28
+ import type { Net } from "./Net"
29
+ import { getClosest } from "lib/utils/getClosest"
30
+ import { z } from "zod"
27
31
 
28
32
  type PcbRouteObjective =
29
33
  | RouteHintPoint
@@ -41,6 +45,12 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
41
45
  source_trace_id: string | null = null
42
46
  pcb_trace_id: string | null = null
43
47
  schematic_trace_id: string | null = null
48
+ _portsRoutedOnPcb: Port[]
49
+
50
+ constructor(props: z.input<typeof traceProps>) {
51
+ super(props)
52
+ this._portsRoutedOnPcb = []
53
+ }
44
54
 
45
55
  get config() {
46
56
  return {
@@ -48,7 +58,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
48
58
  }
49
59
  }
50
60
 
51
- getTracePortPathSelectors(): string[] {
61
+ _getTracePortOrNetSelectorListFromProps(): string[] {
52
62
  if ("from" in this.props && "to" in this.props) {
53
63
  return [
54
64
  typeof this.props.from === "string"
@@ -67,9 +77,29 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
67
77
  return []
68
78
  }
69
79
 
80
+ getTracePortPathSelectors(): string[] {
81
+ return this._getTracePortOrNetSelectorListFromProps().filter(
82
+ (selector) => !selector.includes("net."),
83
+ )
84
+ }
85
+
86
+ getTracePathNetSelectors(): string[] {
87
+ return this._getTracePortOrNetSelectorListFromProps().filter((selector) =>
88
+ selector.includes("net."),
89
+ )
90
+ }
91
+
70
92
  _findConnectedPorts():
71
- | { allPortsFound: true; ports: Array<{ selector: string; port: Port }> }
72
- | { allPortsFound: false; ports?: undefined } {
93
+ | {
94
+ allPortsFound: true
95
+ ports: Port[]
96
+ portsWithSelectors: Array<{ selector: string; port: Port }>
97
+ }
98
+ | {
99
+ allPortsFound: false
100
+ ports?: undefined
101
+ portsWithSelectors?: undefined
102
+ } {
73
103
  const { db } = this.project!
74
104
  const { _parsedProps: props, parent } = this
75
105
 
@@ -77,17 +107,17 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
77
107
 
78
108
  const portSelectors = this.getTracePortPathSelectors()
79
109
 
80
- const ports = portSelectors.map((selector) => ({
110
+ const portsWithSelectors = portSelectors.map((selector) => ({
81
111
  selector,
82
112
  port:
83
- (this.getOpaqueGroup().selectOne(selector, { type: "port" }) as Port) ??
113
+ (this.getSubcircuit().selectOne(selector, { type: "port" }) as Port) ??
84
114
  null,
85
115
  }))
86
116
 
87
- for (const { selector, port } of ports) {
117
+ for (const { selector, port } of portsWithSelectors) {
88
118
  if (!port) {
89
119
  const parentSelector = selector.replace(/\>.*$/, "")
90
- const targetComponent = this.getOpaqueGroup().selectOne(parentSelector)
120
+ const targetComponent = this.getSubcircuit().selectOne(parentSelector)
91
121
  if (!targetComponent) {
92
122
  this.renderError(`Could not find port for selector "${selector}"`)
93
123
  } else {
@@ -103,11 +133,50 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
103
133
  }
104
134
  }
105
135
 
106
- if (ports.some((p) => !p.port)) {
136
+ if (portsWithSelectors.some((p) => !p.port)) {
107
137
  return { allPortsFound: false }
108
138
  }
109
139
 
110
- return { allPortsFound: true, ports }
140
+ return {
141
+ allPortsFound: true,
142
+ portsWithSelectors,
143
+ ports: portsWithSelectors.map(({ port }) => port),
144
+ }
145
+ }
146
+
147
+ _findConnectedNets(): Array<{ selector: string; net: Net }> {
148
+ const nets = this.getTracePathNetSelectors().map((selector) => ({
149
+ selector,
150
+ net: this.getSubcircuit().selectOne(selector, { type: "net" }) as Net,
151
+ }))
152
+
153
+ const undefinedNets = nets.filter((n) => !n.net)
154
+ if (undefinedNets.length > 0) {
155
+ this.renderError(
156
+ `Could not find net for selector "${undefinedNets[0].selector}" inside ${this}`,
157
+ )
158
+ }
159
+
160
+ return nets
161
+ }
162
+
163
+ /**
164
+ * Determine if a trace is explicitly connected to a port (not via a net)
165
+ */
166
+ _isExplicitlyConnectedToPort(port: Port) {
167
+ const { allPortsFound, portsWithSelectors: portsWithMetadata } =
168
+ this._findConnectedPorts()
169
+ if (!allPortsFound) return false
170
+ const ports = portsWithMetadata.map((p) => p.port)
171
+ return ports.includes(port)
172
+ }
173
+
174
+ /**
175
+ * Determine if a trace is explicitly connected to a net (not via a port)
176
+ */
177
+ _isExplicitlyConnectedToNet(net: Net) {
178
+ const nets = this._findConnectedNets().map((n) => n.net)
179
+ return nets.includes(net)
111
180
  }
112
181
 
113
182
  doInitialSourceTraceRender(): void {
@@ -119,12 +188,15 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
119
188
  return
120
189
  }
121
190
 
122
- const { allPortsFound, ports } = this._findConnectedPorts()
191
+ const { allPortsFound, portsWithSelectors: ports } =
192
+ this._findConnectedPorts()
123
193
  if (!allPortsFound) return
124
194
 
195
+ const nets = this._findConnectedNets()
196
+
125
197
  const trace = db.source_trace.insert({
126
198
  connected_source_port_ids: ports.map((p) => p.port.source_port_id!),
127
- connected_source_net_ids: [],
199
+ connected_source_net_ids: nets.map((n) => n.net.source_net_id!),
128
200
  })
129
201
 
130
202
  this.source_trace_id = trace.source_trace_id
@@ -137,9 +209,42 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
137
209
  if (!parent) throw new Error("Trace has no parent")
138
210
 
139
211
  const { allPortsFound, ports } = this._findConnectedPorts()
212
+ const portsConnectedOnPcbViaNet: Port[] = []
140
213
 
141
214
  if (!allPortsFound) return
142
215
 
216
+ const nets = this._findConnectedNets()
217
+
218
+ if (ports.length === 0 && nets.length === 2) {
219
+ // Find the two optimal points to connect the two nets
220
+ this.renderError(
221
+ `Trace connects two nets, we haven't implemented a way to route this yet`,
222
+ )
223
+ return
224
+ // biome-ignore lint/style/noUselessElse: <explanation>
225
+ } else if (ports.length === 1 && nets.length === 1) {
226
+ // Add a port from the net that is closest to the port
227
+ const port = ports[0]
228
+ const portsInNet = nets[0].net.getAllConnectedPorts()
229
+ const otherPortsInNet = portsInNet.filter((p) => p !== port)
230
+ if (otherPortsInNet.length === 0) {
231
+ console.log(
232
+ "Nothing to connect this port to, the net is empty. TODO should emit a warning!",
233
+ )
234
+ return
235
+ }
236
+ const closestPortInNet = getClosest(port, otherPortsInNet)
237
+
238
+ portsConnectedOnPcbViaNet.push(closestPortInNet)
239
+
240
+ ports.push(closestPortInNet)
241
+ } else if (ports.length > 1 && nets.length >= 1) {
242
+ this.renderError(
243
+ `Trace has more than one port and one or more nets, we don't currently support this type of complex trace routing`,
244
+ )
245
+ return
246
+ }
247
+
143
248
  const pcbElements: AnySoupElement[] = db
144
249
  .toArray()
145
250
  .filter(
@@ -154,7 +259,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
154
259
 
155
260
  const source_trace = db.source_trace.get(this.source_trace_id!)!
156
261
 
157
- const hints = ports.flatMap(({ port }) =>
262
+ const hints = ports.flatMap((port) =>
158
263
  port.matchedComponents.filter((c) => c.componentName === "TraceHint"),
159
264
  ) as TraceHint[]
160
265
 
@@ -165,7 +270,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
165
270
  if (ports.length > 2) {
166
271
  this.renderError(
167
272
  `Trace has more than two ports (${ports
168
- .map((p) => p.port.getString())
273
+ .map((p) => p.getString())
169
274
  .join(
170
275
  ", ",
171
276
  )}), routing between more than two ports for a single trace is not implemented`,
@@ -173,21 +278,50 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
173
278
  return
174
279
  }
175
280
 
281
+ const alreadyRoutedTraces = this.getSubcircuit()
282
+ .selectAll("trace")
283
+ .filter(
284
+ (trace) => trace.renderPhaseStates.PcbTraceRender.initialized,
285
+ ) as Trace[]
286
+
287
+ const isAlreadyRouted = alreadyRoutedTraces.some(
288
+ (trace) =>
289
+ trace._portsRoutedOnPcb.length === ports.length &&
290
+ trace._portsRoutedOnPcb.every((portRoutedByOtherTrace) =>
291
+ ports.includes(portRoutedByOtherTrace),
292
+ ),
293
+ )
294
+
295
+ if (isAlreadyRouted) {
296
+ return
297
+ }
298
+
176
299
  if (pcbRouteHints.length === 0) {
177
- const { solution } = autoroute(pcbElements.concat([source_trace]))
300
+ const { solution } = autoroute(
301
+ pcbElements.concat([
302
+ {
303
+ ...source_trace,
304
+ // manually override b/c some of the ports may be connected via nets
305
+ // so they don't appear properly in the source_trace, we don't need
306
+ // to do this if the algorithm correctly looks at connected_source_net_ids
307
+ connected_source_port_ids: ports.map((p) => p.source_port_id!),
308
+ } as SourceTrace,
309
+ ]),
310
+ )
178
311
  // TODO for some reason, the solution gets duplicated inside ijump-astar
179
312
  const inputPcbTrace = solution[0]
180
313
 
181
314
  if (!inputPcbTrace) {
182
315
  // TODO render error indicating we could not find a route
183
316
  console.log(
184
- `Failed to find route ffrom ${ports[0].port} to ${ports[1].port} render error!`,
317
+ `Failed to find route from ${ports[0]} to ${ports[1]} (TODO render error!)`,
185
318
  )
186
319
  return
187
320
  }
188
321
  const pcb_trace = db.pcb_trace.insert(inputPcbTrace as any)
189
322
 
190
323
  this.pcb_trace_id = pcb_trace.pcb_trace_id
324
+ this._portsRoutedOnPcb = ports
191
325
  return
192
326
  }
193
327
 
@@ -195,9 +329,9 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
195
329
  // terminal of the trace and the hints
196
330
  // TODO order based on proximity to ports
197
331
  const orderedRouteObjectives: PcbRouteObjective[] = [
198
- portToObjective(ports[0].port),
332
+ portToObjective(ports[0]),
199
333
  ...pcbRouteHints,
200
- portToObjective(ports[1].port),
334
+ portToObjective(ports[1]),
201
335
  ]
202
336
 
203
337
  // Hints can indicate where there should be a via, but the layer is allowed
@@ -285,7 +419,8 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
285
419
 
286
420
  if (!parent) throw new Error("Trace has no parent")
287
421
 
288
- const { allPortsFound, ports } = this._findConnectedPorts()
422
+ const { allPortsFound, portsWithSelectors: ports } =
423
+ this._findConnectedPorts()
289
424
 
290
425
  if (!allPortsFound) return
291
426
 
@@ -25,6 +25,7 @@ declare global {
25
25
  port: Props.PortProps
26
26
  group: Props.GroupProps
27
27
  netalias: Props.NetAliasProps
28
+ net: Props.NetProps
28
29
  trace: Props.TraceProps
29
30
  custom: any
30
31
  component: Props.ComponentProps