@tscircuit/core 0.0.32 → 0.0.33

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
@@ -3348,6 +3348,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3348
3348
  name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3349
3349
  }>>;
3350
3350
  }, "pcbRotation" | "layer">, {
3351
+ name: zod.ZodOptional<zod.ZodString>;
3351
3352
  shape: zod.ZodLiteral<"circle">;
3352
3353
  holeDiameter: zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>;
3353
3354
  outerDiameter: zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>;
@@ -3358,6 +3359,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3358
3359
  outerDiameter: number;
3359
3360
  pcbX?: number | undefined;
3360
3361
  pcbY?: number | undefined;
3362
+ name?: string | undefined;
3361
3363
  portHints?: (string | number)[] | undefined;
3362
3364
  }, {
3363
3365
  shape: "circle";
@@ -3365,6 +3367,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3365
3367
  outerDiameter: string | number;
3366
3368
  pcbX?: string | number | undefined;
3367
3369
  pcbY?: string | number | undefined;
3370
+ name?: string | undefined;
3368
3371
  portHints?: (string | number)[] | undefined;
3369
3372
  }>, zod.ZodObject<zod.objectUtil.extendShape<Omit<{
3370
3373
  pcbX: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
@@ -3380,6 +3383,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3380
3383
  name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3381
3384
  }>>;
3382
3385
  }, "pcbRotation" | "layer">, {
3386
+ name: zod.ZodOptional<zod.ZodString>;
3383
3387
  shape: zod.ZodLiteral<"oval">;
3384
3388
  outerWidth: zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>;
3385
3389
  outerHeight: zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>;
@@ -3394,6 +3398,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3394
3398
  innerHeight: number;
3395
3399
  pcbX?: number | undefined;
3396
3400
  pcbY?: number | undefined;
3401
+ name?: string | undefined;
3397
3402
  portHints?: (string | number)[] | undefined;
3398
3403
  }, {
3399
3404
  shape: "oval";
@@ -3403,6 +3408,7 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3403
3408
  innerHeight: string | number;
3404
3409
  pcbX?: string | number | undefined;
3405
3410
  pcbY?: string | number | undefined;
3411
+ name?: string | undefined;
3406
3412
  portHints?: (string | number)[] | undefined;
3407
3413
  }>]>;
3408
3414
  };
@@ -3410,6 +3416,24 @@ declare class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
3410
3416
  width: number;
3411
3417
  height: number;
3412
3418
  };
3419
+ _getCircuitJsonBounds(): {
3420
+ center: {
3421
+ x: number;
3422
+ y: number;
3423
+ };
3424
+ bounds: {
3425
+ left: number;
3426
+ top: number;
3427
+ right: number;
3428
+ bottom: number;
3429
+ };
3430
+ width: number;
3431
+ height: number;
3432
+ };
3433
+ _setPositionFromLayout(newCenter: {
3434
+ x: number;
3435
+ y: number;
3436
+ }): void;
3413
3437
  doInitialPortMatching(): void;
3414
3438
  doInitialPcbPrimitiveRender(): void;
3415
3439
  }
