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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Views/Dialog/JsonImport.xaml.cs b/Views/Dialog/JsonImport.xaml.cs
new file mode 100644
index 0000000..42478f9
--- /dev/null
+++ b/Views/Dialog/JsonImport.xaml.cs
@@ -0,0 +1,108 @@
+using HandyControl.Controls;
+using Newtonsoft.Json;
+using SparkClient.Model.Helper;
+using SparkClient.ViewModel;
+using SparkClient.ViewModel.Dialog;
+using System.IO;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Input;
+using System.Windows.Media;
+
+namespace SparkClient.Views.Dialog
+{
+ ///
+ /// JsonImport.xaml 的交互逻辑
+ ///
+ public partial class JsonImport
+ {
+ public BaseViewModel ViewModel = null;
+ public JsonImport()
+ {
+ InitializeComponent();
+ WindowStartupLocation = WindowStartupLocation.CenterScreen;
+ // 动态设置圆角裁剪
+ this.Loaded += (s, e) => ApplyCornerRadiusClip();
+ this.SizeChanged += (s, e) => ApplyCornerRadiusClip(); // 保证在大小改变时也裁剪
+ this.Width = 562;
+ this.Height = 222;
+ this.ViewModel = new StartDialogVM();
+ this.DataContext = this.ViewModel;
+ }
+
+
+ #region 重写窗体操作按钮
+ private void Border_Close_MouseEnter(object sender, MouseEventArgs e)
+ {
+ // 鼠标进入时更改背景色
+ if (sender is Border border)
+ {
+ border.Background = new SolidColorBrush(Color.FromArgb(50, 255, 0, 0));
+ }
+ }
+ private void Border_MouseLeave(object sender, MouseEventArgs e)
+ {
+ // 鼠标离开时恢复背景色
+ if (sender is Border border)
+ {
+ border.Background = new SolidColorBrush(Colors.Transparent);
+ }
+ }
+
+ private void UIElement_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
+ {
+ if (e.LeftButton == MouseButtonState.Pressed)
+ {
+ try
+ {
+ this.DragMove();
+ }
+ catch (InvalidOperationException ex)
+ {
+
+ }
+ }
+ }
+ #endregion
+
+ private void Close_Click(object sender, RoutedEventArgs e)
+ {
+ DialogResult = false;
+ this.Close();
+ }
+ private void open_Click(object sender, RoutedEventArgs e)
+ {
+ Microsoft.Win32.OpenFileDialog openFileDialog = new Microsoft.Win32.OpenFileDialog();
+ openFileDialog.Filter = "Text files (*.json)|*.json|All files (*.*)|*.*";
+ if (openFileDialog.ShowDialog() == true)
+ {
+ string filename = openFileDialog.FileName;
+ if (File.Exists(filename))
+ {
+ FilePath.Text = filename;
+ }
+ }
+ }
+ private void Ok_Click(object sender, RoutedEventArgs e)
+ {
+ if (FilePath.Text.Trim().Length > 0) {
+ DialogResult = true;
+ this.Close();
+ }
+ else
+ {
+ Growl.Error("请选择导入json文件");
+ }
+ }
+ private void ApplyCornerRadiusClip()
+ {
+ // 使用矩形几何生成圆角裁剪
+ this.Border.Clip = new RectangleGeometry
+ {
+ Rect = new Rect(0, 0, this.Border.ActualWidth, this.Border.ActualHeight),
+ RadiusX = this.Border.CornerRadius.TopLeft, // 使用 Border 的 CornerRadius
+ RadiusY = this.Border.CornerRadius.TopLeft
+ };
+ }
+ }
+}
diff --git a/Views/Dialog/StartDialog.xaml.cs b/Views/Dialog/StartDialog.xaml.cs
index 4917034..a83c2d8 100644
--- a/Views/Dialog/StartDialog.xaml.cs
+++ b/Views/Dialog/StartDialog.xaml.cs
@@ -1,9 +1,7 @@
using HandyControl.Controls;
using SparkClient.Model.Helper;
using SparkClient.ViewModel;
-using SparkClient.ViewModel.BaseWindow;
using SparkClient.ViewModel.Dialog;
-using SparkClient.ViewModel.Grading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
diff --git a/Views/Grading/GradingResult.xaml b/Views/Grading/GradingResult.xaml
index 95da7ad..cfcad93 100644
--- a/Views/Grading/GradingResult.xaml
+++ b/Views/Grading/GradingResult.xaml
@@ -103,41 +103,58 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ DisplayMemberPath="Key" SelectedValuePath="Value" SelectedValue="{Binding SymLevel, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" SelectionChanged="ChangSymLevel"/>
-
-
-
-
-
-
-
-
+
-
+
+
+
diff --git a/Views/Grading/GradingResult.xaml.cs b/Views/Grading/GradingResult.xaml.cs
index 2b72af7..5c9f887 100644
--- a/Views/Grading/GradingResult.xaml.cs
+++ b/Views/Grading/GradingResult.xaml.cs
@@ -14,7 +14,7 @@ namespace SparkClient.Views.Grading;
public partial class GradingResult
{
- List triangles = new List();
+ // List triangles = new List();
List mouseAddModels = new List();
public GradingResult()
{
@@ -120,13 +120,9 @@ public partial class GradingResult
// }
}
- private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
+ private void ChangSymLevel(object sender, SelectionChangedEventArgs e)
{
-
- }
-
- private void DataGrid_SelectionChanged_1(object sender, SelectionChangedEventArgs e)
- {
-
+ var d = DataContext as GradingResultVM;
+ d.ChangeSym(null);
}
}
\ No newline at end of file
diff --git a/Views/UserControl/Viewport3D.xaml b/Views/UserControl/Viewport3D.xaml
index 6d71402..0a52865 100644
--- a/Views/UserControl/Viewport3D.xaml
+++ b/Views/UserControl/Viewport3D.xaml
@@ -6,7 +6,7 @@
xmlns:local="clr-namespace:SparkClient.Views.UserControl"
xmlns:hx="http://helix-toolkit.org/wpf/SharpDX"
xmlns:hc="https://handyorg.github.io/handycontrol"
- mc:Ignorable="d"
+ mc:Ignorable="d" Loaded="Viewport3D_OnLoaded"
>
@@ -23,65 +23,86 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
-
+
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
-
+
@@ -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