Metadata-Version: 2.4
Name: RSWallScripting
Version: 1.6
Summary: Python API for controlling RSWall programmatically
Author: Rocscience Inc.
License: Proprietary
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: Microsoft :: Windows
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Topic :: Scientific/Engineering
Requires-Python: >=3.8
Description-Content-Type: text/markdown
Requires-Dist: grpcio>=1.71.2
Requires-Dist: protobuf==5.29.6
Provides-Extra: dev
Requires-Dist: grpcio-tools>=1.71.2; extra == "dev"
Requires-Dist: pytest>=7.0.0; extra == "dev"
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
Provides-Extra: docs
Requires-Dist: sphinx<9,>=7.0.0; extra == "docs"
Requires-Dist: pydata-sphinx-theme>=0.15.0; extra == "docs"
Requires-Dist: sphinx-autodoc-typehints>=1.25.0; extra == "docs"

# RSWall Python Scripting

Python API for controlling RSWall programmatically via gRPC.

Three-repo onboarding (RSWall + this package + MCP / RSInsight):
[`../rsgpt-mcp-rswall/docs/developer-onboarding.md`](../rsgpt-mcp-rswall/docs/developer-onboarding.md).

## Architecture

RSWall scripting uses a client-server architecture where Python communicates with the RSWall application via gRPC (Google Remote Procedure Call). The RSWall C# application hosts a gRPC server using the `Rocscience.Scripting.RPCSupport.Net` NuGet package, which provides infrastructure for thread marshaling, object reference management, and UI notification handling. Protocol Buffer (`.proto`) files define the service contracts and are compiled into both C# server stubs and Python client stubs.

This Python package (`rswall`) wraps the generated gRPC stubs in a user-friendly API. When you call methods like `modeler.openFile()` or `material.setUnitWeight()`, the package translates these into gRPC calls to the RSWall server, which executes the operations on the main UI thread and returns results. Object references (projects, materials) are tracked by GUID strings that map to actual C# objects on the server.

## Installation

Development (editable install from this repo):

```bash
pip install -e .
```

Production installs will use the internal Rocscience PyPI feed (same pattern as
`DipsScripting`), e.g. `pip install RSWallScripting==1.006`, once published.

Import classes from submodules:

```python
from rswall.RSWallModeler import RSWallModeler
from rswall.Wall import Wall
```

## Quick Start (Development)

### 1. Start RSWall with Scripting Server

Navigate to the RSWall build output and start with the scripting flag:
Notice that the location of the path can be different depending on where you keep your RSWall Repo.
This is also assuming that this build has scriptting capabilities.
```bash
cd C:\Users\user-name\source\repos\RSWall\RSWall\bin\x64\Release\net472
RSWall.exe -startScriptingServer 60067
```

### 2. Verify Server is Listening

```bash
netstat -ano | findstr 60067
```

You should see a line showing the port is in `LISTENING` state.

### 3. Run a Python Script

```python
from rswall.RSWallModeler import RSWallModeler

# Path to your test file
FILE_PATH = r"C:\path\to\your\file.rswall"

# Connect to RSWall
print("Connecting to RSWall on port 60067...")
modeler = RSWallModeler(port=60067)
print("✓ Connected!")

# Open file (will appear in RSWall UI)
print(f"\nOpening file: {FILE_PATH}")
wall = modeler.openFile(FILE_PATH)
print("✓ File opened!")

# Get all materials and display their unit weights (project native units)
units = wall.getProjectUnits()
uw_unit = units["unitWeightUnit"]
print("\nMaterials in project:")
materials = wall.getAllMaterials()
for mat in materials:
    unit_weight = mat.getUnitWeight()
    print(f"  - {mat.name}: {unit_weight} {uw_unit}")

# Modify the first material's unit weight
if materials:
    first_material = materials[0]
    print(f"\nChanging '{first_material.name}' unit weight...")
    print(f"  Before: {first_material.getUnitWeight()} {uw_unit}")
    first_material.setUnitWeight(19.69)
    print(f"  After:  {first_material.getUnitWeight()} {uw_unit}")
```

## Expected Results

When you run the script:

1. **RSWall opens the specified file** - You'll see the project appear in the RSWall application window
2. **Materials are listed** - The script prints all soil materials with their unit weights in project native units
3. **Unit weight is modified** - The first material's unit weight changes (value shown with unit label from getProjectUnits)
4. **Change is visible in UI** - If you check the material properties in RSWall, you'll see the updated value

