@tscircuit/core 0.0.30 → 0.0.31

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