diff --git a/App.xaml b/App.xaml index 0fc0efc..0efa2be 100644 --- a/App.xaml +++ b/App.xaml @@ -17,6 +17,7 @@ + diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 462342a..674520d 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -10,6 +10,7 @@ using log4net.Config; using SparkClient.Model.Helper; using SparkClient.ViewModel.BaseWindow; using SparkClient.Views.BaseWindow; +using Window = System.Windows.Window; namespace SparkClient; @@ -23,7 +24,6 @@ public partial class MainWindow public MainWindow() { InitializeComponent(); - Logger.Info("Application started"); } diff --git a/Model/Entity/ApiEntity/AlgorithmResultEntity.cs b/Model/Entity/ApiEntity/AlgorithmResultEntity.cs index b75b266..f399017 100644 --- a/Model/Entity/ApiEntity/AlgorithmResultEntity.cs +++ b/Model/Entity/ApiEntity/AlgorithmResultEntity.cs @@ -84,5 +84,7 @@ namespace SparkClient.Model.Entity.ApiEntity public string CrownType { get; set; } public string PavType { get; set; } public string DiamondCode { get; set; } + public string error_msg { get; set; } + public string status { get; set; } } } diff --git a/Model/Entity/Viewport3DTriangleEntity.cs b/Model/Entity/Viewport3DTriangleEntity.cs deleted file mode 100644 index d24c1c4..0000000 --- a/Model/Entity/Viewport3DTriangleEntity.cs +++ /dev/null @@ -1,129 +0,0 @@ -using System.Security.Cryptography; -using System.Text; -using SharpDX; -using SparkClient.Model.Enums; - -namespace SparkClient.Model.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; } - - public static Viewport3DTriangleEntity CreateByString(string str) - { - if (string.IsNullOrWhiteSpace(str)) - { - throw new ArgumentException("Input string cannot be null or empty"); - } - - // 分割为四段 - var parts = str.Split('|'); - if (parts.Length != 4) - { - throw new ArgumentException("Input string format is incorrect. Expected 4 parts separated by '|'."); - } - - // 解析第一段:坐标 - var coordinates = parts[0].Split(';'); - if (coordinates.Length != 3) - { - throw new ArgumentException("Coordinates part must contain exactly 3 points separated by ';'."); - } - - var points = coordinates.Select(ParseVector3).ToArray(); - - // 解析第二段:TriangleCode [内部标记] - - // 解析第三段:PlaneCode - string planeCode = parts[2]; - if (string.IsNullOrWhiteSpace(planeCode)) - { - throw new ArgumentException("PlaneCode cannot be null or empty"); - } - - // 解析第四段:PlaneType - if (!int.TryParse(parts[3], out int planeTypeValue) || !Enum.IsDefined(typeof(PlaneType), planeTypeValue)) - { - throw new ArgumentException("Invalid PlaneType value"); - } - PlaneType planeType = (PlaneType)planeTypeValue; - - // 生成 TriangleCode 格式:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 生成 MD5 - string concatenatedPoints = string.Join(";", points.Select(p => $"{p.X},{p.Y},{p.Z}")); - string generatedTriangleCode = GenerateMD5Hash(concatenatedPoints); - - return new Viewport3DTriangleEntity - { - Point1 = points[0], - Point2 = points[1], - Point3 = points[2], - TriangleCode = generatedTriangleCode, - PlaneCode = planeCode, - PlaneType = planeType - }; - } - - // public static Viewport3DTriangleEntity CreateByJsonStr(string str) - // { - // - // } - - private static Vector3 ParseVector3(string coordinate) - { - var values = coordinate.Split(','); - if (values.Length != 3) - { - throw new ArgumentException("Each coordinate must contain exactly 3 values separated by ','"); - } - - if (!float.TryParse(values[0], out float x) || - !float.TryParse(values[1], out float y) || - !float.TryParse(values[2], out float z)) - { - throw new ArgumentException("Coordinate values must be valid floating-point numbers"); - } - - return new Vector3(x, y, z); - } - - public static string GenerateMD5Hash(string input) - { - using (var md5 = MD5.Create()) - { - var inputBytes = Encoding.UTF8.GetBytes(input); - var hashBytes = md5.ComputeHash(inputBytes); - - // 转换为大写的16位十六进制字符串 - return string.Concat(hashBytes.Take(8).Select(b => b.ToString("X2"))); - } - } -} \ No newline at end of file diff --git a/Model/Helper/Viewport3DHelper.cs b/Model/Helper/Viewport3DHelper.cs deleted file mode 100644 index 65399e7..0000000 --- a/Model/Helper/Viewport3DHelper.cs +++ /dev/null @@ -1,825 +0,0 @@ -using System.IO; -using System.Windows.Media; -using System.Windows.Media.Media3D; -using SharpDX.Direct3D11; -using SparkClient.Model.Entity; - -namespace SparkClient.Model.Helper; - -using HelixToolkit.Wpf.SharpDX; -using SharpDX; -using System.Collections.Generic; - -public class Viewport3DHelper -{ - // 加载环境贴图 - // public static Stream stream = - // File.Open("D:\\WorkSpace\\spark\\HelixToolkitSharpDX-Demo\\morning_racing_circuit_16k.dds", FileMode.Open); - - /// - /// 生成一个多边形钻石 - /// - /// 控件 - /// 边数 - public static void GenerateDiamond(Viewport3DX viewport, int sides) - { - // 启用法线生成 - var meshBuilder = new MeshBuilder(true, false); - - //设置抗锯齿 - viewport.RenderHost.MSAA = MSAALevel.Maximum; - - - double radius = 3.2915; - double smallRadius = radius / 2; - double topHeight = 1.67; - double bottomHeight = 4.43; - double smallOffset = 1.25; - - var largeVertices = new List(); - var smallVertices = new List(); - - var edgeLines = new List>(); - - // 生成大八边形顶点 - for (int i = 0; i < sides; i++) - { - double angle = i * Math.PI / (sides / 2); - largeVertices.Add(new Vector3((float)(radius * Math.Cos(angle)), (float)topHeight, - (float)(radius * Math.Sin(angle)))); - } - - // 生成小八边形顶点 - for (int i = 0; i < sides; i++) - { - double angle = i * Math.PI / (sides / 2); - smallVertices.Add(new Vector3((float)(smallRadius * Math.Cos(angle)), (float)(topHeight + smallOffset), - (float)(smallRadius * Math.Sin(angle)))); - } - - var bottomPoint = new Vector3(0, (float)(topHeight - bottomHeight), 0); - var topPoint = new Vector3(0, (float)topHeight, 0); - - // 生成底部面 - for (int i = 0; i < sides; i++) - { - int nextIndex = (i + 1) % sides; - - // 计算法线 - // Vector3 edge1 = largeVertices[nextIndex] - bottomPoint; - // Vector3 edge2 = largeVertices[i] - bottomPoint; - // Vector3 normal = Vector3.Cross(edge1, edge2); - // normal.Normalize(); - - - List fanPositions2 = new List() - { bottomPoint, largeVertices[nextIndex], largeVertices[i] }; - List fanNormals2 = new List() - { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; - meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); - // meshBuilder.AddTriangle(bottomPoint, largeVertices[nextIndex], largeVertices[i]); - // meshBuilder.AddTriangle(topPoint, largeVertices[i], largeVertices[nextIndex]); - - - // 添加边框线 - edgeLines.Add(new Tuple(bottomPoint, largeVertices[nextIndex])); - edgeLines.Add(new Tuple(largeVertices[nextIndex], largeVertices[i])); - edgeLines.Add(new Tuple(largeVertices[i], bottomPoint)); - } - - // 生成法线并添加小八边形的面 - var smallNormals = new List(); - for (int i = 0; i < smallVertices.Count; i++) - { - smallNormals.Add(new Vector3(0, 1, 0)); // 小八边形法线向上 - } - - meshBuilder.AddTriangleFan(smallVertices, smallNormals); - - // 生成大八边形和小八边形的连接面 - for (int i = 0; i < sides; i++) - { - int nextIndex = (i + 1) % sides; - - - // 计算法线 - // Vector3 edge1 = largeVertices[nextIndex] - largeVertices[i]; - // Vector3 edge2 = smallVertices[nextIndex] - largeVertices[i]; - // Vector3 normal1 = Vector3.Cross(edge1, edge2); - // normal1.Normalize(); - // - // Vector3 edge3 = smallVertices[nextIndex] - largeVertices[i]; - // Vector3 edge4 = smallVertices[i] - largeVertices[i]; - // Vector3 normal2 = Vector3.Cross(edge3, edge4); - // normal2.Normalize(); - - - List fanPositions1 = new List() - { largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex] }; - List fanNormals1 = new List() - { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; - meshBuilder.AddTriangleFan(fanPositions1, fanNormals1); - - //meshBuilder.AddTriangle(largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex]); - - List fanPositions2 = new List() - { largeVertices[i], smallVertices[nextIndex], smallVertices[i] }; - List fanNormals2 = new List() - { new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0) }; - meshBuilder.AddTriangleFan(fanPositions2, fanNormals2); - //meshBuilder.AddTriangle(largeVertices[i], smallVertices[nextIndex], smallVertices[i]); - - // 添加边框线 - edgeLines.Add(new Tuple(largeVertices[i], largeVertices[nextIndex])); - edgeLines.Add(new Tuple(largeVertices[nextIndex], smallVertices[nextIndex])); - edgeLines.Add(new Tuple(smallVertices[nextIndex], smallVertices[i])); - edgeLines.Add(new Tuple(smallVertices[i], largeVertices[i])); - } - - var mesh = meshBuilder.ToMeshGeometry3D(); - - - var material = new PBRMaterial - { - AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), // 纯白色 - MetallicFactor = 0.0, - RoughnessFactor = 0.01, - ReflectanceFactor = 1.0, - ClearCoatStrength = 1.0, - ClearCoatRoughness = 0.0, - // RenderEnvironmentMap = true, - // IrradianceMap = TextureModel.Create(stream), // 环境贴图 - }; - - // 设置材质的采样器状态 - material.SurfaceMapSampler = new SamplerStateDescription - { - Filter = Filter.MinMagMipLinear, - AddressU = TextureAddressMode.Wrap, - AddressV = TextureAddressMode.Wrap, - AddressW = TextureAddressMode.Wrap - }; - - //边框 - var lineBuilder = new LineBuilder(); - foreach (var line in edgeLines) - { - lineBuilder.AddLine(line.Item1, line.Item2); - } - - var edgeLinesModel = new LineGeometryModel3D - { - Geometry = lineBuilder.ToLineGeometry3D(), - Color = System.Windows.Media.Colors.Black, - Thickness = 1.0 - }; - - viewport.Items.Add(edgeLinesModel); - - var geometryModel = new MeshGeometryModel3D - { - Geometry = mesh, - Material = material - }; - - viewport.Items.Add(geometryModel); - - viewport.ZoomExtents(); - } - - public static void GenerateModelByEntity(Viewport3DX viewport, List entities) - { - var meshBuilder = new MeshBuilder(true, false); - viewport.RenderHost.MSAA = MSAALevel.Maximum; - foreach (var entity in entities) - { - List vertices = new List(){entity.Point1, entity.Point2, entity.Point3}; - meshBuilder.AddPolygon(vertices); - } - - var mesh = meshBuilder.ToMeshGeometry3D(); - var material = new PBRMaterial - { - AlbedoColor = new Color4(0.11f, 0.56f, 1f, 0.6f), - MetallicFactor = 0.0, - RoughnessFactor = 0.01, - ReflectanceFactor = 1.0, - ClearCoatStrength = 1.0, - ClearCoatRoughness = 0.0, - }; - - // 设置材质的采样器状态 - material.SurfaceMapSampler = new SamplerStateDescription - { - Filter = Filter.MinMagMipLinear, - AddressU = TextureAddressMode.Wrap, - AddressV = TextureAddressMode.Wrap, - AddressW = TextureAddressMode.Wrap - }; - var geometryModel = new MeshGeometryModel3D - { - Geometry = mesh, - Material = material - }; - viewport.Items.Add(geometryModel); - } - - /// - /// 传入一个三角形,找到关联面 - /// - /// 控件 - /// 三角形顶点 - /// 选择主体颜色 - /// 选择关联颜色(null不渲染) - /// 选择边框颜色(null不渲染) - /// - public static List HighlightAssociatedFaces(Viewport3DX viewport, - Tuple triangleIndex, Color4 selColor, Color4? linkColor = null, - Color4? borderColor = null) - { - List result = new List(); - //选中平面上的点(实现选中高亮、边框线) - List selFaceVector = new List(); - selFaceVector.Add(triangleIndex.Item1); - selFaceVector.Add(triangleIndex.Item2); - selFaceVector.Add(triangleIndex.Item3); - - foreach (var item in viewport.Items) - { - if (item is MeshGeometryModel3D geometryModel) - { - var geometry = geometryModel.Geometry; - if (geometry != null) - { - var positions = geometry.Positions; // 顶点列表 - var indices = geometry.Indices; // 索引列表 - - // 确保 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 vertex0 = positions[index0]; - var vertex1 = positions[index1]; - var vertex2 = positions[index2]; - - List temp = new List() { vertex0, vertex1, vertex2 }; - - if (ArePointsOnSamePlane(triangleIndex, temp)) - { - //所有点共面,两种情况,三角形自己,三角形相连在同一个平面 - if (temp.Count == 3 && temp.Contains(triangleIndex.Item1) && - temp.Contains(triangleIndex.Item2) && temp.Contains(triangleIndex.Item3)) - continue; - foreach (var vector in temp) - { - // if(!selFaceVector.Contains(vector)) - selFaceVector.Add(vector); - } - } - } - } - } - } - - - //当前选中的平面 - var meshBuilder = new MeshBuilder(true, false); - List fanNormals2 = new List(); - foreach (var vertex in selFaceVector) - fanNormals2.Add(new Vector3(0, 1, 0)); - meshBuilder.AddTriangleFan(selFaceVector, fanNormals2); - var mesh = meshBuilder.ToMeshGeometry3D(); - - var material = new PBRMaterial - { - AlbedoColor = selColor, // 纯白色 - MetallicFactor = 0, - RoughnessFactor = 1, - ReflectanceFactor = 0.5, - }; - var geometryModel1 = new MeshGeometryModel3D - { - Geometry = mesh, - Material = material - }; - - //当前选中平面边框线 - var edgeLines = new List>(); - var lengths = new List(); // 长度列表 - var angles = new List(); // 夹角列表 - HashSet uniqueVectors = new HashSet(selFaceVector); - List sortedVectors = uniqueVectors.ToList(); - Vector3 center = GetCentroid(sortedVectors); - // 按极角顺时针排序 - sortedVectors.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); // 按角度从小到大排序 - }); - - for (int i = 0; i < sortedVectors.Count - 1; i++) - { - var nowItem = sortedVectors[i]; - var nextItem = sortedVectors[i + 1]; - edgeLines.Add(new Tuple(nowItem, nextItem)); - // 计算当前线段的长度 - lengths.Add((nextItem - nowItem).Length()); - } - - edgeLines.Add(new Tuple(sortedVectors.Last(), sortedVectors.First())); - lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); - //计算夹角 - 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 = p2 - p1; - var v2 = p3 - p2; - double angle = AngleBetween(v1, v2); - angles.Add(angle); // 夹角,单位是度 - } - - if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) - { - for (int i = 0; i < sortedVectors.Count; i++) - { - var point = sortedVectors[i]; - var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; - var lengthText = $"L: {lengths[i]:F2}"; - var angleText = $"Angle: {angles[i]:F2}°"; - - var lengthTextPoint = (pointNext + point) / 2; - - // 在合适位置显示文本 - var text3DL = DisplayText3D(lengthText, - new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); - var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); - viewport.Items.Add(text3DL); - viewport.Items.Add(text3DA); - result.Add(text3DL); - result.Add(text3DA); - } - } - else - { - // 如果列表长度不一致,给出提示信息 - Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); - } - - var lineBuilder = new LineBuilder(); - foreach (var line in edgeLines) - { - lineBuilder.AddLine(line.Item1, line.Item2); - } - - var edgeLinesModel = new LineGeometryModel3D - { - Geometry = lineBuilder.ToLineGeometry3D(), - Color = System.Windows.Media.Colors.Red, - Thickness = 1.0 - }; - - viewport.Items.Add(edgeLinesModel); - viewport.Items.Add(geometryModel1); - - - result.Add(geometryModel1); - result.Add(edgeLinesModel); - return result; - } - - /// - /// 遍历视图中的所有三角形 - /// - /// - /// - public static List> ForeachViewPortTriangle(Viewport3DX viewport) - { - List> result = new List>(); - foreach (var item in viewport.Items) - { - if (item is MeshGeometryModel3D geometryModel) - { - var geometry = geometryModel.Geometry; - if (geometry != null) - { - var positions = geometry.Positions; // 顶点列表 - var indices = geometry.Indices; // 索引列表 - - // 确保 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 vertex0 = positions[index0]; - var vertex1 = positions[index1]; - var vertex2 = positions[index2]; - result.Add(new Tuple(vertex0, vertex1, vertex2)); - } - } - } - } - - return result; - } - - private static BillboardTextModel3D DisplayText3D(string text, Vector3 position) - { - var billboardTextModel = new BillboardTextModel3D(); - var billboardText = new BillboardText3D(); - billboardText.TextInfo.Add(new TextInfo(text, position) - { - Foreground = Color.Green, - Scale = 0.5f, - }); - billboardTextModel.Geometry = billboardText; - return billboardTextModel; - } - - public 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 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 bool ArePointsOnSamePlane(Tuple triangle, List points) - { - // 获取三角形的三个点 - var P1 = triangle.Item1; - var P2 = triangle.Item2; - var P3 = triangle.Item3; - - // 计算平面的法线 - Vector3 V1 = P2 - P1; // 向量P1P2 - Vector3 V2 = P3 - P1; // 向量P1P3 - Vector3 normal = Vector3.Cross(V1, V2); // 叉积得到平面法线 - - // 对每个点进行检查 - foreach (var point in points) - { - if (!IsPointInPlane(P1, normal, point)) - { - return false; // 如果有点不在平面上,返回false - } - } - - return true; // 如果所有点都在平面上,返回true - } - - /// - /// 判断点是否在平面上 - /// - /// - private static bool IsPointInPlane(Vector3 planePoint, Vector3 normal, Vector3 point) - { - // 计算点到平面的距离 - // 点到平面的距离公式:d = (point - planePoint) · normal / |normal| - float distance = Vector3.Dot(point - planePoint, normal) / normal.Length(); - - // 如果距离为0,说明点在平面上 - return Math.Abs(distance) < 1e-6; // 容忍一个小的误差 - } - - public static void GenerateLightingForModel(Viewport3DX viewport) - { - // 移除现有的灯光 - List lights = new List(); - foreach (var item in viewport.Items) - { - if (item is Light3D) - { - lights.Add(item as Light3D); - } - } - lights.ForEach(item => viewport.Items.Remove(item)); - - // 获取场景中最大的模型 - var models = viewport.Items.OfType(); - if (!models.Any()) return; - - var largestModel = models - .OrderByDescending(m => GetBoundingBoxVolume(m.Geometry.Bound)) - .FirstOrDefault(); - - if (largestModel == null) return; - - // 获取最大模型的边界立方体 - var boundingBox = largestModel.Geometry.Bound; - var size = boundingBox.Size; - var center = boundingBox.Center; - - // 计算 Bounding Box 的八个顶点 - var corners = new List - { - boundingBox.Minimum.ToPoint3D(), // 左下后 - new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Minimum.Z), // 右下后 - new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 左上后 - new Point3D(boundingBox.Minimum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z), // 左下前 - boundingBox.Maximum.ToPoint3D(), // 右上前 - new Point3D(boundingBox.Maximum.X, boundingBox.Maximum.Y, boundingBox.Minimum.Z), // 右上后 - new Point3D(boundingBox.Minimum.X, boundingBox.Maximum.Y, boundingBox.Maximum.Z), // 左上前 - new Point3D(boundingBox.Maximum.X, boundingBox.Minimum.Y, boundingBox.Maximum.Z) // 右下前 - }; - - // 在每个顶点处添加光源 - 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() * 4 // 光照范围覆盖整个模型 - }; - - viewport.Items.Add(pointLight); - } - - // 添加顶部的多处光源(从上向下照射,模拟太阳光) - double topLightDistance = size.Y * 1.2; // 适当调整顶部光源的高度 - var topLightPositions = new List - { - new Point3D(center.X, boundingBox.Maximum.Y + topLightDistance, center.Z), - new Point3D(boundingBox.Minimum.X - size.X / 3, boundingBox.Maximum.Y + topLightDistance, center.Z), - new Point3D(boundingBox.Maximum.X + size.X / 3, boundingBox.Maximum.Y + topLightDistance, 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) // 控制光的衰减,使光在距离内更有效 - }; - viewport.Items.Add(topLight); - } - // 添加环境光以柔化整体效果并增加亮度 - var ambientLight = new AmbientLight3D - { - Color = Colors.LightGray - }; - viewport.Items.Add(ambientLight); - - - var ambientLigh1t = new AmbientLight3D - { - Color = Colors.Gray // 设置环境光颜色 - }; - viewport.Items.Add(ambientLigh1t); - - - // 添加环境光以柔化整体效果并增加亮度 - // var ambientLight = new AmbientLight3D - // { - // Color = Colors.LightGray - // }; - // viewport.Items.Add(ambientLight); - // 添加底部的光源(从底部中心向上照射) - // var bottomLightPosition = new Point3D(center.X, boundingBox.Minimum.Y - 1.0f, center.Z); // 位于底部中心点下方 - // var bottomLight = new PointLight3D - // { - // Position = bottomLightPosition, - // Color = Colors.White, // 白色光源 - // Range = (float)size.Length() * 2 // 设置光照范围 - // }; - // viewport.Items.Add(bottomLight); - } - - // 辅助方法:获取边界体积 - private static double GetBoundingBoxVolume(BoundingBox bound) - { - var size = bound.Size; - return size.X * size.Y * size.Z; - } - - public static List InitDemo(Viewport3DX viewport) - { - List entities = new List(); - try - { - using (FileStream fileStream = new FileStream(AppDomain.CurrentDomain.BaseDirectory + @"Resource\DimDemo.txt", FileMode.Open, FileAccess.Read)) - using (StreamReader reader = new StreamReader(fileStream)) - { - string line; - while ((line = reader.ReadLine()) != null) - { - if(string.IsNullOrWhiteSpace(line))continue; - if(line.StartsWith("-- "))continue; - entities.Add(Viewport3DTriangleEntity.CreateByString(line)); - } - } - } - catch (Exception ex) - { - Console.WriteLine($"读取文件时出错:{ex.Message}"); - } - GenerateModelByEntity(viewport, entities); - GenerateLightingForModel(viewport); - - return entities; - } - - public static List GenerateEmissiveModelByEntity(Viewport3DX viewport, List entities, Color4 emissiveColor) - { - // 创建 MeshBuilder,启用法线生成 - var meshBuilder = new MeshBuilder(true, false); - viewport.RenderHost.MSAA = MSAALevel.Maximum; - - // 通过实体构建三角形面 - foreach (var entity in entities) - { - List vertices = new List() { entity.Point1, entity.Point2, entity.Point3 }; - meshBuilder.AddPolygon(vertices); - } - - // 生成网格 - var mesh = meshBuilder.ToMeshGeometry3D(); - - // 创建自发光材质 - var material = new PBRMaterial - { - AlbedoColor = new Color4(0, 0, 0, 1.0f), // 黑色,避免其他光照影响 - EmissiveColor = emissiveColor, // 自发光颜色 - MetallicFactor = 0.0, // 非金属 - RoughnessFactor = 1.0, // 高粗糙度,避免反射效果 - ReflectanceFactor = 0.0, // 无反射 - ClearCoatStrength = 0.0, // 无清漆效果 - ClearCoatRoughness = 1.0, // 高粗糙度 - }; - - // 设置材质的采样器状态 - material.SurfaceMapSampler = new SamplerStateDescription - { - Filter = Filter.MinMagMipLinear, - AddressU = TextureAddressMode.Wrap, - AddressV = TextureAddressMode.Wrap, - AddressW = TextureAddressMode.Wrap - }; - - // 创建几何模型 - var geometryModel = new MeshGeometryModel3D - { - Geometry = mesh, - Material = material - }; - - // 将几何模型添加到视图中 - viewport.Items.Add(geometryModel); - List result = new List(); - result.Add(geometryModel); - return result; - } - - public static List GenerateLineTextModelByEntity(Viewport3DX viewport, List entities) - { - List result = new List(); - List selFaceVector = new List(); - foreach (var entity in entities) - { - selFaceVector.Add(entity.Point1); - selFaceVector.Add(entity.Point2); - selFaceVector.Add(entity.Point3); - } - var edgeLines = new List>(); - var lengths = new List(); // 长度列表 - var angles = new List(); // 夹角列表 - HashSet uniqueVectors = new HashSet(selFaceVector); - List sortedVectors = uniqueVectors.ToList(); - Vector3 center = GetCentroid(sortedVectors); - sortedVectors.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); // 按角度从小到大排序 - }); - for (int i = 0; i < sortedVectors.Count - 1; i++) - { - var nowItem = sortedVectors[i]; - var nextItem = sortedVectors[i + 1]; - edgeLines.Add(new Tuple(nowItem, nextItem)); - // 计算当前线段的长度 - lengths.Add((nextItem - nowItem).Length()); - } - edgeLines.Add(new Tuple(sortedVectors.Last(), sortedVectors.First())); - lengths.Add((sortedVectors.Last() - sortedVectors.First()).Length()); - //计算夹角 - 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 = p2 - p1; - var v2 = p3 - p2; - double angle = AngleBetween(v1, v2); - angles.Add(angle); // 夹角,单位是度 - } - if (sortedVectors.Count == lengths.Count && sortedVectors.Count == angles.Count) - { - for (int i = 0; i < sortedVectors.Count; i++) - { - var point = sortedVectors[i]; - var pointNext = i + 1 >= sortedVectors.Count ? sortedVectors.First() : sortedVectors[i + 1]; - var lengthText = $"L: {lengths[i]:F2}"; - var angleText = $"Angle: {angles[i]:F2}°"; - - var lengthTextPoint = (pointNext + point) / 2; - - // 在合适位置显示文本 - var text3DL = DisplayText3D(lengthText, - new Vector3(lengthTextPoint.X, lengthTextPoint.Y + 0.5f, lengthTextPoint.Z)); - var text3DA = DisplayText3D(angleText, new Vector3(point.X, point.Y + 0.5f, point.Z + 0.5f)); - viewport.Items.Add(text3DL); - viewport.Items.Add(text3DA); - result.Add(text3DL); - result.Add(text3DA); - } - } - else - { - // 如果列表长度不一致,给出提示信息 - Console.WriteLine("The lengths or angles list size does not match the sorted vectors list."); - } - var lineBuilder = new LineBuilder(); - foreach (var line in edgeLines) - { - lineBuilder.AddLine(line.Item1, line.Item2); - } - - var edgeLinesModel = new LineGeometryModel3D - { - Geometry = lineBuilder.ToLineGeometry3D(), - Color = System.Windows.Media.Colors.Red, - Thickness = 1.0 - }; - - viewport.Items.Add(edgeLinesModel); - result.Add(edgeLinesModel); - return result; - } - - -} \ No newline at end of file diff --git a/SparkClient.sln.DotSettings.user b/SparkClient.sln.DotSettings.user index 52109ee..e3f03b5 100644 --- a/SparkClient.sln.DotSettings.user +++ b/SparkClient.sln.DotSettings.user @@ -3,8 +3,10 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded @@ -16,9 +18,14 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded ForceIncluded - ForceIncluded \ No newline at end of file + ForceIncluded + ForceIncluded + ForceIncluded \ No newline at end of file diff --git a/ViewModel/Grading/DiamondSelectVM.cs b/ViewModel/Grading/DiamondSelectVM.cs index fc3623e..2d25edb 100644 --- a/ViewModel/Grading/DiamondSelectVM.cs +++ b/ViewModel/Grading/DiamondSelectVM.cs @@ -6,21 +6,18 @@ 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; using System.Windows.Threading; -using SparkClient.Model.Services; -using MessageBox = System.Windows.MessageBox; namespace SparkClient.ViewModel.Grading; public class DiamondSelectVM : BaseViewModel { - - private SOCClientService _socClientService; - private AlgorithmServer _algorithmServer; private string DiamondCode { get; set; } private List _buttons; private List _buttons2; @@ -101,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++) @@ -126,8 +151,14 @@ public class DiamondSelectVM : BaseViewModel }); } - SocResultEntity socResolt = new SocResultEntity(); - AlgorithmResultEntity parameter = new AlgorithmResultEntity(); + // 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) @@ -136,13 +167,6 @@ public class DiamondSelectVM : BaseViewModel parameter.CrownType = value.Split(" ")[1]; parameter.PavType = value.Split(" ")[2]; } - - // 启动soc - socResolt = await DoSoc(); - // 启动算法 - parameter = await DoAlgorithm(socResolt, parameter.Shape, parameter.CrownType); - - parameter.DiamondCode = DiamondCode; GradingResult(parameter); }); @@ -164,132 +188,18 @@ public class DiamondSelectVM : BaseViewModel WindowManager.mainViewModel.Content = vm; WindowManager.openContent.Add(vm); } - - - /// - /// 启动切工仪接口。 - /// - /// 图片索引 - /// 图片索引 - /// 图片索引 - /// 图片的字节数组 - private async Task DoSoc() - { - // 光照度 - int lightLevel = 100; - // 初始化SOC客户端服务,传入SOC端的地址和认证Token - _socClientService = new SOCClientService("http://localhost:5000/api/SoC", "your_basic_auth_token"); - // SOC接口 - string savePath = @"d:\\diamond_images"; - SocResultEntity resultEntity = await _socClientService.ProcessImageCollectionAsync(lightLevel, savePath); - // 转换成json - string jsonResult = JsonConvert.SerializeObject(resultEntity, Formatting.Indented); - ShowMessage(jsonResult); - - return resultEntity; - } - /// - /// 启动算法接口。 - /// - /// 切工仪接口返回值 - /// - /// - /// 定级参数,3D模型参数 - private Task DoAlgorithm(SocResultEntity socResolt, String shape, String crownType) + private AlgorithmResultEntity DoAlgorithm() { - _algorithmServer = new AlgorithmServer(); - //钻石形状:shape - //钻石子形状 - string shape_mode = crownType; - //图片根目录 - string image_file_base_path = "d:\\diamond_images"; - //图片集合 - string image_files = JsonConvert.SerializeObject(socResolt.Images, Formatting.Indented); - - //算法配置参数 - string algo_config = @" - { - ""edgel_detector"": { - ""kernel_size"": 15, - ""num_filters"": 16, - ""non_max_suppression_winsize"": 17 - }, - ""edge_matcher"": { - ""grid_size"": 32, - ""ransac_iterations"": 10, - ""inliner_pts_threshold"": 25, - ""inliner_dist_threshold"": 1.0, - ""inliner_angle_threshold"": 10.0, - ""line_gap_threshold"": 10.0, - ""line_length_threshold"": 30.0 - }, - ""base_detector"": { - ""inliner_dist_threshold"": 30.0, - ""inliner_theta_threshold"": 5.0, - ""inliner_num_threshold"": 50, - ""sample_dist"": 25.0, - ""iterations"": 100, - ""learning_rate"": 0.0002, - ""gradient_clip"": 5.0, - ""gradient_momentum"": 0.1, - ""base_center_x"": 1296.0 - }, - ""girdle_detector"": { - ""pavilion_edge_threshold"": 300.0, - ""pavilion_angle_min"": 30.0, - ""pavilion_angle_max"": 50.0, - ""gridle_angle_threshold"": 80.0, - ""gridle_to_axis_threshold"": 300.0, - ""gridle_y_ratio_min"": 0.2, - ""gridle_y_ratio_max"": 0.7, - ""gridle_num_candid_threshold"": 30, - ""gridle_center_dist_threshold"": 120.0, - ""gridle_search_radius"": 80, - ""iterations"": 2000, - ""learning_rate"": 0.002, - ""gradient_clip"": 5.0, - ""gradient_momentum"": 0.1 - }, - ""pavilion_detector"": { - ""pavilion_edge_threshold"": 300.0, - ""pavilion_angle_min"": 30.0, - ""pavilion_angle_max"": 50.0, - ""silhouette_match_samples"": 10, - ""silhouette_match_padding"": 0.1, - ""iterations"": 10000, - ""learning_rate"": 0.02, - ""gradient_momentum"": 0.1, - ""loss_func_tasks"": 16 - }, - ""crown_detector"": { - ""sample_point_rate"": 0.1, - ""iterations"": 2500, - ""learning_rate"": 0.1, - ""gradient_momentum"": 0.1, - ""loss_func_tasks"": 16 - }, - ""output"": { - ""pixel_length"": 0.00678, - ""girdle_interpolate_pts"": 3 - } - }"; - - AlgorithmResultEntity algoResult = _algorithmServer.CallParseJsonAndReturnActions(shape, shape_mode, image_file_base_path, image_files, algo_config); - - - // 将 algoResult 序列化为格式化的 JSON 字符串 - string algoJsonResult = JsonConvert.SerializeObject(algoResult, Formatting.Indented); - ShowMessage(algoJsonResult); - return Task.FromResult(algoResult); + AlgorithmResultEntity param = new AlgorithmResultEntity(); + string json = $"{{\"status\": \"ok\",\r\n \"facets\": [\r\n {{\r\n \"coords\": [\r\n {{\r\n \"x\": 0.03402838855981827,\r\n \"y\": -0.11212713271379471,\r\n \"z\": 5.701290607452393\r\n }},\r\n {{\r\n \"x\": 0.46919262409210205,\r\n \"y\": 0.058160409331321716,\r\n \"z\": 5.289202690124512\r\n }},\r\n {{\r\n \"x\": 0.1256149709224701,\r\n \"y\": 0.3005124032497406,\r\n \"z\": 5.330680847167969\r\n }}\r\n ],\r\n \"facet_id\": \"21_0\",\r\n \"facet_type\": 21\r\n }},\r\n {{\r\n \"coords\": [\r\n {{\r\n \"x\": 2.9093382358551025,\r\n \"y\": 3.4028751850128174,\r\n \"z\": 1.724348783493042\r\n }},\r\n {{\r\n \"x\": 0.46919262409210205,\r\n \"y\": 0.058160409331321716,\r\n \"z\": 5.289202690124512\r\n }},\r\n {{\r\n \"x\": 0.1256149709224701,\r\n \"y\": 0.3005124032497406,\r\n \"z\": 5.330680847167969\r\n }}\r\n ],\r\n \"facet_id\": \"21_1\",\r\n \"facet_type\": 21\r\n }}\r\n ],\r\n \"measurements\": {{\r\n \"DIAMETER\": 6.43,\r\n \"DIAMETER_DEV\": 1.2,\r\n \"M1\": 6.409999847412109,\r\n \"M2\": 6.46999979019165,\r\n \"M3\": 3.9700000286102295,\r\n \"TABLE\": 58.0,\r\n \"TABLE_MIN\": 57.5,\r\n \"TABLE_MAX\": 58.6,\r\n \"CROWN_HEIGHT\": 11.5,\r\n \"CROWN_H_DEV\": 1.4,\r\n \"CROWN_H_MIN\": 10.7,\r\n \"CROWN_H_MAX\": 12.1,\r\n \"CROWN_ANGLE\": 30.5,\r\n \"CROWN_ANGLE_DEV\": 0.9,\r\n \"CROWN_ANGLE_MIN\": 30.2,\r\n \"CROWN_ANGLE_MAX\": 31.1,\r\n \"PAV_DEPTH\": 35.0,\r\n \"PAV_DEPTH_DEV\": 0.8,\r\n \"PAV_DEPTH_MIN\": 34.5,\r\n \"PAV_DEPTH_MAX\": 35.3,\r\n \"PAV_ANGLE\": 35.1,\r\n \"PAV_ANGLE_DEV\": 0.8,\r\n \"PAV_ANGLE_MIN\": 34.7,\r\n \"PAV_ANGLE_MAX\": 35.5,\r\n \"GIRDLE_BEZEL\": 7.1,\r\n \"GIRDLE_BEZEL_DEV\": 1.8,\r\n \"GIRDLE_BEZEL_MIN\": 6.8,\r\n \"GIRDLE_BEZEL_MAX\": 7.5,\r\n \"GIRDLE_BONE\": 7.8,\r\n \"GIRDLE_BONE_MIN\": 7.3,\r\n \"GIRDLE_BONE_MAX\": 8.3,\r\n \"GIRDLE\": 6.3,\r\n \"GIRDLE_DEV\": 3.3,\r\n \"GIRDLE_MIN\": 4.2,\r\n \"GIRDLE_MAX\": 7.5,\r\n \"TOTAL_DEPTH\": 53.5,\r\n \"CULET\": 1.8,\r\n \"LW_RATIO\": 1.0,\r\n \"TOC\": 1.4,\r\n \"COC\": 0.5,\r\n \"TA\": 3.81,\r\n \"LGF\": 75,\r\n \"STAR\": 65,\r\n \"STAR_MIN\": 61.1,\r\n \"STAR_MAX\": 65.4,\r\n \"LOWER_HALVES_RATIO\": 75,\r\n \"LOWER_HALVES_RATIO_MIN\": 73.3,\r\n \"LOWER_HALVES_RATIO_MAX\": 78.1,\r\n \"TWIST\": 1.3,\r\n \"TWIST_DEV\": 2.4,\r\n \"TWIST_MIN\": -0.2,\r\n \"TWIST_MAX\": -2.4,\r\n \"CULET_TO_TABLE\": 1.4\r\n }}\r\n}}\r\n"; + param = JsonConvert.DeserializeObject(json); + return param; } - - - private void ShowMessage(string message) + private SocResultEntity DoSoc() { - MessageBox.Show(message); + return new SocResultEntity(); } - } public class ButtonInfo { diff --git a/ViewModel/Grading/GradingResultVM.cs b/ViewModel/Grading/GradingResultVM.cs index 9fda292..d76d9e7 100644 --- a/ViewModel/Grading/GradingResultVM.cs +++ b/ViewModel/Grading/GradingResultVM.cs @@ -1,12 +1,17 @@ +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; using log4net.Appender; +using Newtonsoft.Json; using SparkClient.Model.Entity.ApiEntity; using SparkClient.Model.Helper; +using SparkClient.Views.UserControl.ViewportData; using static System.Windows.Forms.VisualStyles.VisualStyleElement.ToolTip; namespace SparkClient.ViewModel.Grading; @@ -15,9 +20,10 @@ public class GradingResultVM : BaseViewModel { private string DiamondCode { get; set; } private List _dtResults; - + // ViewportData private DataTable _dsList; private DataTable _gradeList; + private ViewportData _viewportData; private string _standard; private string _shape; @@ -26,8 +32,11 @@ public class GradingResultVM : BaseViewModel private string _wight; private string _cutLevelTotal; private string _symLevelTotal; - public ICommand ChangeNormCommand { get; } + 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)); } } public string Shape { get { return _shape; } set { _shape = value; OnPropertyChanged(nameof(Shape)); } } public string CrownType { get { return _crownType; } set { _crownType = value; OnPropertyChanged(nameof(CrownType)); } } @@ -48,7 +57,6 @@ public class GradingResultVM : BaseViewModel /// 检测结果 public GradingResultVM(object result) { - ChangeNormCommand = new RelayCommand(ChangeNorm); if (result != null) { InitView(result as AlgorithmResultEntity); @@ -57,6 +65,10 @@ public class GradingResultVM : BaseViewModel } private void InitView(AlgorithmResultEntity result) { + + string data = JsonConvert.SerializeObject(result); + ViewportData = new ViewportData(result.DiamondCode, data); + ViewportData.LoadData(); totalCutGrade = 0; totalSymGrade = 0; Standard = result.Standard; @@ -243,8 +255,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 +265,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 +290,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 +303,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 +315,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 +326,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 +352,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 +365,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 +376,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 +387,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 +396,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 +405,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 +414,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 +426,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 @@ -432,10 +444,67 @@ 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没关联 - 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 +518,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 +573,7 @@ public class CalGradeInfo{ public int isMaxExist { get; set; } public int isMinExist { get; set; } } -public class DataInfo() +public class DataInfo { public string? TestItemId { get; set; } public string? TestItemName { get; set; } @@ -505,18 +582,41 @@ public class DataInfo() public string? Min { get; set; } public string? Max { get; set; } public string? CutLevel { get; set; } - public string? SymLevel { get; set; } - public DataTable GradeList { get { + private int? _symLevel; + //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 new file mode 100644 index 0000000..36447ec --- /dev/null +++ b/Views/Dialog/JsonImport.xaml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - + - - + + - - - - - - - - - - + + + + + + + + + + + - + @@ -101,7 +122,7 @@ - + @@ -110,34 +131,44 @@ - - - - - - - - + + + + + + + + + + diff --git a/Views/UserControl/Viewport3D.xaml.cs b/Views/UserControl/Viewport3D.xaml.cs index 2fdb1cf..c43d73e 100644 --- a/Views/UserControl/Viewport3D.xaml.cs +++ b/Views/UserControl/Viewport3D.xaml.cs @@ -1,41 +1,366 @@ +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; +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 { + public Viewport3D() { InitializeComponent(); DataContext = this; + this.Viewport3Dx.EffectsManager = new DefaultEffectsManager(); + Viewport3Dx.ShowViewCube = false; + Viewport3Dx.ShowCoordinateSystem = false; + ViewportManager.SetViewport3D(Viewport3Dx); } - - private void MenuItem_OnChecked(object sender, RoutedEventArgs e) + + /// + /// 初始化 + /// + /// + /// + private void Viewport3D_OnLoaded(object sender, RoutedEventArgs e) + { + 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 Viewport3Dx_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e) { - Growl.InfoGlobal("Viewport is checked" + e.OriginalSource); + if (ViewportManager.DoubleClickSelect == false) return; + + var mousePosition = e.GetPosition(Viewport3Dx); + var hits = Viewport3Dx.FindHits(mousePosition); + // 如果没有命中任何 3D 对象 + if (hits == null || hits.Count == 0) + { + ViewportManager.ClearDicModels(); + return; + } + + // 获取第一个命中的对象 + var hit = hits[0]; + // 检查是否是 MeshGeometryModel3D + if (hit.ModelHit is MeshGeometryModel3D modelHit) + { + // 获取几何信息 + var geometry = modelHit.Geometry as HelixToolkit.Wpf.SharpDX.MeshGeometry3D; + if (geometry != null) + { + // 获取命中的三角形索引 + var triangleIndex = hit.TriangleIndices; + + // 获取三角形顶点 + 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(); + } } - private void MenuItem_OnUnchecked(object sender, RoutedEventArgs e) + /// + /// 顶部按钮 - 视角切换 + /// + /// + /// + /// + private void BtnAngle_OnClick(object sender, RoutedEventArgs e) + { + 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); + } + + /// + /// 按钮调整相机方向[底部按钮] + /// + /// + /// + private void BtnDirection_OnClick(object sender, RoutedEventArgs e) { - Growl.InfoGlobal("Viewport is Unchecked" + 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; + } } + + #region 右键菜单事件绑定 + /// + /// 右键选择 + /// + /// + /// + private void MenuItem_OnCheckedChanged(object sender, RoutedEventArgs e) + { + 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) { - MainBorder.ContextMenu.IsOpen = true; + if (_isDragging) + { + _isDragging = false; + MainBorder.ContextMenu.Visibility = Visibility.Hidden; + return; + } + MainBorder.ContextMenu.Visibility = Visibility.Visible; + } private void UIElement_OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) { - MainBorder.ContextMenu.IsOpen = false; - e.Handled = true; + _mouseDownPosition = e.GetPosition(Viewport3Dx); + _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 MenuItem_OnClick(object sender, RoutedEventArgs e) + 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 (int.TryParse(textBox.Text, out int value)) + { + if (value < 0) + { + 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/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..ab4ab29 --- /dev/null +++ b/Views/UserControl/ViewportData/Helper/CommonHelper.cs @@ -0,0 +1,91 @@ +using System.Security.Cryptography; +using System.Text; +using System.Windows.Input; +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 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 数据 + 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..3d59451 --- /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 = 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)); + } + + #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..371044d --- /dev/null +++ b/Views/UserControl/ViewportData/Helper/ViewportManager.cs @@ -0,0 +1,358 @@ +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; + +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.Camera.UpDirection = new Vector3D(0, -1, 0); + _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