Revit二次开发小技巧(十六)寻找最短路径

最近遇到一个需求,指定两个配电箱,然后找到两个配电箱之间最短的桥架路径。运用了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为新起点去更新距离数值
在这里插入图片描述
通过不断循环,到最后连接终点的点都被标记过的时候,就是整个循环路径寻找结束的时候,这样子就可以找出最短的路径了
在这里插入图片描述

写在最后

用来测试的样例较为简单,没有经过大量的项目推敲,可以交给大家去测试和修改,主要是提供一个解题的思路,希望可以帮到你~~

  • 4
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

baobao熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值