Add 3d Viewer

This commit is contained in:
Evgeny Redikultsev
2025-11-30 17:34:04 +05:00
parent f381229a63
commit 96e7e9a587
20 changed files with 541 additions and 12 deletions

View File

@@ -12,5 +12,8 @@ namespace FieldVisualizer.Entities.Values.Primitives
IPoint2D Point1 { get; set; }
IPoint2D Point2 { get; set; }
IPoint2D Point3 { get; set; }
double ValuePoint1 { get; set; }
double ValuePoint2 { get; set; }
double ValuePoint3 { get; set; }
}
}

View File

@@ -23,5 +23,9 @@ namespace FieldVisualizer.Entities.Values.Primitives
(Point2.X - Point1.X) * (Point3.Y - Point1.Y) -
(Point3.X - Point1.X) * (Point2.Y - Point1.Y)
);
public double ValuePoint1 { get; set; }
public double ValuePoint2 { get; set; }
public double ValuePoint3 { get; set; }
}
}

View File

@@ -0,0 +1,126 @@
using HelixToolkit.SharpDX;
using HelixToolkit.Wpf.SharpDX;
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
using System.Windows.Media;
using System.Windows.Media.Media3D;
namespace StructureHelper.Infrastructure
{
public class HelixViewModelBase : ViewModelBase
{
private string subTitle = string.Empty;
private string title = string.Empty;
private DefaultEffectsManager effectsManager;
private HelixToolkit.Wpf.SharpDX.Camera camera;
private Color ambientLightColor;
private Viewport3DX viewPort3D;
private Vector3D directionalLightDirection;
private Stream? backgroundTexture;
public HelixToolkit.Wpf.SharpDX.Camera Camera
{
get => camera;
set
{
camera = value;
OnPropertyChanged(nameof(Camera));
}
}
public System.Windows.Media.Color AmbientLightColor
{
get => ambientLightColor;
set
{
ambientLightColor = value;
OnPropertyChanged(nameof(AmbientLightColor));
}
}
public System.Windows.Media.Color DirectionalLightColor { get; set; }
public Vector3D DirectionalLightDirection
{
get => directionalLightDirection;
set
{
directionalLightDirection = value;
OnPropertyChanged(nameof(DirectionalLightDirection));
}
}
public Stream? BackgroundTexture
{
get => backgroundTexture;
set
{
backgroundTexture = value;
OnPropertyChanged(nameof(BackgroundTexture));
}
}
public Viewport3DX Viewport3D
{
get => viewPort3D;
set
{
viewPort3D = value;
OnPropertyChanged(nameof(Viewport3DX));
}
}
public DefaultEffectsManager EffectsManager
{
get => effectsManager;
set
{
effectsManager = value;
OnPropertyChanged(nameof(EffectsManager));
}
}
public string Title
{
get => title;
set
{
title = value;
OnPropertyChanged(nameof(Title));
}
}
public string SubTitle
{
get => subTitle;
set
{
subTitle = value;
OnPropertyChanged(nameof(SubTitle));
}
}
public HelixViewModelBase()
{
EffectsManager = new DefaultEffectsManager();
Camera = new HelixToolkit.Wpf.SharpDX.OrthographicCamera
{
Position = new Point3D(3, 3, 5),
LookDirection = new Vector3D(-3, -3, -5),
UpDirection = new Vector3D(0, 1, 0),
FarPlaneDistance = 50000
};
// setup lighting
AmbientLightColor = Colors.DimGray;
DirectionalLightColor = Colors.White;
DirectionalLightDirection = new Vector3D(-2, -5, -2);
BackgroundTexture =
BitmapExtensions.CreateLinearGradientBitmapStream(EffectsManager, 128, 128, Direct2DImageFormat.Bmp,
new Vector2(0, 0), new Vector2(0, 128), new SharpDX.Direct2D1.GradientStop[]
{
new(){ Color = Colors.White.ToRawColor4(), Position = 0f },
new(){ Color = Colors.DarkGray.ToRawColor4(), Position = 1f }
});
}
}
}

View File

@@ -508,6 +508,17 @@
</Canvas.Children>
</Canvas>
</DataTemplate>
<DataTemplate x:Key="IsoField3DResult">
<Canvas Style="{DynamicResource ButtonResultCanvas}">
<Canvas.Children>
<ContentControl ContentTemplate="{DynamicResource ButtonResultRectangle}"/>
<Line X1="10" Y1="5" X2="10" Y2="18" Stroke="Black"/>
<Line X1="10" Y1="18" X2="5" Y2="27" Stroke="Black"/>
<Line X1="10" Y1="18" X2="27" Y2="18" Stroke="Black"/>
<Path Margin="4" Data="M 6 2 A 20 10 60 0 1 22 14 A 20 10 10 0 1 3 22 A 20 10 -50 0 1 6 2 z" Fill="Gray" Opacity="0.6"/>
</Canvas.Children>
</Canvas>
</DataTemplate>
<DataTemplate x:Key="IsoFieldResult">
<Canvas Style="{DynamicResource ButtonResultCanvas}">
<Canvas.Children>

