feat: 嵌入3D模型

master
Tongg 8 months ago
parent 822c256035
commit 547a2e1c00
  1. 7
      Language/zh_CN.xaml
  2. 129
      Model/Entity/Viewport3DTriangleEntity.cs
  3. 31
      Model/Enums/PlaneType.cs
  4. 825
      Model/Helper/Viewport3DHelper.cs
  5. BIN
      Resource/Images/Temp/rtop.png
  6. 18
      SparkClient.csproj
  7. 2
      ViewModel/BaseWindow/HomeWindowVM.cs
  8. 17
      ViewModel/Grading/GradingResultVM.cs
  9. 79
      Views/Grading/GradingResult.xaml
  10. 111
      Views/Grading/GradingResult.xaml.cs

@ -26,6 +26,13 @@
<sys:String x:Key="BeautifyJson">美化Json</sys:String>
<sys:String x:Key="UglifyJson">压缩Json</sys:String>
<!-- 检测结果 -->
<sys:String x:Key="resAvg">平均</sys:String>
<sys:String x:Key="resYuan">圆度</sys:String>
<sys:String x:Key="resShen">深度</sys:String>
<sys:String x:Key="resMin">最小值</sys:String>
<sys:String x:Key="resMax">最大值</sys:String>
<sys:String x:Key="resLevel">SYM等级</sys:String>
<sys:String x:Key="ExitAsk">是否退出程序?</sys:String>
<sys:String x:Key="ExitAskTitle">是否退出</sys:String>

@ -0,0 +1,129 @@
using System.Security.Cryptography;
using System.Text;
using SharpDX;
using SparkClient.Model.Enums;
namespace SparkClient.Model.Entity;
/// <summary>
/// 三角形
/// </summary>
public class Viewport3DTriangleEntity
{
/// <summary>
/// 点1
/// </summary>
public Vector3 Point1 { get; set; }
/// <summary>
/// 点2
/// </summary>
public Vector3 Point2 { get; set; }
/// <summary>
/// 点3
/// </summary>
public Vector3 Point3 { get; set; }
/// <summary>
/// 三角形代码[生成]
/// 按顺序:p1.x,p1.y,p1.z;p2.x,p2.y,p2.z;p3.x,p3.y,p3.z 拼接后使用生成大写16位md5
/// </summary>
public String TriangleCode { get; set; }
/// <summary>
/// 面代码
/// 由多个三角形组成的面的代码:entity1、entity2组成了一个正方形,那么他俩的TriangleCode属性值一致
/// </summary>
public String PlaneCode { get; set; }
/// <summary>
/// 面类型
/// 比如这个面时钻石的腰部分,那么type统一,当面类型为err时则该面是一个异常面
/// 可选值:PlaneType
/// </summary>
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")));
}
}
}

@ -0,0 +1,31 @@
using System.ComponentModel;
namespace SparkClient.Model.Enums;
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
}

