Docs / Bastion Circuit Designer manual / Writing code and extending Bastion
Writing code and extending Bastion
Welcome to the development guide for Bastion Circuit Designer! Whether you are a seasoned .NET engineer or writing your very first lines of C#, this guide explains exactly how Bastion works under the hood and gives you step-by-step blueprints to add new components, tweak simulation physics, build new UI buttons, and expand AI capabilities.

1. Codebase Architecture (ELI5 Mode)
Bastion is divided into 4 clean, focused projects so that code is organized and easy to maintain:
graph TD
App["Bastion.App (Avalonia 12 UI & ViewModels)"]
Core["Bastion.Core (Models, Catalog, BOM, Serialization)"]
Sim["Bastion.Simulation (SPICE MNA Engine, MCU Firmware, Audio)"]
SDK["Bastion.PluginSDK (External C# Plugin Interface)"]
App --> Core
App --> Sim
App --> SDK
Sim --> Core
SDK --> Core
What Each Project Does:
-
- Contains data models: SchematicDocument.cs, ComponentDefinition.cs, and Net.cs.
- Contains the component library: StandardLibraryCatalog.cs.
- Contains Bill of Materials & Distributor clients: MultiDistributorSupplyChainClient.cs.
- Contains the embedded BastionMcpServer.cs for AI pair programming.
-
- Contains the SPICE physics solver: SimulationEngine.cs and
MnaSolver.cs. - Simulates physical components: resistors, capacitors, inductors, diodes, transistors, op-amps, and 555 timers.
- Emulates microcontrollers (ATmega328P, RP2040, STM32) and runs digital logic.
- Contains the SPICE physics solver: SimulationEngine.cs and
-
- Built with Avalonia UI for cross-platform desktop rendering.
- Follows MVVM (Model-View-ViewModel):
- Views: MainWindow.axaml, DistributorSearchWindow.axaml, TestRunnerWindow.axaml.
- ViewModels: MainViewModel.cs, SettingsViewModel.cs.
- Renderer: SymbolRenderer.cs draws components, pins, and waveforms on the canvas.
2. Anatomy of a Component
Before coding a new component, it helps to understand its anatomy:

