Revit二次开发小技巧(十五)构件的最小矩形外轮廓

前言:我们会经常遇到需要计算一个构件的最小外轮廓,一般直接取BoundingBox只有最大和最小值坐标,也是基于x-y坐标系下的。往往不是最小的矩形,所以分享下面的算法来计算最小的外轮廓,条件为法向量是指向Z轴的,暂时没有考虑曲线的情况

背景

需要得到下面构件的最小外接矩形
在这里插入图片描述
一般我们使用BoundingBox只能取到最大包络框,而且是基于x-y坐标系下面的。
在这里插入图片描述
实际上我们需要的是最小的外接矩形
在这里插入图片描述

代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Autodesk.Revit.UI;
using Autodesk.Revit.DB;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.UI.Selection;

namespace RevitTest
{
    [Transaction(TransactionMode.Manual)]
    public class MinimumBoundingRectangleCmd : IExternalCommand
    {
        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
        {
            UIDocument uidoc = commandData.Application.ActiveUIDocument;
            Document doc = uidoc.Document;
            Reference selRef;
            try
            {
                selRef = uidoc.Selection.PickObject(ObjectType.Element, "选择构件");
            }
            catch
            {
                return Result.Succeeded;
            }
            //实体构件
            List<CurveLoop> downCLList = GetDownFaceCurveLoop(GetSolids(doc.GetElement(selRef)));
            //得到凸包轮廓
            List<Line> convexColsure = GetConvexClosure(doc, downCLList.SelectMany(x => x).OfType<Line>().ToList());
            //得到最小的轮廓
            List<Line> minBoundingRectangle = GetMinimumBoundingRectangle(doc, convexColsure);
            Transaction trans = new Transaction(doc, "生成轮廓线");
            trans.Start();
            minBoundingRectangle.ForEach(x => LineTest(doc, x));
            trans.Commit();
            return Result.Succeeded;
        }
        /// <summary>
        /// 得到最小的矩形轮廓
        /// </summary>
        /// <param name="convexColsure"></param>
        /// <returns></returns>
        public List<Line> GetMinimumBoundingRectangle(Document doc, List<Line> convexColsure)
        {
            List<Line> result = new List<Line>();
            //确定x-y坐标系下最大的外轮廓
            IEnumerable<XYZ> pointList = convexColsure.Select(x => x.GetEndPoint(0));
            double area = double.MaxValue;
            foreach (var l in convexColsure)
            {
                List<Line> rectabgleLineList = GetMinimumBoundingOnPoints(pointList, l.Direction, XYZ.BasisZ.CrossProduct(l.Direction));
                double nwArea = rectabgleLineList[0].Length * rectabgleLineList[1].Length;
                if (nwArea < area)
                {
                    area = nwArea;
                    result = rectabgleLineList;
                }
            }
            return result;
        }
        /// <summary>
        /// 获取点集中的最大点和最小点
        /// </summary>
        /// <param name="points">点集合</param>
        /// <param name="xAxis">x轴</param>
        /// <param name="yAxis">y轴</param>
        /// <returns></returns>
        public List<Line> GetMinimumBoundingOnPoints(IEnumerable<XYZ> points, XYZ xAxis, XYZ yAxis)
        {
            XYZ minPoint = null;
            XYZ maxPoint = null;
            if (points.Count() < 2)
            {
                return null;
            }
            Transform transform = Transform.Identity;
            transform.BasisX = xAxis;
            transform.BasisY = yAxis;
            //取逆矩阵,转回想要的坐标系
            transform = transform.Inverse;
            double minX = int.MaxValue;
            double maxX = int.MinValue;
            double minY = int.MaxValue;
            double maxY = int.MinValue;
            double minZ = int.MaxValue;
            double maxZ = int.MinValue;
            foreach (var xyz in points)
            {
                //转换
                XYZ current = transform == null ? xyz : transform.OfPoint(xyz);
                minX = Math.Min(current.X, minX);
                maxX = Math.Max(current.X, maxX);
                minY = Math.Min(current.Y, minY);
                maxY = Math.Max(current.Y, maxY);
                minZ = Math.Min(current.Z, minZ);
                maxZ = Math.Max(current.Z, maxZ);
            }
            minPoint = new XYZ(minX, minY, minZ);
            maxPoint = new XYZ(maxX, maxY, maxZ);
            XYZ point1 = new XYZ(minX, maxY, maxZ);
            XYZ point2 = new XYZ(maxX, minY, minZ);
            List<Line> result = new List<Line>()
            {
                Line.CreateBound(point1,minPoint).CreateTransformed(transform.Inverse) as Line,
                Line.CreateBound(minPoint,point2).CreateTransformed(transform.Inverse) as Line,
                Line.CreateBound(point2,maxPoint).CreateTransformed(transform.Inverse) as Line,
                Line.CreateBound(maxPoint,point1).CreateTransformed(transform.Inverse) as Line,
            };
            return result;
        }