@@ -3503,7 +3527,7 @@ declare class Hole extends PrimitiveComponent<typeof holeProps> {
3503
3527
  pcb_hole_id: string | null;
3504
3528
  isPcbPrimitive: boolean;
3505
3529
  get config(): {
3506
- zodProps: zod.ZodObject<zod.objectUtil.extendShape<Omit<{
3530
+ zodProps: zod.ZodEffects<zod.ZodObject<zod.objectUtil.extendShape<Omit<{
3507
3531
  pcbX: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3508
3532
  pcbY: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3509
3533
  pcbRotation: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
@@ -3517,19 +3541,41 @@ declare class Hole extends PrimitiveComponent<typeof holeProps> {
3517
3541
  name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3518
3542
  }>>;
3519
3543
  }, "pcbRotation">, {
3520
- holeDiameter: zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>;
3544
+ name: zod.ZodOptional<zod.ZodString>;
3545
+ diameter: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3546
+ radius: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3521
3547
  }>, "strip", zod.ZodTypeAny, {
3522
- holeDiameter: number;
3523
3548
  pcbX?: number | undefined;
3524
3549
  pcbY?: number | undefined;
3525
3550
  layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | undefined;
3551
+ name?: string | undefined;
3552
+ radius?: number | undefined;
3553
+ diameter?: number | undefined;
3526
3554
  }, {
3527
- holeDiameter: string | number;
3528
3555
  pcbX?: string | number | undefined;
3529
3556
  pcbY?: string | number | undefined;
3530
3557
  layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | {
3531
3558
  name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3532
3559
  } | undefined;
3560
+ name?: string | undefined;
3561
+ radius?: string | number | undefined;
3562
+ diameter?: string | number | undefined;
3563
+ }>, {
3564
+ diameter: number;
3565
+ radius: number;
3566
+ pcbX?: number | undefined;
3567
+ pcbY?: number | undefined;
3568
+ layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | undefined;
3569
+ name?: string | undefined;
3570
+ }, {
3571
+ pcbX?: string | number | undefined;
3572
+ pcbY?: string | number | undefined;
3573
+ layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | {
3574
+ name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3575
+ } | undefined;
3576
+ name?: string | undefined;
3577
+ radius?: string | number | undefined;
3578
+ diameter?: string | number | undefined;
3533
3579
  }>;
3534
3580
  };
3535
3581
  getPcbSize(): {
@@ -3537,6 +3583,24 @@ declare class Hole extends PrimitiveComponent<typeof holeProps> {
3537
3583
  height: number;
3538
3584
  };
3539
3585
  doInitialPcbPrimitiveRender(): void;
3586
+ _getCircuitJsonBounds(): {
3587
+ center: {
3588
+ x: number;
3589
+ y: number;
3590
+ };
3591
+ bounds: {
3592
+ left: number;
3593
+ top: number;
3594
+ right: number;
3595
+ bottom: number;
3596
+ };
3597
+ width: number;
3598
+ height: number;
3599
+ };
3600
+ _setPositionFromLayout(newCenter: {
3601
+ x: number;
3602
+ y: number;
3603
+ }): void;
3540
3604
  }
3541
3605
 
3542
3606
  declare global {
package/dist/index.js CHANGED
@@ -535,16 +535,15 @@ var Footprint = class extends PrimitiveComponent {
535
535
  function getKVar(name) {
536
536
  if (!(name in kVars)) {
537
537
  kVars[name] = new kiwi.Variable(name);
538
+ solver.addEditVariable(kVars[name], kiwi.Strength.weak);
538
539
  }
539
540
  return kVars[name];
540
541
  }
541
- for (const { selector, bounds } of involvedComponents) {
542
+ for (const { selector, bounds: bounds2 } of involvedComponents) {
542
543
  const kvx = getKVar(`${selector}_x`);
543
544
  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);
545
+ solver.suggestValue(kvx, bounds2.center.x);
546
+ solver.suggestValue(kvy, bounds2.center.y);
548
547
  }
549
548
  for (const constraint of constraints) {
550
549
  const props = constraint._parsedProps;
@@ -567,7 +566,7 @@ var Footprint = class extends PrimitiveComponent {
567
566
  } else if (edgeToEdge) {
568
567
  const expr = new kiwi.Expression(
569
568
  rightVar,
570
- rightBounds.width / 2,
569
+ -rightBounds.width / 2,
571
570
  [-1, leftVar],
572
571
  -leftBounds.width / 2
573
572
  );
@@ -580,50 +579,82 @@ var Footprint = class extends PrimitiveComponent {
580
579
  )
581
580
  );
582
581
  }
582
+ } else if ("ydist" in props) {
583
+ const { ydist, top, bottom, edgeToEdge, centerToCenter } = props;
584
+ const topVar = getKVar(`${top}_y`);
585
+ const bottomVar = getKVar(`${bottom}_y`);
586
+ const topBounds = getComponentDetails(top)?.bounds;
587
+ const bottomBounds = getComponentDetails(bottom)?.bounds;
588
+ if (centerToCenter) {
589
+ const expr = new kiwi.Expression(topVar, [-1, bottomVar]);
590
+ solver.addConstraint(
591
+ new kiwi.Constraint(
592
+ expr,
593
+ kiwi.Operator.Eq,
594
+ props.ydist,
595
+ kiwi.Strength.required
596
+ )
597
+ );
598
+ } else if (edgeToEdge) {
599
+ const expr = new kiwi.Expression(
600
+ topVar,
601
+ topBounds.height / 2,
602
+ [-1, bottomVar],
603
+ -bottomBounds.height / 2
604
+ );
605
+ solver.addConstraint(
606
+ new kiwi.Constraint(
607
+ expr,
608
+ kiwi.Operator.Eq,
609
+ props.ydist,
610
+ kiwi.Strength.required
611
+ )
612
+ );
613
+ }
583
614
  }
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
- }
615
+ }
616
+ solver.updateVariables();
617
+ if (debug.enabled) {
618
+ console.log("Solution to layout constraints:");
619
+ console.table(
620
+ Object.entries(kVars).map(([key, kvar]) => ({
621
+ var: key,
622
+ val: kvar.value()
623
+ }))
624
+ );
625
+ }
626
+ const bounds = {
627
+ left: Infinity,
628
+ right: -Infinity,
629
+ top: -Infinity,
630
+ bottom: Infinity
631
+ };
632
+ for (const {
633
+ selector,
634
+ bounds: { width, height }
635
+ } of involvedComponents) {
636
+ const kvx = getKVar(`${selector}_x`);
637
+ const kvy = getKVar(`${selector}_y`);
638
+ const newLeft = kvx.value() - width / 2;
639
+ const newRight = kvx.value() + width / 2;
640
+ const newTop = kvy.value() + height / 2;
641
+ const newBottom = kvy.value() - height / 2;
642
+ bounds.left = Math.min(bounds.left, newLeft);
643
+ bounds.right = Math.max(bounds.right, newRight);
644
+ bounds.top = Math.max(bounds.top, newTop);
645
+ bounds.bottom = Math.min(bounds.bottom, newBottom);
646
+ }
647
+ const globalOffset = {
648
+ x: -(bounds.right + bounds.left) / 2,
649
+ y: -(bounds.top + bounds.bottom) / 2
650
+ };
651
+ for (const { component, selector } of involvedComponents) {
652
+ const kvx = getKVar(`${selector}_x`);
653
+ const kvy = getKVar(`${selector}_y`);
654
+ component._setPositionFromLayout({
655
+ x: kvx.value() + globalOffset.x,
656
+ y: kvy.value() + globalOffset.y
657
+ });
627
658
  }
628
659
  }