View File

@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace StructureHelper.Services.Reports.CalculationReports
{
public interface IIsoField3DReport : IReport
{
}
}

View File

@@ -0,0 +1,37 @@
using FieldVisualizer.Entities.Values.Primitives;
using FieldVisualizer.WindowsOperation;
using StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews;
using System;
using System.Collections.Generic;
using System.Text;
namespace StructureHelper.Services.Reports.CalculationReports
{
public class IsoField3DReport : IIsoField3DReport
{
private IEnumerable<IPrimitiveSet> primitiveSets;
private IsoField3DViewerViewModel viewModel;
public void Prepare()
{
viewModel = new IsoField3DViewerViewModel(primitiveSets);
}
public void ShowPrepared()
{
var wnd = new IsoField3DViewerView(viewModel);
wnd.Show();
}
public void Show()
{
Prepare();
ShowPrepared();
}
public IsoField3DReport(IEnumerable<IPrimitiveSet> primitiveSets)
{
this.primitiveSets = primitiveSets;
}
}
}

View File

@@ -1,11 +1,6 @@
using FieldVisualizer.Entities.Values.Primitives;
using FieldVisualizer.Services.PrimitiveServices;
using FieldVisualizer.WindowsOperation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace StructureHelper.Services.Reports.CalculationReports
{

View File

@@ -9,6 +9,7 @@ using StructureHelperLogics.NdmCalculations.Cracking;
using StructureHelperLogics.NdmCalculations.Triangulations;
using System;
using System.Collections.Generic;
using System.Runtime.Intrinsics.Arm;
namespace StructureHelper.Services.ResultViewers
{
@@ -83,7 +84,8 @@ namespace StructureHelper.Services.ResultViewers
}
else if (ndm is ITriangleNdm triangle)
{
valuePrimitive = ProcessTriangle(triangle, val);
//valuePrimitive = ProcessTriangle(triangle, val);
valuePrimitive = ProcessTriangle(strainMatrix, valDelegate, triangle);
}
else
{
@@ -92,18 +94,39 @@ namespace StructureHelper.Services.ResultViewers
return valuePrimitive;
}
private static IValuePrimitive ProcessTriangle(ITriangleNdm triangle, double val)
private static IValuePrimitive ProcessTriangle(IStrainMatrix strainMatrix, ForceResultFunc valDelegate, ITriangleNdm triangle)
{
double delegateResult = valDelegate.ResultFunction.Invoke(strainMatrix, triangle);
double val = delegateResult * valDelegate.UnitFactor;
var moqNdm1 = new Ndm() { CenterX = triangle.Point1.X, CenterY = triangle.Point1.Y, Area = triangle.Area, Material = triangle.Material };
var moqNdm2 = new Ndm() { CenterX = triangle.Point2.X, CenterY = triangle.Point2.Y, Area = triangle.Area, Material = triangle.Material };
var moqNdm3 = new Ndm() { CenterX = triangle.Point3.X, CenterY = triangle.Point3.Y, Area = triangle.Area, Material = triangle.Material };
var primitive = new TrianglePrimitive()
{
Point1 = new Point2D() { X = triangle.Point1.X, Y = triangle.Point1.Y },
Point2 = new Point2D() { X = triangle.Point2.X, Y = triangle.Point2.Y },
Point3 = new Point2D() { X = triangle.Point3.X, Y = triangle.Point3.Y },
Value = val
Value = val,
ValuePoint1 = valDelegate.ResultFunction.Invoke(strainMatrix, moqNdm1) * valDelegate.UnitFactor,
ValuePoint2 = valDelegate.ResultFunction.Invoke(strainMatrix, moqNdm2) * valDelegate.UnitFactor,
ValuePoint3 = valDelegate.ResultFunction.Invoke(strainMatrix, moqNdm3) * valDelegate.UnitFactor,
};
return primitive;
}
//private static IValuePrimitive ProcessTriangle(ITriangleNdm triangle, double val)
//{
// var primitive = new TrianglePrimitive()
// {
// Point1 = new Point2D() { X = triangle.Point1.X, Y = triangle.Point1.Y },
// Point2 = new Point2D() { X = triangle.Point2.X, Y = triangle.Point2.Y },
// Point3 = new Point2D() { X = triangle.Point3.X, Y = triangle.Point3.Y },
// Value = val
// };
// return primitive;
//}
private static IValuePrimitive ProcessRectangle(IRectangleNdm shapeNdm, double val)
{
return new RectanglePrimitive()

View File

@@ -61,7 +61,8 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" Version="8.4.0" />
<PackageReference Include="Autofac" Version="9.0.0" />
<PackageReference Include="HelixToolkit.Wpf.SharpDX" Version="3.1.2" />
<PackageReference Include="LiveChartsCore" Version="2.0.0-rc4.5" />
<PackageReference Include="LiveCharts" Version="0.9.7" />
<PackageReference Include="LiveCharts.Wpf" Version="0.9.7" />

View File

@@ -72,6 +72,9 @@
<Compile Update="Windows\CalculationWindows\CalculatorsViews\ForceCalculatorViews\ForceResultLogic\LimitCurveDataView.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Windows\CalculationWindows\CalculatorsViews\ForceCalculatorViews\IsoField3DViewerView.xaml.cs">
<SubType>Code</SubType>
</Compile>
<Compile Update="Windows\CalculationWindows\CalculatorsViews\ForceCalculatorViews\UserControls\LimitCurveControl.xaml.cs">
<SubType>Code</SubType>
</Compile>

View File

@@ -0,0 +1,11 @@
using StructureHelper.Infrastructure;
using System;
using System.Collections.Generic;
using System.Text;
namespace StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews
{
public class ContourViewportViewModel : HelixViewModelBase
{
}
}

View File

@@ -111,6 +111,16 @@
<ContentControl ContentTemplate="{DynamicResource IsoFieldResult}"/>
</Viewbox>
</Button>
<Button Style="{DynamicResource ToolButton}" Command="{Binding ShowIsoField3DCommand}">
<Button.ToolTip>
<uc:ButtonToolTipEh HeaderText="Show isofield results"
IconContent="{StaticResource IsoField3DResult}"
DescriptionText="Show graphical results with color as function of result value"/>
</Button.ToolTip>
<Viewbox>
<ContentControl ContentTemplate="{DynamicResource IsoField3DResult}"/>
</Viewbox>
</Button>
</ToolBar>
<ToolBar>
<Button Style="{DynamicResource ToolButton}" Command="{Binding ShowGeometryResultCommand}">

View File

@@ -70,6 +70,7 @@ namespace StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalcu
private ICommand? showInteractionDiagramCommand;
private ICommand? graphValuepointsCommand;
private ICommand showForceResultCommand;
private RelayCommand showIsoField3DCommand;
public ValidResultCounterVM ValidResultCounter { get; }
@@ -133,6 +134,41 @@ namespace StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalcu
}, o => SelectedResult != null && SelectedResult.IsValid));
}
}
public ICommand ShowIsoField3DCommand
{
get
{
return showIsoField3DCommand ??= new RelayCommand(o =>
{
if (SelectPrimitives() == true)
{
ShowIsoField3D();
}
}, o => SelectedResult != null && SelectedResult.IsValid);
}
}
private void ShowIsoField3D()
{
try
{
IStrainMatrix strainMatrix = SelectedResult.ForcesTupleResult.LoaderResults.ForceStrainPair.StrainMatrix;
var primitiveSets = ShowIsoFieldResult.GetPrimitiveSets(strainMatrix, ndms, ForceResultFuncFactory.GetResultFuncs());
var report = new IsoField3DReport(primitiveSets);
report.Show();
}
catch (Exception ex)
{
var vm = new ErrorProcessor()
{
ShortText = "Errors apearred during showing isofield, see detailed information",
DetailText = $"{ex}"
};
new ErrorMessage(vm).ShowDialog();
}
}
public ICommand ExportToCSVCommand => exportToCSVCommand ??= new RelayCommand(o => { ExportToCSV();});
private void ExportToCSV()
{

View File

@@ -0,0 +1,79 @@
<Window x:Class="StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews.IsoField3DViewerView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews"
xmlns:hx="http://helix-toolkit.org/wpf/SharpDX"
d:DataContext="{d:DesignInstance local:IsoField3DViewerViewModel}"
mc:Ignorable="d"
Title="Contour 3DViewer" Height="450" Width="800" MinHeight="300" MinWidth="300" MaxHeight="1000" MaxWidth="1400" WindowStartupLocation="CenterScreen">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="200"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<ListBox Name="SetsList" ItemsSource="{Binding PrimitiveSets}" SelectedItem="{Binding SelectedPrimitiveSet}">
<ListBox.ItemTemplate>
<DataTemplate>
<Grid Height="20">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="1" Text="{Binding Path=Name}" Width="270"/>
</Grid>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
<Grid Grid.Column="1">
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="40"/>
</Grid.RowDefinitions>
<hx:Viewport3DX x:Name="View3D" DataContext="{Binding ViewportViewModel}" Title="{Binding Title}"
Grid.Row="0"
BackgroundColor="White"
Camera="{Binding Camera}"
CoordinateSystemLabelForeground="#434343"
EffectsManager="{Binding EffectsManager}"
EnableDesignModeRendering="True"
FXAALevel="Low"
EnableSwapChainRendering="False"
ModelUpDirection="{Binding UpDirection}"
ShowCoordinateSystem="True"
SubTitle="{Binding SubTitle}"
TextBrush="Black"
UseDefaultGestures="False">
<hx:Viewport3DX.InputBindings>
<KeyBinding Key="B"
Command="hx:ViewportCommands.BackView" />
<KeyBinding Key="F"
Command="hx:ViewportCommands.FrontView" />
<KeyBinding Key="U"
Command="hx:ViewportCommands.TopView" />
<KeyBinding Key="D"
Command="hx:ViewportCommands.BottomView" />
<KeyBinding Key="L"
Command="hx:ViewportCommands.LeftView" />
<KeyBinding Key="R"
Command="hx:ViewportCommands.RightView" />
<KeyBinding Command="hx:ViewportCommands.ZoomExtents"
Gesture="Control+E" />
<MouseBinding Command="hx:ViewportCommands.Rotate"
Gesture="RightClick" />
<MouseBinding Command="hx:ViewportCommands.Zoom"
Gesture="MiddleClick" />
<MouseBinding Command="hx:ViewportCommands.Pan"
Gesture="LeftClick" />
</hx:Viewport3DX.InputBindings>
<hx:AmbientLight3D Color="{Binding AmbientLightColor}" />
<hx:DirectionalLight3D Direction="{Binding Camera.LookDirection}"
Color="{Binding DirectionalLightColor}" />
<hx:ScreenQuadModel3D Texture="{Binding BackgroundTexture}" />
</hx:Viewport3DX>
<Slider Grid.Row="1" Minimum="0" Maximum="100" Value="{Binding UserZoomFactor}"/>
</Grid>
</Grid>
</Window>

View File

@@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
namespace StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews
{
/// <summary>
/// Interaction logic for ISoField3DViewerView.xaml
/// </summary>
public partial class IsoField3DViewerView : Window
{
IsoField3DViewerViewModel viewModel;
public IsoField3DViewerView(IsoField3DViewerViewModel viewModel)
{
InitializeComponent();
this.viewModel = viewModel;
this.DataContext = viewModel;
viewModel.ViewportViewModel.Viewport3D = View3D;
viewModel.Refresh();
}
}
}

View File

@@ -0,0 +1,148 @@
using FieldVisualizer.Entities.ColorMaps;
using FieldVisualizer.Entities.ColorMaps.Factories;
using FieldVisualizer.Entities.Values;
using FieldVisualizer.Entities.Values.Primitives;
using FieldVisualizer.Services.ColorServices;
using FieldVisualizer.Services.PrimitiveServices;
using FieldVisualizer.Services.ValueRanges;
using HelixToolkit;
using HelixToolkit.Geometry;
using HelixToolkit.Maths;
using HelixToolkit.SharpDX;
using HelixToolkit.Wpf.SharpDX;
using StructureHelper.Infrastructure;
using StructureHelper.Models.Materials;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Windows.Media;
using System.Windows.Media.Media3D;
using Color = HelixToolkit.Maths.Color;
namespace StructureHelper.Windows.CalculationWindows.CalculatorsViews.ForceCalculatorViews
{
public class IsoField3DViewerViewModel : ViewModelBase
{
const int RangeNumber = 16;
private int userZoomFactor = 100;
private IEnumerable<IPrimitiveSet> primitiveSets;
private Element3D item0;
private Element3D item1;
private Element3D item2;
private IPrimitiveSet selectedPrimitiveSet;
private double zoomValue = 1.0;
private IValueRange valueRange;
private IEnumerable<IValueRange> valueRanges;
private IEnumerable<IValueColorRange> valueColorRanges;
private IColorMap _ColorMap;
private ColorMapsTypes _ColorMapType;
public ContourViewportViewModel ViewportViewModel { get; } = new ContourViewportViewModel();
public IEnumerable<IPrimitiveSet> PrimitiveSets { get => primitiveSets;}
public IsoField3DViewerViewModel(IEnumerable<IPrimitiveSet> primitiveSets)
{
this.primitiveSets = primitiveSets;
_ColorMapType = ColorMapsTypes.LiraSpectrum;
_ColorMap = ColorMapFactory.GetColorMap(_ColorMapType);
}
public IPrimitiveSet SelectedPrimitiveSet
{
get => selectedPrimitiveSet;
set
{
selectedPrimitiveSet = value;
OnPropertyChanged(nameof(SelectedPrimitiveSet));
RebuildPrimitives();
}
}
public int UserZoomFactor
{
get => userZoomFactor;
set
{
userZoomFactor = value;
OnPropertyChanged(nameof(UserZoomFactor));
RebuildPrimitives ();
}
}
private void RebuildPrimitives()
{
SetColor();
item0 = ViewportViewModel.Viewport3D.Items[0];
item1 = ViewportViewModel.Viewport3D.Items[1];
item2 = ViewportViewModel.Viewport3D.Items[2];
ViewportViewModel.Viewport3D.Items.Clear();
ViewportViewModel.Viewport3D.Items.Add(item0);
ViewportViewModel.Viewport3D.Items.Add(item1);
ViewportViewModel.Viewport3D.Items.Add(item2);
double maxValue = SelectedPrimitiveSet.ValuePrimitives.Max(x => x.Value) - SelectedPrimitiveSet.ValuePrimitives.Min(x => x.Value);
zoomValue = UserZoomFactor / 100.0 / maxValue;
foreach (var primitive in SelectedPrimitiveSet.ValuePrimitives)
{
if (primitive is ITrianglePrimitive triangle)
{
var model = CreateTriangle(triangle);
ViewportViewModel.Viewport3D.Items.Add(model);
}
}
}
private MeshGeometryModel3D CreateTriangle(ITrianglePrimitive triangle)
{
// Triangle vertices
Vector3 p0 = new Vector3((float)triangle.Point1.X, (float)triangle.Point1.Y, (float)(triangle.ValuePoint1 * zoomValue));
Vector3 p1 = new Vector3((float)triangle.Point2.X, (float)triangle.Point2.Y, (float)(triangle.ValuePoint2 * zoomValue));
Vector3 p2 = new Vector3((float)triangle.Point3.X, (float)triangle.Point3.Y, (float)(triangle.ValuePoint3 * zoomValue));
var builder = new MeshBuilder();
builder.AddTriangle(p0, p1, p2);
var mesh = builder.ToMeshGeometry3D();
var material = new PhongMaterial
{
DiffuseColor = ToColor4(ColorOperations.GetColorByValue(valueRange, _ColorMap, triangle.Value)),
SpecularShininess = 50f
};
var model = new MeshGeometryModel3D
{
Geometry = mesh,
Material = material,
CullMode = SharpDX.Direct3D11.CullMode.None,
ToolTip = triangle.Value
};
return model;
}
public static Color4 ToColor4(System.Windows.Media.Color c)
{
return new Color4(
c.R / 255f,
c.G / 255f,
c.B / 255f,
c.A / 255f
);
}
internal void Refresh()
{
}
private void SetColor()
{
valueRange = PrimitiveOperations.GetValueRange(SelectedPrimitiveSet.ValuePrimitives);
valueRanges = ValueRangeOperations.DivideValueRange(valueRange, RangeNumber);
valueColorRanges = ColorOperations.GetValueColorRanges(valueRange, valueRanges, _ColorMap);
}
}
}

View File

@@ -99,7 +99,8 @@ namespace StructureHelperCommon.Infrastructures.Settings
VersionNumber = 1,
//SubVersionNumber = 2 //Add Beam shear analysis
//SubVersionNumber = 3 //Add stirrup group and inclined rebar
SubVersionNumber = 4 //Add polygonshape primitive
//SubVersionNumber = 4 //Add polygonshape primitive
SubVersionNumber = 5 //Add curvature calculator
};
}
}

View File

@@ -12,7 +12,7 @@
<ItemGroup>
<PackageReference Include="ExcelDataReader" Version="3.8.0" />
<PackageReference Include="netDxf" Version="2023.11.10" />
<PackageReference Include="NLog" Version="6.0.5" />
<PackageReference Include="NLog" Version="6.0.6" />
</ItemGroup>
<ItemGroup>

View File

@@ -10,7 +10,7 @@
<ItemGroup>
<PackageReference Include="Castle.Core" Version="5.2.1" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.0.1" />
<PackageReference Include="Moq" Version="4.20.72" />
<PackageReference Include="NUnit" Version="4.4.0" />
<PackageReference Include="NUnit3TestAdapter" Version="5.2.0" />