using Autodesk.Revit.DB;
using ShrlAlgoToolkit.RevitCore.Assists;
namespace ShrlAlgoToolkit.RevitCore.Extensions;
public static class SpatialExtensions
{
private static readonly double CurveTolerance = 0.0025;
///
/// 容差
///
private const double VbTolerance = 10E-6;
///
/// 按距离获取点
///
/// 原曲线
/// 拆分的起点长度,超出曲线终点长度时,返回原曲线长度
/// 间距
/// 如果不足以等分,则等于原曲线长度
///
public static Dictionary EvalutePointByFixedLength(this Curve curve, double startParameter, double spacing, out double startLength, out double retainingLength)
{
Dictionary segments = new Dictionary();
if (spacing <= 0)
{
throw new InvalidOperationException("分割距离不能小于等于0");
}
if (curve is Arc arc && arc.IsBound)
{
curve = HermiteSpline.Create(arc.Tessellate(), false);
}
if (curve is Ellipse ellipse && ellipse.IsBound)
{
curve = HermiteSpline.Create(ellipse.Tessellate(), false);
}
var originParameter = startParameter;
//startParameter += curve.GetEndParameter(0);//拖动曲线时,起点参数会发生变化
var param1 = curve.GetEndParameter(0);
var param2 = curve.GetEndParameter(1);
startParameter += param1;
if (curve.IsInside(startParameter))
{
startLength = originParameter;
}
else
{
startLength = curve.Length;
}
if (curve.IsInside(startParameter))
{
var acutallLength = param2 - startParameter;
double count = Math.Floor(acutallLength / spacing);
double remainder = acutallLength % spacing;
for (double i = 0; i <= count; i++)
{
var param = i * spacing + startParameter;
//if (curve is Arc || curve is Ellipse)
//{
// param = curve.ComputeRawParameter(param);
//}
//if (param > param2)
//{
// break;
//}
var point = curve.Evaluate(param, false);
//var point1 = curve.GetEndPoint(0);
var transform = curve.ComputeDerivatives(i * spacing + startParameter, false);
var tangent = transform.BasisX;
var orientation = tangent.CrossProduct(XYZ.BasisZ);
segments.Add(point, orientation);
}
if (remainder < 10e-6)//没有余数,完全等分
{
retainingLength = 0;
}
else
{
retainingLength = curve.Length - count * spacing - startLength;
}
}
else
{
retainingLength = curve.Length;
}
return segments;
}
///
/// 按距离拆分
///
/// 原曲线
/// 拆分的起点长度,计算曲线时会换算成实际的参数,因为曲线可能被伸缩,导致起点不是0,超出曲线终点长度时,返回原曲线,需要大于最短曲线容差0.0025,否则返回空
///
/// 如果刚好等分,那么就为空,如果不足以等分,那么就等于原曲线
///
public static List SplitByFixedLength(this Curve curve, double startParameter, double length, out Curve startCurve, out Curve retainingCurve)
{
List segments = new List();
if (length <= 0)
{
throw new InvalidOperationException("分割距离不能小于等于0");
}
if (curve is Arc arc && arc.IsBound)
{
curve = HermiteSpline.Create(arc.Tessellate(), false);
}
if (curve is Ellipse ellipse && ellipse.IsBound)
{
curve = HermiteSpline.Create(ellipse.Tessellate(), false);
}
var originParameter = startParameter;
//startParameter += curve.GetEndParameter(0);//但你拖动曲线时,起点参数会发生变化
var param1 = curve.GetEndParameter(0);
var param2 = curve.GetEndParameter(1);
startParameter += param1;
if (curve.IsInside(startParameter))
{
if (startParameter < CurveTolerance || originParameter < CurveTolerance)//过短,返回空,一般是0
{
startCurve = null;
}
else
{
startCurve = curve.Clone();
startCurve.MakeBound(param1, startParameter);
}
}
else
{
startCurve = curve.Clone();
}
if (curve.IsInside(startParameter))
{
var acutallLength = param2 - startParameter;
double count = Math.Floor(acutallLength / length);
double remainder = acutallLength % length;
for (double i = 0; i < count; i++)
{
var startParam = i * length + startParameter;
var endParam = startParam + length;
var segment = curve.Clone();
segment.MakeBound(startParam, endParam);
segments.Add(segment);
}
if (remainder < 10e-6)//没有余数,完全等分
{
retainingCurve = null;
}
else
{
retainingCurve = curve.Clone();
retainingCurve.MakeBound(length * count + startParameter, param2);
}
}
else
{
retainingCurve = curve.Clone();
}
return segments;
}
///
/// 获取几何实例的准确的定位
///
/// dwg的变换
///
///
public static XYZ GetLocationPoint(this GeometryInstance ins, Transform dwgTransform, out double rotation)
{
var blockLocation = ins.Transform.Origin;
if (ins.Transform.Origin.GetLength() is > 1000 or 0)
{
foreach (var geometryObject in ins.GetInstanceGeometry())
{
if (geometryObject is Arc arc)
{
blockLocation = arc.Center;
break;
}
if (geometryObject is PolyLine pl)
{
blockLocation = (pl.GetOutline().MaximumPoint + pl.GetOutline().MinimumPoint) / 2;
break;
}
}
}
rotation = Math.Abs(ins.Transform.BasisX.AngleOnPlaneTo(XYZ.BasisX, XYZ.BasisZ) - 2 * Math.PI);
return dwgTransform.OfPoint(blockLocation);
}
///
///
///
///
///
///
private static List GetGeoObjects(this GeometryObject geoObject)
where T : GeometryObject
{
List geometryObjects = [];
if (geoObject is Solid instSolid)
{
if (typeof(T) == typeof(Solid))
{
geometryObjects.Add(instSolid as T);
}
if (typeof(T) == typeof(Face) && instSolid.Faces.Size > 0)
{
geometryObjects.AddRange(instSolid.Faces.Cast());
}
else if (typeof(T) == typeof(Edge) && instSolid.Edges.Size > 0)
{
geometryObjects.AddRange(instSolid.Edges.Cast());
}
}
return geometryObjects;
}
public static XYZ ProjectOnPlane(this XYZ q, Plane plane)
{
//The projection of a point q = (x, y, z) onto a plane given by a point p = (a, b, c) and a normal n = (d1, e, f) is
// q_proj = q - dot(q - p, n) * n
var p = plane.Origin;
var n = plane.Normal.Normalize();
var qProj = q - n.Multiply(n.DotProduct(q - p));
return qProj;
}
/// 创建一个新的实体,它是输入实体的副本
/// 要复制的输入实体
/// 新的实体
public static Solid Clone(this Solid solid)
{
return SolidUtils.Clone(solid);
}
/// 创建一个新的实体,它是输入实体的转换
/// 要转换的输入实体
/// 变换(必须是保角变换)
/// 新的实体
///
/// 变换不保形。
/// 或变换的比例为负或零
///
public static Solid CreateTransformed(this Solid solid, Transform transform)
{
return SolidUtils.CreateTransformed(solid, transform);
}
///
/// 直线间距离,无边界的情况
///
///
///
///
public static double Distance(this Line line1, Line line2)
{
double distance;
var v1 = line1.Direction;
var p1 = line1.GetEndPoint(0);
var v2 = line2.Direction;
var p2 = line2.GetEndPoint(0);
var a = p1.X - p2.X;
var b = p1.Y - p2.Y;
var c = p1.Z - p2.Z;
var pq = (v1.X * v2.Y) - (v2.X * v1.Y);
var qr = (v1.Y * v2.Z) - (v2.Y * v1.Z);
var rp = (v1.Z * v2.X) - (v2.Z * v1.X);
var dev = Math.Sqrt(Math.Pow(qr, 2) + Math.Pow(rp, 2) + Math.Pow(pq, 2));
if (dev < 1e-9)
{
var bp = b * v1.X;
var br = b * v1.Z;
var cp = c * v1.X;
var cq = c * v1.Y;
var aq = a * v1.Y;
var ar = a * v1.Z;
distance =
Math.Sqrt(Math.Pow(br - cq, 2) + Math.Pow(cp - ar, 2) + Math.Pow(aq - bp, 2))
/ Math.Sqrt(Math.Pow(v1.X, 2) + Math.Pow(v1.Y, 2) + Math.Pow(v1.Z, 2));
}
else
{
distance = Math.Abs(((qr * a) + (rp * b) + (pq * c)) / dev);
}
return distance;
}
public static List DrawMesh(this Sweep sweep)
{
Options option = new() { ComputeReferences = true, DetailLevel = ViewDetailLevel.Fine };
var geomElement = sweep.get_Geometry(option);
List triangularPoints = [];
foreach (var geomObj in geomElement)
{
if (geomObj is Solid geomSolid)
{
foreach (Face geomFace in geomSolid.Faces)
{
var mesh = geomFace.Triangulate();
for (var i = 0; i < mesh.NumTriangles; i++)
{
var triangular = mesh.get_Triangle(i);
for (var n = 0; n < 3; n++)
{
var point = triangular.get_Vertex(n);
triangularPoints.Add(point);
}
}
}
}
}
return triangularPoints;
}
///
/// 扩展包围盒的大小
///
///
///
///
public static BoundingBoxXYZ Extend(this BoundingBoxXYZ boundingBox, double distance = 2.0)
{
var transform = boundingBox.Transform;
var max = boundingBox.Max + distance * (transform.BasisX + transform.BasisY + transform.BasisZ);
var min = boundingBox.Min - distance * (transform.BasisX + transform.BasisY + transform.BasisZ);
return new BoundingBoxXYZ() { Max = max, Min = min, Transform = transform };
}
///
/// 延长直线
///
///
///
/// 是否双向延申,单向延长终点
///
public static Line ExtendLine(this Line line, double distance, bool isInteractive = false)
{
var endPoint = line.GetEndPoint(1) + (line.Direction * distance);
var startPoint = line.GetEndPoint(0);
if (isInteractive)
{
startPoint += line.Direction.Negate() * distance;
}
return Line.CreateBound(startPoint, endPoint);
}
///
/// 延长或缩短直线
///
///
///
/// 点在直线内时,保留部分,为null时,保留长的部分
///
public static Line ExtendLine(this Line line, XYZ endPoint, bool? startSection = null)
{
if (line == null)
{
throw new ArgumentNullException(nameof(line), "延长线为空!");
}
if (endPoint == null)
{
throw new ArgumentNullException(nameof(endPoint), "延长点为空!");
}
var v = endPoint - line.GetEndPoint(0);
var v2 = endPoint - line.GetEndPoint(1);
//点刚好是直线端点
if (v.IsZeroLength() || v2.IsZeroLength())
{
return line;
}
//必须平行才能延伸
if (v.IsParallelTo(v2))
{
//点在直线外,同方向
if (line.Direction.DotProduct(v) > 0.0 && line.Direction.DotProduct(v2) > 0.0)
{
return Line.CreateBound(line.GetEndPoint(0), endPoint);
}
//点在直线外,反方向
if (line.Direction.DotProduct(v) < 0.0 && line.Direction.DotProduct(v2) < 0.0)
{
return Line.CreateBound(endPoint, line.GetEndPoint(1));
}
//点在直线内
return v.DotProduct(v2) < 0.0
? startSection == null
? v.GetLength() >= v2.GetLength()
? Line.CreateBound(line.GetEndPoint(0), endPoint)
: Line.CreateBound(endPoint, line.GetEndPoint(1))
: startSection == true
? Line.CreateBound(line.GetEndPoint(0), endPoint)
: Line.CreateBound(endPoint, line.GetEndPoint(1))
: null;
}
return line;
//throw new InvalidOperationException("不可延伸到该点");
}
///
/// 重心,质心
///
///
///
public static XYZ GetCentroid(this List points)
{
var sourceX = from XYZ point in points select point.X;
var sourceY = from XYZ point in points select point.Y;
var sourceZ = from XYZ point in points select point.Z;
var count = points.Count;
var x = sourceX.Sum() / count;
var y = sourceY.Sum() / count;
var z = sourceZ.Sum() / count;
return new XYZ(x, y, z);
}
public static HermiteSpline ConverToHermiteSpline(this PolyLine polyLine)
{
var list = polyLine.GetCoordinates();
return HermiteSpline.Create(list, false);
}
///
/// 将几何实例的实例集合的PolyLine的线条转换成曲线组
///
///
///
public static List GetCurveArray(IEnumerable geometryObjects)
{
List curves = [];
foreach (var geo in geometryObjects)
{
CurveArray lines = new();
if (geo is not PolyLine pl)
{
return curves;
}
var points = pl.GetCoordinates();
var n = 0;
for (var i = 0; i < points.Count - 1; i++)
{
var dis = points[i].DistanceTo(points[points.Count - 1]);
if (dis < 1 / 304.8) //重叠线段长度精度大于1mm,排除掉起点
{
n = i;
}
}
var pts = points.Skip(n).ToList();
var p0 = pts[0] - (pts[0].Z * XYZ.BasisZ);
for (var i = 0; i < pts.Count - 1; i++)
{
var p1 = pts[i] - (pts[i].Z * XYZ.BasisZ);
var p2 = pts[i + 1] - (pts[i + 1].Z * XYZ.BasisZ);
if (i == pts.Count - 2 && !p2.IsAlmostEqualTo(p0))
{
p2 = p0;
}
var l = Line.CreateBound(p1, p2);
lines.Append(l);
}
curves.Add(lines);
}
return curves;
}
///
/// 获取所有线的交点
///
///
///
public static List GetCurvesIntersect(List curves)
{
List points = [];
foreach (var grid in curves)
{
foreach (var grd in curves)
{
grid.Intersect(grd, out var ira);
if (ira != null)
{
var ir = ira.get_Item(0);
// 判断点是否重复
foreach (var p in points)
{
if (!p.IsAlmostEqualTo(ir.XYZPoint))
{
points.Add(ir.XYZPoint);
}
}
}
}
}
return points;
}
///
/// 获取平面的边
///
///
///
public static List GetEdges(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (var obj in edgeLoops)
{
var edgeArray = (EdgeArray)obj;
list.AddRange(edgeArray.Cast());
}
return list;
}
///
///
///
///
///
public static ElementOrientation GetElementOrientation(this Curve curve)
{
var num = 0.0001;
var endPoint = curve.GetEndPoint(0);
var endPoint2 = curve.GetEndPoint(1);
var result = endPoint.IsAlmostEqualTo(endPoint2)
? ElementOrientation.Undefined
: Math.Abs(endPoint.X - endPoint2.X) < num && Math.Abs(endPoint.Y - endPoint2.Y) > num
? ElementOrientation.Vertical
: (Math.Abs(endPoint.X - endPoint2.X) > num && Math.Abs(endPoint.Y - endPoint2.Y) < num)
? ElementOrientation.Horizontal
: Math.Abs(endPoint2.X - endPoint.X) >= Math.Abs(endPoint2.Y - endPoint.Y)
? ElementOrientation.CloseToHorizontal
: ElementOrientation.CloseToVertical;
return result;
}
///
/// 端点
///
///
///
public static List GetEndPoints(this Curve curve)
{
return [curve.GetEndPoint(0), curve.GetEndPoint(1)];
}
///
/// 根据等分量等分点曲线,得到所有分割点,含起终点
///
///
/// 等分量
///
public static List GetEquivalentPoints(this Curve curve, int num)
{
List result = [];
if (num <= 0)
{
throw new ArgumentException("分段数错误");
}
for (var i = 0; i <= num; i++)
{
var parameter = (double)i / num;
var p = curve.Evaluate(parameter, true);
result.Add(p);
}
return result;
}
/////
///// 步长划分线,含起终点
/////
/////
/////
/////
//public static IEnumerable Divide(this Curve curve, double step)
//{
// if (curve.Length > step)
// {
// double startParameter = curve.GetEndParameter(0);
// double endParameter = curve.GetEndParameter(1);
// double parameterStep = (endParameter - startParameter) / (curve.Length / step);
// double parameterSum = startParameter;
// do
// {
// yield return curve.Evaluate(parameterSum, false);
// parameterSum += parameterStep;
// } while (parameterSum < endParameter);
// if (Math.Abs(parameterSum - endParameter) > 0.0001)
// {
// yield return curve.GetEndPoint(1);
// }
// }
// else
// {
// yield return curve.GetEndPoint(0);
// yield return curve.GetEndPoint(1);
// }
//}
///
/// 根据等距离量分割曲线,得到所有分割点,含起点,不含终点
///
///
/// 等距离量
///
public static List GetEquivalentPoints(this Curve curve, double step)
{
List result = [];
var num = Math.Floor(curve.Length / step);
for (var i = 0; i <= num; i++)
{
if (curve.IsInside(step * i))
{
var p = curve.Evaluate(step * i, true);
result.Add(p);
}
}
return result;
}
///
/// 获取最远的两个点的扩展方法
///
///
///
public static Tuple GetFurthestPoints(this IList points)
{
Tuple result = default;
var dist = double.NaN;
for (var i = 0; i < points.Count; i++)
{
var pt1 = points[i];
for (var j = 0; j < points.Count; j++)
{
if (i == j)
{
continue;
}
var pt2 = points[j];
var d = pt1.DistanceTo(pt2);
if (double.IsNaN(dist) || d > dist)
{
result = new Tuple(pt1, pt2);
dist = d;
}
}
}
return result;
}
///
/// 获取几何对象,嵌套一次
///
/// 对应类型的几何对象,如Solid、Face、Edge
/// 获取的元素
///
public static List GetAllGeometryObjects(this Element elem)
where T : GeometryObject
{
List geometryObjects = [];
Options option = new() { ComputeReferences = true, DetailLevel = ViewDetailLevel.Fine };
var geometry = elem.get_Geometry(option);
foreach (var geomObj in geometry)
{
if (geomObj is GeometryInstance geomInstance)
{
var usesSymbolGeometry = elem is FamilyInstance instance && !instance.HasModifiedGeometry();
var geometryElement = usesSymbolGeometry ? geomInstance.GetSymbolGeometry() : geomInstance.GetInstanceGeometry();
foreach (var geoObject in geometryElement)
{
geometryObjects.AddRange(geoObject.GetGeoObjects());
}
}
else
{
geometryObjects.AddRange(geomObj.GetGeoObjects());
}
}
return geometryObjects;
}
///
/// 获取顶层的几何(非嵌套)
///
///
///
///
public static List GetGeometryObjects(this Element elem)
{
List geometryObjects = [];
Options option = new() { ComputeReferences = true, DetailLevel = ViewDetailLevel.Fine };
var geometry = elem.get_Geometry(option);
foreach (var geomObj in geometry)
{
geometryObjects.Add(geomObj);
}
return geometryObjects;
}
///
/// 求面和线的交点
///
///
///
///
public static XYZ GetIntersectPoint(this Face face, Curve curve)
{
//求交点
XYZ intersection = null;
var setComparisonResult = face.Intersect(curve, out var resultArray);
if (SetComparisonResult.Disjoint != setComparisonResult)
{
if (!resultArray.IsEmpty)
{
intersection = resultArray.get_Item(0).XYZPoint;
}
}
return intersection;
}
///
/// 获取元素定位线
///
/// 以点定位的实例
///
public static Curve GetCurve(this Element instance)
{
return instance.Location is LocationCurve lc ? lc.Curve : null;
}
///
/// 获取元素定位线
///
/// 以点定位的实例
///
public static LocationCurve GetLocationCurve(this Element instance)
{
return instance.Location is LocationCurve lc ? lc : null;
}
///
/// 获取元素定位点
///
/// 以点定位的实例
///
public static LocationPoint GetLocationPoint(this Element instance)
{
return instance.Location is LocationPoint lp ? lp : null;
}
///
/// 获取元素定位点
///
/// 以点定位的实例
///
public static XYZ GetLocXYZ(this Element instance)
{
return instance.Location is LocationPoint lp ? lp.Point : null;
}
///
/// 底部中心点
///
///
///
public static XYZ GetBottomCenterPoint(this Element element)
{
BoundingBoxXYZ boundingBox = element.get_BoundingBox(null);
// 获取包围盒的最小点和最大点
XYZ minPoint = boundingBox.Min;
XYZ maxPoint = boundingBox.Max;
// 计算底部中心点的X坐标
double centerX = (minPoint.X + maxPoint.X) / 2.0;
// 计算底部中心点的Y坐标
double centerY = (minPoint.Y + maxPoint.Y) / 2.0;
// 底部中心点的Z坐标就是包围盒的最小Z坐标
double centerZ = minPoint.Z;
// 创建并返回底部中心点
XYZ bottomCenter = new XYZ(centerX, centerY, centerZ);
return bottomCenter;
}
///
/// 过直线朝上的平面
///
///
///
public static Plane GetUpwardPlaneThroughLine(this Line line)
{
XYZ lineOrigin = line.Origin;
XYZ lineDirection = line.Direction; // 确保方向向量是单位向量
// 1. 特殊情况:如果直线是垂直的,无法找到法线Z分量大于0的平面。
// 这意味着 lineDirection 接近 (0,0,1) 或 (0,0,-1)。
// 使用一个小的容差值进行比较。
if (Math.Abs(lineDirection.X) < 1e-6 && Math.Abs(lineDirection.Y) < 1e-6)
{
// 直线是垂直的。返回 null 或抛出异常,因为无法满足“法线Z>0”的条件。
return null;
}
// 2. 第一次叉积:得到一个与 lineDirection 和 XYZ.BasisZ 都正交的向量。
// 这个向量 V1 是一个水平向量,它描述了一个包含 lineDirection 的垂直平面的法线。
XYZ v1 = lineDirection.CrossProduct(XYZ.BasisZ);
// 3. 第二次叉积:得到最终的法线候选向量。
// 这个向量 N_candidate 与 lineDirection 正交,并倾向于有一个正的Z分量。
XYZ normalCandidate = v1.CrossProduct(lineDirection);
// 4. 归一化法线向量,并确保其Z分量为正。
normalCandidate = normalCandidate.Normalize();
if (normalCandidate.Z < 0)
{
normalCandidate = -normalCandidate;
}
// 如果 normalCandidate.Z 此时仍然接近 0,说明直线是水平的,
// 并且两次叉积的结果依然是水平法线。这通常表示一个垂直平面。
// 在这种情况下,如果你的“面朝上”严格要求 Z > 0,则需要进一步处理。
// 然而,对于大多数非垂直线,上述逻辑足以得到一个 Z > 0 的法线。
// 对于水平线,两次叉积会得到一个垂直方向的法线(如(0,0,1)或(0,0,-1)的倍数),
// 经过 Z<0 调整后,确保了 Z > 0。
// 例子:如果 lineDirection 是 (1,0,0) (X轴),
// v1 = (1,0,0) x (0,0,1) = (0, -1, 0)
// normalCandidate = (1,0,0) x (0,-1,0) = (0,0,-1)
// 经过 Z<0 调整后,normalCandidate = (0,0,1)。这是一个面朝上的水平平面。
// 5. 使用直线的起点和计算出的法线创建平面。
Plane upwardFacingPlane = Plane.CreateByNormalAndOrigin(lineOrigin, normalCandidate);
return upwardFacingPlane;
}
public static double GetMaxX(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (Edge edge in edgeArray)
{
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.X.CompareTo(p2.X));
return list.Last().X;
}
public static double GetMaxY(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (Edge edge in edgeArray)
{
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.Y.CompareTo(p2.Y));
return list.Last().Y;
}
public static double GetMaxZ(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (Edge edge in edgeArray)
{
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.Z.CompareTo(p2.Z));
return list.Last().Z;
}
public static double GetMinX(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (Edge edge in edgeArray)
{
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.X.CompareTo(p2.X));
return list.Last().X;
}
public static double GetMinY(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (var obj2 in edgeArray)
{
var edge = (Edge)obj2;
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.Y.CompareTo(p2.Y));
return list.First().Y;
}
public static double GetMinZ(this PlanarFace face)
{
List list = [];
var edgeLoops = face.EdgeLoops;
foreach (EdgeArray edgeArray in edgeLoops)
{
foreach (Edge edge in edgeArray)
{
list.Add(edge.AsCurve().GetEndPoint(0));
list.Add(edge.AsCurve().GetEndPoint(1));
}
}
list.Sort((p1, p2) => p1.Z.CompareTo(p2.Z));
return list.Last().Z;
}
///
/// 两条异面直线的公垂线(两直线距离长度最短)与异面直线的交点(示例:管线碰撞翻弯)
///
///
///
///
public static Dictionary GetNearestPoints(this Line line1, Line line2)
{
Dictionary dictionary = [];
var a = line1.Direction.DotProduct(line2.Direction);
var b = line1.Direction.DotProduct(line1.Direction);
var c = line2.Direction.DotProduct(line2.Direction);
var d = (line2.GetEndPoint(0) - line1.GetEndPoint(0)).DotProduct(line1.Direction);
var e = (line2.GetEndPoint(0) - line1.GetEndPoint(0)).DotProduct(line2.Direction);
double t1;
double t2;
if (a == 0) //表明原始两条直线互相垂直
{
t1 = d / b;
t2 = -e / c;
}
else if (Math.Abs((a * a) - (b * c)) < 0.0001f) //表明共线或平行,注意理想情况下应该是a * a - b * c == 0,才认为共线或平行,但在计算机世界里,有精度这个东西存在,所以我们近视的认为数值小于某一个值则认为等于0
{
//说明共线或平行
return dictionary;
}
else
{
t1 = ((a * e) - (c * d)) / ((a * a) - (b * c));
t2 = (b / a * t1) - (d / a);
}
var p1 = line1.GetEndPoint(0) + (t1 * line1.Direction);
var p2 = line2.GetEndPoint(0) + (t2 * line2.Direction);
dictionary.Add(line1, p1);
dictionary.Add(line2, p2);
return dictionary;
}
///
/// 获取平行面分组
///
///
///
public static void GetParallelFaceGroups(this List faces, List> groups)
{
var face = faces.FirstOrDefault();
faces = faces.FindAll(f => f is PlanarFace);
List group = [face];
groups.Add(group);
var pf = face as PlanarFace;
faces.Remove(face);
for (var i = faces.Count - 1; i >= 0; i--)
{
if (faces[i] is not PlanarFace)
{
continue;
}
var tempFace = faces[i] as PlanarFace;
if (pf != null && tempFace != null && tempFace.FaceNormal.CrossProduct(pf.FaceNormal).IsAlmostEqualTo(XYZ.Zero))
{
group.Add(tempFace);
faces.Remove(tempFace);
//GetParallelFaceGroups(faces, tempFace, group, groups);
}
}
if (faces.Count >= 1)
{
GetParallelFaceGroups(faces, groups);
}
}
///
/// 获取平行面
///
///
///
public static void GetParallelFaces(this List faces, List> groups)
{
while (true)
{
var firstFace = faces.FirstOrDefault();
faces = faces.FindAll(face => face is PlanarFace);
List group = [firstFace];
groups.Add(group);
var pf = firstFace as PlanarFace;
faces.Remove(firstFace);
for (var i = faces.Count - 1; i >= 0; i--)
{
if (faces[i] is not PlanarFace)
{
continue;
}
var tempFace = faces[i] as PlanarFace;
if (pf != null && tempFace != null && tempFace.FaceNormal.CrossProduct(pf.FaceNormal).IsAlmostEqualTo(XYZ.Zero))
{
group.Add(tempFace);
faces.Remove(tempFace);
//GetParallelFaces(faces, tempFace, group, groups);
}
}
if (faces.Count >= 1)
{
continue;
}
break;
}
}
///
/// 递归找到所有长度相同的平行线集合
///
public static void GetParallelLineGroups(List lines, List> groups)
{
while (true)
{
var firstLine = lines.FirstOrDefault();
lines = lines.FindAll(curve => curve is Line);
List group = [firstLine];
groups.Add(group);
var line = firstLine as Line;
lines.Remove(firstLine);
for (var i = lines.Count - 1; i >= 0; i--)
{
if (lines[i] is Line tempLine)
{
if (
line != null
&& tempLine.Direction.CrossProduct(line.Direction).IsAlmostEqualTo(XYZ.Zero)
&& Math.Abs(tempLine.Length - line.Length) < 0.00001
)
{
group.Add(tempLine);
lines.Remove(tempLine);
}
}
}
if (lines.Count >= 1)
{
continue;
}
break;
}
}
///
/// 投影到一个平面
///
///
///
///
public static XYZ GetProjectPoint(this Plane plane, XYZ point)
{
var tf = Transform.Identity;
tf.BasisX = plane.XVec;
tf.BasisY = plane.YVec;
tf.BasisZ = plane.Normal;
tf.Origin = plane.Origin;
//把point转到plane的逆矩阵坐标系上
var p = tf.Inverse.OfPoint(point);
//z为法向,等于0时,点在平面上
p = new XYZ(p.X, p.Y, 0);
return tf.OfPoint(p);
}
///
/// 计算两条线的交点
///
///
///
///
public static XYZ IntersectionPoint(this Curve curve1, Curve curve2, bool makeUnbound = false)
{
var unboundCurve1 = curve1.Clone();
var unboundCurve2 = curve2.Clone();
if (makeUnbound && !curve1.IsBound)
{
unboundCurve1.MakeUnbound();
}
if (makeUnbound && !curve2.IsBound)
{
unboundCurve2.MakeUnbound();
}
var comparisonR = unboundCurve1.Intersect(unboundCurve2, out var intersectionR);
XYZ intersectionResult = null;
if (SetComparisonResult.Disjoint != comparisonR) // Disjoint无交点
{
try
{
if (intersectionR != null && !intersectionR.IsEmpty)
{
intersectionResult = intersectionR.get_Item(0).XYZPoint;
}
}
catch
{
// ignored
}
}
return intersectionResult;
}
///
///
/// 上游分支
/// 下游分支
/// 下游主线
/// 上游主线
///
public static XYZ IntersectionTwoVectors(XYZ upstreamBranch, XYZ downstreamBranch, XYZ downstreamMain, XYZ upstreamMain)
{
var main = upstreamMain - downstreamMain;
var branch = upstreamBranch - downstreamBranch;
var xyz = downstreamMain - downstreamBranch;
var num1 = main.DotProduct(main); //main*main
var num2 = main.DotProduct(branch); //main*branch*cos α1
var num3 = branch.DotProduct(branch); //branch*branch
var num4 = main.DotProduct(xyz); //main*xyz*cos α2
var num5 = branch.DotProduct(xyz); //branch*xyz*cos α3
var num6 = (num1 * num3) - (num2 * num2); //(1-cos α1*cos α1)*num1*num3
double num7;
double num8;
if (num6 < 1E-08) //main和branch同向
{
num7 = 0.0;
if (num2 > num3) //main>branch
{
num8 = num4 / num2; //xyz*cos α2/branch
}
else
{
num8 = num5 / num3; //xyz*cos α3/branch
}
}
else
{
num7 = ((num2 * num5) - (num3 * num4)) / num6; //xyz*(cos α1*cos α3-cos α2)/((1-cos α1*cos α1)*main)
num8 = ((num1 * num5) - (num2 * num4)) / num6; //xyz*(cos α3-cos α1*cos α2)/((1-cos α1*cos α1)*branch)
}
var xyz4 = downstreamMain + (num7 * main);
var xyz5 = downstreamBranch + (num8 * branch);
return (xyz5 + xyz4) / 2.0;
}
///
/// 平面与直线的交点,平行时为null
///
///
///
/// 交点
public static XYZ IntersectPoint(this Plane plane, Line line)
{
var startPoint = line.Origin;
var intersectLine = line;
if (intersectLine.IsBound)
{
intersectLine = line.Clone() as Line;
intersectLine.MakeUnbound();
}
var projectPoint = ProjectOf(plane, startPoint);
if (Math.Abs(intersectLine.Direction.DotProduct(plane.Normal)) < 10e-6) //是否平行
{
return null;
}
var length = startPoint.DistanceTo(projectPoint);
if (length < 10e-6) //不平行,但刚好是Origin,即为交点
{
return projectPoint;
}
var vector = projectPoint - startPoint;
//var per = Line.CreateBound(startPoint, projectPoint);//垂线
if (vector.IsParallelTo(intersectLine.Direction))
{
return projectPoint;
}
var cos = plane.Normal.DotProduct(intersectLine.Direction);
if (cos < 0)
{
cos = plane.Normal.Negate().DotProduct(intersectLine.Direction);
}
var distance = length / cos;
return vector.Normalize().DotProduct(intersectLine.Direction) < 0
? startPoint - (intersectLine.Direction * distance)
: startPoint + (intersectLine.Direction * distance);
}
///
/// 是否水平
///
///
///
public static bool IsHorizontal(this PlanarFace face)
{
return Math.Abs(face.GetMinY() - face.GetMaxY()) < VbTolerance;
}
///
/// 点是否在直线范围内,且与端点距离大于等于一定距离
///
///
///
/// 两边端点距离
///
public static bool IsInsideEx(this Line line, XYZ point, double endParameter = 0.0)
{
if (line == null)
{
throw new ArgumentNullException(nameof(line));
}
if (line.IsBound)
{
var d1 = point.DistanceTo(line.GetEndPoint(0));
var d2 = point.DistanceTo(line.GetEndPoint(1));
return d1 + d2 - line.Length < VbTolerance && d1 >= endParameter && d2 >= endParameter;
}
return false;
}
///
/// 判断方向是否平行
///
///
///
///
public static bool IsParallelTo(this XYZ direction1, XYZ direction2)
{
return direction1.CrossProduct(direction2).IsZeroLength() || direction1.CrossProduct(direction2).IsAlmostEqualTo(XYZ.Zero);
}
///
/// 判断直线是否平行或重合
///
///
///
///
public static bool IsParallelTo(this Line line, Line checkedLine)
{
var a = line.Direction.CrossProduct(checkedLine.Direction).IsZeroLength();
var b = line.Direction.CrossProduct(checkedLine.Direction).IsAlmostEqualTo(XYZ.Zero);
var c = 1 - Math.Abs(line.Direction.DotProduct(checkedLine.Direction)) < VbTolerance;
return a || b || c;
}
///
/// 判断直线是否垂直
///
///
///
///
///
public static bool IsPerpendicularTo(this Line line, Line checkedLine, double tolerance = VbTolerance)
{
return Math.Abs(line.Direction.DotProduct(checkedLine.Direction)) < tolerance; //点乘满足交换律不影响计算结果
}
/// 测试输入的实体或壳体是否可用于细分曲面
/// 固体或外壳
/// 如果实体或外壳对细分有效,则为 true,否则为 false
public static bool IsValidForTessellation(this Solid solid)
{
return SolidUtils.IsValidForTessellation(solid);
}
///
/// 是否是垂直面
///
///
///
public static bool IsVertical(this PlanarFace face)
{
return Math.Abs(face.GetMinX() - face.GetMaxX()) < VbTolerance;
}
///
/// 得到垂足
///
///
///
/// 经过该点作与p0,p1所在直线的垂线
///
public static XYZ PerpendicularIntersection(this XYZ pX, XYZ p0, XYZ p1)
{
//直线向量和p0点到px点的点积
var v0 = pX - p0;
var v1 = p1 - p0;
var dotProduct = v0.DotProduct(v1); //|v0||v1|cos α
var length = v1.GetLength(); //直线的长度|v1|
var normalize = v1 / length; //直线单位向量
var m = dotProduct / length; //垂足的模
return (normalize * m) + p0;
//var nu3 = nu / (nu2 * nu2);
//XYZ result = p0 + v1 * nu3;
//return result;
//double num = (pX.X - p0.X) * (p1.X - p0.X) + (pX.Y - p0.Y) * (p1.Y - p0.Y) + (pX.Z - p0.Z) * (p1.Z - p0.Z);
//double num2 = p0.DistanceTo(p1);//直线的长度
//num /= num2 * num2;
//return new XYZ(p0.X + num * (p1.X - p0.X), p0.Y + num * (p1.Y - p0.Y), p0.Z + num * (p1.Z - p0.Z));
}
///
/// 点在平面的投影
///
///
///
///
public static XYZ ProjectOf(this PlanarFace plane, XYZ p)
{
var v = p - plane.Origin;
//垂向距离(可正可负,夹角钝角为负)
var d = v.DotProduct(plane.FaceNormal);
return p - (d * plane.FaceNormal);
}
///
/// 点在平面的投影
///
///
///
///
public static XYZ ProjectOf(this Plane plane, XYZ p)
{
var v = p - plane.Origin;
//垂向距离(可正可负,夹角钝角为负)
var d = v.DotProduct(plane.Normal);
return p - (d * plane.Normal);
}
///
/// 直线在几何平面上的投影
///
///
///
///
public static Line ProjectOnto(this PlanarFace plane, Line line)
{
return Line.CreateBound(plane.ProjectOf(line.GetEndPoint(0)), plane.ProjectOf(line.GetEndPoint(1)));
}
///
/// 直线在平面上的投影
///
///
///
///
public static Line ProjectOnto(this Plane plane, Line line)
{
return Line.CreateBound(plane.ProjectOf(line.GetEndPoint(0)), plane.ProjectOf(line.GetEndPoint(1)));
}
/////
///// 点在几何面的投影
/////
/////
/////
/////
//public static XYZ ProjectOnto(this PlanarFace plane, XYZ p)
//{
// var d1 = plane.ProjectOf(p, out _);
// var q = p - (d1 * plane.FaceNormal);
// return q;
//}
///
/// 点拍平到xy平面
///
///
///
public static XYZ Flatten(this XYZ xyz)
{
return xyz.Subtract(XYZ.BasisZ * xyz.Z);
}
///
/// 设置元素定位线
///
/// 以点定位的实例
///
///
public static void SetLocationCurve(this Element instance, Curve curve)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (curve == null)
{
throw new ArgumentNullException(nameof(curve));
}
//var sourceCurve=instance.GetCurve();
if (instance.Location is LocationCurve lc)
{
try
{
lc.Curve = curve;
}
catch (Exception)
{
throw new InvalidOperationException("无法修改定位线");
}
}
}
///
/// 获取元素定位线
///
/// 以点定位的实例
///
///
public static void SetLocationPoint(this Element instance, XYZ xyz)
{
if (instance == null)
{
throw new ArgumentNullException(nameof(instance));
}
if (xyz == null)
{
throw new ArgumentNullException(nameof(xyz));
}
if (instance.Location is LocationPoint lp)
{
try
{
lp.Point = xyz;
}
catch (Exception)
{
throw new InvalidOperationException("无法修改定位点");
}
}
}
/// 将实体几何分割成多个独立实体
///
/// 如果不进行拆分,输出数组将返回输入实体的副本
///
/// The solid
/// 分割实体几何图形
///
/// 分割实体几何体失败
///
public static IList SplitVolumes(this Solid solid)
{
return SolidUtils.SplitVolumes(solid);
}
///
/// 此函数对实体或开口壳体进行切面(即三角化)。实体或外壳的每个边界
/// 实体或外壳的每个边界部分都由一个三角形结构来表示
///
///
/// 实体(或壳体)边界组件三角剖分上的每一点都应位于某个 "精度 "输入项指定的三维距离之内。
/// 壳体)上的每一点都应位于 "精确度 "输入所指定的三维距离内,反之亦然。
/// 三角剖面上的某一点的 "精度 "输入所指定的三维距离之内,反之亦然。在某些情况下,可以
/// 启发式地(而非严格地)执行
///
/// 要切面的固体或外壳
///
/// 该输入可控制三角测量的各个方面
///
///
/// 与输入实体的边界成分或输入壳体的成分相对应的三角形结构
/// 输入实体或输入壳体的分量
///
///
/// solidOrShell 对三角剖分无效(例如,它不包含任何面)
///
///
/// 无法对实体或外壳进行三角测量
///
public static TriangulatedSolidOrShell TessellateSolidOrShell(this Solid solid, SolidOrShellTessellationControls tessellationControls)
{
return SolidUtils.TessellateSolidOrShell(solid, tessellationControls);
}
///
/// 变换坐标点等同于transform.OfPoint()
///
/// 点,坐标值为在transform(相对坐标系)坐标系上的值
/// 用于变换的坐标系
/// 在transform坐标系上的点,在绝对坐标系上的值
public static XYZ TransformPoint(this XYZ point, Transform transform)
{
var x = point.X;
var y = point.Y;
var z = point.Z;
var b0 = transform.get_Basis(0);
var b1 = transform.get_Basis(1);
var b2 = transform.get_Basis(2);
var origin = transform.Origin;
var xTemp = (x * b0.X) + (y * b1.X) + (z * b2.X) + origin.X;
var yTemp = (x * b0.Y) + (y * b1.Y) + (z * b2.Y) + origin.Y;
var zTemp = (x * b0.Z) + (y * b1.Z) + (z * b2.Z) + origin.Z;
return new XYZ(xTemp, yTemp, zTemp);
}
#if REVIT2025
/// 查找输入 EdgeEndPoint 所标识顶点上的所有 EdgeEndPoints
/// 标识顶点的输入 EdgeEndPoint
/// 顶点上的所有边缘端点。输入的边缘端点也包括在内
///
/// 查找输入 EdgeEndPoint 所标识顶点上的所有 EdgeEndPoints失败
///
[Pure]
public static IList FindAllEdgeEndPointsAtVertex(this EdgeEndPoint edgeEndPoint)
{
return SolidUtils.FindAllEdgeEndPointsAtVertex(edgeEndPoint);
}
#endif
}