629
660
  };
@@ -1164,6 +1195,29 @@ var PlatedHole = class extends PrimitiveComponent {
1164
1195
  `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
1165
1196
  );
1166
1197
  }
1198
+ _getCircuitJsonBounds() {
1199
+ const { db } = this.root;
1200
+ const platedHole = db.pcb_plated_hole.get(this.pcb_plated_hole_id);
1201
+ const size = this.getPcbSize();
1202
+ return {
1203
+ center: { x: platedHole.x, y: platedHole.y },
1204
+ bounds: {
1205
+ left: platedHole.x - size.width / 2,
1206
+ top: platedHole.y - size.height / 2,
1207
+ right: platedHole.x + size.width / 2,
1208
+ bottom: platedHole.y + size.height / 2
1209
+ },
1210
+ width: size.width,
1211
+ height: size.height
1212
+ };
1213
+ }
1214
+ _setPositionFromLayout(newCenter) {
1215
+ const { db } = this.root;
1216
+ db.pcb_plated_hole.update(this.pcb_plated_hole_id, {
1217
+ x: newCenter.x,
1218
+ y: newCenter.y
1219
+ });
1220
+ }
1167
1221
  doInitialPortMatching() {
1168
1222
  const parentPorts = (this.parent?.children ?? []).filter(
1169
1223
  (c) => c.componentName === "Port"
@@ -1185,19 +1239,19 @@ var PlatedHole = class extends PrimitiveComponent {
1185
1239
  if (!props.portHints) return;
1186
1240
  const position = this._getGlobalPcbPositionBeforeLayout();
1187
1241
  if (props.shape === "circle") {
1188
- const plated_hole_input = {
1242
+ const pcb_plated_hole = db.pcb_plated_hole.insert({
1189
1243
  pcb_component_id: this.parent?.pcb_component_id,
1190
1244
  pcb_port_id: this.matchedPort?.pcb_port_id,
1191
- layers: ["top", "bottom"],
1245
+ // @ts-ignore - some issue with @tscircuit/soup union type
1192
1246
  outer_diameter: props.outerDiameter,
1193
1247
  hole_diameter: props.holeDiameter,
1194
1248
  shape: "circle",
1195
1249
  port_hints: this.getNameAndAliases(),
1196
1250
  x: position.x,
1197
1251
  y: position.y,
1198
- type: "pcb_plated_hole"
1199
- };
1200
- const pcb_plated_hole = db.pcb_plated_hole.insert(plated_hole_input);
1252
+ layers: ["top", "bottom"]
1253
+ });
1254
+ this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id;
1201
1255
  }
1202
1256
  }
1203
1257
  };
@@ -2934,20 +2988,43 @@ var Hole = class extends PrimitiveComponent {
2934
2988
  }
2935
2989
  getPcbSize() {
2936
2990
  const { _parsedProps: props } = this;
2937
- return { width: props.holeDiameter, height: props.holeDiameter };
2991
+ return { width: props.diameter, height: props.diameter };
2938
2992
  }
2939
2993
  doInitialPcbPrimitiveRender() {
2940
2994
  const { db } = this.root;
2941
2995
  const { _parsedProps: props } = this;
2942
2996
  const position = this._getGlobalPcbPositionBeforeLayout();
2943
- const pcb_hole = {
2944
- type: "pcb_hole",
2997
+ const inserted_hole = db.pcb_hole.insert({
2945
2998
  hole_shape: "round",
2946
- hole_diameter: props.holeDiameter,
2999
+ // @ts-ignore
3000
+ hole_diameter: props.diameter,
2947
3001
  x: position.x,
2948
3002
  y: position.y
3003
+ });
3004
+ this.pcb_hole_id = inserted_hole.pcb_hole_id;
3005
+ }
3006
+ _getCircuitJsonBounds() {
3007
+ const { db } = this.root;
3008
+ const hole = db.pcb_hole.get(this.pcb_hole_id);
3009
+ const size = this.getPcbSize();
3010
+ return {
3011
+ center: { x: hole.x, y: hole.y },
3012
+ bounds: {
3013
+ left: hole.x - size.width / 2,
3014
+ top: hole.y - size.height / 2,
3015
+ right: hole.x + size.width / 2,
3016
+ bottom: hole.y + size.height / 2
3017
+ },
3018
+ width: size.width,
3019
+ height: size.height
2949
3020
  };
2950
- const inserted_hole = db.pcb_hole.insert(pcb_hole);
3021
+ }
3022
+ _setPositionFromLayout(newCenter) {
3023
+ const { db } = this.root;
3024
+ db.pcb_hole.update(this.pcb_hole_id, {
3025
+ x: newCenter.x,
3026
+ y: newCenter.y
3027
+ });
2951
3028
  }
2952
3029
  };
2953
3030
 
@@ -39,6 +39,7 @@ export class Footprint extends PrimitiveComponent<typeof footprintProps> {
39
39
  function getKVar(name: string) {
40
40
  if (!(name in kVars)) {
41
41
  kVars[name] = new kiwi.Variable(name)
42
+ solver.addEditVariable(kVars[name], kiwi.Strength.weak)
42
43
  }
43
44
  return kVars[name]
44
45
  }
@@ -47,8 +48,6 @@ export class Footprint extends PrimitiveComponent<typeof footprintProps> {
47
48
  for (const { selector, bounds } of involvedComponents) {
48
49
  const kvx = getKVar(`${selector}_x`)
49
50
  const kvy = getKVar(`${selector}_y`)
50
- solver.addEditVariable(kvx, kiwi.Strength.weak)
51
- solver.addEditVariable(kvy, kiwi.Strength.weak)
52
51
  solver.suggestValue(kvx, bounds.center.x)
53
52
  solver.suggestValue(kvy, bounds.center.y)
54
53
  }
@@ -77,10 +76,10 @@ export class Footprint extends PrimitiveComponent<typeof footprintProps> {
77
76
  )
78
77
  } else if (edgeToEdge) {
79
78
  // rightEdge - leftEdge = xdist
80
- // right + rightBounds.width/2 - left - leftBounds.width/2 = xdist
79
+ // right - rightBounds.width/2 - left - leftBounds.width/2 = xdist
81
80
  const expr = new kiwi.Expression(
82
81
  rightVar,
83
- rightBounds.width / 2,
82
+ -rightBounds.width / 2,
84
83
  [-1, leftVar],
85
84
  -leftBounds.width / 2,
86
85
  )
@@ -93,69 +92,107 @@ export class Footprint extends PrimitiveComponent<typeof footprintProps> {
93
92
  ),
94
93
  )
95
94
  }
96
- }
95
+ } else if ("ydist" in props) {
96
+ const { ydist, top, bottom, edgeToEdge, centerToCenter } = props
97
+ const topVar = getKVar(`${top}_y`)
98
+ const bottomVar = getKVar(`${bottom}_y`)
99
+ const topBounds = getComponentDetails(top)?.bounds!
100
+ const bottomBounds = getComponentDetails(bottom)?.bounds!
97
101
 
98
- // 3. Solve the system of equations
99
- solver.updateVariables()
100
- if (debug.enabled) {
101
- console.log("Solution to layout constraints:")
102
- console.table(
103
- Object.entries(kVars).map(([key, kvar]) => ({
104
- var: key,
105
- val: kvar.value(),
106
- })),
107
- )
108
- }
102
+ // Top - Bottom = ydist
109
103
 
110
- // 3.1 Compute the global offset. There are different ways to do this:
111
- // - If any component has a fixed position, then that can be used as the
112
- // origin to determine the offset of all other components
113
- // - If no component has a fixed position, then we recenter everything
114
- // using the new bounds of all the involved components
115
-
116
- // TODO determine if there's a fixed component
117
-
118
- // Determine the new bounds all the involved components and compute the
119
- // bounds of this footprint
120
- const bounds = {
121
- left: Infinity,
122
- right: -Infinity,
123
- top: -Infinity,
124
- bottom: Infinity,
125
- }
126
- for (const {
127
- selector,
128
- bounds: { width, height },
129
- } of involvedComponents) {
130
- const kvx = getKVar(`${selector}_x`)
131
- const kvy = getKVar(`${selector}_y`)
132
-
133
- const newLeft = kvx.value() - width / 2
134
- const newRight = kvx.value() + width / 2
135
- const newTop = kvy.value() + height / 2
136
- const newBottom = kvy.value() - height / 2
137
-
138
- bounds.left = Math.min(bounds.left, newLeft)
139
- bounds.right = Math.max(bounds.right, newRight)
140
- bounds.top = Math.max(bounds.top, newTop)
141
- bounds.bottom = Math.min(bounds.bottom, newBottom)
104
+ if (centerToCenter) {
105
+ // top - bottom = ydist
106
+ const expr = new kiwi.Expression(topVar, [-1, bottomVar])
107
+ solver.addConstraint(
108
+ new kiwi.Constraint(
109
+ expr,
110
+ kiwi.Operator.Eq,
111
+ props.ydist,
112
+ kiwi.Strength.required,
113
+ ),
114
+ )
115
+ } else if (edgeToEdge) {
116
+ // topElmBottomEdge - bottomElmTopEdge = ydist
117
+ // topElmCenterY - topElmHeight/2 - bottomElmCenterY - bottomElmHeight/2 = ydist
118
+ const expr = new kiwi.Expression(
119
+ topVar,
120
+ topBounds.height / 2,
121
+ [-1, bottomVar],
122
+ -bottomBounds.height / 2,
123
+ )
124
+ solver.addConstraint(
125
+ new kiwi.Constraint(
126
+ expr,
127
+ kiwi.Operator.Eq,
128
+ props.ydist,
129
+ kiwi.Strength.required,
130
+ ),
131
+ )
132
+ }
142
133
  }
134
+ }
143
135
 
144
- // Compute the global offset, we can use this to recenter each component
145
- const globalOffset = {
146
- x: -(bounds.right + bounds.left) / 2,
147
- y: -(bounds.top + bounds.bottom) / 2,
148
- }
136
+ // 3. Solve the system of equations
137
+ solver.updateVariables()
138
+ if (debug.enabled) {
139
+ console.log("Solution to layout constraints:")
140
+ console.table(
141
+ Object.entries(kVars).map(([key, kvar]) => ({
142
+ var: key,
143
+ val: kvar.value(),
144
+ })),
145
+ )
146
+ }
149
147
 
150
- // 4. Update the component positions
151
- for (const { component, selector } of involvedComponents) {
152
- const kvx = getKVar(`${selector}_x`)
153
- const kvy = getKVar(`${selector}_y`)
154
- component._setPositionFromLayout({
155
- x: kvx.value() + globalOffset.x,
156
- y: kvy.value() + globalOffset.y,
157
- })
158
- }
148
+ // 3.1 Compute the global offset. There are different ways to do this:
149
+ // - If any component has a fixed position, then that can be used as the
150
+ // origin to determine the offset of all other components
151
+ // - If no component has a fixed position, then we recenter everything
152
+ // using the new bounds of all the involved components
153
+
154
+ // TODO determine if there's a fixed component
155
+
156
+ // Determine the new bounds all the involved components and compute the
157
+ // bounds of this footprint
158
+ const bounds = {
159
+ left: Infinity,
160
+ right: -Infinity,
161
+ top: -Infinity,
162
+ bottom: Infinity,
163
+ }
164
+ for (const {
165
+ selector,
166
+ bounds: { width, height },
167
+ } of involvedComponents) {
168
+ const kvx = getKVar(`${selector}_x`)
169
+ const kvy = getKVar(`${selector}_y`)
170
+
171
+ const newLeft = kvx.value() - width / 2
172
+ const newRight = kvx.value() + width / 2
173
+ const newTop = kvy.value() + height / 2
174
+ const newBottom = kvy.value() - height / 2
175
+
176
+ bounds.left = Math.min(bounds.left, newLeft)
177
+ bounds.right = Math.max(bounds.right, newRight)
178
+ bounds.top = Math.max(bounds.top, newTop)
179
+ bounds.bottom = Math.min(bounds.bottom, newBottom)
180
+ }
181
+
182
+ // Compute the global offset, we can use this to recenter each component
183
+ const globalOffset = {
184
+ x: -(bounds.right + bounds.left) / 2,
185
+ y: -(bounds.top + bounds.bottom) / 2,
186
+ }
187
+
188
+ // 4. Update the component positions
189
+ for (const { component, selector } of involvedComponents) {
190
+ const kvx = getKVar(`${selector}_x`)
191
+ const kvy = getKVar(`${selector}_y`)
192
+ component._setPositionFromLayout({
193
+ x: kvx.value() + globalOffset.x,
194
+ y: kvy.value() + globalOffset.y,
195
+ })
159
196
  }
160
197
  }
161
198
  }
@@ -14,7 +14,7 @@ export class Hole extends PrimitiveComponent<typeof holeProps> {
14
14
 
15
15
  getPcbSize(): { width: number; height: number } {
16
16
  const { _parsedProps: props } = this
17
- return { width: props.holeDiameter, height: props.holeDiameter }
17
+ return { width: props.diameter, height: props.diameter }
18
18
  }
19
19
 
20
20
  doInitialPcbPrimitiveRender(): void {
@@ -22,15 +22,44 @@ export class Hole extends PrimitiveComponent<typeof holeProps> {
22
22
  const { _parsedProps: props } = this
23
23
  const position = this._getGlobalPcbPositionBeforeLayout()
24
24
 
25
- const pcb_hole: PCBHole = {
26
- type: "pcb_hole",
25
+ const inserted_hole = db.pcb_hole.insert({
27
26
  hole_shape: "round",
28
- hole_diameter: props.holeDiameter,
27
+ // @ts-ignore
28
+ hole_diameter: props.diameter,
29
29
  x: position.x,
30
30
  y: position.y,
31
+ })
32
+ this.pcb_hole_id = inserted_hole.pcb_hole_id!
33
+ }
34
+
35
+ _getCircuitJsonBounds(): {
36
+ center: { x: number; y: number }
37
+ bounds: { left: number; top: number; right: number; bottom: number }
38
+ width: number
39
+ height: number
40
+ } {
41
+ const { db } = this.root!
42
+ const hole = db.pcb_hole.get(this.pcb_hole_id!)!
43
+ const size = this.getPcbSize()
44
+
45
+ return {
46
+ center: { x: hole.x, y: hole.y },
47
+ bounds: {
48
+ left: hole.x - size.width / 2,
49
+ top: hole.y - size.height / 2,
50
+ right: hole.x + size.width / 2,
51
+ bottom: hole.y + size.height / 2,
52
+ },
53
+ width: size.width,
54
+ height: size.height,
31
55
  }
56
+ }
32
57
 
33
- const inserted_hole = db.pcb_hole.insert(pcb_hole)
34
- // this.pcb_hole_id = inserted_hole.pcb_hole_id!
58
+ _setPositionFromLayout(newCenter: { x: number; y: number }) {
59
+ const { db } = this.root!
60
+ db.pcb_hole.update(this.pcb_hole_id!, {
61
+ x: newCenter.x,
62
+ y: newCenter.y,
63
+ })
35
64
  }
36
65
  }
@@ -27,6 +27,37 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
27
27
  )
28
28
  }
29
29
 
30
+ _getCircuitJsonBounds(): {
31
+ center: { x: number; y: number }
32
+ bounds: { left: number; top: number; right: number; bottom: number }
33
+ width: number
34
+ height: number
35
+ } {
36
+ const { db } = this.root!
37
+ const platedHole = db.pcb_plated_hole.get(this.pcb_plated_hole_id!)!
38
+ const size = this.getPcbSize()
39
+
40
+ return {
41
+ center: { x: platedHole.x, y: platedHole.y },
42
+ bounds: {
43
+ left: platedHole.x - size.width / 2,
44
+ top: platedHole.y - size.height / 2,
45
+ right: platedHole.x + size.width / 2,
46
+ bottom: platedHole.y + size.height / 2,
47
+ },
48
+ width: size.width,
49
+ height: size.height,
50
+ }
51
+ }
52
+
53
+ _setPositionFromLayout(newCenter: { x: number; y: number }) {
54
+ const { db } = this.root!
55
+ db.pcb_plated_hole.update(this.pcb_plated_hole_id!, {
56
+ x: newCenter.x,
57
+ y: newCenter.y,
58
+ })
59
+ }
60
+
30
61
  doInitialPortMatching(): void {
31
62
  const parentPorts = (this.parent?.children ?? []).filter(
32
63
  (c) => c.componentName === "Port",
@@ -51,23 +82,20 @@ export class PlatedHole extends PrimitiveComponent<typeof platedHoleProps> {
51
82
  if (!props.portHints) return
52
83
  const position = this._getGlobalPcbPositionBeforeLayout()
53
84
  if (props.shape === "circle") {
54
- const plated_hole_input: PCBPlatedHoleInput = {
85
+ const pcb_plated_hole = db.pcb_plated_hole.insert({
55
86
  pcb_component_id: this.parent?.pcb_component_id!,
56
87
  pcb_port_id: this.matchedPort?.pcb_port_id!,
57
- layers: ["top", "bottom"],
88
+ // @ts-ignore - some issue with @tscircuit/soup union type
58
89
  outer_diameter: props.outerDiameter,
59
90
  hole_diameter: props.holeDiameter,
60
- shape: "circle",
91
+ shape: "circle" as const,
61
92
  port_hints: this.getNameAndAliases(),
62
93
  x: position.x,
63
94
  y: position.y,
64
- type: "pcb_plated_hole",
65
- }
66
-
67
- // @ts-ignore - some issue with soup-util types it seems
68
- const pcb_plated_hole = db.pcb_plated_hole.insert(plated_hole_input)
95
+ layers: ["top", "bottom"],
96
+ })
69
97
 
70
- // this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id
98
+ this.pcb_plated_hole_id = pcb_plated_hole.pcb_plated_hole_id
71
99
  }
72
100
  }
73
101
  }
@@ -1,21 +1,23 @@
1
- import type { SchematicPinStyles } from "@tscircuit/props"
1
+ import type { SchematicPinStyle } from "@tscircuit/props"
2
2
  import { schematic_component } from "@tscircuit/soup"
3
3
  import { z } from "zod"
4
4
 
5
5
  type UnderscorePinStyles = z.input<typeof schematic_component>["pin_styles"]
6
6
 
7
7
  export const underscorifyPinStyles = (
8
- pinStyles: SchematicPinStyles | undefined,
8
+ pinStyles: Record<string, SchematicPinStyle> | undefined,
9
9
  ): UnderscorePinStyles | undefined => {
10
10
  if (!pinStyles) return undefined
11
11
  const underscorePinStyles: UnderscorePinStyles = {}
12
12
 
13
- for (const [pinName, pinStyle] of Object.entries(pinStyles)) {
13
+ for (const [pinName, pinStyle] of Object.entries(pinStyles) as Array<
14
+ [string, SchematicPinStyle]
15
+ >) {
14
16
  underscorePinStyles[pinName] = {
15
- bottom_margin: pinStyle.bottomMargin,
16
- left_margin: pinStyle.leftMargin,
17
- right_margin: pinStyle.rightMargin,
18
- top_margin: pinStyle.topMargin,
17
+ bottom_margin: pinStyle.bottomMargin as number,
18
+ left_margin: pinStyle.leftMargin as number,
19
+ right_margin: pinStyle.rightMargin as number,
20
+ top_margin: pinStyle.topMargin as number,
19
21
  }
20
22
  }
21
23
 
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.32",
5
+ "version": "0.0.33",
6
6
  "types": "dist/index.d.ts",
7
7
  "main": "dist/index.js",
8
8
  "files": [
@@ -35,8 +35,8 @@
35
35
  "dependencies": {
36
36
  "@lume/kiwi": "^0.4.3",
37
37
  "@tscircuit/infgrid-ijump-astar": "^0.0.6",
38
- "@tscircuit/props": "^0.0.58",
39
- "@tscircuit/soup": "^0.0.58",
38
+ "@tscircuit/props": "^0.0.61",
39
+ "@tscircuit/soup": "^0.0.66",
40
40
  "@tscircuit/soup-util": "0.0.18",
41
41
  "footprinter": "^0.0.44",
42
42
  "react": "^18.3.1",