circuit-json-to-tscircuit 0.0.85 → 0.0.87

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 (26) hide show
  1. package/dist/{chunk-UZVNOAO4.js → chunk-DZP2ZWBQ.js} +191 -69
  2. package/dist/cli/main.js +4 -4
  3. package/dist/lib/index.js +1 -1
  4. package/lib/generate-board-schematic-sheet-tsx.ts +11 -6
  5. package/lib/generate-board-schematic-tsx/get-renderer-compatible-symbol-transform.ts +43 -0
  6. package/lib/generate-board-schematic-tsx/get-rigid-pin-identity-transform.ts +95 -0
  7. package/lib/generate-board-schematic-tsx/get-symbol-to-schematic-transform.ts +23 -90
  8. package/lib/generate-board-schematic-tsx/match-schematic-ports-to-symbol-ports.ts +79 -0
  9. package/lib/generate-board-schematic-tsx/match-symbol-ports-by-pin-identity.ts +40 -0
  10. package/package.json +3 -3
  11. package/tests/__snapshots__/test29-standalone-ti-schematic-roundtrip-schematic-comparison.snap.svg +1 -1
  12. package/tests/fixtures/create-ti-evm-roundtrip.ts +16 -7
  13. package/tests/fixtures/ti-evms/README.md +3 -2
  14. package/tests/fixtures/ti-evms/tmds62levm-sheet-13.circuit.json.gz +0 -0
  15. package/tests/test22-pin-attributes.test.tsx +9 -5
  16. package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip-schematic-comparison.snap.svg +3 -3
  17. package/tests/ti-evms/__snapshots__/ti-drv8307evm-roundtrip.test.tsx.snap +1 -1
  18. package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip-schematic-comparison.snap.svg +40 -40
  19. package/tests/ti-evms/__snapshots__/ti-lm251772evm-pd-roundtrip.test.tsx.snap +1 -1
  20. package/tests/ti-evms/__snapshots__/ti-lm5155evm-fly-roundtrip-schematic-comparison.snap.svg +4 -4
  21. package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip-schematic-comparison.snap.svg +4 -4
  22. package/tests/ti-evms/__snapshots__/ti-lmg342x-bb-evm-roundtrip.test.tsx.snap +1 -1
  23. package/tests/ti-evms/__snapshots__/tmds62levm-sheet-05-schematic-graphic.snap.svg +1 -1
  24. package/tests/ti-evms/__snapshots__/tmds62levm-sheet-13-mosfet-geometry.snap.svg +30 -0
  25. package/tests/ti-evms/ti-lm5155evm-fly-roundtrip.test.tsx +1 -1
  26. package/tests/ti-evms/tmds62levm-sheet-13-mosfet-geometry.test.tsx +107 -0
@@ -570,85 +570,134 @@ import { su as su3 } from "@tscircuit/soup-util";
570
570
  import { symbols } from "schematic-symbols";
571
571
 
572
572
  // lib/generate-board-schematic-tsx/get-symbol-to-schematic-transform.ts