        /// <summary>
        /// 得到凸包轮廓
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="lines"></param>
        /// <returns></returns>
        public List<Line> GetConvexClosure(Document doc, List<Line> lines)
        {
            List<Line> result = new List<Line>();
            List<XYZ> allPointList = new List<XYZ>();
            lines.ForEach(x =>
            {
                XYZ point1 = SetZ(x.GetEndPoint(0), 0);
                XYZ point2 = SetZ(x.GetEndPoint(1), 0);
                if (allPointList.FirstOrDefault(m => m.DistanceTo(point1) < 1 / 304.8) == null)
                    allPointList.Add(point1);
                if (allPointList.FirstOrDefault(m => m.DistanceTo(point2) < 1 / 304.8) == null)
                    allPointList.Add(point2);
            });
            //找出最x轴最小的点,然后Y轴最小点
            XYZ originPoint = allPointList.OrderBy(x => x.DotProduct(XYZ.BasisX)).ThenBy(x => x.DotProduct(XYZ.BasisY)).FirstOrDefault();
            //LineTest(doc, Line.CreateBound(originPoint, originPoint.Add(XYZ.BasisX * 100)));
            XYZ startPoint = originPoint;
            XYZ endPoint = null;
            while (true)
            {
                if (allPointList.Count == 0) break;
                XYZ startDirection = new XYZ();
                if (endPoint == null)
                    startDirection = -XYZ.BasisY;
                else
                    startDirection = (endPoint - startPoint).Normalize();
                IEnumerable<XYZ> directionList = allPointList.OrderBy(x =>
                {
                    if (x.DistanceTo(startPoint) < 1 / 304.8 || (endPoint != null && x.DistanceTo(endPoint) < 1 / 304.8)) return 1000000000;
                    XYZ direction = (x - startPoint).Normalize();
                    return startDirection.AngleOnPlaneTo(direction, XYZ.BasisZ);
                });
                XYZ onePoint = directionList.FirstOrDefault();
                result.Add(Line.CreateBound(startPoint, onePoint));
                if (onePoint.DistanceTo(originPoint) < 1 / 304.8) break;
                endPoint = startPoint;
                startPoint = onePoint;
            }
            return result;
        }

        /// <summary>
        /// 返回坐标的Z值
        /// </summary>
        /// <param name="point"></param>
        /// <param name="zValue"></param>
        /// <returns></returns>
        public XYZ SetZ(XYZ point, double zValue)
        {
            return new XYZ(point.X, point.Y, zValue);
        }

        /// <summary>
        /// 得到实体集合中的所有底面轮廓集合
        /// </summary>
        /// <param name="solids"></param>
        /// <returns></returns>
        public List<CurveLoop> GetDownFaceCurveLoop(List<Solid> solids)
        {
            List<CurveLoop> result = new List<CurveLoop>();
            List<PlanarFace> topFaceList = new List<PlanarFace>();
            foreach (var s in solids)
            {
                foreach (Face geoFace in s.Faces)
                {
                    PlanarFace temFace = geoFace as PlanarFace;
                    if (temFace == null) continue;
                    if (IsParallel(temFace.FaceNormal, XYZ.BasisZ, false))
                    {
                        topFaceList.Add(temFace);
                        //break;
                    }
                }
            }

            foreach (var topFace in topFaceList)
            {
                var curveLoopList = topFace.GetEdgesAsCurveLoops().ToList();
                result.AddRange(curveLoopList);
            }
            return result;
        }
        /// <summary>
        /// 计算两点平面上的距离
        /// </summary>
        /// <param name="onePoint"></param>
        /// <param name="twoPoint"></param>
        /// <returns></returns>
        public double GetHorizontalDistance(XYZ onePoint, XYZ twoPoint)
        {
            return Math.Pow(Math.Pow(onePoint.X - twoPoint.X, 2) + Math.Pow(onePoint.Y - twoPoint.Y, 2), 0.5);
        }

