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 log4net; 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; using MeshGeometry3D = HelixToolkit.Wpf.SharpDX.MeshGeometry3D; using Point = System.Windows.Point; using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera; using System.Windows.Controls; using HelixToolkit.Wpf; using MeshBuilder = HelixToolkit.Wpf.SharpDX.MeshBuilder; using OrthographicCamera = HelixToolkit.Wpf.SharpDX.OrthographicCamera; using System.Windows.Input; namespace SparkClient.Views.UserControl.ViewportData.Helper; public class ViewportHelperPro { private static readonly ILog Logger = LogManager.GetLogger(typeof(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.ColorConfig.ErrFacetColor); } #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) { if (entity.PlaneType == PlaneType.Girdle) { meshBuilder.AddTriangleFan(new List() { entity.Point1, entity.Point2, entity.Point3 }, new List() { new Vector3(0, 0, 0), new Vector3(0, 0, 0), new Vector3(0, 0, 0) }); } else { meshBuilder.AddPolygon(new List() { entity.Point1, entity.Point2, entity.Point3 }); } } var mesh = meshBuilder.ToMeshGeometry3D(); var material = new PBRMaterial { AlbedoColor = new Color4(0.0f, 0f,0f,0.8f), // 黑色,避免其他光照影响 EmissiveColor =color ?? ViewportManager.ColorConfig.MainFacetColor , // 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 List GenerateModelByEntityGroupByType(List entities) { var groupedDict = entities .GroupBy(e => e.PlaneType) .ToDictionary(g => g.Key, g => g.ToList()); var result = new List(); foreach (var group in groupedDict) { var key = group.Key; result.Add(GenerateModelByEntity(group.Value, GenFaceColor4(key))); } return result; } private static Color4? GenFaceColor4(PlaneType planeType) { switch (planeType) { case PlaneType.Girdle: return ViewportManager.ColorConfig.GirdleFacetColor; case PlaneType.TableFacet: return ViewportManager.ColorConfig.TableFacetColor; case PlaneType.UpperMainFacet: return ViewportManager.ColorConfig.UpperMainFacetColor; case PlaneType.StarFacet: return ViewportManager.ColorConfig.StarFacetColor; case PlaneType.UpperGirdleFacet: return ViewportManager.ColorConfig.UpperGirdleFacetColor; case PlaneType.PavilionMainFacet: return ViewportManager.ColorConfig.PavilionFacetColor; case PlaneType.LowerGirdleFact: return ViewportManager.ColorConfig.LowerGirdleFacetColor; case PlaneType.Culet: return ViewportManager.ColorConfig.CuletFacetColor; } return null; } /// /// 保存模型截图 /// /// /// 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 async Task ExportModelsToVideo(Viewport3DX viewport, string filePath) { if (viewport == null) viewport = ViewportManager.GetViewport3D(); var generationTask = VideoHelper.StartGenerationAndRotation(viewport); List pngList = await generationTask; await VideoHelper.CreateVideoFromPngListAsync(pngList, filePath); } /// /// 导出模型 /// /// /// public static void ExportModelsToStl(Viewport3DX viewport, string filePath) { if (viewport == null) viewport = ViewportManager.GetViewport3D(); // 打开文件写入流 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 async Task ExportModelsToStlASync(string filePath) { Viewport3DX viewport = ViewportManager.GetViewport3D(); // 创建一个集合来存储几何信息 List, IList>> geometries = new List, IList>>(); // 在 UI 线程中收集几何信息 await Application.Current.Dispatcher.InvokeAsync(() => { foreach (var model in viewport.Items.OfType()) { if (model.Geometry is HelixToolkit.Wpf.SharpDX.Geometry3D geometry) { geometries.Add(new Tuple, IList>(geometry.Positions, geometry.Indices)); } } }); // 在后台线程中处理几何信息并写入 STL 文件 await Task.Run(() => { using (StreamWriter writer = new StreamWriter(filePath)) { writer.WriteLine("solid exportedModel"); foreach (var geometry in geometries) { var positions = geometry.Item1; var indices = geometry.Item2; // 每三个索引构成一个三角形 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.Y} {normal.Z}"); writer.WriteLine(" outer loop"); writer.WriteLine($" vertex {p0.X} {p0.Y} {p0.Z}"); writer.WriteLine($" vertex {p1.X} {p1.Y} {p1.Z}"); writer.WriteLine($" vertex {p2.X} {p2.Y} {p2.Z}"); writer.WriteLine(" endloop"); writer.WriteLine(" endfacet"); } } // 写入 STL 文件结束 writer.WriteLine("endsolid exportedModel"); } }).ConfigureAwait(false); } /// /// 通过三角形实体集合生成每个面的边框线 /// /// /// 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 GentrateLineGirdleByEntity(List entities, Color4? color = null, double thickness = 1.0) { List result = new List(); //按面分组,腰面特殊单独生成 List waistList = entities .Where(entity => entity.PlaneType == PlaneType.Girdle) .ToList(); List selFaceVector = new List(); List> lines = 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); for (int i = 0; i < selFaceVector.ToList().Count - 1; i++) { var nowItem = selFaceVector.ToList()[i]; var nextItem = selFaceVector.ToList()[i + 1]; var line = new Tuple(nowItem, nextItem); if (IsLineSegmentParallelToYAxis(line)) { lines.Add(line); result.Add(DisplayLineModel3D(new List() { nowItem, nextItem }, color ?? ViewportManager.ColorConfig.MainBorderColor, thickness)); } } } CalculateLineSegmentStats(lines, out ViewportManager.MainModelGirdleMaxLines, out ViewportManager.MainModelGirdleMinLines, out ViewportManager.MainModelGirdleAvgLines); ViewportManager.MainModelGirdleLines = lines; 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.ColorConfig.MainBorderColor, 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.ColorConfig.MainBorderColor, thickness)); // result.Add(DisplayLineModel3D( VectorClockwiseSort(ViewportManager.GirdleTopLines), color??ViewportManager.ColorConfig.MainBorderColor, thickness)); } return result; } public static List GenerateLineTextModelsByType(PlaneType type, Color4? textColor = null, bool showAll = false) { List entities = new List(); string planCode = ""; ViewportManager.ViewportTriangle.ForEach(e => { if (e.PlaneType == type && (e.PlaneCode == planCode || string.IsNullOrEmpty(planCode))) { planCode = e.PlaneCode; entities.Add(e); } }); return entities; } /// /// 选择面生成文本信息 /// /// 三角形集合 /// 指定数据集 /// public static List GenerateLineTextModels(List entities, string valKey = "") { Logger.Info("【面文本生成】开始生成面相关文本信息"); var selFacet = entities; var selFacetType = entities.First().PlaneType; var result = new List(); if (selFacetType == PlaneType.Girdle && string.IsNullOrWhiteSpace(valKey)) { Logger.Info($"【面文本生成】 命中面{selFacetType},是腰"); // if (ViewportManager.MainModelGirdleMaxLines != null || ViewportManager.MainModelGirdleMinLines != null || // ViewportManager.MainModelGirdleAvgLines != null) // { // result.Add(DisplayLineModel3D(new List>(){ViewportManager.MainModelGirdleMaxLines}, new Color4(1f, 0, 0, 1f) )); // result.Add(DisplayLineModel3D(new List>(){ViewportManager.MainModelGirdleMinLines}, new Color4(0f, 1f, 0, 1f) )); // result.Add(DisplayLineModel3D(new List>(){ViewportManager.MainModelGirdleAvgLines}, new Color4(1f, 0.5f, 0, 1f) )); // result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleMaxLines)}mm", ViewportManager.MainModelGirdleMaxLines.Item1, textColor)); // result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleMinLines)}mm", ViewportManager.MainModelGirdleMinLines.Item1, textColor)); // result.Add(DisplayText3D($"{CalculateLength(ViewportManager.MainModelGirdleAvgLines)}mm", ViewportManager.MainModelGirdleAvgLines.Item1, textColor)); // } List facetTypeAll = ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) .ToDictionary(group => group.Key, group => group.ToList()); Logger.Info($"【面文本生成】 腰由{groupedDic.Count}个面组成"); var selPlaneCode = entities.First().PlaneCode; Logger.Info($"【面文本生成】 当前选择{selPlaneCode}"); List facetPoints = new List(); entities.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); var facetIndex = -1; int.TryParse(selPlaneCode.Split("_")[1], out facetIndex); if (facetIndex == -1) { Logger.Info($"【面文本生成】 面索引解析失败{selPlaneCode}"); return result; } int linePointType = facetIndex % 4; switch (linePointType) { case 0: case 3: var longestLine1 = GetLongestOrShortestLineSegment(facetPoints, returnLongest: true); result.Add(DisplayLineModel3D(new List>() { longestLine1 }, new Color4(1f, 0, 0, 1f), 2f)); break; case 1: case 2: var longestLine2 = GetLongestOrShortestLineSegment(facetPoints, returnLongest: false); result.Add(DisplayLineModel3D(new List>() { longestLine2 }, new Color4(1f, 0, 0, 1f), 2f)); break; } //开始找值 string param = string.Empty; var valueIndex = 0; if (facetIndex % 8 == 0 || (facetIndex + 1) % 8 == 0) { //上腰面 Logger.Info($"【面文本生成】 腰面值 波峰【上腰面】"); valueIndex = (int)Math.Ceiling(facetIndex / 8.0) == 8 ? 0 : (int)Math.Ceiling(facetIndex / 8.0); param = "GIRDLE_BONE"; } else if (linePointType == 3 || linePointType == 0) { //风筝面 Logger.Info($"【面文本生成】 腰面值 波峰【风筝面】"); valueIndex = (facetIndex / 8); param = "GIRDLE_BEZEL"; } else { //腰厚比 Logger.Info($"【面文本生成】 腰面值 波峰【腰厚比】"); valueIndex = (facetIndex / 4); param = "GIRDLE_VALLEY"; } valueIndex += 1; var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; if (detail == null) { Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); return result; } Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); var paramValue = detail[$"{param}_{valueIndex}"]; if (paramValue == null) { Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} Key不存在"); return result; } Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} ==={paramValue}"); var valueFloat = (Math.Floor(float.Parse(paramValue.ToString()) * 1000) / 10).ToString(); Logger.Info($"【面文本生成】 {valueFloat} -- {valueIndex}"); var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); result.Add(DisplayText3D($"{valueFloat}", facetTextPoint)); // foreach (var kv in groupedDic) // { // var value = kv.Value; // var key = kv.Key; // var facetIndex = -1; // int.TryParse(key.Split("_")[1], out facetIndex); // List facetPoints = new List(); // value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); // // int linePointType = facetIndex % 4; // /*** // * 0 1 2 3 // * 4 5 6 7 // * 面index+1 % 4 = 0 // * 面id-loop4 = 0 - 高亮左边的竖线 - 波峰 - // * 面id-loop4 = 1 - 高亮右边的竖线 - 波谷 // * 面id-loop4 = 2 - 高亮左边的竖线 - 波谷 // * 面id-loop4 = 3 - 高亮右边的竖线 - 波峰 // */ // // } } else if (selFacetType == PlaneType.Girdle && !string.IsNullOrWhiteSpace(valKey)) { Logger.Info($"【面文本生成】 命中面{selFacetType},是腰,显示值{valKey}"); /*** * GIRDLE_BEZEL 风筝 * GIRDLE_BONE 上腰 * GIRDLE_VALLEY 波谷 */ List facetTypeAll = ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) .ToDictionary(group => group.Key, group => group.ToList()); var resultDic = new Dictionary>(); for (int i = 0; i < groupedDic.Count; i++) { switch (valKey) { case "GIRDLE_BEZEL": if (i % 8 == 4) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); break; case "GIRDLE_BONE": if (i % 8 == 0) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); break; case "GIRDLE_VALLEY": if (i % 4 == 2) resultDic.Add($"0_{i}", groupedDic[$"0_{i}"]); break; } } if (resultDic.Count == 0) { Logger.Info($"【面文本生成】 关联面获取失败"); } foreach (var dic in resultDic) { //高亮四边形左边的线,并绑定值 List facetPoints = new List(); dic.Value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); //高亮的线 var showLine = GetLeftParallelLineSegment(facetPoints); if (showLine == null) continue; //文字显示位置 var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); var resIndex = resultDic.Keys.ToList().IndexOf(dic.Key); if (resIndex == -1) continue; resIndex += 1; var detail = ViewportManager.DiamondData[$"{valKey}_DETAIL"]; if (detail == null) { Logger.Info($"【面文本生成】 {valKey}_DETAIL Key不存在"); return result; } Logger.Info($"【面文本生成】 {valKey}_DETAIL == {detail}"); var paramValue = detail[$"{valKey}_{resIndex}"]; if (paramValue == null) { Logger.Info($"【面文本生成】 {valKey}_DETAIL.{valKey}_{resIndex} Key不存在"); return result; } result.Add(DisplayLineModel3D(new List>() { showLine }, new Color4(1f, 0, 0, 1f), 2f)); var valueFloat = ValueFormat(paramValue.ToString(), valKey); result.Add(DisplayText3D($" {resIndex} \r\n {valueFloat}", facetTextPoint)); } } else { Logger.Info($"【面文本生成】 命中面{selFacetType},不是腰"); //查找同类面 List facetTypeAll = ViewportManager.ViewportTriangle.FindAll(e => e.PlaneType == selFacetType); var groupedDic = facetTypeAll.GroupBy(entity => entity.PlaneCode) .ToDictionary(group => group.Key, group => group.ToList()); Logger.Info($"【面文本生成】 同类面{groupedDic.Count}个"); var valueIndex = 1; foreach (var kv in groupedDic) { var value = kv.Value; var key = kv.Key; var facetIndex = -1; int.TryParse(key.Split("_")[1], out facetIndex); List facetPoints = new List(); value.ForEach(e => { facetPoints.Add(e.Point1); facetPoints.Add(e.Point2); facetPoints.Add(e.Point3); }); var param = string.IsNullOrWhiteSpace(valKey) ? ViewportManager.DicFacetToValueParam.ContainsKey(selFacetType) ? ViewportManager.DicFacetToValueParam[selFacetType] : null : valKey; if (param == null) { continue; } if (groupedDic.Count == 1) { var paramValue = ViewportManager.DiamondData[$"{param}"]; if (paramValue == null) { Logger.Info($"【面文本生成】 {param} Key不存在"); continue; } var valueFloat = ValueFormat(paramValue.ToString(), param); Logger.Info($"【面文本生成】 {valueFloat} -- {facetIndex}"); var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); result.Add(DisplayText3D($"{valueFloat}", facetTextPoint)); } else if (groupedDic.Count == 16) { facetIndex += 1; valueIndex = facetIndex == 16 ? 1 : facetIndex % 2 == 0 ? valueIndex + 1 : valueIndex; var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; if (detail == null) { Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); continue; } Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); var paramValue = detail[$"{param}_{valueIndex}"]; if (paramValue == null) { Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} Key不存在"); continue; } Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{valueIndex} ==={paramValue}"); var valueFloat = ValueFormat(paramValue.ToString(), param); Logger.Info($"【面文本生成】 {valueFloat} -- {valueIndex}"); var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); result.Add(DisplayText3D($" {facetIndex} \r\n {valueFloat}", new Vector3(facetTextPoint.X, facetTextPoint.Y + 0.1f, facetTextPoint.Z))); } else if (ViewportManager.DiamondData.Count > 1 && facetIndex != -1) { facetIndex += 1; var detail = ViewportManager.DiamondData[$"{param}_DETAIL"]; if (detail == null) { Logger.Info($"【面文本生成】 {param}_DETAIL Key不存在"); continue; } Logger.Info($"【面文本生成】 {param}_DETAIL == {detail}"); var paramValue = detail[$"{param}_{facetIndex}"]; if (paramValue == null) { Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{facetIndex} Key不存在"); continue; } Logger.Info($"【面文本生成】 {param}_DETAIL.{param}_{facetIndex} ==={paramValue}"); var valueFloat = ValueFormat(paramValue.ToString(), param); Logger.Info($"【面文本生成】 {valueFloat} -- {facetIndex}"); var facetTextPoint = GetOffsetCenter(facetPoints, ViewportManager.CenterVector); result.Add(DisplayText3D($" {facetIndex} \r\n {valueFloat}", facetTextPoint)); } } /*** * 思路: * 1、以面分组,1组为台面、底面,8组、16组、64组、other * 2、每组数据区别 * 1组:取第一个数据 * 8组:按索引0-7 -> 1-8 * 16组:1-2、3-4、5-6、7-8、9-10、11-12、13-14、15-0共享数据 * 64组:这组特殊理论上是腰,按照else里的东西来 * 3、这个方法是主动点击,被动触发理论上通用 * 4、主动触发:循环遍历面组,根据面类型获取算法存储的数据,在Manager里制定面-参数的一对多关系,主动触发时,取参数的首位为默认 * 5、被动触发:根据结果页传入的参数编号,找到对应的面,循环遍历面组,根据面类型获取算法存储的数据,根据被动传入参数找到值 * 6、 ①组面可能由至少两个三角形组成,计算出三角形拼接出的多边形的中心作为文字显示的中心 * ②在从算法结果数据对应好面组的索引,做成值-组面id的文本,计算出这个文本的大小 * ③在拼接的图片中心,平行于平面,向中心点外偏移“文本大小高度+0.1"个单位 显示文本 */ // 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, string valKey = "", params SelShowType[] selType) { var res = ViewportManager.ViewportTriangle.Find(e => triangleCode.Equals(e.TriangleCode)); if (res != null) return GentrateChosenView(res, valKey, selType); return new List(); } /// /// 生成选择的视图(只生成,不添加!!) /// /// 选中的实体 /// 生成范围 /// /// public static List GentrateChosenView(Viewport3DTriangleEntity entity, string valKey = "", 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.ColorConfig.SelFacetColor)); break; case SelShowType.Border: //选中面边框的高亮 result.AddRange(GentrateLineByEntity(selPanel, ViewportManager.ColorConfig.SelBorderColor)); break; case SelShowType.IsTypePanel: //选中面的同类面高亮 result.Add(GenerateModelByEntity(selPanelType, ViewportManager.ColorConfig.SelTypeColor)); break; case SelShowType.LengthText: //选中面 每条边长度标记 // if(PlaneType.Girdle == entity.PlaneType)break; result.AddRange(GenerateLineTextModels(selPanel, valKey)); 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 = 7, int to = 360) { ViewportManager.ClearDicModels(); // 设置旋转的中心点和旋转轴 var rotateTransform = new RotateTransform3D(); var rotation = new AxisAngleRotation3D(axis, 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); double currentAngle = rotation.Angle; // 创建旋转动画 var rotateAnimation = new DoubleAnimation { From = currentAngle, To = to + currentAngle, 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.ColorConfig.SelFontColor, Scale = 0.8f, VerticalAlignment = BillboardVerticalAlignment.Center, HorizontalAlignment = BillboardHorizontalAlignment.Center }); 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.ColorConfig.SelFontColor, 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.OrthographicCamera 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.OrthographicCamera { Position = cameraPosition.ToPoint3D(), LookDirection = lookDirection.ToVector3D(), UpDirection = new Vector3D(0, 1, 0), // 默认全局 Y 轴为上方向 NearPlaneDistance = 0.1f, FarPlaneDistance=1000 }; } /// /// 绘制箭头(相机指向模型中心) /// /// 相机位置 /// 中心位置 /// 模型 /// 箭头总长度 D:5 /// 圆柱部分占比 0.7 /// 箭头直径 1 /// 圆锥直径与圆柱直径的比例 1.5 /// 模型边界范围 1 /// public static MeshGeometryModel3D CreateArrow( Vector3 cameraPosition, Vector3 modelCenter, BoundingBox modelBounds, float totalLength = 1.5f, // 箭头总长度 float cylinderRatio = 0.7f,// 圆柱部分占比 float diameter = 0.25f, // 箭头直径 float headDiameterRatio = 2f, // 圆锥直径与圆柱直径的比例 float padding = 1.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)); } private static Dictionary moveLines = new Dictionary(); /// /// 网状参考线 /// /// /// /// public static List ShowMeshLines(List entities, bool isCrown = true, double thickness = 1.0) { if (isCrown) { moveLines = new(); } List lines = new(); var Y = -0.01F; if (!isCrown) { Y = ViewportManager.ModelBounds.Maximum.Y + 0.01f; } var center = ViewportManager.CenterVector; center.Y = Y; Viewport3DTriangleEntity firstPoint = entities.Where(x => x.PlaneType == PlaneType.TableFacet).FirstOrDefault(); //var b = ((center.Z * firstPoint.Point1.X) - (firstPoint.Point1.Z * center.X)) / (firstPoint.Point1.X - center.X); //var a = (firstPoint.Point1.Z - b) / firstPoint.Point1.X; var lineCal = new LineCalculationHelper(firstPoint.Point1, center); var edgeLines = new List>(); Color4 XlineColor =new Color4(80f, 0f, 0f, 1f); float r =5; if(double.TryParse(ViewportManager.DiamondData["M2"].ToString(), out var v)) { r = (float)(v*0.51); } //var x1 = r; //var z1 = lineCal.calZ(x1); //var x2 = -r; //var z2 = lineCal.calZ(x2); //edgeLines.Add(new Tuple(new Vector3(x1, Y, z1), new Vector3(x2, Y, z2))); //lines.Add(DisplayLineModel3D(edgeLines, XlineColor)); edgeLines = new List>(); var XLine = lineCal.calXline(r); edgeLines.Add(XLine); lines.Add(DisplayLineModel3D(edgeLines, XlineColor)); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(XLine, 2)); var lineA = DisplayLineModel3D(edgeLines, XlineColor); lines.Add(lineA); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(XLine, -2)); var lineB = DisplayLineModel3D(edgeLines, XlineColor); lines.Add(lineB); bindingMoveLine(lineA, lineB); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(XLine, 4)); var lineE = DisplayLineModel3D(edgeLines, XlineColor); lines.Add(lineE); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(XLine, -4)); var lineF = DisplayLineModel3D(edgeLines, XlineColor); lines.Add(lineF); bindingMoveLine(lineE, lineF); Color4 YlineColor = new Color4(0F, 80f, 0f, 1f); //var x3 = r; //var z3 = lineCal.calZVertical(x3); //var x4 = -r; //var z4 = lineCal.calZVertical(x4); //edgeLines = new List>(); //edgeLines.Add(new Tuple(new Vector3(x3, Y, z3), new Vector3(x4, Y, z4))); //lines.Add(DisplayLineModel3D(edgeLines, YlineColor)); edgeLines = new List>(); var YLine = lineCal.calYline(r); edgeLines.Add(YLine); lines.Add(DisplayLineModel3D(edgeLines, YlineColor)); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(YLine, 2)); var lineC = DisplayLineModel3D(edgeLines, YlineColor); lines.Add(lineC); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(YLine, -2)); var lineD = DisplayLineModel3D(edgeLines, YlineColor); lines.Add(lineD); bindingMoveLine(lineC, lineD); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(YLine, 4)); var lineG = DisplayLineModel3D(edgeLines, YlineColor); lines.Add(lineG); edgeLines = new List>(); edgeLines.Add(lineCal.calLineOfOffset(YLine, -4)); var lineH = DisplayLineModel3D(edgeLines, YlineColor); lines.Add(lineH); bindingMoveLine(lineG, lineH); return lines; } /// /// 网状参考线 对向移动绑定 /// /// /// private static void bindingMoveLine(LineGeometryModel3D lineA, LineGeometryModel3D lineB) { lineA.MouseDown3D += LineA_MouseDown3D; lineA.MouseUp3D += LineA_MouseUp3D; lineA.MouseMove3D += LineA_MouseMove3D; moveLines.Add(lineA.GUID, lineB.GUID); lineB.MouseDown3D += LineA_MouseDown3D; lineB.MouseUp3D += LineA_MouseUp3D; lineB.MouseMove3D += LineA_MouseMove3D; moveLines.Add(lineB.GUID, lineA.GUID); } private static bool isDrawing = false; private static Point3D previousMousePosition; private static void LineA_MouseMove3D(object sender, RoutedEventArgs e) { if (isDrawing) { if (e is HelixToolkit.Wpf.SharpDX.MouseMove3DEventArgs ev) { var line = sender as LineGeometryModel3D; // 直线方向分量 var lineDirection = line.Geometry.Positions[1] - line.Geometry.Positions[0]; lineDirection.Normalize(); // 现在鼠标对于3d模型的位置 var mousePosition = ev.Position; var currentMousePosition = Get3DPointFromMouse(mousePosition); // 计算鼠标移动的方向 var moveDirection = previousMousePosition - currentMousePosition; // 计算垂直于线的移动分量 var perpendicularDirection = Vector3D.CrossProduct(lineDirection.ToVector3D(), Vector3D.CrossProduct(moveDirection, lineDirection.ToVector3D())); perpendicularDirection.Normalize(); double scaleFactor = 0.05; // 缩放因子 // 计算垂直距离 double perpendicularDistance = Vector3D.DotProduct(moveDirection, perpendicularDirection) * scaleFactor; System.Console.WriteLine("距离:"+ perpendicularDistance); // 垂直方向偏移量 Vector3D projectedVector = perpendicularDistance * perpendicularDirection; projectedVector.Y = 0; var transform = new TranslateTransform3D(projectedVector); line.Transform = transform; // 更新上一帧的鼠标位置 //previousMousePosition = currentMousePosition; var otherLineGuid = moveLines[line.GUID]; var Viewport = ViewportManager.GetViewport3D(); var otherLine = Viewport.Items.Where(x=>x.GUID == otherLineGuid).FirstOrDefault(); if (otherLine!= null ) { Vector3D projectedVector_other = -perpendicularDistance * perpendicularDirection; projectedVector_other.Y = 0; var transform_other = new TranslateTransform3D(projectedVector_other); otherLine.Transform = transform_other; } } } } private static void LineA_MouseUp3D(object sender, RoutedEventArgs e) { isDrawing = false; var Viewport = ViewportManager.GetViewport3D(); Viewport.Cursor = Cursors.Arrow; } private static void LineA_MouseDown3D(object sender, RoutedEventArgs e) { isDrawing = true; if(e is HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs ev){ var Viewport = ViewportManager.GetViewport3D(); Viewport.Cursor = Cursors.SizeAll; // 获取鼠标点击的位置 var mousePosition = ev.Position; previousMousePosition = Get3DPointFromMouse(mousePosition); } } private static Point3D Get3DPointFromMouse(Point mousePosition) { // 使用 HelixToolkit 的 Ray3D 和 Plane3D var Viewport = ViewportManager.GetViewport3D(); var camera = Viewport.Camera as OrthographicCamera; // 创建射线 var ray = new Ray3D( camera.Position, new Vector3D(mousePosition.X - Viewport.ActualWidth / 2, -camera.Position.Y, -(mousePosition.Y - Viewport.ActualHeight / 2) ) ); // 创建平面(假设平面为 Y=0) var plane = new Plane3D(new Point3D(0, 0, 0), new Vector3D(0, 1, 0)); // 计算射线与平面的交点 var intersection = GetIntersection(ray, plane); return intersection ?? new Point3D(0, 0, 0); } private static Point3D? GetIntersection(Ray3D ray, Plane3D plane) { // 将射线转换为直线(起点和终点) Point3D rayEnd = ray.Origin + ray.Direction * 1000; // 延长射线 // 使用 Plane3D.LineIntersection 方法计算交点 return plane.LineIntersection(ray.Origin, rayEnd); } private static double initialRadius = 1.0; // 初始半径 private static double endRadius = 1.0; // 拖拽后半径 private static double initDistance = 1.0; // 初始与圆心之间距离 private static double endDistance = 1.0; // 拖拽后与圆心之间距离 private static Point dragStartPoint; /// /// 圆圈参考线 /// /// /// /// public static LineGeometryModel3D ShowCircleLine(bool isCrown = true, double radius = 1.0, double thickness = 1.0) { var Y = -0.01F; if (!isCrown) { Y = ViewportManager.ModelBounds.Maximum.Y + 0.01f; } var center = ViewportManager.CenterVector; center.Y = Y; return UpdateCircleGeometry(center, initialRadius); } private static LineGeometryModel3D UpdateCircleGeometry(Vector3 center, double radius = 1.0) { // 生成圆形线的点 int segments = 100; // 圆的细分段数 var positions = new List(); var indices = new List(); for (int i = 0; i < segments; i++) { float angle = (float)(2 * Math.PI * i / segments); float x = (float)(center.X + radius * Math.Cos(angle)); float y = (float)(center.Y); float z = (float)(center.Z + radius * Math.Sin(angle)); positions.Add(new Vector3(x, y, z)); indices.Add(i); indices.Add((i + 1) % segments); } var Circle = DisplayLineModel3D(positions, Color4.Black); Circle.MouseDown3D += Circle_MouseDown3D; Circle.MouseUp3D += Circle_MouseUp3D; Circle.MouseMove3D += Circle_MouseMove3D; // 更新圆形线的几何形状 return Circle; } private static void Circle_MouseMove3D(object sender, RoutedEventArgs e) { if (isDrawing) { if (e is HelixToolkit.Wpf.SharpDX.MouseMove3DEventArgs ev) { var line = sender as LineGeometryModel3D; if (line==null) { return; } var MinRadius = 0.1; var MaxRadius = (ViewportManager.ModelBounds.Maximum.X - ViewportManager.ModelBounds.Minimum.X) * 0.6; // 现在鼠标对于3d模型的位置 var mousePosition = ev.Position; var Y = -0.01F; if (!IsCrown()) { Y = ViewportManager.ModelBounds.Maximum.Y + 0.01F; } var center = ViewportManager.CenterVector; center.Y = Y; Point3D mouseWorldPosition = Get3DPointFromMouse(mousePosition); // 计算鼠标与圆心的距离 double distance = CalculateDistance(mouseWorldPosition, center.ToPoint3D()); double scaleFactor = 0; if (distance < initDistance) { // 鼠标靠近圆心,缩小半径 scaleFactor = -0.01; } else { // 鼠标远离圆心,扩大半径 scaleFactor = +0.01; } // 计算鼠标移动的距离(以屏幕坐标为单位) double delta = (dragStartPoint - mousePosition).Length; // 计算新的半径 double newRadius = initialRadius + delta * scaleFactor; // 缩放因子 // 限制半径范围 newRadius = Math.Clamp(newRadius, MinRadius, MaxRadius); var scaleTransform = new ScaleTransform3D(newRadius, 1, newRadius); // 缩放变换 line.Transform = scaleTransform; endRadius = newRadius; endDistance = distance; } } } // 计算两点之间的距离 private static double CalculateDistance(Point3D p1, Point3D p2) { double dx = p2.X - p1.X; double dy = p2.Y - p1.Y; double dz = p2.Z - p1.Z; return Math.Sqrt(dx * dx + dy * dy + dz * dz); } private static void Circle_MouseUp3D(object sender, RoutedEventArgs e) { isDrawing = false; var Viewport = ViewportManager.GetViewport3D(); Viewport.Cursor = Cursors.Arrow; initialRadius = endRadius; initDistance = endDistance; // 释放鼠标捕获 Viewport.ReleaseMouseCapture(); } private static void Circle_MouseDown3D(object sender, RoutedEventArgs e) { isDrawing = true; if (e is HelixToolkit.Wpf.SharpDX.MouseDown3DEventArgs ev) { var line = sender as LineGeometryModel3D; // 获取鼠标点击的位置 dragStartPoint = ev.Position; Point3D mouseWorldPosition = Get3DPointFromMouse(dragStartPoint); var Y = -0.01F; if(!IsCrown()) { Y = ViewportManager.ModelBounds.Maximum.Y + 0.01F; } var center = ViewportManager.CenterVector; center.Y = Y; initDistance = CalculateDistance(mouseWorldPosition, center.ToPoint3D()); var Viewport = ViewportManager.GetViewport3D(); Viewport.Cursor = Cursors.SizeAll; // 捕获鼠标 Viewport.CaptureMouse(); } } public static bool IsCrown() { bool isCrown = false; var camera = ViewportManager.GetViewport3D().Camera; if (camera.LookDirection.Y >= 0 && camera.UpDirection.Y <= 0) { isCrown = true; } return isCrown; } #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); } public static Vector3 GetOffsetCenter(List facetVector, Vector3 center, float dev = 0.4f) { // 1. 去重 facetVector var distinctFacetVector = facetVector.Distinct().ToList(); // 2. 计算多边形的几何中心 Vector3 polygonCenter = GetCentroid(distinctFacetVector); // 3. 计算从模型中心点到几何中心的向量 Vector3 direction = polygonCenter - center; // 4. 归一化方向向量并根据偏移量 dev 计算最终的偏移位置 Vector3 offsetCenter = polygonCenter + Vector3.Normalize(direction) * dev; return offsetCenter; } /// /// 计算夹角度数 /// /// /// /// 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); } private static bool IsLineSegmentParallelToYAxis(Tuple lineSegment,float epsilon = 1e-6f) { Vector3 nowItem = lineSegment.Item1; Vector3 nextItem = lineSegment.Item2; if (Math.Abs(nowItem.X - nextItem.X) < epsilon && Math.Abs(nowItem.Z - nextItem.Z) < epsilon) { return true; // 平行于 Y 轴 } return false; // 不平行于 Y 轴 } // 计算两个点之间的距离 private static float GetDistance(Vector3 point1, Vector3 point2) { return (float)Math.Sqrt(Math.Pow(point2.X - point1.X, 2) + Math.Pow(point2.Y - point1.Y, 2) + Math.Pow(point2.Z - point1.Z, 2)); } // 判断是否平行于Y轴的线段,返回较长或者较短的线段 private static Tuple GetLongestOrShortestLineSegment(List facetPoints, bool returnLongest = true) { Tuple resultSegment = null; float resultLength = returnLongest ? float.MinValue : float.MaxValue; // 初始化为最小或最大长度 // 遍历所有相邻的点,构成线段 for (int i = 0; i < facetPoints.Count; i++) { Vector3 currentPoint = facetPoints[i]; Vector3 nextPoint = facetPoints[(i + 1) % facetPoints.Count]; // 用模运算实现环形结构,最后一个点与第一个点连接 // 计算线段是否平行于 Y 轴 if (IsLineSegmentParallelToYAxis(new Tuple(currentPoint, nextPoint))) { // 计算线段的长度 float segmentLength = GetDistance(currentPoint, nextPoint); // 根据需要选择较长或较短的线段 if ((returnLongest && segmentLength > resultLength) || (!returnLongest && segmentLength < resultLength)) { resultSegment = new Tuple(currentPoint, nextPoint); resultLength = segmentLength; } } } return resultSegment; // 返回符合条件的线段 } private static void CalculateLineSegmentStats(List> lines, out Tuple maxLine, out Tuple minLine, out Tuple avgLine) { // 计算所有线段的长度 var lineLengths = lines.Select(line => new { Line = line, Length = CalculateLength(line) }).ToList(); // 找到最大、最小和平均长度对应的线段 maxLine = lineLengths.OrderByDescending(l => l.Length).First().Line; minLine = lineLengths.OrderBy(l => l.Length).First().Line; avgLine = lineLengths.OrderBy(l => Math.Abs(l.Length - lineLengths.Average(ll => ll.Length))) .First().Line; } private static double CalculateLength(Tuple line) { Vector3 startPoint = line.Item1; Vector3 endPoint = line.Item2; return Math.Round(Vector3.Distance(startPoint, endPoint), 2); } /// /// 获取四边形内平行于Y轴且位于左侧的边线 /// /// 四边形的顶点列表 /// 是否返回最长的边线 /// 符合条件的边线,如果不存在则返回null public static Tuple GetLeftParallelLineSegment( List facetPoints) { if (facetPoints == null || facetPoints.Count < 4) throw new ArgumentException("facetPoints must contain at least four points."); // 计算多边形(四边形)的中心点 Vector3 center = GetCenter(facetPoints); float minX = float.MaxValue; Tuple resultLine = null; // 遍历所有边线 for (int i = 0; i < facetPoints.Count; i++) { Vector3 p1 = facetPoints[i]; Vector3 p2 = facetPoints[(i + 1) % facetPoints.Count]; // 环形连接 var lineSegment = new Tuple(p1, p2); // 判断该边线是否平行于 Y 轴 if (IsLineSegmentParallelToYAxis(lineSegment)) { return lineSegment; } } return resultLine; } private static string ValueFormat(string value, string type, bool hasUnit = false) { if (double.TryParse(value, out var v) ) { switch (type) { case "GIRDLE_BEZEL": case "GIRDLE_BONE": case "GIRDLE_VALLEY": v = Math.Floor(v * 1000) / 1000; return hasUnit ? $"{(v*100).ToString("F1")}mm" : (v*100).ToString("F1"); case "DIAMETER": v = Math.Floor(v * 10) / 10; return hasUnit ? $"{v.ToString("F1")}mm" : v.ToString("F1"); case "PAV_ANGLE": case "TWIST": v = Math.Floor(v * 10) / 10; return hasUnit ? $"{v.ToString("F1")}°" : v.ToString("F1"); case "CROWN_ANGLE": v = Math.Floor(v * 10) / 10; return hasUnit ? $"{v.ToString("F1")}°" :v.ToString("F1"); case "TABLE": // case "CROWN HEIGHT": // case "PAV DEPTH": v *= 100; return hasUnit ? $"{(Math.Round(v / 0.5) * 0.5).ToString("F1")}%" : (Math.Round(v / 0.5) * 0.5).ToString("F1"); default: v = Math.Floor(v * 1000) / 1000; return hasUnit ? $"{(v*100).ToString("F1")}%" : (v*100).ToString("F1"); } } return "--"; } #endregion }