NrxDebugVisualizer shows objects.

TODO: Update SceneTreeView after adding new objects
This commit is contained in:
Matthias Heil
2026-04-15 15:59:42 +02:00
parent 4549022153
commit 001b945ea1
12 changed files with 233 additions and 80 deletions
+1 -3
View File
@@ -1,6 +1,4 @@
using System; using System.Threading.Tasks;
using System.Numerics;
using System.Threading.Tasks;
namespace NamedPipes; namespace NamedPipes;
public interface IDebugVisualizer public interface IDebugVisualizer
+14 -7
View File
@@ -10,18 +10,25 @@ namespace NamedPipes;
/// </summary> /// </summary>
public class PipeSerializer : IPipeSerializer public class PipeSerializer : IPipeSerializer
{ {
private JsonSerializerOptions options = new()
{
IncludeFields = true
};
public object Deserialize(byte[] data, Type type) public object Deserialize(byte[] data, Type type)
{ {
return JsonSerializer.Deserialize(data, type) ?? throw new InvalidDataException($"Can not deserialize to type: {type} ");
var obj = JsonSerializer.Deserialize(data, type, options) ?? throw new InvalidDataException($"Can not deserialize to type: {type} ");
return obj;
} }
public byte[] Serialize(object o) public byte[] Serialize(object o)
{ {
using (var memoryStream = new MemoryStream())
using (var utf8JsonWriter = new Utf8JsonWriter(memoryStream)) using var memoryStream = new MemoryStream();
{ using var utf8JsonWriter = new Utf8JsonWriter(memoryStream);
JsonSerializer.Serialize(utf8JsonWriter, o); JsonSerializer.Serialize(utf8JsonWriter, o, options);
return memoryStream.ToArray(); var data = memoryStream.ToArray();
} return data;
} }
} }
+2 -1
View File
@@ -5,6 +5,7 @@
<!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. --> <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
<Application.Styles> <Application.Styles>
<FluentTheme /> <SimpleTheme />
<DockSimpleTheme />
</Application.Styles> </Application.Styles>
</Application> </Application>
+7 -6
View File
@@ -1,19 +1,21 @@
using Avalonia; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Data.Core;
using Avalonia.Data.Core.Plugins;
using Avalonia.Markup.Xaml; using Avalonia.Markup.Xaml;
using Controls.Utilities;
using NrxDebugVisualizer.ViewModels; using NrxDebugVisualizer.ViewModels;
using NrxDebugVisualizer.Views; using NrxDebugVisualizer.Views;
using System.Linq;
namespace NrxDebugVisualizer; namespace NrxDebugVisualizer;
public partial class App : Application public partial class App : Application
{ {
public static Settings? Settings { get; set; }
public override void Initialize() public override void Initialize()
{ {
AvaloniaXamlLoader.Load(this); AvaloniaXamlLoader.Load(this);
#if DEBUG
this.AttachDeveloperTools();
#endif
} }
public override void OnFrameworkInitializationCompleted() public override void OnFrameworkInitializationCompleted()
@@ -24,10 +26,9 @@ public partial class App : Application
{ {
DataContext = new MainWindowViewModel(), DataContext = new MainWindowViewModel(),
}; };
} Settings = Settings.Load(desktop.MainWindow);
}
base.OnFrameworkInitializationCompleted(); base.OnFrameworkInitializationCompleted();
} }
} }
+9 -45
View File
@@ -1,22 +1,25 @@
using NamedPipes; using NamedPipes;
using NrxDebugVisualizer.Scenes; using NrxDebugVisualizer.Scenes;
using Num.Roto.Visualization.Math.Geometry; using Num.Roto.Visualization.Math.Geometry;
using Num.Roto.Visualization.Math.Utilities;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Threading; using System.Threading;
using KontractFrame = System.ValueTuple<System.Numerics.Vector3, System.Numerics.Quaternion, float>;
namespace NrxDebugVisualizer.Models; namespace NrxDebugVisualizer.Models;
internal sealed class PipeServer internal sealed class PipeServer
{ {
private string PipeName { get; } private string PipeName { get; }
private NamedPipesServer? NamedPipesServer { get; set; } private NamedPipesServer? NamedPipesServer { get; set; }
public PipeServer(string pipeName, DebugVisualizerScene debuggerScene,CancellationToken cancellationToken) private Action<VisualizerModel> UpdateAction { get; init; }
public PipeServer(string pipeName, Action<VisualizerModel> updateAction,CancellationToken cancellationToken)
{ {
PipeName = pipeName; PipeName = pipeName;
DebugVisualizerScene = debuggerScene; UpdateAction = updateAction;
StartPipeServer(cancellationToken); StartPipeServer(cancellationToken);
} }
private void NamedPipesServer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e) private void NamedPipesServer_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -24,56 +27,17 @@ internal sealed class PipeServer
if (e.PropertyName != nameof(NamedPipesServer.VisualizerModel)) return; if (e.PropertyName != nameof(NamedPipesServer.VisualizerModel)) return;
var visualizerObject = NamedPipesServer?.VisualizerModel; var visualizerObject = NamedPipesServer?.VisualizerModel;
if (visualizerObject is null) return; if (visualizerObject is null) return;
UpdateAction(visualizerObject);
SetPoint(visualizerObject.Point);
SetPointList(visualizerObject.PointArray);
SetFrame(visualizerObject.Frame);
SetFrameList(visualizerObject.FrameArray);
} }
#region NamedPipesServer #region NamedPipesServer
private async void StartPipeServer(CancellationToken cancellationToken) private async void StartPipeServer(CancellationToken cancellationToken)
{ {
NamedPipesServer = new NamedPipesServer(PipeName, logger: Console.WriteLine); NamedPipesServer = new NamedPipesServer(PipeName, logger: (s) => Log.Info(s,0));
NamedPipesServer.PropertyChanged += NamedPipesServer_PropertyChanged; NamedPipesServer.PropertyChanged += NamedPipesServer_PropertyChanged;
await NamedPipesServer.RunAsync(cancellationToken); await NamedPipesServer.RunAsync(cancellationToken).ConfigureAwait(false);
} }
#endregion #endregion
#region DebugVisualizerScene
private DebugVisualizerScene DebugVisualizerScene { get; }
private static Frame ToFrame(KontractFrame kontractFrame)
{
var (translation, orientation, scale) = kontractFrame;
return new Frame(translation, scale, orientation);
}
private static Frame[] ToFrameArray(IEnumerable<KontractFrame> frameList)
{
return [.. frameList.Select(ToFrame)];
}
private void SetPoint(Vector3? point)
{
if (point is null) return;
DebugVisualizerScene.AddPointGeometry(point.Value, 20f);
}
private void SetPointList(Vector3[]? pointList)
{
if (pointList == null || pointList.Length < 1) return;
DebugVisualizerScene.AddPointListGeometry(pointList, 5);
}
private void SetFrame(KontractFrame? kontractFrame)
{
if (kontractFrame is null) return;
DebugVisualizerScene.AddFrameGeometry(ToFrame(kontractFrame.Value), 1);
}
private void SetFrameList(KontractFrame[]? frameArray)
{
if (frameArray == null || frameArray.Length < 1) return;
DebugVisualizerScene.AddFrameListGeometry(ToFrameArray(frameArray), 1);
}
#endregion
} }
@@ -24,6 +24,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\Num.Roto.Visualization\Avalonia\Controls\Controls.csproj" />
<ProjectReference Include="..\..\Num.Roto.Visualization\Math\Num.Roto.Visualization.Math.csproj" /> <ProjectReference Include="..\..\Num.Roto.Visualization\Math\Num.Roto.Visualization.Math.csproj" />
<ProjectReference Include="..\..\Num.Roto.Visualization\SceneGraph\Num.Roto.Visualization.SceneGraph.csproj" /> <ProjectReference Include="..\..\Num.Roto.Visualization\SceneGraph\Num.Roto.Visualization.SceneGraph.csproj" />
<ProjectReference Include="..\NamedPipes\NamedPipes.csproj" /> <ProjectReference Include="..\NamedPipes\NamedPipes.csproj" />
@@ -1,8 +1,127 @@
using CommunityToolkit.Mvvm.ComponentModel; using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using CommunityToolkit.Mvvm.Input;
using Controls.ViewModels;
using NamedPipes;
using NrxDebugVisualizer.Models;
using NrxDebugVisualizer.Scenes;
using Num.Roto.Visualization.Math.Geometry;
using Num.Roto.Visualization.Math.Utilities;
using Num.Roto.Visualization.Types;
using Num.Roto.Visualization.VulkanLib.SceneInteraction;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Threading;
using KontractFrame = System.ValueTuple<System.Numerics.Vector3, System.Numerics.Quaternion, float>;
namespace NrxDebugVisualizer.ViewModels; namespace NrxDebugVisualizer.ViewModels;
public partial class MainWindowViewModel : ObservableObject internal sealed partial class MainWindowViewModel : ViewModelBase
{ {
public string Greeting { get; } = "Welcome to Avalonia!"; public ProportionalDockControlViewModel ProportionalDockControlViewModel { get; } = new();
public VulkanSceneControlViewModel VulkanSceneControlViewModel => ProportionalDockControlViewModel.VulkanSceneControlViewModel;
public SceneTreeViewViewModel SceneTreeViewViewModel => ProportionalDockControlViewModel.SceneTreeViewViewModel?? throw new InvalidOperationException("SceneTreeViewViewModel is not available");
public SceneInteraction SceneInteraction => ProportionalDockControlViewModel.SceneInteraction;
public DebugVisualizerScene DebugVisualizerScene => ProportionalDockControlViewModel.Scene as DebugVisualizerScene ?? throw new InvalidOperationException("Scene is not a DebugVisualizerScene");
private PipeServer PipeServer {get;init;}
public MainWindowViewModel()
{
ProportionalDockControlViewModel.Scene = new DebugVisualizerScene();
ProportionalDockControlViewModel.Scene.SelectedCamera.DefaultFrame = CameraView.Frame(ViewPosition);
PipeServer = new PipeServer(nameof(NrxDebugVisualizer), UpdateScene, cancellationToken: CancellationToken.None);
}
#region Commands
[RelayCommand]
public static void ShowLogViewer()
{
Log.LogViewerEnabled = true;
}
[RelayCommand]
private static void Exit(object? parameter)
{
if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktopApp)
{
desktopApp.Shutdown();
}
}
[RelayCommand]
internal void ResetCamera()
{
ProportionalDockControlViewModel.VulkanSceneControlViewModel.ResetCamera(CameraView.Frame(ViewPosition));
}
[RelayCommand]
internal void FitToWindow()
{
ProportionalDockControlViewModel.Scene?.FitToWindow(0.95f);
ProportionalDockControlViewModel.VulkanSceneControlViewModel.SceneInteraction.Update();
}
#endregion
#region Camera View
public static IEnumerable<CameraView.Position> ViewPositions => Enum.GetValues<CameraView.Position>();
private CameraView.Position _viewPosition = CameraView.Position.Custom1;
public CameraView.Position ViewPosition
{
get => _viewPosition;
set
{
if (_viewPosition == value) return;
_viewPosition = value;
OnPropertyChanged();
if (_viewPosition == CameraView.Position.User) return;
ProportionalDockControlViewModel.Scene?.SelectedCamera.DefaultFrame = CameraView.Frame(_viewPosition);
FitToWindow();
}
}
#endregion
#region Pipe Server
private void UpdateScene(VisualizerModel visualizerModel)
{
SetPoint(visualizerModel.Point);
SetPointList(visualizerModel.PointArray);
SetFrame(visualizerModel.Frame);
SetFrameList(visualizerModel.FrameArray);
SceneInteraction.Update();
}
private static Frame ToFrame(KontractFrame kontractFrame)
{
var (translation, orientation, scale) = kontractFrame;
return new Frame(translation, scale, orientation);
}
private static Frame[] ToFrameArray(IEnumerable<KontractFrame> frameList)
{
return [.. frameList.Select(ToFrame)];
}
private void SetPoint(Vector3? point)
{
if (point is null) return;
DebugVisualizerScene.AddPointGeometry(point.Value, 5f);
}
private void SetPointList(Vector3[]? pointList)
{
if (pointList == null || pointList.Length < 1) return;
DebugVisualizerScene.AddPointListGeometry(pointList, 5);
}
private void SetFrame(KontractFrame? kontractFrame)
{
if (kontractFrame is null) return;
DebugVisualizerScene.AddFrameGeometry(ToFrame(kontractFrame.Value), 1);
}
private void SetFrameList(KontractFrame[]? frameArray)
{
if (frameArray == null || frameArray.Length < 1) return;
DebugVisualizerScene.AddFrameListGeometry(ToFrameArray(frameArray), 1);
}
#endregion
} }
+36 -5
View File
@@ -6,14 +6,45 @@
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450" mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="NrxDebugVisualizer.Views.MainWindow" x:Class="NrxDebugVisualizer.Views.MainWindow"
x:DataType="vm:MainWindowViewModel" x:DataType="vm:MainWindowViewModel"
xmlns:viewModels="clr-namespace:NrxDebugVisualizer.ViewModels"
xmlns:controls="clr-namespace:Controls.Views;assembly=Controls"
Icon="/Assets/avalonia-logo.ico" Icon="/Assets/avalonia-logo.ico"
Title="NrxDebugVisualizer"> Title="NrxDebugVisualizer"
Width="1200"
Height="800"
MinWidth="800"
MinHeight="600">
<Design.DataContext> <Design.DataContext>
<!-- This only sets the DataContext for the previewer in an IDE, <viewModels:MainWindowViewModel />
to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
<vm:MainWindowViewModel/>
</Design.DataContext> </Design.DataContext>
<DockPanel LastChildFill="True" Background="LightGray" >
<Menu DockPanel.Dock="Top" FontSize="12">
<MenuItem Header="_File">
<MenuItem Header="_Exit" Command="{Binding ExitCommand}" />
</MenuItem>
<MenuItem Header="_Options" VerticalAlignment="Center">
<MenuItem Header="_ShowLogViewer" Command="{Binding ShowLogViewerCommand}" />
</MenuItem>
<MenuItem Header="_Camera">
<MenuItem Header="_Reset" Command="{Binding ResetCameraCommand }" />
<MenuItem Header="_Fit" Command="{Binding FitToWindowCommand}" />
<DropDownButton Content="View">
<Button.Flyout>
<Flyout Placement="Right">
<ListBox ItemsSource="{Binding ViewPositions}" SelectedItem="{Binding ViewPosition}" SelectionMode="Single" />
</Flyout>
</Button.Flyout>
</DropDownButton>
</MenuItem>
</Menu>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" Background="DarkGray" >
<Label Content="{Binding ProportionalDockControlViewModel.VulkanSceneControlViewModel.Fps,FallbackValue=Unset}" IsHitTestVisible="False" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" />
<Label Content="{Binding ProportionalDockControlViewModel.VulkanSceneControlViewModel.ObjectInfo,FallbackValue=Unset}" IsHitTestVisible="False" VerticalContentAlignment="Stretch" VerticalAlignment="Stretch" HorizontalContentAlignment="Stretch" />
</StackPanel>
<controls:ProportionalDockControl DataContext="{Binding ProportionalDockControlViewModel}"/>
</DockPanel>
</Window> </Window>
@@ -1,4 +1,10 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Interactivity;
using NrxDebugVisualizer.ViewModels;
using Num.Roto.Visualization.Types;
using System.Data;
using System.Globalization;
using System.Threading;
namespace NrxDebugVisualizer.Views; namespace NrxDebugVisualizer.Views;
@@ -6,6 +12,29 @@ public partial class MainWindow : Window
{ {
public MainWindow() public MainWindow()
{ {
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
InitializeComponent(); InitializeComponent();
Closing += OnClosing;
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
if (DataContext is not MainWindowViewModel viewModel) throw new DataException("wrong viewModel type!");
if (App.Settings?.CameraFrame is not { } cameraFrame) return;
var scene = viewModel.ProportionalDockControlViewModel.Scene;
var camera = scene?.SelectedCamera;
camera?.Frame = cameraFrame;
viewModel.ViewPosition = CameraView.Position.User;
}
private async void OnClosing(object? sender, System.ComponentModel.CancelEventArgs e)
{
if (DataContext is not MainWindowViewModel viewModel) throw new DataException("wrong viewModel type!");
var scene = viewModel.ProportionalDockControlViewModel.Scene;
var camera = scene?.SelectedCamera;
App.Settings?.MainWindow = this;
App.Settings?.CameraFrame = camera?.Frame;
App.Settings?.Save();
} }
} }
+5 -3
View File
@@ -13,10 +13,12 @@ VisualizerModel visualizerModel = new()
}; };
while (true) while (true)
{ {
var pipeClient = new NamedPipeClient("TestPipe", @".\","TestServer",logger:Console.WriteLine); var serverName = "NrxDebugVisualizer";
var pipeClient = new NamedPipeClient(serverName, @".\", serverName, logger:Console.WriteLine);
Thread.Sleep(1000); Thread.Sleep(1000);
visualizerModel.Point = new System.Numerics.Vector3(count, count * 2, count * 3);
count++;
await pipeClient.SetVisualizerModelAsync(visualizerModel); await pipeClient.SetVisualizerModelAsync(visualizerModel);
pipeClient.SetMessageAsync($"Hello from TestClient " + count++).GetAwaiter().GetResult(); //await pipeClient.SetMessageAsync($"Hello from TestClient " + count++);
//pipeClient.SetDebugObjectAsync(new DebugObject{Type = typeof(string).FullName,Data = Encoding.GetEncoding("UTF-8").GetBytes(message)}).GetAwaiter().GetResult();
pipeClient.Dispose(); pipeClient.Dispose();
} }
+1 -1
View File
@@ -1,6 +1,6 @@
using System; using System;
using System.Threading; using System.Threading;
var pipeServer = new NamedPipes.NamedPipesServer("TestPipe", logger:Console.WriteLine); var pipeServer = new NamedPipes.NamedPipesServer("NrxDebugVisualizer", logger:Console.WriteLine);
await pipeServer.RunAsync(CancellationToken.None).ConfigureAwait(true); await pipeServer.RunAsync(CancellationToken.None).ConfigureAwait(true);
+1 -1
View File
@@ -2,7 +2,7 @@
"profiles": { "profiles": {
"TestServer": { "TestServer": {
"commandName": "Project", "commandName": "Project",
"workingDirectory": "C:\\Users\\heilm\\Documents\\Visual Studio 2026\\Visualizers" "workingDirectory": "C:\\Users\\matth\\Documents\\Visual Studio 2026\\Visualizers"
} }
} }
} }