@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.
package/dist/index.js CHANGED
@@ -37,6 +37,7 @@ import "react";
37
37
  var orderedRenderPhases = [
38
38
  "ReactSubtreesRender",
39
39
  // probably going to be removed b/c subtrees should render instantly
40
+ "CreateNetsFromProps",
40
41
  "CreateTracesFromProps",
41
42
  "SourceRender",
42
43
  "SourceParentAttachment",
@@ -54,6 +55,7 @@ var orderedRenderPhases = [
54
55
  "PcbParentAttachment",
55
56
  "PcbLayout",
56
57
  "PcbTraceRender",
58
+ "PcbRouteNetIslands",
57
59
  "CadModelRender",
58
60
  "PcbAnalysis"
59
61
  ];
@@ -174,13 +176,13 @@ var PrimitiveComponent = class extends Renderable {
174
176
  lowercaseComponentName = "";
175
177
  externallyAddedAliases;
176
178
  /**
177
- * An opaque group is a self-contained subcircuit. All the selectors inside
178
- * an opaque group are relative to the group. You can have multiple opaque
179
- * groups and their selectors will not interact with each other (even if the
179
+ * An subcircuit is self-contained. All the selectors inside
180
+ * a subcircuit are relative to the subcircuit group. You can have multiple
181
+ * subcircuits and their selectors will not interact with each other (even if the
180
182
  * components share the same names) unless you explicitly break out some ports
181
183
  */
182
- get isOpaqueGroup() {
183
- return Boolean(this.props.opaque) || // Implied opaque group for top-level group
184
+ get isSubcircuit() {
185
+ return Boolean(this.props.subcircuit) || // Implied opaque group for top-level group
184
186
  this.lowercaseComponentName === "group" && this?.parent?.props?.name === "$root";
185
187
  }
186
188
  source_group_id = null;
@@ -305,12 +307,12 @@ var PrimitiveComponent = class extends Renderable {
305
307
  this.childrenPendingRemoval.push(component);
306
308
  component.shouldBeRemoved = true;
307
309
  }
308
- getOpaqueGroupSelector() {
310
+ getSubcircuitSelector() {
309
311
  const name = this._parsedProps.name;
310
312
  const endPart = name ? `${this.lowercaseComponentName}.${name}` : this.lowercaseComponentName;
311
313
  if (!this.parent) return endPart;
312
- if (this.parent.isOpaqueGroup) return endPart;
313
- return `${this.parent.getOpaqueGroupSelector()} > ${endPart}`;
314
+ if (this.parent.isSubcircuit) return endPart;
315
+ return `${this.parent.getSubcircuitSelector()} > ${endPart}`;
314
316
  }
315
317
  getFullPathSelector() {
316
318
  const name = this._parsedProps.name;
@@ -347,9 +349,9 @@ var PrimitiveComponent = class extends Renderable {
347
349
  return true;
348
350
  return false;
349
351
  }
350
- getOpaqueGroup() {
351
- if (this.isOpaqueGroup) return this;
352
- const group = this.parent?.getOpaqueGroup();
352
+ getSubcircuit() {
353
+ if (this.isSubcircuit) return this;
354
+ const group = this.parent?.getSubcircuit();
353
355
  if (!group)
354
356
  throw new Error("Component is not inside an opaque group (no board?)");
355
357
  return group;
@@ -409,6 +411,9 @@ var PrimitiveComponent = class extends Renderable {
409
411
  if (parent?.props?.name && props?.name) {
410
412
  return `<${cname}#${this._renderId}(.${parent?.props.name}>.${props?.name}) />`;
411
413
  }
414
+ if (props?.from && props?.to) {
415
+ return `<${cname}#${this._renderId}(from:${props.from} to:${props?.to}) />`;
416
+ }
412
417
  if (props?.name) {
413
418
  return `<${cname}#${this._renderId} name=".${props?.name}" />`;
414
419
  }
@@ -525,6 +530,18 @@ var Port = class extends PrimitiveComponent {
525
530
  new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
526
531
  );
527
532
  }
533
+ /**
534
+ * Return traces that are explicitly connected to this port (not via a net)
535
+ */
536
+ _getDirectlyConnectedTraces() {
537
+ const allSubcircuitTraces = this.getSubcircuit().selectAll(
538
+ "trace"
539
+ );
540
+ const connectedTraces = allSubcircuitTraces.filter(
541
+ (trace) => trace._isExplicitlyConnectedToPort(this)
542
+ );
543
+ return connectedTraces;
544
+ }
528
545
  doInitialSourceRender() {
529
546
  const { db } = this.project;
530
547
  const { _parsedProps: props } = this;
@@ -983,6 +1000,149 @@ var createComponentsFromSoup = (soup) => {
983
1000
  return components;
984
1001
  };
985
1002
 
1003
+ // lib/components/primitive-components/Net.ts
1004
+ import { z as z3 } from "zod";
1005
+
1006
+ // lib/utils/pairs.ts
1007
+ function pairs(arr) {
1008
+ const result = [];
1009
+ for (let i = 0; i < arr.length - 1; i++) {
1010
+ result.push([arr[i], arr[i + 1]]);
1011
+ }
1012
+ return result;
1013
+ }
1014
+
1015
+ // lib/components/primitive-components/Net.ts
1016
+ import { autoroute } from "@tscircuit/infgrid-ijump-astar";
1017
+ var netProps = z3.object({
1018
+ name: z3.string()
1019
+ });
1020
+ var Net = class extends PrimitiveComponent {
1021
+ source_net_id;
1022
+ getPortSelector() {
1023
+ return `net.${this.props.name}`;
1024
+ }
1025
+ doInitialSourceComponentRender() {
1026
+ const { db } = this.project;
1027
+ const { _parsedProps: props } = this;
1028
+ const net = db.source_net.insert({
1029
+ name: props.name,
1030
+ member_source_group_ids: []
1031
+ });
1032
+ this.source_net_id = net.source_net_id;
1033
+ }
1034
+ /**
1035
+ * Get all ports connected to this net.
1036
+ *
1037
+ * TODO currently we're not checking for indirect connections (traces that are
1038
+ * connected to other traces that are in turn connected to the net)
1039
+ */
1040
+ getAllConnectedPorts() {
1041
+ const allPorts = this.getSubcircuit().selectAll("port");
1042
+ const connectedPorts = [];
1043
+ for (const port of allPorts) {
1044
+ const traces = port._getDirectlyConnectedTraces();
1045
+ for (const trace of traces) {
1046
+ if (trace._isExplicitlyConnectedToNet(this)) {
1047
+ connectedPorts.push(port);
1048
+ break;
1049
+ }
1050
+ }
1051
+ }
1052
+ return connectedPorts;
1053
+ }
1054
+ /**
1055
+ * Get all traces that are directly connected to this net, i.e. they list
1056
+ * this net in their path, from, or to props
1057
+ */
1058
+ _getAllDirectlyConnectedTraces() {
1059
+ const allTraces = this.getSubcircuit().selectAll("trace");
1060
+ const connectedTraces = [];
1061
+ for (const trace of allTraces) {
1062
+ if (trace._isExplicitlyConnectedToNet(this)) {
1063
+ connectedTraces.push(trace);
1064
+ }
1065
+ }
1066
+ return connectedTraces;
1067
+ }
1068
+ /**
1069
+ * Add PCB Traces to connect net islands together. A net island is a set of
1070
+ * ports that are connected to each other. If a there are multiple net islands
1071
+ * that means that the net is not fully connected and we need to add traces
1072
+ * such that the nets are fully connected
1073
+ */
1074
+ doInitialPcbRouteNetIslands() {
1075
+ const { db } = this.project;
1076
+ const { _parsedProps: props } = this;
1077
+ const traces = this._getAllDirectlyConnectedTraces().filter(
1078
+ (trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0
1079
+ );
1080
+ const islands = [];
1081
+ for (const trace of traces) {
1082
+ const tracePorts = trace._portsRoutedOnPcb;
1083
+ const traceIsland = islands.find(
1084
+ (island) => tracePorts.some((port) => island.ports.includes(port))
1085
+ );
1086
+ if (!traceIsland) {
1087
+ islands.push({ ports: [...tracePorts], traces: [trace] });
1088
+ continue;
1089
+ }
1090
+ traceIsland.traces.push(trace);
1091
+ traceIsland.ports.push(...tracePorts);
1092
+ }
1093
+ if (islands.length === 0) {
1094
+ return;
1095
+ }
1096
+ const islandPairs = pairs(islands);
1097
+ for (const [A, B] of islandPairs) {
1098
+ const Apositions = A.ports.map(
1099
+ (port) => port.getGlobalPcbPosition()
1100
+ );
1101
+ const Bpositions = B.ports.map(
1102
+ (port) => port.getGlobalPcbPosition()
1103
+ );
1104
+ let closestDist = Infinity;
1105
+ let closestPair = [-1, -1];
1106
+ for (let i = 0; i < Apositions.length; i++) {
1107
+ const Apos = Apositions[i];
1108
+ for (let j = 0; j < Bpositions.length; j++) {
1109
+ const Bpos = Bpositions[j];
1110
+ const dist = Math.sqrt(
1111
+ (Apos.x - Bpos.x) ** 2 + (Apos.y - Bpos.y) ** 2
1112
+ );
1113
+ if (dist < closestDist) {
1114
+ closestDist = dist;
1115
+ closestPair = [i, j];
1116
+ }
1117
+ }
1118
+ }
1119
+ const Aport = A.ports[closestPair[0]];
1120
+ const Bport = B.ports[closestPair[1]];
1121
+ const pcbElements = db.toArray().filter(
1122
+ (elm) => elm.type === "pcb_smtpad" || elm.type === "pcb_trace" || elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || elm.type === "pcb_port"
1123
+ );
1124
+ const { solution } = autoroute(
1125
+ pcbElements.concat([
1126
+ {
1127
+ type: "source_trace",
1128
+ source_trace_id: "__net_trace_tmp",
1129
+ connected_source_port_ids: [
1130
+ Aport.source_port_id,
1131
+ Bport.source_port_id
1132
+ ]
1133
+ }
1134
+ ])
1135
+ );
1136
+ const trace = solution[0];
1137
+ if (!trace) {
1138
+ this.renderError("Failed to route net islands");
1139
+ return;
1140
+ }
1141
+ db.pcb_trace.insert(trace);
1142
+ }
1143
+ }
1144
+ };
1145
+
986
1146
  // lib/components/base-components/NormalComponent.ts
987
1147
  var NormalComponent = class extends PrimitiveComponent {
988
1148
  reactSubtrees = [];
@@ -1204,6 +1364,19 @@ var NormalComponent = class extends PrimitiveComponent {
1204
1364
  }
1205
1365
  return newPorts;
1206
1366
  }
1367
+ _createNetsFromProps(propsWithConnections) {
1368
+ for (const prop of propsWithConnections) {
1369
+ if (typeof prop === "string" && prop.startsWith("net.")) {
1370
+ if (!this.getSubcircuit().selectOne(prop)) {
1371
+ this.getSubcircuit().add(
1372
+ new Net({
1373
+ name: prop.split(".")[1]
1374
+ })
1375
+ );
1376
+ }
1377
+ }
1378
+ }
1379
+ }
1207
1380
  /**
1208
1381
  * Use data from our props to create ports for this component.
1209
1382
  *
@@ -1239,10 +1412,24 @@ var NormalComponent = class extends PrimitiveComponent {
1239
1412
 
1240
1413
  // lib/components/normal-components/Board.ts
1241
1414
  import { boardProps } from "@tscircuit/props";
1242
- import { identity as identity3 } from "transformation-matrix";
1243
- var Board = class extends NormalComponent {
1415
+ import { identity as identity4 } from "transformation-matrix";
1416
+
1417
+ // lib/components/primitive-components/Group.ts
1418
+ import { groupProps } from "@tscircuit/props";
1419
+ import "transformation-matrix";
1420
+ import "zod";
1421
+ var Group = class extends NormalComponent {
1422
+ get config() {
1423
+ return {
1424
+ zodProps: groupProps
1425
+ };
1426
+ }
1427
+ };
1428
+
1429
+ // lib/components/normal-components/Board.ts
1430
+ var Board = class extends Group {
1244
1431
  pcb_board_id = null;
1245
- get isOpaqueGroup() {
1432
+ get isSubcircuit() {
1246
1433
  return true;
1247
1434
  }
1248
1435
  get config() {
@@ -1270,7 +1457,7 @@ var Board = class extends NormalComponent {
1270
1457
  this.pcb_board_id = null;
1271
1458
  }
1272
1459
  computePcbGlobalTransform() {
1273
- return identity3();
1460
+ return identity4();
1274
1461
  }
1275
1462
  };
1276
1463
 
@@ -1282,7 +1469,7 @@ import "zod";
1282
1469
  import { traceProps } from "@tscircuit/props";
1283
1470
  import {
1284
1471
  IJumpAutorouter,
1285
- autoroute,
1472
+ autoroute as autoroute2,
1286
1473
  getObstaclesFromSoup,
1287
1474
  markObstaclesAsConnected
1288
1475
  } from "@tscircuit/infgrid-ijump-astar";
@@ -1366,15 +1553,6 @@ var findPossibleTraceLayerCombinations = (hints, layer_path = []) => {
1366
1553
  return candidates;
1367
1554
  };
1368
1555
 
1369
- // lib/utils/pairs.ts
1370
- function pairs(arr) {
1371
- const result = [];
1372
- for (let i = 0; i < arr.length - 1; i++) {
1373
- result.push([arr[i], arr[i + 1]]);
1374
- }
1375
- return result;
1376
- }
1377
-
1378
1556
  // lib/utils/autorouting/mergeRoutes.ts
1379
1557
  function pdist(a, b) {
1380
1558
  return Math.hypot(a.x - b.x, a.y - b.y);
@@ -1417,7 +1595,29 @@ var mergeRoutes = (routes) => {
1417
1595
  return merged;
1418
1596
  };
1419
1597
 
1598
+ // lib/utils/getClosest.ts
1599
+ var getDistance = (a, b) => {
1600
+ const aPos = "getGlobalPcbPosition" in a ? a.getGlobalPcbPosition() : a;
1601
+ const bPos = "getGlobalPcbPosition" in b ? b.getGlobalPcbPosition() : b;
1602
+ return Math.sqrt((aPos.x - bPos.x) ** 2 + (aPos.y - bPos.y) ** 2);
1603
+ };
1604
+ function getClosest(point, candidates) {
1605
+ if (candidates.length === 0)
1606
+ throw new Error("No candidates given to getClosest method");
1607
+ let closest = candidates[0];
1608
+ let closestDist = Infinity;
1609
+ for (const candidate of candidates) {
1610
+ const dist = getDistance(point, candidate);
1611
+ if (dist < closestDist) {
1612
+ closest = candidate;
1613
+ closestDist = dist;
1614
+ }
1615
+ }
1616
+ return closest;
1617
+ }
1618
+
1420
1619
  // lib/components/primitive-components/Trace.ts
1620
+ import "zod";
1421
1621
  var portToObjective = (port) => {
1422
1622
  const portPosition = port.getGlobalPcbPosition();
1423
1623
  return {
@@ -1429,12 +1629,17 @@ var Trace = class extends PrimitiveComponent {
1429
1629
  source_trace_id = null;
1430
1630
  pcb_trace_id = null;
1431
1631
  schematic_trace_id = null;
1632
+ _portsRoutedOnPcb;
1633
+ constructor(props) {
1634
+ super(props);
1635
+ this._portsRoutedOnPcb = [];
1636
+ }
1432
1637
  get config() {
1433
1638
  return {
1434
1639
  zodProps: traceProps
1435
1640
  };
1436
1641
  }
1437
- getTracePortPathSelectors() {
1642
+ _getTracePortOrNetSelectorListFromProps() {
1438
1643
  if ("from" in this.props && "to" in this.props) {
1439
1644
  return [
1440
1645
  typeof this.props.from === "string" ? this.props.from : this.props.from.getPortSelector(),
@@ -1448,19 +1653,29 @@ var Trace = class extends PrimitiveComponent {
1448
1653
  }
1449
1654
  return [];
1450
1655
  }
1656
+ getTracePortPathSelectors() {
1657
+ return this._getTracePortOrNetSelectorListFromProps().filter(
1658
+ (selector) => !selector.includes("net.")
1659
+ );
1660
+ }
1661
+ getTracePathNetSelectors() {
1662
+ return this._getTracePortOrNetSelectorListFromProps().filter(
1663
+ (selector) => selector.includes("net.")
1664
+ );
1665
+ }
1451
1666
  _findConnectedPorts() {
1452
1667
  const { db } = this.project;
1453
1668
  const { _parsedProps: props, parent } = this;
1454
1669
  if (!parent) throw new Error("Trace has no parent");
1455
1670
  const portSelectors = this.getTracePortPathSelectors();
1456
- const ports = portSelectors.map((selector) => ({
1671
+ const portsWithSelectors = portSelectors.map((selector) => ({
1457
1672
  selector,
1458
- port: this.getOpaqueGroup().selectOne(selector, { type: "port" }) ?? null
1673
+ port: this.getSubcircuit().selectOne(selector, { type: "port" }) ?? null
1459
1674
  }));
1460
- for (const { selector, port } of ports) {
1675
+ for (const { selector, port } of portsWithSelectors) {
1461
1676
  if (!port) {
1462
1677
  const parentSelector = selector.replace(/\>.*$/, "");
1463
- const targetComponent = this.getOpaqueGroup().selectOne(parentSelector);
1678
+ const targetComponent = this.getSubcircuit().selectOne(parentSelector);
1464
1679
  if (!targetComponent) {
1465
1680
  this.renderError(`Could not find port for selector "${selector}"`);
1466
1681
  } else {
@@ -1473,10 +1688,43 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1473
1688
  }
1474
1689
  }
1475
1690
  }
1476
- if (ports.some((p) => !p.port)) {
1691
+ if (portsWithSelectors.some((p) => !p.port)) {
1477
1692
  return { allPortsFound: false };
1478
1693
  }
1479
- return { allPortsFound: true, ports };
1694
+ return {
1695
+ allPortsFound: true,
1696
+ portsWithSelectors,
1697
+ ports: portsWithSelectors.map(({ port }) => port)
1698
+ };
1699
+ }
1700
+ _findConnectedNets() {
1701
+ const nets = this.getTracePathNetSelectors().map((selector) => ({
1702
+ selector,
1703
+ net: this.getSubcircuit().selectOne(selector, { type: "net" })
1704
+ }));
1705
+ const undefinedNets = nets.filter((n) => !n.net);
1706
+ if (undefinedNets.length > 0) {
1707
+ this.renderError(
1708
+ `Could not find net for selector "${undefinedNets[0].selector}" inside ${this}`
1709
+ );
1710
+ }
1711
+ return nets;
1712
+ }
1713
+ /**
1714
+ * Determine if a trace is explicitly connected to a port (not via a net)
1715
+ */
1716
+ _isExplicitlyConnectedToPort(port) {
1717
+ const { allPortsFound, portsWithSelectors: portsWithMetadata } = this._findConnectedPorts();
1718
+ if (!allPortsFound) return false;
1719
+ const ports = portsWithMetadata.map((p) => p.port);
1720
+ return ports.includes(port);
1721
+ }
1722
+ /**
1723
+ * Determine if a trace is explicitly connected to a net (not via a port)
1724
+ */
1725
+ _isExplicitlyConnectedToNet(net) {
1726
+ const nets = this._findConnectedNets().map((n) => n.net);
1727
+ return nets.includes(net);
1480
1728
  }
1481
1729
  doInitialSourceTraceRender() {
1482
1730
  const { db } = this.project;
@@ -1485,11 +1733,12 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1485
1733
  this.renderError("Trace has no parent");
1486
1734
  return;
1487
1735
  }
1488
- const { allPortsFound, ports } = this._findConnectedPorts();
1736
+ const { allPortsFound, portsWithSelectors: ports } = this._findConnectedPorts();
1489
1737
  if (!allPortsFound) return;
1738
+ const nets = this._findConnectedNets();
1490
1739
  const trace = db.source_trace.insert({
1491
1740
  connected_source_port_ids: ports.map((p) => p.port.source_port_id),
1492
- connected_source_net_ids: []
1741
+ connected_source_net_ids: nets.map((n) => n.net.source_net_id)
1493
1742
  });
1494
1743
  this.source_trace_id = trace.source_trace_id;
1495
1744
  }
@@ -1498,42 +1747,90 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1498
1747
  const { _parsedProps: props, parent } = this;
1499
1748
  if (!parent) throw new Error("Trace has no parent");
1500
1749
  const { allPortsFound, ports } = this._findConnectedPorts();
1750
+ const portsConnectedOnPcbViaNet = [];
1501
1751
  if (!allPortsFound) return;
1752
+ const nets = this._findConnectedNets();
1753
+ if (ports.length === 0 && nets.length === 2) {
1754
+ this.renderError(
1755
+ `Trace connects two nets, we haven't implemented a way to route this yet`
1756
+ );
1757
+ return;
1758
+ } else if (ports.length === 1 && nets.length === 1) {
1759
+ const port = ports[0];
1760
+ const portsInNet = nets[0].net.getAllConnectedPorts();
1761
+ const otherPortsInNet = portsInNet.filter((p) => p !== port);
1762
+ if (otherPortsInNet.length === 0) {
1763
+ console.log(
1764
+ "Nothing to connect this port to, the net is empty. TODO should emit a warning!"
1765
+ );
1766
+ return;
1767
+ }
1768
+ const closestPortInNet = getClosest(port, otherPortsInNet);
1769
+ portsConnectedOnPcbViaNet.push(closestPortInNet);
1770
+ ports.push(closestPortInNet);
1771
+ } else if (ports.length > 1 && nets.length >= 1) {
1772
+ this.renderError(
1773
+ `Trace has more than one port and one or more nets, we don't currently support this type of complex trace routing`
1774
+ );
1775
+ return;
1776
+ }
1502
1777
  const pcbElements = db.toArray().filter(
1503
1778
  (elm) => elm.type === "pcb_smtpad" || elm.type === "pcb_trace" || elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || elm.type === "pcb_port"
1504
1779
  );
1505
1780
  const source_trace = db.source_trace.get(this.source_trace_id);
1506
1781
  const hints = ports.flatMap(
1507
- ({ port }) => port.matchedComponents.filter((c) => c.componentName === "TraceHint")
1782
+ (port) => port.matchedComponents.filter((c) => c.componentName === "TraceHint")
1508
1783
  );
1509
1784
  const pcbRouteHints = (this._parsedProps.pcbRouteHints ?? []).concat(
1510
1785
  hints.flatMap((h) => h.getPcbRouteHints())
1511
1786
  );
1512
1787
  if (ports.length > 2) {
1513
1788
  this.renderError(
1514
- `Trace has more than two ports (${ports.map((p) => p.port.getString()).join(
1789
+ `Trace has more than two ports (${ports.map((p) => p.getString()).join(
1515
1790
  ", "
1516
1791
  )}), routing between more than two ports for a single trace is not implemented`
1517
1792
  );
1518
1793
  return;
1519
1794
  }
1795
+ const alreadyRoutedTraces = this.getSubcircuit().selectAll("trace").filter(
1796
+ (trace) => trace.renderPhaseStates.PcbTraceRender.initialized
1797
+ );
1798
+ const isAlreadyRouted = alreadyRoutedTraces.some(
1799
+ (trace) => trace._portsRoutedOnPcb.length === ports.length && trace._portsRoutedOnPcb.every(
1800
+ (portRoutedByOtherTrace) => ports.includes(portRoutedByOtherTrace)
1801
+ )
1802
+ );
1803
+ if (isAlreadyRouted) {
1804
+ return;
1805
+ }
1520
1806
  if (pcbRouteHints.length === 0) {
1521
- const { solution } = autoroute(pcbElements.concat([source_trace]));
1807
+ const { solution } = autoroute2(
1808
+ pcbElements.concat([
1809
+ {
1810
+ ...source_trace,
1811
+ // manually override b/c some of the ports may be connected via nets
1812
+ // so they don't appear properly in the source_trace, we don't need
1813
+ // to do this if the algorithm correctly looks at connected_source_net_ids
1814
+ connected_source_port_ids: ports.map((p) => p.source_port_id)
1815
+ }
1816
+ ])
1817
+ );
1522
1818
  const inputPcbTrace = solution[0];
1523
1819
  if (!inputPcbTrace) {
1524
1820
  console.log(
1525
- `Failed to find route ffrom ${ports[0].port} to ${ports[1].port} render error!`
1821
+ `Failed to find route from ${ports[0]} to ${ports[1]} (TODO render error!)`
1526
1822
  );
1527
1823
  return;
1528
1824
  }
1529
1825
  const pcb_trace2 = db.pcb_trace.insert(inputPcbTrace);
1530
1826
  this.pcb_trace_id = pcb_trace2.pcb_trace_id;
1827
+ this._portsRoutedOnPcb = ports;
1531
1828
  return;
1532
1829
  }
1533
1830
  const orderedRouteObjectives = [
1534
- portToObjective(ports[0].port),
1831
+ portToObjective(ports[0]),
1535
1832
  ...pcbRouteHints,
1536
- portToObjective(ports[1].port)
1833
+ portToObjective(ports[1])
1537
1834
  ];
1538
1835
  const candidateLayerCombinations = findPossibleTraceLayerCombinations(
1539
1836
  orderedRouteObjectives
@@ -1600,7 +1897,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1600
1897
  const { db } = this.project;
1601
1898
  const { _parsedProps: props, parent } = this;
1602
1899
  if (!parent) throw new Error("Trace has no parent");
1603
- const { allPortsFound, ports } = this._findConnectedPorts();
1900
+ const { allPortsFound, portsWithSelectors: ports } = this._findConnectedPorts();
1604
1901
  if (!allPortsFound) return;
1605
1902
  const obstacles = [];
1606
1903
  const connection = {
@@ -1671,17 +1968,20 @@ var Resistor = class extends NormalComponent {
1671
1968
  }
1672
1969
  pin1 = this.portMap.pin1;
1673
1970
  pin2 = this.portMap.pin2;
1971
+ doInitialCreateNetsFromProps() {
1972
+ this._createNetsFromProps([this.props.pullupFor, this.props.pullupTo]);
1973
+ }
1674
1974
  doInitialCreateTracesFromProps() {
1675
1975
  if (this.props.pullupFor && this.props.pullupTo) {
1676
1976
  this.add(
1677
1977
  new Trace({
1678
- from: `${this.getOpaqueGroupSelector()} > port.1`,
1978
+ from: `${this.getSubcircuitSelector()} > port.1`,
1679
1979
  to: this.props.pullupFor
1680
1980
  })
1681
1981
  );
1682
1982
  this.add(
1683
1983
  new Trace({
1684
- from: `${this.getOpaqueGroupSelector()} > port.2`,
1984
+ from: `${this.getSubcircuitSelector()} > port.2`,
1685
1985
  to: this.props.pullupTo
1686
1986
  })
1687
1987
  );
@@ -1743,17 +2043,23 @@ var Capacitor = class extends NormalComponent {
1743
2043
  }
1744
2044
  pin1 = this.portMap.pin1;
1745
2045
  pin2 = this.portMap.pin2;
2046
+ doInitialCreateNetsFromProps() {
2047
+ this._createNetsFromProps([
2048
+ this.props.decouplingFor,
2049
+ this.props.decouplingTo
2050
+ ]);
2051
+ }
1746
2052
  doInitialCreateTracesFromProps() {
1747
2053
  if (this.props.decouplingFor && this.props.decouplingTo) {
1748
2054
  this.add(
1749
2055
  new Trace({
1750
- from: `${this.getOpaqueGroupSelector()} > port.1`,
2056
+ from: `${this.getSubcircuitSelector()} > port.1`,
1751
2057
  to: this.props.decouplingFor
1752
2058
  })
1753
2059
  );
1754
2060
  this.add(
1755
2061
  new Trace({
1756
- from: `${this.getOpaqueGroupSelector()} > port.2`,
2062
+ from: `${this.getSubcircuitSelector()} > port.2`,
1757
2063
  to: this.props.decouplingTo
1758
2064
  })
1759
2065
  );
@@ -1774,17 +2080,6 @@ var Capacitor = class extends NormalComponent {
1774
2080
  }
1775
2081
  };
1776
2082
 
1777
- // lib/components/primitive-components/Net.ts
1778
- import { z as z5 } from "zod";
1779
- var netProps = z5.object({
1780
- name: z5.string()
1781
- });
1782
- var Net = class extends PrimitiveComponent {
1783
- getPortSelector() {
1784
- return `net.${this.props.name}`;
1785
- }
1786
- };
1787
-
1788
2083
  // lib/components/primitive-components/TraceHint.ts
1789
2084
  import "@tscircuit/props";
1790
2085
  import { applyToPoint as applyToPoint4 } from "transformation-matrix";
@@ -1830,17 +2125,6 @@ var TraceHint = class extends PrimitiveComponent {
1830
2125
  }
1831
2126
  };
1832
2127
 
1833
- // lib/components/primitive-components/Group.ts
1834
- import { groupProps } from "@tscircuit/props";
1835
- import "transformation-matrix";
1836
- var Group = class extends PrimitiveComponent {
1837
- get config() {
1838
- return {
1839
- zodProps: groupProps
1840
- };
1841
- }
1842
- };
1843
-
1844
2128
  // lib/components/normal-components/Chip.ts
1845
2129
  import { chipProps } from "@tscircuit/props";
1846
2130
 
package/lib/Project.ts CHANGED
@@ -117,7 +117,7 @@ export class Circuit {
117
117
 
118
118
  selectOne(
119
119
  selector: string,
120
- opts: { type?: "component" | "port" },
120
+ opts?: { type?: "component" | "port" },
121
121
  ): PrimitiveComponent | null {
122
122
  return this.rootComponent?.selectOne(selector, opts) ?? null
123
123
  }
@@ -16,6 +16,7 @@ import {
16
16
  } from "lib/fiber/create-instance-from-react-element"
17
17
  import { getPortFromHints } from "lib/utils/getPortFromHints"
18
18
  import { createComponentsFromSoup } from "lib/utils/createComponentsFromSoup"
19
+ import { Net } from "../primitive-components/Net"
19
20
 
20
21
  export type PortMap<T extends string> = {
21
22
  [K in T]: Port
@@ -307,6 +308,20 @@ export class NormalComponent<
307
308
  return newPorts
308
309
  }
309
310
 
311
+ _createNetsFromProps(propsWithConnections: (string | undefined | null)[]) {
312
+ for (const prop of propsWithConnections) {
313
+ if (typeof prop === "string" && prop.startsWith("net.")) {
314
+ if (!this.getSubcircuit().selectOne(prop)) {
315
+ this.getSubcircuit().add(
316
+ new Net({
317
+ name: prop.split(".")[1],
318
+ }),
319
+ )
320
+ }
321
+ }
322
+ }
323
+ }
324
+
310
325
  /**
311
326
  * Use data from our props to create ports for this component.
312
327
  *