Skip to content

Refactor to introduce NatNet and telemetry abstraction layers#8

Merged
rhughes42 merged 1 commit into
masterfrom
release/v1.3.0-natnet-abstraction
May 12, 2026
Merged

Refactor to introduce NatNet and telemetry abstraction layers#8
rhughes42 merged 1 commit into
masterfrom
release/v1.3.0-natnet-abstraction

Conversation

@rhughes42

Copy link
Copy Markdown
Owner

This pull request introduces a major architectural refactor to decouple the Grasshopper plugin from the NatNet SDK, establish clear internal boundaries, and lay the foundation for privacy-aware telemetry. The update adds SDK-independent OptiTrack core models and interfaces, a NatNet-backed adapter, and a no-op telemetry abstraction. Comprehensive developer and architecture documentation is also included to clarify the new design and boundaries.

Core Architecture and Abstraction Layers:

  • Introduced SDK-independent OptiTrack core models (OptiTrackFrame, OptiTrackMarker, OptiTrackRigidBody, etc.) and the IOptiTrackClient interface in OptiTrack.Core, enabling the Grasshopper component to consume internal domain models instead of raw NatNet SDK types. (src/Tracker/OptiTrack/Core/OptiTrackModels.cs, src/Tracker/OptiTrack/Core/IOptiTrackClient.cs, src/Tracker/OptiTrack/Core/OptiTrackConnectionOptions.cs, src/Tracker/OptiTrack/Core/OptiTrackConnectionInfo.cs, src/Tracker/OptiTrack/Core/OptiTrackConnectionStatus.cs, src/Tracker/OptiTrack/Core/OptiTrackEventArgs.cs) [1] [2] [3] [4] [5] [6]
  • Added a NatNet-backed adapter (NatNetOptiTrackClient) that isolates all direct NatNetML usage under OptiTrack.NatNet, handling connection, frame conversion, and event propagation using the new core models. (src/Tracker/OptiTrack/NatNet/NatNetOptiTrackClient.cs, src/Tracker/OptiTrack/NatNet/NatNetFrameConverter.cs) [1] [2]

Telemetry and Privacy:

  • Introduced an internal telemetry abstraction (ITelemetryService, NoOpTelemetryService, TelemetryContext, TelemetryScope, TelemetrySanitizer) to support future privacy-aware error and performance reporting, defaulting to no-op behavior unless explicitly configured. (src/Tracker/OptiTrack/Telemetry/ITelemetryService.cs, src/Tracker/OptiTrack/Telemetry/NoOpTelemetryService.cs) [1] [2]
  • Updated documentation to emphasize privacy boundaries and the need for sanitization in future telemetry integrations. (docs/telemetry.md, docs/architecture.md, docs/developer-guide.md) [1] [2] [3]

Documentation and Versioning:

  • Added detailed developer and architecture documentation, including a Mermaid diagram of the Motive-to-Grasshopper flow and clear guidelines for using the new abstractions and boundaries. (docs/architecture.md, docs/developer-guide.md) [1] [2]
  • Updated assembly metadata, documentation, and changelogs to reflect the new v1.3.0 release. (CHANGELOG.md, README.md, docs/telemetry.md) [1] [2] [3]

This refactor sets up the codebase for easier future upgrades, testing, and privacy-compliant telemetry, while making the plugin architecture clearer and more maintainable.

Copilot AI review requested due to automatic review settings May 12, 2026 14:59
@rhughes42 rhughes42 merged commit 2959c4f into master May 12, 2026
8 checks passed
@rhughes42 rhughes42 deleted the release/v1.3.0-natnet-abstraction branch May 12, 2026 15:02

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR refactors the Tracker Grasshopper plugin to decouple it from direct NatNet SDK usage by introducing SDK-independent OptiTrack core models (OptiTrack.Core), a NatNet-backed adapter (OptiTrack.NatNet), and a default-disabled telemetry boundary (OptiTrack.Telemetry). It also updates docs and versioning to v1.3.0.

