- scripts/create-spicebook-notebooks.py: Creates 8 circuit notebooks (555 timer, op-amp, comms, sensor) via SpiceBook REST API with full SPICE netlists and educational markdown - scripts/fix-opamp-notebooks.py: Patches 3 op-amp notebooks that fail due to missing LM741.MOD by inlining a behavioral op-amp subcircuit - docs/agent-threads/spicebook-embed-bugfixes/: 3-message coordination thread documenting the 4 embed bugs (postMessage type, waveform CSS vars, theme remount, light-mode overrides) and their verification
590 lines
24 KiB
Python
590 lines
24 KiB
Python
#!/usr/bin/env python3
|
|
"""Create the 9 missing SpiceBook notebooks for Mims library integration.
|
|
|
|
Uses the SpiceBook API to PUT notebooks with specific IDs matching
|
|
the Mims frontmatter references. After creation, runs each SPICE cell
|
|
and generates schematics.
|
|
|
|
Usage: python3 scripts/create-spicebook-notebooks.py [--api-url http://localhost:8099]
|
|
"""
|
|
|
|
import json
|
|
import sys
|
|
import time
|
|
import urllib.request
|
|
import urllib.error
|
|
from datetime import datetime, timezone
|
|
|
|
API_BASE = sys.argv[1] if len(sys.argv) > 1 else "http://localhost:8099"
|
|
NOW = datetime.now(timezone.utc).isoformat()
|
|
|
|
NOTEBOOKS = {
|
|
"555-astable-blinker": {
|
|
"metadata": {
|
|
"title": "555 Astable LED Blinker",
|
|
"engine": "ngspice",
|
|
"tags": ["555", "timer", "astable", "beginner"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# 555 Astable LED Blinker\n\n"
|
|
"The 555 timer in astable mode produces a continuous square wave "
|
|
"without any external trigger. Two resistors (Ra, Rb) and a capacitor (C1) "
|
|
"set the frequency and duty cycle.\n\n"
|
|
"- **Frequency:** f = 1.44 / ((Ra + 2*Rb) * C1)\n"
|
|
"- **Duty cycle:** D = (Ra + Rb) / (Ra + 2*Rb)\n\n"
|
|
"With Ra = 1k, Rb = 10k, C1 = 10uF:\n"
|
|
"f = 1.44 / ((1k + 20k) * 10u) = 6.86 Hz (~7 blinks/second)"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-astable",
|
|
"type": "spice",
|
|
"source": (
|
|
"555 Astable Multivibrator\n"
|
|
"* Power supply\n"
|
|
"VCC vcc 0 DC 9\n"
|
|
"* Timing resistors\n"
|
|
"Ra vcc dis 1k\n"
|
|
"Rb dis thr 10k\n"
|
|
"* Timing capacitor\n"
|
|
"C1 thr 0 10u IC=0\n"
|
|
"* 555 timer modeled with behavioral sources\n"
|
|
"* Comparator thresholds: 2/3 Vcc (upper), 1/3 Vcc (lower)\n"
|
|
"* SR flip-flop drives output\n"
|
|
"Bcomp out 0 V = V(vcc) * (V(thr) < V(vcc)*2/3 ? 1 : 0) * (V(thr) > V(vcc)/3 ? 1 : V(vcc) > 0 ? 1 : 0)\n"
|
|
"* Discharge transistor\n"
|
|
"Sdis dis 0 out 0 SWMOD\n"
|
|
".model SWMOD SW VT=4 VH=0.5 RON=10 ROFF=1G\n"
|
|
".tran 10u 0.5\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## How It Works\n\n"
|
|
"The capacitor C1 charges through Ra + Rb and discharges through Rb alone. "
|
|
"The 555's internal comparators trip at 1/3 and 2/3 of Vcc, toggling the "
|
|
"output flip-flop and the discharge transistor. The result is a square wave "
|
|
"at the output pin.\n\n"
|
|
"The LED (not shown in the SPICE model) would connect from the output "
|
|
"through a 330 ohm current-limiting resistor to ground."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"555-monostable-pulse": {
|
|
"metadata": {
|
|
"title": "555 Monostable Pulse Generator",
|
|
"engine": "ngspice",
|
|
"tags": ["555", "timer", "monostable", "beginner"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# 555 Monostable Pulse Generator\n\n"
|
|
"In monostable (one-shot) mode, the 555 produces a single timed pulse "
|
|
"when triggered by a falling edge on the trigger pin.\n\n"
|
|
"- **Pulse width:** T = 1.1 * R * C\n\n"
|
|
"With R = 100k and C = 10uF: T = 1.1 * 100k * 10u = 1.1 seconds"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-mono",
|
|
"type": "spice",
|
|
"source": (
|
|
"555 Monostable One-Shot\n"
|
|
"* Power supply\n"
|
|
"VCC vcc 0 DC 9\n"
|
|
"* Trigger pulse (brief low pulse at t=0.1s)\n"
|
|
"Vtrig trig 0 PULSE(9 0 0.1 1n 1n 1u 10)\n"
|
|
"* Timing components\n"
|
|
"R1 vcc thr 100k\n"
|
|
"C1 thr 0 10u IC=0\n"
|
|
"* Simplified 555 monostable behavior\n"
|
|
"* Output goes high on trigger, stays high until C1 charges to 2/3 Vcc\n"
|
|
"Bout out 0 V = V(vcc) * ( V(thr) < V(vcc)*2/3 ? (V(trig) < V(vcc)/3 ? 1 : 0) : 0 )\n"
|
|
"* Discharge switch\n"
|
|
"Sdis thr 0 out 0 SWMOD\n"
|
|
".model SWMOD SW VT=4 VH=0.5 RON=1G ROFF=10\n"
|
|
".tran 1m 3\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## How It Works\n\n"
|
|
"At rest, the output is low and the discharge transistor holds the "
|
|
"timing capacitor at 0V. When the trigger input drops below 1/3 Vcc, "
|
|
"the output goes high and the capacitor starts charging through R.\n\n"
|
|
"When the capacitor voltage reaches 2/3 Vcc, the threshold comparator "
|
|
"resets the flip-flop, the output goes low, and the discharge transistor "
|
|
"drains the capacitor. The circuit then waits for the next trigger."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"inverting-op-amp": {
|
|
"metadata": {
|
|
"title": "Inverting Op-Amp Amplifier",
|
|
"engine": "ngspice",
|
|
"tags": ["op-amp", "amplifier", "analog", "beginner"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# Inverting Op-Amp Amplifier\n\n"
|
|
"The inverting amplifier is a fundamental op-amp circuit. The input signal "
|
|
"connects through R1 to the inverting (-) input, and feedback resistor Rf "
|
|
"sets the gain.\n\n"
|
|
"- **Gain:** Av = -Rf / R1\n"
|
|
"- **Input impedance:** Zin = R1\n\n"
|
|
"With R1 = 10k and Rf = 47k: Av = -47k/10k = -4.7"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-ac",
|
|
"type": "spice",
|
|
"source": (
|
|
"Inverting Op-Amp Amplifier\n"
|
|
"* Dual power supply\n"
|
|
"VCC vcc 0 DC 12\n"
|
|
"VEE vee 0 DC -12\n"
|
|
"* Input signal (1kHz sine, 100mV amplitude)\n"
|
|
"Vin in 0 SIN(0 0.1 1k)\n"
|
|
"* Input and feedback resistors\n"
|
|
"R1 in inv 10k\n"
|
|
"Rf out inv 47k\n"
|
|
"* LM741 op-amp (subcircuit)\n"
|
|
".include /usr/share/ngspice/spice3/LM741.MOD\n"
|
|
"XU1 0 inv vcc vee out LM741\n"
|
|
".tran 10u 5m\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## Reading the Waveform\n\n"
|
|
"The output (red) should be an inverted and amplified version of the "
|
|
"input (blue). With a gain of -4.7:\n"
|
|
"- 100mV input peak becomes ~470mV output peak\n"
|
|
"- The output is 180 degrees out of phase (inverted)\n\n"
|
|
"The virtual ground principle keeps the inverting input at ~0V. "
|
|
"All the input current (Vin/R1) flows through Rf, creating the "
|
|
"output voltage: Vout = -Vin * (Rf/R1)."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"op-amp-comparator": {
|
|
"metadata": {
|
|
"title": "Op-Amp Voltage Comparator",
|
|
"engine": "ngspice",
|
|
"tags": ["op-amp", "comparator", "digital", "beginner"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# Op-Amp Voltage Comparator\n\n"
|
|
"An op-amp with no feedback acts as a comparator: the output swings "
|
|
"to the positive or negative rail depending on which input is higher.\n\n"
|
|
"- If V(+) > V(-): output goes to +Vcc\n"
|
|
"- If V(+) < V(-): output goes to -Vcc\n\n"
|
|
"A reference voltage on one input sets the threshold. "
|
|
"This is the basis for level detectors, zero-crossing circuits, and "
|
|
"analog-to-digital conversion."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-comp",
|
|
"type": "spice",
|
|
"source": (
|
|
"Op-Amp Voltage Comparator\n"
|
|
"* Dual power supply\n"
|
|
"VCC vcc 0 DC 12\n"
|
|
"VEE vee 0 DC -12\n"
|
|
"* Slowly rising input (ramp)\n"
|
|
"Vin inp 0 PULSE(-5 5 0 10m 10m 1n 20m)\n"
|
|
"* Reference voltage (voltage divider)\n"
|
|
"R1 vcc ref 10k\n"
|
|
"R2 ref 0 10k\n"
|
|
"* Op-amp in open-loop (comparator mode)\n"
|
|
".include /usr/share/ngspice/spice3/LM741.MOD\n"
|
|
"XU1 inp ref vcc vee out LM741\n"
|
|
".tran 10u 40m\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## Interpreting the Output\n\n"
|
|
"The triangular input ramps between -5V and +5V. The reference is "
|
|
"set at Vcc * R2/(R1+R2) = 6V (from the 12V supply divided by equal "
|
|
"resistors). When the input crosses the reference, the output snaps "
|
|
"between the rails.\n\n"
|
|
"In practice, a dedicated comparator IC (like the LM339) switches "
|
|
"faster than a general-purpose op-amp, but the principle is identical."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"am-radio-receiver": {
|
|
"metadata": {
|
|
"title": "AM Envelope Detector",
|
|
"engine": "ngspice",
|
|
"tags": ["radio", "am", "diode", "communications"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# AM Envelope Detector\n\n"
|
|
"The simplest AM radio receiver: a diode rectifies the RF carrier, "
|
|
"and an RC filter extracts the audio envelope. This is the circuit "
|
|
"used in crystal radios and the detector stage of superheterodyne "
|
|
"receivers.\n\n"
|
|
"- **Carrier:** 1 MHz (AM broadcast band)\n"
|
|
"- **Modulating signal:** 1 kHz audio tone\n"
|
|
"- **Modulation depth:** 50%"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-detector",
|
|
"type": "spice",
|
|
"source": (
|
|
"AM Envelope Detector\n"
|
|
"* AM modulated source: carrier 1MHz, modulated at 1kHz\n"
|
|
"* Vam = (1 + m*sin(2*pi*fm*t)) * sin(2*pi*fc*t)\n"
|
|
"Vam rf 0 AM(1 1e6 0 0 0.5 1e3)\n"
|
|
"* Detector diode\n"
|
|
"D1 rf det DMOD\n"
|
|
".model DMOD D IS=1e-14 N=1 BV=100\n"
|
|
"* Load + filter\n"
|
|
"R1 det 0 10k\n"
|
|
"C1 det 0 1n\n"
|
|
".tran 0.1u 3m\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## How It Works\n\n"
|
|
"The RF input is an amplitude-modulated carrier: a 1 MHz sine wave "
|
|
"whose amplitude varies at the 1 kHz audio rate. The diode conducts "
|
|
"only on positive half-cycles, charging C1 through the diode. "
|
|
"Between peaks, C1 slowly discharges through R1.\n\n"
|
|
"The RC time constant (R1 * C1 = 10us) is chosen to be:\n"
|
|
"- Much longer than the carrier period (1us) to filter out RF\n"
|
|
"- Much shorter than the audio period (1ms) to follow the envelope\n\n"
|
|
"The result at the detector output is the recovered audio envelope."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"colpitts-oscillator": {
|
|
"metadata": {
|
|
"title": "Colpitts RF Oscillator",
|
|
"engine": "ngspice",
|
|
"tags": ["oscillator", "rf", "bjt", "communications"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# Colpitts RF Oscillator\n\n"
|
|
"The Colpitts oscillator uses a capacitive voltage divider (C1, C2) "
|
|
"with an inductor (L1) to form the resonant tank circuit. The BJT "
|
|
"provides gain to sustain oscillation.\n\n"
|
|
"- **Oscillation frequency:** f = 1 / (2*pi * sqrt(L * Cs))\n"
|
|
"- Where Cs = C1*C2 / (C1+C2) (series combination)\n\n"
|
|
"With L1 = 1uH, C1 = 100pF, C2 = 100pF:\n"
|
|
"Cs = 50pF, f = 1 / (2*pi * sqrt(1u * 50p)) = ~22.5 MHz"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-osc",
|
|
"type": "spice",
|
|
"source": (
|
|
"Colpitts RF Oscillator\n"
|
|
"VCC vcc 0 DC 9\n"
|
|
"* Bias network\n"
|
|
"Rb1 vcc base 47k\n"
|
|
"Rb2 base 0 10k\n"
|
|
"Re emitter 0 1k\n"
|
|
"Ce emitter 0 10n\n"
|
|
"Rc vcc collector 1k\n"
|
|
"* BJT\n"
|
|
"Q1 collector base emitter 2N2222\n"
|
|
".model 2N2222 NPN(IS=14.34f BF=255.9 VAF=74.03 IKF=0.2847 ISE=14.34f NE=1.307 BR=6.092 VAR=28 IKR=0 ISC=0 NC=2 RB=10 RC=1 CJE=22.01p CJC=7.306p TF=0.4115n TR=46.91n)\n"
|
|
"* Tank circuit: L + capacitive divider\n"
|
|
"L1 vcc collector 1u\n"
|
|
"C1 collector base 100p\n"
|
|
"C2 base 0 100p\n"
|
|
"* Coupling cap to output\n"
|
|
"Cout collector out 10p\n"
|
|
"Rload out 0 50\n"
|
|
".tran 0.5n 500n UIC\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## What to Look For\n\n"
|
|
"The collector voltage should show oscillation building up from noise. "
|
|
"The frequency is set by the LC tank — the capacitive divider (C1, C2) "
|
|
"provides the feedback path from collector to base.\n\n"
|
|
"The Colpitts topology is popular in RF circuits because the capacitive "
|
|
"divider provides good frequency stability and the inductor can be a "
|
|
"simple air-core coil at VHF frequencies."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"thermistor-bridge": {
|
|
"metadata": {
|
|
"title": "Thermistor Bridge Circuit",
|
|
"engine": "ngspice",
|
|
"tags": ["sensor", "thermistor", "bridge", "measurement"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# Thermistor Wheatstone Bridge\n\n"
|
|
"A Wheatstone bridge converts small resistance changes into a "
|
|
"measurable voltage. Here, an NTC thermistor replaces one bridge arm. "
|
|
"As temperature changes, the thermistor resistance shifts and "
|
|
"unbalances the bridge.\n\n"
|
|
"The bridge is balanced (Vout = 0) when R_therm = R3 * R2/R1.\n\n"
|
|
"We simulate temperature change by sweeping the thermistor value."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-bridge",
|
|
"type": "spice",
|
|
"source": (
|
|
"Thermistor Wheatstone Bridge\n"
|
|
"* Bridge excitation\n"
|
|
"V1 vcc 0 DC 5\n"
|
|
"* Fixed bridge arms\n"
|
|
"R1 vcc a 10k\n"
|
|
"R2 a 0 10k\n"
|
|
"R3 vcc b 10k\n"
|
|
"* Thermistor arm (swept from 5k to 20k to simulate temperature)\n"
|
|
"Rtherm b 0 10k\n"
|
|
".dc Rtherm 5k 20k 100\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## Reading the Bridge Output\n\n"
|
|
"The differential voltage V(a) - V(b) is the bridge output. At balance "
|
|
"(Rtherm = 10k), both nodes sit at Vcc/2 = 2.5V and the difference is "
|
|
"zero.\n\n"
|
|
"As the thermistor resistance decreases (temperature rises for NTC), "
|
|
"V(b) increases and the bridge output goes negative. As resistance "
|
|
"increases (temperature falls), V(b) decreases and output goes positive.\n\n"
|
|
"An instrumentation amplifier (like the INA128) would typically amplify "
|
|
"this small differential signal for measurement."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
"photodiode-amplifier": {
|
|
"metadata": {
|
|
"title": "Photodiode Transimpedance Amplifier",
|
|
"engine": "ngspice",
|
|
"tags": ["sensor", "photodiode", "op-amp", "transimpedance"],
|
|
},
|
|
"cells": [
|
|
{
|
|
"id": "cell-intro",
|
|
"type": "markdown",
|
|
"source": (
|
|
"# Photodiode Transimpedance Amplifier\n\n"
|
|
"A transimpedance amplifier (TIA) converts the tiny current from a "
|
|
"photodiode into a usable voltage. The op-amp holds the photodiode "
|
|
"at virtual ground, and the feedback resistor Rf sets the conversion "
|
|
"gain.\n\n"
|
|
"- **Output voltage:** Vout = -Iphoto * Rf\n"
|
|
"- **Bandwidth** is limited by Rf and the feedback capacitance Cf\n\n"
|
|
"With Rf = 1M and a 1uA photocurrent: Vout = 1V"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-tia",
|
|
"type": "spice",
|
|
"source": (
|
|
"Photodiode Transimpedance Amplifier\n"
|
|
"* Dual supply\n"
|
|
"VCC vcc 0 DC 12\n"
|
|
"VEE vee 0 DC -12\n"
|
|
"* Photodiode modeled as current source (light pulses)\n"
|
|
"Iphoto 0 inv PULSE(0 1u 1m 0.1m 0.1m 2m 5m)\n"
|
|
"* Feedback network\n"
|
|
"Rf out inv 1MEG\n"
|
|
"Cf out inv 1p\n"
|
|
"* Op-amp\n"
|
|
".include /usr/share/ngspice/spice3/LM741.MOD\n"
|
|
"XU1 0 inv vcc vee out LM741\n"
|
|
".tran 10u 15m\n"
|
|
".end"
|
|
),
|
|
"outputs": [],
|
|
},
|
|
{
|
|
"id": "cell-explain",
|
|
"type": "markdown",
|
|
"source": (
|
|
"## Understanding the Response\n\n"
|
|
"The photocurrent pulse (simulated as a current source) flows through "
|
|
"the feedback resistor Rf. Since the op-amp keeps its inverting input "
|
|
"at virtual ground, all the photocurrent must flow through Rf.\n\n"
|
|
"The output voltage is simply Vout = Iphoto * Rf (inverted). With "
|
|
"Rf = 1M ohm and Iphoto = 1uA, the output should reach ~1V.\n\n"
|
|
"The small feedback capacitor Cf (1pF) prevents oscillation by "
|
|
"rolling off the gain at high frequencies. Without it, the parasitic "
|
|
"capacitance of the photodiode can cause the TIA to ring or oscillate."
|
|
),
|
|
"outputs": [],
|
|
},
|
|
],
|
|
},
|
|
}
|
|
|
|
|
|
def api_put(path: str, data: dict) -> dict:
|
|
"""PUT JSON to the API and return parsed response."""
|
|
url = f"{API_BASE}{path}"
|
|
body = json.dumps(data).encode()
|
|
req = urllib.request.Request(
|
|
url, data=body, method="PUT",
|
|
headers={"Content-Type": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req) as resp:
|
|
return json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
err_body = e.read().decode() if e.fp else ""
|
|
print(f" ERROR {e.code}: {err_body}", file=sys.stderr)
|
|
return {}
|
|
|
|
|
|
def api_post(path: str, data: dict = None) -> dict:
|
|
"""POST JSON to the API and return parsed response."""
|
|
url = f"{API_BASE}{path}"
|
|
body = json.dumps(data).encode() if data else b"{}"
|
|
req = urllib.request.Request(
|
|
url, data=body, method="POST",
|
|
headers={"Content-Type": "application/json"},
|
|
)
|
|
try:
|
|
with urllib.request.urlopen(req) as resp:
|
|
return json.loads(resp.read())
|
|
except urllib.error.HTTPError as e:
|
|
err_body = e.read().decode() if e.fp else ""
|
|
print(f" ERROR {e.code}: {err_body}", file=sys.stderr)
|
|
return {}
|
|
|
|
|
|
def main():
|
|
print(f"Creating {len(NOTEBOOKS)} notebooks via {API_BASE}")
|
|
print()
|
|
|
|
for nb_id, nb_data in NOTEBOOKS.items():
|
|
print(f"--- {nb_id} ---")
|
|
|
|
# PUT the full notebook (inject required timestamps)
|
|
metadata = {**nb_data["metadata"], "created": NOW, "modified": NOW}
|
|
notebook = {
|
|
"spicebook_version": "2026-02-13",
|
|
"metadata": metadata,
|
|
"cells": nb_data["cells"],
|
|
}
|
|
result = api_put(f"/api/notebooks/{nb_id}", notebook)
|
|
if result:
|
|
print(f" Created: {nb_data['metadata']['title']}")
|
|
else:
|
|
print(f" FAILED to create {nb_id}")
|
|
continue
|
|
|
|
# Run each SPICE cell and generate schematics
|
|
for cell in nb_data["cells"]:
|
|
if cell["type"] != "spice":
|
|
continue
|
|
cell_id = cell["id"]
|
|
|
|
# Generate schematic
|
|
print(f" Generating schematic for {cell_id}...")
|
|
api_post(f"/api/notebooks/{nb_id}/cells/{cell_id}/schematic")
|
|
|
|
# Run simulation
|
|
print(f" Running simulation for {cell_id}...")
|
|
sim_result = api_post(f"/api/notebooks/{nb_id}/cells/{cell_id}/run")
|
|
if sim_result and sim_result.get("success"):
|
|
elapsed = sim_result.get("elapsed_seconds", 0)
|
|
print(f" Simulation OK ({elapsed:.3f}s)")
|
|
else:
|
|
error = sim_result.get("error", "unknown error") if sim_result else "no response"
|
|
print(f" Simulation issue: {error}")
|
|
|
|
# Small delay between cells to avoid overwhelming ngspice
|
|
time.sleep(0.5)
|
|
|
|
print()
|
|
|
|
print("Done! All notebooks created.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|