circuit-json-to-tscircuit 0.0.72 → 0.0.74

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.
@@ -571,6 +571,117 @@ var convertSymbolPrimitives = ({
571
571
  // lib/generate-board-schematic-tsx/convert-box-components.ts
572
572
  import { su as su2 } from "@tscircuit/soup-util";
573
573
  import "circuit-json";
574
+
575
+ // lib/generate-board-schematic-tsx/convert-box-port-markers.ts
576
+ var ARROW_SIZE = 0.1;
577
+ var ARROW_HALF_ANGLE_RADIANS = Math.PI / 6;
578
+ var ARROW_AXIAL_LENGTH = ARROW_SIZE * Math.cos(ARROW_HALF_ANGLE_RADIANS);
579
+ var ARROW_HALF_WIDTH = ARROW_SIZE * Math.sin(ARROW_HALF_ANGLE_RADIANS);
580
+ var MARKER_STROKE_WIDTH = 0.02 / 3;
581
+ var MARKER_STROKE_COLOR = "#840000";
582
+ var MARKER_FILL_COLOR = "#ffffff";
583
+ var INVERSION_BUBBLE_RADIUS = 0.06;
584
+ var getOutwardDirection = (side) => {
585
+ switch (side) {
586
+ case "left":
587
+ return { x: -1, y: 0 };
588
+ case "right":
589
+ return { x: 1, y: 0 };
590
+ case "top":
591
+ return { x: 0, y: 1 };
592
+ case "bottom":
593
+ return { x: 0, y: -1 };
594
+ default:
595
+ return null;
596
+ }
597
+ };
598
+ var createArrowPath = ({
599
+ tip,
600
+ direction
601
+ }) => {
602
+ const base = {
603
+ x: tip.x - direction.x * ARROW_AXIAL_LENGTH,
604
+ y: tip.y - direction.y * ARROW_AXIAL_LENGTH
605
+ };
606
+ const perpendicular = { x: -direction.y, y: direction.x };
607
+ const firstBaseCorner = {
608
+ x: base.x + perpendicular.x * ARROW_HALF_WIDTH,
609
+ y: base.y + perpendicular.y * ARROW_HALF_WIDTH
610
+ };
611
+ const secondBaseCorner = {
612
+ x: base.x - perpendicular.x * ARROW_HALF_WIDTH,
613
+ y: base.y - perpendicular.y * ARROW_HALF_WIDTH
614
+ };
615
+ return formatElement("schematicpath", {
616
+ points: [tip, firstBaseCorner, secondBaseCorner, tip],
617
+ strokeWidth: MARKER_STROKE_WIDTH,
618
+ strokeColor: MARKER_STROKE_COLOR,
619
+ fillColor: MARKER_FILL_COLOR,
620
+ isFilled: true
621
+ });
622
+ };
623
+ var convertBoxPortMarkers = ({
624
+ schematicPort,
625
+ edge
626
+ }) => {
627
+ const outward = getOutwardDirection(schematicPort.side_of_component);
628
+ if (!outward) return { markers: [], pinLineStart: edge };
629
+ const inversionOffset = schematicPort.is_drawn_with_inversion_circle ? INVERSION_BUBBLE_RADIUS * 2 : 0;
630
+ const pinLineStart = {
631
+ x: edge.x + outward.x * inversionOffset,
632
+ y: edge.y + outward.y * inversionOffset
633
+ };
634
+ const markers = [];
635
+ if (schematicPort.is_drawn_with_inversion_circle) {
636
+ markers.push(
637
+ formatElement("schematiccircle", {
638
+ center: {
639
+ x: edge.x + outward.x * INVERSION_BUBBLE_RADIUS,
640
+ y: edge.y + outward.y * INVERSION_BUBBLE_RADIUS
641
+ },
642
+ radius: INVERSION_BUBBLE_RADIUS,
643
+ strokeWidth: MARKER_STROKE_WIDTH * 3,
644
+ color: MARKER_STROKE_COLOR,
645
+ fillColor: MARKER_FILL_COLOR,
646
+ isFilled: true
647
+ })
648
+ );
649
+ }
650
+ if (schematicPort.has_input_arrow) {
651
+ markers.push(
652
+ createArrowPath({
653
+ tip: pinLineStart,
654
+ direction: { x: -outward.x, y: -outward.y }
655
+ })
656
+ );
657
+ }
658
+ if (schematicPort.has_output_arrow) {
659
+ const separation = schematicPort.has_input_arrow ? ARROW_AXIAL_LENGTH : 0;
660
+ markers.push(
661
+ createArrowPath({
662
+ tip: {
663
+ x: pinLineStart.x + outward.x * (separation + ARROW_SIZE),
664
+ y: pinLineStart.y + outward.y * (separation + ARROW_SIZE)
665
+ },
666
+ direction: outward
667
+ })
668
+ );
669
+ }
670
+ return { markers, pinLineStart };
671
+ };
672
+
673
+ // lib/generate-board-schematic-tsx/get-box-pin-display-label.ts
674
+ var getBoxPinDisplayLabel = ({
675
+ schematicPort,
676
+ sourcePort
677
+ }) => {
678
+ if (schematicPort.display_pin_label !== void 0)
679
+ return schematicPort.display_pin_label;
680
+ const sourceName = sourcePort?.name;
681
+ return sourceName && !/^\d+$/.test(sourceName) ? sourceName : void 0;
682
+ };
683
+
684
+ // lib/generate-board-schematic-tsx/convert-box-components.ts
574
685
  var DEFAULT_BOX_BODY_STROKE_WIDTH = 0.02;
575
686
  var DEFAULT_BOX_BODY_STROKE_COLOR = "#840000";
576
687
  var DEFAULT_BOX_BODY_FILL_COLOR = "#ffffc2";
@@ -611,16 +722,21 @@ var convertBoxComponent = ({
611
722
  edge.y = schematicComponent.center.y - schematicComponent.size.height / 2;
612
723
  break;
613
724
  }
725
+ const { markers, pinLineStart } = convertBoxPortMarkers({
726
+ schematicPort,
727
+ edge
728
+ });
614
729
  primitives.push(
615
730
  formatElement("schematicline", {
616
- x1: edge.x,
617
- y1: edge.y,
731
+ x1: pinLineStart.x,
732
+ y1: pinLineStart.y,
618
733
  x2: schematicPort.center.x,
619
734
  y2: schematicPort.center.y,
620
735
  strokeWidth: DEFAULT_BOX_BODY_STROKE_WIDTH,
621
736
  color: DEFAULT_BOX_BODY_STROKE_COLOR
622
737
  })
623
738
  );
739
+ primitives.push(...markers);
624
740
  const sourcePort = su2(circuitJson).source_port.get(
625
741
  schematicPort.source_port_id
626
742
  );
@@ -655,7 +771,10 @@ var convertBoxComponent = ({
655
771
  rotation = -90;
656
772
  break;
657
773
  }
658
- const displayPinLabel = schematicPort.display_pin_label ?? sourcePort?.name;
774
+ const displayPinLabel = getBoxPinDisplayLabel({
775
+ schematicPort,
776
+ sourcePort
777
+ });
659
778
  if (displayPinLabel)
660
779
  primitives.push(
661
780
  formatElement("schematictext", {
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-2BNMFKVO.js";
4
+ } from "../chunk-KDQXBFSE.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.71",
19
+ version: "0.0.73",
20
20
  repository: {
21
21
  type: "git",
22
22
  url: "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
package/dist/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  convertCircuitJsonToTscircuit
3
- } from "../chunk-2BNMFKVO.js";
3
+ } from "../chunk-KDQXBFSE.js";
4
4
  export {
5
5
  convertCircuitJsonToTscircuit
6
6
  };
@@ -1,7 +1,9 @@
1
1
  import { su } from "@tscircuit/soup-util"
2
2
  import { type AnyCircuitElement, type SchematicComponent } from "circuit-json"
3
3
  import { convertRect } from "./convert-rect"
4
+ import { convertBoxPortMarkers } from "./convert-box-port-markers"
4
5
  import { formatElement } from "./format-attributes"
6
+ import { getBoxPinDisplayLabel } from "./get-box-pin-display-label"
5
7
 
6
8
  // Circuit JSON does not encode box body styling. These values mirror the
7
9
  // circuit-to-svg defaults so reconstructed boxes keep the compiled view.
@@ -54,16 +56,21 @@ export const convertBoxComponent = ({
54
56
  schematicComponent.center.y - schematicComponent.size.height / 2
55
57
  break
56
58
  }
59
+ const { markers, pinLineStart } = convertBoxPortMarkers({
60
+ schematicPort,
61
+ edge,
62
+ })
57
63
  primitives.push(
58
64
  formatElement("schematicline", {
59
- x1: edge.x,
60
- y1: edge.y,
65
+ x1: pinLineStart.x,
66
+ y1: pinLineStart.y,
61
67
  x2: schematicPort.center.x,
62
68
  y2: schematicPort.center.y,
63
69
  strokeWidth: DEFAULT_BOX_BODY_STROKE_WIDTH,
64
70
  color: DEFAULT_BOX_BODY_STROKE_COLOR,
65
71
  }),
66
72
  )
73
+ primitives.push(...markers)
67
74
  const sourcePort = su(circuitJson).source_port.get(
68
75
  schematicPort.source_port_id,
69
76
  )
@@ -98,7 +105,10 @@ export const convertBoxComponent = ({
98
105
  rotation = -90
99
106
  break
100
107
  }
101
- const displayPinLabel = schematicPort.display_pin_label ?? sourcePort?.name
108
+ const displayPinLabel = getBoxPinDisplayLabel({
109
+ schematicPort,
110
+ sourcePort,
111
+ })
102
112
  if (displayPinLabel)
103
113
  primitives.push(
104
114
  formatElement("schematictext", {
@@ -0,0 +1,114 @@
1
+ import type { Point, SchematicPort } from "circuit-json"
2
+ import { formatElement } from "./format-attributes"
3
+
4
+ const ARROW_SIZE = 0.1
5
+ const ARROW_HALF_ANGLE_RADIANS = Math.PI / 6
6
+ const ARROW_AXIAL_LENGTH = ARROW_SIZE * Math.cos(ARROW_HALF_ANGLE_RADIANS)
7
+ const ARROW_HALF_WIDTH = ARROW_SIZE * Math.sin(ARROW_HALF_ANGLE_RADIANS)
8
+ const MARKER_STROKE_WIDTH = 0.02 / 3
9
+ const MARKER_STROKE_COLOR = "#840000"
10
+ const MARKER_FILL_COLOR = "#ffffff"
11
+ const INVERSION_BUBBLE_RADIUS = 0.06
12
+
13
+ const getOutwardDirection = (
14
+ side: SchematicPort["side_of_component"],
15
+ ): Point | null => {
16
+ switch (side) {
17
+ case "left":
18
+ return { x: -1, y: 0 }
19
+ case "right":
20
+ return { x: 1, y: 0 }
21
+ case "top":
22
+ return { x: 0, y: 1 }
23
+ case "bottom":
24
+ return { x: 0, y: -1 }
25
+ default:
26
+ return null
27
+ }
28
+ }
29
+
30
+ const createArrowPath = ({
31
+ tip,
32
+ direction,
33
+ }: {
34
+ tip: Point
35
+ direction: Point
36
+ }): string => {
37
+ const base = {
38
+ x: tip.x - direction.x * ARROW_AXIAL_LENGTH,
39
+ y: tip.y - direction.y * ARROW_AXIAL_LENGTH,
40
+ }
41
+ const perpendicular = { x: -direction.y, y: direction.x }
42
+ const firstBaseCorner = {
43
+ x: base.x + perpendicular.x * ARROW_HALF_WIDTH,
44
+ y: base.y + perpendicular.y * ARROW_HALF_WIDTH,
45
+ }
46
+ const secondBaseCorner = {
47
+ x: base.x - perpendicular.x * ARROW_HALF_WIDTH,
48
+ y: base.y - perpendicular.y * ARROW_HALF_WIDTH,
49
+ }
50
+ return formatElement("schematicpath", {
51
+ points: [tip, firstBaseCorner, secondBaseCorner, tip],
52
+ strokeWidth: MARKER_STROKE_WIDTH,
53
+ strokeColor: MARKER_STROKE_COLOR,
54
+ fillColor: MARKER_FILL_COLOR,
55
+ isFilled: true,
56
+ })
57
+ }
58
+
59
+ export const convertBoxPortMarkers = ({
60
+ schematicPort,
61
+ edge,
62
+ }: {
63
+ schematicPort: SchematicPort
64
+ edge: Point
65
+ }): { markers: string[]; pinLineStart: Point } => {
66
+ const outward = getOutwardDirection(schematicPort.side_of_component)
67
+ if (!outward) return { markers: [], pinLineStart: edge }
68
+
69
+ const inversionOffset = schematicPort.is_drawn_with_inversion_circle
70
+ ? INVERSION_BUBBLE_RADIUS * 2
71
+ : 0
72
+ const pinLineStart = {
73
+ x: edge.x + outward.x * inversionOffset,
74
+ y: edge.y + outward.y * inversionOffset,
75
+ }
76
+ const markers: string[] = []
77
+
78
+ if (schematicPort.is_drawn_with_inversion_circle) {
79
+ markers.push(
80
+ formatElement("schematiccircle", {
81
+ center: {
82
+ x: edge.x + outward.x * INVERSION_BUBBLE_RADIUS,
83
+ y: edge.y + outward.y * INVERSION_BUBBLE_RADIUS,
84
+ },
85
+ radius: INVERSION_BUBBLE_RADIUS,
86
+ strokeWidth: MARKER_STROKE_WIDTH * 3,
87
+ color: MARKER_STROKE_COLOR,
88
+ fillColor: MARKER_FILL_COLOR,
89
+ isFilled: true,
90
+ }),
91
+ )
92
+ }
93
+ if (schematicPort.has_input_arrow) {
94
+ markers.push(
95
+ createArrowPath({
96
+ tip: pinLineStart,
97
+ direction: { x: -outward.x, y: -outward.y },
98
+ }),
99
+ )
100
+ }
101
+ if (schematicPort.has_output_arrow) {
102
+ const separation = schematicPort.has_input_arrow ? ARROW_AXIAL_LENGTH : 0
103
+ markers.push(
104
+ createArrowPath({
105
+ tip: {
106
+ x: pinLineStart.x + outward.x * (separation + ARROW_SIZE),
107
+ y: pinLineStart.y + outward.y * (separation + ARROW_SIZE),
108
+ },
109
+ direction: outward,
110
+ }),
111
+ )
112
+ }
113
+ return { markers, pinLineStart }
114
+ }
@@ -0,0 +1,15 @@
1
+ import type { SchematicPort, SourcePort } from "circuit-json"
2
+
3
+ export const getBoxPinDisplayLabel = ({
4
+ schematicPort,
5
+ sourcePort,
6
+ }: {
7
+ schematicPort: SchematicPort
8
+ sourcePort: SourcePort | null
9
+ }): string | undefined => {
10
+ if (schematicPort.display_pin_label !== undefined)
11
+ return schematicPort.display_pin_label
12
+
13
+ const sourceName = sourcePort?.name
14
+ return sourceName && !/^\d+$/.test(sourceName) ? sourceName : undefined
15
+ }
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "./cli": "./dist/cli/main.js"
7
7
  },
8
8
  "type": "module",
9
- "version": "0.0.72",
9
+ "version": "0.0.74",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
@@ -0,0 +1,103 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { CircuitJson } from "circuit-json"
3
+ import { convertCircuitJsonToTscircuit } from "lib"
4
+ import { runTscircuitCode } from "tscircuit"
5
+
6
+ test("preserves input and output arrows on imported box pins", async () => {
7
+ const circuitJson: CircuitJson = [
8
+ {
9
+ type: "pcb_board",
10
+ pcb_board_id: "board",
11
+ center: { x: 0, y: 0 },
12
+ width: 10,
13
+ height: 10,
14
+ num_layers: 2,
15
+ thickness: 1.6,
16
+ material: "fr4",
17
+ },
18
+ {
19
+ type: "schematic_component",
20
+ schematic_component_id: "controller",
21
+ center: { x: 0, y: 0 },
22
+ size: { width: 2, height: 2 },
23
+ is_box_with_pins: true,
24
+ },
25
+ {
26
+ type: "schematic_port",
27
+ schematic_port_id: "output",
28
+ source_port_id: "output_source",
29
+ schematic_component_id: "controller",
30
+ center: { x: -1.4, y: 0 },
31
+ side_of_component: "left",
32
+ facing_direction: "left",
33
+ has_output_arrow: true,
34
+ is_drawn_with_inversion_circle: true,
35
+ pin_number: 1,
36
+ },
37
+ {
38
+ type: "schematic_port",
39
+ schematic_port_id: "input",
40
+ source_port_id: "input_source",
41
+ schematic_component_id: "controller",
42
+ center: { x: 1.4, y: 0.4 },
43
+ side_of_component: "right",
44
+ facing_direction: "right",
45
+ has_input_arrow: true,
46
+ is_drawn_with_inversion_circle: true,
47
+ pin_number: 2,
48
+ },
49
+ ]
50
+ const renderedCircuitJson = await runTscircuitCode(
51
+ convertCircuitJsonToTscircuit(circuitJson, {
52
+ componentName: "ArrowPinBoard",
53
+ }),
54
+ )
55
+ const arrowPaths = renderedCircuitJson.flatMap((element) =>
56
+ element.type === "schematic_path" &&
57
+ element.is_filled &&
58
+ element.fill_color === "#ffffff"
59
+ ? [element]
60
+ : [],
61
+ )
62
+
63
+ expect(arrowPaths).toHaveLength(2)
64
+ expect(
65
+ arrowPaths.some((path) =>
66
+ path.points.some(
67
+ (point) => Math.abs(point.x + 1.22) < 0.000001 && point.y === 0,
68
+ ),
69
+ ),
70
+ ).toBe(true)
71
+ expect(
72
+ arrowPaths.some((path) =>
73
+ path.points.some(
74
+ (point) =>
75
+ Math.abs(point.x - 1.12) < 0.000001 &&
76
+ Math.abs(point.y - 0.4) < 0.000001,
77
+ ),
78
+ ),
79
+ ).toBe(true)
80
+
81
+ const inversionCircles = renderedCircuitJson.flatMap((element) =>
82
+ element.type === "schematic_circle" &&
83
+ element.is_filled &&
84
+ element.fill_color === "#ffffff" &&
85
+ element.radius === 0.06
86
+ ? [element]
87
+ : [],
88
+ )
89
+ expect(inversionCircles).toHaveLength(2)
90
+ expect(inversionCircles.map((circle) => circle.center.x).sort()).toEqual([
91
+ -1.06, 1.06,
92
+ ])
93
+
94
+ const shiftedPinLines = renderedCircuitJson.flatMap((element) =>
95
+ element.type === "schematic_line" &&
96
+ ((Math.abs(element.x1 + 1.12) < 0.000001 && element.y1 === 0) ||
97
+ (Math.abs(element.x1 - 1.12) < 0.000001 &&
98
+ Math.abs(element.y1 - 0.4) < 0.000001))
99
+ ? [element]
100
+ : [],
101
+ )
102
+ expect(shiftedPinLines).toHaveLength(2)
103
+ })
@@ -0,0 +1,72 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { CircuitJson } from "circuit-json"
3
+ import { convertCircuitJsonToTscircuit } from "lib"
4
+ import { runTscircuitCode } from "tscircuit"
5
+
6
+ test("does not duplicate numeric box pin names", async () => {
7
+ const circuitJson: CircuitJson = [
8
+ {
9
+ type: "pcb_board",
10
+ pcb_board_id: "board",
11
+ center: { x: 0, y: 0 },
12
+ width: 10,
13
+ height: 10,
14
+ num_layers: 2,
15
+ thickness: 1.6,
16
+ material: "fr4",
17
+ },
18
+ {
19
+ type: "schematic_component",
20
+ schematic_component_id: "net_tie",
21
+ center: { x: 0, y: 0 },
22
+ size: { width: 0.4, height: 0.4 },
23
+ is_box_with_pins: true,
24
+ },
25
+ {
26
+ type: "source_port",
27
+ source_port_id: "net_tie_left_source",
28
+ name: "1",
29
+ pin_number: 1,
30
+ },
31
+ {
32
+ type: "source_port",
33
+ source_port_id: "net_tie_right_source",
34
+ name: "2",
35
+ pin_number: 2,
36
+ },
37
+ {
38
+ type: "schematic_port",
39
+ schematic_port_id: "net_tie_left",
40
+ source_port_id: "net_tie_left_source",
41
+ schematic_component_id: "net_tie",
42
+ center: { x: -0.4, y: 0 },
43
+ side_of_component: "left",
44
+ facing_direction: "left",
45
+ pin_number: 1,
46
+ },
47
+ {
48
+ type: "schematic_port",
49
+ schematic_port_id: "net_tie_right",
50
+ source_port_id: "net_tie_right_source",
51
+ schematic_component_id: "net_tie",
52
+ center: { x: 0.4, y: 0 },
53
+ side_of_component: "right",
54
+ facing_direction: "right",
55
+ pin_number: 2,
56
+ },
57
+ ]
58
+ const renderedCircuitJson = await runTscircuitCode(
59
+ convertCircuitJsonToTscircuit(circuitJson, {
60
+ componentName: "NetTieBoard",
61
+ }),
62
+ )
63
+ const numericTexts = renderedCircuitJson
64
+ .flatMap((element) =>
65
+ element.type === "schematic_text" && /^[12]$/.test(element.text)
66
+ ? [element.text]
67
+ : [],
68
+ )
69
+ .sort()
70
+
71
+ expect(numericTexts).toEqual(["1", "2"])
72
+ })