573
+ import { fromTriangles as fromTriangles2 } from "transformation-matrix";
574
+
575
+ // lib/generate-board-schematic-tsx/get-renderer-compatible-symbol-transform.ts
576
+ import { compose as compose2, scale, translate as translate2 } from "transformation-matrix";
577
+
578
+ // lib/generate-board-schematic-tsx/match-schematic-ports-to-symbol-ports.ts
579
+ var getAngle = ({
580
+ center,
581
+ point
582
+ }) => Math.atan2(point.y - center.y, point.x - center.x);
583
+ var getAngularDifference = (first, second) => {
584
+ const directDifference = Math.abs(first - second);
585
+ return Math.min(directDifference, 2 * Math.PI - directDifference);
586
+ };
587
+ var matchSchematicPortsToSymbolPorts = ({
588
+ schematicComponent,
589
+ schematicPorts,
590
+ symbol
591
+ }) => {
592
+ const schematicPortsByAngle = schematicPorts.map((schematicPort) => ({
593
+ angle: getAngle({
594
+ center: schematicComponent.center,
595
+ point: schematicPort.center
596
+ }),
597
+ schematicPort
598
+ })).sort((first, second) => first.angle - second.angle);
599
+ const symbolPortsByAngle = symbol.ports.map((symbolPort) => ({
600
+ angle: getAngle({ center: symbol.center, point: symbolPort }),
601
+ symbolPort
602
+ })).sort((first, second) => first.angle - second.angle);
603
+ const usedSymbolPorts = /* @__PURE__ */ new Set();
604
+ const matches = [];
605
+ for (const schematicPortWithAngle of schematicPortsByAngle) {
606
+ const closestSymbolPort = symbolPortsByAngle.filter(({ symbolPort }) => !usedSymbolPorts.has(symbolPort)).map(({ angle, symbolPort }) => ({
607
+ angularDifference: getAngularDifference(
608
+ schematicPortWithAngle.angle,
609
+ angle
610
+ ),
611
+ symbolPort
612
+ })).sort(
613
+ (first, second) => first.angularDifference - second.angularDifference
614
+ )[0];
615
+ if (!closestSymbolPort || closestSymbolPort.angularDifference >= Math.PI / 4)
616
+ continue;
617
+ usedSymbolPorts.add(closestSymbolPort.symbolPort);
618
+ matches.push({
619
+ schematicPort: schematicPortWithAngle.schematicPort,
620
+ symbolPort: closestSymbolPort.symbolPort
621
+ });
622
+ }
623
+ return matches;
624
+ };
625
+
626
+ // lib/generate-board-schematic-tsx/get-renderer-compatible-symbol-transform.ts
627
+ var getRendererCompatibleSymbolTransform = ({
628
+ schematicComponent,
629
+ schematicPorts,
630
+ symbol
631
+ }) => {
632
+ const matches = matchSchematicPortsToSymbolPorts({
633
+ schematicComponent,
634
+ schematicPorts,
635
+ symbol
636
+ });
637
+ const first = matches[0];
638
+ if (!first) return void 0;
639
+ const second = matches[1];
640
+ const firstSymbolPoint = second?.symbolPort ?? symbol.center;
641
+ const firstSchematicPoint = second?.schematicPort.center ?? schematicComponent.center;
642
+ const originalDistance = Math.hypot(
643
+ first.symbolPort.x - firstSymbolPoint.x,
644
+ first.symbolPort.y - firstSymbolPoint.y
645
+ );
646
+ if (originalDistance === 0) return void 0;
647
+ const renderedDistance = Math.hypot(
648
+ first.schematicPort.center.x - firstSchematicPoint.x,
649
+ first.schematicPort.center.y - firstSchematicPoint.y
650
+ );
651
+ const uniformScale = renderedDistance / originalDistance;
652
+ return compose2(
653
+ translate2(firstSchematicPoint.x, firstSchematicPoint.y),
654
+ scale(uniformScale),
655
+ translate2(-firstSymbolPoint.x, -firstSymbolPoint.y)
656
+ );
657
+ };
658
+
659
+ // lib/generate-board-schematic-tsx/get-rigid-pin-identity-transform.ts
573
660
  import { applyToPoint as applyToPoint3, fromTriangles } from "transformation-matrix";
574
661
 
575
662
  // lib/generate-board-schematic-tsx/get-port-pair-triangle.ts
576
663
  import {
577
664
  applyToPoint as applyToPoint2,
578
- compose as compose2,
665
+ compose as compose3,
579
666
  rotateDEG as rotateDEG2,
580
- translate as translate2
667
+ translate as translate3
581
668
  } from "transformation-matrix";