Changes:

  • Added OptiTrack.Core domain models + IOptiTrackClient interface and updated the Grasshopper component to consume them.
  • Implemented NatNetOptiTrackClient + NatNetFrameConverter to isolate NatNetML integration and perform frame conversion.
  • Introduced a no-op telemetry abstraction with sanitization utilities and documented architecture/privacy boundaries.

Reviewed changes

Copilot reviewed 23 out of 24 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/Tracker/TrackerComponent.cs Switches Grasshopper component from direct NatNetML usage to IOptiTrackClient + core models; adds telemetry hooks.
src/Tracker/Tracker.csproj Includes new Core/NatNet/Telemetry source files in the project.
src/Tracker/Properties/AssemblyInfo.cs Bumps assembly versions to 1.3.0.
src/Tracker/OptiTrack/Core/IOptiTrackClient.cs Defines SDK-independent client interface used by Grasshopper layer.
src/Tracker/OptiTrack/Core/OptiTrackConnectionInfo.cs Introduces connection info model surfaced through the client boundary.
src/Tracker/OptiTrack/Core/OptiTrackConnectionOptions.cs Introduces connection option model (include flags, frame divisor, connection type).
src/Tracker/OptiTrack/Core/OptiTrackConnectionStatus.cs Defines connection lifecycle status enum.
src/Tracker/OptiTrack/Core/OptiTrackEventArgs.cs Defines event args for connection/frame events.
src/Tracker/OptiTrack/Core/OptiTrackModels.cs Adds SDK-independent frame/marker/rigid body/skeleton models.
src/Tracker/OptiTrack/NatNet/NatNetOptiTrackClient.cs Implements IOptiTrackClient using NatNetML; manages connection, descriptors, eventing.
src/Tracker/OptiTrack/NatNet/NatNetFrameConverter.cs Converts NatNet frames into core models for the Grasshopper layer.
src/Tracker/OptiTrack/Telemetry/ITelemetryService.cs Defines telemetry abstraction interface.
src/Tracker/OptiTrack/Telemetry/NoOpTelemetryService.cs Provides default no-op telemetry implementation.
src/Tracker/OptiTrack/Telemetry/TelemetryContext.cs Adds tag/metric container with sanitization on keys/values.
src/Tracker/OptiTrack/Telemetry/TelemetryEvent.cs Defines telemetry event payload object with timestamp.
src/Tracker/OptiTrack/Telemetry/TelemetrySanitizer.cs Adds privacy-oriented redaction/sanitization helpers.
src/Tracker/OptiTrack/Telemetry/TelemetryScope.cs Adds span-style timing scope that emits duration metrics.
src/Tracker/OptiTrack/Telemetry/TelemetrySeverity.cs Defines severity enum for telemetry messages.
README.md Updates modernization target version reference to v1.3.0.
docs/telemetry.md Documents telemetry boundary and updates example release string to 1.3.0.
docs/developer-guide.md Adds developer guide describing boundaries and project layout.
docs/architecture.md Adds architecture overview + Mermaid diagram and boundary/privacy notes.
CHANGELOG.md Adds v1.3.0 changelog entry describing refactor and docs.
.gitignore Ignores root .vscode/ directory.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

