@tscircuit/core 0.0.28 → 0.0.30

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.d.ts CHANGED
@@ -9,9 +9,11 @@ import * as Props from '@tscircuit/props';
9
9
  import { traceProps, groupProps, boardProps, footprintProps, smtPadProps, resistorProps, ledProps, capacitorProps, traceHintProps, chipProps, jumperProps, silkscreenPathProps, platedHoleProps } from '@tscircuit/props';
10
10
 
11
11
  declare class Circuit {
12
- rootComponent: PrimitiveComponent | null;
12
+ firstChild: PrimitiveComponent | null;
13
13
  children: PrimitiveComponent[];
14
14
  db: SoupUtilObjects;
15
+ root: Circuit | null;
16
+ isRoot: boolean;
15
17
  _hasRenderedAtleastOnce: boolean;
16
18
  constructor();
17
19
  add(componentOrElm: PrimitiveComponent | ReactElement): void;
@@ -27,8 +29,8 @@ declare class Circuit {
27
29
  previewName: string;
28
30
  tscircuitApiKey?: string;
29
31
  }): Promise<void>;
30
- computeGlobalSchematicTransform(): Matrix;
31
- computeGlobalPcbTransform(): Matrix;
32
+ computeSchematicGlobalTransform(): Matrix;
33
+ computePcbGlobalTransform(): Matrix;
32
34
  selectAll(selector: string): PrimitiveComponent[];
33
35
  selectOne(selector: string, opts?: {
34
36
  type?: "component" | "port";
@@ -36,7 +38,7 @@ declare class Circuit {
36
38
  }
37
39
  declare const Project: typeof Circuit;
38
40
 
39
- declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPortRender", "PcbPrimitiveRender", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "CadModelRender", "PcbAnalysis"];
41
+ declare const orderedRenderPhases: readonly ["ReactSubtreesRender", "InitializePortsFromChildren", "CreateNetsFromProps", "CreateTracesFromProps", "SourceRender", "SourceParentAttachment", "PortDiscovery", "PortMatching", "SourceTraceRender", "SchematicComponentRender", "SchematicLayout", "SchematicPortRender", "SchematicTraceRender", "PcbComponentRender", "PcbPortRender", "PcbPrimitiveRender", "PcbParentAttachment", "PcbLayout", "PcbTraceRender", "PcbRouteNetIslands", "PcbComponentSizeCalculation", "CadModelRender"];
40
42
  type RenderPhase = (typeof orderedRenderPhases)[number];
41
43
  type RenderPhaseFn<K extends RenderPhase = RenderPhase> = `doInitial${K}` | `update${K}` | `remove${K}`;
42
44
  type RenderPhaseStates = Record<RenderPhase, {
@@ -91,7 +93,6 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
91
93
  children: PrimitiveComponent[];
92
94
  childrenPendingRemoval: PrimitiveComponent[];
93
95
  get config(): BaseComponentConfig;
94
- project: Circuit | null;
95
96
  props: z.input<ZodProps>;
96
97
  _parsedProps: z.infer<ZodProps>;
97
98
  componentName: string;
@@ -103,14 +104,13 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
103
104
  * subcircuits and their selectors will not interact with each other (even if the
104
105
  * components share the same names) unless you explicitly break out some ports
105
106
  */
106
- get isSubcircuit(): boolean;
107
+ get isSubcircuit(): any;
107
108
  source_group_id: string | null;
108
109
  source_component_id: string | null;
109
110
  schematic_component_id: string | null;
110
111
  pcb_component_id: string | null;
111
112
  cad_component_id: string | null;
112
113
  constructor(props: z.input<ZodProps>);
113
- setProject(project: Circuit): void;
114
114
  setProps(props: Partial<z.input<ZodProps>>): void;
115
115
  /**
116
116
  * Computes a transformation matrix from the props of this component for PCB
@@ -152,6 +152,7 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
152
152
  x: number;
153
153
  y: number;
154
154
  };
155
+ get root(): Circuit | null;
155
156
  onAddToParent(parent: PrimitiveComponent): void;
156
157
  /**
157
158
  * Called whenever the props change
@@ -170,6 +171,10 @@ declare abstract class PrimitiveComponent<ZodProps extends ZodType = any> extend
170
171
  getNameAndAliases(): string[];
171
172
  isMatchingNameOrAlias(name: string): boolean;
172
173
  isMatchingAnyOf(aliases: Array<string | number>): boolean;
174
+ getPcbSize(): {
175
+ width: number;
176
+ height: number;
177
+ };
173
178
  doesSelectorMatch(selector: string): boolean;
174
179
  getSubcircuit(): PrimitiveComponent;
175
180
  selectAll(selector: string): PrimitiveComponent[];
@@ -548,9 +553,15 @@ declare class NormalComponent<ZodProps extends ZodType = any, PortNames extends
548
553
  */
549
554
  doInitialSchematicComponentRender(): void;
550
555
  doInitialPcbComponentRender(): void;
551
- doInitialPcbAnalysis(): void;
556
+ /**
557
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
558
+ * the width/height of the component
559
+ */
560
+ doInitialPcbComponentSizeCalculation(): void;
552
561
  _renderReactSubtree(element: ReactElement): ReactSubtree;
562
+ doInitialInitializePortsFromChildren(): void;
553
563
  doInitialReactSubtreesRender(): void;
564
+ _hasExistingPortExactly(port1: Port): boolean;
554
565
  add(componentOrElm: PrimitiveComponent | ReactElement): void;
555
566
  getPortsFromFootprint(): Port[];
556
567
  getPortsFromSchematicSymbol(): Port[];
@@ -705,6 +716,10 @@ declare class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
705
716
  height?: string | number | undefined;
706
717
  }>]>;
707
718
  };
719
+ getPcbSize(): {
720
+ width: number;
721
+ height: number;
722
+ };
708
723
  doInitialPortMatching(): void;
709
724
  doInitialPcbPrimitiveRender(): void;
710
725
  }
@@ -3329,6 +3344,10 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3329
3344
  portHints?: (string | number)[] | undefined;
3330
3345
  }>]>;
3331
3346
  };
3347
+ getPcbSize(): {
3348
+ width: number;
3349
+ height: number;
3350
+ };
3332
3351
  doInitialPortMatching(): void;
3333
3352
  doInitialPcbPrimitiveRender(): void;
3334
3353
  }
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
+ "InitializePortsFromChildren",
40
41
  "CreateNetsFromProps",
41
42
  "CreateTracesFromProps",
42
43
  "SourceRender",
@@ -56,8 +57,8 @@ var orderedRenderPhases = [
56
57
  "PcbLayout",
57
58
  "PcbTraceRender",
58
59
  "PcbRouteNetIslands",
59
- "CadModelRender",
60
- "PcbAnalysis"
60
+ "PcbComponentSizeCalculation",
61
+ "CadModelRender"
61
62
  ];
62
63
  var globalRenderCounter = 0;
63
64
  var Renderable = class {
@@ -169,7 +170,6 @@ var PrimitiveComponent = class extends Renderable {
169
170
  zodProps: z.object({}).passthrough()
170
171
  };
171
172
  }
172
- project = null;
173
173
  props;
174
174
  _parsedProps;
175
175
  componentName = "";
