# Simple Beam Model

This walkthrough demonstrates a complete SPACE GASS API workflow from
start to finish: create a project, build a simply-supported beam with
section, material, and loads, run a linear static analysis, and
retrieve the design results.

## What We Will Build

A simply-supported steel beam with self-weight, a dead load and a live
load, and ULS / SLS combinations to AS/NZS 1170:

- **Node 1** at (0, 0, 0) — fixed support
- **Node 2** at (6, 0, 0) — pinned support
- **Member 1** between the nodes, using a library section + library
  material
- **Three primary load cases** — self-weight, dead, live
- **Two combination cases** — ULS strength, SLS deflection
- A linear static analysis run, then queries for the maximum ULS
  bending moment and the SLS midspan deflection

## Project Setup

Before you can run this example you need a code editor and the SPACE GASS
SDK for your chosen language. If you already have a working .NET or
Python environment, skip ahead to [Step 1](#step-1--create-the-client-and-a-new-project).

We recommend [Visual Studio Code](https://code.visualstudio.com/) — it
is free, runs on Windows, macOS, and Linux, and has first-class support
for both C# and Python. If you already use Visual Studio, JetBrains
Rider, or PyCharm, those work too — the steps below translate directly.

### C#

1. Install the [.NET 8 SDK](https://dotnet.microsoft.com/download) and
   [Visual Studio Code](https://code.visualstudio.com/).
2. In VS Code, install the
   [C# Dev Kit](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.csdevkit)
   extension.
3. Pick a folder for your project (anywhere on disk), open it in VS Code
   via **File → Open Folder...**, then open the integrated terminal with
   **View → Terminal**. In the terminal, run:

   ```bash
   dotnet new console
   dotnet add package SpaceGassApi
   ```

4. Open `Program.cs` (created by the first command) and paste the example
   code from the steps below. Run it with `dotnet run`.

### Python

1. Install [Python 3.10 or newer](https://www.python.org/downloads/) and
   [Visual Studio Code](https://code.visualstudio.com/).
2. In VS Code, install the
   [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python).
3. Pick a folder for your project (anywhere on disk), open it in VS Code
   via **File → Open Folder...**, then open the integrated terminal with
   **View → Terminal**. In the terminal, run:

   ```bash
   python -m venv .venv
   .venv\Scripts\activate
   pip install space-gass-api
   ```

   On macOS or Linux replace the second line with `source .venv/bin/activate`.

4. Create a new file called `main.py` (**File → New File**, save with
   that name in the project folder) and paste the example code from the
   steps below. Run it with `python main.py`.

## Step 1 — Create the Client and a New Project

`SpaceGassApiClient` is your handle to the running API. Every endpoint
follows the same pattern — `GetAsync` to read, `PostAsync` to create,
`PatchAsync` to update, `DeleteAsync` to remove. See the
[Using the SDK guide](/using-the-sdk) if these are new.

The API only has one project open at a time. After `Job.New` (or
`Job.Open` on an existing `.sg` file) the new project becomes the
**active** job — every later call in this walkthrough acts on it until
you `Job.Close` it in Step 16.

<CodeTabs>
```csharp title="C#"
using SpaceGassApi;
using SpaceGassApi.Models;

var client = SpaceGassApiClient.CreateClient("http://localhost:34560");

// Create a new blank project
await client.Job.New.PostAsync();
```

```python title="Python"
from space_gass_api import SpaceGassApiClient
import space_gass_api.models as models

client = SpaceGassApiClient.create_client("http://localhost:34560")

# Create a new blank project
await client.job.new.post()
```
</CodeTabs>

## Step 2 — Create Nodes

Create the two endpoints of the beam — Node 1 at the origin and Node 2
six metres along X. POSTing a `NodeCreate` to `Job.Structure.Nodes`
returns the saved node including the `Id` SPACE GASS allocated; hold
onto each Id, every later call that references this node uses it.

<CodeTabs>
```csharp title="C#"
var node1 = await client.Job.Structure.Nodes.PostAsync(
    new NodeCreate { X = 0.0, Y = 0.0, Z = 0.0 });

var node2 = await client.Job.Structure.Nodes.PostAsync(
    new NodeCreate { X = 6.0, Y = 0.0, Z = 0.0 });

Console.WriteLine($"Node {node1.Id}: ({node1.X}, {node1.Y}, {node1.Z})");
Console.WriteLine($"Node {node2.Id}: ({node2.X}, {node2.Y}, {node2.Z})");
```

```python title="Python"
node1 = await client.job.structure.nodes.post(
    models.NodeCreate(x=0.0, y=0.0, z=0.0))

node2 = await client.job.structure.nodes.post(
    models.NodeCreate(x=6.0, y=0.0, z=0.0))

print(f"Node {node1.id}: ({node1.x}, {node1.y}, {node1.z})")
print(f"Node {node2.id}: ({node2.x}, {node2.y}, {node2.z})")
```
</CodeTabs>

## Step 3 — Apply Restraints

Restraints are top-level entities — POST a `NodeRestraintCreate` (with
`Node` set on the body) to `Job.Structure.NodeRestraints`. The
6-character `RestraintCode` maps to the TX, TY, TZ, RX, RY, RZ DOFs.
Each character is one of:

| Code | Meaning |
| --- | --- |
| `F` | **Fixed** — prevents movement |
| `R` | **Released** — allows movement |
| `S` | Spring (movement governed by a spring stiffness) |
| `V` | Variable spring (stiffness-vs-deflection table) |
| `P` | Plastic (upper force / moment limit on the reaction) |
| `N` | Friction (limit proportional to the normal-axis reaction) |

For our beam, Node 1 is fully fixed (`FFFFFF` — all six DOFs prevented
from moving) and Node 2 is pinned (`FFFRRR` — translations fixed,
rotations released).

<CodeTabs>
```csharp title="C#"
// Node 1: Fully fixed support (all 6 DOFs Fixed)
await client.Job.Structure.NodeRestraints.PostAsync(
    new NodeRestraintCreate { Node = node1.Id, RestraintCode = "FFFFFF" });

// Node 2: Pinned support (translations Fixed, rotations Released)
await client.Job.Structure.NodeRestraints.PostAsync(
    new NodeRestraintCreate { Node = node2.Id, RestraintCode = "FFFRRR" });
```

```python title="Python"
# Node 1: Fully fixed support (all 6 DOFs Fixed)
await client.job.structure.node_restraints.post(
    models.NodeRestraintCreate(node=node1.id, restraint_code="FFFFFF"))

# Node 2: Pinned support (translations Fixed, rotations Released)
await client.job.structure.node_restraints.post(
    models.NodeRestraintCreate(node=node2.id, restraint_code="FFFRRR"))
```
</CodeTabs>

## Step 4 — Add a Material

Add the material before the member so its `Id` is available to assign.
POSTing a `MaterialLibraryCreate` to `Job.Structure.Materials.Library`
pulls a standard material from a SPACE GASS library — `Library` is the
library file name installed with SPACE GASS and `Name` is the material
designation in that library. (Pass a `MaterialUserCreate` to
`Job.Structure.Materials` directly if you need a fully user-defined
material instead.)

<CodeTabs>
```csharp title="C#"
var steel = await client.Job.Structure.Materials.Library.PostAsync(
    new MaterialLibraryCreate
    {
        Library = "Aust",
        Name = "STEEL",
    });

Console.WriteLine($"Material {steel.Id}: {steel.Name}");
```

```python title="Python"
steel = await client.job.structure.materials.library.post(
    models.MaterialLibraryCreate(
        library="Aust",
        name="STEEL",
    ))

print(f"Material {steel.id}: {steel.name}")
```
</CodeTabs>

## Step 5 — Add a Section

Like the material, create the section before the member so its `Id`
is available. POSTing a `SectionLibraryCreate` to
`Job.Structure.Sections.Library` pulls a standard profile from a SPACE
GASS library — `Library` is the library file name installed with SPACE
GASS and `Name` is the section designation in that library.

<CodeTabs>
```csharp title="C#"
var section = await client.Job.Structure.Sections.Library.PostAsync(
    new SectionLibraryCreate
    {
        Library = "Aust300",
        Name = "360 UB 44.7",
        Mark = "B1",
    });

Console.WriteLine($"Section {section.Id}: {section.Name}");
```

```python title="Python"
section = await client.job.structure.sections.library.post(
    models.SectionLibraryCreate(
        library="Aust300",
        name="360 UB 44.7",
        mark="B1",
    ))

print(f"Section {section.id}: {section.name}")
```
</CodeTabs>

## Step 6 — Create the Member

`MemberCreate` connects the two nodes with a single member and assigns
the section and material we just made. `NodeA` / `NodeB` take the Ids
from Step 2; `Section` and `Material` take the Ids returned by Steps 4
and 5.

<CodeTabs>
```csharp title="C#"
var member = await client.Job.Structure.Members.PostAsync(
    new MemberCreate
    {
        NodeA = node1.Id,
        NodeB = node2.Id,
        Section = section.Id,
        Material = steel.Id,
    });

Console.WriteLine($"Member {member.Id}: Node {member.NodeA} → Node {member.NodeB}");
```

```python title="Python"
member = await client.job.structure.members.post(
    models.MemberCreate(
        node_a=node1.id,
        node_b=node2.id,
        section=section.id,
        material=steel.id,
    ))

print(f"Member {member.id}: Node {member.node_a} → Node {member.node_b}")
```
</CodeTabs>

## Step 7 — Create Primary Load Cases

Create three primary cases up front — self-weight, dead, and live — so
their Ids are available when we attach loads in Steps 8–10. POSTing a
`LoadCaseCreate` to `Job.Loads.LoadCases` creates a primary case;
combination cases come later in Step 11.

<CodeTabs>
```csharp title="C#"
var selfWeightCase = await client.Job.Loads.LoadCases.PostAsync(
    new LoadCaseCreate { Id = 1, Title = "Self-weight" });

var deadCase = await client.Job.Loads.LoadCases.PostAsync(
    new LoadCaseCreate { Id = 2, Title = "Dead Load" });

var liveCase = await client.Job.Loads.LoadCases.PostAsync(
    new LoadCaseCreate { Id = 3, Title = "Live Load" });

Console.WriteLine($"Load cases: SW={selfWeightCase.Id}, G={deadCase.Id}, Q={liveCase.Id}");
```

```python title="Python"
self_weight_case = await client.job.loads.load_cases.post(
    models.LoadCaseCreate(id=1, title="Self-weight"))

dead_case = await client.job.loads.load_cases.post(
    models.LoadCaseCreate(id=2, title="Dead Load"))

live_case = await client.job.loads.load_cases.post(
    models.LoadCaseCreate(id=3, title="Live Load"))

print(f"Load cases: SW={self_weight_case.id}, G={dead_case.id}, Q={live_case.id}")
```
</CodeTabs>

## Step 8 — Apply the Self-Weight Load

Attach a self-weight load to the self-weight case. POST a
`SelfWeightLoadCreate` to `Job.Loads.SelfWeightLoads` with `Case` set
on the body — the load case must already exist, and one self-weight
load per case is allowed. Acceleration is expressed in **G** (multiples
of gravity), so set `AccelerationY = -1.0` for one G of gravity
in the negative-Y direction.

<CodeTabs>
```csharp title="C#"
await client.Job.Loads.SelfWeightLoads.PostAsync(
    new SelfWeightLoadCreate
    {
        LoadCase = selfWeightCase.Id,
        AccelerationX = 0.0,
        AccelerationY = -1.0,    // 1 G downward
        AccelerationZ = 0.0,
    });
```

```python title="Python"
await client.job.loads.self_weight_loads.post(
    models.SelfWeightLoadCreate(
        load_case=self_weight_case.id,
        acceleration_x=0.0,
        acceleration_y=-1.0,    # 1 G downward
        acceleration_z=0.0,
    ))
```
</CodeTabs>

## Step 9 — Add a Member Distributed Load to the Dead Case

Apply a uniform 2 kN/m downward across the full 6 m span on the dead
case. `MemberDistributedLoadCreate` takes the target `LoadCase` and
`Member` Ids, start / finish positions along the member (in length
units), and start / finish force intensity per unit length on each
axis.

<CodeTabs>
```csharp title="C#"
await client.Job.Loads.MemberDistributedLoads.PostAsync(
    new MemberDistributedLoadCreate
    {
        LoadCase = deadCase.Id,
        Member = member.Id,
        PositionUnits = LoadPositionUnits.Percent,
        StartPosition = 0.0,
        FinishPosition = 100.0,
        FyStart = -2.0,    // kN/m downward
        FyFinish = -2.0,
    });
```

```python title="Python"
await client.job.loads.member_distributed_loads.post(
    models.MemberDistributedLoadCreate(
        load_case=dead_case.id,
        member=member.id,
        position_units=models.LoadPositionUnits.Percent,
        start_position=0.0,
        finish_position=100.0,
        fy_start=-2.0,    # kN/m downward
        fy_finish=-2.0,
    ))
```
</CodeTabs>

## Step 10 — Add a Member Distributed Load to the Live Case

Same shape as Step 9 — swap the `LoadCase` to the live case and bump the
intensity to 5 kN/m.

<CodeTabs>
```csharp title="C#"
await client.Job.Loads.MemberDistributedLoads.PostAsync(
    new MemberDistributedLoadCreate
    {
        LoadCase = liveCase.Id,
        Member = member.Id,
        PositionUnits = LoadPositionUnits.Percent,
        StartPosition = 0.0,
        FinishPosition = 100.0,
        FyStart = -5.0,    // kN/m downward
        FyFinish = -5.0,
    });
```

```python title="Python"
await client.job.loads.member_distributed_loads.post(
    models.MemberDistributedLoadCreate(
        load_case=live_case.id,
        member=member.id,
        position_units=models.LoadPositionUnits.Percent,
        start_position=0.0,
        finish_position=100.0,
        fy_start=-5.0,    # kN/m downward
        fy_finish=-5.0,
    ))
```
</CodeTabs>

## Step 11 — Define the ULS and SLS Combinations

A combination case is created in a single call via
`Job.Loads.CombinationLoadCases`, with the title, Id, and the list of
items (primary case + factor pairs) supplied inline on
`CombinationLoadCaseCreate`. Below we define ULS (`1.2G + 1.5Q`) and SLS
short-term (`1.0G + 0.7Q`) combinations to AS/NZS 1170.

<CodeTabs>
```csharp title="C#"
// ULS: 1.2 G + 1.5 Q (self-weight + dead are both G)
var ulsCase = await client.Job.Loads.CombinationLoadCases.PostAsync(
    new CombinationLoadCaseCreate
    {
        Id = 10,
        Title = "ULS - Strength",
        CombinationItems = new List<CombinationLoadCaseItem>
        {
            new() { LoadCase = selfWeightCase.Id, MultiplyingFactor = 1.2 },
            new() { LoadCase = deadCase.Id,       MultiplyingFactor = 1.2 },
            new() { LoadCase = liveCase.Id,       MultiplyingFactor = 1.5 },
        },
    });

// SLS short-term: 1.0 G + 0.7 Q
var slsCase = await client.Job.Loads.CombinationLoadCases.PostAsync(
    new CombinationLoadCaseCreate
    {
        Id = 20,
        Title = "SLS - Short-term Deflection",
        CombinationItems = new List<CombinationLoadCaseItem>
        {
            new() { LoadCase = selfWeightCase.Id, MultiplyingFactor = 1.0 },
            new() { LoadCase = deadCase.Id,       MultiplyingFactor = 1.0 },
            new() { LoadCase = liveCase.Id,       MultiplyingFactor = 0.7 },
        },
    });
```

```python title="Python"
# ULS: 1.2 G + 1.5 Q (self-weight + dead are both G)
uls_case = await client.job.loads.combination_load_cases.post(
    models.CombinationLoadCaseCreate(
        id=10,
        title="ULS - Strength",
        combination_items=[
            models.CombinationLoadCaseItem(load_case=self_weight_case.id, multiplying_factor=1.2),
            models.CombinationLoadCaseItem(load_case=dead_case.id,        multiplying_factor=1.2),
            models.CombinationLoadCaseItem(load_case=live_case.id,        multiplying_factor=1.5),
        ],
    ))

# SLS short-term: 1.0 G + 0.7 Q
sls_case = await client.job.loads.combination_load_cases.post(
    models.CombinationLoadCaseCreate(
        id=20,
        title="SLS - Short-term Deflection",
        combination_items=[
            models.CombinationLoadCaseItem(load_case=self_weight_case.id, multiplying_factor=1.0),
            models.CombinationLoadCaseItem(load_case=dead_case.id,        multiplying_factor=1.0),
            models.CombinationLoadCaseItem(load_case=live_case.id,        multiplying_factor=0.7),
        ],
    ))
```
</CodeTabs>

## Step 12 — Save the Initial Model

Persist the project to disk before kicking off the analysis. If
something fails downstream, you can open the saved `.sg` in SPACE GASS
and inspect every entity, load, and combination to confirm the model
is set up the way you expect.

The `Save` response is a `JobStatus` whose `State.File` carries the
file's `Path`, `Name`, and `Source` — useful both as a sanity check
and for any tooling that needs to know where the file landed.

<CodeTabs>
```csharp title="C#"
var initialSave = await client.Job.Save.PostAsync(
    new SaveJobRequest { FilePath = @"C:\Projects\SimpleBeam.sg" });

var jobFile = initialSave?.State?.File;
Console.WriteLine($"  Path:   {jobFile?.Path}");
Console.WriteLine($"  Name:   {jobFile?.Name}");
Console.WriteLine($"  Source: {jobFile?.Source}");
Console.WriteLine($"  IsNew:  {initialSave?.State?.IsNew}");
Console.WriteLine($"  IsOpen: {initialSave?.State?.IsOpen}");
```

```python title="Python"
initial_save = await client.job.save.post(
    models.SaveJobRequest(file_path="C:\\Projects\\SimpleBeam.sg"))

job_file = initial_save.state.file if initial_save and initial_save.state else None
print(f"  Path:   {job_file.path if job_file else None}")
print(f"  Name:   {job_file.name if job_file else None}")
print(f"  Source: {job_file.source if job_file else None}")
print(f"  IsNew:  {initial_save.state.is_new if initial_save and initial_save.state else None}")
print(f"  IsOpen: {initial_save.state.is_open if initial_save and initial_save.state else None}")
```
</CodeTabs>

## Step 13 — Run a Linear Static Analysis

Analyses are **asynchronous** in SPACE GASS — when you start one, the
API returns immediately with a run Id and your code keeps going. Before
you can query results you need to wait until the run reaches a terminal
status (`Completed`, `Failed`, or `Cancelled`).

The simplest way is a poll loop, shown below. For longer analyses
where you want to display progress, see the
[Running Analysis guide](/guides/running-analysis).

<CodeTabs>
```csharp title="C#"
// Start a linear static analysis. Pass an empty StaticSettingsUpdate
// to use the project's current settings as-is.
var run = await client.Job.Analysis.Static.RunLinear.PostAsync(
    new StaticSettingsUpdate());

Console.WriteLine($"Run {run.RunId} queued; waiting for completion...");

// Poll until the run reaches a terminal state
AnalysisRun finalRun;
while (true)
{
    await Task.Delay(500);
    finalRun = await client.Job.Analysis.Runs[run.RunId.Value].GetAsync();
    if (finalRun.Status is AnalysisRunStatus.Completed
                        or AnalysisRunStatus.Failed
                        or AnalysisRunStatus.Cancelled)
    {
        break;
    }
}

Console.WriteLine($"Analysis {finalRun.Status} in {finalRun.ElapsedTime}");

if (finalRun.Status != AnalysisRunStatus.Completed)
{
    throw new Exception($"Analysis did not complete: {finalRun.ErrorMessage}");
}
```

```python title="Python"
import asyncio

# Start a linear static analysis. Pass an empty models.StaticSettingsUpdate to
# use the project's current settings as-is.
run = await client.job.analysis.static.run_linear.post(
    models.StaticSettingsUpdate())

print(f"Run {run.run_id} queued; waiting for completion...")

# Poll until the run reaches a terminal state
while True:
    await asyncio.sleep(0.5)
    final_run = await client.job.analysis.runs.by_run_id(str(run.run_id)).get()
    if final_run.status in (
        models.AnalysisRunStatus.Completed,
        models.AnalysisRunStatus.Failed,
        models.AnalysisRunStatus.Cancelled,
    ):
        break

print(f"Analysis {final_run.status} in {final_run.elapsed_time}")

if final_run.status != models.AnalysisRunStatus.Completed:
    raise RuntimeError(f"Analysis did not complete: {final_run.error_message}")
```
</CodeTabs>

## Step 14 — Query Reactions

Read the support reactions under the ULS combination. By default the
query returns every result for every case; the `LoadCases` query parameter
filters to the load case(s) you want.

The result also carries a `Warnings` object. If a requested case (or
any of its constituent primaries, for combinations) has not been
analysed, its Id appears in `Warnings.LoadCasesNotAnalyzed` — check this
before reading `Results` so a missing run doesn't silently return zero
rows.

<CodeTabs>
```csharp title="C#"
var reactions = await client.Job.Query.Analysis.Static.NodeReactions.GetAsync(
    config => config.QueryParameters.LoadCases = $"{ulsCase.Id}");

if (reactions.Warnings?.LoadCasesNotAnalyzed is { Length: > 0 } missing)
{
    throw new Exception($"Cases not analysed: {missing}. Run the analysis first.");
}

foreach (var r in reactions.Results)
{
    Console.WriteLine(
        $"Node {r.Node}, LC {r.LoadCase}: " +
        $"Fx={r.Fx:F2}, Fy={r.Fy:F2}, Fz={r.Fz:F2}");
}
```

```python title="Python"
reactions = await client.job.query.analysis.static.node_reactions.get(
    load_cases=str(uls_case.id))

if reactions.warnings and reactions.warnings.load_cases_not_analyzed:
    raise RuntimeError(
        f"Cases not analysed: {reactions.warnings.load_cases_not_analyzed}. "
        "Run the analysis first.")

for r in reactions.results:
    print(f"Node {r.node}, LC {r.load_case}: "
          f"Fx={r.fx:.2f}, Fy={r.fy:.2f}, Fz={r.fz:.2f}")
```
</CodeTabs>

## Step 15 — Get the Maximum ULS Bending Moment

Read the peak bending moment along the beam under the ULS combination.
The result is one row per (case, member) combination; force values are
returned as parallel arrays indexed by station, so element `[i]` of
every array describes the same point along the member:

```text
MemberIntermediateForce {
  Case:     int           // load case Id
  Member:   int           // member Id
  Station:  int[]         // station index per sample
  Location: float[]       // distance along the member per sample
  Fx, Fy, Fz: float[]     // force per sample
  Mx, My, Mz: float[]     // moment per sample
}
```

Filter by ULS case + the member Id and take the maximum of `|Mz|`.

<CodeTabs>
```csharp title="C#"
var ulsForces = await client.Job.Query.Analysis.Static.MemberIntermediateForces
    .GetAsync(config =>
    {
        config.QueryParameters.LoadCases = $"{ulsCase.Id}";
        config.QueryParameters.Members   = $"{member.Id}";
    });

var beamForces = ulsForces.Results.First();
var maxMz = beamForces.Mz.Max(v => Math.Abs(v ?? 0.0));

Console.WriteLine($"Max ULS bending moment on Member {member.Id}: {maxMz:F2} kNm");
```

```python title="Python"
uls_forces = await client.job.query.analysis.static.member_intermediate_forces.get(
    load_cases=str(uls_case.id),
    members=str(member.id))

beam_forces = uls_forces.results[0]
max_mz = max(abs(v) for v in beam_forces.mz if v is not None)

print(f"Max ULS bending moment on Member {member.id}: {max_mz:.2f} kNm")
```
</CodeTabs>

## Step 16 — Get the SLS Midspan Deflection

Read the maximum vertical deflection along the beam under the SLS
combination. `MemberIntermediateDisplacement` follows the same columnar
shape as Step 14 — one row per (case, member), translations as
parallel arrays indexed by station:

```text
MemberIntermediateDisplacement {
  Case:     int                       // load case Id
  Member:   int                       // member Id
  Station:  int[]                     // station index per sample
  Location: float[]                   // distance along the member per sample
  TxLocal,  TyLocal,  TzLocal:  float[]  // translation in member-local axes
  TxGlobal, TyGlobal, TzGlobal: float[]  // translation in global axes
}
```

`TyGlobal` is the global-Y translation at each station; filter by SLS
case + the member Id and take the maximum of `|TyGlobal|`.

<CodeTabs>
```csharp title="C#"
var slsDisplacements = await client.Job.Query.Analysis.Static.MemberIntermediateDisplacements
    .GetAsync(config =>
    {
        config.QueryParameters.LoadCases = $"{slsCase.Id}";
        config.QueryParameters.Members   = $"{member.Id}";
    });

var beamDisplacements = slsDisplacements.Results.First();
var maxDeflection = beamDisplacements.TyGlobal.Max(v => Math.Abs(v ?? 0.0));

Console.WriteLine($"Max SLS midspan deflection on Member {member.Id}: {maxDeflection * 1000:F2} mm");
```

```python title="Python"
sls_displacements = await client.job.query.analysis.static.member_intermediate_displacements.get(
    load_cases=str(sls_case.id),
    members=str(member.id))

beam_displacements = sls_displacements.results[0]
max_deflection = max(abs(v) for v in beam_displacements.ty_global if v is not None)

print(f"Max SLS midspan deflection on Member {member.id}: {max_deflection * 1000:.2f} mm")
```
</CodeTabs>

## Step 17 — Save and Close

Save persists the project to disk so you can re-open it later from
SPACE GASS. Close ends the active job and releases the file. Run
`Job.Close` from a `finally` (C#) or `finally`-equivalent (Python)
block so the active job is always cleaned up — even if a step above
threw — leaving the service ready for the next run.

<CodeTabs>
```csharp title="C#"
try
{
    // ...all the steps above...

    await client.Job.Save.PostAsync(
        new SaveJobRequest { FilePath = @"C:\Projects\SimpleBeam.sg" });
}
finally
{
    // Always close the active job so the next run starts clean.
    try
    {
        await client.Job.Close.PostAsync();
    }
    catch (Exception closeEx)
    {
        Console.Error.WriteLine($"Warning: failed to close job: {closeEx.Message}");
    }
}
```

```python title="Python"
try:
    # ...all the steps above...

    await client.job.save.post(
        models.SaveJobRequest(file_path="C:\\Projects\\SimpleBeam.sg"))

finally:
    # Always close the active job so the next run starts clean.
    try:
        await client.job.close.post()
    except Exception as close_ex:
        print(f"Warning: failed to close job: {close_ex}", file=sys.stderr)
```
</CodeTabs>

## Run this example locally

The complete program with error handling is in the repo as
[`Example.CreateSimpleBeam`](https://github.com/SpaceGass/space-gass-api/tree/main/sdks/csharp/examples/Example.CreateSimpleBeam) (C#) and
[`create_simple_beam`](https://github.com/SpaceGass/space-gass-api/tree/main/sdks/python/examples/create_simple_beam) (Python).
Clone the repo and run it directly — no copy-paste:

<CodeTabs>
```bash title="C#"
git clone https://github.com/SpaceGass/space-gass-api.git
cd space-gass-api/sdks/csharp/examples/Example.CreateSimpleBeam
dotnet run
```

```bash title="Python"
git clone https://github.com/SpaceGass/space-gass-api.git
cd space-gass-api/sdks/python/examples/create_simple_beam
pip install space-gass-api    # once per environment
python create_simple_beam.py
```
</CodeTabs>

The example saves the model to `~/Desktop/SpaceGass Examples/SimpleBeam.sg` so
you can open it in SPACE GASS Desktop and verify the geometry / results
visually.
