Files
MsAddIns/CenterCurveAddins/TunnelLengthHelper.cs
2026-02-28 21:01:57 +08:00

599 lines
25 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using Bentley.DgnPlatformNET;
using Bentley.DgnPlatformNET.DgnEC;
using Bentley.DgnPlatformNET.Elements;
using Bentley.ECObjects.Instance;
using Bentley.ECObjects.Schema;
using Bentley.GeometryNET;
using Bentley.MstnPlatformNET;
using ClrCode;
namespace CenterCurveAddins
{
/// <summary>
/// 凸包算法
/// </summary>
internal class ConvexHull
{
public static HashSet<DPoint3d> Compute(HashSet<DPoint3d> DPoint3ds)
{
if (DPoint3ds.Count <= 1)
return new HashSet<DPoint3d>(DPoint3ds);
// 按x升序排列x相同则按y升序
var sortedDPoint3ds = DPoint3ds.OrderBy(p => p.X).ThenBy(p => p.Y).ToList();
List<DPoint3d> lowerHull = new List<DPoint3d>();
// 构造下凸包
foreach (var DPoint3d in sortedDPoint3ds)
{
while (lowerHull.Count >= 2 && Cross(lowerHull[lowerHull.Count - 2], lowerHull[lowerHull.Count - 1], DPoint3d) <= 0)
{
lowerHull.RemoveAt(lowerHull.Count - 1);
}
lowerHull.Add(DPoint3d);
}
List<DPoint3d> upperHull = new List<DPoint3d>();
// 构造上凸包,逆序处理
for (int i = sortedDPoint3ds.Count - 1; i >= 0; i--)
{
DPoint3d DPoint3d = sortedDPoint3ds[i];
while (upperHull.Count >= 2 && Cross(upperHull[upperHull.Count - 2], upperHull[upperHull.Count - 1], DPoint3d) <= 0)
{
upperHull.RemoveAt(upperHull.Count - 1);
}
upperHull.Add(DPoint3d);
}
// 合并上下凸包,并移除重复点
lowerHull.AddRange(upperHull);
if (lowerHull.Count > 0)
lowerHull.RemoveAt(lowerHull.Count - 1); // 移除上凸包的第一个点(与下凸包最后一个点重复)
return new HashSet<DPoint3d>(lowerHull);
}
// 计算叉积:(b - a) × (c - b)
private static double Cross(DPoint3d a, DPoint3d b, DPoint3d c)
{
return (b.X - a.X) * (c.Y - b.Y) - (b.Y - a.Y) * (c.X - b.X);
}
}
/// <summary>
/// 隧道网格投影到中线的帮助类
/// </summary>
public class TunnelLengthHelper
{
/// <summary>
/// 分割具有间隙的网格,引用算量源码的方法
/// </summary>
/// <param name="eleMeshDiv"></param>
/// <param name="listEleId">可拆分后的网格,或不可拆分的原网格</param>
/// <returns></returns>
private static bool DivMesh(Element eleMeshDiv, out List<int> listEleId)
{
listEleId = new List<int>();
int[] iArrMeshDivide = { };//元素id
bool bRet = ClrCode.GeneralToolClr.DivideMesh(eleMeshDiv.ElementId.GetHashCode(), ref iArrMeshDivide);
if (bRet)
{
if (iArrMeshDivide.Count() > 1)//如果拆分后的数量>1则证明原曲面与拆分后曲面是不一样的
{
foreach (var iEle in iArrMeshDivide)
{
listEleId.Add(iEle);
ulong lEle = (ulong)iEle;
var id = new ElementId(ref lEle);
var elem = Session.Instance.GetActiveDgnModel().FindElementById(id);
DeliverItemTypesTo(eleMeshDiv, elem);
}
//删除原始元素
eleMeshDiv?.DeleteFromModel();
return true;
}
else
{
listEleId.Add((int)eleMeshDiv.ElementId);
foreach (var iEle in iArrMeshDivide)
{
ulong lEle = (ulong)iEle;
var id = new ElementId(ref lEle);
var elem = Session.Instance.GetActiveDgnModel().FindElementById(id);
elem?.DeleteFromModel();
}
}
}
return false;
}
/// <summary>
/// 获取网格投影到XOY平面的边界点
/// </summary>
/// <param name="eleMesh"></param>
/// <returns></returns>
private static HashSet<DPoint3d> GetBoundariesFromMeshByProject(Element eleMesh)
{
var listPtNew = new HashSet<DPoint3d>();
try
{
long lElementIDProject = 0;
//平面投影
bool bRet = GeneralToolClr.ProjectMeshToXY(eleMesh.ElementId.GetHashCode(), ref lElementIDProject);
if (!bRet)
{
return listPtNew;
}
//平面投影元素
ElementId eleIdProject = new ElementId(ref lElementIDProject);
Element eleProject = Session.Instance.GetActiveDgnModel().FindElementById(eleIdProject);
if (eleProject == null)
{
return null;
}
ChildElementEnumerator enumeratorPro = new ChildElementEnumerator(eleProject);
while (enumeratorPro.MoveNext())
{
ShapeElement shapeEle = enumeratorPro.Current as ShapeElement;
if (shapeEle == null)
{
MessageCenter.Instance.ShowInfoMessage($"shapeEle:{shapeEle.ElementId.GetHashCode()}", "未找到子元素ShapeElement", false);
continue;
}
CurveVector curVector = shapeEle.GetCurveVector();
if (curVector == null)
{
MessageCenter.Instance.ShowInfoMessage($"shapeEle:{shapeEle.ElementId.GetHashCode()}", "未找到ShapeElement的边线", false);
}
foreach (var itemCur in curVector)
{
if (itemCur.GetCurvePrimitiveType() == CurvePrimitive.CurvePrimitiveType.LineString)
{
var pts = new List<DPoint3d>();
if (itemCur.TryGetLineString(pts) && pts.Count > 2)
{
foreach (var item in pts)
{
listPtNew.Add(item);
}
//listPtNew = new HashSet<DPoint3d>(pts);
}
else
{
MessageCenter.Instance.ShowInfoMessage($"shapeEle:{shapeEle.ElementId.GetHashCode()}, pts:{pts.Count}", "", false);
}
}
}
}
listPtNew = ConvexHull.Compute(listPtNew);
eleProject.DeleteFromModel();
return listPtNew;
}
catch (Exception)
{
return listPtNew;
}
}
/// <summary>
/// 通过点分布获取中心线
/// </summary>
/// <param name="listLineMilGeoData">线元素</param>
/// <param name="listPtAll">拍平后的所有平面点</param>
/// <returns></returns>
private static Element GetCenterLine(List<Element> listLineMilGeoData, HashSet<DPoint3d> listPtAll)
{
Element targetLine = null;
//随机取点
if (listPtAll == null || listPtAll.Count == 0)
{
return targetLine;
}
var pt3dMid = listPtAll.FirstOrDefault();
double dDisMin = double.MaxValue;
//找到距离点最近的线元素
foreach (var item in listLineMilGeoData)
{
CurveVector cv = CurvePathQuery.ElementToCurveVector(item);
if (cv == null)
{
continue;
}
//平面距离
CurveLocationDetail curLocDetail = cv.ClosestPointBoundedXY(pt3dMid);
if (curLocDetail == null)
{
continue;
}
//平面距离
double dDis = pt3dMid.DistanceXY(curLocDetail.Point);
if (dDis < dDisMin)
{
dDisMin = dDis;
targetLine = item;
}
}
return targetLine;
}
/// <summary>
/// 获取网格投影在最近的中线投影长度
/// </summary>
/// <param name="meshHeaderEle">mesh实体</param>
/// <param name="centerLineLevel">中心线所在图层如DG-ZZX-ZX</param>
/// <returns></returns>
private static double GetMeshLength(MeshHeaderElement meshHeaderEle, string centerLineLevel)
{
if (meshHeaderEle == null)
{
return 0.0;
}
DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
var levelCache = dgnModel.GetLevelCache();
//过滤获取线条元素,即所有中心线
var listLineMilGeoData = dgnModel.GetGraphicElements()
.Where(
e => e.IsValid &&
!e.IsInvisible &&
levelCache.GetLevel(e.LevelId)?.DisplayName.StartsWith(centerLineLevel) == true &&
IsCurve(e))
.ToList();
//曲面拍平的所有点
HashSet<DPoint3d> listPtAll = GetBoundariesFromMeshByProject(meshHeaderEle);
//通过点分布获取中心线
var targetLine = GetCenterLine(listLineMilGeoData, listPtAll);
if (targetLine != null)
{
CurveVector curveVector = CurvePathQuery.ElementToCurveVector(targetLine);
//找到曲面拍平的 所有点 到 平面距离最近中心线上的点
var locs = listPtAll.Select(curveVector.ClosestPointBoundedXY);
//StringBuilder sb = new StringBuilder();
//foreach (var item in locs)
//{
// sb.AppendLine($"{item.Point}");
//}
//File.WriteAllText(
// Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "结果.txt"),
// sb.ToString());
//locs = locs.Distinct((p1, p2) => p1.Point.Equals(p2.Point));
//var minFraction = locs.Min(loc => loc.Fraction);
//var maxFraction = locs.Max(loc => loc.Fraction);
//foreach (var item in locs)
//{
// //var z = item.Point + DPoint3d.UnitZ * 100000;
// var p = curveVector.ClosestPointBoundedXY(item.Point);
// var pointString = new LineStringElement(dgnModel, null, new DPoint3d[2] { item.Point, p.Point });
// pointString.AddToModel();
//}
var ordered = locs.OrderBy(loc => curveVector.CurveLocationDetailIndex(loc)).ThenBy(loc => loc.Fraction);
var min = ordered.FirstOrDefault();
var max = ordered.LastOrDefault();
var minIndex = curveVector.CurveLocationDetailIndex(min);
var maxIndex = curveVector.CurveLocationDetailIndex(max);
var minCurve = curveVector.GetPrimitive((int)minIndex);
var maxCurve = curveVector.GetPrimitive((int)maxIndex);
//拿到最大最小线段距离
minCurve.SignedDistanceBetweenFractions(0, min.Fraction, out var dist);
maxCurve.SignedDistanceBetweenFractions(0, max.Fraction, out var dist1);
//var l = (dist1 - dist) / Session.Instance.GetActiveDgnModel().GetModelInfo().UorPerMeter > 5000;
if (minIndex == maxIndex) //是否在同一条线上
{
return (dist1 - dist) / Session.Instance.GetActiveDgnModel().GetModelInfo().UorPerMeter;
}
else if (maxIndex > minIndex)
{
minCurve.Length(out var minLength);
//maxCurve.Length(out var maxlength);
var minDis = minLength - dist;//前置曲线的后半段
//var maxDis = max.Fraction * maxlength;//后置曲线的前半段
var length = minDis + dist1;
if (maxIndex - minIndex > 1)//不连续,中间还有其他的线
{
for (var i = minIndex + 1; i < maxIndex; i++)
{
curveVector.GetPrimitive((int)i).Length(out var l);
length += l;
}
}
return length / Session.Instance.GetActiveDgnModel().GetModelInfo().UorPerMeter;
}
}
return 0.0;
}
/// <summary>
/// 判断元素是否是曲线
/// </summary>
/// <param name="elem">元素</param>
/// <returns>是否是曲线</returns>
private static bool IsCurve(Element elem)
{
return elem is LineElement ||
elem is ComplexStringElement ||
elem is LineStringBaseElement ||
elem is EllipseElement ||
elem is ComplexShapeElement ||
elem is BSplineCurveElement;
}
/// <summary>
/// 获取ItemType包含多个CustomProperty
/// </summary>
/// <param name="itemLibName"></param>
/// <param name="itemTypeName"></param>
/// <returns></returns>
private static ItemType GetItemType(string itemLibName, string itemTypeName)
{
DgnFile dgnFile = Session.Instance.GetActiveDgnFile();
//根据声明名称获取对应的ItemTypeLibrary
ItemTypeLibrary itemTypeLibrary = ItemTypeLibrary.FindByName(itemLibName, dgnFile);
return itemTypeLibrary?.GetItemTypeByName(itemTypeName);
}
/// <summary>
/// 挂接ItemType到元素
/// </summary>
/// <param name="elem"></param>
/// <param name="itemLibName"></param>
/// <param name="itemTypeName"></param>
private static void AttachItemType(Element elem, string itemLibName, string itemTypeName)
{
DgnFile dgnFile = Session.Instance.GetActiveDgnFile();//获得当前激活的文件
DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
//根据声明名称获取对应的ItemType
ItemType itemType = GetItemType(itemLibName, itemTypeName);
CustomItemHost host = new CustomItemHost(elem, true);//声明元素的CustomItemHost
//实例化
IDgnECInstance item = host.ApplyCustomItem(itemType, true);//对CustomItemHost添加ItemType得到EC实例
//EditParameterDefinitions defs = EditParameterDefinitions.GetForModel(dgnModel);//获得模型中的参数定义
//DgnECInstanceEnabler enabler = DgnECManager.Manager.ObtainInstanceEnabler(dgnFile, itemType.ECClass);//获得文件中使用指定EC类的EC实例
//if (null != enabler && enabler.SupportsCreateInstanceOnElement)//判断是否是否支持对元素挂接EC实例
// defs.SetDomainParameters(enabler.SharedWipInstance);//对参数定义设置域参数
item.ScheduleChanges(elem);//对元素更新ItemType信息
elem.ReplaceInModel(elem);
}
/// <summary>
/// 元素之间传递ItemTypes及值
/// </summary>
/// <param name="source"></param>
/// <param name="target"></param>
private static void DeliverItemTypesTo(Element source, Element target)
{
CustomItemHost host = new CustomItemHost(source, true);//声明元素的CustomItemHost
var instances = host.CustomItems;
//List<ItemType> list = new List<ItemType>();
foreach (var instance in instances)
{
var ecClass = instance.ClassDefinition;//ItemTypes
var lib = ecClass.Schema;//ItemTypesLibrary
AttachItemType(target, lib.DisplayLabel, ecClass.DisplayLabel);
if (instance.ContainsValues)
{
foreach (var propValue in instance)
{
if (propValue.TryGetNativeValue(out var value))
{
var prop = propValue.Property;
SetECPropertyValues(target, prop.DisplayLabel, value);
}
}
}
}
}
/// <summary>
/// 通过属性名设置属性值,多个同名属性会同时设置
/// </summary>
/// <param name="elem">元素</param>
/// <param name="propName">属性名</param>
/// <returns></returns>
private static void SetECPropertyValues(Element elem, string propName, object value)
{
var instances = new CustomItemHost(elem, false).CustomItems;
try
{
foreach (var instance in instances)
{
instance.SetValue(propName, value);
instance.WriteChanges();
}
}
catch (Exception)
{
}
}
/// <summary>
/// 获取所有属性
/// </summary>
/// <param name="element"></param>
private static List<IECPropertyValue> GetProperyValues(Element element, string propName)
{
if (null == element)
{
throw new ArgumentNullException(nameof(element), "所提供ElemId无效");
}
List<IECPropertyValue> eCProperties = new List<IECPropertyValue>();
DgnECManager ecManager = DgnECManager.Manager;
DgnECInstanceCollection instCol = ecManager.GetElementProperties(element, ECQueryProcessFlags.SearchAllClasses);
foreach (IDgnECInstance inst in instCol)
{
IEnumerator<IECProperty> propertyEnum = inst.ClassDefinition.GetEnumerator();
while (propertyEnum.MoveNext())
{
IECPropertyValue propertyValue = inst.GetPropertyValue(propertyEnum.Current.Name);
if (propertyValue == null)
{
continue;
}
if (propertyValue.IsArray)
{
IECArrayValue arrayVal = propertyValue as IECArrayValue;
if (arrayVal.Count >= 1)
propertyValue = arrayVal[0];
}
var prop = propertyEnum.Current.Name;
ECNameValidation.DecodeFromValidName(ref prop);
if (propName == prop)
{
eCProperties.Add(propertyValue);
}
}
}
return eCProperties;
}
/// <summary>
/// 获取元素的图层,如果是一般单元,则按第一个子元素来获取
/// </summary>
/// <param name="element"></param>
/// <returns></returns>
private static LevelHandle GetLevel(Element element)
{
ElementPropertiesGetter getter = new ElementPropertiesGetter(element);
var lvlcache = Session.Instance.GetActiveDgnFile().GetLevelCache();
//一般单元
if (element.GetChildren().Count() > 0)
{
foreach (var elem in element.GetChildren())
{
if (elem is DisplayableElement && !elem.IsInvisible)
{
return GetLevel(elem);
}
}
}
return lvlcache.GetLevel(getter.Level, true);
}
/// <summary>
/// 初支按TC-400-结构组成属性获取上部或下部或全部的元素
/// </summary>
/// <param name="elements"></param>
/// <returns></returns>
private static IEnumerable<Element> GetElementsFiltered(IEnumerable<Element> elements)
{
var groups = elements.GroupBy(e => GetProperyValues(e, "TC-400-结构组成").FirstOrDefault(p => !string.IsNullOrEmpty(p.StringValue))?.StringValue?.Trim());
foreach (var item in groups)
{
if (item.Key == "全部" || item.Key == "上部" || item.Key == "下部")
{
return item.ToList();
}
}
return new List<Element>();
}
/// <summary>
/// 获取每个初支或二衬拱墙图层上的网格元素在中心线上的投影长度
/// </summary>
/// <param name="centerLineLevel">中心线图层所在图层</param>
/// <param name="meshPropertyName">实体属性名,如果存在初支或者拱墙上下部的情况,需要</param>
/// <param name="meshPropertyValue">实体属性值</param>
/// <returns>网格元素和投影长度的集合</returns>
/// <remarks></remarks>
public static Dictionary<Element, double> GetMeshProjectLength(string centerLineLevel)
{
Dictionary<Element, double> meshLengthDictionary = new Dictionary<Element, double>();
DgnModel dgnModel = Session.Instance.GetActiveDgnModel();
var levelCache = dgnModel.GetLevelCache();
try
{
//获取用于计算长度的模型
List<Element> models = new List<Element>();
//初支元素
var primaryLinings = dgnModel.GetGraphicElements()
.Where(e => e.IsValid
&& !e.IsInvisible
&& GetLevel(e).DisplayName.Trim().ToUpper() == "JG_XTZH_PH");
//primaryLinings = GetElementsFiltered(primaryLinings);
//包含初支Mesh
if (primaryLinings.Any())
{
models.AddRange(primaryLinings);
}
else//查找二衬
{
//二衬元素仰拱
var secondaryLinings = dgnModel.GetGraphicElements()
.Where(e => e.IsValid && !e.IsInvisible && (GetLevel(e)?.DisplayName.Trim().ToUpper() == "JG_CQ_GQ"));
if (secondaryLinings.Any())
{
models.AddRange(secondaryLinings);
}
}
if (!models.Any())
{
MessageCenter.Instance.ShowInfoMessage("不存在可以用于计算长度的元素的mesh元素", "", false);
return meshLengthDictionary;
}
foreach (var model in models)
{
if (model is MeshHeaderElement)//网格的情况
{
//需要拆分则subMeshIds为拆分后的网格ID否则为原网格ID即只有一个
DivMesh(model, out List<int> subMeshIds);
//遍历网格元素,获取元素到中线的投影长度
foreach (var itemIds in subMeshIds)
{
long lEleId = itemIds;
ElementId eleIdMeshTest = new ElementId(ref lEleId);
Element elem = Session.Instance.GetActiveDgnModel().FindElementById(eleIdMeshTest);
var subLength = GetMeshLength(elem as MeshHeaderElement, centerLineLevel);
meshLengthDictionary.Add(elem, subLength);
}
}
else//不是网格元素,需要转为网格计算
{
//可以拆分
//将非网格的元素转成网格。
MyMeshProcessor tool = new MyMeshProcessor();
ElementGraphicsOutput.Process(model, tool);
var resultLength = tool.resultList.Sum(elem => GetMeshLength(elem, centerLineLevel));
foreach (var item in tool.resultList)
{
item.DeleteFromModel();
}
meshLengthDictionary.Add(model, resultLength);
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
return meshLengthDictionary;
}
}
}