Automation
AutomationDriving Nabla headlessly from a Python script, or conversationally through an MCP-connected LLM.
Both paths sit on the same Python API and the same in-process JVM as the GUI — a script or an MCP tool call performs the same model mutation the corresponding GUI action would, and can mesh, solve and read results exactly as the GUI does. Neither needs the GUI running; a headless run needs only the bundled solver and the model files it produces.
The Python API
A packaged Nabla install ships a one-time setup script — setup_api.bat
(Windows) or setup_api.sh (Linux) in the Nabla folder — that creates a private
Python environment and installs the API into it (Python 3.10+ on the system is the only
prerequisite). From then on, run any script with the environment's own interpreter, from anywhere,
with no PYTHONPATH to set:
# Windows
<NablaDir>\nabla-python.bat my_script.py
# Linux
<NablaDir>/nabla-python my_script.py
Scripts mesh with Triangle too. The API uses the same mesh generator the GUI
does, looked up in the same four places (see
Getting started — Installing), so if you used the
installer's Download Triangle step the API already has it. The setup script says
which copy it found; if it finds none — the usual case when the API was installed on its own,
without the desktop installer — it explains where Triangle comes from and offers to download
it for you, and does so only if you say yes. Answer no and everything except
generateMesh() still works; re-run the setup script (or pass
--triangle download) whenever you want it.
A script looks like this — the same geometry/regions/BCs/coils/mesh/solve sequence as a GUI session, just written down instead of clicked through:
from nabla_api import Nabla
model = Nabla("my_model")
model.start() # boots the bundled JVM - no JDK needed
model.createNewModel("my_model")
# ... geometry / regions / BCs / coils ...
model.generateMesh()
model.saveModel()
model.run_solver_with_monitor()
Model and result files are written next to wherever you run the script from; call
setWorkingDirectory(...) to choose a different location. Runnable examples ship under
API/examples/ in the install — start with linear_01.py. Because
nabla-python is a plain virtual environment, ordinary tooling works unmodified:
nabla-python -m pip install scipy pandas to add a library, or registering it as a
Jupyter kernel for interactive work.
A fuller example: build, mesh, solve, extract
The snippet above stops at run_solver_with_monitor(). A complete script normally
continues into reading a result, matching the mesh → solve → post-process shape of a GUI
session:
from nabla_api import Nabla
model = Nabla("my_pmsm")
model.start()
# Build a machine the same way New Machine would.
model.machinesModuleToggle(True) # must precede createNewMachine
model.createNewMachine("PMSM")
model.setMachineGeometry("stator", "slots", 12)
model.setMachineGeometry("stator", "OD", 90)
model.setMachineGeometry("stator", "ID", 55)
model.setMachineGeometry("rotor", "OD", 53)
model.setWindingType("Concentrated", layers="Double")
model.setTurnsPerCoil(24)
model.generateMesh()
model.saveModel()
result = model.run_solver_with_monitor()
if not result.get("converged", True):
print(model.getLogTail(50)) # see what the solver actually complained about
raise RuntimeError("solve did not converge")
torque = model.getMachineTorque()
phase_a_flux = model.getResultsInCoil("PhaseA", "FluxLinkage")
print(f"torque={torque}, phase-A flux linkage={phase_a_flux}")
model.stop()
This mirrors the MCP tool sequence in the next section almost one-to-one — each Python call
here has a corresponding mcp__nabla__* tool with a similar name and the same
parameters, because both sides wrap the same underlying Java calls.
The MCP server
The same API is exposed as an MCP (Model Context
Protocol) server, so any MCP-capable LLM client can drive Nabla conversationally: you
describe what you want in natural language, the LLM calls MCP tools, the tools call the same
Python API a script would. A packaged install ships a
nabla-mcp launcher; point your client's MCP configuration at it (or, from a repo
checkout, run python -m nabla_mcp with the API on PYTHONPATH). The server
holds one model session per process — open a new client connection to start a fresh model.
The tool surface is organised into packs so a client is not shown every tool at once: six core packs (model, geometry, regions, coils, simulation, results) are always loaded, and four extension packs (machine, materials, thermal, performance) load on demand — automatically the moment you open or create a machine model, or explicitly by asking to enable one. Only a pack your licence actually grants is ever registered, so an LLM never plans around a tool call that would just refuse.
What each pack contains
| Pack | Loaded | Covers | Example tools |
|---|---|---|---|
| model | always | session, model lifecycle, inspection, tool packs | new_model, open_model, save_model,
get_model_state, nabla_status, list_tool_packs |
| geometry | always | shapes, selection, transforms, DXF | create_line, create_arc, select_in_box,
mirror_selection, import_dxf |
| regions | always | regions, holes, region properties, materials, BCs | create_region, set_region_material,
assign_boundary_condition, list_materials |
| coils | always | coils, coil-to-region assignment, external circuit | define_coil, assign_coil_to_region, add_circuit_element |
| simulation | always | meshing, units, solver configuration, motion, running the solver | generate_mesh, configure_solver, add_circular_motion,
run_solver |
| results | always | field probes, forces, region/coil/motion/circuit results, exports | get_field_at_point, get_region_result, get_force,
export_results |
| machine | on demand Machines |
parametric PMSM/IM design, winding, operating point, machine results, PDF report | create_machine, set_machine_geometry, set_winding_type,
get_machine_waveform, create_report |
| materials | on demand (free) | material library and editor | list_materials, get_material_curve,
set_material_curve |
| thermal | on demand Thermal |
thermal module: properties, heat sources, thermal solver | enable_thermal, set_thermal_region_property,
define_heat_source, run_thermal_solver |
| performance | on demand Performance |
performance analysis: curves, efficiency/loss maps, breakdowns | get_im_equivalent_circuit, get_performance_curve,
get_performance_map, get_loss_breakdown |
Call nabla_status at any point to see which packs are loaded, which are available but
not yet loaded, and which are hidden by your licence; call enable_tool_pack to load one
mid-session — the client is told the tool list changed and re-reads it, so a newly loaded
pack's tools become callable without restarting the session.
A conversational example
An MCP session looks like an ordinary conversation with the tool calls happening in between; you do not write MCP JSON yourself. For a prompt like “model a 12-slot 10-pole PMSM, 90 mm stator OD, mesh it and check the no-load back-EMF”, a client typically issues a call sequence like:
set_machine_type(machine_type="PMSM")
set_machine_geometry(part="stator", parameter="slots", value=12)
set_machine_geometry(part="stator", parameter="OD", value=90)
apply_slot_pole_combination(slots=12, poles=10)
set_winding_type(winding_type="Concentrated", layers="Double")
generate_mesh()
run_solver()
get_machine_waveform(quantity="BackEMF")
Each call returns a small JSON result — the applied value, any clamping, and warnings —
which is why a model rarely needs to call get_model_state after every step: the
previous tool's own response usually already says whether the change took.
Sessions and error handling
Both a Python script and an MCP server process hold exactly one model per
process — there is no multi-model juggling inside a single session. Starting a new model
(new_model / createNewModel) or opening one
(open_model / openModel) replaces whatever was loaded, and both refuse to
discard unsaved changes unless told to proceed anyway. Call
set_working_directory / setWorkingDirectory early if you want model and
result files to land somewhere other than the current directory.
A tool or API call that fails raises rather than silently doing nothing: an unknown region, coil,
shape or material name, or an unknown property name, fails at the call boundary. When a call
succeeds but the model behaved unexpectedly — a mesh that looks wrong, a solve that reports
non-convergence — get_log_tail / getLogTail is the first thing to
read: it surfaces the Java layer's own diagnostics, which are usually more specific than the
exception message alone.
How licensing behaves in Python
The Python and MCP layers report your entitlements rather than enforcing them a second time — Python is the most editable layer in the stack, so a licence check written there would be a comment, not a gate. The real gates stay in Java and in the solver binary; Python asks Java for the answer and behaves accordingly:
- starting the API at all (
model.start()) is the one hard gate — it needs the Automation entitlement and raises a clearNablaLicenseErrorif your licence does not include it, rather than booting a session that can only fail later; - a module your licence does not include (the machine, thermal or performance tool pack; the equivalent Python module) is refused as a whole, with a message naming the missing entitlement, rather than letting you call halfway into it;
- a single gated capability inside an otherwise-available module — time-harmonic solves, second-order elements, Crank–Nicolson, the far-field boundary condition, a second motion zone — is checked inline, the same as its GUI counterpart;
- if the solver itself refuses (exit code 3, a
LICENSE_ERROR:line on stdout) the Python layer turns that into the sameNablaLicenseError, naming the feature, instead of surfacing a bare "solver exited 3"; nabla_status(MCP) and the equivalent status call (Python) always report your current licence tier and entitlements, and never raise or start a JVM just to answer — a licence read that fails for any reason is treated as permissive, so a transient read error never takes away a capability you have already paid for.
Every gate here mirrors the GUI's own licensing rules one-to-one: what a script or an LLM session can do never depends on which door you walked in through — see Troubleshooting — licence errors for what a refusal means and Help → Licence... for the state of your own copy.
Next steps
- API reference — every public method, with signatures, parameters and return values.
- Machines — worked example — the same build/mesh/solve/analyse pipeline this page automates, walked through in the GUI.
- Troubleshooting — reading a
LICENSE_ERROR:refusal. - Back to the contents.