// Create marker labels.
string markerLabel = data.OtherMarkers[ i ].ID.ToString();
mLabels.Add( markerLabel );
foreach ( OptiTrackMarker marker in frame.Markers ) {
Comment on lines +168 to +196
private static void OnConnectionChanged( object sender, OptiTrackConnectionEventArgs e ) {
if ( !string.IsNullOrWhiteSpace( e.Message ) ) {
Log.Add( e.Message );
}
}

/// <summary>
/// [NatNet] Process mocap frame data.
/// </summary>
/// <param name="data"> </param>
/// <exception cref="Exception"> </exception>
private static void ProcessFrameData( FrameOfMocapData data ) {
private static void ClearFrameState() {
currentFrame = null;
Log.Clear();
mPoints.Clear();
mLabels.Clear();
rBodyNames.Clear();
rBodyPlanes.Clear();
}

private static void ProcessFrameData( OptiTrackFrame frame ) {
if ( frame == null ) {
return;
}

mPoints.Clear();
mLabels.Clear();
rBodyNames.Clear();
rBodyPlanes.Clear();

Point3d markerPoint;
bool createPoints = false; // Set tp true to create points for each marker.
bool allPoints = false; // Set to true to output all marker points.

// [NatNet] Fetch Labeled Markers (Point Cloud)
for ( int i = 0; i < data.nOtherMarkers; i++ ) {
if ( createPoints ) {
// Recreate point data.
markerPoint = new Point3d(
Math.Round( data.LabeledMarkers[ i ].x, 4 ),
Math.Round( data.LabeledMarkers[ i ].y, 4 ),
Math.Round( data.LabeledMarkers[ i ].z, 4 ) );

mPoints.Add( markerPoint );

if ( allPoints ) {
for ( int j = 0; j < data.MarkerSets[ i ].nMarkers; j++ ) {
// Recreate point data.
markerPoint = new Point3d(
Math.Round( data.LabeledMarkers[ i ].x, 4 ),
Math.Round( data.LabeledMarkers[ i ].y, 4 ),
Math.Round( data.LabeledMarkers[ i ].z, 4 ) );

mPoints.Add( markerPoint );
}
}
}
Log.Clear();
foreach ( string message in frame.StatusMessages ) {
Log.Add( message );
}
Comment on lines +30 to +51
for ( int i = 0; i < rigidBodyDescriptions.Count; i++ ) {
RigidBody description = rigidBodyDescriptions[ i ];

for ( int j = 0; j < data.nRigidBodies; j++ ) {
RigidBodyData rigidBodyData = data.RigidBodies[ j ];
if ( !rigidBodyData.Tracked ) {
continue;
}

rigidBodies.Add( new OptiTrackRigidBody {
Id = description.ID,
Name = description.Name,
IsTracked = rigidBodyData.Tracked,
X = Math.Round( rigidBodyData.x, 4 ),
Y = Math.Round( rigidBodyData.y, 4 ),
Z = Math.Round( rigidBodyData.z, 4 ),
Qx = Math.Round( rigidBodyData.qx, 6 ),
Qy = Math.Round( rigidBodyData.qy, 6 ),
Qz = Math.Round( rigidBodyData.qz, 6 ),
Qw = Math.Round( rigidBodyData.qw, 6 )
} );
}
Comment on lines +81 to +90
natNetClient.Connect( connectParams );
FetchServerDescriptor();
FetchDataDescriptions();
natNetClient.OnFrameReady += OnFrameReady;
}

SetConnectionStatus( OptiTrackConnectionStatus.Connected, "Success: Data Port Connected." );
} catch ( Exception exception ) {
telemetryService.CaptureException( exception, new TelemetryContext().SetTag( "operation", "natnet_connect" ) );
SetConnectionStatus( OptiTrackConnectionStatus.Faulted, "Error: Failed to connect. Check the connection settings." );

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@copilot implement these suggestions.

Comment on lines +21 to +24
private List<ForcePlate> forcePlates;
private List<string> statusMessages;
private bool assetsChanged;

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@codex[agent] Implement the signalling mechanism mentioned here if it makes sense and is obvious as to how to do so. If it doesn't make sense to you, then remove the unchanged variable.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Comment on lines +124 to +126
IncludeRigidBodies = RigidBody,
IncludeSkeletons = Skeleton,
IncludeForcePlates = ForcePlate,

@rhughes42 rhughes42 left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

@copilot fix the issue with Rigid body conversion currently nests for each rigidBodyDescriptions inside for each data.RigidBodies without any ID match. This produces duplicates (N descriptions × M tracked bodies) and also assigns the description's ID/Name to every tracked rigid body regardless of which body the frame data belongs to. Convert by matching the frame rigid body data to the corresponding description (typically by rigid body ID), and emit at most one OptiTrackRigidBody per tracked rigid body.

@rhughes42 rhughes42 restored the release/v1.3.0-natnet-abstraction branch May 12, 2026 15:17
@rhughes42 rhughes42 deleted the release/v1.3.0-natnet-abstraction branch May 14, 2026 15:30
@rhughes42 rhughes42 restored the release/v1.3.0-natnet-abstraction branch May 14, 2026 15:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants