前言:当前方法使用在既定的情况下,选择闭合的墙体,然后找到对应的外轮廓,并不支持存在开口的情况,仅提供一种思路。​​Revit​​版本为2019

代码:

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

namespace RevitTest
{
[Transaction(TransactionMode.Manual)]
public class AlgorithmCmd : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument;
Document doc = uidoc.Document;

List<Reference> selRefList = new List<Reference>();
try
{
selRefList = uidoc.Selection.PickObjects(ObjectType.Element, new OnlySelectWall(), "选择墙体").ToList();
}
catch (Exception)
{
return Result.Succeeded;
}
//提取实体
List<Solid> selectWallSolidList = selRefList.Select(x => GetUnionSolid(GetSolids(doc.GetElement(x)), BooleanOperationsType.Union)).ToList();
//提取顶面
List<CurveLoop> topCLList = selectWallSolidList.SelectMany(x => GetDirectionOriginCurveLoop(x, XYZ.BasisZ)).ToList();
List<Line> maxOuterLineList = GetMaximumOuterContour(doc, topCLList.SelectMany(x => x).OfType<Line>().ToList());
Transaction trans = new Transaction(doc, "測試");
trans.Start();
maxOuterLineList.ForEach(x => LineTest(doc, x));
trans.Commit();
return Result.Succeeded;
}
/// <summary>
/// 计算最大外轮廓
/// </summary>
/// <param name="lineList"></param>
/// <returns></returns>
public List<Line> GetMaximumOuterContour(Document doc, List<Line> lineList)
{
List<Line> result = new List<Line>();
//先得到线段中x轴最小的点
XYZ targetPoint = lineList.SelectMany(x =>
{
return new List<XYZ>() { x.GetEndPoint(0), x.GetEndPoint(1) };
}).OrderBy(x => x.X).ThenBy(x => x.Y).FirstOrDefault();
XYZ startPoint = targetPoint;
XYZ lastPoint = null;
while (true)
{
List<Line> lines = lineList.FindAll(x => GetHorizontalDistance(x.GetEndPoint(0), startPoint) < 1 / 304.8 || GetHorizontalDistance(x.GetEndPoint(1), startPoint) < 1 / 304.8).Select(x =>
{
if (GetHorizontalDistance(x.GetEndPoint(1), startPoint) < 1 / 304.8) return x.CreateReversed() as Line;
else return x;
}).ToList();
//如果线只有一个的时候,说明断开了
if (lineList.Count == 0) break;
if (lastPoint == null)
{
lastPoint = startPoint.Add(-XYZ.BasisY * 10);
}
//确定一开始的方向
XYZ direction = (lastPoint - startPoint).Normalize();
Line nextLine = null;

if (GetHorizontalDistance(startPoint, targetPoint) < 1 / 304.8)
nextLine = lines.OrderBy(x => direction.AngleOnPlaneTo(x.Direction, XYZ.BasisZ)).FirstOrDefault();
else
nextLine = lines.Where(x => !IsParallel(x.Direction, direction, true)).OrderBy(x => direction.AngleOnPlaneTo(x.Direction, XYZ.BasisZ)).FirstOrDefault();
result.Add(nextLine);
if (nextLine.GetEndPoint(1).DistanceTo(targetPoint) < 1 / 304.8) break;
startPoint = nextLine.GetEndPoint(1);
lastPoint = nextLine.GetEndPoint(0);
}
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;
}
}
public class OnlySelectWall : ISelectionFilter
{
public bool AllowElement(Element elem)
{
if (elem is Wall) return true;
return false;
}

public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
}
}

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

namespace RevitTest
{
[Transaction(TransactionMode.Manual)]
public class AlgorithmCmd : IExternalCommand
{
public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)
{
UIDocument uidoc = commandData.Application.ActiveUIDocument;
Document doc = uidoc.Document;

List<Reference> selRefList = new List<Reference>();
try
{
selRefList = uidoc.Selection.PickObjects(ObjectType.Element, new OnlySelectWall(), "选择墙体").ToList();
}
catch (Exception)
{
return Result.Succeeded;
}
//提取实体
List<Solid> selectWallSolidList = selRefList.Select(x => GetUnionSolid(GetSolids(doc.GetElement(x)), BooleanOperationsType.Union)).ToList();
//提取顶面
List<CurveLoop> topCLList = selectWallSolidList.SelectMany(x => GetDirectionOriginCurveLoop(x, XYZ.BasisZ)).ToList();
List<Line> maxOuterLineList = GetMaximumOuterContour(doc, topCLList.SelectMany(x => x).OfType<Line>().ToList());
Transaction trans = new Transaction(doc, "測試");
trans.Start();
maxOuterLineList.ForEach(x => LineTest(doc, x));
trans.Commit();
return Result.Succeeded;
}
/// <summary>
/// 计算最大外轮廓
/// </summary>
/// <param name="lineList"></param>
/// <returns></returns>
public List<Line> GetMaximumOuterContour(Document doc, List<Line> lineList)
{
List<Line> result = new List<Line>();
//先得到线段中x轴最小的点
XYZ targetPoint = lineList.SelectMany(x =>
{
return new List<XYZ>() { x.GetEndPoint(0), x.GetEndPoint(1) };
}).OrderBy(x => x.X).ThenBy(x => x.Y).FirstOrDefault();
XYZ startPoint = targetPoint;
XYZ lastPoint = null;
while (true)
{
List<Line> lines = lineList.FindAll(x => GetHorizontalDistance(x.GetEndPoint(0), startPoint) < 1 / 304.8 || GetHorizontalDistance(x.GetEndPoint(1), startPoint) < 1 / 304.8).Select(x =>
{
if (GetHorizontalDistance(x.GetEndPoint(1), startPoint) < 1 / 304.8) return x.CreateReversed() as Line;
else return x;
}).ToList();
//如果线只有一个的时候,说明断开了
if (lineList.Count == 0) break;
if (lastPoint == null)
{
lastPoint = startPoint.Add(-XYZ.BasisY * 10);
}
//确定一开始的方向
XYZ direction = (lastPoint - startPoint).Normalize();
Line nextLine = null;

if (GetHorizontalDistance(startPoint, targetPoint) < 1 / 304.8)
nextLine = lines.OrderBy(x => direction.AngleOnPlaneTo(x.Direction, XYZ.BasisZ)).FirstOrDefault();
else
nextLine = lines.Where(x => !IsParallel(x.Direction, direction, true)).OrderBy(x => direction.AngleOnPlaneTo(x.Direction, XYZ.BasisZ)).FirstOrDefault();
result.Add(nextLine);
if (nextLine.GetEndPoint(1).DistanceTo(targetPoint) < 1 / 304.8) break;
startPoint = nextLine.GetEndPoint(1);
lastPoint = nextLine.GetEndPoint(0);
}
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;
}
}
public class OnlySelectWall : ISelectionFilter
{
public bool AllowElement(Element elem)
{
if (elem is Wall) return true;
return false;
}

public bool AllowReference(Reference reference, XYZ position)
{
return true;
}
}
}

实现的效果图

目标墙实体轮廓



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_.net


提取出来的线轮廓



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_c#_02


最终计算得到的轮廓线



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_.net_03


思路写在最后

(1)第一个点我找的是x和y轴都最小的点,默认的方向为指向下。



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_windows_04


(2)找到起终点在第一个点上的线段,然后重构成以第一个点为起点的线。



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_Powered by 金山文档_05


(3)然后找与第一个方向单位向量,逆时针夹角最小的那一个方向的线,就是我们需要的线。然后再以这根线的终点作为找下一根线的起点,线的终点指向起点的方向为新起点方向。继续上面的循环通过找最小的夹角来确定需要的线。



Revit二次开发小技巧(十四)封闭墙体最大外轮廓_.net_06


(4)结束的点在,找到的终点就是我们一开始的点,就是循环的结束。希望能给你带来帮助