@tscircuit/schematic-viewer 2.0.24 → 2.0.25

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.
@@ -0,0 +1,81 @@
1
+ import { circuitJsonToSpice } from "circuit-json-to-spice"
2
+ import type { CircuitJson } from "circuit-json"
3
+
4
+ export const getSpiceFromCircuitJson = (circuitJson: CircuitJson): string => {
5
+ const spiceNetlist = circuitJsonToSpice(circuitJson as any)
6
+ const baseSpiceString = spiceNetlist.toSpiceString()
7
+
8
+ const lines = baseSpiceString.split("\n").filter((l) => l.trim() !== "")
9
+ const componentLines = lines.filter(
10
+ (l) => !l.startsWith("*") && !l.startsWith(".") && l.trim() !== "",
11
+ )
12
+
13
+ const allNodes = new Set<string>()
14
+ const capacitorNodes = new Set<string>()
15
+
16
+ for (const line of componentLines) {
17
+ const parts = line.trim().split(/\s+/)
18
+ if (parts.length < 3) continue
19
+
20
+ const componentType = parts[0][0].toUpperCase()
21
+ let nodesOnLine: string[] = []
22
+
23
+ if (["R", "C", "L", "V", "I", "D"].includes(componentType)) {
24
+ nodesOnLine = parts.slice(1, 3)
25
+ } else if (componentType === "Q" && parts.length >= 4) {
26
+ // BJT
27
+ nodesOnLine = parts.slice(1, 4)
28
+ } else if (componentType === "M" && parts.length >= 5) {
29
+ // MOSFET
30
+ nodesOnLine = parts.slice(1, 5)
31
+ } else if (componentType === "X") {
32
+ // Subcircuit
33
+ // Assume last part is model name, everything in between is a node
34
+ nodesOnLine = parts.slice(1, -1)
35
+ } else {
36
+ continue
37
+ }
38
+
39
+ nodesOnLine.forEach((node) => allNodes.add(node))
40
+
41
+ if (componentType === "C") {
42
+ nodesOnLine.forEach((node) => capacitorNodes.add(node))
43
+ }
44
+ }
45
+
46
+ // Do not probe/set IC for ground
47
+ allNodes.delete("0")
48
+ capacitorNodes.delete("0")
49
+
50
+ const icLines = Array.from(capacitorNodes).map((node) => `.ic V(${node})=0`)
51
+
52
+ const probeNodes = Array.from(allNodes).map((node) => `V(${node})`)
53
+ const probeLine =
54
+ probeNodes.length > 0 ? `.probe ${probeNodes.join(" ")}` : ""
55
+
56
+ const tranLine = ".tran 0.1ms 50ms UIC"
57
+
58
+ const endStatement = ".end"
59
+ const originalLines = baseSpiceString.split("\n")
60
+ let endIndex = -1
61
+ for (let i = originalLines.length - 1; i >= 0; i--) {
62
+ if (originalLines[i].trim().toLowerCase().startsWith(endStatement)) {
63
+ endIndex = i
64
+ break
65
+ }
66
+ }
67
+
68
+ const injectionLines = [...icLines, probeLine, tranLine].filter(Boolean)
69
+
70
+ let finalLines: string[]
71
+
72
+ if (endIndex !== -1) {
73
+ const beforeEnd = originalLines.slice(0, endIndex)
74
+ const endLineAndAfter = originalLines.slice(endIndex)
75
+ finalLines = [...beforeEnd, ...injectionLines, ...endLineAndAfter]
76
+ } else {
77
+ finalLines = [...originalLines, ...injectionLines, endStatement]
78
+ }
79
+
80
+ return finalLines.join("\n")
81
+ }
@@ -1,5 +1,6 @@
1
1
  export const zIndexMap = {
2
2
  schematicEditIcon: 50,
3
3
  schematicGridIcon: 49,
4
+ spiceSimulationIcon: 51,
4
5
  clickToInteractOverlay: 100,
5
6
  }
@@ -0,0 +1,51 @@
1
+ import type * as EecircuitEngine from "../types/eecircuit-engine"
2
+
3
+ let sim: EecircuitEngine.Simulation | null = null
4
+
5
+ const fetchSimulation = async (): Promise<
6
+ typeof EecircuitEngine.Simulation
7
+ > => {
8
+ const module = await import(
9
+ // @ts-ignore
10
+ "https://cdn.jsdelivr.net/npm/eecircuit-engine@1.5.2/+esm"
11
+ )
12
+ return module.Simulation as typeof EecircuitEngine.Simulation
13
+ }
14
+
15
+ const initializeSimulation = async () => {
16
+ if (sim && sim.isInitialized()) return
17
+ const Simulation = await fetchSimulation()
18
+ sim = new Simulation()
19
+ await sim.start()
20
+ }
21
+
22
+ self.onmessage = async (event: MessageEvent<{ spiceString: string }>) => {
23
+ try {
24
+ await initializeSimulation()
25
+ if (!sim) throw new Error("Simulation not initialized")
26
+
27
+ let engineSpiceString = event.data.spiceString
28
+ const wrdataMatch = engineSpiceString.match(/wrdata\s+(\S+)\s+(.*)/i)
29
+ if (wrdataMatch) {
30
+ const variables = wrdataMatch[2].trim().split(/\s+/)
31
+ const probeLine = `.probe ${variables.join(" ")}`
32
+ engineSpiceString = engineSpiceString.replace(/wrdata.*/i, probeLine)
33
+ } else if (!engineSpiceString.match(/\.probe/i)) {
34
+ const plotMatch = engineSpiceString.match(/plot\s+(.*)/i)
35
+ if (plotMatch) {
36
+ throw new Error(
37
+ "The 'plot' command is not supported for data extraction. Please use 'wrdata <filename> <var1> ...' or '.probe <var1> ...' instead.",
38
+ )
39
+ }
40
+ throw new Error(
41
+ "No '.probe' or 'wrdata' command found in SPICE file. Use 'wrdata <filename> <var1> ...' to specify output.",
42
+ )
43
+ }
44
+
45
+ sim.setNetList(engineSpiceString)
46
+ const result = await sim.runSim()
47
+ self.postMessage({ type: "result", result })
48
+ } catch (e: any) {
49
+ self.postMessage({ type: "error", error: e.message })
50
+ }
51
+ }
package/package.json CHANGED
@@ -1,27 +1,27 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-viewer",
3
- "version": "2.0.24",
3
+ "version": "2.0.25",
4
4
  "main": "dist/index.js",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
