@tscircuit/core 0.0.31 → 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
@@ -6,7 +6,7 @@ import { ReactElement } from 'react';
6
6
  import { Matrix } from 'transformation-matrix';
7
7
  import { SchSymbol, BaseSymbolName } from 'schematic-symbols';
8
8
  import * as Props from '@tscircuit/props';
9
- import { traceProps, groupProps, boardProps, footprintProps, smtPadProps, resistorProps, ledProps, capacitorProps, traceHintProps, chipProps, jumperProps, silkscreenPathProps, platedHoleProps, constraintProps } from '@tscircuit/props';
9
+ import { traceProps, groupProps, boardProps, footprintProps, smtPadProps, resistorProps, ledProps, capacitorProps, traceHintProps, chipProps, jumperProps, silkscreenPathProps, platedHoleProps, constraintProps, holeProps } from '@tscircuit/props';
10
10
 
11
11
  declare class Circuit {
12
12
  firstChild: PrimitiveComponent | null;
@@ -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
  }
@@ -3499,6 +3523,86 @@ declare class Constraint extends PrimitiveComponent<typeof constraintProps> {
3499
3523
  };
3500
3524
  }
3501
3525
 
3526
+ declare class Hole extends PrimitiveComponent<typeof holeProps> {
3527
+ pcb_hole_id: string | null;
3528
+ isPcbPrimitive: boolean;
3529
+ get config(): {
3530
+ zodProps: zod.ZodEffects<zod.ZodObject<zod.objectUtil.extendShape<Omit<{
3531
+ pcbX: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3532
+ pcbY: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3533
+ pcbRotation: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodString, zod.ZodNumber]>, number, string | number>>;
3534
+ layer: zod.ZodOptional<zod.ZodEffects<zod.ZodUnion<[zod.ZodEnum<["top", "bottom", "inner1", "inner2", "inner3", "inner4", "inner5", "inner6"]>, zod.ZodObject<{
3535
+ name: zod.ZodEnum<["top", "bottom", "inner1", "inner2", "inner3", "inner4", "inner5", "inner6"]>;
3536
+ }, "strip", zod.ZodTypeAny, {
3537
+ name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3538
+ }, {
3539
+ name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3540
+ }>]>, "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6", "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | {
3541
+ name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
3542
+ }>>;
3543
+ }, "pcbRotation">, {
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>>;
3547
+ }>, "strip", zod.ZodTypeAny, {
3548
+ pcbX?: number | undefined;
3549
+ pcbY?: number | undefined;
3550
+ layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | undefined;
3551
+ name?: string | undefined;
3552
+ radius?: number | undefined;
3553
+ diameter?: number | undefined;
3554
+ }, {
3555
+ pcbX?: string | number | undefined;
3556
+ pcbY?: string | number | undefined;
3557
+ layer?: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6" | {
3558
+ name: "top" | "bottom" | "inner1" | "inner2" | "inner3" | "inner4" | "inner5" | "inner6";
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;
3579
+ }>;
3580
+ };
3581
+ getPcbSize(): {
3582
+ width: number;
3583
+ height: number;
3584
+ };
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;
3604
+ }
3605
+
3502
3606
  declare global {
3503
3607
  namespace JSX {
3504
3608
  interface IntrinsicElements {
@@ -3544,4 +3648,4 @@ declare global {
3544
3648
  }
3545
3649
  }
3546
3650
 
3547
- export { Board, Capacitor, Chip, Circuit, Constraint, Footprint, Group, type IRenderable, Jumper, Led, Net, NormalComponent, PlatedHole, Port, PrimitiveComponent, Project, Renderable, Resistor, SilkscreenPath, SmtPad, Trace, TraceHint };
3651
+ export { Board, Capacitor, Chip, Circuit, Constraint, Footprint, Group, Hole, type IRenderable, Jumper, Led, Net, NormalComponent, PlatedHole, Port, PrimitiveComponent, Project, Renderable, Resistor, SilkscreenPath, SmtPad, Trace, TraceHint };
package/dist/index.js CHANGED
@@ -13,6 +13,7 @@ __export(components_exports, {
13
13
  Constraint: () => Constraint2,
14
14
  Footprint: () => Footprint,
15
15
  Group: () => Group,
16
+ Hole: () => Hole,
16
17
  Jumper: () => Jumper,
17
18
  Led: () => Led,
18
19
  Net: () => Net,
@@ -534,16 +535,15 @@ var Footprint = class extends PrimitiveComponent {
534
535
  function getKVar(name) {
535
536
  if (!(name in kVars)) {
536
537
  kVars[name] = new kiwi.Variable(name);
538
+ solver.addEditVariable(kVars[name], kiwi.Strength.weak);
537
539
  }
538
540
  return kVars[name];
539
541
  }
540
- for (const { selector, bounds } of involvedComponents) {
542
+ for (const { selector, bounds: bounds2 } of involvedComponents) {
541
543
  const kvx = getKVar(`${selector}_x`);
542
544
  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);
545
+ solver.suggestValue(kvx, bounds2.center.x);
546
+ solver.suggestValue(kvy, bounds2.center.y);
547
547
  }
548
548
  for (const constraint of constraints) {
549
549
  const props = constraint._parsedProps;
@@ -566,7 +566,7 @@ var Footprint = class extends PrimitiveComponent {
566
566
  } else if (edgeToEdge) {
567
567
  const expr = new kiwi.Expression(
568
568
  rightVar,
569
- rightBounds.width / 2,
569
+ -rightBounds.width / 2,
570
570
  [-1, leftVar],
571
571
  -leftBounds.width / 2
572
572
  );
@@ -579,50 +579,82 @@ var Footprint = class extends PrimitiveComponent {
579
579
  )
580
580
  );
581
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
+ }
582
614
  }
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
- }
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
+ });
626
658
  }
