circuit-json-to-tscircuit 0.0.75 → 0.0.77

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.
@@ -493,7 +493,7 @@ var isNonCollinear = ({
493
493
  // lib/generate-board-schematic-tsx/convert-symbol-primitives.ts
494
494
  import { schematic_text as schematic_text2 } from "circuit-json";
495
495
  import { applyToPoint as applyToPoint4 } from "transformation-matrix";
496
- var DEFAULT_SYMBOL_CIRCLE_STROKE_WIDTH = 0.02;
496
+ var SYMBOL_OUTLINE_STROKE_WIDTH = 0.02;
497
497
  var anchors = {
498
498
  middle_top: "top_center",
499
499
  middle_bottom: "bottom_center",
@@ -518,7 +518,7 @@ var convertSymbolPrimitives = ({
518
518
  if (primitive.closed && points[0]) points.push(points[0]);
519
519
  return formatElement("schematicpath", {
520
520
  points,
521
- strokeWidth: primitive.strokeWidth,
521
+ strokeWidth: SYMBOL_OUTLINE_STROKE_WIDTH,
522
522
  isFilled: primitive.fill
523
523
  });
524
524
  }
@@ -546,7 +546,7 @@ var convertSymbolPrimitives = ({
546
546
  return formatElement("schematiccircle", {
547
547
  center,
548
548
  radius: Math.hypot(radiusVector.x, radiusVector.y),
549
- strokeWidth: DEFAULT_SYMBOL_CIRCLE_STROKE_WIDTH,
549
+ strokeWidth: SYMBOL_OUTLINE_STROKE_WIDTH,
550
550
  isFilled: primitive.fill
551
551
  });
552
552
  }
@@ -827,6 +827,13 @@ import { su as su4 } from "@tscircuit/soup-util";
827
827
  import { schematic_text as schematic_text3 } from "circuit-json";
828
828
  import { symbols as symbols2 } from "schematic-symbols";
829
829
  import { translate as translate3 } from "transformation-matrix";
830
+ function canRenderNativeNetLabel(name) {
831
+ const selector = `net.${name}`;
832
+ if (/net\.[^\s>]*\./.test(selector) || /net\.[^\s>]*[+-]/.test(selector) || /net\.[0-9]/.test(selector)) {
833
+ return false;
834
+ }
835
+ return /^\/?[A-Za-z0-9_]+$/.test(name);
836
+ }
830
837
  var convertNetLabels = (circuitJson) => su4(circuitJson).schematic_net_label.list().flatMap((schematicNetLabel) => {
831
838
  const anchorPosition = schematicNetLabel.anchor_position ?? schematicNetLabel.center;
832
839
  const symbolName = schematicNetLabel.symbol_name;
@@ -843,6 +850,16 @@ var convertNetLabels = (circuitJson) => su4(circuitJson).schematic_net_label.lis
843
850
  displayText: schematicNetLabel.text
844
851
  });
845
852
  }
853
+ if (!symbolName && schematicNetLabel.anchor_position && canRenderNativeNetLabel(schematicNetLabel.text)) {
854
+ return [
855
+ formatElement("netlabel", {
856
+ net: schematicNetLabel.text,
857
+ schX: anchorPosition.x,
858
+ schY: anchorPosition.y,
859
+ anchorSide: schematicNetLabel.anchor_side
860
+ })
861
+ ];
862
+ }
846
863
  return [
847
864
  formatElement("schematictext", {
848
865
  text: schematicNetLabel.text,
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-A65QMTEY.js";
4
+ } from "../chunk-35I53U4L.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.74",
19
+ version: "0.0.76",
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-A65QMTEY.js";
3
+ } from "../chunk-35I53U4L.js";
4
4
  export {
5
5
  convertCircuitJsonToTscircuit
6
6
  };
@@ -6,6 +6,23 @@ import type { SchematicElementConverter } from "./converter-types"
6
6
  import { convertSymbolPrimitives } from "./convert-symbol-primitives"
7
7
  import { formatElement } from "./format-attributes"
8
8
 
9
+ // Match core's preprocessSelector checks for net names. The validator is not
10
+ // exported by @tscircuit/core; runtime regression tests keep these in sync.
11
+ // https://github.com/tscircuit/core/blob/main/lib/components/base-components/PrimitiveComponent/preprocessSelector.ts
12
+ function canRenderNativeNetLabel(name: string): boolean {
13
+ const selector = `net.${name}`
14
+ if (
15
+ /net\.[^\s>]*\./.test(selector) ||
16
+ /net\.[^\s>]*[+-]/.test(selector) ||
17
+ /net\.[0-9]/.test(selector)
18
+ ) {
19
+ return false
20
+ }
21
+ // A label is one literal name, not a selector expression. Whitespace and CSS
22
+ // punctuation can parse as a different selection even when core accepts it.
23
+ return /^\/?[A-Za-z0-9_]+$/.test(name)
24
+ }
25
+
9
26
  export const convertNetLabels: SchematicElementConverter = (circuitJson) =>
10
27
  su(circuitJson)
11
28
  .schematic_net_label.list()
@@ -26,6 +43,23 @@ export const convertNetLabels: SchematicElementConverter = (circuitJson) =>
26
43
  displayText: schematicNetLabel.text,
27
44
  })
28
45
  }
46
+ // Native labels own their outline, typography and anchor-side layout.
47
+ // Core resolves net names through selectors; retain the text fallback
48
+ // for names that cannot safely be used by that runtime path.
49
+ if (
50
+ !symbolName &&
51
+ schematicNetLabel.anchor_position &&
52
+ canRenderNativeNetLabel(schematicNetLabel.text)
53
+ ) {
54
+ return [
55
+ formatElement("netlabel", {
56
+ net: schematicNetLabel.text,
57
+ schX: anchorPosition.x,
58
+ schY: anchorPosition.y,
59
+ anchorSide: schematicNetLabel.anchor_side,
60
+ }),
61
+ ]
62
+ }
29
63
  return [
30
64
  formatElement("schematictext", {
31
65
  text: schematicNetLabel.text,
@@ -3,9 +3,9 @@ import type { SchSymbol } from "schematic-symbols"
3
3
  import { applyToPoint, type Matrix } from "transformation-matrix"
4
4
  import { formatElement } from "./format-attributes"
5
5
 
6
- // Circuit JSON symbol primitives do not encode a stroke width. This matches
7
- // the component outline width used by circuit-to-svg.
8
- const DEFAULT_SYMBOL_CIRCLE_STROKE_WIDTH = 0.02
6
+ // circuit-to-svg renders library symbol paths and circles with a fixed
7
+ // component outline width, independent of library strokeWidth and symbol scale.
8
+ const SYMBOL_OUTLINE_STROKE_WIDTH = 0.02
9
9
 
10
10
  const anchors = {
11
11
  middle_top: "top_center",
@@ -38,7 +38,7 @@ export const convertSymbolPrimitives = ({
38
38
  if (primitive.closed && points[0]) points.push(points[0])
39
39
  return formatElement("schematicpath", {
40
40
  points,
41
- strokeWidth: primitive.strokeWidth,
41
+ strokeWidth: SYMBOL_OUTLINE_STROKE_WIDTH,
42
42
  isFilled: primitive.fill,
43
43
  })
44
44
  }
@@ -72,7 +72,7 @@ export const convertSymbolPrimitives = ({
72
72
  return formatElement("schematiccircle", {
73
73
  center,
74
74
  radius: Math.hypot(radiusVector.x, radiusVector.y),
75
- strokeWidth: DEFAULT_SYMBOL_CIRCLE_STROKE_WIDTH,
75
+ strokeWidth: SYMBOL_OUTLINE_STROKE_WIDTH,
76
76
  isFilled: primitive.fill,
77
77
  })
78
78
  }
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.75",
9
+ "version": "0.0.77",
10
10
  "repository": {
11
11
  "type": "git",
12
12
  "url": "https://github.com/tscircuit/circuit-json-to-tscircuit.git"
@@ -0,0 +1,86 @@
1
+ import { expect, test } from "bun:test"
2
+ import { convertNetLabels } from "../lib/generate-board-schematic-tsx/convert-net-labels"
3
+ import { runTscircuitCode } from "tscircuit"
4
+
5
+ function convertPlainNetLabel(text: string) {
6
+ return convertNetLabels([
7
+ {
8
+ type: "schematic_net_label",
9
+ schematic_net_label_id: "label",
10
+ source_net_id: "net",
11
+ text,
12
+ center: { x: 2, y: 3 },
13
+ anchor_position: { x: 2, y: 3 },
14
+ anchor_side: "left",
15
+ },
16
+ ]).join("\n")
17
+ }
18
+
19
+ for (const name of ["VCC-P", "VCC+P", "3V3", "1", "-VCC", "net.name"]) {
20
+ test(`conversion preserves text when core rejects ${name}`, async () => {
21
+ await expect(
22
+ runTscircuitCode(
23
+ `export default () => <board><netlabel net={${JSON.stringify(name)}} /></board>`,
24
+ ),
25
+ ).rejects.toThrow(/Net name/)
26
+ const generated = convertPlainNetLabel(name)
27
+ expect(generated).not.toContain("<netlabel")
28
+ const output = await runTscircuitCode(
29
+ `export default () => <board><symbol>${generated}</symbol></board>`,
30
+ )
31
+ expect(output).toEqual(
32
+ expect.arrayContaining([
33
+ expect.objectContaining({ type: "schematic_text", text: name }),
34
+ ]),
35
+ )
36
+ })
37
+ }
38
+
39
+ for (const name of ["SDA", "V3_3", "_RESET", "/RESET", "/3V3", "A1"]) {
40
+ test(`conversion preserves native labels accepted by core: ${name}`, async () => {
41
+ const source = await runTscircuitCode(
42
+ `export default () => <board><netlabel net={${JSON.stringify(name)}} schX={2} schY={3} anchorSide="left" /></board>`,
43
+ )
44
+ const generated = convertNetLabels(source).join("\n")
45
+ expect(generated).toContain("<netlabel")
46
+ const output = await runTscircuitCode(
47
+ `export default () => <board><symbol>${generated}</symbol></board>`,
48
+ )
49
+ expect(output).toEqual(
50
+ expect.arrayContaining([
51
+ expect.objectContaining({
52
+ type: "schematic_net_label",
53
+ text: name,
54
+ anchor_position: { x: 2, y: 3 },
55
+ anchor_side: "left",
56
+ }),
57
+ ]),
58
+ )
59
+ })
60
+ }
61
+
62
+ for (const name of [
63
+ "BUS/DATA",
64
+ "//RESET",
65
+ "HV VSYS",
66
+ "A>B",
67
+ "A,B",
68
+ "A[1]",
69
+ "A:B",
70
+ 'A"B',
71
+ "",
72
+ "電源",
73
+ ]) {
74
+ test(`conversion preserves selector-unsafe label text: ${name}`, async () => {
75
+ const generated = convertPlainNetLabel(name)
76
+ expect(generated).not.toContain("<netlabel")
77
+ const output = await runTscircuitCode(
78
+ `export default () => <board><symbol>${generated}</symbol></board>`,
79
+ )
80
+ expect(output).toEqual(
81
+ expect.arrayContaining([
82
+ expect.objectContaining({ type: "schematic_text", text: name }),
83
+ ]),
84
+ )
85
+ })
86
+ }
@@ -0,0 +1,82 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { CircuitJson } from "circuit-json"
3
+ import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg"
4
+ import { convertCircuitJsonToTscircuit } from "lib"
5
+ import { runTscircuitCode } from "tscircuit"
6
+
7
+ test("native plain labels preserve rendering on every connection side", async () => {
8
+ for (const side of ["left", "right", "top", "bottom"] as const) {
9
+ const input = await runTscircuitCode(`export default () => (
10
+ <board width={10} height={10}>
11
+ <netlabel net="DATA" schX={2} schY={3} anchorSide="${side}" />
12
+ </board>
13
+ )`)
14
+ const output = await runTscircuitCode(
15
+ convertCircuitJsonToTscircuit(input, { componentName: "LabelBoard" }),
16
+ )
17
+ const sourceLabels = input.filter((e) => e.type === "schematic_net_label")
18
+ const renderedLabels = output.filter(
19
+ (e) => e.type === "schematic_net_label",
20
+ )
21
+ expect(renderedLabels).toHaveLength(1)
22
+ const original = sourceLabels[0]!
23
+ expect(renderedLabels[0]).toMatchObject({
24
+ text: original.text,
25
+ anchor_side: original.anchor_side,
26
+ anchor_position: original.anchor_position,
27
+ center: original.center,
28
+ })
29
+ expect(
30
+ convertCircuitJsonToSchematicSvg(renderedLabels, {
31
+ includeVersion: false,
32
+ }),
33
+ ).toBe(
34
+ convertCircuitJsonToSchematicSvg(sourceLabels, { includeVersion: false }),
35
+ )
36
+ }
37
+ for (const name of [
38
+ "HV VSYS",
39
+ "BUS/DATA",
40
+ "net.name",
41
+ "",
42
+ 'A"B',
43
+ "CENTER_ONLY",
44
+ "VCC-P",
45
+ "VCC+P",
46
+ "3V3",
47
+ "1",
48
+ "-VCC",
49
+ ]) {
50
+ const input: CircuitJson = [
51
+ {
52
+ type: "pcb_board",
53
+ pcb_board_id: "board",
54
+ center: { x: 0, y: 0 },
55
+ width: 10,
56
+ height: 10,
57
+ num_layers: 2,
58
+ thickness: 1.6,
59
+ material: "fr4",
60
+ },
61
+ {
62
+ type: "schematic_net_label",
63
+ schematic_net_label_id: "label",
64
+ text: name,
65
+ center: { x: 2, y: 3 },
66
+ anchor_position: name === "CENTER_ONLY" ? undefined : { x: 2, y: 3 },
67
+ anchor_side: "left",
68
+ source_net_id: "net",
69
+ },
70
+ ]
71
+ const tsx = convertCircuitJsonToTscircuit(input, {
72
+ componentName: "Fallback",
73
+ })
74
+ expect(tsx).not.toContain("<netlabel")
75
+ const fallback = await runTscircuitCode(tsx)
76
+ expect(fallback).toEqual(
77
+ expect.arrayContaining([
78
+ expect.objectContaining({ type: "schematic_text", text: name }),
79
+ ]),
80
+ )
81
+ }
82
+ })
@@ -0,0 +1,91 @@
1
+ import { expect, test } from "bun:test"
2
+ import { readFileSync } from "node:fs"
3
+ import { gunzipSync } from "node:zlib"
4
+ import type { CircuitJson } from "circuit-json"
5
+ import { convertCircuitJsonToSchematicSvg } from "circuit-to-svg"
6
+ import { convertCircuitJsonToTscircuit } from "lib"
7
+ import { runTscircuitCode } from "tscircuit"
8
+ import { applyToPoint, compose, scale, translate } from "transformation-matrix"
9
+
10
+ // Compare stroke/segment-length ratios so SVG auto-fit does not affect the test.
11
+ const firstPathStrokeRatio = (svg: string) => {
12
+ const path = svg.match(/<path\b[^>]*\bd="M ([^\"]+)"[^>]*>/)?.[0]
13
+ if (!path) throw new Error("Missing rendered symbol path")
14
+ const coordinates = path
15
+ .match(/\bd="M ([^\"]+)"/)![1]!
16
+ .split(/[MLZ\s,]+/)
17
+ .filter(Boolean)
18
+ .map(Number)
19
+ const width = Number.parseFloat(path.match(/\bstroke-width="([^\"]+)"/)![1]!)
20
+ return (
21
+ width /
22
+ Math.hypot(
23
+ coordinates[2]! - coordinates[0]!,
24
+ coordinates[3]! - coordinates[1]!,
25
+ )
26
+ )
27
+ }
28
+
29
+ for (const [fixture, symbolName, scaleFactor] of [
30
+ ["drv8307evm", "boxresistor_up", 1],
31
+ ["lm251772evm-pd", "boxresistor_right", 2],
32
+ ["lm5155evm-fly", "capacitor_down", 0.5],
33
+ ["lmg342x-bb-evm", "capacitor_polarized_down", 1],
34
+ ["drv8307evm", "diode_down", 1],
35
+ ] as const) {
36
+ test(`${fixture} ${symbolName} keeps rendered outline width at scale ${scaleFactor}`, async () => {
37
+ const source = JSON.parse(
38
+ gunzipSync(
39
+ readFileSync(
40
+ new URL(
41
+ `./fixtures/ti-evms/${fixture}.circuit.json.gz`,
42
+ import.meta.url,
43
+ ),
44
+ ),
45
+ ).toString(),
46
+ ) as CircuitJson
47
+ const component = source.find(
48
+ (e) => e.type === "schematic_component" && e.symbol_name === symbolName,
49
+ )
50
+ if (component?.type !== "schematic_component")
51
+ throw new Error("Missing fixture symbol")
52
+ const subset = source.filter(
53
+ (e) =>
54
+ e.type === "pcb_board" ||
55
+ e === component ||
56
+ (e.type === "source_component" &&
57
+ e.source_component_id === component.source_component_id) ||
58
+ (e.type === "source_port" &&
59
+ e.source_component_id === component.source_component_id) ||
60
+ (e.type === "schematic_port" &&
61
+ e.schematic_component_id === component.schematic_component_id),
62
+ )
63
+ const schematicToScaledSchematicTransform = compose(
64
+ translate(component.center.x, component.center.y),
65
+ scale(scaleFactor),
66
+ translate(-component.center.x, -component.center.y),
67
+ )
68
+ const scaledSize = applyToPoint(scale(scaleFactor), {
69
+ x: component.size.width,
70
+ y: component.size.height,
71
+ })
72
+ component.size = { width: scaledSize.x, height: scaledSize.y }
73
+ for (const element of subset) {
74
+ if (element.type === "schematic_port") {
75
+ element.center = applyToPoint(
76
+ schematicToScaledSchematicTransform,
77
+ element.center,
78
+ )
79
+ }
80
+ }
81
+ const output = await runTscircuitCode(
82
+ convertCircuitJsonToTscircuit(subset, { componentName: "SymbolOutline" }),
83
+ )
84
+ expect(
85
+ firstPathStrokeRatio(convertCircuitJsonToSchematicSvg(output)),
86
+ ).toBeCloseTo(
87
+ firstPathStrokeRatio(convertCircuitJsonToSchematicSvg(subset)),
88
+ 8,
89
+ )
90
+ })
91
+ }