8
- "build": "tsup-node ./lib/index.ts --dts --format esm --sourcemap",
8
+ "build:webworker": "tsup --config tsup-webworker.config.ts",
9
+ "build:blob-url": "bun scripts/build-worker-blob-url.ts",
10
+ "build": "bun run build:webworker && bun run build:blob-url && tsup-node ./lib/index.ts --dts --format esm --sourcemap",
9
11
  "build:site": "cosmos-export",
10
- "vercel-build": "bun run build:site",
12
+ "vercel-build": "bun run build:webworker && bun run build:blob-url && bun run build:site",
11
13
  "format": "biome format --write .",
12
14
  "format:check": "biome format ."
13
15
  },
14
16
  "devDependencies": {
15
17
  "@biomejs/biome": "^1.9.4",
16
- "@tscircuit/core": "^0.0.362",
17
- "@tscircuit/props": "^0.0.172",
18
+ "tscircuit": "^0.0.580",
18
19
  "@types/bun": "latest",
19
20
  "@types/debug": "^4.1.12",
20
21
  "@types/react": "^19.0.1",
21
22
  "@types/react-dom": "^19.0.2",
23
+ "@types/recharts": "^2.0.1",
22
24
  "@vitejs/plugin-react": "^4.3.4",
23
- "circuit-json": "^0.0.154",
24
- "circuit-to-svg": "^0.0.155",
25
25
  "react": "18",
26
26
  "react-cosmos": "^6.2.1",
27
27
  "react-cosmos-plugin-vite": "^6.2.0",
@@ -32,13 +32,14 @@
32
32
  },
33
33
  "peerDependencies": {
34
34
  "typescript": "^5.0.0",
35
- "@tscircuit/core": "*",
36
- "@tscircuit/props": "*",
37
- "circuit-to-svg": "*"
35
+ "tscircuit": "*"
38
36
  },
39
37
  "dependencies": {
38
+ "chart.js": "^4.5.0",
39
+ "circuit-json-to-spice": "^0.0.9",
40
40
  "debug": "^4.4.0",
41
41
  "performance-now": "^2.1.0",
42
+ "react-chartjs-2": "^5.3.0",
42
43
  "use-mouse-matrix-transform": "^1.2.2"
43
44
  }
44
45
  }
@@ -0,0 +1,55 @@
1
+ import fs from "fs/promises"
2
+ import path from "path"
3
+
4
+ const workerInputPath = path.resolve(
5
+ process.cwd(),
6
+ "dist/workers/spice-simulation.worker.js",
7
+ )
8
+ const workerOutputPath = path.resolve(
9
+ process.cwd(),
10
+ "lib/workers/spice-simulation.worker.blob.js",
11
+ )
12
+
13
+ const build = async () => {
14
+ try {
15
+ await fs.mkdir(path.dirname(workerOutputPath), { recursive: true })
16
+ const workerCode = await fs.readFile(workerInputPath, "utf-8")
17
+
18
+ const base64 = Buffer.from(workerCode).toString("base64")
19
+
20
+ const blobUrlModule = `// This file is generated by scripts/build-worker-blob-url.ts
21
+ // Do not edit this file directly.
22
+
23
+ const b64 = "${base64}";
24
+
25
+ let blobUrl = null;
26
+
27
+ export const getSpiceSimulationWorkerBlobUrl = () => {
28
+ if (typeof window === "undefined") return null;
29
+ if (blobUrl) return blobUrl;
30
+
31
+ try {
32
+ const blob = new Blob([atob(b64)], { type: "application/javascript" });
33
+ blobUrl = URL.createObjectURL(blob);
34
+ return blobUrl;
35
+ } catch (e) {
36
+ console.error("Failed to create blob URL for worker", e);
37
+ return null;
38
+ }
39
+ };
40
+ `
41
+
42
+ await fs.writeFile(workerOutputPath, blobUrlModule)
43
+ console.log(`Wrote worker blob URL module to ${workerOutputPath}`)
44
+ } catch (e: any) {
45
+ if (e.code === "ENOENT") {
46
+ console.error(`Error: Worker file not found at ${workerInputPath}`)
47
+ console.error("Please run 'bun run build:webworker' first.")
48
+ } else {
49
+ console.error("Failed to build worker blob URL", e)
50
+ }
51
+ process.exit(1)
52
+ }
53
+ }
54
+
55
+ build()
@@ -0,0 +1,13 @@
1
+ import { defineConfig } from "tsup"
2
+
3
+ export default defineConfig({
4
+ entry: ["lib/workers/spice-simulation.worker.ts"],
5
+ outDir: "dist/workers",
6
+ format: ["esm"],
7
+ platform: "browser",
8
+ dts: false,
9
+ splitting: false,
10
+ sourcemap: false,
11
+ clean: true,
12
+ minify: true,
13
+ })