Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Sample.PythonConnector/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Local configuration with credentials - do not commit
config.yml

# State database
state.db

# Python cache
__pycache__/
*.pyc

bin
obj

publish/
files/
33 changes: 33 additions & 0 deletions Sample.PythonConnector/ConnectorRuntime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Cognite.Simulator.Utils;
using Cognite.Simulator.Utils.Automation;
using CogniteSdk.Alpha;
using Microsoft.Extensions.DependencyInjection;
using Sample.PythonConnector.Lib;

namespace Sample.PythonConnector;

public static class ConnectorRuntime
{
public static void Init()
{
DefaultConnectorRuntime<PythonConfig, DefaultModelFilestate, DefaultModelFileStatePoco>
.ConfigureServices = ConfigureServices;
DefaultConnectorRuntime<PythonConfig, DefaultModelFilestate, DefaultModelFileStatePoco>
.ConnectorName = "MuJoCo";
DefaultConnectorRuntime<PythonConfig, DefaultModelFilestate, DefaultModelFileStatePoco>
.SimulatorDefinition = PythonSimulatorDefinitionBridge.Get();
}

static void ConfigureServices(IServiceCollection services)
{
services.AddScoped<ISimulatorClient<DefaultModelFilestate, SimulatorRoutineRevision>,
PythonBridgeClient>();
}

public static async Task RunStandalone()
{
Init();
await DefaultConnectorRuntime<PythonConfig, DefaultModelFilestate, DefaultModelFileStatePoco>
.RunStandalone().ConfigureAwait(false);
}
}
22 changes: 22 additions & 0 deletions Sample.PythonConnector/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace Sample.PythonConnector;

public class Program
{
public static int Main(string[] args)
{
try
{
ConnectorRuntime.RunStandalone().Wait();
return 0;
}
catch (Exception ex)
{
Console.Error.WriteLine($"Fatal error: {ex.Message}");
if (ex.InnerException != null)
{
Console.Error.WriteLine($"Inner exception: {ex.InnerException.Message}");
}
return 1;
}
}
Comment on lines +5 to +21
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The Main method blocks on an async task using .Wait(), which is a discouraged practice that can lead to deadlocks. Additionally, the top-level exception handler only logs the exception message, omitting the stack trace and other crucial details, which severely hampers debugging of fatal errors.

    public static async Task<int> Main(string[] args)
    {
        try
        {
            await ConnectorRuntime.RunStandalone().ConfigureAwait(false);
            return 0;
        }
        catch (Exception ex)
        {
            Console.Error.WriteLine($"Fatal error: {ex}");
            return 1;
        }
    }

}
53 changes: 53 additions & 0 deletions Sample.PythonConnector/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# MuJoCo Python Connector

A sample connector using pythonnet to integrate [MuJoCo](https://mujoco.org/) physics simulator with CDF. Python files are embedded in the executable at build time.

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│ .NET Connector Runtime │
├─────────────────────────────────────────────────────────────┤
│ lib/ (C# Bridge Layer) │
├─────────────────────────────────────────────────────────────┤
│ EmbeddedPythonLoader │ Extracts .py files, auto-detect │
│ PythonBridgeBase │ GIL handling, large-stack exec │
│ PythonBridgeClient │ ISimulatorClient implementation │
│ PythonBridgeRoutine │ IRoutineImplementation impl │
├─────────────────────────────────────────────────────────────┤
│ python/ (Embedded at build) │
├─────────────────────────────────────────────────────────────┤
│ definition.py │ Simulator definition (MuJoCo) │
│ client.py │ MuJoCo model loading/validation │
│ routine.py │ Simulation execution engine │
└─────────────────────────────────────────────────────────────┘
```

## Prerequisites

- .NET 8.0 SDK
- Python 3.9+ with: `pip install mujoco find-libpython`

## Production Build

```bash
# macOS Apple Silicon
dotnet publish -c Release -r osx-arm64 --self-contained \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:DebugType=none -o ./publish

# Linux x64
dotnet publish -c Release -r linux-x64 --self-contained \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:DebugType=none -o ./publish

# Windows x64
dotnet publish -c Release -r win-x64 --self-contained \
-p:PublishSingleFile=true \
-p:IncludeNativeLibrariesForSelfExtract=true \
-p:DebugType=none -o ./publish
```

Produces a single ~85MB executable with embedded Python files.
26 changes: 26 additions & 0 deletions Sample.PythonConnector/Sample.PythonConnector.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<LangVersion>12.0</LangVersion>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\Cognite.Simulator.Utils\Cognite.Simulator.Utils.csproj" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="pythonnet" Version="3.0.4" />
</ItemGroup>

<!-- Embed Python files as resources at build time -->
<ItemGroup>
<EmbeddedResource Include="python\*.py">
<LogicalName>%(Filename)%(Extension)</LogicalName>
</EmbeddedResource>
</ItemGroup>

</Project>
47 changes: 47 additions & 0 deletions Sample.PythonConnector/config.example.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
version: 1

logger:
console:
level: "debug"

cognite:
project: ${COGNITE_PROJECT}
host: ${COGNITE_HOST}
idp-authentication:
tenant: ${COGNITE_TENANT_ID}
client-id: ${COGNITE_CLIENT_ID}
secret: ${COGNITE_CLIENT_SECRET}
# scopes:
# - https://api.cognitedata.com/.default
scopes:
- ${COGNITE_SCOPE}

connector:
name-prefix: "mujoco-connector@"
data-set-id: ${COGNITE_DATA_SET_ID}

# MuJoCo Connector Configuration
# =================================
# This connector integrates MuJoCo physics simulator with Cognite Data Fusion.
# Python files (definition.py, client.py, routine.py) are embedded in the executable
# at build time - no external Python files needed on the target machine.
#
# Supported model file types: .xml (MJCF), .mjcf, .urdf
#
automation:
# Path to Python DLL (optional, pythonnet will auto-detect if not specified)
# On Windows: "python311.dll" or "C:\\Python311\\python311.dll"
# On Linux: "libpython3.11.so" or "/usr/lib/x86_64-linux-gnu/libpython3.11.so"
# On macOS: "libpython3.11.dylib" or "/usr/local/opt/[email protected]/Frameworks/Python.framework/Versions/3.11/lib/libpython3.11.dylib"
# python-dll: ""

# Python home directory (optional, for setting PYTHONHOME)
# python-home: ""

# Timeout for Python operations in milliseconds (5 minutes)
execution-timeout: 300000

# Additional Python paths to add to sys.path (optional)
# NOTE: MuJoCo must be installed in the Python environment: pip install mujoco
# python-paths:
# - "/path/to/additional/modules"
Loading