582
669
  var getPortPairTriangle = ({
583
670
  first,
584
671
  second
585
672
  }) => {
586
- const portToPerpendicularTransform = compose2(
587
- translate2(first.x, first.y),
673
+ const portToPerpendicularTransform = compose3(
674
+ translate3(first.x, first.y),
588
675
  rotateDEG2(90),
589
- translate2(-first.x, -first.y)
676
+ translate3(-first.x, -first.y)
590
677
  );
591
678
  return [first, second, applyToPoint2(portToPerpendicularTransform, second)];
592
679
  };
593
680
 
594
- // lib/generate-board-schematic-tsx/get-symbol-to-schematic-transform.ts
595
- var getSymbolToSchematicTransform = ({
596
- symbol,
597
- schematicComponent,
598
- schematicPorts,
599
- sourcePorts
600
- }) => {
601
- const matches = symbol.ports.flatMap((symbolPort) => {
602
- const candidates = schematicPorts.filter((schematicPort) => {
603
- const sourcePort = sourcePorts.find(
604
- (port) => port.source_port_id === schematicPort.source_port_id
605
- );
606
- const pinNumber = schematicPort.pin_number ?? sourcePort?.pin_number;
607
- const labels = [
608
- sourcePort?.name,
609
- ...sourcePort?.port_hints ?? [],
610
- ...pinNumber === void 0 ? [] : [String(pinNumber), `pin${pinNumber}`]
611
- ];
612
- return labels.some(
613
- (label) => label !== void 0 && symbolPort.labels.includes(label)
614
- );
615
- });
616
- return candidates.length === 1 ? [{ symbolPort, schematicPort: candidates[0] }] : [];
617
- });
681
+ // lib/generate-board-schematic-tsx/get-rigid-pin-identity-transform.ts
682
+ var TRANSFORM_TOLERANCE = 1e-6;
683
+ var getRigidPinIdentityTransform = (matches) => {
618
684
  const first = matches[0];
619
685
  const second = matches.find(
620
686
  (match) => match !== first && match.schematicPort !== first?.schematicPort
621
687
  );
622
- if (first && second) {
623
- const third = matches.find(
624
- (match) => match !== first && match !== second && isNonCollinear({
625
- first: first.symbolPort,
626
- second: second.symbolPort,
627
- third: match.symbolPort
628
- }) && isNonCollinear({
629
- first: first.schematicPort.center,
630
- second: second.schematicPort.center,
631
- third: match.schematicPort.center
632
- })
633
- );
634
- if (third) {
635
- const transform = fromTriangles(
636
- [first.symbolPort, second.symbolPort, third.symbolPort],
637
- [
638
- first.schematicPort.center,
639
- second.schematicPort.center,
640
- third.schematicPort.center
641
- ]
642
- );
643
- const fitsAllMatchedPorts = matches.every((match) => {
644
- const transformedPort = applyToPoint3(transform, match.symbolPort);
645
- return Math.hypot(
646
- transformedPort.x - match.schematicPort.center.x,
647
- transformedPort.y - match.schematicPort.center.y
648
- ) < 1e-6;
649
- });
650
- if (fitsAllMatchedPorts) return transform;
651
- }
688
+ if (!first || !second) return void 0;
689
+ const third = matches.find(
690
+ (match) => match !== first && match !== second && pointsAreNonCollinear({
691
+ first: first.symbolPort,
692
+ second: second.symbolPort,
693
+ third: match.symbolPort
694
+ }) && pointsAreNonCollinear({
695
+ first: first.schematicPort.center,
696
+ second: second.schematicPort.center,
697
+ third: match.schematicPort.center
698
+ })
699
+ );
700
+ if (!third) {
652
701
  return fromTriangles(
653
702
  getPortPairTriangle({
654
703
  first: first.symbolPort,
@@ -660,7 +709,87 @@ var getSymbolToSchematicTransform = ({
660
709
  })
661
710
  );
662
711
  }
663
- return fromTriangles(
712
+ const transform = fromTriangles(
713
+ [first.symbolPort, second.symbolPort, third.symbolPort],
714
+ [
715
+ first.schematicPort.center,
716
+ second.schematicPort.center,
717
+ third.schematicPort.center
718
+ ]
719
+ );
720
+ if (!isRigidUniformTransform(transform)) return void 0;
721
+ const fitsAllMatchedPorts = matches.every((match) => {
722
+ const transformedPort = applyToPoint3(transform, match.symbolPort);
723
+ return Math.hypot(
724
+ transformedPort.x - match.schematicPort.center.x,
725
+ transformedPort.y - match.schematicPort.center.y
726
+ ) < TRANSFORM_TOLERANCE;
727
+ });
728
+ return fitsAllMatchedPorts ? transform : void 0;
729
+ };
730
+ var pointsAreNonCollinear = ({
731
+ first,
732
+ second,
733
+ third
734
+ }) => Math.abs(
735
+ (second.x - first.x) * (third.y - first.y) - (second.y - first.y) * (third.x - first.x)
736
+ ) > Number.EPSILON;
737
+ var isRigidUniformTransform = (transform) => {
738
+ const firstAxisLength = Math.hypot(transform.a, transform.b);
739
+ const secondAxisLength = Math.hypot(transform.c, transform.d);
740
+ if (firstAxisLength === 0 || secondAxisLength === 0) return false;
741
+ const axisDotProduct = transform.a * transform.c + transform.b * transform.d;
742
+ const relativeScaleDifference = Math.abs(firstAxisLength - secondAxisLength) / Math.max(firstAxisLength, secondAxisLength);
743
+ const normalizedAxisDotProduct = Math.abs(axisDotProduct) / (firstAxisLength * secondAxisLength);
744
+ return relativeScaleDifference < TRANSFORM_TOLERANCE && normalizedAxisDotProduct < TRANSFORM_TOLERANCE;
745
+ };
746
+
747
+ // lib/generate-board-schematic-tsx/match-symbol-ports-by-pin-identity.ts
748
+ var matchSymbolPortsByPinIdentity = ({
749
+ schematicPorts,
750
+ sourcePorts,
751
+ symbol
752
+ }) => symbol.ports.flatMap((symbolPort) => {
753
+ const candidates = schematicPorts.filter((schematicPort) => {
754
+ const sourcePort = sourcePorts.find(
755
+ (port) => port.source_port_id === schematicPort.source_port_id
756
+ );
757
+ const pinNumber = schematicPort.pin_number ?? sourcePort?.pin_number;
758
+ const portIdentities = [
759
+ sourcePort?.name,
760
+ ...sourcePort?.port_hints ?? [],
761
+ ...pinNumber === void 0 ? [] : [String(pinNumber), `pin${pinNumber}`]
762
+ ];
763
+ return portIdentities.some(
764
+ (portIdentity) => portIdentity !== void 0 && symbolPort.labels.includes(portIdentity)
765
+ );
766
+ });
767
+ return candidates.length === 1 ? [{ symbolPort, schematicPort: candidates[0] }] : [];
768
+ });
769
+
770
+ // lib/generate-board-schematic-tsx/get-symbol-to-schematic-transform.ts
771
+ var getSymbolToSchematicTransform = ({
772
+ symbol,
773
+ schematicComponent,
774
+ schematicPorts,
775
+ sourcePorts
776
+ }) => {
777
+ const pinIdentityMatches = matchSymbolPortsByPinIdentity({
778
+ schematicPorts,
779
+ sourcePorts,
780
+ symbol
781
+ });
782
+ const rigidPinIdentityTransform = getRigidPinIdentityTransform(pinIdentityMatches);
783
+ if (rigidPinIdentityTransform) return rigidPinIdentityTransform;
784
+ if (pinIdentityMatches.length >= 3) {
785
+ const rendererCompatibleTransform = getRendererCompatibleSymbolTransform({
786
+ schematicComponent,
787
+ schematicPorts,
788
+ symbol
789
+ });
790
+ if (rendererCompatibleTransform) return rendererCompatibleTransform;
791
+ }
792
+ return fromTriangles2(
664
793
  [
665
794
  symbol.center,
666
795
  { x: symbol.center.x + symbol.size.width, y: symbol.center.y },
@@ -679,13 +808,6 @@ var getSymbolToSchematicTransform = ({
679
808
  ]
680
809
  );
681
810
  };
682
- var isNonCollinear = ({
683
- first,
684
- second,
685
- third
686
- }) => Math.abs(
687
- (second.x - first.x) * (third.y - first.y) - (second.y - first.y) * (third.x - first.x)
688
- ) > Number.EPSILON;
689
811
 
690
812
  // lib/generate-board-schematic-tsx/convert-symbol-primitives.ts
691
813
  import { schematic_text as schematic_text2 } from "circuit-json";
@@ -1041,7 +1163,7 @@ import { symbols as symbols2 } from "schematic-symbols";
1041
1163
  // lib/generate-board-schematic-tsx/get-net-label-symbol-to-schematic-transform.ts
1042
1164
  import {
1043
1165
  fromOneMovingPoint,
1044
- translate as translate3
1166
+ translate as translate4
1045
1167
  } from "transformation-matrix";
1046
1168
  var getNetLabelSymbolToSchematicTransform = ({
1047
1169
  symbol,
@@ -1049,7 +1171,7 @@ var getNetLabelSymbolToSchematicTransform = ({
1049
1171
  }) => {
1050
1172
  const symbolPort = symbol.ports[0];
1051
1173
  if (!symbolPort) {
1052
- return translate3(schematicAnchorPosition.x, schematicAnchorPosition.y);
1174
+ return translate4(schematicAnchorPosition.x, schematicAnchorPosition.y);
1053
1175
  }
1054
1176
  return fromOneMovingPoint(symbolPort, schematicAnchorPosition);
1055
1177
  };
package/dist/cli/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  convertCircuitJsonToTscircuit
4
- } from "../chunk-UZVNOAO4.js";
4
+ } from "../chunk-DZP2ZWBQ.js";
5
5
 
6
6
  // cli/main.ts
7
7
  import { Command } from "commander";
@@ -16,7 +16,7 @@ var package_default = {
16
16
  "./cli": "./dist/cli/main.js"
17
17
  },
18
18
  type: "module",
19
- version: "0.0.84",
19
+ version: "0.0.86",
20
20
  repository: {
21
21
  type: "git",
22
22
  url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
@@ -37,7 +37,7 @@ var package_default = {
37
37
  "@types/react": "18",
38
38
  "@types/react-dom": "18",
39
39
  "bun-match-svg": "^0.0.15",
40
- "circuit-json": "^0.0.507",
40
+ "circuit-json": "^0.0.521",
41
41
  "circuit-to-svg": "https://pkg.pr.new/tscircuit/circuit-to-svg@738ef1d",
42
42
  commander: "^13.1.0",
43
43
  prettier: "^3.9.5",
@@ -45,7 +45,7 @@ var package_default = {
45
45
  shiki: "^4.3.1",
46
46
  "stack-svgs": "^0.0.1",
47
47
  "transformation-matrix": "3.1.0",
48
- tscircuit: "0.0.2703-libonly",
48
+ tscircuit: "0.0.2775-libonly",
49
49
  tsup: "^8.3.5",
50
50
  zod: "^3.25.76"
51
51
  },
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  convertCircuitJsonToTscircuit
3
- } from "../chunk-UZVNOAO4.js";
3
+ } from "../chunk-DZP2ZWBQ.js";
4
4
  export {
5
5
  convertCircuitJsonToTscircuit
6
6
  };
@@ -1,6 +1,11 @@
1
1
  import { mmStr } from "@tscircuit/mm"
2
2
  import type { AnyCircuitElement } from "circuit-json"
3
3
 
4
+ const DEFAULT_SHEET_DIMENSIONS_MM = {
5
+ a4: { width: 297, height: 210 },
6
+ ansi_b: { width: 431.8, height: 279.4 },
7
+ } as const
8
+
4
9
  export const generateBoardSchematicSheetTsx = ({
5
10
  circuitJson,
6
11
  schematicElements,
@@ -21,11 +26,11 @@ export const generateBoardSchematicSheetTsx = ({
21
26
  if (sheet.sheet_size) {
22
27
  attrs.push(`sheetSize="${sheet.sheet_size === "a4" ? "A4" : "ANSI_B"}"`)
23
28
  }
24
- if (sheet.sheet_width !== undefined) {
25
- attrs.push(`sheetWidth="${mmStr(sheet.sheet_width)}"`)
26
- }
27
- if (sheet.sheet_height !== undefined) {
28
- attrs.push(`sheetHeight="${mmStr(sheet.sheet_height)}"`)
29
- }
29
+ const defaultDimensions =
30
+ DEFAULT_SHEET_DIMENSIONS_MM[sheet.sheet_size ?? "a4"]
31
+ const sheetWidth = sheet.sheet_width ?? defaultDimensions.width
32
+ const sheetHeight = sheet.sheet_height ?? defaultDimensions.height
33
+ attrs.push(`sheetWidth="${mmStr(sheetWidth)}"`)
34
+ attrs.push(`sheetHeight="${mmStr(sheetHeight)}"`)
30
35
  return `<schematicsheet ${attrs.join(" ")}>\n${schematicElements.join("\n")}\n</schematicsheet>`
31
36
  }
@@ -0,0 +1,43 @@
1
+ import type { SchematicComponent, SchematicPort } from "circuit-json"
2
+ import type { SchSymbol } from "schematic-symbols"
3
+ import { compose, scale, translate, type Matrix } from "transformation-matrix"
4
+ import { matchSchematicPortsToSymbolPorts } from "./match-schematic-ports-to-symbol-ports"
5
+
6
+ export const getRendererCompatibleSymbolTransform = ({
7
+ schematicComponent,
8
+ schematicPorts,
9
+ symbol,
10
+ }: {
11
+ schematicComponent: SchematicComponent
12
+ schematicPorts: SchematicPort[]
13
+ symbol: SchSymbol
14
+ }): Matrix | undefined => {
15
+ const matches = matchSchematicPortsToSymbolPorts({
16
+ schematicComponent,
17
+ schematicPorts,
18
+ symbol,
19
+ })
20
+ const first = matches[0]
21
+ if (!first) return undefined
22
+
23
+ const second = matches[1]
24
+ const firstSymbolPoint = second?.symbolPort ?? symbol.center
25
+ const firstSchematicPoint =
26
+ second?.schematicPort.center ?? schematicComponent.center
27
+ const originalDistance = Math.hypot(
28
+ first.symbolPort.x - firstSymbolPoint.x,
29
+ first.symbolPort.y - firstSymbolPoint.y,
30
+ )
31
+ if (originalDistance === 0) return undefined
32
+
33
+ const renderedDistance = Math.hypot(
34
+ first.schematicPort.center.x - firstSchematicPoint.x,
35
+ first.schematicPort.center.y - firstSchematicPoint.y,
36
+ )
37
+ const uniformScale = renderedDistance / originalDistance
38
+ return compose(
39
+ translate(firstSchematicPoint.x, firstSchematicPoint.y),
40
+ scale(uniformScale),
41
+ translate(-firstSymbolPoint.x, -firstSymbolPoint.y),
42
+ )
43
+ }
@@ -0,0 +1,95 @@
1
+ import { applyToPoint, fromTriangles, type Matrix } from "transformation-matrix"
2
+ import { getPortPairTriangle } from "./get-port-pair-triangle"
3
+ import type { SchematicSymbolPortMatch } from "./match-symbol-ports-by-pin-identity"
4
+
5
+ const TRANSFORM_TOLERANCE = 0.000001
6
+
7
+ export const getRigidPinIdentityTransform = (
8
+ matches: SchematicSymbolPortMatch[],
9
+ ): Matrix | undefined => {
10
+ const first = matches[0]
11
+ const second = matches.find(
12
+ (match) => match !== first && match.schematicPort !== first?.schematicPort,
13
+ )
14
+ if (!first || !second) return undefined
15
+
16
+ const third = matches.find(
17
+ (match) =>
18
+ match !== first &&
19
+ match !== second &&
20
+ pointsAreNonCollinear({
21
+ first: first.symbolPort,
22
+ second: second.symbolPort,
23
+ third: match.symbolPort,
24
+ }) &&
25
+ pointsAreNonCollinear({
26
+ first: first.schematicPort.center,
27
+ second: second.schematicPort.center,
28
+ third: match.schematicPort.center,
29
+ }),
30
+ )
31
+ if (!third) {
32
+ return fromTriangles(
33
+ getPortPairTriangle({
34
+ first: first.symbolPort,
35
+ second: second.symbolPort,
36
+ }),
37
+ getPortPairTriangle({
38
+ first: first.schematicPort.center,
39
+ second: second.schematicPort.center,
40
+ }),
41
+ )
42
+ }
43
+
44
+ const transform = fromTriangles(
45
+ [first.symbolPort, second.symbolPort, third.symbolPort],
46
+ [
47
+ first.schematicPort.center,
48
+ second.schematicPort.center,
49
+ third.schematicPort.center,
50
+ ],
51
+ )
52
+ if (!isRigidUniformTransform(transform)) return undefined
53
+
54
+ const fitsAllMatchedPorts = matches.every((match) => {
55
+ const transformedPort = applyToPoint(transform, match.symbolPort)
56
+ return (
57
+ Math.hypot(
58
+ transformedPort.x - match.schematicPort.center.x,
59
+ transformedPort.y - match.schematicPort.center.y,
60
+ ) < TRANSFORM_TOLERANCE
61
+ )
62
+ })
63
+ return fitsAllMatchedPorts ? transform : undefined
64
+ }
65
+
66
+ const pointsAreNonCollinear = ({
67
+ first,
68
+ second,
69
+ third,
70
+ }: {
71
+ first: { x: number; y: number }
72
+ second: { x: number; y: number }
73
+ third: { x: number; y: number }
74
+ }) =>
75
+ Math.abs(
76
+ (second.x - first.x) * (third.y - first.y) -
77
+ (second.y - first.y) * (third.x - first.x),
78
+ ) > Number.EPSILON
79
+
80
+ const isRigidUniformTransform = (transform: Matrix) => {
81
+ const firstAxisLength = Math.hypot(transform.a, transform.b)
82
+ const secondAxisLength = Math.hypot(transform.c, transform.d)
83
+ if (firstAxisLength === 0 || secondAxisLength === 0) return false
84
+
85
+ const axisDotProduct = transform.a * transform.c + transform.b * transform.d
86
+ const relativeScaleDifference =
87
+ Math.abs(firstAxisLength - secondAxisLength) /
88
+ Math.max(firstAxisLength, secondAxisLength)
89
+ const normalizedAxisDotProduct =
90
+ Math.abs(axisDotProduct) / (firstAxisLength * secondAxisLength)
91
+ return (
92
+ relativeScaleDifference < TRANSFORM_TOLERANCE &&
93
+ normalizedAxisDotProduct < TRANSFORM_TOLERANCE
94
+ )
95
+ }
@@ -4,9 +4,10 @@ import type {
4
4
  SourcePort,
5
5
  } from "circuit-json"
6
6
  import type { SchSymbol } from "schematic-symbols"
7
- import { applyToPoint, fromTriangles } from "transformation-matrix"
8
-
9
- import { getPortPairTriangle } from "./get-port-pair-triangle"
7
+ import { fromTriangles } from "transformation-matrix"
8
+ import { getRendererCompatibleSymbolTransform } from "./get-renderer-compatible-symbol-transform"
9
+ import { getRigidPinIdentityTransform } from "./get-rigid-pin-identity-transform"
10
+ import { matchSymbolPortsByPinIdentity } from "./match-symbol-ports-by-pin-identity"
10
11
 
11
12
  export const getSymbolToSchematicTransform = ({
12
13
  symbol,
@@ -19,81 +20,27 @@ export const getSymbolToSchematicTransform = ({
19
20
  schematicPorts: SchematicPort[]
20
21
  sourcePorts: SourcePort[]
21
22
  }) => {
22
- // Match declared pin identities, never inferred angles or array order.
23
- const matches = symbol.ports.flatMap((symbolPort) => {
24
- const candidates = schematicPorts.filter((schematicPort) => {
25
- const sourcePort = sourcePorts.find(
26
- (port) => port.source_port_id === schematicPort.source_port_id,
27
- )
28
- const pinNumber = schematicPort.pin_number ?? sourcePort?.pin_number
29
- const labels = [
30
- sourcePort?.name,
31
- ...(sourcePort?.port_hints ?? []),
32
- ...(pinNumber === undefined
33
- ? []
34
- : [String(pinNumber), `pin${pinNumber}`]),
35
- ]
36
- return labels.some(
37
- (label) => label !== undefined && symbolPort.labels.includes(label),
38
- )
39
- })
40
- return candidates.length === 1
41
- ? [{ symbolPort, schematicPort: candidates[0]! }]
42
- : []
23
+ const pinIdentityMatches = matchSymbolPortsByPinIdentity({
24
+ schematicPorts,
25
+ sourcePorts,
26
+ symbol,
43
27
  })
44
- const first = matches[0]
45
- const second = matches.find(
46
- (match) => match !== first && match.schematicPort !== first?.schematicPort,
47
- )
48
- if (first && second) {
49
- const third = matches.find(
50
- (match) =>
51
- match !== first &&
52
- match !== second &&
53
- isNonCollinear({
54
- first: first.symbolPort,
55
- second: second.symbolPort,
56
- third: match.symbolPort,
57
- }) &&
58
- isNonCollinear({
59
- first: first.schematicPort.center,
60
- second: second.schematicPort.center,
61
- third: match.schematicPort.center,
62
- }),
63
- )
64
- if (third) {
65
- const transform = fromTriangles(
66
- [first.symbolPort, second.symbolPort, third.symbolPort],
67
- [
68
- first.schematicPort.center,
69
- second.schematicPort.center,
70
- third.schematicPort.center,
71
- ],
72
- )
73
- const fitsAllMatchedPorts = matches.every((match) => {
74
- const transformedPort = applyToPoint(transform, match.symbolPort)
75
- return (
76
- Math.hypot(
77
- transformedPort.x - match.schematicPort.center.x,
78
- transformedPort.y - match.schematicPort.center.y,
79
- ) < 0.000001
80
- )
81
- })
82
- if (fitsAllMatchedPorts) return transform
83
- }
84
- return fromTriangles(
85
- getPortPairTriangle({
86
- first: first.symbolPort,
87
- second: second.symbolPort,
88
- }),
89
- getPortPairTriangle({
90
- first: first.schematicPort.center,
91
- second: second.schematicPort.center,
92
- }),
93
- )
28
+ const rigidPinIdentityTransform =
29
+ getRigidPinIdentityTransform(pinIdentityMatches)
30
+ if (rigidPinIdentityTransform) return rigidPinIdentityTransform
31
+
32
+ if (pinIdentityMatches.length >= 3) {
33
+ // Inconsistent pin identities must not shear standard symbol artwork.
34
+ // Match circuit-to-svg's established transform for the visual fallback.
35
+ const rendererCompatibleTransform = getRendererCompatibleSymbolTransform({
36
+ schematicComponent,
37
+ schematicPorts,
38
+ symbol,
39
+ })
40
+ if (rendererCompatibleTransform) return rendererCompatibleTransform
94
41
  }
95
- // Without matching pin identities, use the explicitly imported component
96
- // bounds. The library constructs the transform from those correspondences.
42
+
43
+ // Without matching ports, use the explicitly imported component bounds.
97
44
  return fromTriangles(
98
45
  [
99
46
  symbol.center,
@@ -113,17 +60,3 @@ export const getSymbolToSchematicTransform = ({
113
60
  ],
114
61
  )
115
62
  }
116
-
117
- const isNonCollinear = ({
118
- first,
119
- second,
120
- third,
121
- }: {
122
- first: { x: number; y: number }
123
- second: { x: number; y: number }
124
- third: { x: number; y: number }
125
- }) =>
126
- Math.abs(
127
- (second.x - first.x) * (third.y - first.y) -
128
- (second.y - first.y) * (third.x - first.x),
129
- ) > Number.EPSILON