        /// <summary>
        /// 生成直线
        /// </summary>
        /// <param name="doc"></param>
        /// <param name="l"></param>
        /// <returns></returns>
        public ModelCurve LineTest(Document doc, Line l)
        {
            XYZ basicZ = XYZ.BasisZ;
            if (l.Direction.AngleTo(XYZ.BasisZ) < 0.0001 || l.Direction.AngleTo(-XYZ.BasisZ) < 0.0001)
                basicZ = XYZ.BasisY;
            XYZ normal = basicZ.CrossProduct(l.Direction).Normalize();

            Plane plane = Plane.CreateByNormalAndOrigin(normal, l.GetEndPoint(0));

            Transaction transCreate = null;
            if (!doc.IsModifiable)
                transCreate = new Transaction(doc, "模型线测试");
            transCreate?.Start();
            SketchPlane sktpl = SketchPlane.Create(doc, plane);
            ModelCurve mc = doc.IsFamilyDocument ? doc.FamilyCreate.NewModelCurve(l, sktpl) : doc.Create.NewModelCurve(l, sktpl);
            transCreate?.Commit();
            return mc;
        }
        /// <summary>
        /// 拿到对应构件的Solid集合
        /// </summary>
        /// <param name="elem"></param>
        /// <param name="vdtLevel"></param>
        /// <returns></returns>
        public List<Solid> GetSolids(Element elem, ViewDetailLevel vdtLevel = ViewDetailLevel.Fine)
        {
            if (elem == null)
            {
                return new List<Solid>();
            }
            GeometryElement geometryElement = elem.get_Geometry(new Options
            {
                ComputeReferences = true,
                DetailLevel = vdtLevel,
                IncludeNonVisibleObjects = false,
            });
            return GetSolids(geometryElement);
        }
        /// <summary>
        /// 获取所有的Solid
        /// </summary>
        /// <param name="geometryElement"></param>
        /// <returns></returns>
        public List<Solid> GetSolids(GeometryElement geometryElement)
        {
            List<Solid> result = new List<Solid>();
            foreach (GeometryObject geomObj in geometryElement)
            {
                Solid solid = geomObj as Solid;
                if (null != solid)
                {
                    result.Add(solid);
                    continue;
                }
                //If this GeometryObject is Instance, call AddCurvesAndSolids
                GeometryInstance geomInst = geomObj as GeometryInstance;
                if (null != geomInst)
                {
                    GeometryElement transformedGeomElem = geomInst.GetInstanceGeometry(Transform.Identity);
                    result.AddRange(GetSolids(transformedGeomElem));
                }
            }
            return result;
        }
        /// <summary>
        /// 得到实体指定方向的一开始的面(取该方向上最里面的面)
        /// </summary>
        /// <param name="targetSolid"></param>
        /// <returns></returns>
        public List<CurveLoop> GetDirectionOriginCurveLoop(Solid targetSolid, XYZ direction)
        {
            List<CurveLoop> result = new List<CurveLoop>();
            List<PlanarFace> topFaceList = new List<PlanarFace>();
            foreach (Face geoFace in targetSolid.Faces)
            {
                PlanarFace temFace = geoFace as PlanarFace;
                if (temFace == null) continue;
                if (IsParallel(temFace.FaceNormal, direction))
                {
                    topFaceList.Add(temFace);
                }
            }
            var topFace = topFaceList.OrderBy(x => x.Origin.DotProduct(direction)).FirstOrDefault();
            var curveLoopList = topFace.GetEdgesAsCurveLoops().ToList();
            result.AddRange(curveLoopList);
            return result;
        }
        /// <summary>
        /// 向量是否平行
        /// </summary>
        /// <param name="vector1"></param>
        /// <param name="vector2"></param>
        /// <param name="v">true为同向平行,false为反向平行,null为平行</param>
        /// <param name="tolerance">允许误差的角度</param>
        /// <returns></returns>
        public bool IsParallel(XYZ vector1, XYZ vector2, bool? v = null, double tolerance = 0.1)
        {
            var angle = vector1.AngleTo(vector2) / Math.PI * 180;
            if (v == null)
            {
                return angle >= 180 - tolerance || angle <= tolerance;
            }
            else if (v == true)
            {
                return angle <= tolerance;
            }
            else
            {
                return angle >= 180 - tolerance;
            }
        }
        /// <summary>
        /// 传入集合的集合体,批量操作
        /// </summary>
        /// <param name="solids"></param>
        /// <param name="booleanOperationsType"></param>
        /// <returns></returns>
        public Solid GetUnionSolid(List<Solid> solids, BooleanOperationsType booleanOperationsType)
        {
            Solid firstSolid = solids[0];
            solids.RemoveAt(0);
            //对所有的几何体进行融合
            foreach (var oneSoild in solids)
            {
                try
                {
                    firstSolid = SolidBooleanOperation(firstSolid, oneSoild, booleanOperationsType);
                }
                catch
                {

                }

            }
            return firstSolid;
        }
        /// <summary>
        /// Solid布尔操作
        /// </summary>
        /// <param name="solidA"></param>
        /// <param name="solidB"></param>
        /// <param name="booleanOperationsType"></param>
        /// <returns></returns>
        public Solid SolidBooleanOperation(Solid solidA, Solid solidB, BooleanOperationsType booleanOperationsType)
        {
            Solid result = null;
            try
            {
                result = BooleanOperationsUtils.ExecuteBooleanOperation(solidA, solidB, booleanOperationsType);
            }
            catch (Exception ex)
            {
                result = BooleanOperationsUtils.ExecuteBooleanOperation(solidB, solidA, booleanOperationsType);
            }
            return result;
        }

    }
}

