@tscircuit/core 0.0.98 → 0.0.100

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +48 -48
  2. package/dist/index.js +863 -831
  3. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -34,13 +34,12 @@ __export(components_exports, {
34
34
  TraceHint: () => TraceHint
35
35
  });
36
36
 
37
- // lib/components/primitive-components/Footprint.ts
38
- import { footprintProps } from "@tscircuit/props";
37
+ // lib/components/base-components/NormalComponent.ts
38
+ import { rotation } from "circuit-json";
39
+ import { fp } from "footprinter";
39
40
 
40
- // lib/components/base-components/PrimitiveComponent.ts
41
- import { z } from "zod";
42
- import { symbols } from "schematic-symbols";
43
- import "react";
41
+ // lib/fiber/create-instance-from-react-element.ts
42
+ import ReactReconciler from "react-reconciler";
44
43
 
45
44
  // lib/components/base-components/Renderable.ts
46
45
  var orderedRenderPhases = [
@@ -133,12 +132,169 @@ var Renderable = class {
133
132
  }
134
133
  };
135
134
 
135
+ // lib/fiber/catalogue.ts
136
+ var catalogue = {};
137
+ var extendCatalogue = (objects) => {
138
+ const altKeys = Object.fromEntries(
139
+ Object.entries(objects).map(([key, v]) => [key.toLowerCase(), v])
140
+ );
141
+ Object.assign(catalogue, objects);
142
+ Object.assign(catalogue, altKeys);
143
+ };
144
+
145
+ // lib/fiber/create-instance-from-react-element.ts
146
+ import { identity } from "transformation-matrix";
147
+ function prepare(object, state) {
148
+ const instance = object;
149
+ instance.__tsci = {
150
+ ...state
151
+ };
152
+ return object;
153
+ }
154
+ var hostConfig = {
155
+ supportsMutation: true,
156
+ createInstance(type, props) {
157
+ const target = catalogue[type];
158
+ if (!target) {
159
+ if (Object.keys(catalogue).length === 0) {
160
+ throw new Error(
161
+ "No components registered in catalogue, did you forget to import lib/register-catalogue in your test file?"
162
+ );
163
+ }
164
+ throw new Error(
165
+ `Unsupported component type (not registered in @tscircuit/core catalogue): "${type}" See CREATING_NEW_COMPONENTS.md`
166
+ );
167
+ }
168
+ const instance = prepare(new target(props), {});
169
+ return instance;
170
+ },
171
+ createTextInstance() {
172
+ return {};
173
+ },
174
+ appendInitialChild(parentInstance, child) {
175
+ parentInstance.add(child);
176
+ },
177
+ appendChild(parentInstance, child) {
178
+ parentInstance.add(child);
179
+ },
180
+ appendChildToContainer(container, child) {
181
+ container.add(child);
182
+ },
183
+ finalizeInitialChildren() {
184
+ return false;
185
+ },
186
+ prepareUpdate() {
187
+ return null;
188
+ },
189
+ shouldSetTextContent() {
190
+ return false;
191
+ },
192
+ getRootHostContext() {
193
+ return {};
194
+ },
195
+ getChildHostContext() {
196
+ return {};
197
+ },
198
+ prepareForCommit() {
199
+ return null;
200
+ },
201
+ resetAfterCommit() {
202
+ },
203
+ commitMount() {
204
+ },
205
+ commitUpdate() {
206
+ },
207
+ removeChild() {
208
+ },
209
+ clearContainer() {
210
+ },
211
+ supportsPersistence: false,
212
+ getPublicInstance(instance) {
213
+ return instance;
214
+ },
215
+ preparePortalMount(containerInfo) {
216
+ throw new Error("Function not implemented.");
217
+ },
218
+ scheduleTimeout(fn, delay) {
219
+ throw new Error("Function not implemented.");
220
+ },
221
+ cancelTimeout(id) {
222
+ throw new Error("Function not implemented.");
223
+ },
224
+ noTimeout: void 0,
225
+ isPrimaryRenderer: false,
226
+ getCurrentEventPriority() {
227
+ throw new Error("Function not implemented.");
228
+ },
229
+ getInstanceFromNode(node) {
230
+ throw new Error("Function not implemented.");
231
+ },
232
+ beforeActiveInstanceBlur() {
233
+ throw new Error("Function not implemented.");
234
+ },
235
+ afterActiveInstanceBlur() {
236
+ throw new Error("Function not implemented.");
237
+ },
238
+ prepareScopeUpdate: (scopeInstance, instance) => {
239
+ throw new Error("Function not implemented.");
240
+ },
241
+ getInstanceFromScope: (scopeInstance) => {
242
+ throw new Error("Function not implemented.");
243
+ },
244
+ detachDeletedInstance: (node) => {
245
+ throw new Error("Function not implemented.");
246
+ },
247
+ supportsHydration: false
248
+ };
249
+ var reconciler = ReactReconciler(hostConfig);
250
+ var createInstanceFromReactElement = (reactElm) => {
251
+ const rootContainer = {
252
+ children: [],
253
+ props: {
254
+ name: "$root"
255
+ },
256
+ add(instance) {
257
+ instance.parent = this;
258
+ this.children.push(instance);
259
+ },
260
+ computePcbGlobalTransform() {
261
+ return identity();
262
+ }
263
+ };
264
+ const container = reconciler.createContainer(
265
+ // TODO Replace with store like react-three-fiber
266
+ // https://github.com/pmndrs/react-three-fiber/blob/a457290856f57741bf8beef4f6ff9dbf4879c0a5/packages/fiber/src/core/index.tsx#L172
267
+ // https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/store.ts#L168
268
+ rootContainer,
269
+ 0,
270
+ null,
271
+ false,
272
+ null,
273
+ "tsci",
274
+ (error) => {
275
+ console.log("Error in createContainer");
276
+ console.error(error);
277
+ },
278
+ null
279
+ );
280
+ reconciler.updateContainer(reactElm, container, null, () => {
281
+ });
282
+ const rootInstance = reconciler.getPublicRootInstance(
283
+ container
284
+ );
285
+ if (rootInstance) return rootInstance;
286
+ return rootContainer.children[0];
287
+ };
288
+
136
289
  // lib/components/base-components/PrimitiveComponent.ts
290
+ import { z } from "zod";
291
+ import { symbols } from "schematic-symbols";
292
+ import "react";
137
293
  import {
138
294
  applyToPoint,
139
295
  compose,
140
296
  flipY,
141
- identity,
297
+ identity as identity2,
142
298
  rotate,
143
299
  translate
144
300
  } from "transformation-matrix";
