最近遇到一个需求,指定两个配电箱,然后找到两个配电箱之间最短的桥架路径。运用了Dijkstra算法去计算最短路径,以配电箱实体、三通、四通为节点,简化中间弯头计算的方式。
背景
选择起点和终点的配电箱,找到最短的桥架路径
代码文件
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;
namespace RevitTest.Short
{
[Transaction(TransactionMode.Manual)]
public class ShortCmd : IExternalCommand
{
/// <summary>
/// 所有结点集合
/// </summary>
List<NodeModel> _allNodelModelList = new List<NodeModel>();
/// <summary>
/// 所有结点连接集合
/// </summary>
List<NodeLink> _allNodeLinkList = new List<NodeLink>();
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument;
Document doc = uidoc.Document;
Reference selOneRef = null;
Reference selTwoRef = null;
try
{
selOneRef = uidoc.Selection.PickObject(ObjectType.Element, "选择起始配电箱");
selTwoRef = uidoc.Selection.PickObject(ObjectType.Element, "选择终点配电箱");
}
catch
{
}
FamilyInstance targetFI = doc.GetElement(selOneRef) as FamilyInstance;
FamilyInstance endFI = doc.GetElement(selTwoRef) as FamilyInstance;
NodeInit(doc, targetFI);
//先构造所有的点数据
List<NodeLink> shortNLList = new List<NodeLink>();
for (int i = 0; i < _allNodelModelList.Count; i++)
{
NodeLink nwLink = new NodeLink();
nwLink.OneModel = _allNodelModelList[0];
nwLink.TwoModel = _allNodelModelList[i];
if (i == 0) nwLink.LinkLenght = 0;
else nwLink.LinkLenght = double.MaxValue;
shortNLList.Add(nwLink);
}
while (true)
{
//如果终点给标记的时候就弹出了
if (shortNLList.FirstOrDefault(x => x.TwoModel.Equals(endFI)).IsTag == true) break;
NodeLink oneLink = shortNLList.Where(x => !x.IsTag).OrderBy(x => x.LinkLenght).FirstOrDefault();
if (oneLink == null) break;
List<NodeLink> linkList = _allNodeLinkList.FindAll(x => x.OneModel.Equals(oneLink.TwoModel) || x.TwoModel.Equals(oneLink.TwoModel));
foreach (var nl in linkList)
{
double lenght = nl.LinkLenght + oneLink.LinkLenght;
NodeModel orderNM = nl.OneModel.Equals(oneLink.TwoModel) ? nl.TwoModel : nl.OneModel;
NodeLink orderNL = shortNLList.FirstOrDefault(x => x.TwoModel.Equals(orderNM));
if (lenght < orderNL.LinkLenght)
{
orderNL.LinkLenght = lenght;
orderNL.LinkMEPCurveList = new List<MEPCurve>();
orderNL.LinkMEPCurveList.AddRange(oneLink.LinkMEPCurveList);
orderNL.LinkMEPCurveList.AddRange(nl.LinkMEPCurveList);
}
}
oneLink.IsTag = true;
}
NodeLink findNL = shortNLList.FirstOrDefault(x => x.TwoModel.Equals(endFI));
uidoc.Selection.SetElementIds(findNL.LinkMEPCurveList.Select(x => x.Id).ToList());
return Result.Succeeded;
}
/// <summary>
/// 结点数据初始化
/// </summary>
/// <param name="doc"></param>
/// <param name="targetFI"></param>
public void NodeInit(Document doc, FamilyInstance targetFI)
{
NodeModel nm = new NodeModel(targetFI);
if (_allNodelModelList.FirstOrDefault(x => x.Equals(nm)) != null) return;
//拿到已经连接的连接器
List<Connector> connList = targetFI.MEPModel.ConnectorManager.Connectors.OfType<Connector>().Where(x => x.IsConnected && x.AllRefs.Cast<Connector>().FirstOrDefault(m => m.Owner.Id == targetFI.Id) == null).ToList();
nm.ConnectorList = connList;
_allNodelModelList.Add(nm);
while (true)
{
NodeModel oneNM = _allNodelModelList.FirstOrDefault(x => x.ConnectorList.Count != 0);
if (oneNM == null) break;
for (int i = 0; i < oneNM.ConnectorList.Count; i++)
{
Connector con = oneNM.ConnectorList[i];
List<MEPCurve> mepCurveList = new List<MEPCurve>();
FamilyInstance nodeFI = null;
ElementId endElementID = null;
//找到对应连接的连接件
List<Connector> linkConList = con.AllRefs.Cast<Connector>().Where(x => x.Owner.Id != con.Owner.Id).ToList();
//添加连接到的构件
var linkElement = doc.GetElement(linkConList.FirstOrDefault().Owner.Id);
if (linkElement is FamilyInstance)
{
mepCurveList.AddRange(GetLinkMepCurveList(linkElement as FamilyInstance, oneNM.NodeFamilyInstance.Id, out nodeFI, out endElementID));
}
else
{
mepCurveList.AddRange(GetLinkMepCurveList(linkElement as MEPCurve, oneNM.NodeFamilyInstance.Id, out nodeFI, out endElementID));
}
#region 计算长度
double mepLenght = 0;
foreach (var item in mepCurveList)
{
try
{
mepLenght += (item.Location as LocationCurve).Curve.Length;
}
catch
{
}
}
#endregion
NodeLink nodeLink = new NodeLink();
nodeLink.OneModel = oneNM;
nodeLink.TwoModel = null;
nodeLink.LinkMEPCurveList = mepCurveList;
nodeLink.LinkLenght = mepLenght;
if (nodeFI != null)
{
NodeModel nextNM = _allNodelModelList.FirstOrDefault(x => x.Equals(nodeFI));
if (nextNM == null)
{
nextNM = new NodeModel(nodeFI);
//拿到所有构件的连接件
nextNM.ConnectorList = nodeFI.MEPModel.ConnectorManager.Connectors.Cast<Connector>().Where(x => x.IsConnected).ToList();
_allNodelModelList.Add(nextNM);
}
nodeLink.TwoModel = nextNM;
//处理终点的连接器
Connector nextEndConn = nextNM.ConnectorList.FirstOrDefault(x => x.AllRefs.OfType<Connector>().FirstOrDefault(m => m.Owner.Id == endElementID) != null);
nextNM.ConnectorList.Remove(nextEndConn);
nextNM.UserConnectIndexDic.Add(nextEndConn, _allNodeLinkList.Count - 1);
//处理起始结点的连接器
_allNodeLinkList.Add(nodeLink);
oneNM.UserConnectIndexDic.Add(con, _allNodeLinkList.Count - 1);
}
oneNM.ConnectorList.RemoveAt(i);
i--;
}
}
}
/// <summary>
/// 计算单条机电线管设备的完整路径
/// </summary>
/// <param name="mepCurve"></param>
/// <param name="linkId"></param>
/// <param name="fi"></param>
/// <param name="endConnMCID"></param>
/// <returns></returns>
public List<MEPCurve> GetLinkMepCurveList(MEPCurve mepCurve, ElementId linkId, out FamilyInstance fi, out ElementId endConnMCID)
{
List<MEPCurve> mepCurveList = new List<MEPCurve> { mepCurve };
//拿到两端连接件
List<Connector> connertorList = mepCurve.ConnectorManager.Connectors.Cast<Connector>().Where(x => x.IsConnected && x.AllRefs.Cast<Connector>().FirstOrDefault(m => m.Owner.Id == linkId) == null).ToList();
Connector con = connertorList.FirstOrDefault();
if (con == null)
{
fi = null;
endConnMCID = null;
return mepCurveList;
}
//找到对应连接的连接件
List<Connector> linkConList = con.AllRefs.Cast<Connector>().Where(x => x.Owner.Id != con.Owner.Id).ToList();
//添加连接到的构件
var linkFitting = mepCurve.Document.GetElement(linkConList.FirstOrDefault().Owner.Id) as FamilyInstance;
//拿到管件连接的所有管线
List<MEPCurve> linkMC = GetLinkMepCurveList(linkFitting, mepCurve.Id, out fi, out endConnMCID);
mepCurveList.AddRange(linkMC);
return mepCurveList;
}
/// <summary>
/// 计算单条机电线管设备的完整路径
/// </summary>
/// <param name="fitting"></param>
/// <param name="linkId"></param>
/// <param name="nodeFI"></param>
/// <param name="endConnMCID"></param>
/// <returns></returns>
public List<MEPCurve> GetLinkMepCurveList(FamilyInstance fitting, ElementId linkId, out FamilyInstance nodeFI, out ElementId endConnMCID)
{
List<MEPCurve> mepCurveList = new List<MEPCurve>();
nodeFI = null;
endConnMCID = null;
//拿到所有构件的连接件
var fittingLinkConList = fitting.MEPModel.ConnectorManager.Connectors.Cast<Connector>().Where(x => x.IsConnected && x.AllRefs.Cast<Connector>().FirstOrDefault(m => m.Owner.Id == linkId) == null);
//如果遇到有分支的,直接返回
if (fittingLinkConList.Count() >= 2 || fittingLinkConList.Count() == 0)
{
nodeFI = fitting;
endConnMCID = linkId;
return mepCurveList;
}
foreach (var con in fittingLinkConList)
{
//找到对应连接的连接件
List<Connector> linkConList = con.AllRefs.Cast<Connector>().Where(x => x.Owner.Id != con.Owner.Id).ToList();
//添加连接到的构件
var linkElement = fitting.Document.GetElement(linkConList.FirstOrDefault().Owner.Id);
if (linkElement is FamilyInstance)
{
mepCurveList.AddRange(GetLinkMepCurveList(linkElement as FamilyInstance, fitting.Id, out nodeFI, out endConnMCID));
}
else
{
mepCurveList.AddRange(GetLinkMepCurveList(linkElement as MEPCurve, fitting.Id, out nodeFI, out endConnMCID));
}
}
return mepCurveList;
}
}
/// <summary>
/// 结点类,三通或者四通为一个结点,选择的起点和终点也是一个结点
/// </summary>
public class NodeModel : IEquatable<NodeModel>, IEquatable<FamilyInstance>
{
/// <summary>
/// 结点构件
/// </summary>
public FamilyInstance NodeFamilyInstance { get; set; }
/// <summary>
/// 未使用连接器集合
/// </summary>
public List<Connector> ConnectorList { get; set; } = new List<Connector>();
/// <summary>
/// 使用过的连接器和对应的连接条索引
/// </summary>
public Dictionary<Connector, int> UserConnectIndexDic { get; set; } = new Dictionary<Connector, int>();
public NodeModel()
{
}
public NodeModel(FamilyInstance fi)
{
NodeFamilyInstance = fi;
}
public bool Equals(NodeModel other)
{
try
{
if (other.NodeFamilyInstance == null || this.NodeFamilyInstance == null) return false;
return other.NodeFamilyInstance.Id.IntegerValue == this.NodeFamilyInstance.Id.IntegerValue;
}
catch (Exception)
{
return false;
}
}
public bool Equals(FamilyInstance other)
{
try
{
if (other == null || this.NodeFamilyInstance == null) return false;
return other.Id.IntegerValue == this.NodeFamilyInstance.Id.IntegerValue;
}
catch (Exception)
{
return false;
}
}
}
/// <summary>
/// 两个结点直接相连的类
/// </summary>
public class NodeLink
{
/// <summary>
/// 第一个结点
/// </summary>
public NodeModel OneModel { get; set; }
/// <summary>
/// 第二个结点
/// </summary>
public NodeModel TwoModel { get; set; }
/// <summary>
/// 两个结点之间的线集合
/// </summary>
public List<MEPCurve> LinkMEPCurveList { get; set; } = new List<MEPCurve>();
/// <summary>
/// 连接的长度
/// </summary>
public double LinkLenght { get; set; } = 0;
/// <summary>
/// 是否标记
/// </summary>
public bool IsTag { get; set; } = false;
}
}
步骤讲解
1.构建节点
节点:实体构件、三通、四通
节点筛选的原则:如果一个实体构件的连接器除前连接前管道外,还有两个以上(包含两个)的连接器时,判断为节点,结束一条线的循环。
节点属性(NodeModel)
属性 | 备注 |
---|---|
NodeFamilyInstance | 节点构件 |
ConnectorList | 未使用的连接器集合 |
UserConnectIndexDic | 字典类型,用来记录对应的连接器和索引 |
思路:
a.以起点为开始构建循环,然后返回一个FamilyInstance,通过查找总的节点判断是否存在当前FamilyInstance的节点,如果没有需要新建加入到总的节点集合中。
b.循环的结束点在所有的节点的连接器全部都用完时,就跳出。
c.因此可以得到9个节点,和对应的连接长度NodelLink实例
2. Dijkstra算法
具体思路可以参考以下的博客:
最短路径算法
下面是我简单的理解和总结
a.先简化上面的图形
b.根据节点和起点来构建一个NodeLink实例,用来记录具体的起点到达每个节点的距离。然后找到和起点相连的节点,选择距离最短的点为下一个起点,并且把旧起点标记为已经使用过。然后每次的循环都是找所有节点中没有标记使用过的节点,并且是距离最短的为下一个节点。这里可能有点绕,通过图片来讲解
找到最近点B,然后A标记为使用过.
以B为起点,去修改与B相连的点的数值。数值替换的原则是,起点到改点的距离小于记录的距离时,附上新值
把B标记为已经使用,找下一个距离最近未使用过的点C,以C为新起点去更新距离数值
通过不断循环,到最后连接终点的点都被标记过的时候,就是整个循环路径寻找结束的时候,这样子就可以找出最短的路径了
写在最后
用来测试的样例较为简单,没有经过大量的项目推敲,可以交给大家去测试和修改,主要是提供一个解题的思路,希望可以帮到你~~