@ -0,0 +1,825 @@
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);
/// <summary>
/// 生成一个多边形钻石
/// </summary>
/// <param name="viewport">控件</param>
/// <param name="sides">边数</param>
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<Vector3>();
var smallVertices = new List<Vector3>();
var edgeLines = new List<Tuple<Vector3, Vector3>>();
// 生成大八边形顶点
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<Vector3> fanPositions2 = new List<Vector3>()
{ bottomPoint, largeVertices[nextIndex], largeVertices[i] };
List<Vector3> fanNormals2 = new List<Vector3>()
{ 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<Vector3, Vector3>(bottomPoint, largeVertices[nextIndex]));
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[nextIndex], largeVertices[i]));
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[i], bottomPoint));
}
// 生成法线并添加小八边形的面
var smallNormals = new List<Vector3>();
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<Vector3> fanPositions1 = new List<Vector3>()
{ largeVertices[i], largeVertices[nextIndex], smallVertices[nextIndex] };
List<Vector3> fanNormals1 = new List<Vector3>()
{ 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<Vector3> fanPositions2 = new List<Vector3>()
{ largeVertices[i], smallVertices[nextIndex], smallVertices[i] };
List<Vector3> fanNormals2 = new List<Vector3>()
{ 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<Vector3, Vector3>(largeVertices[i], largeVertices[nextIndex]));
edgeLines.Add(new Tuple<Vector3, Vector3>(largeVertices[nextIndex], smallVertices[nextIndex]));
edgeLines.Add(new Tuple<Vector3, Vector3>(smallVertices[nextIndex], smallVertices[i]));
edgeLines.Add(new Tuple<Vector3, Vector3>(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<Viewport3DTriangleEntity> entities)
{
var meshBuilder = new MeshBuilder(true, false);
viewport.RenderHost.MSAA = MSAALevel.Maximum;
foreach (var entity in entities)
{
List<Vector3> vertices = new List<Vector3>(){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);
}
/// <summary>
/// 传入一个三角形,找到关联面
/// </summary>
/// <param name="viewport">控件</param>
/// <param name="triangleIndex">三角形顶点</param>
/// <param name="selColor">选择主体颜色</param>
/// <param name="linkColor">选择关联颜色(null不渲染)</param>
/// <param name="borderColor">选择边框颜色(null不渲染)</param>
/// <returns></returns>
public static List<GeometryModel3D> HighlightAssociatedFaces(Viewport3DX viewport,
Tuple<Vector3, Vector3, Vector3> triangleIndex, Color4 selColor, Color4? linkColor = null,
Color4? borderColor = null)
{
List<GeometryModel3D> result = new List<GeometryModel3D>();
//选中平面上的点(实现选中高亮、边框线)
List<Vector3> selFaceVector = new List<Vector3>();
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<Vector3> temp = new List<Vector3>() { 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<Vector3> fanNormals2 = new List<Vector3>();
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<Tuple<Vector3, Vector3>>();
var lengths = new List<double>(); // 长度列表
var angles = new List<double>(); // 夹角列表
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector);
List<Vector3> 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<Vector3, Vector3>(nowItem, nextItem));
// 计算当前线段的长度
lengths.Add((nextItem - nowItem).Length());
}
edgeLines.Add(new Tuple<Vector3, Vector3>(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;
}
/// <summary>
/// 遍历视图中的所有三角形
/// </summary>
/// <param name="viewport"></param>
/// <returns></returns>
public static List<Tuple<Vector3, Vector3, Vector3>> ForeachViewPortTriangle(Viewport3DX viewport)
{
List<Tuple<Vector3, Vector3, Vector3>> result = new List<Tuple<Vector3, Vector3, Vector3>>();
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<Vector3, Vector3, Vector3>(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<Vector3> 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);
}
/// <summary>
/// 判断集合元素是狗共面
/// </summary>
/// <param name="triangle"></param>
/// <param name="points"></param>
/// <returns></returns>
public static bool ArePointsOnSamePlane(Tuple<Vector3, Vector3, Vector3> triangle, List<Vector3> 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
}
/// <summary>
/// 判断点是否在平面上
/// </summary>
/// <returns></returns>
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<Light3D> lights = new List<Light3D>();
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<MeshGeometryModel3D>();
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<Point3D>
{
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<Point3D>
{
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<Viewport3DTriangleEntity> InitDemo(Viewport3DX viewport)
{
List<Viewport3DTriangleEntity> entities = new List<Viewport3DTriangleEntity>();
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<GeometryModel3D> GenerateEmissiveModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities, Color4 emissiveColor)
{
// 创建 MeshBuilder,启用法线生成
var meshBuilder = new MeshBuilder(true, false);
viewport.RenderHost.MSAA = MSAALevel.Maximum;
// 通过实体构建三角形面
foreach (var entity in entities)
{
List<Vector3> vertices = new List<Vector3>() { 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<GeometryModel3D> result = new List<GeometryModel3D>();
result.Add(geometryModel);
return result;
}
public static List<GeometryModel3D> GenerateLineTextModelByEntity(Viewport3DX viewport, List<Viewport3DTriangleEntity> entities)
{
List<GeometryModel3D> result = new List<GeometryModel3D>();
List<Vector3> selFaceVector = new List<Vector3>();
foreach (var entity in entities)
{
selFaceVector.Add(entity.Point1);
selFaceVector.Add(entity.Point2);
selFaceVector.Add(entity.Point3);
}
var edgeLines = new List<Tuple<Vector3, Vector3>>();
var lengths = new List<double>(); // 长度列表
var angles = new List<double>(); // 夹角列表
HashSet<Vector3> uniqueVectors = new HashSet<Vector3>(selFaceVector);
List<Vector3> 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<Vector3, Vector3>(nowItem, nextItem));
// 计算当前线段的长度
lengths.Add((nextItem - nowItem).Length());
}
edgeLines.Add(new Tuple<Vector3, Vector3>(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;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@ -14,6 +14,7 @@
<PackageReference Include="HandyControl" Version="3.5.1" />
<PackageReference Include="HandyControl.Lang.en" Version="3.5.1" />
<PackageReference Include="HandyControls.Lang.zh-CN" Version="3.5.3" />
<PackageReference Include="HelixToolkit.Wpf.SharpDX" Version="2.25.0" />
<PackageReference Include="log4net" Version="3.0.3" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="9.0.0" />
<PackageReference Include="Microsoft.Web.WebView2" Version="1.0.2903.40" />
@ -57,6 +58,18 @@
</Content>
<None Remove="Resource\Other\Json-Mode-Default.xshd" />
<EmbeddedResource Include="Resource\Other\Json-Mode-Default.xshd" />
<None Remove="Resource\DimDemo.txt" />
<Content Include="Resource\DimDemo.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Resource\Images\Temp\DimDemo.txt">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<None Remove="Resource\Images\Temp\left.png" />
<None Remove="Resource\Images\Temp\rtop.png" />
<None Remove="Resource\Images\Temp\top.png" />
<EmbeddedResource Remove="Resource\Images\Temp\**" />
<None Remove="Resource\Images\Temp\**" />
</ItemGroup>
<ItemGroup>
@ -80,6 +93,11 @@
<XamlRuntime>Wpf</XamlRuntime>
<SubType>Designer</SubType>
</Page>
<Page Remove="Resource\Images\Temp\**" />
</ItemGroup>
<ItemGroup>
<Compile Remove="Resource\Images\Temp\**" />
</ItemGroup>
</Project>

@ -38,7 +38,7 @@ public class HomeWindowVM : BaseViewModel
public void ShowDiamlondSelPage(object parameter)
{
BaseControlVM vm = new BaseControlVM(new DiamondSelectVM(), MultilingualHelper.getString("DiamondSelect"));
BaseControlVM vm = new BaseControlVM(new GradingResultVM(null), MultilingualHelper.getString("DetectionResult"));
WindowManager.mainViewModel.Content = vm;
WindowManager.openContent.Add(vm);
}

@ -1,13 +1,27 @@
using System.Data;
using System.Windows.Input;
using HandyControl.Controls;
namespace SparkClient.ViewModel.Grading;
public class GradingResultVM: BaseViewModel
public class GradingResultVM : BaseViewModel
{
private DataTable _dtResults;
public ICommand ChangeNormCommand { get; }
public DataTable DtResults{ get { return _dtResults; } set { _dtResults = value; OnPropertyChanged("DtResults"); } }
/// <summary>
/// 构造
/// </summary>
/// <param name="result">检测结果</param>
public GradingResultVM(object result)
{
ChangeNormCommand = new RelayCommand(ChangeNorm);
_dtResults = new DataTable();
}
@ -17,6 +31,7 @@ public class GradingResultVM: BaseViewModel
/// <param name="norm"></param>
public void ChangeNorm(object norm)
{
}
#region 钻石操作相关

@ -3,10 +3,85 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:hc="https://handyorg.github.io/handycontrol"
xmlns:local="clr-namespace:SparkClient.Views.Grading"
xmlns:hx="http://helix-toolkit.org/wpf/SharpDX"
mc:Ignorable="d"
d:DesignWidth="1000"
d:DesignHeight="600"
>
<Grid Background="Blue">
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Center" FontSize="80">检测结果</TextBlock>
<Grid >
<Grid.ColumnDefinitions>
<ColumnDefinition MinWidth="300" Width="5*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition MinWidth="300" Width="4*" />
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="3" Background="White" Height="50">
</Border>
<Border Grid.Row="1" Grid.Column="0" Background="Aqua">
<DataGrid HeadersVisibility="All" AutoGenerateColumns="False" ItemsSource="{Binding DtResults}">
<DataGrid.Columns>
<DataGridTextColumn Width="120" IsReadOnly="True" Header="" Binding="{Binding Name}"/>
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resAvg}" Binding="{Binding Avg}"/>
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resYuan}" Binding="{Binding Yuan}" />
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resShen}" Binding="{Binding Shen}"/>
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resMin}" Binding="{Binding Min}" />
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resMax}" Binding="{Binding Max}"/>
<DataGridTextColumn Width="Auto" IsReadOnly="True" Header="{DynamicResource resLevel}" Binding="{Binding Level}"/>
<!-- <DataGridTemplateColumn Width="Auto" IsReadOnly="True" Header="操作" > -->
<!-- <DataGridTemplateColumn.CellTemplate> -->
<!-- <DataTemplate> -->
<!-- <Button Command="{Binding DataContext.ChangeNormCommand, RelativeSource={RelativeSource AncestorType=DataGrid}}" CommandParameter="{Binding}">测试</Button> -->
<!-- </DataTemplate> -->
<!-- </DataGridTemplateColumn.CellTemplate> -->
<!-- </DataGridTemplateColumn> -->
</DataGrid.Columns>
</DataGrid>
</Border>
<GridSplitter Grid.Row="1" Grid.Column="1" Width="3" HorizontalAlignment="Stretch"></GridSplitter>
<Border Grid.Row="1" Grid.Column="2" Background="Aquamarine">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<Border Grid.Row="0" Background="Fuchsia" />
<Border Grid.Row="1" BorderThickness="1" CornerRadius="0" BorderBrush="Black" Margin="5">
<hx:Viewport3DX Grid.Column="0" x:Name="Viewport3Dx" MouseLeftButtonDown="Viewport3Dx_OnMouseLeftButtonDown" ClipToBounds="True">
<hx:Viewport3DX.InputBindings>
<KeyBinding Key="B" Command="hx:ViewportCommands.BackView" />
<KeyBinding Key="F" Command="hx:ViewportCommands.FrontView" />
<KeyBinding Key="U" Command="hx:ViewportCommands.TopView" />
<KeyBinding Key="D" Command="hx:ViewportCommands.BottomView" />
<KeyBinding Key="L" Command="hx:ViewportCommands.LeftView" />
<KeyBinding Key="R" Command="hx:ViewportCommands.RightView" />
<KeyBinding Command="hx:ViewportCommands.ZoomExtents" Gesture="Control+E" />
<MouseBinding Command="hx:ViewportCommands.Rotate" Gesture="RightClick" />
<MouseBinding Command="hx:ViewportCommands.Zoom" Gesture="MiddleClick" />
<MouseBinding Command="hx:ViewportCommands.Pan" Gesture="LeftClick" />
</hx:Viewport3DX.InputBindings>
<!-- <hx:DirectionalLight3D x:Name="MainDirectionalLight" /> -->
<!-- <hx:PointLight3D x:Name="MainPointLight" /> -->
<!-- <hx:AmbientLight3D x:Name="AmbientLight" /> -->
<!-- <hx:AxisPlaneGridModel3D -->
<!-- AutoSpacing="false" -->
<!-- RenderShadowMap="true" -->
<!-- Offset="-65" /> -->
</hx:Viewport3DX>
</Border>
</Grid>
</Border>
</Grid>
</Border>

@ -1,11 +1,122 @@
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Media3D;
using HelixToolkit.Wpf.SharpDX;
using SharpDX;
using SparkClient.Model.Entity;
using SparkClient.Model.Helper;
using SparkClient.ViewModel.Grading;
using GeometryModel3D = HelixToolkit.Wpf.SharpDX.GeometryModel3D;
using MeshGeometry3D = HelixToolkit.Wpf.SharpDX.MeshGeometry3D;
using PerspectiveCamera = HelixToolkit.Wpf.SharpDX.PerspectiveCamera;
namespace SparkClient.Views.Grading;
public partial class GradingResult
{
List<Viewport3DTriangleEntity> triangles = new List<Viewport3DTriangleEntity>();
List<GeometryModel3D> mouseAddModels = new List<GeometryModel3D>();
public GradingResult()
{
InitializeComponent();
DataContext = new GradingResultVM(null);
this.Viewport3Dx.EffectsManager = new DefaultEffectsManager();
this.Viewport3Dx.Camera = new PerspectiveCamera()
{
Position = new Point3D(0, 0, 5),
LookDirection = new Vector3D(0, 0, -1),
UpDirection = new Vector3D(0, 1, 0),
FarPlaneDistance = 1000,
NearPlaneDistance = 0.1
};
Loaded += (sender, args) =>
{
var entities = Viewport3DHelper.InitDemo(Viewport3Dx);
triangles.AddRange(entities);
};
}
/// <summary>
/// 点击模型
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void Viewport3Dx_OnMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
// 获取鼠标点击位置
var mousePosition = e.GetPosition(Viewport3Dx);
// 查找鼠标点击的 3D 对象
var hits = Viewport3Dx.FindHits(mousePosition);
// 如果没有命中任何 3D 对象
if (hits == null || hits.Count == 0)
{
Console.WriteLine("未点击到任何模型");
foreach (var item in mouseAddModels)
{
Viewport3Dx.Items.Remove(item);
}
mouseAddModels.Clear();
return;
}
// 获取第一个命中的对象
var hit = hits[0];
// 检查是否是 MeshGeometryModel3D
if (hit.ModelHit is MeshGeometryModel3D modelHit)
{
foreach (var item in mouseAddModels)
{
Viewport3Dx.Items.Remove(item);
}
mouseAddModels.Clear();
// 获取几何信息
var geometry = modelHit.Geometry as 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];
string strPoint = vertex1.X + ","+ vertex1.Y+","+ vertex1.Z + ";"+ vertex2.X + ","+ vertex2.Y + ","+ vertex2.Z+";"+vertex3.X + ","+ vertex3.Y + ","+ vertex3.Z;
string triangleCode = Viewport3DTriangleEntity.GenerateMD5Hash(strPoint);
//命中实体
Viewport3DTriangleEntity res = triangles.Find(e => triangleCode.Equals(e.TriangleCode));
if (res == null)
return;
//命中面
List<Viewport3DTriangleEntity> facet = triangles.Where(e => res.PlaneCode.Equals(e.PlaneCode)).ToList();
var colorFacet = new Color4(1.0f, 1.0f, 0.0f, 1.0f);
var data1 = Viewport3DHelper.GenerateEmissiveModelByEntity(Viewport3Dx,facet,colorFacet);
mouseAddModels.AddRange(data1);
//命中面标线
var data2 = Viewport3DHelper.GenerateLineTextModelByEntity(Viewport3Dx, facet);
mouseAddModels.AddRange(data2);
//命中同类面
List<Viewport3DTriangleEntity> facetType = triangles.Where(e => res.PlaneType.Equals(e.PlaneType)).ToList();
//排除自己
facet.ForEach(e=>facetType.Remove(e));
var colorFacetType = new Color4(0.9f, 0.9f, 0.7f, 1.0f);
var data3 = Viewport3DHelper.GenerateEmissiveModelByEntity(Viewport3Dx,facetType,colorFacetType);
mouseAddModels.AddRange(data3);
}
}
else
{
Console.WriteLine("点击的对象不是 MeshGeometryModel3D");
}
}
}
Loading…
Cancel
Save