@@ -267,7 +423,7 @@ var PrimitiveComponent = class extends Renderable {
267
423
  const manualPlacement = this.getSubcircuit()._getManualPlacementForComponent(this);
268
424
  if (manualPlacement && this.props.pcbX === void 0 && this.props.pcbY === void 0) {
269
425
  return compose(
270
- this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
426
+ this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity2(),
271
427
  compose(
272
428
  translate(manualPlacement.x, manualPlacement.y),
273
429
  rotate((props.pcbRotation ?? 0) * Math.PI / 180)
@@ -286,7 +442,7 @@ var PrimitiveComponent = class extends Renderable {
286
442
  translate(-containerCenter.x, -containerCenter.y)
287
443
  );
288
444
  return compose(
289
- this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
445
+ this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity2(),
290
446
  flipY(),
291
447
  this.computePcbPropsTransform()
292
448
  );
@@ -294,7 +450,7 @@ var PrimitiveComponent = class extends Renderable {
294
450
  }
295
451
  }
296
452
  return compose(
297
- this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity(),
453
+ this.parent?._computePcbGlobalTransformBeforeLayout() ?? identity2(),
298
454
  this.computePcbPropsTransform()
299
455
  );
300
456
  }
@@ -356,7 +512,7 @@ var PrimitiveComponent = class extends Renderable {
356
512
  */
357
513
  computeSchematicGlobalTransform() {
358
514
  return compose(
359
- this.parent?.computeSchematicGlobalTransform?.() ?? identity(),
515
+ this.parent?.computeSchematicGlobalTransform?.() ?? identity2(),
360
516
  this.computeSchematicPropsTransform()
361
517
  );
362
518
  }
@@ -566,603 +722,166 @@ var PrimitiveComponent = class extends Renderable {
566
722
  }
567
723
  };
568
724
 
569
- // lib/components/primitive-components/Footprint.ts
570
- import * as kiwi from "@lume/kiwi";
571
- import Debug from "debug";
572
- var debug = Debug("tscircuit:core:footprint");
573
- var Footprint = class extends PrimitiveComponent {
725
+ // lib/components/primitive-components/Net.ts
726
+ import { z as z2 } from "zod";
727
+
728
+ // lib/utils/pairs.ts
729
+ function pairs(arr) {
730
+ const result = [];
731
+ for (let i = 0; i < arr.length - 1; i++) {
732
+ result.push([arr[i], arr[i + 1]]);
733
+ }
734
+ return result;
735
+ }
736
+
737
+ // lib/components/primitive-components/Net.ts
738
+ import { autoroute } from "@tscircuit/infgrid-ijump-astar";
739
+ var netProps = z2.object({
740
+ name: z2.string()
741
+ });
742
+ var Net = class extends PrimitiveComponent {
743
+ source_net_id;
574
744
  get config() {
575
745
  return {
576
- componentName: "Footprint",
577
- zodProps: footprintProps
746
+ componentName: "Net",
747
+ zodProps: netProps
578
748
  };
579
749
  }
750
+ getPortSelector() {
751
+ return `net.${this.props.name}`;
752
+ }
753
+ doInitialSourceRender() {
754
+ const { db } = this.root;
755
+ const { _parsedProps: props } = this;
756
+ const net = db.source_net.insert({
757
+ name: props.name,
758
+ member_source_group_ids: []
759
+ });
760
+ this.source_net_id = net.source_net_id;
761
+ }
580
762
  /**
581
- * A footprint is a constrainedlayout, the db elements are adjusted according
582
- * to any constraints that are defined.
763
+ * Get all ports connected to this net.
764
+ *
765
+ * TODO currently we're not checking for indirect connections (traces that are
766
+ * connected to other traces that are in turn connected to the net)
583
767
  */
584
- doInitialPcbFootprintLayout() {
585
- const constraints = this.children.filter(
586
- (child) => child.componentName === "Constraint"
587
- );
588
- if (constraints.length === 0) return;
589
- const { isFlipped } = this._getPcbPrimitiveFlippedHelpers();
590
- const maybeFlipLeftRight = (props) => {
591
- if (isFlipped) {
592
- if ("left" in props && "right" in props) {
593
- return {
594
- ...props,
595
- left: props.right,
596
- right: props.left
597
- };
768
+ getAllConnectedPorts() {
769
+ const allPorts = this.getSubcircuit().selectAll("port");
770
+ const connectedPorts = [];
771
+ for (const port of allPorts) {
772
+ const traces = port._getDirectlyConnectedTraces();
773
+ for (const trace of traces) {
774
+ if (trace._isExplicitlyConnectedToNet(this)) {
775
+ connectedPorts.push(port);
776
+ break;
598
777
  }
599
778
  }
600
- return props;
601
- };
602
- const involvedComponents = constraints.flatMap(
603
- (constraint) => constraint._getAllReferencedComponents().componentsWithSelectors
604
- ).map(({ component, selector, componentSelector, edge }) => ({
605
- component,
606
- selector,
607
- componentSelector,
608
- edge,
609
- bounds: component._getPcbCircuitJsonBounds()
610
- }));
611
- if (involvedComponents.some((c) => c.edge)) {
612
- throw new Error(
613
- "edge constraints not implemented yet for footprint layout, contributions welcome!"
614
- );
615
- }
616
- function getComponentDetails(selector) {
617
- return involvedComponents.find(({ selector: s }) => s === selector);
618
- }
619
- const solver = new kiwi.Solver();
620
- const kVars = {};
621
- function getKVar(name) {
622
- if (!(name in kVars)) {
623
- kVars[name] = new kiwi.Variable(name);
624
- solver.addEditVariable(kVars[name], kiwi.Strength.weak);
625
- }
626
- return kVars[name];
627
- }
628
- for (const { selector, bounds: bounds2 } of involvedComponents) {
629
- const kvx = getKVar(`${selector}_x`);
630
- const kvy = getKVar(`${selector}_y`);
631
- solver.suggestValue(kvx, bounds2.center.x);
632
- solver.suggestValue(kvy, bounds2.center.y);
633
- }
634
- for (const constraint of constraints) {
635
- const props = constraint._parsedProps;
636
- if ("xDist" in props) {
637
- const { xDist, left, right, edgeToEdge, centerToCenter } = maybeFlipLeftRight(props);
638
- const leftVar = getKVar(`${left}_x`);
639
- const rightVar = getKVar(`${right}_x`);
640
- const leftBounds = getComponentDetails(left)?.bounds;
641
- const rightBounds = getComponentDetails(right)?.bounds;
642
- if (centerToCenter) {
643
- const expr = new kiwi.Expression(rightVar, [-1, leftVar]);
644
- solver.addConstraint(
645
- new kiwi.Constraint(
646
- expr,
647
- kiwi.Operator.Eq,
648
- props.xDist,
649
- kiwi.Strength.required
650
- )
651
- );
652
- } else if (edgeToEdge) {
653
- const expr = new kiwi.Expression(
654
- rightVar,
655
- -rightBounds.width / 2,
656
- [-1, leftVar],
657
- -leftBounds.width / 2
658
- );
659
- solver.addConstraint(
660
- new kiwi.Constraint(
661
- expr,
662
- kiwi.Operator.Eq,
663
- props.xDist,
664
- kiwi.Strength.required
665
- )
666
- );
667
- }
668
- } else if ("yDist" in props) {
669
- const { yDist, top, bottom, edgeToEdge, centerToCenter } = props;
670
- const topVar = getKVar(`${top}_y`);
671
- const bottomVar = getKVar(`${bottom}_y`);
672
- const topBounds = getComponentDetails(top)?.bounds;
673
- const bottomBounds = getComponentDetails(bottom)?.bounds;
674
- if (centerToCenter) {
675
- const expr = new kiwi.Expression(topVar, [-1, bottomVar]);
676
- solver.addConstraint(
677
- new kiwi.Constraint(
678
- expr,
679
- kiwi.Operator.Eq,
680
- props.yDist,
681
- kiwi.Strength.required
682
- )
683
- );
684
- } else if (edgeToEdge) {
685
- const expr = new kiwi.Expression(
686
- topVar,
687
- topBounds.height / 2,
688
- [-1, bottomVar],
689
- -bottomBounds.height / 2
690
- );
691
- solver.addConstraint(
692
- new kiwi.Constraint(
693
- expr,
694
- kiwi.Operator.Eq,
695
- props.yDist,
696
- kiwi.Strength.required
697
- )
698
- );
699
- }
700
- } else if ("sameY" in props) {
701
- const { for: selectors } = props;
702
- if (selectors.length < 2) continue;
703
- const vars = selectors.map((selector) => getKVar(`${selector}_y`));
704
- const expr = new kiwi.Expression(...vars.slice(1));
705
- solver.addConstraint(
706
- new kiwi.Constraint(
707
- expr,
708
- kiwi.Operator.Eq,
709
- vars[0],
710
- kiwi.Strength.required
711
- )
712
- );
713
- } else if ("sameX" in props) {
714
- const { for: selectors } = props;
715
- if (selectors.length < 2) continue;
716
- const vars = selectors.map((selector) => getKVar(`${selector}_x`));
717
- const expr = new kiwi.Expression(...vars.slice(1));
718
- solver.addConstraint(
719
- new kiwi.Constraint(
720
- expr,
721
- kiwi.Operator.Eq,
722
- vars[0],
723
- kiwi.Strength.required
724
- )
725
- );
726
- }
727
- }
728
- solver.updateVariables();
729
- if (debug.enabled) {
730
- console.log("Solution to layout constraints:");
731
- console.table(
732
- Object.entries(kVars).map(([key, kvar]) => ({
733
- var: key,
734
- val: kvar.value()
735
- }))
736
- );
737
- }
738
- const bounds = {
739
- left: Infinity,
740
- right: -Infinity,
741
- top: -Infinity,
742
- bottom: Infinity
743
- };
744
- for (const {
745
- selector,
746
- bounds: { width, height }
747
- } of involvedComponents) {
748
- const kvx = getKVar(`${selector}_x`);
749
- const kvy = getKVar(`${selector}_y`);
750
- const newLeft = kvx.value() - width / 2;
751
- const newRight = kvx.value() + width / 2;
752
- const newTop = kvy.value() + height / 2;
753
- const newBottom = kvy.value() - height / 2;
754
- bounds.left = Math.min(bounds.left, newLeft);
755
- bounds.right = Math.max(bounds.right, newRight);
756
- bounds.top = Math.max(bounds.top, newTop);
757
- bounds.bottom = Math.min(bounds.bottom, newBottom);
758
- }
759
- const globalOffset = {
760
- x: -(bounds.right + bounds.left) / 2,
761
- y: -(bounds.top + bounds.bottom) / 2
762
- };
763
- const containerPos = this.getPrimitiveContainer()._getGlobalPcbPositionBeforeLayout();
764
- globalOffset.x += containerPos.x;
765
- globalOffset.y += containerPos.y;
766
- for (const { component, selector } of involvedComponents) {
767
- const kvx = getKVar(`${selector}_x`);
768
- const kvy = getKVar(`${selector}_y`);
769
- component._setPositionFromLayout({
770
- x: kvx.value() + globalOffset.x,
771
- y: kvy.value() + globalOffset.y
772
- });
773
- }
774
- }
775
- };
776
-
777
- // lib/components/base-components/NormalComponent.ts
778
- import { z as z4 } from "zod";
779
-
780
- // lib/utils/get-relative-direction.ts
781
- function getRelativeDirection(pointA, pointB) {
782
- const dx = pointB.x - pointA.x;
783
- const dy = pointB.y - pointA.y;
784
- if (Math.abs(dx) > Math.abs(dy)) {
785
- return dx > 0 ? "right" : "left";
786
- }
787
- return dy > 0 ? "down" : "up";
788
- }
789
-
790
- // lib/components/primitive-components/Port.ts
791
- import "schematic-symbols";
792
- import { applyToPoint as applyToPoint2, compose as compose2, translate as translate2 } from "transformation-matrix";
793
- import { z as z2 } from "zod";
794
- var portProps = z2.object({
795
- name: z2.string().optional(),
796
- pinNumber: z2.number().optional(),
797
- aliases: z2.array(z2.string()).optional()
798
- });
799
- var Port = class extends PrimitiveComponent {
800
- source_port_id = null;
801
- pcb_port_id = null;
802
- schematic_port_id = null;
803
- schematicSymbolPortDef = null;
804
- matchedComponents;
805
- facingDirection = null;
806
- get config() {
807
- return {
808
- componentName: "Port",
809
- zodProps: portProps
810
- };
811
- }
812
- constructor(props) {
813
- if (!props.name && props.pinNumber) props.name = `pin${props.pinNumber}`;
814
- if (!props.name) {
815
- throw new Error("Port must have a name or a pinNumber");
816
- }
817
- super(props);
818
- this.matchedComponents = [];
819
- }
820
- _getGlobalPcbPositionBeforeLayout() {
821
- const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive);
822
- if (!matchedPcbElm) {
823
- throw new Error(
824
- `Port ${this} has no matched pcb component, can't get global schematic position`
825
- );
826
- }
827
- return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 };
828
- }
829
- _getPcbCircuitJsonBounds() {
830
- if (!this.pcb_port_id) {
831
- return super._getPcbCircuitJsonBounds();
832
- }
833
- const { db } = this.root;
834
- const pcb_port = db.pcb_port.get(this.pcb_port_id);
835
- return {
836
- center: { x: pcb_port.x, y: pcb_port.y },
837
- bounds: { left: 0, top: 0, right: 0, bottom: 0 },
838
- width: 0,
839
- height: 0
840
- };
841
- }
842
- _getGlobalPcbPositionAfterLayout() {
843
- return this._getPcbCircuitJsonBounds().center;
844
- }
845
- _getGlobalSchematicPositionBeforeLayout() {
846
- if (!this.schematicSymbolPortDef) {
847
- return applyToPoint2(this.parent.computeSchematicGlobalTransform(), {
848
- x: 0,
849
- y: 0
850
- });
851
- }
852
- const symbol = this.parent?.getSchematicSymbol();
853
- if (!symbol) {
854
- console.warn(`Could not find parent symbol for ${this}`);
855
- return { x: 0, y: 0 };
856
- }
857
- const transform = compose2(
858
- this.parent.computeSchematicGlobalTransform(),
859
- translate2(-symbol.center.x, -symbol.center.y)
860
- );
861
- return applyToPoint2(transform, this.schematicSymbolPortDef);
862
- }
863
- /**
864
- * Smtpads and platedholes call this method to register themselves as a match
865
- * for this port. All the matching is done by primitives other than the Port,
866
- * but everyone registers themselves as a match with their Port.
867
- */
868
- registerMatch(component) {
869
- this.matchedComponents.push(component);
870
- }
871
- getNameAndAliases() {
872
- const { _parsedProps: props } = this;
873
- return Array.from(
874
- /* @__PURE__ */ new Set([
875
- ...props.aliases ?? [],
876
- props.name,
877
- ...typeof props.pinNumber === "number" ? [`pin${props.pinNumber}`, props.pinNumber.toString()] : [],
878
- ...this.externallyAddedAliases
879
- ])
880
- );
881
- }
882
- isMatchingPort(port) {
883
- return this.isMatchingAnyOf(port.getNameAndAliases());
884
- }
885
- getPortSelector() {
886
- return `.${this.parent?.props.name} > port.${this.props.name}`;
887
- }
888
- getAvailablePcbLayers() {
889
- return Array.from(
890
- new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
891
- );
892
- }
893
- /**
894
- * Return traces that are explicitly connected to this port (not via a net)
895
- */
896
- _getDirectlyConnectedTraces() {
897
- const allSubcircuitTraces = this.getSubcircuit().selectAll(
898
- "trace"
899
- );
900
- const connectedTraces = allSubcircuitTraces.filter(
901
- (trace) => trace._isExplicitlyConnectedToPort(this)
902
- );
903
- return connectedTraces;
904
- }
905
- doInitialSourceRender() {
906
- const { db } = this.root;
907
- const { _parsedProps: props } = this;
908
- const port_hints = this.getNameAndAliases();
909
- const source_port = db.source_port.insert({
910
- name: props.name,
911
- pin_number: props.pinNumber,
912
- port_hints,
913
- source_component_id: this.parent?.source_component_id
914
- });
915
- this.source_port_id = source_port.source_port_id;
916
- }
917
- doInitialSourceParentAttachment() {
918
- const { db } = this.root;
919
- if (!this.parent?.source_component_id) {
920
- throw new Error(
921
- `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`
922
- );
923
- }
924
- db.source_port.update(this.source_port_id, {
925
- source_component_id: this.parent?.source_component_id
926
- });
927
- this.source_component_id = this.parent?.source_component_id;
928
- }
929
- doInitialPcbPortRender() {
930
- const { db } = this.root;
931
- const { matchedComponents } = this;
932
- if (!this.parent?.pcb_component_id) {
933
- throw new Error(
934
- `${this.getString()} has no parent pcb component, cannot render pcb_port (parent: ${this.parent?.getString()})`
935
- );
936
- }
937
- const pcbMatches = matchedComponents.filter((c) => c.isPcbPrimitive);
938
- if (pcbMatches.length === 0) return;
939
- if (pcbMatches.length > 1) {
940
- throw new Error(
941
- `${this.getString()} has multiple pcb matches, unclear how to place pcb_port: ${pcbMatches.map((c) => c.getString()).join(", ")}`
942
- );
943
- }
944
- const pcbMatch = pcbMatches[0];
945
- if ("_getPcbCircuitJsonBounds" in pcbMatch) {
946
- const pcb_port = db.pcb_port.insert({
947
- pcb_component_id: this.parent?.pcb_component_id,
948
- layers: this.getAvailablePcbLayers(),
949
- ...pcbMatch._getPcbCircuitJsonBounds().center,
950
- source_port_id: this.source_port_id
951
- });
952
- this.pcb_port_id = pcb_port.pcb_port_id;
953
- } else {
954
- throw new Error(
955
- `${pcbMatch.getString()} does not have a _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`
956
- );
957
779
  }
780
+ return connectedPorts;
958
781
  }
959
- doInitialSchematicPortRender() {
960
- const { db } = this.root;
961
- const { _parsedProps: props } = this;
962
- const container = this.getPrimitiveContainer();
963
- if (!container) return;
964
- let center = this._getGlobalSchematicPositionBeforeLayout();
965
- if ("schematicDimensions" in container && props.pinNumber !== void 0) {
966
- const chipDims = container.schematicDimensions;
967
- center = chipDims.getPortPositionByPinNumber(props.pinNumber);
782
+ /**
783
+ * Get all traces that are directly connected to this net, i.e. they list
784
+ * this net in their path, from, or to props
785
+ */
786
+ _getAllDirectlyConnectedTraces() {
787
+ const allTraces = this.getSubcircuit().selectAll("trace");
788
+ const connectedTraces = [];
789
+ for (const trace of allTraces) {
790
+ if (trace._isExplicitlyConnectedToNet(this)) {
791
+ connectedTraces.push(trace);
792
+ }
968
793
  }
969
- const containerCenter = container._getGlobalSchematicPositionBeforeLayout();
970
- this.facingDirection = getRelativeDirection(containerCenter, center);
971
- const schematic_port = db.schematic_port.insert({
972
- schematic_component_id: this.parent?.schematic_component_id,
973
- center,
974
- source_port_id: this.source_port_id,
975
- facing_direction: this.facingDirection
976
- });
977
- this.schematic_port_id = schematic_port.schematic_port_id;
794
+ return connectedTraces;
978
795
  }
979
- _setPositionFromLayout(newCenter) {
796
+ /**
797
+ * Add PCB Traces to connect net islands together. A net island is a set of
798
+ * ports that are connected to each other. If a there are multiple net islands
799
+ * that means that the net is not fully connected and we need to add traces
800
+ * such that the nets are fully connected
801
+ */
802
+ doInitialPcbRouteNetIslands() {
980
803
  const { db } = this.root;
981
- if (!this.pcb_port_id) return;
982
- db.pcb_port.update(this.pcb_port_id, {
983
- x: newCenter.x,
984
- y: newCenter.y
985
- });
986
- }
987
- _hasMatchedPcbPrimitive() {
988
- return this.matchedComponents.some((c) => c.isPcbPrimitive);
989
- }
990
- };
991
-
992
- // lib/components/base-components/NormalComponent.ts
993
- import { symbols as symbols2 } from "schematic-symbols";
994
- import { fp } from "footprinter";
995
- import {
996
- isValidElement as isReactElement2,
997
- isValidElement
998
- } from "react";
999
-
1000
- // lib/fiber/create-instance-from-react-element.ts
1001
- import ReactReconciler from "react-reconciler";
1002
-
1003
- // lib/fiber/catalogue.ts
1004
- var catalogue = {};
1005
- var extendCatalogue = (objects) => {
1006
- const altKeys = Object.fromEntries(
1007
- Object.entries(objects).map(([key, v]) => [key.toLowerCase(), v])
1008
- );
1009
- Object.assign(catalogue, objects);
1010
- Object.assign(catalogue, altKeys);
1011
- };
1012
-
1013
- // lib/fiber/create-instance-from-react-element.ts
1014
- import { identity as identity2 } from "transformation-matrix";
1015
- function prepare(object, state) {
1016
- const instance = object;
1017
- instance.__tsci = {
1018
- ...state
1019
- };
1020
- return object;
1021
- }
1022
- var hostConfig = {
1023
- supportsMutation: true,
1024
- createInstance(type, props) {
1025
- const target = catalogue[type];
1026
- if (!target) {
1027
- if (Object.keys(catalogue).length === 0) {
1028
- throw new Error(
1029
- "No components registered in catalogue, did you forget to import lib/register-catalogue in your test file?"
1030
- );
1031
- }
1032
- throw new Error(
1033
- `Unsupported component type (not registered in @tscircuit/core catalogue): "${type}" See CREATING_NEW_COMPONENTS.md`
804
+ const { _parsedProps: props } = this;
805
+ const traces = this._getAllDirectlyConnectedTraces().filter(
806
+ (trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0
807
+ );
808
+ const islands = [];
809
+ for (const trace of traces) {
810
+ const tracePorts = trace._portsRoutedOnPcb;
811
+ const traceIsland = islands.find(
812
+ (island) => tracePorts.some((port) => island.ports.includes(port))
1034
813
  );
814
+ if (!traceIsland) {
815
+ islands.push({ ports: [...tracePorts], traces: [trace] });
816
+ continue;
817
+ }
818
+ traceIsland.traces.push(trace);
819
+ traceIsland.ports.push(...tracePorts);
1035
820
  }
1036
- const instance = prepare(new target(props), {});
1037
- return instance;
1038
- },
1039
- createTextInstance() {
1040
- return {};
1041
- },
1042
- appendInitialChild(parentInstance, child) {
1043
- parentInstance.add(child);
1044
- },
1045
- appendChild(parentInstance, child) {
1046
- parentInstance.add(child);
1047
- },
1048
- appendChildToContainer(container, child) {
1049
- container.add(child);
1050
- },
1051
- finalizeInitialChildren() {
1052
- return false;
1053
- },
1054
- prepareUpdate() {
1055
- return null;
1056
- },
1057
- shouldSetTextContent() {
1058
- return false;
1059
- },
1060
- getRootHostContext() {
1061
- return {};
1062
- },
1063
- getChildHostContext() {
1064
- return {};
1065
- },
1066
- prepareForCommit() {
1067
- return null;
1068
- },
1069
- resetAfterCommit() {
1070
- },
1071
- commitMount() {
1072
- },
1073
- commitUpdate() {
1074
- },
1075
- removeChild() {
1076
- },
1077
- clearContainer() {
1078
- },
1079
- supportsPersistence: false,
1080
- getPublicInstance(instance) {
1081
- return instance;
1082
- },
1083
- preparePortalMount(containerInfo) {
1084
- throw new Error("Function not implemented.");
1085
- },
1086
- scheduleTimeout(fn, delay) {
1087
- throw new Error("Function not implemented.");
1088
- },
1089
- cancelTimeout(id) {
1090
- throw new Error("Function not implemented.");
1091
- },
1092
- noTimeout: void 0,
1093
- isPrimaryRenderer: false,
1094
- getCurrentEventPriority() {
1095
- throw new Error("Function not implemented.");
1096
- },
1097
- getInstanceFromNode(node) {
1098
- throw new Error("Function not implemented.");
1099
- },
1100
- beforeActiveInstanceBlur() {
1101
- throw new Error("Function not implemented.");
1102
- },
1103
- afterActiveInstanceBlur() {
1104
- throw new Error("Function not implemented.");
1105
- },
1106
- prepareScopeUpdate: (scopeInstance, instance) => {
1107
- throw new Error("Function not implemented.");
1108
- },
1109
- getInstanceFromScope: (scopeInstance) => {
1110
- throw new Error("Function not implemented.");
1111
- },
1112
- detachDeletedInstance: (node) => {
1113
- throw new Error("Function not implemented.");
1114
- },
1115
- supportsHydration: false
1116
- };
1117
- var reconciler = ReactReconciler(hostConfig);
1118
- var createInstanceFromReactElement = (reactElm) => {
1119
- const rootContainer = {
1120
- children: [],
1121
- props: {
1122
- name: "$root"
1123
- },
1124
- add(instance) {
1125
- instance.parent = this;
1126
- this.children.push(instance);
1127
- },
1128
- computePcbGlobalTransform() {
1129
- return identity2();
821
+ if (islands.length === 0) {
822
+ return;
1130
823
  }
1131
- };
1132
- const container = reconciler.createContainer(
1133
- // TODO Replace with store like react-three-fiber
1134
- // https://github.com/pmndrs/react-three-fiber/blob/a457290856f57741bf8beef4f6ff9dbf4879c0a5/packages/fiber/src/core/index.tsx#L172
1135
- // https://github.com/pmndrs/react-three-fiber/blob/master/packages/fiber/src/core/store.ts#L168
1136
- rootContainer,
1137
- 0,
1138
- null,
1139
- false,
1140
- null,
1141
- "tsci",
1142
- (error) => {
1143
- console.log("Error in createContainer");
1144
- console.error(error);
1145
- },
1146
- null
1147
- );
1148
- reconciler.updateContainer(reactElm, container, null, () => {
1149
- });
1150
- const rootInstance = reconciler.getPublicRootInstance(
1151
- container
1152
- );
1153
- if (rootInstance) return rootInstance;
1154
- return rootContainer.children[0];
824
+ const islandPairs = pairs(islands);
825
+ for (const [A, B] of islandPairs) {
826
+ const Apositions = A.ports.map(
827
+ (port) => port._getGlobalPcbPositionBeforeLayout()
828
+ );
829
+ const Bpositions = B.ports.map(
830
+ (port) => port._getGlobalPcbPositionBeforeLayout()
831
+ );
832
+ let closestDist = Infinity;
833
+ let closestPair = [-1, -1];
834
+ for (let i = 0; i < Apositions.length; i++) {
835
+ const Apos = Apositions[i];
836
+ for (let j = 0; j < Bpositions.length; j++) {
837
+ const Bpos = Bpositions[j];
838
+ const dist = Math.sqrt(
839
+ (Apos.x - Bpos.x) ** 2 + (Apos.y - Bpos.y) ** 2
840
+ );
841
+ if (dist < closestDist) {
842
+ closestDist = dist;
843
+ closestPair = [i, j];
844
+ }
845
+ }
846
+ }
847
+ const Aport = A.ports[closestPair[0]];
848
+ const Bport = B.ports[closestPair[1]];
849
+ const pcbElements = db.toArray().filter(
850
+ (elm) => elm.type === "pcb_smtpad" || elm.type === "pcb_trace" || elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || elm.type === "pcb_port"
851
+ );
852
+ const { solution } = autoroute(
853
+ pcbElements.concat([
854
+ {
855
+ type: "source_trace",
856
+ source_trace_id: "__net_trace_tmp",
857
+ connected_source_port_ids: [
858
+ Aport.source_port_id,
859
+ Bport.source_port_id
860
+ ]
861
+ }
862
+ ])
863
+ // Remove as any when autorouting-dataset has been updated
864
+ );
865
+ const trace = solution[0];
866
+ if (!trace) {
867
+ this.renderError("Failed to route net islands");
868
+ return;
869
+ }
870
+ db.pcb_trace.insert(trace);
871
+ }
872
+ }
1155
873
  };
1156
874
 
1157
- // lib/utils/getPortFromHints.ts
1158
- function getPortFromHints(hints) {
1159
- const pinNumber = hints.find((p) => /^(pin)?\d+$/.test(p));
1160
- if (!pinNumber) return null;
1161
- return new Port({
1162
- pinNumber: Number.parseInt(pinNumber.replace(/^pin/, "")),
1163
- aliases: hints.filter((p) => p !== pinNumber)
1164
- });
1165
- }
875
+ // lib/utils/components/createNetsFromProps.ts
876
+ var createNetsFromProps = (component, props) => {
877
+ for (const prop of props) {
878
+ if (typeof prop === "string" && prop.startsWith("net.")) {
879
+ if (!component.getSubcircuit().selectOne(prop)) {
880
+ component.getSubcircuit().add(new Net({ name: prop.split("net.")[1] }));
881
+ }
882
+ }
883
+ }
884
+ };
1166
885
 
1167
886
  // lib/components/primitive-components/SmtPad.ts
1168
887
  import { smtPadProps } from "@tscircuit/props";
@@ -1233,6 +952,16 @@ var SmtPad = class extends PrimitiveComponent {
1233
952
  x: position.x,
1234
953
  y: position.y
1235
954
  });
955
+ db.pcb_solder_paste.insert({
956
+ layer: pcb_smtpad.layer,
957
+ shape: "circle",
958
+ // @ts-ignore: no idea why this is triggering
959
+ radius: pcb_smtpad.radius * 0.7,
960
+ x: pcb_smtpad.x,
961
+ y: pcb_smtpad.y,
962
+ pcb_component_id: pcb_smtpad.pcb_component_id,
963
+ pcb_smtpad_id: pcb_smtpad.pcb_smtpad_id
964
+ });
1236
965
  } else if (props.shape === "rect") {
1237
966
  pcb_smtpad = db.pcb_smtpad.insert({
1238
967
  pcb_component_id,
@@ -1245,6 +974,18 @@ var SmtPad = class extends PrimitiveComponent {
1245
974
  x: position.x,
1246
975
  y: position.y
1247
976
  });
977
+ if (pcb_smtpad.shape === "rect")
978
+ db.pcb_solder_paste.insert({
979
+ layer: pcb_smtpad.layer,
980
+ shape: "rect",
981
+ // @ts-ignore: no idea why this is triggering
982
+ width: pcb_smtpad.width * 0.7,
983
+ height: pcb_smtpad.height * 0.7,
984
+ x: pcb_smtpad.x,
985
+ y: pcb_smtpad.y,
986
+ pcb_component_id: pcb_smtpad.pcb_component_id,
987
+ pcb_smtpad_id: pcb_smtpad.pcb_smtpad_id
988
+ });
1248
989
  }
1249
990
  if (pcb_smtpad) {
1250
991
  this.pcb_smtpad_id = pcb_smtpad.pcb_smtpad_id;
@@ -1301,7 +1042,7 @@ var SmtPad = class extends PrimitiveComponent {
1301
1042
 
1302
1043
  // lib/components/primitive-components/SilkscreenPath.ts
1303
1044
  import { silkscreenPathProps } from "@tscircuit/props";
1304
- import { applyToPoint as applyToPoint4 } from "transformation-matrix";
1045
+ import { applyToPoint as applyToPoint3 } from "transformation-matrix";
1305
1046
  var SilkscreenPath = class extends PrimitiveComponent {
1306
1047
  pcb_silkscreen_path_id = null;
1307
1048
  get config() {
@@ -1324,7 +1065,7 @@ var SilkscreenPath = class extends PrimitiveComponent {
1324
1065
  pcb_component_id: this.parent?.pcb_component_id,
1325
1066
  layer,
1326
1067
  route: props.route.map((p) => {
1327
- const transformedPosition = applyToPoint4(transform, {
1068
+ const transformedPosition = applyToPoint3(transform, {
1328
1069
  x: p.x,
1329
1070
  y: p.y
1330
1071
  });
@@ -1512,250 +1253,527 @@ var createComponentsFromSoup = (soup) => {
1512
1253
  portHints: elm.port_hints
1513
1254
  })
1514
1255
  );
1515
- } else if (elm.type === "pcb_silkscreen_path") {
1516
- components.push(
1517
- new SilkscreenPath({
1518
- layer: elm.layer,
1519
- route: elm.route,
1520
- strokeWidth: elm.stroke_width
1521
- })
1256
+ } else if (elm.type === "pcb_silkscreen_path") {
1257
+ components.push(
1258
+ new SilkscreenPath({
1259
+ layer: elm.layer,
1260
+ route: elm.route,
1261
+ strokeWidth: elm.stroke_width
1262
+ })
1263
+ );
1264
+ } else if (elm.type === "pcb_plated_hole" && elm.shape === "circle") {
1265
+ if (elm.shape === "circle") {
1266
+ components.push(
1267
+ new PlatedHole({
1268
+ pcbX: elm.x,
1269
+ pcbY: elm.y,
1270
+ shape: "circle",
1271
+ holeDiameter: elm.hole_diameter,
1272
+ outerDiameter: elm.outer_diameter,
1273
+ portHints: elm.port_hints
1274
+ })
1275
+ );
1276
+ }
1277
+ } else if (elm.type === "pcb_keepout" && elm.shape === "circle") {
1278
+ components.push(
1279
+ new Keepout({
1280
+ pcbX: elm.center.x,
1281
+ pcbY: elm.center.y,
1282
+ shape: "circle",
1283
+ radius: elm.radius
1284
+ })
1285
+ );
1286
+ } else if (elm.type === "pcb_keepout" && elm.shape === "rect") {
1287
+ components.push(
1288
+ new Keepout({
1289
+ pcbX: elm.center.x,
1290
+ pcbY: elm.center.y,
1291
+ shape: "rect",
1292
+ width: elm.width,
1293
+ height: elm.height
1294
+ })
1295
+ );
1296
+ }
1297
+ }
1298
+ return components;
1299
+ };
1300
+
1301
+ // lib/utils/get-bounds-of-pcb-components.ts
1302
+ function getBoundsOfPcbComponents(components) {
1303
+ let minX = Infinity;
1304
+ let minY = Infinity;
1305
+ let maxX = -Infinity;
1306
+ let maxY = -Infinity;
1307
+ for (const child of components) {
1308
+ if (child.isPcbPrimitive) {
1309
+ const { x, y } = child._getGlobalPcbPositionBeforeLayout();
1310
+ const { width: width2, height: height2 } = child.getPcbSize();
1311
+ minX = Math.min(minX, x - width2 / 2);
1312
+ minY = Math.min(minY, y - height2 / 2);
1313
+ maxX = Math.max(maxX, x + width2 / 2);
1314
+ maxY = Math.max(maxY, y + height2 / 2);
1315
+ } else if (child.componentName === "Footprint") {
1316
+ const childBounds = getBoundsOfPcbComponents(child.children);
1317
+ minX = Math.min(minX, childBounds.minX);
1318
+ minY = Math.min(minY, childBounds.minY);
1319
+ maxX = Math.max(maxX, childBounds.maxX);
1320
+ maxY = Math.max(maxY, childBounds.maxY);
1321
+ }
1322
+ }
1323
+ let width = maxX - minX;
1324
+ let height = maxY - minY;
1325
+ if (width < 0) width = 0;
1326
+ if (height < 0) height = 0;
1327
+ return {
1328
+ minX,
1329
+ minY,
1330
+ maxX,
1331
+ maxY,
1332
+ width,
1333
+ height
1334
+ };
1335
+ }
1336
+
1337
+ // lib/utils/get-relative-direction.ts
1338
+ function getRelativeDirection(pointA, pointB) {
1339
+ const dx = pointB.x - pointA.x;
1340
+ const dy = pointB.y - pointA.y;
1341
+ if (Math.abs(dx) > Math.abs(dy)) {
1342
+ return dx > 0 ? "right" : "left";
1343
+ }
1344
+ return dy > 0 ? "down" : "up";
1345
+ }
1346
+
1347
+ // lib/components/primitive-components/Port.ts
1348
+ import "schematic-symbols";
1349
+ import { applyToPoint as applyToPoint4, compose as compose3, translate as translate3 } from "transformation-matrix";
1350
+ import { z as z3 } from "zod";
1351
+ var portProps = z3.object({
1352
+ name: z3.string().optional(),
1353
+ pinNumber: z3.number().optional(),
1354
+ aliases: z3.array(z3.string()).optional()
1355
+ });
1356
+ var Port = class extends PrimitiveComponent {
1357
+ source_port_id = null;
1358
+ pcb_port_id = null;
1359
+ schematic_port_id = null;
1360
+ schematicSymbolPortDef = null;
1361
+ matchedComponents;
1362
+ facingDirection = null;
1363
+ get config() {
1364
+ return {
1365
+ componentName: "Port",
1366
+ zodProps: portProps
1367
+ };
1368
+ }
1369
+ constructor(props) {
1370
+ if (!props.name && props.pinNumber) props.name = `pin${props.pinNumber}`;
1371
+ if (!props.name) {
1372
+ throw new Error("Port must have a name or a pinNumber");
1373
+ }
1374
+ super(props);
1375
+ this.matchedComponents = [];
1376
+ }
1377
+ _getGlobalPcbPositionBeforeLayout() {
1378
+ const matchedPcbElm = this.matchedComponents.find((c) => c.isPcbPrimitive);
1379
+ if (!matchedPcbElm) {
1380
+ throw new Error(
1381
+ `Port ${this} has no matched pcb component, can't get global schematic position`
1382
+ );
1383
+ }
1384
+ return matchedPcbElm?._getGlobalPcbPositionBeforeLayout() ?? { x: 0, y: 0 };
1385
+ }
1386
+ _getPcbCircuitJsonBounds() {
1387
+ if (!this.pcb_port_id) {
1388
+ return super._getPcbCircuitJsonBounds();
1389
+ }
1390
+ const { db } = this.root;
1391
+ const pcb_port = db.pcb_port.get(this.pcb_port_id);
1392
+ return {
1393
+ center: { x: pcb_port.x, y: pcb_port.y },
1394
+ bounds: { left: 0, top: 0, right: 0, bottom: 0 },
1395
+ width: 0,
1396
+ height: 0
1397
+ };
1398
+ }
1399
+ _getGlobalPcbPositionAfterLayout() {
1400
+ return this._getPcbCircuitJsonBounds().center;
1401
+ }
1402
+ _getGlobalSchematicPositionBeforeLayout() {
1403
+ if (!this.schematicSymbolPortDef) {
1404
+ return applyToPoint4(this.parent.computeSchematicGlobalTransform(), {
1405
+ x: 0,
1406
+ y: 0
1407
+ });
1408
+ }
1409
+ const symbol = this.parent?.getSchematicSymbol();
1410
+ if (!symbol) {
1411
+ console.warn(`Could not find parent symbol for ${this}`);
1412
+ return { x: 0, y: 0 };
1413
+ }
1414
+ const transform = compose3(
1415
+ this.parent.computeSchematicGlobalTransform(),
1416
+ translate3(-symbol.center.x, -symbol.center.y)
1417
+ );
1418
+ return applyToPoint4(transform, this.schematicSymbolPortDef);
1419
+ }
1420
+ /**
1421
+ * Smtpads and platedholes call this method to register themselves as a match
1422
+ * for this port. All the matching is done by primitives other than the Port,
1423
+ * but everyone registers themselves as a match with their Port.
1424
+ */
1425
+ registerMatch(component) {
1426
+ this.matchedComponents.push(component);
1427
+ }
1428
+ getNameAndAliases() {
1429
+ const { _parsedProps: props } = this;
1430
+ return Array.from(
1431
+ /* @__PURE__ */ new Set([
1432
+ ...props.aliases ?? [],
1433
+ props.name,
1434
+ ...typeof props.pinNumber === "number" ? [`pin${props.pinNumber}`, props.pinNumber.toString()] : [],
1435
+ ...this.externallyAddedAliases
1436
+ ])
1437
+ );
1438
+ }
1439
+ isMatchingPort(port) {
1440
+ return this.isMatchingAnyOf(port.getNameAndAliases());
1441
+ }
1442
+ getPortSelector() {
1443
+ return `.${this.parent?.props.name} > port.${this.props.name}`;
1444
+ }
1445
+ getAvailablePcbLayers() {
1446
+ return Array.from(
1447
+ new Set(this.matchedComponents.flatMap((c) => c.getAvailablePcbLayers()))
1448
+ );
1449
+ }
1450
+ /**
1451
+ * Return traces that are explicitly connected to this port (not via a net)
1452
+ */
1453
+ _getDirectlyConnectedTraces() {
1454
+ const allSubcircuitTraces = this.getSubcircuit().selectAll(
1455
+ "trace"
1456
+ );
1457
+ const connectedTraces = allSubcircuitTraces.filter(
1458
+ (trace) => trace._isExplicitlyConnectedToPort(this)
1459
+ );
1460
+ return connectedTraces;
1461
+ }
1462
+ doInitialSourceRender() {
1463
+ const { db } = this.root;
1464
+ const { _parsedProps: props } = this;
1465
+ const port_hints = this.getNameAndAliases();
1466
+ const source_port = db.source_port.insert({
1467
+ name: props.name,
1468
+ pin_number: props.pinNumber,
1469
+ port_hints,
1470
+ source_component_id: this.parent?.source_component_id
1471
+ });
1472
+ this.source_port_id = source_port.source_port_id;
1473
+ }
1474
+ doInitialSourceParentAttachment() {
1475
+ const { db } = this.root;
1476
+ if (!this.parent?.source_component_id) {
1477
+ throw new Error(
1478
+ `${this.getString()} has no parent source component (parent: ${this.parent?.getString()})`
1479
+ );
1480
+ }
1481
+ db.source_port.update(this.source_port_id, {
1482
+ source_component_id: this.parent?.source_component_id
1483
+ });
1484
+ this.source_component_id = this.parent?.source_component_id;
1485
+ }
1486
+ doInitialPcbPortRender() {
1487
+ const { db } = this.root;
1488
+ const { matchedComponents } = this;
1489
+ if (!this.parent?.pcb_component_id) {
1490
+ throw new Error(
1491
+ `${this.getString()} has no parent pcb component, cannot render pcb_port (parent: ${this.parent?.getString()})`
1522
1492
  );
1523
- } else if (elm.type === "pcb_plated_hole" && elm.shape === "circle") {
1524
- if (elm.shape === "circle") {
1525
- components.push(
1526
- new PlatedHole({
1527
- pcbX: elm.x,
1528
- pcbY: elm.y,
1529
- shape: "circle",
1530
- holeDiameter: elm.hole_diameter,
1531
- outerDiameter: elm.outer_diameter,
1532
- portHints: elm.port_hints
1533
- })
1534
- );
1535
- }
1536
- } else if (elm.type === "pcb_keepout" && elm.shape === "circle") {
1537
- components.push(
1538
- new Keepout({
1539
- pcbX: elm.center.x,
1540
- pcbY: elm.center.y,
1541
- shape: "circle",
1542
- radius: elm.radius
1543
- })
1493
+ }
1494
+ const pcbMatches = matchedComponents.filter((c) => c.isPcbPrimitive);
1495
+ if (pcbMatches.length === 0) return;
1496
+ if (pcbMatches.length > 1) {
1497
+ throw new Error(
1498
+ `${this.getString()} has multiple pcb matches, unclear how to place pcb_port: ${pcbMatches.map((c) => c.getString()).join(", ")}`
1544
1499
  );
1545
- } else if (elm.type === "pcb_keepout" && elm.shape === "rect") {
1546
- components.push(
1547
- new Keepout({
1548
- pcbX: elm.center.x,
1549
- pcbY: elm.center.y,
1550
- shape: "rect",
1551
- width: elm.width,
1552
- height: elm.height
1553
- })
1500
+ }
1501
+ const pcbMatch = pcbMatches[0];
1502
+ if ("_getPcbCircuitJsonBounds" in pcbMatch) {
1503
+ const pcb_port = db.pcb_port.insert({
1504
+ pcb_component_id: this.parent?.pcb_component_id,
1505
+ layers: this.getAvailablePcbLayers(),
1506
+ ...pcbMatch._getPcbCircuitJsonBounds().center,
1507
+ source_port_id: this.source_port_id
1508
+ });
1509
+ this.pcb_port_id = pcb_port.pcb_port_id;
1510
+ } else {
1511
+ throw new Error(
1512
+ `${pcbMatch.getString()} does not have a _getGlobalPcbPositionBeforeLayout method (needed for pcb_port placement)`
1554
1513
  );
1555
1514
  }
1556
1515
  }
1557
- return components;
1516
+ doInitialSchematicPortRender() {
1517
+ const { db } = this.root;
1518
+ const { _parsedProps: props } = this;
1519
+ const container = this.getPrimitiveContainer();
1520
+ if (!container) return;
1521
+ let center = this._getGlobalSchematicPositionBeforeLayout();
1522
+ if ("schematicDimensions" in container && props.pinNumber !== void 0) {
1523
+ const chipDims = container.schematicDimensions;
1524
+ center = chipDims.getPortPositionByPinNumber(props.pinNumber);
1525
+ }
1526
+ const containerCenter = container._getGlobalSchematicPositionBeforeLayout();
1527
+ this.facingDirection = getRelativeDirection(containerCenter, center);
1528
+ const schematic_port = db.schematic_port.insert({
1529
+ schematic_component_id: this.parent?.schematic_component_id,
1530
+ center,
1531
+ source_port_id: this.source_port_id,
1532
+ facing_direction: this.facingDirection
1533
+ });
1534
+ this.schematic_port_id = schematic_port.schematic_port_id;
1535
+ }
1536
+ _setPositionFromLayout(newCenter) {
1537
+ const { db } = this.root;
1538
+ if (!this.pcb_port_id) return;
1539
+ db.pcb_port.update(this.pcb_port_id, {
1540
+ x: newCenter.x,
1541
+ y: newCenter.y
1542
+ });
1543
+ }
1544
+ _hasMatchedPcbPrimitive() {
1545
+ return this.matchedComponents.some((c) => c.isPcbPrimitive);
1546
+ }
1558
1547
  };
1559
1548
 
1560
- // lib/components/primitive-components/Net.ts
1561
- import { z as z3 } from "zod";
1562
-
1563
- // lib/utils/pairs.ts
1564
- function pairs(arr) {
1565
- const result = [];
1566
- for (let i = 0; i < arr.length - 1; i++) {
1567
- result.push([arr[i], arr[i + 1]]);
1568
- }
1569
- return result;
1549
+ // lib/utils/getPortFromHints.ts
1550
+ function getPortFromHints(hints) {
1551
+ const pinNumber = hints.find((p) => /^(pin)?\d+$/.test(p));
1552
+ if (!pinNumber) return null;
1553
+ return new Port({
1554
+ pinNumber: Number.parseInt(pinNumber.replace(/^pin/, "")),
1555
+ aliases: hints.filter((p) => p !== pinNumber)
1556
+ });
1570
1557
  }
1571
1558
 
1572
- // lib/components/primitive-components/Net.ts
1573
- import { autoroute } from "@tscircuit/infgrid-ijump-astar";
1574
- var netProps = z3.object({
1575
- name: z3.string()
1576
- });
1577
- var Net = class extends PrimitiveComponent {
1578
- source_net_id;
1559
+ // lib/components/base-components/NormalComponent.ts
1560
+ import {
1561
+ isValidElement as isReactElement2,
1562
+ isValidElement
1563
+ } from "react";
1564
+ import { symbols as symbols2 } from "schematic-symbols";
1565
+ import { z as z4 } from "zod";
1566
+
1567
+ // lib/components/primitive-components/Footprint.ts
1568
+ import { footprintProps } from "@tscircuit/props";
1569
+ import * as kiwi from "@lume/kiwi";
1570
+ import Debug from "debug";
1571
+ var debug = Debug("tscircuit:core:footprint");
1572
+ var Footprint = class extends PrimitiveComponent {
1579
1573
  get config() {
1580
1574
  return {
1581
- componentName: "Net",
1582
- zodProps: netProps
1575
+ componentName: "Footprint",
1576
+ zodProps: footprintProps
1583
1577
  };
1584
1578
  }
1585
- getPortSelector() {
1586
- return `net.${this.props.name}`;
1587
- }
1588
- doInitialSourceRender() {
1589
- const { db } = this.root;
1590
- const { _parsedProps: props } = this;
1591
- const net = db.source_net.insert({
1592
- name: props.name,
1593
- member_source_group_ids: []
1594
- });
1595
- this.source_net_id = net.source_net_id;
1596
- }
1597
1579
  /**
1598
- * Get all ports connected to this net.
1599
- *
1600
- * TODO currently we're not checking for indirect connections (traces that are
1601
- * connected to other traces that are in turn connected to the net)
1580
+ * A footprint is a constrainedlayout, the db elements are adjusted according
1581
+ * to any constraints that are defined.
1602
1582
  */
1603
- getAllConnectedPorts() {
1604
- const allPorts = this.getSubcircuit().selectAll("port");
1605
- const connectedPorts = [];
1606
- for (const port of allPorts) {
1607
- const traces = port._getDirectlyConnectedTraces();
1608
- for (const trace of traces) {
1609
- if (trace._isExplicitlyConnectedToNet(this)) {
1610
- connectedPorts.push(port);
1611
- break;
1583
+ doInitialPcbFootprintLayout() {
1584
+ const constraints = this.children.filter(
1585
+ (child) => child.componentName === "Constraint"
1586
+ );
1587
+ if (constraints.length === 0) return;
1588
+ const { isFlipped } = this._getPcbPrimitiveFlippedHelpers();
1589
+ const maybeFlipLeftRight = (props) => {
1590
+ if (isFlipped) {
1591
+ if ("left" in props && "right" in props) {
1592
+ return {
1593
+ ...props,
1594
+ left: props.right,
1595
+ right: props.left
1596
+ };
1612
1597
  }
1613
1598
  }
1599
+ return props;
1600
+ };
1601
+ const involvedComponents = constraints.flatMap(
1602
+ (constraint) => constraint._getAllReferencedComponents().componentsWithSelectors
1603
+ ).map(({ component, selector, componentSelector, edge }) => ({
1604
+ component,
1605
+ selector,
1606
+ componentSelector,
1607
+ edge,
1608
+ bounds: component._getPcbCircuitJsonBounds()
1609
+ }));
1610
+ if (involvedComponents.some((c) => c.edge)) {
1611
+ throw new Error(
1612
+ "edge constraints not implemented yet for footprint layout, contributions welcome!"
1613
+ );
1614
1614
  }
1615
- return connectedPorts;
1616
- }
1617
- /**
1618
- * Get all traces that are directly connected to this net, i.e. they list
1619
- * this net in their path, from, or to props
1620
- */
1621
- _getAllDirectlyConnectedTraces() {
1622
- const allTraces = this.getSubcircuit().selectAll("trace");
1623
- const connectedTraces = [];
1624
- for (const trace of allTraces) {
1625
- if (trace._isExplicitlyConnectedToNet(this)) {
1626
- connectedTraces.push(trace);
1627
- }
1615
+ function getComponentDetails(selector) {
1616
+ return involvedComponents.find(({ selector: s }) => s === selector);
1628
1617
  }
1629
- return connectedTraces;
1630
- }
1631
- /**
1632
- * Add PCB Traces to connect net islands together. A net island is a set of
1633
- * ports that are connected to each other. If a there are multiple net islands
1634
- * that means that the net is not fully connected and we need to add traces
1635
- * such that the nets are fully connected
1636
- */
1637
- doInitialPcbRouteNetIslands() {
1638
- const { db } = this.root;
1639
- const { _parsedProps: props } = this;
1640
- const traces = this._getAllDirectlyConnectedTraces().filter(
1641
- (trace) => (trace._portsRoutedOnPcb?.length ?? 0) > 0
1642
- );
1643
- const islands = [];
1644
- for (const trace of traces) {
1645
- const tracePorts = trace._portsRoutedOnPcb;
1646
- const traceIsland = islands.find(
1647
- (island) => tracePorts.some((port) => island.ports.includes(port))
1648
- );
1649
- if (!traceIsland) {
1650
- islands.push({ ports: [...tracePorts], traces: [trace] });
1651
- continue;
1618
+ const solver = new kiwi.Solver();
1619
+ const kVars = {};
1620
+ function getKVar(name) {
1621
+ if (!(name in kVars)) {
1622
+ kVars[name] = new kiwi.Variable(name);
1623
+ solver.addEditVariable(kVars[name], kiwi.Strength.weak);
1652
1624
  }
1653
- traceIsland.traces.push(trace);
1654
- traceIsland.ports.push(...tracePorts);
1625
+ return kVars[name];
1655
1626
  }
1656
- if (islands.length === 0) {
1657
- return;
1627
+ for (const { selector, bounds: bounds2 } of involvedComponents) {
1628
+ const kvx = getKVar(`${selector}_x`);
1629
+ const kvy = getKVar(`${selector}_y`);
1630
+ solver.suggestValue(kvx, bounds2.center.x);
1631
+ solver.suggestValue(kvy, bounds2.center.y);
1658
1632
  }
1659
- const islandPairs = pairs(islands);
1660
- for (const [A, B] of islandPairs) {
1661
- const Apositions = A.ports.map(
1662
- (port) => port._getGlobalPcbPositionBeforeLayout()
1663
- );
1664
- const Bpositions = B.ports.map(
1665
- (port) => port._getGlobalPcbPositionBeforeLayout()
1666
- );
1667
- let closestDist = Infinity;
1668
- let closestPair = [-1, -1];
1669
- for (let i = 0; i < Apositions.length; i++) {
1670
- const Apos = Apositions[i];
1671
- for (let j = 0; j < Bpositions.length; j++) {
1672
- const Bpos = Bpositions[j];
1673
- const dist = Math.sqrt(
1674
- (Apos.x - Bpos.x) ** 2 + (Apos.y - Bpos.y) ** 2
1633
+ for (const constraint of constraints) {
1634
+ const props = constraint._parsedProps;
1635
+ if ("xDist" in props) {
1636
+ const { xDist, left, right, edgeToEdge, centerToCenter } = maybeFlipLeftRight(props);
1637
+ const leftVar = getKVar(`${left}_x`);
1638
+ const rightVar = getKVar(`${right}_x`);
1639
+ const leftBounds = getComponentDetails(left)?.bounds;
1640
+ const rightBounds = getComponentDetails(right)?.bounds;
1641
+ if (centerToCenter) {
1642
+ const expr = new kiwi.Expression(rightVar, [-1, leftVar]);
1643
+ solver.addConstraint(
1644
+ new kiwi.Constraint(
1645
+ expr,
1646
+ kiwi.Operator.Eq,
1647
+ props.xDist,
1648
+ kiwi.Strength.required
1649
+ )
1650
+ );
1651
+ } else if (edgeToEdge) {
1652
+ const expr = new kiwi.Expression(
1653
+ rightVar,
1654
+ -rightBounds.width / 2,
1655
+ [-1, leftVar],
1656
+ -leftBounds.width / 2
1657
+ );
1658
+ solver.addConstraint(
1659
+ new kiwi.Constraint(
1660
+ expr,
1661
+ kiwi.Operator.Eq,
1662
+ props.xDist,
1663
+ kiwi.Strength.required
1664
+ )
1665
+ );
1666
+ }
1667
+ } else if ("yDist" in props) {
1668
+ const { yDist, top, bottom, edgeToEdge, centerToCenter } = props;
1669
+ const topVar = getKVar(`${top}_y`);
1670
+ const bottomVar = getKVar(`${bottom}_y`);
1671
+ const topBounds = getComponentDetails(top)?.bounds;
1672
+ const bottomBounds = getComponentDetails(bottom)?.bounds;
1673
+ if (centerToCenter) {
1674
+ const expr = new kiwi.Expression(topVar, [-1, bottomVar]);
1675
+ solver.addConstraint(
1676
+ new kiwi.Constraint(
1677
+ expr,
1678
+ kiwi.Operator.Eq,
1679
+ props.yDist,
1680
+ kiwi.Strength.required
1681
+ )
1682
+ );
1683
+ } else if (edgeToEdge) {
1684
+ const expr = new kiwi.Expression(
1685
+ topVar,
1686
+ topBounds.height / 2,
1687
+ [-1, bottomVar],
1688
+ -bottomBounds.height / 2
1689
+ );
1690
+ solver.addConstraint(
1691
+ new kiwi.Constraint(
1692
+ expr,
1693
+ kiwi.Operator.Eq,
1694
+ props.yDist,
1695
+ kiwi.Strength.required
1696
+ )
1675
1697
  );
1676
- if (dist < closestDist) {
1677
- closestDist = dist;
1678
- closestPair = [i, j];
1679
- }
1680
1698
  }
1699
+ } else if ("sameY" in props) {
1700
+ const { for: selectors } = props;
1701
+ if (selectors.length < 2) continue;
1702
+ const vars = selectors.map((selector) => getKVar(`${selector}_y`));
1703
+ const expr = new kiwi.Expression(...vars.slice(1));
1704
+ solver.addConstraint(
1705
+ new kiwi.Constraint(
1706
+ expr,
1707
+ kiwi.Operator.Eq,
1708
+ vars[0],
1709
+ kiwi.Strength.required
1710
+ )
1711
+ );
1712
+ } else if ("sameX" in props) {
1713
+ const { for: selectors } = props;
1714
+ if (selectors.length < 2) continue;
1715
+ const vars = selectors.map((selector) => getKVar(`${selector}_x`));
1716
+ const expr = new kiwi.Expression(...vars.slice(1));
1717
+ solver.addConstraint(
1718
+ new kiwi.Constraint(
1719
+ expr,
1720
+ kiwi.Operator.Eq,
1721
+ vars[0],
1722
+ kiwi.Strength.required
1723
+ )
1724
+ );
1681
1725
  }
1682
- const Aport = A.ports[closestPair[0]];
1683
- const Bport = B.ports[closestPair[1]];
1684
- const pcbElements = db.toArray().filter(
1685
- (elm) => elm.type === "pcb_smtpad" || elm.type === "pcb_trace" || elm.type === "pcb_plated_hole" || elm.type === "pcb_hole" || elm.type === "source_port" || elm.type === "pcb_port"
1686
- );
1687
- const { solution } = autoroute(
1688
- pcbElements.concat([
1689
- {
1690
- type: "source_trace",
1691
- source_trace_id: "__net_trace_tmp",
1692
- connected_source_port_ids: [
1693
- Aport.source_port_id,
1694
- Bport.source_port_id
1695
- ]
1696
- }
1697
- ])
1698
- // Remove as any when autorouting-dataset has been updated
1726
+ }
1727
+ solver.updateVariables();
1728
+ if (debug.enabled) {
1729
+ console.log("Solution to layout constraints:");
1730
+ console.table(
1731
+ Object.entries(kVars).map(([key, kvar]) => ({
1732
+ var: key,
1733
+ val: kvar.value()
1734
+ }))
1699
1735
  );
1700
- const trace = solution[0];
1701
- if (!trace) {
1702
- this.renderError("Failed to route net islands");
1703
- return;
1704
- }
1705
- db.pcb_trace.insert(trace);
1706
1736
  }
1707
- }
1708
- };
1709
-
1710
- // lib/utils/components/createNetsFromProps.ts
1711
- var createNetsFromProps = (component, props) => {
1712
- for (const prop of props) {
1713
- if (typeof prop === "string" && prop.startsWith("net.")) {
1714
- if (!component.getSubcircuit().selectOne(prop)) {
1715
- component.getSubcircuit().add(new Net({ name: prop.split("net.")[1] }));
1716
- }
1737
+ const bounds = {
1738
+ left: Infinity,
1739
+ right: -Infinity,
1740
+ top: -Infinity,
1741
+ bottom: Infinity
1742
+ };
1743
+ for (const {
1744
+ selector,
1745
+ bounds: { width, height }
1746
+ } of involvedComponents) {
1747
+ const kvx = getKVar(`${selector}_x`);
1748
+ const kvy = getKVar(`${selector}_y`);
1749
+ const newLeft = kvx.value() - width / 2;
1750
+ const newRight = kvx.value() + width / 2;
1751
+ const newTop = kvy.value() + height / 2;
1752
+ const newBottom = kvy.value() - height / 2;
1753
+ bounds.left = Math.min(bounds.left, newLeft);
1754
+ bounds.right = Math.max(bounds.right, newRight);
1755
+ bounds.top = Math.max(bounds.top, newTop);
1756
+ bounds.bottom = Math.min(bounds.bottom, newBottom);
1717
1757
  }
1718
- }
1719
- };
1720
-
1721
- // lib/utils/get-bounds-of-pcb-components.ts
1722
- function getBoundsOfPcbComponents(components) {
1723
- let minX = Infinity;
1724
- let minY = Infinity;
1725
- let maxX = -Infinity;
1726
- let maxY = -Infinity;
1727
- for (const child of components) {
1728
- if (child.isPcbPrimitive) {
1729
- const { x, y } = child._getGlobalPcbPositionBeforeLayout();
1730
- const { width: width2, height: height2 } = child.getPcbSize();
1731
- minX = Math.min(minX, x - width2 / 2);
1732
- minY = Math.min(minY, y - height2 / 2);
1733
- maxX = Math.max(maxX, x + width2 / 2);
1734
- maxY = Math.max(maxY, y + height2 / 2);
1735
- } else if (child.componentName === "Footprint") {
1736
- const childBounds = getBoundsOfPcbComponents(child.children);
1737
- minX = Math.min(minX, childBounds.minX);
1738
- minY = Math.min(minY, childBounds.minY);
1739
- maxX = Math.max(maxX, childBounds.maxX);
1740
- maxY = Math.max(maxY, childBounds.maxY);
1758
+ const globalOffset = {
1759
+ x: -(bounds.right + bounds.left) / 2,
1760
+ y: -(bounds.top + bounds.bottom) / 2
1761
+ };
1762
+ const containerPos = this.getPrimitiveContainer()._getGlobalPcbPositionBeforeLayout();
1763
+ globalOffset.x += containerPos.x;
1764
+ globalOffset.y += containerPos.y;
1765
+ for (const { component, selector } of involvedComponents) {
1766
+ const kvx = getKVar(`${selector}_x`);
1767
+ const kvy = getKVar(`${selector}_y`);
1768
+ component._setPositionFromLayout({
1769
+ x: kvx.value() + globalOffset.x,
1770
+ y: kvy.value() + globalOffset.y
1771
+ });
1741
1772
  }
1742
1773
  }
1743
- let width = maxX - minX;
1744
- let height = maxY - minY;
1745
- if (width < 0) width = 0;
1746
- if (height < 0) height = 0;
1747
- return {
1748
- minX,
1749
- minY,
1750
- maxX,
1751
- maxY,
1752
- width,
1753
- height
1754
- };
1755
- }
1774
+ };
1756
1775
 
1757
1776
  // lib/components/base-components/NormalComponent.ts
1758
- import { rotation } from "circuit-json";
1759
1777
  var rotation3 = z4.object({
1760
1778
  x: rotation,
1761
1779
  y: rotation,
@@ -1786,6 +1804,36 @@ var NormalComponent = class extends PrimitiveComponent {
1786
1804
  initPorts() {
1787
1805
  const { config } = this;
1788
1806
  const portsToCreate = [];
1807
+ const schPortArrangement = this._parsedProps.schPortArrangement;
1808
+ if (schPortArrangement) {
1809
+ for (const side in schPortArrangement) {
1810
+ const pins = schPortArrangement[side].pins;
1811
+ if (Array.isArray(pins)) {
1812
+ for (const pinNumber of pins) {
1813
+ portsToCreate.push(new Port({ pinNumber }));
1814
+ }
1815
+ }
1816
+ }
1817
+ }
1818
+ const pinLabels = this._parsedProps.pinLabels;
1819
+ if (pinLabels) {
1820
+ for (let [pinNumber, label] of Object.entries(pinLabels)) {
1821
+ pinNumber = pinNumber.replace("pin", "");
1822
+ let existingPort = portsToCreate.find(
1823
+ (p) => p._parsedProps.pinNumber === Number(pinNumber)
1824
+ );
1825
+ if (!existingPort) {
1826
+ existingPort = new Port({
1827
+ pinNumber: parseInt(pinNumber),
1828
+ name: label
1829
+ });
1830
+ portsToCreate.push(existingPort);
1831
+ } else {
1832
+ existingPort.externallyAddedAliases.push(label);
1833
+ existingPort.props.name = label;
1834
+ }
1835
+ }
1836
+ }
1789
1837
  if (config.schematicSymbolName) {
1790
1838
  const sym = symbols2[`${config.schematicSymbolName}_horz`];
1791
1839
  if (!sym) return;
@@ -1800,21 +1848,9 @@ var NormalComponent = class extends PrimitiveComponent {
1800
1848
  return;
1801
1849
  }
1802
1850
  const portsFromFootprint = this.getPortsFromFootprint();
1803
- portsToCreate.push(...portsFromFootprint);
1804
- const pinLabels = this._parsedProps.pinLabels;
1805
- if (pinLabels) {
1806
- for (let [pinNumber, label] of Object.entries(pinLabels)) {
1807
- pinNumber = pinNumber.replace("pin", "");
1808
- const existingPort = portsToCreate.find(
1809
- (p) => p._parsedProps.pinNumber === Number(pinNumber)
1810
- );
1811
- if (!existingPort) {
1812
- throw new Error(
1813
- `Could not find port for pin number ${pinNumber} in chip ${this.getString()}`
1814
- );
1815
- }
1816
- existingPort.externallyAddedAliases.push(label);
1817
- existingPort.props.name = label;
1851
+ for (const port of portsFromFootprint) {
1852
+ if (!portsToCreate.some((p) => p.isMatchingAnyOf(port.getNameAndAliases()))) {
1853
+ portsToCreate.push(port);
1818
1854
  }
1819
1855
  }
1820
1856
  if (portsToCreate.length > 0) {
@@ -3122,9 +3158,6 @@ var underscorifyPortArrangement = (portArrangement) => {
3122
3158
  return void 0;
3123
3159
  };
3124
3160
 
3125
- // lib/utils/schematic/getAllDimensionsForSchematicBox.ts
3126
- import "circuit-json";
3127
-
3128
3161
  // lib/utils/schematic/getSizeOfSidesFromPortArrangement.ts
3129
3162
  var hasExplicitPinMapping = (pa) => {
3130
3163
  for (const side of [
@@ -3155,8 +3188,9 @@ var getSizeOfSidesFromPortArrangement = (pa) => {
3155
3188
  };
3156
3189
 
3157
3190
  // lib/utils/schematic/getAllDimensionsForSchematicBox.ts
3158
- import "@tscircuit/props";
3159
- import "zod";
3191
+ function isExplicitPinMappingArrangement(arrangement) {
3192
+ return arrangement.leftSide !== void 0;
3193
+ }
3160
3194
  var getAllDimensionsForSchematicBox = (params) => {
3161
3195
  const portDistanceFromEdge = params.portDistanceFromEdge ?? params.schPinSpacing * 2;
3162
3196
  let sidePinCounts = params.schPortArrangement ? getSizeOfSidesFromPortArrangement(params.schPortArrangement) : null;
@@ -3195,7 +3229,7 @@ var getAllDimensionsForSchematicBox = (params) => {
3195
3229
  let currentDistanceFromEdge = 0;
3196
3230
  let truePinIndex = 0;
3197
3231
  for (let sideIndex = 0; sideIndex < sidePinCounts.leftSize; sideIndex++) {
3198
- const pinNumber = truePinIndex + 1;
3232
+ const pinNumber = params.schPortArrangement && isExplicitPinMappingArrangement(params.schPortArrangement) ? params.schPortArrangement?.leftSide?.pins[sideIndex] : truePinIndex + 1;
3199
3233
  const pinStyle = params.schPinStyle?.[`pin${pinNumber}`] ?? params.schPinStyle?.[pinNumber];
3200
3234
  if (pinStyle?.topMargin) {
3201
3235
  currentDistanceFromEdge += pinStyle.topMargin;
@@ -3219,7 +3253,7 @@ var getAllDimensionsForSchematicBox = (params) => {
3219
3253
  }
3220
3254
  currentDistanceFromEdge = 0;
3221
3255
  for (let sideIndex = 0; sideIndex < sidePinCounts.bottomSize; sideIndex++) {
3222
- const pinNumber = truePinIndex + 1;
3256
+ const pinNumber = params.schPortArrangement && isExplicitPinMappingArrangement(params.schPortArrangement) ? params.schPortArrangement.bottomSide?.pins[sideIndex] : truePinIndex + 1;
3223
3257
  const pinStyle = params.schPinStyle?.[`pin${pinNumber}`] ?? params.schPinStyle?.[pinNumber];
3224
3258
  if (pinStyle?.leftMargin) {
3225
3259
  currentDistanceFromEdge += pinStyle.leftMargin;
@@ -3243,7 +3277,7 @@ var getAllDimensionsForSchematicBox = (params) => {
3243
3277
  }
3244
3278
  currentDistanceFromEdge = 0;
3245
3279
  for (let sideIndex = 0; sideIndex < sidePinCounts.rightSize; sideIndex++) {
3246
- const pinNumber = truePinIndex + 1;
3280
+ const pinNumber = params.schPortArrangement && isExplicitPinMappingArrangement(params.schPortArrangement) ? params.schPortArrangement.rightSide?.pins[sideIndex] : truePinIndex + 1;
3247
3281
  const pinStyle = params.schPinStyle?.[`pin${pinNumber}`] ?? params.schPinStyle?.[pinNumber];
3248
3282
  if (pinStyle?.bottomMargin) {
3249
3283
  currentDistanceFromEdge += pinStyle.bottomMargin;
@@ -3267,7 +3301,7 @@ var getAllDimensionsForSchematicBox = (params) => {
3267
3301
  }
3268
3302
  currentDistanceFromEdge = 0;
3269
3303
  for (let sideIndex = 0; sideIndex < sidePinCounts.topSize; sideIndex++) {
3270
- const pinNumber = truePinIndex + 1;
3304
+ const pinNumber = params.schPortArrangement && isExplicitPinMappingArrangement(params.schPortArrangement) ? params.schPortArrangement.topSide?.pins[sideIndex] : truePinIndex + 1;
3271
3305
  const pinStyle = params.schPinStyle?.[`pin${pinNumber}`] ?? params.schPinStyle?.[pinNumber];
3272
3306
  if (pinStyle?.rightMargin) {
3273
3307
  currentDistanceFromEdge += pinStyle.rightMargin;
@@ -3347,9 +3381,7 @@ var getAllDimensionsForSchematicBox = (params) => {
3347
3381
  (p) => p.pinNumber.toString() === pinNumber.toString()
3348
3382
  );
3349
3383
  if (!port) {
3350
- throw new Error(
3351
- `Could not find port for pin number ${pinNumber}, available pins: ${truePortsWithPositions.map((tp) => tp.pinNumber).join(", ")}`
3352
- );
3384
+ return { x: 0, y: 0 };
3353
3385
  }
3354
3386
  return port;
3355
3387
  },