A component in Bastion has 4 layers:
- Catalog Definition (ComponentDefinition.cs): Its ID, display name, category, package, default value, and list of pins.
- Visual Symbol (SymbolRenderer.cs): How Avalonia paints the lines, shapes, and colors on screen.
- Physics Simulation (SimulationEngine.cs): How voltages, currents, frequencies, and pin logic are simulated.
- Supply Chain Mapping (MultiDistributorSupplyChainClient.cs): Default MPN and pricing from DigiKey, Mouser, Octopart, and LCSC.
3. Step-by-Step Tutorial: How to Create a New Component
Let's create a real component: an LM386 Audio Power Amplifier IC!
Step 1: Add a SymbolType (If it needs a custom drawing shape)
If your component is an IC chip, you can use SymbolType.GenericIc (it automatically renders as a sleek chip box with legs). If it needs a unique shape:
Open ComponentDefinition.cs and add to SymbolType:
public enum SymbolType
{
// ...
AudioAmpLm386,
}
Step 2: Register in StandardLibraryCatalog.cs
Open StandardLibraryCatalog.cs and add your component definition:
// LM386 Low Voltage Audio Power Amplifier (DIP-8)
_catalog.Add(new ComponentDefinition
{
Id = "std:audio_amp_lm386",
Name = "LM386 Audio Power Amplifier",
Category = "Actuators & Audio",
SubCategory = "Audio Amplifiers",
Description = "Texas Instruments low voltage audio power amplifier. Operates from 4V-12V, voltage gain 20 to 200.",
DefaultValue = "LM386",
Prefix = "U",
Symbol = SymbolType.GenericIc, // or custom symbol
Width = 60,
Height = 80,
Pins = new()
{
// Pin layout (X, Y relative to center):
// Left side: negative X. Right side: positive X.
new PinDefinition("GAIN1", "GAIN (1)", ElectricalType.Passive, -30, -30),
new PinDefinition("IN-", "-INPUT (2)",ElectricalType.Input, -30, -10),
new PinDefinition("IN+", "+INPUT (3)",ElectricalType.Input, -30, 10),
new PinDefinition("GND", "GND (4)", ElectricalType.PowerIn, -30, 30),
new PinDefinition("GAIN8", "GAIN (8)", ElectricalType.Passive, 30, -30),
new PinDefinition("BYPASS","BYPASS (7)",ElectricalType.Passive, 30, -10),
new PinDefinition("VS", "VS (6)", ElectricalType.PowerIn, 30, 10),
new PinDefinition("VOUT", "VOUT (5)", ElectricalType.Output, 30, 30)
},
DefaultParameters = new()
{
["package"] = "DIP-8",
["supplyVoltageMin"] = "4V",
["supplyVoltageMax"] = "12V",
["voltageGain"] = 20,
["mpn"] = "LM386N-1/NOPB"
}
});
Step 3: Add Search Aliases
In _idAliases dictionary in StandardLibraryCatalog.cs:
["std:lm386"] = "std:audio_amp_lm386",
["std:audio_amp"] = "std:audio_amp_lm386",
Step 4: Add Visual Rendering (Optional)
If you chose a custom SymbolType, open SymbolRenderer.cs:
case SymbolType.AudioAmpLm386:
DrawAudioAmp(context, center, width, height, comp);
break;
Implement DrawAudioAmp:
private static void DrawAudioAmp(DrawingContext ctx, Point c, double w, double h, ComponentInstance comp)
{
// Draw amplifier triangle:
var geom = new StreamGeometry();
using (var gc = geom.Open())
{
gc.BeginFigure(new Point(c.X - w/2, c.Y - h/2), true);
gc.LineTo(new Point(c.X + w/2, c.Y));
gc.LineTo(new Point(c.X - w/2, c.Y + h/2));
gc.EndFigure(true);
}
ctx.DrawGeometry(ComponentFill, ComponentPen, geom);
}
Step 5: Add SPICE Physics / MNA Model
If the component participates in electrical nodal analysis:
In SimulationEngine.cs or the device stamps folder (src/Bastion.Simulation/Devices/):
- Connect the input pins to the output pin with the operational transfer function: \(V_{\text{out}} = A_v \cdot (V_{\text{in}+} - V_{\text{in}-})\)
- Enforce output saturation clipping between \(V_{\text{GND}}\) and \(V_s - 1.5\text{V}\).
4. How to Add a New Menu Option or Toolbar Button
Want to add a new tool or menu command? It takes less than 2 minutes:
1. Define the Command in MainViewModel.cs
Open MainViewModel.cs:
[RelayCommand]
private void AutoAlignComponents()
{
// Your logic here!
foreach (var comp in CurrentSchematic.Components)
{
// Snap to 10px grid
comp.Position = new Point2D(
Math.Round(comp.Position.X / 10.0) * 10.0,
Math.Round(comp.Position.Y / 10.0) * 10.0
);
}
StatusMessage = "All components snapped to grid.";
}
2. Bind in MainWindow.axaml
Open MainWindow.axaml:
<MenuItem Header="Auto-Align to Grid"
Command="{Binding AutoAlignComponentsCommand}"
HotKey="Ctrl+Shift+A"
InputGesture="Ctrl+Shift+A" />
5. How to Add a New MCP Tool for AI Control
Bastion has a built-in Model Context Protocol (MCP) server on port 8765. To add a new tool for AI assistants (like Claude, Cursor, or Antigravity):
Open BastionMcpServer.cs:
- Add the tool signature to
ListTools():
new McpTool
{
Name = "my_custom_tool",
Description = "Explains what this tool does in plain English.",
InputSchema = new
{
type = "object",
properties = new { myParam = new { type = "string", description = "Parameter description" } },
required = new[] { "myParam" }
}
}
- Handle the tool execution in
CallToolAsync():
case "my_custom_tool":
string param = args.TryGetValue("myParam", out var p) ? p?.ToString() ?? "" : "";
return new McpToolResult { Content = $"Successfully processed {param}" };
6. Instant Component Ingestion Studio (No C# Code Needed!)
Don't want to type C# code to create new chips? Bastion includes a built-in SPICE & Component Ingestion Studio:
Open via menu File > Import SPICE Model / Component... or Tools > SPICE & Component Ingestion Studio..., or click the Import SPICE / Datasheet... button in the Component Library sidebar!
1. Ingesting SPICE Subcircuits (.subckt)
Paste any manufacturer SPICE macro-model:
.SUBCKT DIVIDER IN OUT GND
R1 IN OUT 10k
R2 OUT GND 10k
.ENDS DIVIDER
Bastion automatically parses the ports (IN, OUT, GND), creates a component box with pins, and inside the physics engine allocates internal matrix nodes. When placed on canvas, real current flows through it and 60 FPS electron particles animate on connected wires!
2. Ingesting PDF Datasheet Pinout Tables
Copy and paste text directly from a PDF datasheet or website:
1: VCC (Supply voltage +5V)
2: TRIG (Trigger pulse input)
3: ECHO (Ultrasonic echo output)
4: GND (Ground 0V)
Or markdown tables (| Pin | Name | Type | Description |) or CSV.
Bastion auto-detects pin numbers, names, and electrical types (PowerIn, Input, Output, Bidirectional), and lets you pick package styles (DualInLine, SingleRow, QuadPackage).
3. Ingesting KiCad Symbols (.kicad_sym & .lib)
Paste any .kicad_sym S-expression block from open-source KiCad component libraries. Bastion extracts all pins, names, electrical directions, and positions with 100% fidelity.
Click "Import into Workbench & Canvas" and the component is immediately ready to use!
7. Testing Your Changes
Always verify your changes before submitting code!
# 1. Build the solution (must have 0 errors, 0 warnings)
dotnet build "BastionCircuitDesigner.slnx" --configuration Debug
# 2. Run all unit tests (over 580+ automated tests)
dotnet test "BastionCircuitDesigner.slnx" --configuration Debug
# 3. Run the UI automation test suite (20 automated tests)
python tests/test_ui_automation.py
Updated 2026-11-01