思路

一、确定底面轮廓线

通过找到构件的所有底面,然后返回所有的面的轮廓线
在这里插入图片描述

二、根据轮廓线计算凸包轮廓

把线的起终点都添加到集合里面,然后去重。由于我们当前方法仅处理法向量为XYZ.BasisZ的平面,需要把所有的点都降到统一的平面上,默认Z轴为0。
在这里插入图片描述
凸包算法的思路:

  1. 找到X轴和Y轴数值最小的点,确定为原点,也为起点
    在这里插入图片描述

  2. 默认开始的方向为-Y轴的方向
    在这里插入图片描述

  3. 根据点集里面的点,分别构建一个起点出发,指向其他点的向量。
    在这里插入图片描述

  4. 如果是原点的时候,就找-Y轴和哪个点构造的向量的逆时针夹角最小。这个点就是我们要的下一个凸点。
    在这里插入图片描述

  5. 以找到的凸点为起点,找到的凸点和上一个凸点的向量为初始向量。重复构造点集向量,找最小夹角的过程。

  6. 在回到原点的时候,寻找结束。
    在这里插入图片描述

三、根据凸包轮廓生成矩形轮廓

  1. 以凸包轮廓的每一条线的方向为X轴,通过Z轴和X轴的叉乘得到Y轴
    在这里插入图片描述

  2. 以X和Y轴的单位向量,构建一个Transform对象,并把BasisX和BasisY设置组成对应的XY轴方向。(相当于把构件以点旋转了一定角度,把坐标系变成我们要的坐标系)
    在这里插入图片描述

  3. 把所有的点都转到以我们需要的XY轴的坐标系下,再计算四个角点,并连成矩形。得到的线再转回到项目的坐标下,就得到这条边对应的外接矩形了。
    在这里插入图片描述

  4. 所有的边都计算一个外接矩形,然后得到哪个外接矩形面积最小,就是我们要的那个了。
    在这里插入图片描述

写在最后

希望这篇文章可以给你带来帮助~~~

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

baobao熊

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

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

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

打赏作者

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

抵扣说明:

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

余额充值