@@ -183,7 +183,7 @@ var PrimitiveComponent = class extends Renderable {
183
183
  */
184
184
  get isSubcircuit() {
185
185
  return Boolean(this.props.subcircuit) || // Implied opaque group for top-level group
186
- this.lowercaseComponentName === "group" && this?.parent?.props?.name === "$root";
186
+ this.lowercaseComponentName === "group" && this?.parent?.isRoot;
187
187
  }
188
188
  source_group_id = null;
189
189
  source_component_id = null;
@@ -204,12 +204,6 @@ var PrimitiveComponent = class extends Renderable {
204
204
  this.lowercaseComponentName = this.componentName.toLowerCase();
205
205
  }
206
206
  }
207
- setProject(project) {
208
- this.project = project;
209
- for (const c of this.children) {
210
- c.setProject(project);
211
- }
212
- }
213
207
  setProps(props) {
214
208
  const newProps = this.config.zodProps.parse({
215
209
  ...this.props,
@@ -315,9 +309,11 @@ var PrimitiveComponent = class extends Renderable {
315
309
  getGlobalSchematicPosition() {
316
310
  return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 });
317
311
  }
312
+ get root() {
313
+ return this.parent?.root ?? null;
314
+ }
318
315
  onAddToParent(parent) {
319
316
  this.parent = parent;
320
- this.project = parent.project;
321
317
  }
322
318
  /**
323
319
  * Called whenever the props change
@@ -370,6 +366,9 @@ var PrimitiveComponent = class extends Renderable {
370
366
  (a) => aliases.map((a2) => a2.toString()).includes(a)
371
367
  );
372
368
  }
369
+ getPcbSize() {
370
+ throw new Error(`getPcbSize not implemented for ${this.componentName}`);
371
+ }
373
372
  doesSelectorMatch(selector) {
374
373
  const myTypeNames = [this.componentName, this.lowercaseComponentName];
375
374
  const myClassNames = [this._parsedProps.name].filter(Boolean);
@@ -578,7 +577,7 @@ var Port = class extends PrimitiveComponent {
578
577
  return connectedTraces;
579
578
  }
580
579
  doInitialSourceRender() {
581
- const { db } = this.project;
580
+ const { db } = this.root;
582
581
  const { _parsedProps: props } = this;
583
582
  const port_hints = this.getNameAndAliases();
584
583
  const source_port = db.source_port.insert({
@@ -590,7 +589,7 @@ var Port = class extends PrimitiveComponent {
590
589
  this.source_port_id = source_port.source_port_id;
591
590
  }
592
591
  doInitialSourceParentAttachment() {
593
- const { db } = this.project;
592
+ const { db } = this.root;
594
593
  if (!this.parent?.source_component_id) {
595
594
  throw new Error(
596
595
  `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`
@@ -607,7 +606,7 @@ var Port = class extends PrimitiveComponent {
607
606
  * to exist)
608
607
  */
609
608
  doInitialPcbPortRender() {
610
- const { db } = this.project;
609
+ const { db } = this.root;
611
610
  const { matchedComponents } = this;
612
611
  if (!this.parent?.pcb_component_id) {
613
612
  throw new Error(
@@ -637,7 +636,7 @@ var Port = class extends PrimitiveComponent {
637
636
  }
638
637
  }
639
638
  doInitialSchematicPortRender() {
640
- const { db } = this.project;
639
+ const { db } = this.root;
641
640
  const { _parsedProps: props } = this;
642
641
  if (!this.parent) return;
643
642
  const center = this.getGlobalSchematicPosition();
@@ -840,6 +839,18 @@ var SmtPad = class extends PrimitiveComponent {
840
839
  zodProps: smtPadProps
841
840
  };
842
841
  }
842
+ getPcbSize() {
843
+ const { _parsedProps: props } = this;
844
+ if (props.shape === "circle") {
845
+ return { width: props.radius * 2, height: props.radius * 2 };
846
+ }
847
+ if (props.shape === "rect") {
848
+ return { width: props.width, height: props.height };
849
+ }
850
+ throw new Error(
851
+ `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
852
+ );
853
+ }
843
854
  doInitialPortMatching() {
844
855
  const parentPorts = (this.parent?.children ?? []).filter(
845
856
  (c) => c.componentName === "Port"
@@ -856,7 +867,7 @@ var SmtPad = class extends PrimitiveComponent {
856
867
  }
857
868
  }
858
869
  doInitialPcbPrimitiveRender() {
859
- const { db } = this.project;
870
+ const { db } = this.root;
860
871
  const { _parsedProps: props } = this;
861
872
  if (!props.portHints) return;
862
873
  const position = this.getGlobalPcbPosition();
@@ -904,7 +915,7 @@ var SilkscreenPath = class extends PrimitiveComponent {
904
915
  };
905
916
  }
906
917
  doInitialPcbPrimitiveRender() {
907
- const { db } = this.project;
918
+ const { db } = this.root;
908
919
  const { _parsedProps: props } = this;
909
920
  const layer = props.layer ?? "top";
910
921
  if (layer !== "top" && layer !== "bottom") {
@@ -944,6 +955,18 @@ var PlatedHole = class extends PrimitiveComponent {
944
955
  zodProps: platedHoleProps
945
956
  };
946
957
  }
958
+ getPcbSize() {
959
+ const { _parsedProps: props } = this;
960
+ if (props.shape === "circle") {
961
+ return { width: props.outerDiameter, height: props.outerDiameter };
962
+ }
963
+ if (props.shape === "oval") {
964
+ return { width: props.outerWidth, height: props.outerHeight };
965
+ }
966
+ throw new Error(
967
+ `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
968
+ );
969
+ }
947
970
  doInitialPortMatching() {
948
971
  const parentPorts = (this.parent?.children ?? []).filter(
949
972
  (c) => c.componentName === "Port"
@@ -960,7 +983,7 @@ var PlatedHole = class extends PrimitiveComponent {
960
983
  }
961
984
  }
962
985
  doInitialPcbPrimitiveRender() {
963
- const { db } = this.project;
986
+ const { db } = this.root;
964
987
  const { _parsedProps: props } = this;
965
988
  if (!props.portHints) return;
966
989
  const position = this.getGlobalPcbPosition();
@@ -1058,7 +1081,7 @@ var Net = class extends PrimitiveComponent {
1058
1081
  return `net.${this.props.name}`;
1059
1082
  }
1060
1083
  doInitialSourceComponentRender() {
1061
- const { db } = this.project;
1084
+ const { db } = this.root;
1062
1085
  const { _parsedProps: props } = this;
1063
1086
  const net = db.source_net.insert({
1064
1087
  name: props.name,
@@ -1107,7 +1130,7 @@ var Net = class extends PrimitiveComponent {
1107
1130
  * such that the nets are fully connected
1108
1131
  */
1109
1132
  doInitialPcbRouteNetIslands() {
1110
- const { db } = this.project;
1133
+ const { db } = this.root;
1111
1134
  const { _parsedProps: props } = this;
1112
1135
  const traces = this._getAllDirectlyConnectedTraces().filter(
1113
1136
  (trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0
@@ -1213,6 +1236,7 @@ var NormalComponent = class extends PrimitiveComponent {
1213
1236
  */
1214
1237
  initPorts() {
1215
1238
  const { config } = this;
1239
+ const portsToCreate = [];
1216
1240
  if (config.schematicSymbolName) {
1217
1241
  const sym = symbols3[`${config.schematicSymbolName}_horz`];
1218
1242
  if (!sym) return;
@@ -1220,27 +1244,31 @@ var NormalComponent = class extends PrimitiveComponent {
1220
1244
  const port = getPortFromHints(symPort.labels);
1221
1245
  if (port) {
1222
1246
  port.schematicSymbolPortDef = symPort;
1223
- this.add(port);
1247
+ portsToCreate.push(port);
1224
1248
  }
1225
1249
  }
1250
+ this.addAll(portsToCreate);
1226
1251
  return;
1227
1252
  }
1228
1253
  const portsFromFootprint = this.getPortsFromFootprint();
1229
- this.addAll(portsFromFootprint);
1254
+ portsToCreate.push(...portsFromFootprint);
1230
1255
  const pinLabels = this._parsedProps.pinLabels;
1231
1256
  if (pinLabels) {
1232
1257
  for (let [pinNumber, label] of Object.entries(pinLabels)) {
1233
1258
  pinNumber = pinNumber.replace("pin", "");
1234
- const port = this.selectOne(`port[pinNumber='${pinNumber}']`);
1235
- if (!port) {
1259
+ const existingPort = portsToCreate.find(
1260
+ (p) => p._parsedProps.pinNumber === Number(pinNumber)
1261
+ );
1262
+ if (!existingPort) {
1236
1263
  throw new Error(
1237
1264
  `Could not find port for pin number ${pinNumber} in chip ${this.getString()}`
1238
1265
  );
1239
1266
  }
1240
- port.externallyAddedAliases.push(label);
1241
- port.props.name = label;
1267
+ existingPort.externallyAddedAliases.push(label);
1268
+ existingPort.props.name = label;
1242
1269
  }
1243
1270
  }
1271
+ this.addAll(portsToCreate);
1244
1272
  }
1245
1273
  _addChildrenFromStringFootprint() {
1246
1274
  const { footprint } = this.props;
@@ -1278,7 +1306,7 @@ var NormalComponent = class extends PrimitiveComponent {
1278
1306
  doInitialSourceRender() {
1279
1307
  const ftype = this.config.sourceFtype;
1280
1308
  if (!ftype) return;
1281
- const { db } = this.project;
1309
+ const { db } = this.root;
1282
1310
  const { _parsedProps: props } = this;
1283
1311
  const source_component = db.source_component.insert({
1284
1312
  ftype,
@@ -1295,7 +1323,7 @@ var NormalComponent = class extends PrimitiveComponent {
1295
1323
  * You can override this method to do more complicated things.
1296
1324
  */
1297
1325
  doInitialSchematicComponentRender() {
1298
- const { db } = this.project;
1326
+ const { db } = this.root;
1299
1327
  const { schematicSymbolName } = this.config;
1300
1328
  if (!schematicSymbolName) return;
1301
1329
  const symbol_name = `${this.config.schematicSymbolName}_horz`;
@@ -1314,11 +1342,11 @@ var NormalComponent = class extends PrimitiveComponent {
1314
1342
  this.schematic_component_id = schematic_component2.schematic_component_id;
1315
1343
  }
1316
1344
  doInitialPcbComponentRender() {
1317
- const { db } = this.project;
1345
+ const { db } = this.root;
1318
1346
  const { _parsedProps: props } = this;
1319
1347
  const pcb_component = db.pcb_component.insert({
1320
- center: { x: this.props.pcbX ?? 0, y: this.props.pcbY ?? 0 },
1321
- // width/height are computed in the PcbAnalysis phase
1348
+ center: this.getGlobalPcbPosition(),
1349
+ // width/height are computed in the PcbComponentSizeCalculation phase
1322
1350
  width: 0,
1323
1351
  height: 0,
1324
1352
  layer: props.layer ?? "top",
@@ -1327,9 +1355,34 @@ var NormalComponent = class extends PrimitiveComponent {
1327
1355
  });
1328
1356
  this.pcb_component_id = pcb_component.pcb_component_id;
1329
1357
  }
1330
- doInitialPcbAnalysis() {
1331
- const { db } = this.project;
1358
+ /**
1359
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
1360
+ * the width/height of the component
1361
+ */
1362
+ doInitialPcbComponentSizeCalculation() {
1363
+ if (!this.pcb_component_id) return;
1364
+ const { db } = this.root;
1332
1365
  const { _parsedProps: props } = this;
1366
+ let minX = Infinity;
1367
+ let minY = Infinity;
1368
+ let maxX = -Infinity;
1369
+ let maxY = -Infinity;
1370
+ for (const child of this.children) {
1371
+ if (child.isPcbPrimitive) {
1372
+ const { x, y } = child.getGlobalPcbPosition();
1373
+ const { width: width2, height: height2 } = child.getPcbSize();
1374
+ minX = Math.min(minX, x - width2 / 2);
1375
+ minY = Math.min(minY, y - height2 / 2);
1376
+ maxX = Math.max(maxX, x + width2 / 2);
1377
+ maxY = Math.max(maxY, y + height2 / 2);
1378
+ }
1379
+ }
1380
+ const width = maxX - minX;
1381
+ const height = maxY - minY;
1382
+ db.pcb_component.update(this.pcb_component_id, {
1383
+ width,
1384
+ height
1385
+ });
1333
1386
  }
1334
1387
  _renderReactSubtree(element) {
1335
1388
  return {
@@ -1337,6 +1390,9 @@ var NormalComponent = class extends PrimitiveComponent {
1337
1390
  component: createInstanceFromReactElement(element)
1338
1391
  };
1339
1392
  }
1393
+ doInitialInitializePortsFromChildren() {
1394
+ this.initPorts();
1395
+ }
1340
1396
  doInitialReactSubtreesRender() {
1341
1397
  if (isReactElement2(this.props.footprint)) {
1342
1398
  if (this.reactSubtrees.some((rs) => rs.element === this.props.footprint))
@@ -1346,6 +1402,16 @@ var NormalComponent = class extends PrimitiveComponent {
1346
1402
  this.add(subtree.component);
1347
1403
  }
1348
1404
  }
1405
+ _hasExistingPortExactly(port1) {
1406
+ const existingPorts = this.children.filter(
1407
+ (c) => c.componentName === "Port"
1408
+ );
1409
+ return existingPorts.some((port2) => {
1410
+ const aliases1 = port1.getNameAndAliases();
1411
+ const aliases2 = port2.getNameAndAliases();
1412
+ return aliases1.length === aliases2.length && aliases1.every((alias) => aliases2.includes(alias));
1413
+ });
1414
+ }
1349
1415
  add(componentOrElm) {
1350
1416
  let component;
1351
1417
  if (isReactElement2(componentOrElm)) {
@@ -1355,6 +1421,20 @@ var NormalComponent = class extends PrimitiveComponent {
1355
1421
  } else {
1356
1422
  component = componentOrElm;
1357
1423
  }
1424
+ if (component.componentName === "Port") {
1425
+ if (this._hasExistingPortExactly(component)) return;
1426
+ const existingPorts = this.children.filter(
1427
+ (c) => c.componentName === "Port"
1428
+ );
1429
+ const conflictingPort = existingPorts.find(
1430
+ (p) => p.isMatchingAnyOf(component.getNameAndAliases())
1431
+ );
1432
+ if (conflictingPort) {
1433
+ throw new Error(
1434
+ `Conflicting ports added. Port 1: ${conflictingPort}, Port 2: ${component}`
1435
+ );
1436
+ }
1437
+ }
1358
1438
  super.add(component);
1359
1439
  }
1360
1440
  getPortsFromFootprint() {
@@ -1382,6 +1462,11 @@ var NormalComponent = class extends PrimitiveComponent {
1382
1462
  if (!newPort) continue;
1383
1463
  newPorts2.push(newPort);
1384
1464
  }
1465
+ if (newPorts2.length === 0) {
1466
+ throw new Error(
1467
+ `No ports found in chip ${this} (define a schPortArrangement, a footprint or add portHints to elements of the footprint)`
1468
+ );
1469
+ }
1385
1470
  return newPorts2;
1386
1471
  }
1387
1472
  const newPorts = [];
@@ -1474,7 +1559,7 @@ var Board = class extends Group {
1474
1559
  };
1475
1560
  }
1476
1561
  doInitialPcbComponentRender() {
1477
- const { db } = this.project;
1562
+ const { db } = this.root;
1478
1563
  const { _parsedProps: props } = this;
1479
1564
  if (!props.width || !props.height) {
1480
1565
  throw new Error("Board width and height or an outline are required");
@@ -1487,7 +1572,7 @@ var Board = class extends Group {
1487
1572
  this.pcb_board_id = pcb_board.pcb_board_id;
1488
1573
  }
1489
1574
  removePcbComponentRender() {
1490
- const { db } = this.project;
1575
+ const { db } = this.root;
1491
1576
  if (!this.pcb_board_id) return;
1492
1577
  db.pcb_board.delete(this.pcb_board_id);
1493
1578
  this.pcb_board_id = null;
@@ -1700,7 +1785,7 @@ var Trace = class extends PrimitiveComponent {
1700
1785
  );
1701
1786
  }
1702
1787
  _findConnectedPorts() {
1703
- const { db } = this.project;
1788
+ const { db } = this.root;
1704
1789
  const { _parsedProps: props, parent } = this;
1705
1790
  if (!parent) throw new Error("Trace has no parent");
1706
1791
  const portSelectors = this.getTracePortPathSelectors();
@@ -1784,7 +1869,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1784
1869
  createNetsFromProps(this, this.getTracePathNetSelectors());
1785
1870
  }
1786
1871
  doInitialSourceTraceRender() {
1787
- const { db } = this.project;
1872
+ const { db } = this.root;
1788
1873
  const { _parsedProps: props, parent } = this;
1789
1874
  if (!parent) {
1790
1875
  this.renderError("Trace has no parent");
@@ -1800,7 +1885,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1800
1885
  this.source_trace_id = trace.source_trace_id;
1801
1886
  }
1802
1887
  doInitialPcbTraceRender() {
1803
- const { db } = this.project;
1888
+ const { db } = this.root;
1804
1889
  const { _parsedProps: props, parent } = this;
1805
1890
  if (!parent) throw new Error("Trace has no parent");
1806
1891
  const { allPortsFound, ports } = this._findConnectedPorts();
@@ -1881,7 +1966,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1881
1966
  `Could not find a common layer (using hints) for trace ${this.getString()}`
1882
1967
  );
1883
1968
  }
1884
- const obstacles = getObstaclesFromSoup(this.project.db.toArray());
1969
+ const obstacles = getObstaclesFromSoup(this.root.db.toArray());
1885
1970
  markObstaclesAsConnected(
1886
1971
  obstacles,
1887
1972
  orderedRouteObjectives,
@@ -1947,7 +2032,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
1947
2032
  this.pcb_trace_id = pcb_trace.pcb_trace_id;
1948
2033
  }
1949
2034
  doInitialSchematicTraceRender() {
1950
- const { db } = this.project;
2035
+ const { db } = this.root;
1951
2036
  const { _parsedProps: props, parent } = this;
1952
2037
  if (!parent) throw new Error("Trace has no parent");
1953
2038
  const { allPortsFound, portsWithSelectors: ports } = this._findConnectedPorts();
@@ -2041,7 +2126,7 @@ var Resistor = class extends NormalComponent {
2041
2126
  }
2042
2127
  }
2043
2128
  doInitialSourceRender() {
2044
- const { db } = this.project;
2129
+ const { db } = this.root;
2045
2130
  const { _parsedProps: props } = this;
2046
2131
  const source_component = db.source_component.insert({
2047
2132
  ftype: "simple_resistor",
@@ -2119,7 +2204,7 @@ var Capacitor = class extends NormalComponent {
2119
2204
  }
2120
2205
  }
2121
2206
  doInitialSourceRender() {
2122
- const { db } = this.project;
2207
+ const { db } = this.root;
2123
2208
  const { _parsedProps: props } = this;
2124
2209
  const source_component = db.source_component.insert({
2125
2210
  ftype: "simple_capacitor",
@@ -2139,7 +2224,7 @@ import { applyToPoint as applyToPoint4 } from "transformation-matrix";
2139
2224
  var TraceHint = class extends PrimitiveComponent {
2140
2225
  matchedPort = null;
2141
2226
  doInitialPortMatching() {
2142
- const { db } = this.project;
2227
+ const { db } = this.root;
2143
2228
  const { _parsedProps: props, parent } = this;
2144
2229
  if (!parent) return;
2145
2230
  if (parent.componentName === "Trace") {
@@ -2470,7 +2555,7 @@ var Chip = class extends NormalComponent {
2470
2555
  };
2471
2556
  }
2472
2557
  doInitialSourceRender() {
2473
- const { db } = this.project;
2558
+ const { db } = this.root;
2474
2559
  const { _parsedProps: props } = this;
2475
2560
  const source_component = db.source_component.insert({
2476
2561
  ftype: "simple_chip",
@@ -2481,7 +2566,7 @@ var Chip = class extends NormalComponent {
2481
2566
  this.source_component_id = source_component.source_component_id;
2482
2567
  }
2483
2568
  doInitialSchematicComponentRender() {
2484
- const { db } = this.project;
2569
+ const { db } = this.root;
2485
2570
  const { _parsedProps: props } = this;
2486
2571
  const ports = this.children.filter((child) => child instanceof Port);
2487
2572
  const pinSpacing = props.schPinSpacing ?? 0.2;
@@ -2513,7 +2598,7 @@ var Chip = class extends NormalComponent {
2513
2598
  this.schematic_component_id = schematic_component2.schematic_component_id;
2514
2599
  }
2515
2600
  doInitialPcbComponentRender() {
2516
- const { db } = this.project;
2601
+ const { db } = this.root;
2517
2602
  const { _parsedProps: props } = this;
2518
2603
  const pcb_component = db.pcb_component.insert({
2519
2604
  center: { x: props.pcbX ?? 0, y: props.pcbY ?? 0 },
@@ -2539,7 +2624,7 @@ var Jumper = class extends NormalComponent {
2539
2624
  };
2540
2625
  }
2541
2626
  doInitialSourceRender() {
2542
- const { db } = this.project;
2627
+ const { db } = this.root;
2543
2628
  const { _parsedProps: props } = this;
2544
2629
  const source_component = db.source_component.insert({
2545
2630
  ftype: "simple_chip",
@@ -2551,7 +2636,7 @@ var Jumper = class extends NormalComponent {
2551
2636
  this.source_component_id = source_component.source_component_id;
2552
2637
  }
2553
2638
  doInitialSchematicComponentRender() {
2554
- const { db } = this.project;
2639
+ const { db } = this.root;
2555
2640
  const { _parsedProps: props } = this;
2556
2641
  const ports = this.children.filter((child) => child instanceof Port);
2557
2642
  const pinSpacing = props.schPinSpacing ?? 0.2;
@@ -2586,7 +2671,7 @@ var Jumper = class extends NormalComponent {
2586
2671
  this.schematic_component_id = schematic_component2.schematic_component_id;
2587
2672
  }
2588
2673
  doInitialPcbComponentRender() {
2589
- const { db } = this.project;
2674
+ const { db } = this.root;
2590
2675
  const { _parsedProps: props } = this;
2591
2676
  const pcb_component = db.pcb_component.insert({
2592
2677
  center: { x: props.pcbX ?? 0, y: props.pcbY ?? 0 },
@@ -2607,13 +2692,16 @@ import { su } from "@tscircuit/soup-util";
2607
2692
  import { isValidElement as isValidElement2 } from "react";
2608
2693
  import { identity as identity5 } from "transformation-matrix";
2609
2694
  var Circuit = class {
2610
- rootComponent = null;
2695
+ firstChild = null;
2611
2696
  children;
2612
2697
  db;
2698
+ root = null;
2699
+ isRoot = true;
2613
2700
  _hasRenderedAtleastOnce = false;
2614
2701
  constructor() {
2615
2702
  this.children = [];
2616
2703
  this.db = su([]);
2704
+ this.root = this;
2617
2705
  }
2618
2706
  add(componentOrElm) {
2619
2707
  let component;
@@ -2625,9 +2713,9 @@ var Circuit = class {
2625
2713
  this.children.push(component);
2626
2714
  }
2627
2715
  _guessRootComponent() {
2628
- if (this.rootComponent) return;
2716
+ if (this.firstChild) return;
2629
2717
  if (this.children.length === 1) {
2630
- this.rootComponent = this.children[0];
2718
+ this.firstChild = this.children[0];
2631
2719
  return;
2632
2720
  }
2633
2721
  if (this.children.length === 0) {
@@ -2638,7 +2726,7 @@ var Circuit = class {
2638
2726
  if (this.children.length > 0) {
2639
2727
  const board = this.children.find((c) => c.componentName === "Board") ?? null;
2640
2728
  if (board) {
2641
- this.rootComponent = board;
2729
+ this.firstChild = board;
2642
2730
  return;
2643
2731
  }
2644
2732
  }
@@ -2647,13 +2735,13 @@ var Circuit = class {
2647
2735
  );
2648
2736
  }
2649
2737
  render() {
2650
- if (!this.rootComponent) {
2738
+ if (!this.firstChild) {
2651
2739
  this._guessRootComponent();
2652
2740
  }
2653
- const { rootComponent, db } = this;
2654
- if (!rootComponent) throw new Error("Project has no root component");
2655
- rootComponent.setProject(this);
2656
- rootComponent.runRenderCycle();
2741
+ const { firstChild, db } = this;
2742
+ if (!firstChild) throw new Error("Project has no root component");
2743
+ firstChild.parent = this;
2744
+ firstChild.runRenderCycle();
2657
2745
  this._hasRenderedAtleastOnce = true;
2658
2746
  }
2659
2747
  getSoup() {
@@ -2677,17 +2765,17 @@ var Circuit = class {
2677
2765
  const previewOpts = typeof previewNameOrOpts === "object" ? previewNameOrOpts : { previewName: previewNameOrOpts };
2678
2766
  throw new Error("project.preview is not yet implemented");
2679
2767
  }
2680
- computeGlobalSchematicTransform() {
2768
+ computeSchematicGlobalTransform() {
2681
2769
  return identity5();
2682
2770
  }
2683
- computeGlobalPcbTransform() {
2771
+ computePcbGlobalTransform() {
2684
2772
  return identity5();
2685
2773
  }
2686
2774
  selectAll(selector) {
2687
- return this.rootComponent?.selectAll(selector) ?? [];
2775
+ return this.firstChild?.selectAll(selector) ?? [];
2688
2776
  }
2689
2777
  selectOne(selector, opts) {
2690
- return this.rootComponent?.selectOne(selector, opts) ?? null;
2778
+ return this.firstChild?.selectOne(selector, opts) ?? null;
2691
2779
  }
2692
2780
  };
2693
2781
  var Project = Circuit;
package/lib/Project.ts CHANGED
@@ -7,15 +7,18 @@ import { createInstanceFromReactElement } from "./fiber/create-instance-from-rea
7
7
  import { identity, type Matrix } from "transformation-matrix"
8
8
 
9
9
  export class Circuit {
10
- rootComponent: PrimitiveComponent | null = null
10
+ firstChild: PrimitiveComponent | null = null
11
11
  children: PrimitiveComponent[]
12
12
  db: SoupUtilObjects
13
+ root: Circuit | null = null
14
+ isRoot = true
13
15
 
14
16
  _hasRenderedAtleastOnce = false
15
17
 
16
18
  constructor() {
17
19
  this.children = []
18
20
  this.db = su([])
21
+ this.root = this
19
22
  }
20
23
 
21
24
  add(componentOrElm: PrimitiveComponent | ReactElement) {
@@ -30,9 +33,9 @@ export class Circuit {
30
33
  }
31
34
 
32
35
  _guessRootComponent() {
33
- if (this.rootComponent) return
36
+ if (this.firstChild) return
34
37
  if (this.children.length === 1) {
35
- this.rootComponent = this.children[0]
38
+ this.firstChild = this.children[0]
36
39
  return
37
40
  }
38
41
  if (this.children.length === 0) {
@@ -46,7 +49,7 @@ export class Circuit {
46
49
  this.children.find((c) => c.componentName === "Board") ?? null
47
50
 
48
51
  if (board) {
49
- this.rootComponent = board
52
+ this.firstChild = board
50
53
  return
51
54
  }
52
55
  }
@@ -56,16 +59,14 @@ export class Circuit {
56
59
  }
57
60
 
58
61
  render() {
59
- if (!this.rootComponent) {
62
+ if (!this.firstChild) {
60
63
  this._guessRootComponent()
61
64
  }
62
- const { rootComponent, db } = this
65
+ const { firstChild, db } = this
63
66
 
64
- if (!rootComponent) throw new Error("Project has no root component")
65
-
66
- rootComponent.setProject(this)
67
-
68
- rootComponent.runRenderCycle()
67
+ if (!firstChild) throw new Error("Project has no root component")
68
+ firstChild.parent = this as any
69
+ firstChild.runRenderCycle()
69
70
  this._hasRenderedAtleastOnce = true
70
71
  }
71
72
 
@@ -103,23 +104,23 @@ export class Circuit {
103
104
  throw new Error("project.preview is not yet implemented")
104
105
  }
105
106
 
106
- computeGlobalSchematicTransform(): Matrix {
107
+ computeSchematicGlobalTransform(): Matrix {
107
108
  return identity()
108
109
  }
109
110
 
110
- computeGlobalPcbTransform(): Matrix {
111
+ computePcbGlobalTransform(): Matrix {
111
112
  return identity()
112
113
  }
113
114
 
114
115
  selectAll(selector: string): PrimitiveComponent[] {
115
- return this.rootComponent?.selectAll(selector) ?? []
116
+ return this.firstChild?.selectAll(selector) ?? []
116
117
  }
117
118
 
118
119
  selectOne(
119
120
  selector: string,
120
121
  opts?: { type?: "component" | "port" },
121
122
  ): PrimitiveComponent | null {
122
- return this.rootComponent?.selectOne(selector, opts) ?? null
123
+ return this.firstChild?.selectOne(selector, opts) ?? null
123
124
  }
124
125
  }
125
126
 
@@ -68,6 +68,7 @@ export class NormalComponent<
68
68
  */
69
69
  initPorts() {
70
70
  const { config } = this
71
+ const portsToCreate: Port[] = []
71
72
  if (config.schematicSymbolName) {
72
73
  const sym = symbols[
73
74
  `${config.schematicSymbolName}_horz` as keyof typeof symbols
@@ -79,32 +80,36 @@ export class NormalComponent<
79
80
 
80
81
  if (port) {
81
82
  port.schematicSymbolPortDef = symPort
82
- this.add(port)
83
+ portsToCreate.push(port)
83
84
  }
84
85
  }
85
86
 
87
+ this.addAll(portsToCreate)
86
88
  return
87
89
  }
88
90
 
89
91
  const portsFromFootprint = this.getPortsFromFootprint()
90
-
91
- this.addAll(portsFromFootprint)
92
+ portsToCreate.push(...portsFromFootprint)
92
93
 
93
94
  const pinLabels: Record<string, string> | undefined =
94
95
  this._parsedProps.pinLabels
95
96
  if (pinLabels) {
96
97
  for (let [pinNumber, label] of Object.entries(pinLabels)) {
97
98
  pinNumber = pinNumber.replace("pin", "")
98
- const port = this.selectOne(`port[pinNumber='${pinNumber}']`)
99
- if (!port) {
99
+ const existingPort = portsToCreate.find(
100
+ (p) => p._parsedProps.pinNumber === Number(pinNumber),
101
+ )
102
+ if (!existingPort) {
100
103
  throw new Error(
101
104
  `Could not find port for pin number ${pinNumber} in chip ${this.getString()}`,
102
105
  )
103
106
  }
104
- port.externallyAddedAliases.push(label)
105
- port.props.name = label
107
+ existingPort.externallyAddedAliases.push(label)
108
+ existingPort.props.name = label
106
109
  }
107
110
  }
111
+
112
+ this.addAll(portsToCreate)
108
113
  }
109
114
 
110
115
  _addChildrenFromStringFootprint() {
@@ -148,7 +153,7 @@ export class NormalComponent<
148
153
  doInitialSourceRender() {
149
154
  const ftype = this.config.sourceFtype
150
155
  if (!ftype) return
151
- const { db } = this.project!
156
+ const { db } = this.root!
152
157
  const { _parsedProps: props } = this
153
158
  const source_component = db.source_component.insert({
154
159
  ftype,
@@ -166,7 +171,7 @@ export class NormalComponent<
166
171
  * You can override this method to do more complicated things.
167
172
  */
168
173
  doInitialSchematicComponentRender() {
169
- const { db } = this.project!
174
+ const { db } = this.root!
170
175
  const { schematicSymbolName } = this.config
171
176
  if (!schematicSymbolName) return
172
177
  // TODO switch between horizontal and vertical based on schRotation
@@ -191,11 +196,11 @@ export class NormalComponent<
191
196
  }
192
197
 
193
198
  doInitialPcbComponentRender() {
194
- const { db } = this.project!
199
+ const { db } = this.root!
195
200
  const { _parsedProps: props } = this
196
201
  const pcb_component = db.pcb_component.insert({
197
- center: { x: this.props.pcbX ?? 0, y: this.props.pcbY ?? 0 },
198
- // width/height are computed in the PcbAnalysis phase
202
+ center: this.getGlobalPcbPosition(),
203
+ // width/height are computed in the PcbComponentSizeCalculation phase
199
204
  width: 0,
200
205
  height: 0,
201
206
  layer: props.layer ?? "top",
@@ -205,11 +210,38 @@ export class NormalComponent<
205
210
  this.pcb_component_id = pcb_component.pcb_component_id
206
211
  }
207
212
 
208
- doInitialPcbAnalysis(): void {
209
- const { db } = this.project!
213
+ /**
214
+ * At this stage, the smtpads/pcb primitives are placed, so we can compute
215
+ * the width/height of the component
216
+ */
217
+ doInitialPcbComponentSizeCalculation(): void {
218
+ if (!this.pcb_component_id) return
219
+ const { db } = this.root!
210
220
  const { _parsedProps: props } = this
211
221
 
212
- // TODO Examine children to compute width/height and pcbX/pcbY
222
+ let minX = Infinity
223
+ let minY = Infinity
224
+ let maxX = -Infinity
225
+ let maxY = -Infinity
226
+
227
+ for (const child of this.children) {
228
+ if (child.isPcbPrimitive) {
229
+ const { x, y } = child.getGlobalPcbPosition()
230
+ const { width, height } = child.getPcbSize()
231
+ minX = Math.min(minX, x - width / 2)
232
+ minY = Math.min(minY, y - height / 2)
233
+ maxX = Math.max(maxX, x + width / 2)
234
+ maxY = Math.max(maxY, y + height / 2)
235
+ }
236
+ }
237
+
238
+ const width = maxX - minX
239
+ const height = maxY - minY
240
+
241
+ db.pcb_component.update(this.pcb_component_id!, {
242
+ width,
243
+ height,
244
+ })
213
245
  }
214
246
 
215
247
  _renderReactSubtree(element: ReactElement): ReactSubtree {
@@ -219,6 +251,10 @@ export class NormalComponent<
219
251
  }
220
252
  }
221
253
 
254
+ doInitialInitializePortsFromChildren(): void {
255
+ this.initPorts()
256
+ }
257
+
222
258
  doInitialReactSubtreesRender(): void {
223
259
  if (isReactElement(this.props.footprint)) {
224
260
  if (this.reactSubtrees.some((rs) => rs.element === this.props.footprint))
@@ -229,6 +265,20 @@ export class NormalComponent<
229
265
  }
230
266
  }
231
267
 
268
+ _hasExistingPortExactly(port1: Port): boolean {
269
+ const existingPorts = this.children.filter(
270
+ (c) => c.componentName === "Port",
271
+ ) as Port[]
272
+ return existingPorts.some((port2) => {
273
+ const aliases1 = port1.getNameAndAliases()
274
+ const aliases2 = port2.getNameAndAliases()
275
+ return (
276
+ aliases1.length === aliases2.length &&
277
+ aliases1.every((alias) => aliases2.includes(alias))
278
+ )
279
+ })
280
+ }
281
+
232
282
  add(componentOrElm: PrimitiveComponent | ReactElement) {
233
283
  let component: PrimitiveComponent
234
284
  if (isReactElement(componentOrElm)) {
@@ -239,6 +289,23 @@ export class NormalComponent<
239
289
  component = componentOrElm as PrimitiveComponent
240
290
  }
241
291
 
292
+ if (component.componentName === "Port") {
293
+ if (this._hasExistingPortExactly(component as Port)) return
294
+ // Check if this port is already contained in the children, skip if it's
295
+ // already defined
296
+ const existingPorts = this.children.filter(
297
+ (c) => c.componentName === "Port",
298
+ ) as Port[]
299
+ const conflictingPort = existingPorts.find((p) =>
300
+ p.isMatchingAnyOf(component.getNameAndAliases()),
301
+ )
302
+ if (conflictingPort) {
303
+ throw new Error(
304
+ `Conflicting ports added. Port 1: ${conflictingPort}, Port 2: ${component}`,
305
+ )
306
+ }
307
+ }
308
+
242
309
  super.add(component)
243
310
  }
244
311
 
@@ -277,6 +344,12 @@ export class NormalComponent<
277
344
  newPorts.push(newPort)
278
345
  }
279
346
 
347
+ if (newPorts.length === 0) {
348
+ throw new Error(
349
+ `No ports found in chip ${this} (define a schPortArrangement, a footprint or add portHints to elements of the footprint)`,
350
+ )
351
+ }
352
+
280
353
  return newPorts
281
354
  }
282
355
 
@@ -41,7 +41,6 @@ export abstract class PrimitiveComponent<
41
41
  }
42
42
  }
43
43
 
44
- project: Circuit | null = null
45
44
  props: z.input<ZodProps>
46
45
  _parsedProps: z.infer<ZodProps>
47
46
 
@@ -60,8 +59,7 @@ export abstract class PrimitiveComponent<
60
59
  return (
61
60
  Boolean(this.props.subcircuit) ||
62
61
  // Implied opaque group for top-level group
63
- (this.lowercaseComponentName === "group" &&
64
- this?.parent?.props?.name === "$root")
62
+ (this.lowercaseComponentName === "group" && (this?.parent as any)?.isRoot)
65
63
  )
66
64
  }
67
65
 
@@ -86,13 +84,6 @@ export abstract class PrimitiveComponent<
86
84
  }
87
85
  }
88
86
 
89
- setProject(project: Circuit) {
90
- this.project = project
91
- for (const c of this.children) {
92
- c.setProject(project)
93
- }
94
- }
95
-
96
87
  setProps(props: Partial<z.input<ZodProps>>) {
97
88
  const newProps = this.config.zodProps.parse({
98
89
  ...this.props,
@@ -227,9 +218,12 @@ export abstract class PrimitiveComponent<
227
218
  return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 })
228
219
  }
229
220
 
221
+ get root(): Circuit | null {
222
+ return this.parent?.root ?? null
223
+ }
224
+
230
225
  onAddToParent(parent: PrimitiveComponent) {
231
226
  this.parent = parent
232
- this.project = parent.project
233
227
  }
234
228
 
235
229
  /**
@@ -298,6 +292,9 @@ export abstract class PrimitiveComponent<
298
292
  aliases.map((a) => a.toString()).includes(a),
299
293
  )
300
294
  }
295
+ getPcbSize(): { width: number; height: number } {
296
+ throw new Error(`getPcbSize not implemented for ${this.componentName}`)
297
+ }
301
298
 
302
299
  doesSelectorMatch(selector: string): boolean {
303
300
  const myTypeNames = [this.componentName, this.lowercaseComponentName]
@@ -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
+ "InitializePortsFromChildren",
6
7
  "CreateNetsFromProps",
7
8
  "CreateTracesFromProps",
8
9
  "SourceRender",
@@ -21,8 +22,8 @@ export const orderedRenderPhases = [
21
22
  "PcbLayout",
22
23
  "PcbTraceRender",
23
24
  "PcbRouteNetIslands",
25
+ "PcbComponentSizeCalculation",
24
26
  "CadModelRender",
25
- "PcbAnalysis",
26
27
  ] as const
27
28
 
28
29
  export type RenderPhase = (typeof orderedRenderPhases)[number]
@@ -18,7 +18,7 @@ export class Board extends Group<typeof boardProps> {
18
18
  }
19
19
 
20
20
  doInitialPcbComponentRender(): void {
21
- const { db } = this.project!
21
+ const { db } = this.root!
22
22
  const { _parsedProps: props } = this
23
23
 
24
24
  if (!props.width || !props.height) {
@@ -36,7 +36,7 @@ export class Board extends Group<typeof boardProps> {
36
36
  }
37
37
 
38
38
  removePcbComponentRender(): void {
39
- const { db } = this.project!
39
+ const { db } = this.root!
40
40
  if (!this.pcb_board_id) return
41
41
  db.pcb_board.delete(this.pcb_board_id!)
42
42
  this.pcb_board_id = null
@@ -54,7 +54,7 @@ export class Capacitor extends NormalComponent<
54
54
  }
55
55
 
56
56
  doInitialSourceRender() {
57
- const { db } = this.project!
57
+ const { db } = this.root!
58
58
  const { _parsedProps: props } = this
59
59
  const source_component = db.source_component.insert({
60
60
  ftype: "simple_capacitor",
@@ -22,7 +22,7 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
22
22
  }
23
23
 
24
24
  doInitialSourceRender(): void {
25
- const { db } = this.project!
25
+ const { db } = this.root!
26
26
  const { _parsedProps: props } = this
27
27
 
28
28
  const source_component = db.source_component.insert({
@@ -36,7 +36,7 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
36
36
  }
37
37
 
38
38
  doInitialSchematicComponentRender() {
39
- const { db } = this.project!
39
+ const { db } = this.root!
40
40
  const { _parsedProps: props } = this
41
41
 
42
42
  const ports = this.children.filter((child) => child instanceof Port)
@@ -81,7 +81,7 @@ export class Chip<PinLabels extends string = never> extends NormalComponent<
81
81
  }
82
82
 
83
83
  doInitialPcbComponentRender() {
84
- const { db } = this.project!
84
+ const { db } = this.root!
85
85
  const { _parsedProps: props } = this
86
86
 
87
87
  const pcb_component = db.pcb_component.insert({
@@ -22,7 +22,7 @@ export class Jumper<PinLabels extends string = never> extends NormalComponent<
22
22
  }
23
23
 
24
24
  doInitialSourceRender(): void {
25
- const { db } = this.project!
25
+ const { db } = this.root!
26
26
  const { _parsedProps: props } = this
27
27
 
28
28
  const source_component = db.source_component.insert({
@@ -36,7 +36,7 @@ export class Jumper<PinLabels extends string = never> extends NormalComponent<
36
36
  }
37
37
 
38
38
  doInitialSchematicComponentRender() {
39
- const { db } = this.project!
39
+ const { db } = this.root!
40
40
  const { _parsedProps: props } = this
41
41
 
42
42
  const ports = this.children.filter((child) => child instanceof Port)
@@ -84,7 +84,7 @@ export class Jumper<PinLabels extends string = never> extends NormalComponent<
84
84
  }
85
85
 
86
86
  doInitialPcbComponentRender() {
87
- const { db } = this.project!
87
+ const { db } = this.root!
88
88
  const { _parsedProps: props } = this
89
89
 
90
90
  const pcb_component = db.pcb_component.insert({
@@ -43,7 +43,7 @@ export class Resistor extends NormalComponent<
43
43
  }
44
44
 
45
45
  doInitialSourceRender() {
46
- const { db } = this.project!
46
+ const { db } = this.root!
47
47
  const { _parsedProps: props } = this
48
48
  const source_component = db.source_component.insert({
49
49
  ftype: "simple_resistor",
@@ -18,7 +18,7 @@ export class Net extends PrimitiveComponent<typeof netProps> {
18
18
  }
19
19
 
20
20
  doInitialSourceComponentRender(): void {
21
- const { db } = this.project!
21
+ const { db } = this.root!
22
22
  const { _parsedProps: props } = this
23
23
 
24
24
  const net = db.source_net.insert({
@@ -77,7 +77,7 @@ export class Net extends PrimitiveComponent<typeof netProps> {
77
77
  * such that the nets are fully connected
78
78
  */
79
79
  doInitialPcbRouteNetIslands(): void {
80
- const { db } = this.project!
80
+ const { db } = this.root!
81
81
  const { _parsedProps: props } = this
82
82
 
83
83
  const traces = this._getAllDirectlyConnectedTraces().filter(
@@ -14,6 +14,19 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
14
14
  }
15
15
  }
16
16
 
17
+ getPcbSize(): { width: number; height: number } {
18
+ const { _parsedProps: props } = this
19
+ if (props.shape === "circle") {
20
+ return { width: props.outerDiameter, height: props.outerDiameter }
21
+ }
22
+ if (props.shape === "oval") {
23
+ return { width: props.outerWidth, height: props.outerHeight }
24
+ }
25
+ throw new Error(
26
+ `getPcbSize for shape "${(props as any).shape}" not implemented for ${this.componentName}`,
27
+ )
28
+ }
29
+
17
30
  doInitialPortMatching(): void {
18
31
  const parentPorts = (this.parent?.children ?? []).filter(
19
32
  (c) => c.componentName === "Port",
@@ -33,7 +46,7 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
33
46
  }
34
47
 
35
48
  doInitialPcbPrimitiveRender(): void {
36
- const { db } = this.project!
49
+ const { db } = this.root!
37
50
  const { _parsedProps: props } = this
38
51
  if (!props.portHints) return
39
52
  const position = this.getGlobalPcbPosition()
@@ -116,7 +116,7 @@ export class Port extends PrimitiveComponent<typeof portProps> {
116
116
  }
117
117
 
118
118
  doInitialSourceRender(): void {
119
- const { db } = this.project!
119
+ const { db } = this.root!
120
120
  const { _parsedProps: props } = this
121
121
 
122
122
  const port_hints = this.getNameAndAliases()
@@ -132,7 +132,7 @@ export class Port extends PrimitiveComponent<typeof portProps> {
132
132
  }
133
133
 
134
134
  doInitialSourceParentAttachment(): void {
135
- const { db } = this.project!
135
+ const { db } = this.root!
136
136
  if (!this.parent?.source_component_id) {
137
137
  throw new Error(
138
138
  `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`,
@@ -152,7 +152,7 @@ export class Port extends PrimitiveComponent<typeof portProps> {
152
152
  * to exist)
153
153
  */
154
154
  doInitialPcbPortRender(): void {
155
- const { db } = this.project!
155
+ const { db } = this.root!
156
156
  const { matchedComponents } = this
157
157
 
158
158
  if (!this.parent?.pcb_component_id) {
@@ -191,7 +191,7 @@ export class Port extends PrimitiveComponent<typeof portProps> {
191
191
  }
192
192
 
193
193
  doInitialSchematicPortRender(): void {
194
- const { db } = this.project!
194
+ const { db } = this.root!
195
195
  const { _parsedProps: props } = this
196
196
 
197
197
  if (!this.parent) return
@@ -14,7 +14,7 @@ export class SilkscreenPath extends PrimitiveComponent<
14
14
  }
15
15
 
16
16
  doInitialPcbPrimitiveRender(): void {
17
- const { db } = this.project!
17
+ const { db } = this.root!
18
18
  const { _parsedProps: props } = this
19
19
 
20
20
  const layer = props.layer ?? "top"
@@ -18,6 +18,19 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
18
18
  }
19
19
  }
20
20
 
21
+ getPcbSize(): { width: number; height: number } {
22
+ const { _parsedProps: props } = this
23
+ if (props.shape === "circle") {
24
+ return { width: props.radius! * 2, height: props.radius! * 2 }
25
+ }
26
+ if (props.shape === "rect") {
27
+ return { width: props.width!, height: props.height! }
28
+ }
29
+ throw new Error(
30
+ `getPcbSize for shape "${(props as any).shape}" not implemented for ${this.componentName}`,
31
+ )
32
+ }
33
+
21
34
  doInitialPortMatching(): void {
22
35
  const parentPorts = (this.parent?.children ?? []).filter(
23
36
  (c) => c.componentName === "Port",
@@ -37,7 +50,7 @@ export class SmtPad extends PrimitiveComponent<typeof smtPadProps> {
37
50
  }
38
51
 
39
52
  doInitialPcbPrimitiveRender(): void {
40
- const { db } = this.project!
53
+ const { db } = this.root!
41
54
  const { _parsedProps: props } = this
42
55
  if (!props.portHints) return
43
56
  const position = this.getGlobalPcbPosition()
@@ -104,7 +104,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
104
104
  ports?: undefined
105
105
  portsWithSelectors?: undefined
106
106
  } {
107
- const { db } = this.project!
107
+ const { db } = this.root!
108
108
  const { _parsedProps: props, parent } = this
109
109
 
110
110
  if (!parent) throw new Error("Trace has no parent")
@@ -215,7 +215,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
215
215
  }
216
216
 
217
217
  doInitialSourceTraceRender(): void {
218
- const { db } = this.project!
218
+ const { db } = this.root!
219
219
  const { _parsedProps: props, parent } = this
220
220
 
221
221
  if (!parent) {
@@ -238,7 +238,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
238
238
  }
239
239
 
240
240
  doInitialPcbTraceRender(): void {
241
- const { db } = this.project!
241
+ const { db } = this.root!
242
242
  const { _parsedProps: props, parent } = this
243
243
 
244
244
  if (!parent) throw new Error("Trace has no parent")
@@ -365,7 +365,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
365
365
 
366
366
  // Cache the PCB obstacles, they'll be needed for each segment between
367
367
  // ports/hints
368
- const obstacles = getObstaclesFromSoup(this.project!.db.toArray())
368
+ const obstacles = getObstaclesFromSoup(this.root!.db.toArray())
369
369
  markObstaclesAsConnected(
370
370
  obstacles,
371
371
  orderedRouteObjectives,
@@ -445,7 +445,7 @@ export class Trace extends PrimitiveComponent<typeof traceProps> {
445
445
  }
446
446
 
447
447
  doInitialSchematicTraceRender(): void {
448
- const { db } = this.project!
448
+ const { db } = this.root!
449
449
  const { _parsedProps: props, parent } = this
450
450
 
451
451
  if (!parent) throw new Error("Trace has no parent")
@@ -8,7 +8,7 @@ export class TraceHint extends PrimitiveComponent<typeof traceHintProps> {
8
8
  matchedPort: Port | null = null
9
9
 
10
10
  doInitialPortMatching(): void {
11
- const { db } = this.project!
11
+ const { db } = this.root!
12
12
  const { _parsedProps: props, parent } = this
13
13
 
14
14
  if (!parent) return
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@tscircuit/core",
3
3
  "module": "index.ts",
4
4
  "type": "module",
5
- "version": "0.0.28",
5
+ "version": "0.0.30",
6
6
  "types": "dist/index.d.ts",
7
7
  "main": "dist/index.js",
8
8
  "files": [
@@ -32,7 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@tscircuit/infgrid-ijump-astar": "^0.0.6",
35
- "@tscircuit/props": "^0.0.51",
35
+ "@tscircuit/props": "^0.0.52",
36
36
  "@tscircuit/soup": "^0.0.58",
37
37
  "@tscircuit/soup-util": "0.0.18",
38
38
  "footprinter": "^0.0.44",