@tscircuit/core 0.0.30 → 0.0.32

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
@@ -10,8 +10,10 @@ __export(components_exports, {
10
10
  Board: () => Board,
11
11
  Capacitor: () => Capacitor,
12
12
  Chip: () => Chip,
13
+ Constraint: () => Constraint2,
13
14
  Footprint: () => Footprint,
14
15
  Group: () => Group,
16
+ Hole: () => Hole,
15
17
  Jumper: () => Jumper,
16
18
  Led: () => Led,
17
19
  Net: () => Net,
@@ -36,14 +38,12 @@ import "react";
36
38
  import "react";
37
39
  var orderedRenderPhases = [
38
40
  "ReactSubtreesRender",
39
- // probably going to be removed b/c subtrees should render instantly
40
41
  "InitializePortsFromChildren",
41
42
  "CreateNetsFromProps",
42
43
  "CreateTracesFromProps",
43
44
  "SourceRender",
44
45
  "SourceParentAttachment",
45
46
  "PortDiscovery",
46
- // probably going to be removed b/c port discovery can always be done on prop change
47
47
  "PortMatching",
48
48
  "SourceTraceRender",
49
49
  "SchematicComponentRender",
@@ -51,8 +51,9 @@ var orderedRenderPhases = [
51
51
  "SchematicPortRender",
52
52
  "SchematicTraceRender",
53
53
  "PcbComponentRender",
54
- "PcbPortRender",
55
54
  "PcbPrimitiveRender",
55
+ "PcbFootprintLayout",
56
+ "PcbPortRender",
56
57
  "PcbParentAttachment",
57
58
  "PcbLayout",
58
59
  "PcbTraceRender",
@@ -185,6 +186,17 @@ var PrimitiveComponent = class extends Renderable {
185
186
  return Boolean(this.props.subcircuit) || // Implied opaque group for top-level group
186
187
  this.lowercaseComponentName === "group" && this?.parent?.isRoot;
187
188
  }
189
+ /**
190
+ * A primitive container is a component that contains one or more ports and
191
+ * primitive components that are designed to interact.
192
+ *
193
+ * For example a resistor contains ports and smtpads that interact, so the
194
+ * resistor is a primitive container. Inside a primitive container, the ports
195
+ * and pads are likely to reference each other and look for eachother during
196
+ * the port matching phase.
197
+ *
198
+ */
199
+ isPrimitiveContainer = false;
188
200
  source_group_id = null;
189
201
  source_component_id = null;
190
202
  schematic_component_id = null;
@@ -236,14 +248,14 @@ var PrimitiveComponent = class extends Renderable {
236
248
  * components, including this component's translation and rotation.
237
249
  *
238
250
  * This is used to compute this component's position as well as all children
239
- * components positions
251
+ * components positions before layout is applied
240
252
  */
241
- computePcbGlobalTransform() {
253
+ _computePcbGlobalTransformBeforeLayout() {
242
254
  const { _parsedProps: props } = this;
243
255
  const manualPlacement = this.getSubcircuit()._getManualPlacementForComponent(this);
244
256
  if (manualPlacement && this.props.pcbX === void 0 && this.props.pcbY === void 0) {
245
257
  return compose(
246
- this.parent?.computePcbGlobalTransform() ?? identity(),
258
+ this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
247
259
  compose(
248
260
  translate(manualPlacement.x, manualPlacement.y),
249
261
  rotate((props.pcbRotation ?? 0) * Math.PI / 180)
@@ -251,10 +263,37 @@ var PrimitiveComponent = class extends Renderable {
251
263
  );
252
264
  }
253
265
  return compose(
254
- this.parent?.computePcbGlobalTransform() ?? identity(),
266
+ this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
255
267
  this.computePcbPropsTransform()
256
268
  );
257
269
  }
270
+ getPrimitiveContainer() {
271
+ if (this.isPrimitiveContainer) return this;
272
+ return this.parent?.getPrimitiveContainer?.() ?? null;
273
+ }
274
+ /**
275
+ * Compute the bounds of this component the circuit json elements associated
276
+ * with it.
277
+ */
278
+ _getCircuitJsonBounds() {
279
+ return {
280
+ center: { x: 0, y: 0 },
281
+ bounds: { left: 0, top: 0, right: 0, bottom: 0 },
282
+ width: 0,
283
+ height: 0
284
+ };
285
+ }
286
+ /**
287
+ * Set the position of this component from the layout solver. This method
288
+ * should operate using CircuitJson associated with this component, like
289
+ * _getCircuitJsonBounds it can be called multiple times as different
290
+ * parents apply layout to their children.
291
+ */
292
+ _setPositionFromLayout(newCenter) {
293
+ throw new Error(
294
+ `_setPositionFromLayout not implemented for ${this.componentName}`
295
+ );
296
+ }
258
297
  /**
259
298
  * Computes a transformation matrix from the props of this component for
260
299
  * schematic components
@@ -295,7 +334,7 @@ var PrimitiveComponent = class extends Renderable {
295
334
  for (const position of placementConfig.positions) {
296
335
  if (isMatchingSelector(component, position.selector)) {
297
336
  const center = applyToPoint(
298
- this.computePcbGlobalTransform(),
337
+ this._computePcbGlobalTransformBeforeLayout(),
299
338
  position.center
300
339
  );
301
340
  return center;
@@ -303,10 +342,13 @@ var PrimitiveComponent = class extends Renderable {
303
342
  }
304
343
  return null;
305
344
  }
306
- getGlobalPcbPosition() {
307
- return applyToPoint(this.computePcbGlobalTransform(), { x: 0, y: 0 });
345
+ _getGlobalPcbPositionBeforeLayout() {
346
+ return applyToPoint(this._computePcbGlobalTransformBeforeLayout(), {
347
+ x: 0,
348
+ y: 0
349
+ });
308
350
  }
309
- getGlobalSchematicPosition() {
351
+ _getGlobalSchematicPositionBeforeLayout() {
310
352
  return applyToPoint(this.computeSchematicGlobalTransform(), { x: 0, y: 0 });
311
353
  }
312
354
  get root() {
@@ -465,7 +507,125 @@ var PrimitiveComponent = class extends Renderable {
465
507
  };
466
508
 
467
509
  // lib/components/primitive-components/Footprint.ts
510
+ import * as kiwi from "@lume/kiwi";
511
+ import Debug from "debug";
512
+ var debug = Debug("tscircuit:core:footprint");
468
513
  var Footprint = class extends PrimitiveComponent {
514
+ /**
515
+ * A footprint is a constrainedlayout, the db elements are adjusted according
516
+ * to any constraints that are defined.
517
+ */
518
+ doInitialPcbFootprintLayout() {
519
+ const constraints = this.children.filter(
520
+ (child) => child.componentName === "Constraint"
521
+ );
522
+ if (constraints.length === 0) return;
523
+ const involvedComponents = constraints.flatMap(
524
+ (constraint) => constraint._getAllReferencedComponents().componentsWithSelectors
525
+ ).map(({ component, selector }) => ({
526
+ component,
527
+ selector,
528
+ bounds: component._getCircuitJsonBounds()
529
+ }));
530
+ function getComponentDetails(selector) {
531
+ return involvedComponents.find(({ selector: s }) => s === selector);
532
+ }
533
+ const solver = new kiwi.Solver();
534
+ const kVars = {};
535
+ function getKVar(name) {
536
+ if (!(name in kVars)) {
537
+ kVars[name] = new kiwi.Variable(name);
538
+ }
539
+ return kVars[name];
540
+ }
541
+ for (const { selector, bounds } of involvedComponents) {
542
+ const kvx = getKVar(`${selector}_x`);
543
+ const kvy = getKVar(`${selector}_y`);
544
+ solver.addEditVariable(kvx, kiwi.Strength.weak);
545
+ solver.addEditVariable(kvy, kiwi.Strength.weak);
546
+ solver.suggestValue(kvx, bounds.center.x);
547
+ solver.suggestValue(kvy, bounds.center.y);
548
+ }
549
+ for (const constraint of constraints) {
550
+ const props = constraint._parsedProps;
551
+ if ("xdist" in props) {
552
+ const { xdist, left, right, edgeToEdge, centerToCenter } = props;
553
+ const leftVar = getKVar(`${left}_x`);
554
+ const rightVar = getKVar(`${right}_x`);
555
+ const leftBounds = getComponentDetails(left)?.bounds;
556
+ const rightBounds = getComponentDetails(right)?.bounds;
557
+ if (centerToCenter) {
558
+ const expr = new kiwi.Expression(rightVar, [-1, leftVar]);
559
+ solver.addConstraint(
560
+ new kiwi.Constraint(
561
+ expr,
562
+ kiwi.Operator.Eq,
563
+ props.xdist,
564
+ kiwi.Strength.required
565
+ )
566
+ );
567
+ } else if (edgeToEdge) {
568
+ const expr = new kiwi.Expression(
569
+ rightVar,
570
+ rightBounds.width / 2,
571
+ [-1, leftVar],
572
+ -leftBounds.width / 2
573
+ );
574
+ solver.addConstraint(
575
+ new kiwi.Constraint(
576
+ expr,
577
+ kiwi.Operator.Eq,
578
+ props.xdist,
579
+ kiwi.Strength.required
580
+ )
581
+ );
582
+ }
583
+ }
584
+ solver.updateVariables();
585
+ if (debug.enabled) {
586
+ console.log("Solution to layout constraints:");
587
+ console.table(
588
+ Object.entries(kVars).map(([key, kvar]) => ({
589
+ var: key,
590
+ val: kvar.value()
591
+ }))
592
+ );
593
+ }
594
+ const bounds = {
595
+ left: Infinity,
596
+ right: -Infinity,
597
+ top: -Infinity,
598
+ bottom: Infinity
599
+ };
600
+ for (const {
601
+ selector,
602
+ bounds: { width, height }
603
+ } of involvedComponents) {
604
+ const kvx = getKVar(`${selector}_x`);
605
+ const kvy = getKVar(`${selector}_y`);
606
+ const newLeft = kvx.value() - width / 2;
607
+ const newRight = kvx.value() + width / 2;
608
+ const newTop = kvy.value() + height / 2;
609
+ const newBottom = kvy.value() - height / 2;
610
+ bounds.left = Math.min(bounds.left, newLeft);
611
+ bounds.right = Math.max(bounds.right, newRight);
612
+ bounds.top = Math.max(bounds.top, newTop);
613
+ bounds.bottom = Math.min(bounds.bottom, newBottom);
614
+ }
615
+ const globalOffset = {
616
+ x: -(bounds.right + bounds.left) / 2,
617
+ y: -(bounds.top + bounds.bottom) / 2
618
+ };
619
+ for (const { component, selector } of involvedComponents) {
620
+ const kvx = getKVar(`${selector}_x`);
621
+ const kvy = getKVar(`${selector}_y`);
622
+ component._setPositionFromLayout({
623
+ x: kvx.value() + globalOffset.x,
624
+ y: kvy.value() + globalOffset.y
625
+ });
626
+ }
627
+ }
628
+ }
469
629
  };
470
630
 
471
631
  // lib/components/base-components/NormalComponent.ts
@@ -507,16 +667,16 @@ var Port = class extends PrimitiveComponent {
507
667
  super(props);
508
668
  this.matchedComponents = [];
509
669
  }
510
- getGlobalPcbPosition() {
670
+ _getGlobalPcbPositionBeforeLayout() {
511
671
  const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive);
512
672
  if (!matchedPcbElm) {
513
673
  throw new Error(
514
674
  `Port ${this} has no matched pcb component, can't get global schematic position`
515
675
  );
516
676
  }
517
- return matchedPcbElm?.getGlobalPcbPosition() ?? { x: 0, y: 0 };
677
+ return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 };
518
678
  }
519
- getGlobalSchematicPosition() {
679
+ _getGlobalSchematicPositionBeforeLayout() {
520
680
  if (!this.schematicSymbolPortDef) {
521
681
  return applyToPoint2(this.parent.computeSchematicGlobalTransform(), {
522
682
  x: 0,
@@ -600,11 +760,6 @@ var Port = class extends PrimitiveComponent {
600
760
  });
601
761
  this.source_component_id = this.parent?.source_component_id;
602
762
  }
603
- /**
604
- * For PcbPorts, we use the parent attachment phase to determine where to place
605
- * the pcb_port (prior to this phase, the smtpad/platedhole isn't guaranteed
606
- * to exist)
607
- */
608
763
  doInitialPcbPortRender() {
609
764
  const { db } = this.root;
610
765
  const { matchedComponents } = this;
@@ -621,17 +776,17 @@ var Port = class extends PrimitiveComponent {
621
776
  );
622
777
  }
623
778
  const pcbMatch = pcbMatches[0];
624
- if ("getGlobalPcbPosition" in pcbMatch) {
779
+ if ("_getCircuitJsonBounds" in pcbMatch) {
625
780
  const pcb_port = db.pcb_port.insert({
626
781
  pcb_component_id: this.parent?.pcb_component_id,
627
782
  layers: ["top"],
628
- ...pcbMatch.getGlobalPcbPosition(),
783
+ ...pcbMatch._getCircuitJsonBounds().center,
629
784
  source_port_id: this.source_port_id
630
785
  });
631
786
  this.pcb_port_id = pcb_port.pcb_port_id;
632
787
  } else {
633
788
  throw new Error(
634
- `${pcbMatch.getString()} does not have a getGlobalPcbPosition method (needed for pcb_port placement)`
789
+ `${pcbMatch.getString()} does not have a _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`
635
790
  );
636
791
  }
637
792
  }
@@ -639,8 +794,8 @@ var Port = class extends PrimitiveComponent {
639
794
  const { db } = this.root;
640
795
  const { _parsedProps: props } = this;
641
796
  if (!this.parent) return;
642
- const center = this.getGlobalSchematicPosition();
643
- const parentCenter = this.parent?.getGlobalSchematicPosition();
797
+ const center = this._getGlobalSchematicPositionBeforeLayout();
798
+ const parentCenter = this.parent?._getGlobalSchematicPositionBeforeLayout();
644
799
  this.facingDirection = getRelativeDirection(parentCenter, center);
645
800
  const schematic_port = db.schematic_port.insert({
646
801
  schematic_component_id: this.parent?.schematic_component_id,
@@ -852,8 +1007,8 @@ var SmtPad = class extends PrimitiveComponent {
852
1007
  );
853
1008
  }
854
1009
  doInitialPortMatching() {
855
- const parentPorts = (this.parent?.children ?? []).filter(
856
- (c) => c.componentName === "Port"
1010
+ const parentPorts = this.getPrimitiveContainer()?.selectAll(
1011
+ "port"
857
1012
  );
858
1013
  if (!this.props.portHints) {
859
1014
  return;
@@ -870,8 +1025,10 @@ var SmtPad = class extends PrimitiveComponent {
870
1025
  const { db } = this.root;
871
1026
  const { _parsedProps: props } = this;
872
1027
  if (!props.portHints) return;
873
- const position = this.getGlobalPcbPosition();
874
- const decomposedMat = decomposeTSR(this.computePcbGlobalTransform());
1028
+ const position = this._getGlobalPcbPositionBeforeLayout();
1029
+ const decomposedMat = decomposeTSR(
1030
+ this._computePcbGlobalTransformBeforeLayout()
1031
+ );
875
1032
  const isRotated90 = Math.abs(decomposedMat.rotation.angle * (180 / Math.PI) - 90) < 0.01;
876
1033
  let pcb_smtpad = null;
877
1034
  if (props.shape === "circle") {
@@ -902,6 +1059,46 @@ var SmtPad = class extends PrimitiveComponent {
902
1059
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
903
1060
  }
904
1061
  }
1062
+ _getCircuitJsonBounds() {
1063
+ const { db } = this.root;
1064
+ const smtpad = db.pcb_smtpad.get(this.pcb_smtpad_id);
1065
+ if (smtpad.shape === "rect") {
1066
+ return {
1067
+ center: { x: smtpad.x, y: smtpad.y },
1068
+ bounds: {
1069
+ left: smtpad.x - smtpad.width / 2,
1070
+ top: smtpad.y - smtpad.height / 2,
1071
+ right: smtpad.x + smtpad.width / 2,
1072
+ bottom: smtpad.y + smtpad.height / 2
1073
+ },
1074
+ width: smtpad.width,
1075
+ height: smtpad.height
1076
+ };
1077
+ }
1078
+ if (smtpad.shape === "circle") {
1079
+ return {
1080
+ center: { x: smtpad.x, y: smtpad.y },
1081
+ bounds: {
1082
+ left: smtpad.x - smtpad.radius,
1083
+ top: smtpad.y - smtpad.radius,
1084
+ right: smtpad.x + smtpad.radius,
1085
+ bottom: smtpad.y + smtpad.radius
1086
+ },
1087
+ width: smtpad.radius * 2,
1088
+ height: smtpad.radius * 2
1089
+ };
1090
+ }
1091
+ throw new Error(
1092
+ `circuitJson bounds calculation not implemented for shape "${smtpad.shape}"`
1093
+ );
1094
+ }
1095
+ _setPositionFromLayout(newCenter) {
1096
+ const { db } = this.root;
1097
+ db.pcb_smtpad.update(this.pcb_smtpad_id, {
1098
+ x: newCenter.x,
1099
+ y: newCenter.y
1100
+ });
1101
+ }
905
1102
  };
906
1103
 
907
1104
  // lib/components/primitive-components/SilkscreenPath.ts
@@ -923,7 +1120,7 @@ var SilkscreenPath = class extends PrimitiveComponent {
923
1120
  `Invalid layer "${layer}" for SilkscreenPath. Must be "top" or "bottom".`
924
1121
  );
925
1122
  }
926
- const transform = this.computePcbGlobalTransform();
1123
+ const transform = this._computePcbGlobalTransformBeforeLayout();
927
1124
  const pcb_silkscreen_path = db.pcb_silkscreen_path.insert({
928
1125
  pcb_component_id: this.parent?.pcb_component_id,
929
1126
  layer,
@@ -986,7 +1183,7 @@ var PlatedHole = class extends PrimitiveComponent {
986
1183
  const { db } = this.root;
987
1184
  const { _parsedProps: props } = this;
988
1185
  if (!props.portHints) return;
989
- const position = this.getGlobalPcbPosition();
1186
+ const position = this._getGlobalPcbPositionBeforeLayout();
990
1187
  if (props.shape === "circle") {
991
1188
  const plated_hole_input = {
992
1189
  pcb_component_id: this.parent?.pcb_component_id,
@@ -1154,10 +1351,10 @@ var Net = class extends PrimitiveComponent {
1154
1351
  const islandPairs = pairs(islands);
1155
1352
  for (const [A, B] of islandPairs) {
1156
1353
  const Apositions = A.ports.map(
1157
- (port) => port.getGlobalPcbPosition()
1354
+ (port) => port._getGlobalPcbPositionBeforeLayout()
1158
1355
  );
1159
1356
  const Bpositions = B.ports.map(
1160
- (port) => port.getGlobalPcbPosition()
1357
+ (port) => port._getGlobalPcbPositionBeforeLayout()
1161
1358
  );
1162
1359
  let closestDist = Infinity;
1163
1360
  let closestPair = [-1, -1];
@@ -1215,6 +1412,7 @@ var createNetsFromProps = (component, props) => {
1215
1412
  // lib/components/base-components/NormalComponent.ts
1216
1413
  var NormalComponent = class extends PrimitiveComponent {
1217
1414
  reactSubtrees = [];
1415
+ isPrimitiveContainer = true;
1218
1416
  constructor(props) {
1219
1417
  super(props);
1220
1418
  this._addChildrenFromStringFootprint();
@@ -1345,7 +1543,7 @@ var NormalComponent = class extends PrimitiveComponent {
1345
1543
  const { db } = this.root;
1346
1544
  const { _parsedProps: props } = this;
1347
1545
  const pcb_component = db.pcb_component.insert({
1348
- center: this.getGlobalPcbPosition(),
1546
+ center: this._getGlobalPcbPositionBeforeLayout(),
1349
1547
  // width/height are computed in the PcbComponentSizeCalculation phase
1350
1548
  width: 0,
1351
1549
  height: 0,
@@ -1369,7 +1567,7 @@ var NormalComponent = class extends PrimitiveComponent {
1369
1567
  let maxY = -Infinity;
1370
1568
  for (const child of this.children) {
1371
1569
  if (child.isPcbPrimitive) {
1372
- const { x, y } = child.getGlobalPcbPosition();
1570
+ const { x, y } = child._getGlobalPcbPositionBeforeLayout();
1373
1571
  const { width: width2, height: height2 } = child.getPcbSize();
1374
1572
  minX = Math.min(minX, x - width2 / 2);
1375
1573
  minY = Math.min(minY, y - height2 / 2);
@@ -1577,7 +1775,7 @@ var Board = class extends Group {
1577
1775
  db.pcb_board.delete(this.pcb_board_id);
1578
1776
  this.pcb_board_id = null;
1579
1777
  }
1580
- computePcbGlobalTransform() {
1778
+ _computePcbGlobalTransformBeforeLayout() {
1581
1779
  return identity4();
1582
1780
  }
1583
1781
  };
@@ -1718,8 +1916,8 @@ var mergeRoutes = (routes) => {
1718
1916
 
1719
1917
  // lib/utils/getClosest.ts
1720
1918
  var getDistance = (a, b) => {
1721
- const aPos = "getGlobalPcbPosition" in a ? a.getGlobalPcbPosition() : a;
1722
- const bPos = "getGlobalPcbPosition" in b ? b.getGlobalPcbPosition() : b;
1919
+ const aPos = "_getGlobalPcbPositionBeforeLayout" in a ? a._getGlobalPcbPositionBeforeLayout() : a;
1920
+ const bPos = "_getGlobalPcbPositionBeforeLayout" in b ? b._getGlobalPcbPositionBeforeLayout() : b;
1723
1921
  return Math.sqrt((aPos.x - bPos.x) ** 2 + (aPos.y - bPos.y) ** 2);
1724
1922
  };
1725
1923
  function getClosest(point, candidates) {
@@ -1740,7 +1938,7 @@ function getClosest(point, candidates) {
1740
1938
  // lib/components/primitive-components/Trace.ts
1741
1939
  import "zod";
1742
1940
  var portToObjective = (port) => {
1743
- const portPosition = port.getGlobalPcbPosition();
1941
+ const portPosition = port._getGlobalPcbPositionBeforeLayout();
1744
1942
  return {
1745
1943
  ...portPosition,
1746
1944
  layers: port.getAvailablePcbLayers()
@@ -2056,7 +2254,7 @@ searched component ${targetComponent.getString()}, which has ports: ${targetComp
2056
2254
  for (const { port } of ports) {
2057
2255
  connection.pointsToConnect.push(
2058
2256
  projectPointInDirection(
2059
- port.getGlobalSchematicPosition(),
2257
+ port._getGlobalSchematicPositionBeforeLayout(),
2060
2258
  port.facingDirection,
2061
2259
  0.1501
2062
2260
  )
@@ -2251,7 +2449,7 @@ var TraceHint = class extends PrimitiveComponent {
2251
2449
  const { _parsedProps: props } = this;
2252
2450
  const offsets = props.offset ? [props.offset] : props.offsets;
2253
2451
  if (!offsets) return [];
2254
- const globalTransform = this.computePcbGlobalTransform();
2452
+ const globalTransform = this._computePcbGlobalTransformBeforeLayout();
2255
2453
  return offsets.map(
2256
2454
  (offset) => ({
2257
2455
  ...applyToPoint4(globalTransform, offset),
@@ -2687,6 +2885,72 @@ var Jumper = class extends NormalComponent {
2687
2885
  }
2688
2886
  };
2689
2887
 
2888
+ // lib/components/primitive-components/Constraint.ts
2889
+ import { constraintProps } from "@tscircuit/props";
2890
+ import "zod";
2891
+ var Constraint2 = class extends PrimitiveComponent {
2892
+ get config() {
2893
+ return {
2894
+ zodProps: constraintProps
2895
+ };
2896
+ }
2897
+ constructor(props) {
2898
+ super(props);
2899
+ if ("xdist" in props || "ydist" in props) {
2900
+ if (!("edgeToEdge" in props) && !("centerToCenter" in props)) {
2901
+ throw new Error(
2902
+ "edgeToEdge, centerToCenter (or from*/to* props) must be set for xdist or ydist for <constraint />"
2903
+ );
2904
+ }
2905
+ }
2906
+ }
2907
+ _getAllReferencedComponents() {
2908
+ const componentsWithSelectors = [];
2909
+ for (const key of ["left", "right", "top", "bottom"]) {
2910
+ if (key in this._parsedProps) {
2911
+ const selector = this._parsedProps[key];
2912
+ const component = this.getSubcircuit().selectOne(selector);
2913
+ if (component) {
2914
+ componentsWithSelectors.push({
2915
+ selector,
2916
+ component
2917
+ });
2918
+ }
2919
+ }
2920
+ }
2921
+ return { componentsWithSelectors };
2922
+ }
2923
+ };
2924
+
2925
+ // lib/components/primitive-components/Hole.ts
2926
+ import { holeProps } from "@tscircuit/props";
2927
+ var Hole = class extends PrimitiveComponent {
2928
+ pcb_hole_id = null;
2929
+ isPcbPrimitive = true;
2930
+ get config() {
2931
+ return {
2932
+ zodProps: holeProps
2933
+ };
2934
+ }
2935
+ getPcbSize() {
2936
+ const { _parsedProps: props } = this;
2937
+ return { width: props.holeDiameter, height: props.holeDiameter };
2938
+ }
2939
+ doInitialPcbPrimitiveRender() {
2940
+ const { db } = this.root;
2941
+ const { _parsedProps: props } = this;
2942
+ const position = this._getGlobalPcbPositionBeforeLayout();
2943
+ const pcb_hole = {
2944
+ type: "pcb_hole",
2945
+ hole_shape: "round",
2946
+ hole_diameter: props.holeDiameter,
2947
+ x: position.x,
2948
+ y: position.y
2949
+ };
2950
+ const inserted_hole = db.pcb_hole.insert(pcb_hole);
2951
+ }
2952
+ };
2953
+
2690
2954
  // lib/Project.ts
2691
2955
  import { su } from "@tscircuit/soup-util";
2692
2956
  import { isValidElement as isValidElement2 } from "react";
@@ -2768,7 +3032,7 @@ var Circuit = class {
2768
3032
  computeSchematicGlobalTransform() {
2769
3033
  return identity5();
2770
3034
  }
2771
- computePcbGlobalTransform() {
3035
+ _computePcbGlobalTransformBeforeLayout() {
2772
3036
  return identity5();
2773
3037
  }
2774
3038
  selectAll(selector) {
@@ -2790,8 +3054,10 @@ export {
2790
3054
  Capacitor,
2791
3055
  Chip,
2792
3056
  Circuit,
3057
+ Constraint2 as Constraint,
2793
3058
  Footprint,
2794
3059
  Group,
3060
+ Hole,
2795
3061
  Jumper,
2796
3062
  Led,
2797
3063
  Net,
package/lib/Project.ts CHANGED
@@ -108,7 +108,7 @@ export class Circuit {
108
108
  return identity()
109
109
  }
110
110
 
111
- computePcbGlobalTransform(): Matrix {
111
+ _computePcbGlobalTransformBeforeLayout(): Matrix {
112
112
  return identity()
113
113
  }
114
114
 
@@ -124,4 +124,7 @@ export class Circuit {
124
124
  }
125
125
  }
126
126
 
127
+ /**
128
+ * @deprecated
129
+ */
127
130
  export const Project = Circuit
@@ -46,6 +46,8 @@ export class NormalComponent<
46
46
  > extends PrimitiveComponent<ZodProps> {
47
47
  reactSubtrees: Array<ReactSubtree> = []
48
48
 
49
+ isPrimitiveContainer = true
50
+
49
51
  constructor(props: z.input<ZodProps>) {
50
52
  super(props)
51
53
  this._addChildrenFromStringFootprint()
@@ -199,7 +201,7 @@ export class NormalComponent<
199
201
  const { db } = this.root!
200
202
  const { _parsedProps: props } = this
201
203
  const pcb_component = db.pcb_component.insert({
202
- center: this.getGlobalPcbPosition(),
204
+ center: this._getGlobalPcbPositionBeforeLayout(),
203
205
  // width/height are computed in the PcbComponentSizeCalculation phase
204
206
  width: 0,
205
207
  height: 0,
@@ -226,7 +228,7 @@ export class NormalComponent<
226
228
 
227
229
  for (const child of this.children) {
228
230
  if (child.isPcbPrimitive) {
229
- const { x, y } = child.getGlobalPcbPosition()
231
+ const { x, y } = child._getGlobalPcbPositionBeforeLayout()
230
232
  const { width, height } = child.getPcbSize()
231
233
  minX = Math.min(minX, x - width / 2)
232
234
  minY = Math.min(minY, y - height / 2)