## API Reference

The full scripting reference manual is built with Sphinx from `docs/`:

```bash
pip install -e ".[docs]"
python -m sphinx -W -b html docs docs/_build/html
```

The site has two tracks (same layout as RSPile):

* **rswall package** — one API page per public class/helper
* **Python Script Examples** — code snippets + downloadable `ExampleModel.rswall`

Published site (once GitHub Pages is enabled): https://rocscience.github.io/rswall-scripting/

See [docs/PUBLISHING.md](docs/PUBLISHING.md) for org publishing steps.

**Note**: All numeric wire values use the **project's native unit system**. Call
`wall.getProjectUnits()` after opening a file to read unit labels. No client-side
conversion is applied.

## Troubleshooting

| Issue | Solution |
|-------|----------|
| "Connection refused" | Ensure RSWall is running with `-startScriptingServer 60067` |
| "File not found" | Use full absolute path to the `.rswall` file |
| Port not listening | Check if another process is using port 60067 |

## Requirements

- Python 3.8+
- RSWall with scripting support (built from source with Phases 1-2 complete)
- Windows

## Future Development

To expose additional RSWall functions (e.g., setting soil name), follow these steps:

### Step 1: Update the Proto File (RSWall repo)

Add new messages and RPC method to the appropriate `.proto` file in `RSWall/RPCServices/protoFiles/`.

Example - adding `SetName` to `MaterialService.proto`:
```protobuf
// Add new messages
message SetNameRequest {
    string materialId = 1;
    string name = 2;
}

message SetNameResponse {
    bool success = 1;
}

// Add to the service block
service MaterialService {
    // ... existing methods ...
    rpc SetName(SetNameRequest) returns (SetNameResponse);
}
```

### Step 2: Build RSWall (generates C# stubs)

Build the RSWall solution in Visual Studio. The `Grpc.Tools` NuGet package automatically generates C# code from the `.proto` files into the `obj/` folder.

### Step 3: Implement C# Server Logic (RSWall repo)

**a) Add service method** in `RSWall/RPCServices/serviceImplementations/MaterialServiceImpl.cs`:
```csharp
public override Task<SetNameResponse> SetName(
    SetNameRequest request,
    ServerCallContext context) =>
    ScriptingData.RunScriptingFunction(request, context, SetNameImpl);

private SetNameResponse SetNameImpl(SetNameRequest request)
{
    MaterialLogic.SetName(request.MaterialId, request.Name, ScriptingData);
    return new SetNameResponse { Success = true };
}
```

**b) Add business logic** in `RSWall/RPCServices/staticLogic/MaterialLogic.cs`:
```csharp
public static void SetName(string materialId, string name, ScriptingData scriptingData)
{
    var soil = GetSoilData(materialId, scriptingData);
    soil.Name = name;
}
```

### Step 4: Copy Proto File to RSWallScripting

Copy the updated `.proto` file from `RSWall/RPCServices/protoFiles/` to `RSWallScripting/src/rswall/generatedFiles/`.

### Step 5: Generate Python Stubs (RSWallScripting repo)

```powershell
.\tools\generate_stubs.ps1
```

That regenerates every `*_pb2.py` / `*_pb2_grpc.py` from the `.proto` files in
`src/rswall/generatedFiles/` and patches the imports to be package-relative.

### Step 6: Add Python Wrapper Method (RSWallScripting repo)

Add user-friendly method in `src/rswall/Material.py`:
```python
def setName(self, name: str) -> None:
    """Set the name of the material."""
    request = MaterialService.SetNameRequest(
        materialId=self._objectId,
        name=name
    )
    response = self._client.callFunction(
        self._materialService.SetName, request
    )
    if not response.success:
        raise RuntimeError("Failed to set name")
    self._name = name  # Update local cache
```

### Summary

| Step | Location | Action |
|------|----------|--------|
| 1 | RSWall `.proto` | Define messages + RPC |
| 2 | RSWall (VS) | Build → C# stubs generated |
| 3 | RSWall `ServiceImpl` + `Logic` | Implement server-side code |
| 4 | Copy `.proto` | RSWall → RSWallScripting |
| 5 | RSWallScripting | Generate Python stubs |
| 6 | RSWallScripting `Material.py` | Add Python wrapper |
