From c4d442ebd7095ba64ee9225526e4cbcfb0910d75 Mon Sep 17 00:00:00 2001 From: sunhonglei Date: Thu, 12 Dec 2024 16:52:31 +0800 Subject: [PATCH 1/5] =?UTF-8?q?feat=EF=BC=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ViewModel/Grading/DiamondSelectVM.cs | 48 ++++++++-- ViewModel/Grading/GradingResultVM.cs | 130 ++++++++++++++++----------- Views/Dialog/JsonImport.xaml | 74 +++++++++++++++ Views/Dialog/JsonImport.xaml.cs | 108 ++++++++++++++++++++++ Views/Dialog/StartDialog.xaml.cs | 2 - Views/Grading/GradingResult.xaml | 43 ++++++--- Views/Grading/GradingResult.xaml.cs | 10 +-- 7 files changed, 337 insertions(+), 78 deletions(-) create mode 100644 Views/Dialog/JsonImport.xaml create mode 100644 Views/Dialog/JsonImport.xaml.cs diff --git a/ViewModel/Grading/DiamondSelectVM.cs b/ViewModel/Grading/DiamondSelectVM.cs index cf5a7c7..2d25edb 100644 --- a/ViewModel/Grading/DiamondSelectVM.cs +++ b/ViewModel/Grading/DiamondSelectVM.cs @@ -6,7 +6,9 @@ using SparkClient.Views.Dialog; using System; using System.Diagnostics.Metrics; using System.DirectoryServices.ActiveDirectory; +using System.IO; using System.Reflection.Metadata; +using System.Text; using System.Windows; using System.Windows.Input; using System.Windows.Media.Imaging; @@ -96,17 +98,45 @@ public class DiamondSelectVM : BaseViewModel Buttons2 = tempButtons2; } } + public void StartGrading(object param) + { + DoStartGrading(param); + } /// /// 开始检测(对soc和算法开始通讯) /// /// - public async void StartGrading(object param) + public async void DoStartGrading(object param) { if (param != null) { + AlgorithmResultEntity parameter = new AlgorithmResultEntity(); + JsonImport jsonImport = new JsonImport(); + bool? a = jsonImport.ShowDialog(); + if (a ?? false) + { + string fileName = jsonImport.FilePath.Text; + string[] lines = File.ReadAllLines(fileName); + StringBuilder stringBuilder = new StringBuilder(); + + foreach (var line in lines) + { + stringBuilder.Append(line); + } + parameter = JsonConvert.DeserializeObject(stringBuilder.ToString()); + } + else + { + return; + } LoadingDialog loading = new LoadingDialog(); - var progressTask = Task.Run(() => Application.Current.Dispatcher.Invoke(() => loading.ShowDialog())); + var tcs = new TaskCompletionSource(); + var progressTask = Task.Run(() => Application.Current.Dispatcher.Invoke(() => { + loading.Closed += (s, e) => tcs.SetResult(true); + loading.ShowDialog(); + } + )); await Task.Run(async () => { for (int i = 0; i <= 100; i++) @@ -121,12 +151,14 @@ public class DiamondSelectVM : BaseViewModel }); } - SocResultEntity socResolt = new SocResultEntity(); - AlgorithmResultEntity parameter = new AlgorithmResultEntity(); - // 启动soc - socResolt = DoSoc(); - // 启动算法 - parameter = DoAlgorithm(); + // SocResultEntity socResolt = new SocResultEntity(); + // AlgorithmResultEntity parameter = new AlgorithmResultEntity(); + // // 启动soc + // socResolt = DoSoc(); + // // 启动算法 + // parameter = DoAlgorithm(); + + //parameter = DoSoc(); parameter.Standard = "IGI 2024"; string value = param.ToString()??""; if (value!= null && value.Split(" ").Length==3) diff --git a/ViewModel/Grading/GradingResultVM.cs b/ViewModel/Grading/GradingResultVM.cs index 9fda292..f57db76 100644 --- a/ViewModel/Grading/GradingResultVM.cs +++ b/ViewModel/Grading/GradingResultVM.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Data; using System.IO; using System.Reflection; @@ -26,7 +27,7 @@ public class GradingResultVM : BaseViewModel private string _wight; private string _cutLevelTotal; private string _symLevelTotal; - public ICommand ChangeNormCommand { get; } + public ICommand ChangeSymCommand { get; } public List DtResults { get { return _dtResults; } set { _dtResults = value; OnPropertyChanged(nameof(DtResults)); } } public string Standard { get { return _standard; } set { _standard = value; OnPropertyChanged(nameof(Standard)); } } public string Shape { get { return _shape; } set { _shape = value; OnPropertyChanged(nameof(Shape)); } } @@ -48,7 +49,7 @@ public class GradingResultVM : BaseViewModel /// 检测结果 public GradingResultVM(object result) { - ChangeNormCommand = new RelayCommand(ChangeNorm); + ChangeSymCommand = new RelayCommand(ChangeSym); if (result != null) { InitView(result as AlgorithmResultEntity); @@ -243,8 +244,8 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "TOTAL_DEPTH"; info.TestItemName = GetName("TOTAL_DEPTH"); - info.Avg = result.measurements.TOTAL_DEPTH.ToString(digitsFormat); - info.CutLevel = calGrade_TOTAL_DEPTH(result.measurements.TOTAL_DEPTH); + info.Avg = Math.Floor(result.measurements.TOTAL_DEPTH*100).ToString(digitsFormat); + info.CutLevel = calGrade_TOTAL_DEPTH(result.measurements.TOTAL_DEPTH * 100); return info; } @@ -253,9 +254,9 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "TABLE"; info.TestItemName = GetName("TABLE"); - info.Avg = result.measurements.TABLE.ToString(digitsFormat); - info.Min = result.measurements.TABLE_MIN.ToString(digitsFormat); - info.Max = result.measurements.TABLE_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.TABLE * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.TABLE_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.TABLE_MAX * 100).ToString(digitsFormat); info.CutLevel = calGrade_TABLE(result.measurements.TABLE_MIN, result.measurements.TABLE_MAX); return info; } @@ -278,10 +279,10 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "CROWN_HEIGHT"; info.TestItemName = GetName("CROWN_HEIGHT"); - info.Avg = result.measurements.CROWN_HEIGHT.ToString(digitsFormat); - info.Dev = result.measurements.CROWN_H_DEV.ToString(digitsFormat); - info.Min = result.measurements.CROWN_H_MIN.ToString(digitsFormat); - info.Max = result.measurements.CROWN_H_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.CROWN_HEIGHT * 100).ToString(digitsFormat); + info.Dev = Math.Floor(result.measurements.CROWN_H_DEV * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.CROWN_H_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.CROWN_H_MAX * 100).ToString(digitsFormat); info.CutLevel = calGrade_CROWN_HEIGHT(result.measurements.CROWN_H_MIN, result.measurements.CROWN_H_MAX); return info; } @@ -291,10 +292,10 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "GIRDLE_BEZEL"; info.TestItemName = GetName("GIRDLE_BEZEL"); - info.Avg = result.measurements.GIRDLE_BEZEL.ToString(digitsFormat); - info.Dev = result.measurements.GIRDLE_BEZEL_DEV.ToString(digitsFormat); - info.Min = result.measurements.GIRDLE_BEZEL_MIN.ToString(digitsFormat); - info.Max = result.measurements.GIRDLE_BEZEL_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.GIRDLE_BEZEL * 100).ToString(digitsFormat); + info.Dev = Math.Floor(result.measurements.GIRDLE_BEZEL_DEV * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.GIRDLE_BEZEL_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.GIRDLE_BEZEL_MAX * 100).ToString(digitsFormat); return info; } @@ -303,9 +304,9 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "GIRDLE_BONE"; info.TestItemName = GetName("GIRDLE_BONE"); - info.Avg = result.measurements.GIRDLE_BONE.ToString(digitsFormat); - info.Min = result.measurements.GIRDLE_BONE_MIN.ToString(digitsFormat); - info.Max = result.measurements.GIRDLE_BONE_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.GIRDLE_BONE * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.GIRDLE_BONE_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.GIRDLE_BONE_MAX * 100).ToString(digitsFormat); return info; } @@ -314,10 +315,10 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "GIRDLE"; info.TestItemName = GetName("GIRDLE"); - info.Avg = result.measurements.GIRDLE.ToString(digitsFormat); - info.Dev = result.measurements.GIRDLE_DEV.ToString(digitsFormat); - info.Min = result.measurements.GIRDLE_MIN.ToString(digitsFormat); - info.Max = result.measurements.GIRDLE_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.GIRDLE * 100).ToString(digitsFormat); + info.Dev = Math.Floor(result.measurements.GIRDLE_DEV * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.GIRDLE_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.GIRDLE_MAX * 100).ToString(digitsFormat); info.CutLevel = calGrade_GIRDLE(result.measurements.GIRDLE_MIN, result.measurements.GIRDLE_MAX); return info; } @@ -340,10 +341,10 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "PAV_DEPTH"; info.TestItemName = GetName("PAV_DEPTH"); - info.Avg = result.measurements.PAV_DEPTH.ToString(digitsFormat); - info.Dev = result.measurements.PAV_DEPTH_DEV.ToString(digitsFormat); - info.Min = result.measurements.PAV_DEPTH_MIN.ToString(digitsFormat); - info.Max = result.measurements.PAV_DEPTH_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.PAV_DEPTH * 100).ToString(digitsFormat); + info.Dev = Math.Floor(result.measurements.PAV_DEPTH_DEV * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.PAV_DEPTH_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.PAV_DEPTH_MAX * 100).ToString(digitsFormat); info.CutLevel = calGrade_PAV_DEPTH(result.measurements.PAV_DEPTH_MIN, result.measurements.PAV_DEPTH_MAX); return info; } @@ -353,9 +354,9 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "STAR"; info.TestItemName = GetName("STAR"); - info.Avg = result.measurements.STAR.ToString(digitsFormat); - info.Min = result.measurements.STAR_MIN.ToString(digitsFormat); - info.Max = result.measurements.STAR_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.STAR * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.STAR_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.STAR_MAX * 100).ToString(digitsFormat); return info; } @@ -364,9 +365,9 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "LOWER_HALVES_RATIO"; info.TestItemName = GetName("LOWER_HALVES_RATIO"); - info.Avg = result.measurements.LOWER_HALVES_RATIO.ToString(digitsFormat); - info.Min = result.measurements.LOWER_HALVES_RATIO_MIN.ToString(digitsFormat); - info.Max = result.measurements.LOWER_HALVES_RATIO_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.LOWER_HALVES_RATIO * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.LOWER_HALVES_RATIO_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.LOWER_HALVES_RATIO_MAX * 100).ToString(digitsFormat); return info; } @@ -375,7 +376,7 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "CULET"; info.TestItemName = GetName("CULET"); - info.Avg = result.measurements.CULET.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.CULET * 100).ToString(digitsFormat); return info; } @@ -384,7 +385,7 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "TOC"; info.TestItemName = GetName("TOC"); - info.Avg = result.measurements.TOC.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.TOC * 100).ToString(digitsFormat); return info; } @@ -393,7 +394,7 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "COC"; info.TestItemName = GetName("COC"); - info.Avg = result.measurements.COC.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.COC * 100).ToString(digitsFormat); return info; } @@ -402,10 +403,10 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "TWIST"; info.TestItemName = GetName("TWIST"); - info.Avg = result.measurements.TWIST.ToString(digitsFormat); - info.Dev = result.measurements.TWIST_DEV.ToString(digitsFormat); - info.Min = result.measurements.TWIST_MIN.ToString(digitsFormat); - info.Max = result.measurements.TWIST_MAX.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.TWIST * 100).ToString(digitsFormat); + info.Dev = Math.Floor(result.measurements.TWIST_DEV * 100).ToString(digitsFormat); + info.Min = Math.Floor(result.measurements.TWIST_MIN * 100).ToString(digitsFormat); + info.Max = Math.Floor(result.measurements.TWIST_MAX * 100).ToString(digitsFormat); return info; } @@ -414,7 +415,7 @@ public class GradingResultVM : BaseViewModel DataInfo info = new DataInfo(); info.TestItemId = "CULET_TO_TABLE"; info.TestItemName = GetName("CULET_TO_TABLE"); - info.Avg = result.measurements.CULET_TO_TABLE.ToString(digitsFormat); + info.Avg = Math.Floor(result.measurements.CULET_TO_TABLE * 100).ToString(digitsFormat); return info; } #endregion @@ -435,7 +436,25 @@ public class GradingResultVM : BaseViewModel private string GetName(string id) { // TODO DB没关联 - return id; + Dictionary dictionary = new(); + dictionary.Add("DIAMETER", "直径"); + dictionary.Add("TOTAL_DEPTH", "全深比"); + dictionary.Add("TABLE", "台宽比"); + dictionary.Add("CROWN_ANGLE", "冠角"); + dictionary.Add("CROWN_HEIGHT", "冠高比"); + dictionary.Add("GIRDLE_BEZEL", "波峰(风筝面)"); + dictionary.Add("GIRDLE_BONE", "波峰(上腰面)"); + dictionary.Add("GIRDLE", "腰厚比"); + dictionary.Add("PAV_ANGLE", "亭角"); + dictionary.Add("PAV_DEPTH", "亭深比"); + dictionary.Add("STAR", "星刻面长度比"); + dictionary.Add("LOWER_HALVES_RATIO", "下腰比"); + dictionary.Add("CULET", "底尖比"); + dictionary.Add("TOC", "台面偏心比"); + dictionary.Add("COC", "底尖偏心比"); + dictionary.Add("TWIST", "扭曲度"); + dictionary.Add("CULET_TO_TABLE", "底尖到台面偏心比"); + return dictionary[id]; } private string GetGradeName(int order) { @@ -449,12 +468,20 @@ public class GradingResultVM : BaseViewModel return dictionary[order]; } /// - /// 修改检测标准 + /// 修改对称性等级 /// /// - public void ChangeNorm(object norm) + public void ChangeSym(object norm) { - + int? order = this.DtResults.Select(x => x.SymLevel).Max(); + if (order.HasValue) + { + SymLevelTotal = GetGradeName((int)order.Value); + } + else + { + SymLevelTotal = string.Empty; + } } private void InitCombobox() @@ -496,7 +523,7 @@ public class CalGradeInfo{ public int isMaxExist { get; set; } public int isMinExist { get; set; } } -public class DataInfo() +public class DataInfo() : BaseViewModel { public string? TestItemId { get; set; } public string? TestItemName { get; set; } @@ -505,17 +532,18 @@ public class DataInfo() public string? Min { get; set; } public string? Max { get; set; } public string? CutLevel { get; set; } - public string? SymLevel { get; set; } + private int? _symLevel; + public int? SymLevel { get { return _symLevel; } set { _symLevel = value; OnPropertyChanged(nameof(SymLevel)); }} public DataTable GradeList { get { // TODO DB没关联 DataTable GradeList = new DataTable(); GradeList.Columns.Add("Key"); GradeList.Columns.Add("Value"); - GradeList.Rows.Add("极好", "1"); - GradeList.Rows.Add("很好", "2"); - GradeList.Rows.Add("好", "3"); - GradeList.Rows.Add("一般", "4"); - GradeList.Rows.Add("差", "5"); + GradeList.Rows.Add("极好", 1); + GradeList.Rows.Add("很好", 2); + GradeList.Rows.Add("好", 3); + GradeList.Rows.Add("一般", 4); + GradeList.Rows.Add("差", 5); return GradeList; } } diff --git a/Views/Dialog/JsonImport.xaml b/Views/Dialog/JsonImport.xaml new file mode 100644 index 0000000..27dbf80 --- /dev/null +++ b/Views/Dialog/JsonImport.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - + + - - - - - - - - - - + + + + + + + + + + + - + diff --git a/Views/UserControl/Viewport3D.xaml.cs b/Views/UserControl/Viewport3D.xaml.cs index 2fdb1cf..cdaa1e1 100644 --- a/Views/UserControl/Viewport3D.xaml.cs +++ b/Views/UserControl/Viewport3D.xaml.cs @@ -1,18 +1,161 @@ +using System.Text.Json.Nodes; using System.Windows; using System.Windows.Controls; using System.Windows.Input; using HandyControl.Controls; +using HelixToolkit.Wpf.SharpDX; +using SharpDX; +using SparkClient.Views.UserControl.ViewportData.Entity; +using SparkClient.Views.UserControl.ViewportData.Enum; +using SparkClient.Views.UserControl.ViewportData.Helper; namespace SparkClient.Views.UserControl; public partial class Viewport3D { + // 注册一个依赖属性,类型是 Viewport3DData + public static readonly DependencyProperty ViewportDataProperty = + DependencyProperty.Register( + nameof(ViewportData), + typeof(ViewportData.ViewportData), + typeof(Viewport3D), + new PropertyMetadata(null, OnViewportDataChanged)); + + // 公开的 CLR 属性包装器 + public ViewportData.ViewportData ViewportData + { + get => (ViewportData.ViewportData)GetValue(ViewportDataProperty); + set => SetValue(ViewportDataProperty, value); + } + + // 当 ViewportData 属性发生变化时触发 + private static void OnViewportDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is Viewport3D control && e.NewValue is ViewportData.ViewportData data) + { + control.Initialize(data); + } + } + + + private void Initialize(ViewportData.ViewportData vpData) + { + // 根据 Viewport3DData 的内容初始化控件 + // 例如:设置控件内部的某些属性,渲染 3D 模型等 + if (!string.IsNullOrEmpty(vpData.DiamondData)) + { + JsonNode? json = null; + try + { + json = JsonNode.Parse(vpData.DiamondData); + } + catch + { + throw new Exception("DiamondData is invalid"); + } + + if(json == null) throw new Exception("DiamondData is invalid"); + + try + { + List facets = new List(); + if (json.AsObject()["facets"] != null) + { + JsonArray jsonArray = json.AsObject()["facets"].AsArray(); + foreach (var item in jsonArray) + { + var value = CommonHelper.CreateByJsonStr(item.ToString()); + facets.Add(value); + } + } + else + { + throw new Exception("facets is invalid"); + } + + if (json.AsObject()["positive_direction"] != null) + { + JsonObject positive = json.AsObject()["positive_direction"].AsObject(); + ViewportManager.PositiveDirection.X = float.Parse(positive["x"]?.ToString() ?? "1.0"); + ViewportManager.PositiveDirection.Y = float.Parse(positive["z"]?.ToString() ?? "0"); + ViewportManager.PositiveDirection.Z = float.Parse(positive["y"]?.ToString() ?? "0"); + } + else + { + ViewportManager.PositiveDirection.X = 1.0f; + ViewportManager.PositiveDirection.Y = 0f; + ViewportManager.PositiveDirection.Z = 0; + } + + var midZ = facets.SelectMany(e => new[] { e.Point1.X, e.Point2.X, e.Point3.X }).OrderBy(z => Math.Abs(z)) + .First(); + List facetsFinal = new List(); + foreach (var item in facets) + { + var data = ViewportHelperPro.VectorClockwiseSort(new List + { item.Point1, item.Point2, item.Point3 }); + + if (item.PlaneType == PlaneType.StarFacet || item.PlaneType == PlaneType.UpperGirdleFacet || + item.PlaneType == PlaneType.UpperMainFacet || item.PlaneType == PlaneType.TableFacet) + { + item.Point1 = data[0]; + item.Point2 = data[1]; + item.Point3 = data[2]; + } + else if (item.PlaneType == PlaneType.Girdle) + { + var center = ViewportHelperPro.GetCentroid(data); + data.Sort((v1, v2) => + { + double angle1 = Math.Atan2(v1.Y - center.Y, v1.X - center.X); + double angle2 = Math.Atan2(v2.Y - center.Y, v2.X - center.X); + return angle1.CompareTo(angle2); + }); + if (center.Z >= midZ) + { + + item.Point1 = data[0]; + item.Point2 = data[1]; + item.Point3 = data[2]; + } + else + { + item.Point1 = data[2]; + item.Point2 = data[1]; + item.Point3 = data[0]; + + } + } + else + { + item.Point1 = data[2]; + item.Point2 = data[1]; + item.Point3 = data[0]; + } + + item.TriangleCode = CommonHelper.GenerateTriangleCode(item.Point1, item.Point2, item.Point3); + facetsFinal.Add(item); + } + + // ViewportManager.ViewportTriangle.AddRange(facetsFinal); + ViewportManager.LoadModelByEntities(facetsFinal); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + } + public Viewport3D() { InitializeComponent(); DataContext = this; + this.Viewport3Dx.EffectsManager = new DefaultEffectsManager(); + ViewportManager.SetViewport3D(Viewport3Dx); } + #region 菜单事件绑定 private void MenuItem_OnChecked(object sender, RoutedEventArgs e) { Growl.InfoGlobal("Viewport is checked" + e.OriginalSource); @@ -22,7 +165,13 @@ public partial class Viewport3D { Growl.InfoGlobal("Viewport is Unchecked" + e.OriginalSource); } - + private void MenuItem_OnClick(object sender, RoutedEventArgs e) + { + + } + #endregion + + #region 页面隐式交互 private void UIElement_OnPreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) { MainBorder.ContextMenu.IsOpen = true; @@ -33,9 +182,11 @@ public partial class Viewport3D MainBorder.ContextMenu.IsOpen = false; e.Handled = true; } + #endregion - private void MenuItem_OnClick(object sender, RoutedEventArgs e) + private void Viewport3D_OnLoaded(object sender, RoutedEventArgs e) { - + //ViewportData.LoadData(); + ViewportManager.LoadModelByEntities(new List()); } } \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Entity/MeasurementsDataEntity.cs b/Views/UserControl/ViewportData/Entity/MeasurementsDataEntity.cs new file mode 100644 index 0000000..23d9fed --- /dev/null +++ b/Views/UserControl/ViewportData/Entity/MeasurementsDataEntity.cs @@ -0,0 +1,6 @@ +namespace SparkClient.Views.UserControl.ViewportData.Entity; + +public class MeasurementsDataEntity +{ + +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Entity/Viewport3DTriangleEntity.cs b/Views/UserControl/ViewportData/Entity/Viewport3DTriangleEntity.cs new file mode 100644 index 0000000..70834c2 --- /dev/null +++ b/Views/UserControl/ViewportData/Entity/Viewport3DTriangleEntity.cs @@ -0,0 +1,41 @@ +using SharpDX; +using SparkClient.Views.UserControl.ViewportData.Enum; + +namespace SparkClient.Views.UserControl.ViewportData.Entity; + +/// +/// 三角形模型实体 +/// +public class Viewport3DTriangleEntity +{ + /// + /// 点1 + /// + public Vector3 Point1 { get; set; } + /// + /// 点2 + /// + public Vector3 Point2 { get; set; } + /// + /// 点3 + /// + public Vector3 Point3 { get; set; } + /// + /// 三角形代码[生成] + /// 按顺序:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 拼接后使用生成大写16位md5 + /// + public String TriangleCode { get; set; } + /// + /// 面代码 + /// 由多个三角形组成的面的代码:entity1、entity2组成了一个正方形,那么他俩的TriangleCode属性值一致 + /// + public String PlaneCode { get; set; } + /// + /// 面类型 + /// 比如这个面时钻石的腰部分,那么type统一,当面类型为err时则该面是一个异常面 + /// 可选值:PlaneType + /// + public PlaneType PlaneType { get; set; } + + +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Enum/PlaneType.cs b/Views/UserControl/ViewportData/Enum/PlaneType.cs new file mode 100644 index 0000000..c548982 --- /dev/null +++ b/Views/UserControl/ViewportData/Enum/PlaneType.cs @@ -0,0 +1,31 @@ +using System.ComponentModel; + +namespace SparkClient.Views.UserControl.ViewportData.Enum; + +public enum PlaneType +{ + [Description("瑕疵面")] + Error = -1, + [Description("腰")] + Girdle = 0, + [Description("冠部")] + Crown = 1, + [Description("亭部")] + Pavilion = 2, + [Description("台面")] + TableFacet = 11, + [Description("冠部主刻面(风筝面)")] + UpperMainFacet = 12, + [Description("星刻面")] + StarFacet = 13, + [Description("上腰面")] + UpperGirdleFacet = 14, + [Description("亭部主刻面")] + PavilionMainFacet = 21, + [Description("下腰面")] + LowerGirdleFact = 22, + [Description("底尖(或底小面)")] + Culet = 23, + [Description("其他")] + Other = 99 +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Helper/CommonHelper.cs b/Views/UserControl/ViewportData/Helper/CommonHelper.cs new file mode 100644 index 0000000..83a7362 --- /dev/null +++ b/Views/UserControl/ViewportData/Helper/CommonHelper.cs @@ -0,0 +1,78 @@ +using System.Security.Cryptography; +using System.Text; +using Newtonsoft.Json; +using SharpDX; +using SparkClient.Views.UserControl.ViewportData.Entity; +using SparkClient.Views.UserControl.ViewportData.Enum; + +namespace SparkClient.Views.UserControl.ViewportData.Helper; + +public class CommonHelper +{ + public static Viewport3DTriangleEntity CreateByJsonStr(string json) + { + // 解析 JSON 数据 + var jsonObject = JsonConvert.DeserializeObject(json); + if (jsonObject == null) throw new ArgumentException("Json object is null"); + // 提取坐标 + var coords = jsonObject.coords.ToObject(); + if (coords.Length < 3) + { + throw new ArgumentException("The input JSON does not have enough points to form a triangle."); + } + + // 创建三角形顶点 + var point1 = new Vector3((float)coords[0].x, (float)coords[0].z, (float)coords[0].y); + var point2 = new Vector3((float)coords[1].x, (float)coords[1].z, (float)coords[1].y); + var point3 = new Vector3((float)coords[2].x, (float)coords[2].z, (float)coords[2].y); + // var point1 = new Vector3((float)coords[0].x, (float)coords[0].y, (float)coords[0].z); + // var point2 = new Vector3((float)coords[1].x, (float)coords[1].y, (float)coords[1].z); + // var point3 = new Vector3((float)coords[2].x, (float)coords[2].y, (float)coords[2].z); + + // 提取 PlaneCode 和 PlaneType + string planeCode = jsonObject.facet_id ?? (string)jsonObject.facet_id; + PlaneType planeType = (PlaneType)(int)jsonObject.facet_type; + + // 生成 TriangleCode + var triangleCode = GenerateTriangleCode(point1, point2, point3); + + // 创建并返回实体对象 + return new Viewport3DTriangleEntity + { + Point1 = point1, + Point2 = point2, + Point3 = point3, + TriangleCode = triangleCode, + PlaneCode = planeCode, + PlaneType = planeType + }; + + } + + /// + /// 生成三角形签名 + /// + /// + /// + /// + /// + public static string GenerateTriangleCode(Vector3 p1, Vector3 p2, Vector3 p3) + { + var concatenated = $"{p1.X},{p1.Y},{p1.Z};{p2.X},{p2.Y},{p2.Z};{p3.X},{p3.Y},{p3.Z}"; + return GenerateMd5Hash(concatenated); + } + /// + /// 文本转32大写MD5 + /// + /// + /// + private static string GenerateMd5Hash(string input) + { + using (var md5 = MD5.Create()) + { + var inputBytes = Encoding.UTF8.GetBytes(input); + var hashBytes = md5.ComputeHash(inputBytes); + return string.Concat(hashBytes.Select(b => b.ToString("X2"))); + } + } +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs b/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs new file mode 100644 index 0000000..c4398bd --- /dev/null +++ b/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs @@ -0,0 +1,873 @@ +using System.Collections.ObjectModel; +using System.IO; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Media.Imaging; +using System.Windows.Media.Media3D; +using HelixToolkit.Wpf.SharpDX; +using SharpDX; +using SharpDX.Direct3D11; +using SparkClient.Views.UserControl.ViewportData.Enum; +using SparkClient.Views.UserControl.ViewportData.Entity; +using Color = SharpDX.Color; +using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D; + +namespace SparkClient.Views.UserControl.ViewportData.Helper; + +public class ViewportHelperPro +{ + /// + /// 对指定类型的面进行标色 + /// + /// + /// + /// + public static MeshGeometryModel3D GenerateTypePanelHot(PlaneType planeType, Color4? color = null) + { + var entities = ViewportManager.ViewportTriangle.Where(e=>e.PlaneType==planeType).ToList(); + return GenerateModelByEntity(entities, color??ViewportManager.Red); + } + + #region 已经确定和调整好的方法 + /// + /// 通过三角形实体集合生成面模型(生成并添加) + /// + /// + /// + public static MeshGeometryModel3D GenerateModelByEntity(Viewport3DX viewport, List entities, Color4? color = null) + { + var geometryModel = GenerateModelByEntity(entities, color); + viewport.Items.Add(geometryModel); + return geometryModel; + } + + /// + /// 通过三角形实体集合生成面模型(只生成不添加) + /// + /// + /// + /// + public static MeshGeometryModel3D GenerateModelByEntity(List entities, Color4? color = null) + { + var meshBuilder = new MeshBuilder(true, false); + foreach (var entity in entities) + { + meshBuilder.AddPolygon(new List() { entity.Point1, entity.Point2, entity.Point3 }); + } + var mesh = meshBuilder.ToMeshGeometry3D(); + + var material = new PBRMaterial + { + AlbedoColor = ViewportManager.Black, // 黑色,避免其他光照影响 + EmissiveColor =color ?? ViewportManager.LightGray , // LightGray #D3D3D3 + MetallicFactor = 0.0, // 非金属 + RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 + ReflectanceFactor = 0.0, // 无反射 + ClearCoatStrength = 0.0, // 无清漆效果 + ClearCoatRoughness = 1.0, // 高粗糙度 + SurfaceMapSampler = new SamplerStateDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap + }, + // NormalMap = texture + }; + return new MeshGeometryModel3D + { + Geometry = mesh, + Material = material, + }; + } + /// + /// 保存模型截图 + /// + /// + /// + public static void SaveViewportAsImage(Viewport3DX viewport, string filePath) + { + // 定义图像大小 + int width = (int)viewport.ActualWidth; + int height = (int)viewport.ActualHeight; + + // 创建 RenderTargetBitmap,捕获 Viewport3DX 的内容 + var renderTargetBitmap = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32); + renderTargetBitmap.Render(viewport); + + // 使用 PngBitmapEncoder 保存为 PNG 文件 + var encoder = new PngBitmapEncoder(); + encoder.Frames.Add(BitmapFrame.Create(renderTargetBitmap)); + + using (var fileStream = new FileStream(filePath, FileMode.Create)) + { + encoder.Save(fileStream); + } + } + + /// + /// 导出模型 + /// + /// + /// + public static void ExportModelsToStl(Viewport3DX viewport, string filePath) + { + // 打开文件写入流 + using (StreamWriter writer = new StreamWriter(filePath)) + { + writer.WriteLine("solid exportedModel"); + foreach (var model in viewport.Items.OfType()) + { + if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry) + { + var positions = geometry.Positions; + var indices = geometry.Indices; + + // 每三个索引构成一个三角形 + for (int i = 0; i < indices.Count; i += 3) + { + int index0 = indices[i]; + int index1 = indices[i + 1]; + int index2 = indices[i + 2]; + + var p0 = positions[index0]; + var p1 = positions[index1]; + var p2 = positions[index2]; + + // 计算法线 + var normal = CalculateNormal(p0, p1, p2); + + // 写入三角形信息到 STL 文件 + writer.WriteLine($" facet normal {normal.X} {normal.Z} {normal.Y}"); + writer.WriteLine(" outer loop"); + writer.WriteLine($" vertex {p0.X} {p0.Z} {p0.Y}"); + writer.WriteLine($" vertex {p1.X} {p1.Z} {p1.Y}"); + writer.WriteLine($" vertex {p2.X} {p2.Z} {p2.Y}"); + writer.WriteLine(" endloop"); + writer.WriteLine(" endfacet"); + } + } + } + + // 写入 STL 文件结束 + writer.WriteLine("endsolid exportedModel"); + } + } + + /// + /// 通过三角形实体集合生成每个面的边框线 + /// + /// + /// + public static List GentrateLineByEntity(Viewport3DX viewport, List entities, Color4? color = null, double thickness = 1.0) + { + List result = GentrateLineByEntity(entities, color, thickness); + result.ForEach(e => viewport.Items.Add(e)); + return result; + } + + /// + /// 通过三角形实体集合生成每个面的边框线(只生成不添加) + /// + /// + /// + /// + /// + public static List GentrateLineByEntity(List entities, Color4? color = null, double thickness = 1.0) + { + List result = new List(); + //按面分组,腰面特殊单独生成 + List waistList = entities + .Where(entity => entity.PlaneType == PlaneType.Girdle) + .ToList(); + + Dictionary> feactList = entities + .Where(entity => entity.PlaneType != PlaneType.Girdle) + .GroupBy(entity => entity.PlaneCode) + .ToDictionary(group => group.Key, group => group.ToList()); + + foreach (var item in feactList) + { + //GenerateLineTextModelByEntity(viewport, item.Value, Viewport3DManager.Gray); + List temp = new List(); + foreach (var entity in item.Value) + { + temp.Add(entity.Point1); + temp.Add(entity.Point2); + temp.Add(entity.Point3); + } + result.Add(DisplayLineModel3D(VectorClockwiseSort(new HashSet(temp).ToList()), color??ViewportManager.LineColor, thickness)); + } + + //腰 - 特殊 + List selFaceVector = new List(); + if (waistList.Count > 0) + { + foreach (var entity in waistList) + { + selFaceVector.Add(entity.Point1); + selFaceVector.Add(entity.Point2); + selFaceVector.Add(entity.Point3); + } + HashSet uniqueVectors = new HashSet(selFaceVector); + float maxY = uniqueVectors.Max(v => v.Y); + float minY = uniqueVectors.Min(v => v.Y); + float mid = (maxY + minY) / 2; + ViewportManager.GirdleTopLines.Clear(); + ViewportManager.GirdleBottomLines.Clear(); + foreach (var vector in uniqueVectors) + { + if(vector.Y < mid) + ViewportManager.GirdleBottomLines.Add(vector); + else + ViewportManager.GirdleTopLines.Add(vector); + } + result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleBottomLines), color??ViewportManager.LineColor, thickness)); + result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleTopLines), color??ViewportManager.LineColor, thickness)); + + } + return result; + } + + /// + /// 显示线段长度mm + /// + /// 三角形集合 + /// 文字颜色 + /// 是否显示全部(false:只显示不重复) + /// + public static List GenerateLineTextModels(List entities, + Color4? textColor = null,bool showAll = false) + { + var result = new List(); + var selFaceVector = entities + .SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) + .Distinct() + .ToList(); + var uniqueLines = new HashSet(); + var sortedVectors = VectorClockwiseSort(selFaceVector); + for (int i = 0; i < sortedVectors.Count; i++) + { + var current = sortedVectors[i]; + var next = sortedVectors[(i + 1) % sortedVectors.Count]; + + double length = (next - current).Length(); + string lineKey = $"{length:F2}"; + if (showAll == false) + { + if (uniqueLines.Contains(lineKey)) continue; + uniqueLines.Add(lineKey); + } + var midPoint = (current + next) / 2; + var text = $"{length:F2}mm"; + var textY = midPoint.Y > ViewportManager.CenterVector.Y ? midPoint.Y + 0.3f : midPoint.Y - 0.3f; + var lengthTextModel = DisplayText3D(text, new Vector3(midPoint.X, textY, midPoint.Z),next - current, textColor); + result.Add(lengthTextModel); + } + return result; + } + + /// + /// 生成选择的视图() + /// + /// 三角形ID + /// 生成范围 + /// + public static List GentrateChosenView(string triangleCode, params SelShowType[] selType) + { + var res = ViewportManager.ViewportTriangle.Find(e => triangleCode.Equals(e.TriangleCode)); + if(res != null) + return GentrateChosenView(res, selType); + return new List(); + } + + /// + /// 生成选择的视图(只生成,不添加!!) + /// + /// 选中的实体 + /// 生成范围 + /// + /// + public static List GentrateChosenView(Viewport3DTriangleEntity entity, params SelShowType[] selType) + { + if (entity == null || entity.TriangleCode == null || entity.TriangleCode.Length == 0) + { + throw new Exception("Invalid Viewport3DTriangleEntity object!"); + } + + if (selType == null || selType.Length == 0) + { + throw new Exception("Invalid SelShowType collection"); + } + List result = new List(); + + //选中的面 + var selPanel = ViewportManager.ViewportTriangle.Where(e => entity.PlaneCode.Equals(e.PlaneCode)).ToList(); + //选中同类的面 + var selPanelType = ViewportManager.ViewportTriangle.Where(e => entity.PlaneType.Equals(e.PlaneType)).ToList(); + selPanel.ForEach(e=>selPanelType.Remove(e)); + foreach (var type in selType) + { + switch (type) + { + case SelShowType.SelPanel: + //选中面的高亮 + result.Add(GenerateModelByEntity(selPanel, ViewportManager.DarkKhaki)); + break; + case SelShowType.Border: + //选中面边框的高亮 + result.AddRange(GentrateLineByEntity(selPanel, ViewportManager.Black)); + break; + case SelShowType.IsTypePanel: + //选中面的同类面高亮 + result.Add(GenerateModelByEntity(selPanelType, ViewportManager.LtGoldenrodYello)); + break; + case SelShowType.LengthText: + //选中面 每条边长度标记 + if(PlaneType.Girdle == entity.PlaneType)break; + result.AddRange(GenerateLineTextModels(selPanel)); + break; + case SelShowType.BorderAngle: + //选中面 每条边向内的夹角 + if(PlaneType.Girdle == entity.PlaneType)break; + result.AddRange(GenerateLineAngleTextModels(selPanel)); + break; + } + } + //生成的需要添加的元素 + return result; + } + + + /// + /// 生成线段夹角文本(不绘制) + /// + /// 线集合 + /// 文本颜色(默认红) + /// 是否显示全部(默认否) + /// + public static List GenerateLineAngleTextModels(List entities, + Color4? textColor = null, bool showAll = false) + { + var result = new List(); + var uniqueAngles = new HashSet(); + var selFaceVector = entities + .SelectMany(entity => new[] { entity.Point1, entity.Point2, entity.Point3 }) + .Distinct() + .ToList(); + + var sortedVectors = VectorClockwiseSort(selFaceVector); + + for (int i = 0; i < sortedVectors.Count; i++) + { + int prevIndex = (i - 1 + sortedVectors.Count) % sortedVectors.Count; + int nextIndex = (i + 1) % sortedVectors.Count; + + var p1 = sortedVectors[prevIndex]; + var p2 = sortedVectors[i]; + var p3 = sortedVectors[nextIndex]; + + // 向量 + var v1 = Vector3.Normalize(p1 - p2); + var v2 = Vector3.Normalize(p3 - p2); + + // 计算内角(考虑方向性) + double angle = Math.Acos(Vector3.Dot(v1, v2)) * (180 / Math.PI); + + string angleKey = $"{angle:F2}"; // 唯一标识 + if (showAll == false) + { + if (uniqueAngles.Contains(angleKey)) continue; + uniqueAngles.Add(angleKey); + } + + // 显示内角 + var text = $"{angle:F2}°"; + var textY = p2.Y > ViewportManager.CenterVector.Y ? p2.Y + 0.3f : p2.Y - 0.3f; + var angleTextModel = DisplayText3D(text, new Vector3(p2.X, textY, p2.Z), textColor); + result.Add(angleTextModel); + } + + return result; + } + + #endregion + + /// + /// 旋转主体 + /// + /// 中心 + /// 是否包含线 + /// 旋转时间 秒 + public static void RotateModel(Vector3D axis, bool hasLine = true, int speed = 5) + { + // 设置旋转的中心点和旋转轴 + var rotateTransform = new RotateTransform3D(); + var rotation = new AxisAngleRotation3D(axis, 0); // 初始角度为 0 + rotateTransform.Rotation = rotation; + rotateTransform.CenterX = ViewportManager.CenterVector.X; + rotateTransform.CenterY = ViewportManager.CenterVector.Y; + rotateTransform.CenterZ = ViewportManager.CenterVector.Z; + + // 将旋转变换应用到模型 + ViewportManager.MainModel3D.Transform = rotateTransform; + if(hasLine) + ViewportManager.MainModelLines.ForEach(e => e.Transform = rotateTransform); + // 创建旋转动画 + var rotateAnimation = new DoubleAnimation + { + From = 0, + To = 360, + Duration = new Duration(TimeSpan.FromSeconds(speed)), + FillBehavior = FillBehavior.HoldEnd + }; + + // 应用动画 + rotation.BeginAnimation(AxisAngleRotation3D.AngleProperty, rotateAnimation); + } + + + + + /// + /// 在场景中添加文字 + /// + /// 文字 + /// 位置 + /// + public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Color4? color = null) + { + var billboardTextModel = new BillboardTextModel3D(); + var billboardText = new BillboardText3D(); + billboardText.TextInfo.Add(new TextInfo(text, position) + { + Foreground = color??ViewportManager.Red, + Scale = 0.5f, + }); + billboardTextModel.Geometry = billboardText; + return billboardTextModel; + } + /// + /// 在场景中添加文字 + /// + /// + /// + /// + /// + /// + public static BillboardTextModel3D DisplayText3D(string text, Vector3 position, Vector3 direction, Color4? color = null) + { + // 计算线段方向的角度 + double angle = Math.Atan2(direction.Y, direction.X) * (180 / Math.PI); + + var billboardTextModel = new BillboardTextModel3D() + { + Geometry = new BillboardText3D() + { + TextInfo = new ObservableCollection() + { + new TextInfo(text, position){ + Foreground = color ?? ViewportManager.Red, + Scale = 0.5f, + Offset = new Vector2(0.2f, 0.2f), + // Angle = (float)angle // 设置文字的旋转角度 + } + } + }, + DepthBias = -99, + SlopeScaledDepthBias = 1.0f, + IsDepthClipEnabled = true + }; + + + return billboardTextModel; + } + /// + /// 生成线对象 + /// + /// 点集合(自动闭环) + /// 线颜色 + /// 线粗细 默认1 + /// + public static LineGeometryModel3D DisplayLineModel3D(List points, Color4 lineColor, + double thickness = 1.0) + { + var edgeLines = new List>(); + for (int i = 0; i < points.Count - 1; i++) + { + var nowItem = points[i]; + var nextItem = points[i + 1]; + edgeLines.Add(new Tuple(nowItem, nextItem)); + + } + edgeLines.Add(new Tuple(points.Last(), points.First())); + return DisplayLineModel3D(edgeLines, lineColor, thickness); + } + /// + /// 在正方向生成相机 + /// + /// + /// + /// + /// + /// + public static HelixToolkit.Wpf.SharpDX.PerspectiveCamera CalculateCamera(Vector3 positiveDirection, BoundingBox boundingBox, double fieldOfView = 45, double aspectRatio = 16.0 / 9.0) + { + // 单位化正方向 + var normalizedDirection = Vector3.Normalize(positiveDirection); + + // 计算模型中心和最大尺寸 + var center = boundingBox.Center; // 包围盒中心 + var maxDimension = boundingBox.Size.Length(); // 包围盒对角线长度 + + // 计算视场角的一半(弧度制) + var halfFov = Math.PI * fieldOfView / 360.0; + + // 计算相机距离 + var distance = maxDimension / (2 * Math.Tan(halfFov)); + + // 相机位置和朝向 + var cameraPosition = center - normalizedDirection * (float)distance; // 沿正方向放置相机 + var lookDirection = center - cameraPosition; // 朝向模型中心 + + + // 创建并返回相机 + return new HelixToolkit.Wpf.SharpDX.PerspectiveCamera + { + Position = cameraPosition.ToPoint3D(), + LookDirection = lookDirection.ToVector3D(), + UpDirection = new Vector3D(0, 1, 0), // 默认全局 Y 轴为上方向 + FieldOfView = fieldOfView, + }; + } + + /// + /// 绘制箭头(相机指向模型中心) + /// + /// 相机位置 + /// 中心位置 + /// 模型 + /// 箭头总长度 D:5 + /// 圆柱部分占比 0.7 + /// 箭头直径 1 + /// 圆锥直径与圆柱直径的比例 1.5 + /// 模型边界范围 1 + /// + public static MeshGeometryModel3D CreateArrow( + Vector3 cameraPosition, + Vector3 modelCenter, + BoundingBox modelBounds, + float totalLength = 2f, // 箭头总长度 + float cylinderRatio = 0.7f,// 圆柱部分占比 + float diameter = 0.5f, // 箭头直径 + float headDiameterRatio = 1f, // 圆锥直径与圆柱直径的比例 + float padding = 2.0f + ) + { + // 计算模型的半径(对角线的一半) + var modelSize = modelBounds.Size; + var modelRadius = modelSize.Length() / 2; + + // 计算方向向量(从相机位置指向模型中心) + var direction = Vector3.Normalize(modelCenter - cameraPosition); + + // 调整箭头起点,避免与模型交集 + var adjustedStart = modelCenter - direction * (modelRadius + padding); + + // 计算圆柱和圆锥部分长度 + float cylinderLength = totalLength * cylinderRatio; + float coneLength = totalLength - cylinderLength; + + // 箭头圆柱终点 + var cylinderEnd = adjustedStart + direction * cylinderLength; + + // 箭头终点(圆锥部分终点) + var arrowEnd = cylinderEnd + direction * coneLength; + + // 创建箭头几何体 + var builder = new MeshBuilder(); + + // 添加圆柱部分 + builder.AddCylinder(adjustedStart, cylinderEnd, diameter, 20); + + // 添加圆锥部分 + builder.AddCone(cylinderEnd, arrowEnd, diameter * headDiameterRatio, true, 20); + + // 设置箭头自发光材质 + var material = new PhongMaterial + { + DiffuseColor = Color.Red, // 箭头的基本颜色 + EmissiveColor = Color.Red // 箭头自发光颜色 + }; + + + + // 创建 3D 模型 + return new MeshGeometryModel3D + { + Geometry = builder.ToMeshGeometry3D(), + Material = material + }; + } + + + /// + /// 生成线对象 + /// + /// 线段集合 + /// 线段颜色 + /// 线段粗细 + /// + public static LineGeometryModel3D DisplayLineModel3D(List> points, Color4 lineColor, + double thickness = 1.0) + { + var lineBuilder = new LineBuilder(); + foreach (var line in points) + { + lineBuilder.AddLine(line.Item1, line.Item2); + } + + + var edgeLinesModel = new LineGeometryModel3D + { + Geometry = lineBuilder.ToLineGeometry3D(), + Color = lineColor.ToColor(), + Thickness = thickness, + }; + return edgeLinesModel; + } + + /// + /// 根据视图中模型生成光照 + /// + /// + /// + public static List GenerateLightingForModel(Viewport3DX viewport) + { + + List result = new List(); + + var models = viewport.Items.OfType(); + if (!models.Any()) throw new Exception("Model in view not found"); + var largestModel = models + .OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) + .FirstOrDefault(); + if (largestModel == null) throw new Exception("Model in view not found"); + var boundingBox = largestModel.Geometry.Bound; + var size = boundingBox.Size; + var center = boundingBox.Center; + ViewportManager.CenterVector = center; + ViewportManager.ModelBounds = boundingBox; + var yao = CalculateCenter(ViewportManager.GirdleTopLines, ViewportManager.GirdleBottomLines); + var corners = new List + { + new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 + new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Minimum.Z-0.5), // 右下后 + new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 左下前 + new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Minimum.Y-boundingBox.Maximum.Y/2, boundingBox.Maximum.Z+0.5), // 右下前 + + // new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 + // new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Minimum.Z-1), // 右下后 + // new Point3D(boundingBox.Minimum.X-1, yao.Y, boundingBox.Maximum.Z+1), // 左下前 + // new Point3D(boundingBox.Maximum.X+1, yao.Y, boundingBox.Maximum.Z+1), // 右下前 + + new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 左上后 + new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4,boundingBox.Maximum.Z+0.5), // 右上前 + new Point3D(boundingBox.Maximum.X+0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Minimum.Z-0.5), // 右上后 + new Point3D(boundingBox.Minimum.X-0.5, boundingBox.Maximum.Y+boundingBox.Maximum.Y/4, boundingBox.Maximum.Z+0.5), // 左上前 + }; + for (int i = 0; i < corners.Count; i++) + { + var corner = corners[i]; + var color = i % 2 == 0 ? Colors.LightGoldenrodYellow : Colors.LightSkyBlue; + var pointLight = new PointLight3D + { + Position = corner, + Color = color, + Range = (float)size.Length() + }; + result.Add(pointLight); + } + // var topLightPositions = new List + // { + // new Point3D(center.X, center.Y, center.Z), + // }; + // foreach (var position in topLightPositions) + // { + // var topLight = new PointLight3D + // { + // Position = position, + // Color = Colors.LightGoldenrodYellow, + // Range = (float)size.Length() * 0.5, // 增加光的照射范围 + // Attenuation = new Vector3D(1, 0.1f, 0.05f) // 控制光的衰减,使光在距离内更有效 + // }; + // result.Add(topLight); + // } + // 添加环境光以柔化整体效果并增加亮度 + result.Add(new AmbientLight3D + { + Color = Colors.LightGray + }); + + result.Add(new AmbientLight3D + { + Color = Colors.Gray // 设置环境光颜色 + }); + + RemoveLightingInViewport(viewport); + result.ForEach(e => viewport.Items.Add(e)); + return result; + } + + public static Vector3 GetCenterOfTriangles(List triangles) + { + if (triangles == null || triangles.Count == 0) + throw new ArgumentException("The list of triangles cannot be null or empty."); + + // 累加所有顶点的坐标 + Vector3 total = Vector3.Zero; + int vertexCount = 0; + + foreach (var triangle in triangles) + { + total += triangle.Point1; + total += triangle.Point2; + total += triangle.Point3; + vertexCount += 3; // 每个三角形有3个顶点 + } + + // 计算平均坐标 + return total / vertexCount; + } + + public static Vector3 CalculateCenter(List girdleTopLines, List girdleBottomLines) + { + // 计算 GirdleTopLines 的中心点 + Vector3 topCenter = GetCenter(girdleTopLines); + + // 计算 GirdleBottomLines 的中心点 + Vector3 bottomCenter = GetCenter(girdleBottomLines); + + // 计算两个中心点的中点作为垂直方向的中心点 + Vector3 centerInVertical = new Vector3( + (topCenter.X + bottomCenter.X) / 2, + (topCenter.Y + bottomCenter.Y) / 2, + (topCenter.Z + bottomCenter.Z) / 2 + ); + + return centerInVertical; + } + /// + /// 删除视图中的照明 + /// + /// + public static void RemoveLightingInViewport(Viewport3DX viewport) + { + List lights = new List(); + foreach (var item in viewport.Items) + { + if (item is Light3D light3D) + { + lights.Add(light3D); + } + } + lights.ForEach(item => viewport.Items.Remove(item)); + } + + #region 私有方法 + + /// + /// 向量按中心点顺时针排序 + /// + /// + /// + public static List VectorClockwiseSort(List points) + { + Vector3 center = GetCentroid(points); + return VectorClockwiseSort(points, center); + } + + /// + /// 向量按中心点顺时针排序 + /// + /// + /// + /// + public static List VectorClockwiseSort(List points, Vector3 center) + { + points.Sort((v1, v2) => + { + double angle1 = Math.Atan2(v1.Z - center.Z, v1.X - center.X); + double angle2 = Math.Atan2(v2.Z - center.Z, v2.X - center.X); + return angle1.CompareTo(angle2); + }); + return points; + } + + private static Vector3 GetCenter(List points) + { + float x = points.Average(p => p.X); + float y = points.Average(p => p.Y); + float z = points.Average(p => p.Z); + return new Vector3(x, y, z); + } + + /// + /// 计算点集合的中心点(几何质心) + /// + /// + /// + public static Vector3 GetCentroid(List vectors) + { + float x = vectors.Sum(v => v.X) / vectors.Count; + float y = vectors.Sum(v => v.Y) / vectors.Count; + float z = vectors.Sum(v => v.Z) / vectors.Count; + return new Vector3(x, y, z); + } + /// + /// 计算夹角度数 + /// + /// + /// + /// + private static float AngleBetween(Vector3 v1, Vector3 v2) + { + // 计算两个向量的点积 + float dotProduct = Vector3.Dot(v1, v2); + + // 计算每个向量的模长 + float magnitudeV1 = v1.Length(); + float magnitudeV2 = v2.Length(); + + // 防止除以零错误 + if (magnitudeV1 == 0 || magnitudeV2 == 0) + { + return 0f; + } + + // 计算夹角的余弦值 + float cosTheta = dotProduct / (magnitudeV1 * magnitudeV2); + + // 限制cosTheta的值范围在 -1 到 1 之间,以防计算机浮点误差 + cosTheta = Math.Max(-1f, Math.Min(1f, cosTheta)); + + // 返回角度,单位是度 + return (float)(Math.Acos(cosTheta) * (180.0 / Math.PI)); + } + + private static double GetBoundingBoxVolume(BoundingBox bound) + { + var size = bound.Size; + return size.X * size.Y * size.Z; + } + + private static Vector3 CalculateNormal(Vector3 p0, Vector3 p1, Vector3 p2) + { + var u = p1 - p0; + var v = p2 - p0; + return Vector3.Cross(u, v); + } + #endregion + +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Helper/ViewportManager.cs b/Views/UserControl/ViewportData/Helper/ViewportManager.cs new file mode 100644 index 0000000..c38f54f --- /dev/null +++ b/Views/UserControl/ViewportData/Helper/ViewportManager.cs @@ -0,0 +1,355 @@ +using HelixToolkit.Wpf.SharpDX; +using SharpDX; +using SharpDX.Direct3D11; +using SparkClient.Views.UserControl.ViewportData.Entity; +using SparkClient.Views.UserControl.ViewportData.Enum; + +namespace SparkClient.Views.UserControl.ViewportData.Helper; + +public class ViewportManager +{ + /// + /// 视图中三角形实体们 + /// + public static List ViewportTriangle = new List(); + /// + /// 模型正方向(从模型中心出发的方向) + /// + public static Vector3 PositiveDirection = new Vector3(0, 0, 0); + /// + /// 模型中心点 + /// + public static Vector3 CenterVector = new Vector3(0, 0, 0); + /// + /// 腰的顶部线 + /// + public static List GirdleTopLines = new List(); + /// + /// 腰的底部线 + /// + public static List GirdleBottomLines = new List(); + /// + /// 模型盒子 + /// + public static BoundingBox ModelBounds = new BoundingBox(); + /// + /// 模型主体 + /// + public static MeshGeometryModel3D MainModel3D = null; + /// + /// 模型边框线 + /// + public static List MainModelLines = new List(); + /// + /// 模型光照 + /// + public static List MainModelLighting = new List(); + /// + /// 模型控件对象映射 + /// + private static Viewport3DX _viewport; + public static void SetViewport3D(Viewport3DX viewport3D) + { + if (_viewport == null) + _viewport = viewport3D; + + } + #region 模型选中交互管理 + //是否双击选中 + public static bool DoubleClickSelect = true; + //开启选中边框线 + public static bool DoubleClickSelectShowBorder = true; + //选中边框长度文字 + public static bool DoubleClickSelectShowBorderLength = false; + //选中边框夹角文字 + public static bool DoubleClickSelectShowBorderAngle = false; + //选中同类面 + public static bool DoubleClickSelectShowPlaneType = true; + //选中三角形代码 + public static string ChooseTriangleCode = string.Empty; + //通过选中添加的元素 + public static List ChooseAddModels = new List(); + /// + /// 重置选中的面 + /// + public static void ResetChooseAddModels() + { + if (string.IsNullOrWhiteSpace(ChooseTriangleCode)) + { + return; + } + + ClearDicModels(); + HashSet models = new HashSet(); + models.Add(SelShowType.SelPanel); + if(DoubleClickSelectShowBorder) models.Add(SelShowType.Border); + if (DoubleClickSelectShowBorderLength) models.Add(SelShowType.LengthText); + if (DoubleClickSelectShowBorderAngle) models.Add(SelShowType.BorderAngle); + if (DoubleClickSelectShowPlaneType) models.Add(SelShowType.IsTypePanel); + ChooseAddModels.AddRange( ViewportHelperPro.GentrateChosenView(ChooseTriangleCode, models.ToArray())); + ChooseAddModels.ForEach(e => _viewport.Items.Add(e)); + } + /// + /// 清除所有的选中效果 + /// + public static void ClearDicModels() + { + foreach (var item in ChooseAddModels) + { + _viewport.Items.Remove(item); + } + ChooseAddModels.Clear(); + } + #endregion + + #region 统一控制方法 + + public static MeshGeometryModel3D PointTowardsTheFrontModel = new MeshGeometryModel3D(); + public static void PointTowardsTheFront(bool isPoint) + { + if (isPoint) + { + var camera = ViewportHelperPro.CalculateCamera(PositiveDirection, ModelBounds); + PointTowardsTheFrontModel = ViewportHelperPro.CreateArrow(camera.Position.ToVector3(), ModelBounds.Center, ModelBounds); + _viewport.Items.Add(PointTowardsTheFrontModel); + } + else + { + _viewport.Items.Remove(PointTowardsTheFrontModel); + } + } + + public static MeshGeometryModel3D MarkFacesModel = new MeshGeometryModel3D(); + public static void MarkSpecificFaces(bool isMark) + { + if (isMark) + { + MarkFacesModel = ViewportHelperPro.GenerateTypePanelHot(PlaneType.Error); + _viewport.Items.Add(MarkFacesModel); + } + else + { + _viewport.Items.Remove(MarkFacesModel); + } + } + + /// + /// 初始化加载模型 + /// + /// + /// + public static void LoadModelByEntities(List entities) + { + if (entities.Count == 0 && ViewportTriangle.Count != 0) + { + entities.AddRange(ViewportTriangle); + } + + ViewportTriangle.Clear(); + ViewportTriangle.AddRange(entities); + MainModel3D = ViewportHelperPro.GenerateModelByEntity(_viewport, entities); + MainModelLines = ViewportHelperPro.GentrateLineByEntity(_viewport, entities); + MainModelLighting = ViewportHelperPro.GenerateLightingForModel(_viewport); + //切换相机视角 + _viewport.Camera = ViewportHelperPro.CalculateCamera(PositiveDirection, ModelBounds); + _viewport.RenderHost.MSAA = MSAALevel.Maximum; + } + + /// + /// 是否显示主体模型 + /// + /// + public static void ShowMainModel3D(bool isShow) + { + if (MainModel3D == null) return; + if (_viewport == null) return; + if (isShow) + { + if(_viewport.Items.Contains(MainModel3D)) + return; + else + _viewport.Items.Add(MainModel3D); + } + else + { + _viewport.Items.Remove(MainModel3D); + } + } + + public static void ShowMainModelLines(bool isShow) + { + if (_viewport == null) return; + if (isShow) + { + MainModelLines.ForEach(e => + { + if(!_viewport.Items.Contains(e)) + _viewport.Items.Add(e); + }); + } + else + { + MainModelLines.ForEach(e => _viewport.Items.Remove(e)); + } + } + + public static void ShowMainModelLighting(bool isShow) + { + if (_viewport == null) return; + if (isShow) + { + MainModelLighting.ForEach(e => + { + if(!_viewport.Items.Contains(e)) + _viewport.Items.Add(e); + }); + } + else + { + MainModelLighting.ForEach(e => _viewport.Items.Remove(e)); + } + } + public static void Translucent(bool isShow) + { + if (_viewport == null) return; + if (isShow) + { + MainModel3D.Material = _glassMaterial; + } + else + { + MainModel3D.Material = _material; + } + } + + public static void TranslucentTex(bool isShow) + { + if (_viewport == null) return; + if (isShow) + { + string texturePath2 = "pack://Appliction:,,,/Res/morning_racing_circuit_16k.dds"; + + var texture2 = TextureModel.Create(texturePath2); + + var material = new PBRMaterial + { + AlbedoColor = new SharpDX.Color4(0.0f, 1.0f, 1.0f, 1.0f), // 白色基色 + MetallicFactor = 0.0f, + RoughnessFactor = 0.5f, + ReflectanceFactor = 0.2f, + RenderEnvironmentMap = false, + // AlbedoMap = texture, + IrradianceMap = texture2, + RenderIrradianceMap = true, + }; + MainModel3D.Material = material; + } + else + { + MainModel3D.Material = _material; + } + } + public static void LockCameraView(bool isShow) + { + /*Generate Ruler + * FixedPosition:用于静态视角,用户无法互动。 + * WalkAround:用于用户自由控制相机位置。 + * Inspect:用于观察和检查物体,用户可以旋转和缩放,但不能平移。(默认) + */ + // Viewport3Dx.CameraMode = CameraMode.Inspect; + if (_viewport == null) return; + if (isShow) + { + _viewport.CameraMode = CameraMode.FixedPosition; + } + else + { + _viewport.CameraMode = CameraMode.Inspect; + } + } + + #endregion + + + #region 预制颜色 + /// + /// 模型面颜色 + /// + public static Color4 LightGray = new Color4(0.7372f, 0.8235f, 0.93f, 1.0f); + /// + /// 模型面边框颜色 + /// + public static Color4 LineColor = new Color4(0.4117f, 0.3490f, 0.8039f, 1.0f); + /// + /// 选中面高亮色 + /// + public static Color4 DarkKhaki = new Color4(0.7411f, 0.71764f, 0.4196f, 1.0f); + /// + /// 选中面同类面的高亮色 + /// + public static Color4 LtGoldenrodYello = new Color4(0.98039f, 0.98039f, 0.813529f, 1.0f); + /// + /// 选中面高亮的边框色 + /// + public static Color4 Black = new Color4(0f, 0f, 0f, 1.0f); + /// + /// 文字颜色 + /// + public static Color4 SandyBrown = new Color4(0.95686f, 0.64313f, 0.37647f, 1.0f); + public static Color4 Red = new Color4(1f, 0.0f, 0.0f, 1.0f); + + /// + /// 材质:半透明蓝 + /// + private static PBRMaterial _glassMaterial = new PBRMaterial + { + // 半透明蓝色 + AlbedoColor = new Color4(0.0f, 0.0f, 1.0f, 0.5f), + // 设置为非金属 + MetallicFactor = 0.0, + // 光滑表面 + RoughnessFactor = 0.05, + // 环境光遮蔽 + AmbientOcclusionFactor = 1.0, + // 强反射 + ReflectanceFactor = 0.9, + // 清漆效果(类似折射效果) + ClearCoatStrength = 0.8, + ClearCoatRoughness = 0.05, + // 启用环境贴图以增强反射 + RenderEnvironmentMap = true, + // 启用阴影效果 + RenderShadowMap = true + }; + + /// + /// 默认灰色自发光 + /// + private static PBRMaterial _material = new PBRMaterial + { + AlbedoColor = Black, // 黑色,避免其他光照影响 + EmissiveColor = LightGray , // LightGray #D3D3D3 + MetallicFactor = 0.0, // 非金属 + RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 + ReflectanceFactor = 0.0, // 无反射 + ClearCoatStrength = 0.0, // 无清漆效果 + ClearCoatRoughness = 1.0, // 高粗糙度 + SurfaceMapSampler = new SamplerStateDescription + { + Filter = Filter.MinMagMipLinear, + AddressU = TextureAddressMode.Wrap, + AddressV = TextureAddressMode.Wrap, + AddressW = TextureAddressMode.Wrap + } + }; + + #endregion +} +public enum SelShowType +{ + Border = 0, + LengthText = 1, + BorderAngle = 2, + IsTypePanel = 3, + SelPanel = 4, +} \ No newline at end of file diff --git a/Views/UserControl/ViewportData/ViewportData.cs b/Views/UserControl/ViewportData/ViewportData.cs new file mode 100644 index 0000000..c0f6147 --- /dev/null +++ b/Views/UserControl/ViewportData/ViewportData.cs @@ -0,0 +1,143 @@ +using System.Text.Json.Nodes; +using SharpDX; +using SparkClient.Views.UserControl.ViewportData.Entity; +using SparkClient.Views.UserControl.ViewportData.Enum; +using SparkClient.Views.UserControl.ViewportData.Helper; + +namespace SparkClient.Views.UserControl.ViewportData; + +public class ViewportData +{ + /// + /// 钻石编码 + /// + public String DiamondCode { get; set; } + + /// + /// 钻石数据 + /// + public String DiamondData { get; set; } + + /// + /// 初始化构造 + /// + /// 钻石编码 + /// 钻石数据 + public ViewportData(string diamondCode, string diamondData) + { + DiamondCode = diamondCode; + DiamondData = diamondData; + } + /// + /// 空白构造 + /// + public ViewportData() + { + } + + public void LoadData() + { + JsonNode? json = null; + try + { + json = JsonNode.Parse(DiamondData); + } + catch + { + throw new Exception("DiamondData is invalid"); + } + + if(json == null) throw new Exception("DiamondData is invalid"); + + try + { + List facets = new List(); + if (json.AsObject()["facets"] != null) + { + JsonArray jsonArray = json.AsObject()["facets"].AsArray(); + foreach (var item in jsonArray) + { + var value = CommonHelper.CreateByJsonStr(item.ToString()); + facets.Add(value); + } + } + else + { + throw new Exception("facets is invalid"); + } + + if (json.AsObject()["positive_direction"] != null) + { + JsonObject positive = json.AsObject()["positive_direction"].AsObject(); + ViewportManager.PositiveDirection.X = float.Parse(positive["x"]?.ToString() ?? "1.0"); + ViewportManager.PositiveDirection.Y = float.Parse(positive["z"]?.ToString() ?? "0"); + ViewportManager.PositiveDirection.Z = float.Parse(positive["y"]?.ToString() ?? "0"); + } + else + { + ViewportManager.PositiveDirection.X = 1.0f; + ViewportManager.PositiveDirection.Y = 0f; + ViewportManager.PositiveDirection.Z = 0; + } + + var midZ = facets.SelectMany(e => new[] { e.Point1.X, e.Point2.X, e.Point3.X }).OrderBy(z => Math.Abs(z)) + .First(); + List facetsFinal = new List(); + foreach (var item in facets) + { + var data = ViewportHelperPro.VectorClockwiseSort(new List + { item.Point1, item.Point2, item.Point3 }); + + if (item.PlaneType == PlaneType.StarFacet || item.PlaneType == PlaneType.UpperGirdleFacet || + item.PlaneType == PlaneType.UpperMainFacet || item.PlaneType == PlaneType.TableFacet) + { + item.Point1 = data[0]; + item.Point2 = data[1]; + item.Point3 = data[2]; + } + else if (item.PlaneType == PlaneType.Girdle) + { + var center = ViewportHelperPro.GetCentroid(data); + data.Sort((v1, v2) => + { + double angle1 = Math.Atan2(v1.Y - center.Y, v1.X - center.X); + double angle2 = Math.Atan2(v2.Y - center.Y, v2.X - center.X); + return angle1.CompareTo(angle2); + }); + if (center.Z >= midZ) + { + + item.Point1 = data[0]; + item.Point2 = data[1]; + item.Point3 = data[2]; + } + else + { + item.Point1 = data[2]; + item.Point2 = data[1]; + item.Point3 = data[0]; + + } + } + else + { + item.Point1 = data[2]; + item.Point2 = data[1]; + item.Point3 = data[0]; + } + + item.TriangleCode = CommonHelper.GenerateTriangleCode(item.Point1, item.Point2, item.Point3); + facetsFinal.Add(item); + } + + ViewportManager.ViewportTriangle.AddRange(facetsFinal); + // ViewportManager.LoadModelByEntities(facetsFinal); + } + catch (Exception ex) + { + throw new Exception(ex.Message); + } + } + + +} \ No newline at end of file From d5b8e1cd5878bff2890cbaecf37b40cc9435638f Mon Sep 17 00:00:00 2001 From: Tongg Date: Thu, 12 Dec 2024 17:33:06 +0800 Subject: [PATCH 3/5] =?UTF-8?q?fix:=203D=E5=8F=B3=E9=94=AE=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Views/UserControl/Viewport3D.xaml | 4 ++-- Views/UserControl/Viewport3D.xaml.cs | 34 ++++++++++++++++++++++++++-- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/Views/UserControl/Viewport3D.xaml b/Views/UserControl/Viewport3D.xaml index 772b276..fca505d 100644 --- a/Views/UserControl/Viewport3D.xaml +++ b/Views/UserControl/Viewport3D.xaml @@ -75,7 +75,7 @@ - + @@ -95,7 +95,7 @@ - + diff --git a/Views/UserControl/Viewport3D.xaml.cs b/Views/UserControl/Viewport3D.xaml.cs index cdaa1e1..01bb24c 100644 --- a/Views/UserControl/Viewport3D.xaml.cs +++ b/Views/UserControl/Viewport3D.xaml.cs @@ -172,15 +172,29 @@ public partial class Viewport3D #endregion #region 页面隐式交互 + private System.Windows.Point _mouseDownPosition; + private bool _isDragging = false; private void UIElement_OnPreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) { + if (_isDragging) + { + // 如果发生拖动,不显示菜单 + _isDragging = false; // 重置拖动状态 + MainBorder.ContextMenu.Visibility = Visibility.Hidden; + MainBorder.ContextMenu.IsOpen = false; + return; + } + MainBorder.ContextMenu.Visibility = Visibility.Visible; + // 未拖动时,显示右键菜单 MainBorder.ContextMenu.IsOpen = true; + // e.Handled = true; // 阻止事件继续传播 } private void UIElement_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { - MainBorder.ContextMenu.IsOpen = false; - e.Handled = true; + _mouseDownPosition = e.GetPosition(Viewport3Dx); + _isDragging = false; // 重置拖动状态 + } #endregion @@ -189,4 +203,20 @@ public partial class Viewport3D //ViewportData.LoadData(); ViewportManager.LoadModelByEntities(new List()); } + + private void Viewport3Dx_OnPreviewMouseMove(object sender, MouseEventArgs e) + { + if (e.RightButton == MouseButtonState.Pressed) + { + // 计算鼠标移动距离 + var currentPosition = e.GetPosition(Viewport3Dx); + var distance = (currentPosition - _mouseDownPosition).Length; + + // 判断是否达到拖动的阈值 + if (distance > 5) // 阈值可根据需要调整 + { + _isDragging = true; // 标记为拖动 + } + } + } } \ No newline at end of file From d524ddfe8b5d6083c34eeb4e5096ad1aaeb22aaa Mon Sep 17 00:00:00 2001 From: sunhonglei Date: Thu, 12 Dec 2024 19:17:10 +0800 Subject: [PATCH 4/5] feat: --- ViewModel/Grading/GradingResultVM.cs | 90 ++++++++++++++++++++++++---- Views/Dialog/JsonImport.xaml | 2 +- Views/Grading/GradingResult.xaml | 12 +--- 3 files changed, 80 insertions(+), 24 deletions(-) diff --git a/ViewModel/Grading/GradingResultVM.cs b/ViewModel/Grading/GradingResultVM.cs index 8a5b137..d76d9e7 100644 --- a/ViewModel/Grading/GradingResultVM.cs +++ b/ViewModel/Grading/GradingResultVM.cs @@ -1,7 +1,9 @@ using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.IO; using System.Reflection; +using System.Windows.Forms; using System.Windows.Input; using System.Windows.Shapes; using HandyControl.Controls; @@ -31,6 +33,8 @@ public class GradingResultVM : BaseViewModel private string _cutLevelTotal; private string _symLevelTotal; public ICommand ChangeSymCommand { get; } + + public ICommand SaveAsCommand { get; } public List DtResults { get { return _dtResults; } set { _dtResults = value; OnPropertyChanged(nameof(DtResults)); } } public ViewportData ViewportData { get { return _viewportData; } set { _viewportData = value; OnPropertyChanged(nameof(ViewportData)); } } public string Standard { get { return _standard; } set { _standard = value; OnPropertyChanged(nameof(Standard)); } } @@ -53,7 +57,6 @@ public class GradingResultVM : BaseViewModel /// 检测结果 public GradingResultVM(object result) { - ChangeSymCommand = new RelayCommand(ChangeSym); if (result != null) { InitView(result as AlgorithmResultEntity); @@ -441,6 +444,45 @@ public class GradingResultVM : BaseViewModel { return m*m; } + public void SaveAs() + { + string filePath = ""; + using (var folderBrowserDlg = new FolderBrowserDialog()) + { + DialogResult result = folderBrowserDlg.ShowDialog(); + + if (result == DialogResult.OK) + { + filePath = folderBrowserDlg.SelectedPath; + ExportFile(filePath); + } + } + } + private void ExportFile(string filePath) + { + TxtFile(filePath); + ExcelFile(filePath); + DatFile(filePath); + STLFile(filePath); + } + + private void TxtFile(string filePath) + { + + } + private void ExcelFile(string filePath) + { + + } + private void DatFile(string filePath) + { + + } + private void STLFile(string filePath) + { + + } + private string GetName(string id) { // TODO DB没关联 @@ -531,7 +573,7 @@ public class CalGradeInfo{ public int isMaxExist { get; set; } public int isMinExist { get; set; } } -public class DataInfo() : BaseViewModel +public class DataInfo { public string? TestItemId { get; set; } public string? TestItemName { get; set; } @@ -541,18 +583,40 @@ public class DataInfo() : BaseViewModel public string? Max { get; set; } public string? CutLevel { get; set; } private int? _symLevel; - public int? SymLevel { get { return _symLevel; } set { _symLevel = value; OnPropertyChanged(nameof(SymLevel)); }} - public DataTable GradeList { get { + //public int? SymLevel { get; set; } + public int? SymLevel + { + get { return _symLevel; } + set + { + if (_symLevel != value) + { + _symLevel = value; + OnPropertyChanged(nameof(SymLevel)); + } + } + } + private DataTable? _gradeList; + public DataTable GradeList { + get + { // TODO DB没关联 - DataTable GradeList = new DataTable(); - GradeList.Columns.Add("Key"); - GradeList.Columns.Add("Value"); - GradeList.Rows.Add("极好", 1); - GradeList.Rows.Add("很好", 2); - GradeList.Rows.Add("好", 3); - GradeList.Rows.Add("一般", 4); - GradeList.Rows.Add("差", 5); - return GradeList; + if (this._gradeList == null) { + _gradeList = new DataTable(); + _gradeList.Columns.Add("Key"); + _gradeList.Columns.Add("Value"); + _gradeList.Rows.Add("极好", 1); + _gradeList.Rows.Add("很好", 2); + _gradeList.Rows.Add("好", 3); + _gradeList.Rows.Add("一般", 4); + _gradeList.Rows.Add("差", 5); + } + return _gradeList; } } + public event PropertyChangedEventHandler PropertyChanged; + protected void OnPropertyChanged(string propertyName) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } } \ No newline at end of file diff --git a/Views/Dialog/JsonImport.xaml b/Views/Dialog/JsonImport.xaml index 27dbf80..36447ec 100644 --- a/Views/Dialog/JsonImport.xaml +++ b/Views/Dialog/JsonImport.xaml @@ -66,7 +66,7 @@ - - - - - - - - - + + + + + + + + + + diff --git a/Views/UserControl/Viewport3D.xaml.cs b/Views/UserControl/Viewport3D.xaml.cs index 01bb24c..c43d73e 100644 --- a/Views/UserControl/Viewport3D.xaml.cs +++ b/Views/UserControl/Viewport3D.xaml.cs @@ -2,6 +2,7 @@ using System.Text.Json.Nodes; using System.Windows; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Media.Media3D; using HandyControl.Controls; using HelixToolkit.Wpf.SharpDX; using SharpDX; @@ -13,210 +14,353 @@ namespace SparkClient.Views.UserControl; public partial class Viewport3D { - // 注册一个依赖属性,类型是 Viewport3DData - public static readonly DependencyProperty ViewportDataProperty = - DependencyProperty.Register( - nameof(ViewportData), - typeof(ViewportData.ViewportData), - typeof(Viewport3D), - new PropertyMetadata(null, OnViewportDataChanged)); - // 公开的 CLR 属性包装器 - public ViewportData.ViewportData ViewportData + public Viewport3D() { - get => (ViewportData.ViewportData)GetValue(ViewportDataProperty); - set => SetValue(ViewportDataProperty, value); + InitializeComponent(); + DataContext = this; + this.Viewport3Dx.EffectsManager = new DefaultEffectsManager(); + Viewport3Dx.ShowViewCube = false; + Viewport3Dx.ShowCoordinateSystem = false; + ViewportManager.SetViewport3D(Viewport3Dx); } - // 当 ViewportData 属性发生变化时触发 - private static void OnViewportDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + /// + /// 初始化 + /// + /// + /// + private void Viewport3D_OnLoaded(object sender, RoutedEventArgs e) { - if (d is Viewport3D control && e.NewValue is ViewportData.ViewportData data) - { - control.Initialize(data); - } + ViewportManager.LoadModelByEntities(new List()); + + //选项初始化 显示-后端-管理类 一致 + ViewportRightMenuSelectFaceFrame.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceLengthText.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceAngleText.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceKind.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuShowModelFace.IsChecked = true; + ViewportRightMenuShowModelFrame.IsChecked = true; + ViewportRightMenuShowLighting.IsChecked = true; + ViewportRightMenuSelectFace.IsChecked = ViewportManager.DoubleClickSelect; + ViewportRightMenuSelectFaceFrame.IsChecked = ViewportManager.DoubleClickSelectShowBorder; + ViewportRightMenuSelectFaceLengthText.IsChecked = ViewportManager.DoubleClickSelectShowBorderLength; + ViewportRightMenuSelectFaceAngleText.IsChecked = ViewportManager.DoubleClickSelectShowBorderAngle; + ViewportRightMenuSelectFaceKind.IsChecked = ViewportManager.DoubleClickSelectShowPlaneType; + } - - - private void Initialize(ViewportData.ViewportData vpData) + /// + /// 双击选择面 + /// + /// + /// + private void Viewport3Dx_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - // 根据 Viewport3DData 的内容初始化控件 - // 例如:设置控件内部的某些属性,渲染 3D 模型等 - if (!string.IsNullOrEmpty(vpData.DiamondData)) + if (ViewportManager.DoubleClickSelect == false) return; + + var mousePosition = e.GetPosition(Viewport3Dx); + var hits = Viewport3Dx.FindHits(mousePosition); + // 如果没有命中任何 3D 对象 + if (hits == null || hits.Count == 0) { - JsonNode? json = null; - try - { - json = JsonNode.Parse(vpData.DiamondData); - } - catch - { - throw new Exception("DiamondData is invalid"); - } - - if(json == null) throw new Exception("DiamondData is invalid"); + ViewportManager.ClearDicModels(); + return; + } - try + // 获取第一个命中的对象 + var hit = hits[0]; + // 检查是否是 MeshGeometryModel3D + if (hit.ModelHit is MeshGeometryModel3D modelHit) + { + // 获取几何信息 + var geometry = modelHit.Geometry as HelixToolkit.Wpf.SharpDX.MeshGeometry3D; + if (geometry != null) { - List facets = new List(); - if (json.AsObject()["facets"] != null) - { - JsonArray jsonArray = json.AsObject()["facets"].AsArray(); - foreach (var item in jsonArray) - { - var value = CommonHelper.CreateByJsonStr(item.ToString()); - facets.Add(value); - } - } - else - { - throw new Exception("facets is invalid"); - } + // 获取命中的三角形索引 + var triangleIndex = hit.TriangleIndices; - if (json.AsObject()["positive_direction"] != null) - { - JsonObject positive = json.AsObject()["positive_direction"].AsObject(); - ViewportManager.PositiveDirection.X = float.Parse(positive["x"]?.ToString() ?? "1.0"); - ViewportManager.PositiveDirection.Y = float.Parse(positive["z"]?.ToString() ?? "0"); - ViewportManager.PositiveDirection.Z = float.Parse(positive["y"]?.ToString() ?? "0"); - } - else - { - ViewportManager.PositiveDirection.X = 1.0f; - ViewportManager.PositiveDirection.Y = 0f; - ViewportManager.PositiveDirection.Z = 0; - } - - var midZ = facets.SelectMany(e => new[] { e.Point1.X, e.Point2.X, e.Point3.X }).OrderBy(z => Math.Abs(z)) - .First(); - List facetsFinal = new List(); - foreach (var item in facets) - { - var data = ViewportHelperPro.VectorClockwiseSort(new List - { item.Point1, item.Point2, item.Point3 }); - - if (item.PlaneType == PlaneType.StarFacet || item.PlaneType == PlaneType.UpperGirdleFacet || - item.PlaneType == PlaneType.UpperMainFacet || item.PlaneType == PlaneType.TableFacet) - { - item.Point1 = data[0]; - item.Point2 = data[1]; - item.Point3 = data[2]; - } - else if (item.PlaneType == PlaneType.Girdle) - { - var center = ViewportHelperPro.GetCentroid(data); - data.Sort((v1, v2) => - { - double angle1 = Math.Atan2(v1.Y - center.Y, v1.X - center.X); - double angle2 = Math.Atan2(v2.Y - center.Y, v2.X - center.X); - return angle1.CompareTo(angle2); - }); - if (center.Z >= midZ) - { - - item.Point1 = data[0]; - item.Point2 = data[1]; - item.Point3 = data[2]; - } - else - { - item.Point1 = data[2]; - item.Point2 = data[1]; - item.Point3 = data[0]; - - } - } - else - { - item.Point1 = data[2]; - item.Point2 = data[1]; - item.Point3 = data[0]; - } - - item.TriangleCode = CommonHelper.GenerateTriangleCode(item.Point1, item.Point2, item.Point3); - facetsFinal.Add(item); - } - - // ViewportManager.ViewportTriangle.AddRange(facetsFinal); - ViewportManager.LoadModelByEntities(facetsFinal); - } - catch (Exception ex) - { - throw new Exception(ex.Message); + // 获取三角形顶点 + var vertex1 = geometry.Positions[triangleIndex.Item1]; + var vertex2 = geometry.Positions[triangleIndex.Item2]; + var vertex3 = geometry.Positions[triangleIndex.Item3]; + + ViewportManager.ChooseTriangleCode = CommonHelper.GenerateTriangleCode(vertex1, vertex2, vertex3); + ViewportManager.ResetChooseAddModels(); } } + else + { + ViewportManager.ClearDicModels(); + } } - - public Viewport3D() + + /// + /// 顶部按钮 - 视角切换 + /// + /// + /// + /// + private void BtnAngle_OnClick(object sender, RoutedEventArgs e) { - InitializeComponent(); - DataContext = this; - this.Viewport3Dx.EffectsManager = new DefaultEffectsManager(); - ViewportManager.SetViewport3D(Viewport3Dx); + var directionName = ((Button)sender).Name.ToString(); + var directionValue = (int) TbCustomizeRevolve.Value; + + var center = ViewportManager.ModelBounds.Center; + var maxDimension = ViewportManager.ModelBounds.Size.Length(); + var distance = maxDimension *1.2; // 调整相机到模型的距离,保证视野范围内 + // 获取当前相机 + var camera = Viewport3Dx.Camera as HelixToolkit.Wpf.SharpDX.PerspectiveCamera; + + switch (directionName) + { + case "BtnFrontView": + //测 + camera.Position = new Point3D(center.X, center.Y, center.Z + distance); // 从前面看,Z轴正方向 + camera.UpDirection = new Vector3D(0, -1, 0); + break; + break; + case "BtnTopView": + //顶、 + camera.Position = new Point3D(center.X, center.Y - distance, center.Z); // 从底部看,Y轴负方向 + camera.UpDirection = new Vector3D(0, 0, -1); + + break; + case "BtnBottomView": + //低 + camera.Position = new Point3D(center.X, center.Y + distance, center.Z); // 从顶部看,Y轴正方向 + camera.UpDirection = new Vector3D(0, 0, 1); + break; + + } + camera.LookDirection = new Vector3D(center.X - camera.Position.X, center.Y - camera.Position.Y, center.Z - camera.Position.Z); } - - #region 菜单事件绑定 - private void MenuItem_OnChecked(object sender, RoutedEventArgs e) + + /// + /// 按钮调整相机方向[底部按钮] + /// + /// + /// + private void BtnDirection_OnClick(object sender, RoutedEventArgs e) { - Growl.InfoGlobal("Viewport is checked" + e.OriginalSource); + var directionName = ((Button)sender).Name.ToString(); + var directionValue = (int) TbCustomizeRevolve.Value; + + switch (directionName) + { + case "BtnTop": + //上 + break; + case "BtnBottom": + //下 + break; + case "BtnLeft": + //左 + break; + case "BtnRight": + //右 + break; + } } - private void MenuItem_OnUnchecked(object sender, RoutedEventArgs e) + + #region 右键菜单事件绑定 + /// + /// 右键选择 + /// + /// + /// + private void MenuItem_OnCheckedChanged(object sender, RoutedEventArgs e) { - Growl.InfoGlobal("Viewport is Unchecked" + e.OriginalSource); + ViewportRightMenuSelectFaceFrame.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceLengthText.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceAngleText.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + ViewportRightMenuSelectFaceKind.IsEnabled = ViewportRightMenuSelectFace.IsChecked ; + + var controlName = ((MenuItem)sender).Name.ToString(); + var checkResult = (bool)((MenuItem)sender).IsChecked; + switch (controlName) + { + case "ViewportRightMenuShowModelFace": + //显示模型面 + ViewportManager.ShowMainModel3D(checkResult); + break; + case "ViewportRightMenuShowModelFrame": + //显示模型边框 + ViewportManager.ShowMainModelLines(checkResult); + break; + case "ViewportRightMenuShowLighting": + //显示光照 + ViewportManager.ShowMainModelLighting(checkResult); + break; + case "ViewportRightMenuShowDefectFace": + //显示瑕疵面 + ViewportManager.MarkSpecificFaces(checkResult); + break; + case "ViewportRightMenuShowFront": + //显示正方向箭头 + ViewportManager.PointTowardsTheFront(checkResult); + break; + case "ViewportRightMenuSelectFace": + //双击选择面 + ViewportManager.DoubleClickSelect = checkResult; + ViewportManager.ClearDicModels(); + break; + case "ViewportRightMenuSelectFaceFrame": + //双击选择面的边框 + ViewportManager.DoubleClickSelectShowBorder = checkResult; + ViewportManager.ResetChooseAddModels(); + break; + case "ViewportRightMenuSelectFaceLengthText": + //双击选择面的边框长度文字 + ViewportManager.DoubleClickSelectShowBorderLength = checkResult; + ViewportManager.ResetChooseAddModels(); + break; + case "ViewportRightMenuSelectFaceAngleText": + //双击选择面的边框夹角文字 + ViewportManager.DoubleClickSelectShowBorderAngle = checkResult; + ViewportManager.ResetChooseAddModels(); + break; + case "ViewportRightMenuSelectFaceKind": + //双击选择面的选择同类面 + ViewportManager.DoubleClickSelectShowPlaneType = checkResult; + ViewportManager.ResetChooseAddModels(); + break; + } } + /// + /// 右键按钮 + /// + /// + /// private void MenuItem_OnClick(object sender, RoutedEventArgs e) { - + var controlName = ((MenuItem)sender).Name.ToString(); + switch (controlName) + { + case "ViewportRightMenuFront": + //切换到正面 + var Camear = + ViewportHelperPro.CalculateCamera(ViewportManager.PositiveDirection, ViewportManager.ModelBounds); + Viewport3Dx.Camera.Position = Camear.Position; + Viewport3Dx.Camera.LookDirection = Camear.LookDirection; + Viewport3Dx.Camera.UpDirection = new Vector3D(Camear.UpDirection.X, Camear.UpDirection.Y*-1, Camear.UpDirection.Z); + break; + case "ViewportRightMenuSaveViewToPNG": + //保存图片 + break; + } } #endregion + #region 页面隐式交互 + + public static readonly DependencyProperty ViewportDataProperty = + DependencyProperty.Register( + nameof(ViewportData), + typeof(ViewportData.ViewportData), + typeof(Viewport3D), + new PropertyMetadata(null, OnViewportDataChanged)); + + public ViewportData.ViewportData ViewportData + { + get => (ViewportData.ViewportData)GetValue(ViewportDataProperty); + set => SetValue(ViewportDataProperty, value); + } + private static void OnViewportDataChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + if (d is Viewport3D control && e.NewValue is ViewportData.ViewportData data) + { + control.Initialize(data); + } + } + + + private void Initialize(ViewportData.ViewportData vpData) + { + + } + private System.Windows.Point _mouseDownPosition; private bool _isDragging = false; private void UIElement_OnPreviewMouseRightButtonUp(object sender, MouseButtonEventArgs e) { if (_isDragging) { - // 如果发生拖动,不显示菜单 - _isDragging = false; // 重置拖动状态 + _isDragging = false; MainBorder.ContextMenu.Visibility = Visibility.Hidden; - MainBorder.ContextMenu.IsOpen = false; return; } MainBorder.ContextMenu.Visibility = Visibility.Visible; - // 未拖动时,显示右键菜单 - MainBorder.ContextMenu.IsOpen = true; - // e.Handled = true; // 阻止事件继续传播 + } private void UIElement_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { _mouseDownPosition = e.GetPosition(Viewport3Dx); - _isDragging = false; // 重置拖动状态 - - } - #endregion - - private void Viewport3D_OnLoaded(object sender, RoutedEventArgs e) - { - //ViewportData.LoadData(); - ViewportManager.LoadModelByEntities(new List()); + _isDragging = false; + if (MainBorder.ContextMenu.Visibility == Visibility.Visible) + { + MainBorder.ContextMenu.Visibility = Visibility.Hidden; + MainBorder.ContextMenu.IsOpen = false; + } } - + private void Viewport3Dx_OnPreviewMouseMove(object sender, MouseEventArgs e) { if (e.RightButton == MouseButtonState.Pressed) { - // 计算鼠标移动距离 var currentPosition = e.GetPosition(Viewport3Dx); var distance = (currentPosition - _mouseDownPosition).Length; + MainBorder.ContextMenu.Visibility = Visibility.Hidden; + MainBorder.ContextMenu.IsOpen = false; + if (distance > 5) + { + _isDragging = true; + } + } + } + private void TbCustomizeRevolve_OnPreviewTextInput(object sender, TextCompositionEventArgs e) + { + e.Handled = !CommonHelper.IsTextNumeric(e.Text); + } + + private void TbCustomizeRevolve_OnPreviewKeyDown(object sender, KeyEventArgs e) + { + if (e.Key == Key.Back || e.Key == Key.Delete || e.Key == Key.Tab || e.Key == Key.Left || e.Key == Key.Right) + { + e.Handled = false; // 不拦截 + } + else + { + e.Handled = !CommonHelper.IsKeyNumeric(e.Key); + } + } + + private void TbCustomizeRevolve_OnTextChanged(object sender, TextChangedEventArgs e) + { + var textBox = sender as System.Windows.Controls.TextBox; - // 判断是否达到拖动的阈值 - if (distance > 5) // 阈值可根据需要调整 + if (int.TryParse(textBox.Text, out int value)) + { + if (value < 0) { - _isDragging = true; // 标记为拖动 + textBox.Text = "0"; // 如果小于0,重置为0 } + else if (value > 360) + { + textBox.Text = "360"; // 如果大于360,重置为360 + } + textBox.SelectionStart = textBox.Text.Length; // 保持光标在文本末尾 + } + else if (!string.IsNullOrWhiteSpace(textBox.Text)) + { + textBox.Text = "0"; } } + + #endregion + + + } \ No newline at end of file diff --git a/Views/UserControl/ViewportData/Helper/CommonHelper.cs b/Views/UserControl/ViewportData/Helper/CommonHelper.cs index 83a7362..ab4ab29 100644 --- a/Views/UserControl/ViewportData/Helper/CommonHelper.cs +++ b/Views/UserControl/ViewportData/Helper/CommonHelper.cs @@ -1,5 +1,6 @@ using System.Security.Cryptography; using System.Text; +using System.Windows.Input; using Newtonsoft.Json; using SharpDX; using SparkClient.Views.UserControl.ViewportData.Entity; @@ -9,6 +10,18 @@ namespace SparkClient.Views.UserControl.ViewportData.Helper; public class CommonHelper { + + public static bool IsTextNumeric(string text) + { + return int.TryParse(text, out _); // 只允许数字输入 + } + + public static bool IsKeyNumeric(Key key) + { + // 判断按键是否是数字键 + return (key >= Key.D0 && key <= Key.D9) || (key >= Key.NumPad0 && key <= Key.NumPad9); + } + public static Viewport3DTriangleEntity CreateByJsonStr(string json) { // 解析 JSON 数据 diff --git a/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs b/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs index c4398bd..3d59451 100644 --- a/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs +++ b/Views/UserControl/ViewportData/Helper/ViewportHelperPro.cs @@ -558,11 +558,11 @@ public class ViewportHelperPro Vector3 cameraPosition, Vector3 modelCenter, BoundingBox modelBounds, - float totalLength = 2f, // 箭头总长度 + float totalLength = 1.5f, // 箭头总长度 float cylinderRatio = 0.7f,// 圆柱部分占比 - float diameter = 0.5f, // 箭头直径 - float headDiameterRatio = 1f, // 圆锥直径与圆柱直径的比例 - float padding = 2.0f + float diameter = 0.25f, // 箭头直径 + float headDiameterRatio = 2f, // 圆锥直径与圆柱直径的比例 + float padding = 1.0f ) { // 计算模型的半径(对角线的一半) diff --git a/Views/UserControl/ViewportData/Helper/ViewportManager.cs b/Views/UserControl/ViewportData/Helper/ViewportManager.cs index c38f54f..371044d 100644 --- a/Views/UserControl/ViewportData/Helper/ViewportManager.cs +++ b/Views/UserControl/ViewportData/Helper/ViewportManager.cs @@ -1,8 +1,10 @@ +using System.Windows.Media.Media3D; using HelixToolkit.Wpf.SharpDX; using SharpDX; using SharpDX.Direct3D11; using SparkClient.Views.UserControl.ViewportData.Entity; using SparkClient.Views.UserControl.ViewportData.Enum; +using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D; namespace SparkClient.Views.UserControl.ViewportData.Helper; @@ -152,6 +154,7 @@ public class ViewportManager MainModelLighting = ViewportHelperPro.GenerateLightingForModel(_viewport); //切换相机视角 _viewport.Camera = ViewportHelperPro.CalculateCamera(PositiveDirection, ModelBounds); + _viewport.Camera.UpDirection = new Vector3D(0, -1, 0); _viewport.RenderHost.MSAA = MSAALevel.Maximum; }