← All posts
Dev
etabs
csharp
mcp
automation

Building ETABSharp: A C# Wrapper for the ETABS API

The raw ETABS API is verbose and weakly typed. Here's how I designed a fluent C# wrapper that makes structural automation actually enjoyable to write and get help from LLM.

14 min read

The ETABS API is powerful. It exposes almost everything — model geometry, load cases, analysis results, design checks. But working with it directly is painful.

Methods are stringly typed. There’s no IntelliSense guidance on what parameters mean. Error handling is COM-era ref parameters. You end up writing the same boilerplate fifty times per script.

ETABSharp is a C# wrapper that fixes this.

The problem with the raw API

The pain is not only one API call. In real automation, you have to orchestrate stateful ETABS sessions safely:

  • Mode A attach to a running ETABS and never kill the user’s app
  • Mode B start a hidden ETABS instance, run a batch task, and always close it
  • Handle model lock/unlock before edits
  • Suppress save prompts in unattended flows
  • Check dozens of integer return codes (ret != 0) after every operation

This is the kind of control flow you end up writing:

ETABSApplication? app = null;
try
{
    // Mode B: create isolated hidden ETABS
    app = ETABSWrapper.CreateNew(startApplication: true);
    if (app == null) throw new Exception("Failed to create ETABS instance.");

    app.Application.Hide();

    int openRet = app.Model.Files.OpenFile(TEST_MODEL_PATH);
    if (openRet != 0) throw new Exception($"OpenFile failed: {openRet}");

    if (app.Model.ModelInfo.IsLocked())
        app.Model.ModelInfo.SetLocked(false);

    int analysisRet = app.Model.Analyze.RunCompleteAnalysis();
    if (analysisRet != 0) throw new Exception($"RunCompleteAnalysis failed: {analysisRet}");

    int saveRet = app.Model.Files.SaveFile(TEST_MODEL_PATH);
    if (saveRet != 0) throw new Exception($"SaveFile failed: {saveRet}");
}
finally
{
    // Critical: hidden instance must always be closed
    if (app != null) app.Application.ApplicationExit(false);
}

And if you need joint displacement from raw COM, you still have the long Results.JointDispl(...) signature with many ref arrays.

The ETABSharp approach

ETABSharp wraps the common automation paths so the intent is clearer. For example, in your sidecar tests:

var app = ETABSWrapper.Connect(); // Mode A: attach to running ETABS
if (app == null) return;

var filePath = app.Model.ModelInfo.GetModelFilename(includePath: true);
var isLocked = app.Model.ModelInfo.IsLocked();
var caseStatuses = app.Model.Analyze.GetCaseStatus();
var isAnalyzed = caseStatuses.Any(cs => cs.IsFinished);

Console.WriteLine($"File: {filePath}");
Console.WriteLine($"Locked: {isLocked}");
Console.WriteLine($"Analyzed: {isAnalyzed}");

// Typed wrapper result instead of manual COM ref arrays
var jointDispl = app.Model.AnalysisResults.GetJointDispl("Joint28", eItemTypeElm.GroupElm);
Console.WriteLine($"Joint displacement rows: {jointDispl.NumberResults}");

Same ETABS power, but cleaner orchestration and safer session lifecycle rules.

Architecture decisions

1. Thin wrapper, not an abstraction

ETABSSharp wraps the ETABS COM API — it doesn’t try to replace it. Every method maps 1:1 to an ETABS API call. This means:

  • The full API surface is available
  • Result pattern
  • Behaviour is predictable — if ETABS does it, ETABSharp can do it
  • No magic or hidden logic

2. Result types instead of ref parameters

Every output is a strongly typed record:

public record JointDisplacement(
    string Obj,
    string LoadCase,
    string StepType,
    double StepNum,
    double U1,
    double U2,
    double U3
);

3. Fluent access pattern

Can get results dirrect from API or you can get table results for those table dont have direct api like material table

var etabs = await ETABSWrapper.Connect();

// Navigate to what you need
var frameForces = app.Model
    .AnalysisResults
    .FrameForces("level6",eItemTypeElm.GroupElm );

// Get table from etabs model
var tableResults = app.Model
    .DatabaseTables
    .GetTableForDisplayArray(tableKey, fieldKeys, group);

The AI layer

The real reason I built ETABSharp isn’t just the cleaner API — it’s what the clean API enables: an MCP server that lets AI assistants talk to ETABS directly and building .

More on that in the next post.

Get it

dotnet add package EtabSharp --version 0.3.1-beta

If you’re automating ETABS with C# and you have questions, open an issue or reach out. I use this in production work every week.

#etabs #csharp #mcp #automation

Discover more