@tscircuit/cli 0.1.2287 → 0.1.2288

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,352 @@
1
+ """Read Palace phasors, normalize port basis excitations, and sample ground only."""
2
+
3
+ import argparse
4
+ import csv
5
+ import hashlib
6
+ import json
7
+ import math
8
+ import re
9
+ import xml.etree.ElementTree as ET
10
+ from pathlib import Path
11
+
12
+ import numpy as np
13
+ import vtk
14
+ from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy
15
+ from surface_sample import sample_surface
16
+
17
+ # Keep VTK sampling deterministic and avoid oversubscribing FEM jobs.
18
+ vtk.vtkSMPTools.Initialize(1)
19
+
20
+
21
+ def digest(path):
22
+ return hashlib.sha256(path.read_bytes()).hexdigest()
23
+
24
+
25
+ def read_ports(path):
26
+ with path.open() as stream:
27
+ rows = list(csv.DictReader(stream, skipinitialspace=True))
28
+ return [
29
+ {
30
+ key.strip(): (float(number) if number.strip() != "NULL" else None)
31
+ for key, number in row.items()
32
+ }
33
+ for row in rows
34
+ ]
35
+
36
+
37
+ def read_ground(path):
38
+ # Read piece files directly. vtkXMLPUnstructuredGridReader in VTK 9.3 can
39
+ # expose invalid shared arrays to extract filters for Palace's output.
40
+ pieces = [
41
+ path.parent / piece.attrib["Source"] for piece in ET.parse(path).iter("Piece")
42
+ ]
43
+ readers = []
44
+ append = vtk.vtkAppendFilter()
45
+ for piece in pieces:
46
+ reader = vtk.vtkXMLUnstructuredGridReader()
47
+ reader.SetFileName(str(piece))
48
+ reader.Update()
49
+ if reader.GetOutput().GetNumberOfCells() == 0:
50
+ raise ValueError(f"Empty Palace volume piece: {piece}")
51
+ readers.append(reader)
52
+ append.AddInputData(reader.GetOutput())
53
+ if not readers:
54
+ raise ValueError("Palace data.pvtu contains no piece files")
55
+ if len(readers) == 1:
56
+ mesh = readers[0].GetOutput()
57
+ else:
58
+ append.Update()
59
+ mesh = append.GetOutput()
60
+ attributes = vtk_to_numpy(mesh.GetCellData().GetArray("attribute"))
61
+ copper_cells = np.flatnonzero(attributes == 3)
62
+ ground_cells = vtk.vtkIdList()
63
+ for index in copper_cells:
64
+ # Probe coordinates, not vertex heights, select bottom foil. A plated
65
+ # barrel can share valid copper tetrahedra across z=0; excluding those
66
+ # loses real bottom-plane samples around a ground via.
67
+ ground_cells.InsertNextId(int(index))
68
+ if ground_cells.GetNumberOfIds() == 0:
69
+ raise ValueError("Palace output has no bottom copper volume cells")
70
+ extract = vtk.vtkExtractCells()
71
+ extract.SetInputData(mesh)
72
+ extract.SetCellList(ground_cells)
73
+ extract.Update()
74
+ return extract.GetOutput()
75
+
76
+
77
+ def probe_ground(ground, options):
78
+ coordinates = options["coordinates"]
79
+ model = options["model"]
80
+ electric_field_scale = options["electricFieldScale"]
81
+ locations = vtk.vtkPoints()
82
+ locations.SetData(numpy_to_vtk(coordinates, deep=True))
83
+ mesh = vtk.vtkPolyData()
84
+ mesh.SetPoints(locations)
85
+ probe = vtk.vtkProbeFilter()
86
+ probe.SetInputData(mesh)
87
+ probe.SetSourceData(ground)
88
+ probe.SetCellLocatorPrototype(vtk.vtkStaticCellLocator())
89
+ probe.SetComputeTolerance(False)
90
+ probe.SetTolerance(1e-5)
91
+ probe.Update()
92
+ sampled = probe.GetOutput().GetPointData()
93
+ valid = vtk_to_numpy(sampled.GetArray("vtkValidPointMask")).astype(bool)
94
+ if not valid.all():
95
+ raise ValueError(
96
+ f"{np.count_nonzero(~valid)} sample locations do not lie on Palace ground triangles"
97
+ )
98
+ real = vtk_to_numpy(sampled.GetArray("E_real"))
99
+ imag = vtk_to_numpy(sampled.GetArray("E_imag"))
100
+ if not np.isfinite(real).all() or not np.isfinite(imag).all():
101
+ raise ValueError("Non-finite Palace copper electric field")
102
+ # Ohmic J = sigma E, A/m² → A/mm². Integrate copper depth for K.
103
+ current = (
104
+ (real[:, :2] + 1j * imag[:, :2])
105
+ * electric_field_scale
106
+ * model["copperConductivity"]
107
+ / 1e6
108
+ )
109
+ current = current.reshape(5, -1, 2)
110
+ _, weights = np.polynomial.legendre.leggauss(5)
111
+ return np.einsum("d,dnc->nc", weights / 2, current) * model["copperThickness"]
112
+
113
+
114
+ def coordinate(number):
115
+ return f"{0 if number == 0 else number:.9f}"
116
+
117
+
118
+ def point(position):
119
+ return [coordinate(position["x"]), coordinate(position["y"])]
120
+
121
+
122
+ def geometry_signature(geometry):
123
+ signature = [
124
+ [point(position) for position in geometry["boardOutline"]],
125
+ [
126
+ [
127
+ [point(position) for position in region["outer"]],
128
+ [[point(position) for position in hole] for hole in [*region["holes"], *region.get("maskCutouts", [])]],
129
+ ]
130
+ for region in geometry["groundRegions"]
131
+ ],
132
+ [[point(position) for position in outline] for outline in geometry["cutouts"]],
133
+ [
134
+ [
135
+ [*point(route), coordinate(route["width"]), route["layer"]]
136
+ for route in signal["route"]
137
+ if route["route_type"] == "wire"
138
+ ]
139
+ for signal in geometry["signals"]
140
+ ],
141
+ *([geometry["physicalModelSignature"]] if geometry.get("physicalModelSignature") else []),
142
+ [
143
+ [
144
+ coordinate(excitation["current"]),
145
+ point(excitation["return_source"]),
146
+ point(excitation["return_sink"]),
147
+ *([[excitation.get("source_port", {}).get("reference_layer", "bottom"), coordinate(excitation.get("source_port", {}).get("resistance", 50)), excitation.get("load_port", {}).get("reference_layer", "bottom"), coordinate(excitation.get("load_port", {}).get("resistance", 50))]] if excitation.get("source_port") or excitation.get("load_port") else []),
148
+ ]
149
+ for excitation in geometry["excitations"]
150
+ ],
151
+ ]
152
+ return json.dumps(signature, separators=(",", ":"))
153
+
154
+
155
+ def serialize_currents(currents):
156
+ return [
157
+ {"real": float(current.real), "imag": float(current.imag)}
158
+ for current in currents
159
+ ]
160
+
161
+
162
+ def sample_case(case):
163
+ model = json.loads((case / "model.json").read_text())
164
+ grid = json.loads((case / "sample-grid.json").read_text())
165
+ config = json.loads((case / "palace.json").read_text())
166
+ log = (case / "palace.log").read_text()
167
+ version = re.search(r"Git changeset ID:\s*(\S+)", log)
168
+ if not version or version[1] != "v0.14.0":
169
+ raise ValueError(
170
+ "This importer requires Palace v0.14.0, whose ParaView fields are nondimensional; other versions require an audited units adapter"
171
+ )
172
+ if not re.search(r"^Total\s+[0-9.]+\s+[0-9.]+\s+[0-9.]+", log, re.MULTILINE):
173
+ raise ValueError("Only completed Palace runs can be imported")
174
+ if re.search(r"solver.*(?:did not|failed to).*converge", log, re.IGNORECASE):
175
+ raise ValueError("Palace linear solver failed to converge")
176
+ postpro = case / config["Problem"]["Output"]
177
+ rows = read_ports(postpro / "port-I.csv")
178
+ excitations = model["geometry"]["excitations"]
179
+ count = len(excitations)
180
+ if len(rows) != 1:
181
+ raise ValueError(
182
+ "Expected exactly one frequency row containing all port basis solutions"
183
+ )
184
+ # Palace reports V/R, the current in each port's termination, not the net
185
+ # current injected into the PCB. A driven port also has a Norton source
186
+ # of 2*I_inc. Thus I_into_PCB = 2*I_inc - I_termination at active ports.
187
+ source_matrix = np.zeros((count, count), dtype=complex)
188
+ load_matrix = np.zeros_like(source_matrix)
189
+ fields = []
190
+ surface_receipts = []
191
+ outline = model["geometry"]["boardOutline"]
192
+ width = (
193
+ max(p["x"] for p in outline)
194
+ - min(p["x"] for p in outline)
195
+ + 2 * model["airPadding"]
196
+ )
197
+ height = (
198
+ max(p["y"] for p in outline)
199
+ - min(p["y"] for p in outline)
200
+ + 2 * model["airPadding"]
201
+ )
202
+ depth = model["layerSeparation"] + 2 * model["airPadding"]
203
+ characteristic_length_m = (
204
+ config["Model"].get("Lc", max(width, height, depth)) * config["Model"]["L0"]
205
+ )
206
+ impedance_free_space = math.sqrt((4e-7 * math.pi) / 8.8541878176e-12)
207
+ electric_field_scale = impedance_free_space**0.5 / characteristic_length_m
208
+ surface_impedance = model["copperModel"] == "surface_impedance_copper"
209
+ surface_current_scale = 1 / (impedance_free_space**0.5 * characteristic_length_m * 1000)
210
+ coordinates = np.array([[point["x"], point["y"]] for point in grid["points"]])
211
+ depth_nodes, _ = np.polynomial.legendre.leggauss(5)
212
+ depths = (depth_nodes - 1) / 2 * model["copperThickness"]
213
+ if model.get("multilayer"):
214
+ foil = next(layer for layer in model["multilayer"]["stackup"]["copperLayers"]
215
+ if layer["name"] == model["multilayer"]["sampleLayer"])
216
+ depths = foil["zMin"] + (depth_nodes + 1) / 2 * (foil["zMax"] - foil["zMin"])
217
+ volume_coordinates = np.concatenate(
218
+ [
219
+ np.column_stack([coordinates, np.full(len(coordinates), depth)])
220
+ for depth in depths
221
+ ]
222
+ )
223
+ for basis in range(count):
224
+ row = rows[0]
225
+ excitation_index = 2 * basis + 1
226
+ if (
227
+ abs(row["f (GHz)"] * 1e9 - model["frequencyHz"])
228
+ > model["frequencyHz"] * 1e-8
229
+ ):
230
+ raise ValueError("Palace output frequency does not match the model")
231
+ for index in range(count):
232
+ source_index, load_index = 2 * index + 1, 2 * index + 2
233
+ source_label = (
234
+ f"[{source_index}][{excitation_index}]"
235
+ if count > 1
236
+ else f"[{source_index}]"
237
+ )
238
+ load_label = (
239
+ f"[{load_index}][{excitation_index}]"
240
+ if count > 1
241
+ else f"[{load_index}]"
242
+ )
243
+ incident_label = (
244
+ f"[{source_index}][{excitation_index}]"
245
+ if count > 1
246
+ else f"[{source_index}]"
247
+ )
248
+ termination = complex(
249
+ row[f"Re{{I{source_label}}} (A)"], row[f"Im{{I{source_label}}} (A)"]
250
+ )
251
+ incident = (
252
+ row.get(f"I_inc{incident_label} (A)", 0.0) if index == basis else 0.0
253
+ )
254
+ source_matrix[index, basis] = 2 * incident - termination
255
+ load_matrix[index, basis] = complex(
256
+ row[f"Re{{I{load_label}}} (A)"], row[f"Im{{I{load_label}}} (A)"]
257
+ )
258
+ boundary = postpro / "paraview" / ("driven_boundary" if surface_impedance else "driven")
259
+ if count > 1:
260
+ boundary = boundary / f"excitation_{excitation_index}"
261
+ path = boundary / "Cycle000001" / "data.pvtu"
262
+ fields.append(sample_surface(path, coordinates, model, surface_current_scale, surface_receipts) if surface_impedance else
263
+ probe_ground(
264
+ read_ground(path),
265
+ {
266
+ "coordinates": volume_coordinates,
267
+ "model": model,
268
+ "electricFieldScale": electric_field_scale,
269
+ },
270
+ )
271
+ )
272
+ condition = float(np.linalg.cond(source_matrix))
273
+ if not np.isfinite(condition) or condition > 1e8:
274
+ raise ValueError("Source-current normalization is singular or ill-conditioned")
275
+ desired = np.array(
276
+ [excitation["current"] for excitation in excitations], dtype=complex
277
+ )
278
+ coefficients = np.linalg.solve(source_matrix, desired)
279
+ combined = np.einsum("b,bnc->nc", coefficients, np.stack(fields))
280
+ normalized_sources = source_matrix @ coefficients
281
+ normalized_loads = load_matrix @ coefficients
282
+ if not np.allclose(normalized_sources, desired, atol=1e-10, rtol=1e-10):
283
+ raise ValueError("Source-current normalization failed")
284
+ samples = [
285
+ {
286
+ "x": float(x),
287
+ "y": float(y),
288
+ "sheetCurrentXReal": float(current[0].real),
289
+ "sheetCurrentYReal": float(current[1].real),
290
+ "sheetCurrentXImag": float(current[0].imag),
291
+ "sheetCurrentYImag": float(current[1].imag),
292
+ }
293
+ for (x, y), current in zip(coordinates, combined)
294
+ ]
295
+ reference = {
296
+ "schemaVersion": 1,
297
+ **({"sampleLayer": model["multilayer"]["sampleLayer"]} if model.get("multilayer") else {}),
298
+ "solver": "palace",
299
+ "solverVersion": version[1],
300
+ "femOrder": model["order"],
301
+ "frequencyHz": model["frequencyHz"],
302
+ "copperModel": model["copperModel"],
303
+ **({"samplingMethod": "sum_foil_face_surface_currents", "surfaceCurrentScaleAmpsPerMm": surface_current_scale} if surface_impedance else {}),
304
+ "copperThickness": model["copperThickness"],
305
+ "layerSeparation": model["layerSeparation"],
306
+ **{key: grid[key] for key in ["cellWidth", "cellHeight", "columns", "rows"]},
307
+ "samples": samples,
308
+ "sourceCurrents": serialize_currents(normalized_sources),
309
+ "loadCurrents": serialize_currents(normalized_loads),
310
+ "normalizationConditionNumber": condition,
311
+ "electricFieldScaleVoltsPerMeter": electric_field_scale,
312
+ "provenance": {
313
+ "circuitSha256": digest(case / "circuit.json"),
314
+ "geometrySignature": geometry_signature(model["geometry"]),
315
+ "modelSha256": digest(case / "model.json"),
316
+ "meshSha256": digest(case / "mesh.msh"),
317
+ },
318
+ }
319
+ (case / "reference.json").write_text(
320
+ json.dumps(reference, separators=(",", ":"), allow_nan=False) + "\n"
321
+ )
322
+ (case / "normalization.json").write_text(
323
+ json.dumps(
324
+ {
325
+ "basisCoefficients": serialize_currents(coefficients),
326
+ "sourceCurrents": reference["sourceCurrents"],
327
+ "loadCurrents": reference["loadCurrents"],
328
+ "conditionNumber": condition,
329
+ },
330
+ indent=2,
331
+ )
332
+ + "\n"
333
+ )
334
+ if surface_impedance:
335
+ (case / "surface-sampling.json").write_text(json.dumps({
336
+ "copperModel": model["copperModel"],
337
+ "method": "sum_foil_face_surface_currents",
338
+ "normalConvention": "metal-outward normal crossed with H; no additional face sign flip",
339
+ "units": "A/mm, complex peak phasors after source-current basis normalization",
340
+ "bases": surface_receipts,
341
+ }, indent=2, allow_nan=False) + "\n")
342
+
343
+
344
+ def main():
345
+ parser = argparse.ArgumentParser()
346
+ parser.add_argument("case")
347
+ args = parser.parse_args()
348
+ sample_case(Path(args.case).resolve())
349
+
350
+
351
+ if __name__ == "__main__":
352
+ main()
@@ -0,0 +1,88 @@
1
+ """Import Palace's actual finite-conductivity boundary current coefficients."""
2
+ import xml.etree.ElementTree as ET
3
+ import numpy as np
4
+ import vtk
5
+ from vtk.util.numpy_support import numpy_to_vtk, vtk_to_numpy
6
+
7
+
8
+ def read_boundary(path):
9
+ pieces = [path.parent / piece.attrib["Source"] for piece in ET.parse(path).iter("Piece")]
10
+ readers, append = [], vtk.vtkAppendFilter()
11
+ append.MergePointsOff()
12
+ for piece in pieces:
13
+ reader = vtk.vtkXMLUnstructuredGridReader()
14
+ reader.SetFileName(str(piece))
15
+ reader.Update()
16
+ readers.append(reader)
17
+ append.AddInputData(reader.GetOutput())
18
+ if not readers:
19
+ raise ValueError("No Palace boundary pieces")
20
+ append.Update()
21
+ return append.GetOutput()
22
+
23
+
24
+ def probe_face(boundary, coordinates, z, receipt=None):
25
+ attribute = boundary.GetCellData().GetArray("attribute")
26
+ if attribute is None:
27
+ raise ValueError("Palace boundary has no material-face attributes")
28
+ ids = vtk.vtkIdList()
29
+ attributes = vtk_to_numpy(attribute)
30
+ face_heights = []
31
+ for index in np.flatnonzero(attributes == 11):
32
+ cell = boundary.GetCell(int(index))
33
+ if all(abs(cell.GetPoints().GetPoint(point)[2] - z) < 1e-7 for point in range(cell.GetNumberOfPoints())):
34
+ ids.InsertNextId(int(index))
35
+ face_heights.extend(cell.GetPoints().GetPoint(point)[2] for point in range(cell.GetNumberOfPoints()))
36
+ if ids.GetNumberOfIds() == 0:
37
+ if receipt is not None:
38
+ receipt.update({"physicalZMm": z, "triangles": 0, "validSamples": 0})
39
+ return np.zeros((len(coordinates), 2), dtype=complex), np.zeros(len(coordinates), dtype=bool)
40
+ extract = vtk.vtkExtractCells()
41
+ extract.SetInputData(boundary)
42
+ extract.SetCellList(ids)
43
+ extract.Update()
44
+ # Palace writes float32 VTK coordinates. For a flat triangle locator its
45
+ # bounding box can reject the exact physical double-precision Z before the
46
+ # probe tolerance is applied (e.g. -0.035 versus -0.035000000149). Probe the
47
+ # verified exported face plane, without moving XY or filling absent faces.
48
+ probe_z = float(np.mean(face_heights))
49
+ if max(face_heights) - min(face_heights) > 1e-10:
50
+ raise ValueError("Selected Palace foil face is not planar")
51
+ locations = vtk.vtkPoints()
52
+ locations.SetData(numpy_to_vtk(np.column_stack([coordinates, np.full(len(coordinates), probe_z)]), deep=True))
53
+ points = vtk.vtkPolyData()
54
+ points.SetPoints(locations)
55
+ probe = vtk.vtkProbeFilter()
56
+ probe.SetInputData(points)
57
+ probe.SetSourceData(extract.GetOutput())
58
+ probe.SetCellLocatorPrototype(vtk.vtkStaticCellLocator())
59
+ probe.SetComputeTolerance(False)
60
+ probe.SetTolerance(1e-5)
61
+ probe.Update()
62
+ data = probe.GetOutput().GetPointData()
63
+ valid = vtk_to_numpy(data.GetArray("vtkValidPointMask")).astype(bool)
64
+ real, imag = data.GetArray("J_s_real"), data.GetArray("J_s_imag")
65
+ if real is None or imag is None:
66
+ raise ValueError("Palace did not export J_s_real/J_s_imag finite-conductivity boundary currents")
67
+ current = vtk_to_numpy(real)[:, :2] + 1j * vtk_to_numpy(imag)[:, :2]
68
+ if not np.isfinite(current[valid]).all():
69
+ raise ValueError("Non-finite Palace surface current")
70
+ current[~valid] = 0
71
+ if receipt is not None:
72
+ receipt.update({"physicalZMm": z, "exportedZMm": probe_z, "triangles": ids.GetNumberOfIds(), "validSamples": int(np.count_nonzero(valid))})
73
+ return current, valid
74
+
75
+
76
+ def sample_surface(path, coordinates, model, scale_amps_per_mm, receipts=None):
77
+ boundary = read_boundary(path)
78
+ top_receipt, bottom_receipt = {}, {}
79
+ top, top_valid = probe_face(boundary, coordinates, 0, top_receipt)
80
+ bottom, bottom_valid = probe_face(boundary, coordinates, -model["copperThickness"], bottom_receipt)
81
+ if not (top_valid | bottom_valid).all():
82
+ raise ValueError(f"{np.count_nonzero(~(top_valid | bottom_valid))} declared conductor samples have no exposed foil face")
83
+ if receipts is not None:
84
+ receipts.append({"attribute": 11, "fieldArrays": ["J_s_real", "J_s_imag"], "scaleAmpsPerMm": scale_amps_per_mm, "declaredConductorSamples": len(coordinates), "bothFaces": int(np.count_nonzero(top_valid & bottom_valid)), "oneFace": int(np.count_nonzero(top_valid ^ bottom_valid)), "faces": [top_receipt, bottom_receipt]})
85
+ # J_s is n_out_of_metal × H, already signed on each exterior face. Sum the
86
+ # two physical currents; no extra normal/sign flip. A buried via junction
87
+ # may expose only one face and contributes only that physical face.
88
+ return (top + bottom) * scale_amps_per_mm
package/dist/lib/index.js CHANGED
@@ -69139,7 +69139,7 @@ var package_default = {
69139
69139
  "@tscircuit/krt-wasm": "^0.1.6",
69140
69140
  "@tscircuit/math-utils": "0.0.36",
69141
69141
  "@tscircuit/modelprinter": "^0.0.11",
69142
- "@tscircuit/props": "^0.0.694",
69142
+ "@tscircuit/props": "^0.0.698",
69143
69143
  "@tscircuit/runframe": "^0.0.2953",
69144
69144
  "@tscircuit/schematic-match-adapt": "^0.0.22",
69145
69145
  "@tscircuit/schematic-trace-solver": "^0.0.204",
@@ -69155,7 +69155,7 @@ var package_default = {
69155
69155
  "@types/semver": "^7.5.8",
69156
69156
  "bun-match-svg": "^0.0.12",
69157
69157
  chokidar: "4.0.1",
69158
- "circuit-json": "^0.0.520",
69158
+ "circuit-json": "^0.0.525",
69159
69159
  "circuit-json-to-3d-png": "^0.0.6",
69160
69160
  "circuit-json-to-altium": "github:tscircuit/circuit-json-to-altium#1a50d0dd2c0737cf940d92c0315852dc0111dd87",
69161
69161
  "circuit-json-to-bom-csv": "^0.0.19",
@@ -69171,7 +69171,7 @@ var package_default = {
69171
69171
  "circuit-json-to-tscircuit": "^0.0.50",
69172
69172
  "circuit-json-trace-length-analysis": "github:tscircuit/circuit-json-trace-length-analysis#2b44792a40df0ca83b6bfb6ac95ed5e35e7168b8",
69173
69173
  "circuit-to-canvas": "^0.0.123",
69174
- "circuit-to-svg": "^0.0.444",
69174
+ "circuit-to-svg": "^0.0.447",
69175
69175
  commander: "^14.0.0",
69176
69176
  conf: "^13.1.0",
69177
69177
  configstore: "^7.0.0",
@@ -69203,6 +69203,7 @@ var package_default = {
69203
69203
  redaxios: "^0.5.1",
69204
69204
  "schematic-symbols": "^0.0.246",
69205
69205
  semver: "^7.6.3",
69206
+ "simulate-return-current": "https://pkg.pr.new/tscircuit/simulate-return-current@6a7c84cb4f9f66c2cb54199aee50d3c7453748e8",
69206
69207
  spicets: "^0.0.4",
69207
69208
  stepts: "^0.0.3",
69208
69209
  tempy: "^3.1.0",
@@ -69212,7 +69213,9 @@ var package_default = {
69212
69213
  zod: "^3.23.8"
69213
69214
  },
69214
69215
  peerDependencies: {
69215
- "circuit-json": "^0.0.520",
69216
+ "@tscircuit/math-utils": "0.0.36",
69217
+ "circuit-json": "^0.0.525",
69218
+ "circuit-to-svg": "^0.0.447",
69216
69219
  tscircuit: "*"
69217
69220
  },
69218
69221
  bin: {
@@ -69229,10 +69232,11 @@ var package_default = {
69229
69232
  }
69230
69233
  },
69231
69234
  overrides: {
69232
- "circuit-json": "^0.0.520",
69235
+ "circuit-json": "^0.0.525",
69233
69236
  altiumts: "0.0.59",
69234
69237
  poppygl: "0.0.34",
69235
- "@tscircuit/core": "^0.0.2104"
69238
+ "@tscircuit/core": "^0.0.2121",
69239
+ "jscad-planner": "0.0.18"
69236
69240
  },
69237
69241
  scripts: {
69238
69242
  start: "bun run dev",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/cli",
3
- "version": "0.1.2287",
3
+ "version": "0.1.2288",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/tscircuit/cli"
@@ -27,7 +27,7 @@
27
27
  "@tscircuit/krt-wasm": "^0.1.6",
28
28
  "@tscircuit/math-utils": "0.0.36",
29
29
  "@tscircuit/modelprinter": "^0.0.11",
30
- "@tscircuit/props": "^0.0.694",
30
+ "@tscircuit/props": "^0.0.698",
31
31
  "@tscircuit/runframe": "^0.0.2953",
32
32
  "@tscircuit/schematic-match-adapt": "^0.0.22",
33
33
  "@tscircuit/schematic-trace-solver": "^0.0.204",
@@ -43,7 +43,7 @@
43
43
  "@types/semver": "^7.5.8",
44
44
  "bun-match-svg": "^0.0.12",
45
45
  "chokidar": "4.0.1",
46
- "circuit-json": "^0.0.520",
46
+ "circuit-json": "^0.0.525",
47
47
  "circuit-json-to-3d-png": "^0.0.6",
48
48
  "circuit-json-to-altium": "github:tscircuit/circuit-json-to-altium#1a50d0dd2c0737cf940d92c0315852dc0111dd87",
49
49
  "circuit-json-to-bom-csv": "^0.0.19",
@@ -59,7 +59,7 @@
59
59
  "circuit-json-to-tscircuit": "^0.0.50",
60
60
  "circuit-json-trace-length-analysis": "github:tscircuit/circuit-json-trace-length-analysis#2b44792a40df0ca83b6bfb6ac95ed5e35e7168b8",
61
61
  "circuit-to-canvas": "^0.0.123",
62
- "circuit-to-svg": "^0.0.444",
62
+ "circuit-to-svg": "^0.0.447",
63
63
  "commander": "^14.0.0",
64
64
  "conf": "^13.1.0",
65
65
  "configstore": "^7.0.0",
@@ -91,6 +91,7 @@
91
91
  "redaxios": "^0.5.1",
92
92
  "schematic-symbols": "^0.0.246",
93
93
  "semver": "^7.6.3",
94
+ "simulate-return-current": "https://pkg.pr.new/tscircuit/simulate-return-current@6a7c84cb4f9f66c2cb54199aee50d3c7453748e8",
94
95
  "spicets": "^0.0.4",
95
96
  "stepts": "^0.0.3",
96
97
  "tempy": "^3.1.0",
@@ -100,7 +101,9 @@
100
101
  "zod": "^3.23.8"
101
102
  },
102
103
  "peerDependencies": {
103
- "circuit-json": "^0.0.520",
104
+ "@tscircuit/math-utils": "0.0.36",
105
+ "circuit-json": "^0.0.525",
106
+ "circuit-to-svg": "^0.0.447",
104
107
  "tscircuit": "*"
105
108
  },
106
109
  "bin": {
@@ -117,10 +120,11 @@
117
120
  }
118
121
  },
119
122
  "overrides": {
120
- "circuit-json": "^0.0.520",
123
+ "circuit-json": "^0.0.525",
121
124
  "altiumts": "0.0.59",
122
125
  "poppygl": "0.0.34",
123
- "@tscircuit/core": "^0.0.2104"
126
+ "@tscircuit/core": "^0.0.2121",
127
+ "jscad-planner": "0.0.18"
124
128
  },
125
129
  "scripts": {
126
130
  "start": "bun run dev",