627
659
  }
628
660
  };
@@ -1163,6 +1195,29 @@ var PlatedHole = class extends PrimitiveComponent {
1163
1195
  `getPcbSize for shape "${props.shape}" not implemented for ${this.componentName}`
1164
1196
  );
1165
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
+ }
1166
1221
  doInitialPortMatching() {
1167
1222
  const parentPorts = (this.parent?.children ?? []).filter(
1168
1223
  (c) => c.componentName === "Port"
@@ -1184,19 +1239,19 @@ var PlatedHole = class extends PrimitiveComponent {
1184
1239
  if (!props.portHints) return;
1185
1240
  const position = this._getGlobalPcbPositionBeforeLayout();
1186
1241
  if (props.shape === "circle") {
1187
- const plated_hole_input = {
1242
+ const pcb_plated_hole = db.pcb_plated_hole.insert({
1188
1243
  pcb_component_id: this.parent?.pcb_component_id,
1189
1244
  pcb_port_id: this.matchedPort?.pcb_port_id,
1190
- layers: ["top", "bottom"],
1245
+ // @ts-ignore - some issue with @tscircuit/soup union type
1191
1246
  outer_diameter: props.outerDiameter,
1192
1247
  hole_diameter: props.holeDiameter,
1193
1248
  shape: "circle",
1194
1249
  port_hints: this.getNameAndAliases(),
1195
1250
  x: position.x,
1196
1251
  y: position.y,
1197
- type: "pcb_plated_hole"
1198
- };
1199
- 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;
1200
1255
  }
1201
1256
  }
1202
1257
  };
@@ -2921,6 +2976,58 @@ var Constraint2 = class extends PrimitiveComponent {
2921
2976
  }
2922
2977
  };
2923
2978
 
2979
+ // lib/components/primitive-components/Hole.ts
2980
+ import { holeProps } from "@tscircuit/props";
2981
+ var Hole = class extends PrimitiveComponent {
2982
+ pcb_hole_id = null;
2983
+ isPcbPrimitive = true;
2984
+ get config() {
2985
+ return {
2986
+ zodProps: holeProps
2987
+ };
2988
+ }
2989
+ getPcbSize() {
2990
+ const { _parsedProps: props } = this;
2991
+ return { width: props.diameter, height: props.diameter };
2992
+ }
2993
+ doInitialPcbPrimitiveRender() {
2994
+ const { db } = this.root;
2995
+ const { _parsedProps: props } = this;
2996
+ const position = this._getGlobalPcbPositionBeforeLayout();
2997
+ const inserted_hole = db.pcb_hole.insert({
2998
+ hole_shape: "round",
2999
+ // @ts-ignore
3000
+ hole_diameter: props.diameter,
3001
+ x: position.x,
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
3020
+ };
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
+ });
3028
+ }
3029
+ };
3030
+
2924
3031
  // lib/Project.ts
2925
3032
  import { su } from "@tscircuit/soup-util";
2926
3033
  import { isValidElement as isValidElement2 } from "react";
@@ -3027,6 +3134,7 @@ export {
3027
3134
  Constraint2 as Constraint,
3028
3135
  Footprint,
3029
3136
  Group,
3137
+ Hole,
3030
3138
  Jumper,
3031
3139
  Led,
3032
3140
  Net,
@@ -17,3 +17,4 @@ export { Jumper } from "./normal-components/Jumper"
17
17
  export { SilkscreenPath } from "./primitive-components/SilkscreenPath"
18
18
  export { PlatedHole } from "./primitive-components/PlatedHole"
19
19
  export { Constraint } from "./primitive-components/Constraint"
20
+ export { Hole } from "./primitive-components/Hole"
@@ -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
  }
@@ -0,0 +1,65 @@
1
+ import { PrimitiveComponent } from "../base-components/PrimitiveComponent"
2
+ import { holeProps } from "@tscircuit/props"
3
+ import type { PCBHole } from "@tscircuit/soup"
4
+
5
+ export class Hole extends PrimitiveComponent<typeof holeProps> {
6
+ pcb_hole_id: string | null = null
7
+ isPcbPrimitive = true
8
+
9
+ get config() {
10
+ return {
11
+ zodProps: holeProps,
12
+ }
13
+ }
14
+
15
+ getPcbSize(): { width: number; height: number } {
16
+ const { _parsedProps: props } = this
17
+ return { width: props.diameter, height: props.diameter }
18
+ }
19
+
20
+ doInitialPcbPrimitiveRender(): void {
21
+ const { db } = this.root!
22
+ const { _parsedProps: props } = this
23
+ const position = this._getGlobalPcbPositionBeforeLayout()
24
+
25
+ const inserted_hole = db.pcb_hole.insert({
26
+ hole_shape: "round",
27
+ // @ts-ignore
28
+ hole_diameter: props.diameter,
29
+ x: position.x,
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,
55
+ }
56
+ }
57
+
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
+ })
64
+ }
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.31",
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",