如何在XBim中获取IFC家具与卫生构件的有向边界框或凸包?
使用XBim提取IFC家具/卫生构件的2D轮廓(凸包/非轴对齐边界框)
要实现这个需求,核心流程是:获取构件的3D几何数据 → 投影到目标2D平面 → 计算凸包或非轴对齐边界框。以下是具体步骤和代码实现:
1. 初始化几何上下文
XBim需要先创建几何上下文来解析IFC中的几何表示,提前预生成所有构件的几何数据能提升后续查询效率:
using var store = IfcStore.Open(myFile); using var context = new Xbim3DModelContext(store); context.CreateContext(); // 预生成所有构件的几何信息,首次执行耗时较长
2. 遍历构件并处理几何
以家具为例,卫生构件(IIfcSanitaryTerminal)的处理逻辑完全一致,只需替换类型即可:
var furnitures = store.Instances.OfType<IIfcFurniture>(); foreach (var furniture in furnitures) { // 获取构件的三角化3D形状(XBim默认以三角面存储几何) var shape = context.ShapeRepository.GetShape(furniture); if (shape == null || shape.GeometryType != XbimGeometryType.Triangulation) continue; // 将3D顶点投影到XY平面(可根据需求调整投影规则,比如取构件所在楼层的Z标高) var triangulation = shape.Geometry as XbimTriangulation; var projectedPoints = triangulation.Vertices .Select(v => new Point2D(v.X, v.Y)) .ToList(); // 计算非轴对齐边界框 var orientedBbox = CalculateOrientedBoundingBox(projectedPoints); // 计算凸包 var convexHullPoints = CalculateConvexHull(projectedPoints); // 输出结果示例 Console.WriteLine($"构件ID: {furniture.GlobalId}"); Console.WriteLine($"非轴对齐边界框:中心({orientedBbox.Center.X:F2}, {orientedBbox.Center.Y:F2}),半长({orientedBbox.HalfLengths.X:F2}, {orientedBbox.HalfLengths.Y:F2}),旋转角{orientedBbox.Rotation * 180 / Math.PI:F1}°"); Console.WriteLine($"凸包顶点数:{convexHullPoints.Count}"); }
3. 实现2D计算方法
XBim没有直接提供凸包和非轴对齐边界框的API,需要自己实现计算逻辑:
凸包计算(Andrew算法)
private static List<Point2D> CalculateConvexHull(List<Point2D> points) { if (points.Count <= 1) return points.ToList(); // 按X坐标排序,X相同则按Y排序 var sorted = points.OrderBy(p => p.X).ThenBy(p => p.Y).ToList(); var lower = new List<Point2D>(); foreach (var p in sorted) { // 移除凹点 while (lower.Count >= 2 && CrossProduct(lower[lower.Count-2], lower[lower.Count-1], p) <= 0) lower.RemoveAt(lower.Count-1); lower.Add(p); } var upper = new List<Point2D>(); foreach (var p in sorted.Reverse()) { while (upper.Count >= 2 && CrossProduct(upper[upper.Count-2], upper[upper.Count-1], p) <= 0) upper.RemoveAt(upper.Count-1); upper.Add(p); } // 合并并去重首尾重复点 lower.RemoveAt(lower.Count-1); upper.RemoveAt(upper.Count-1); lower.AddRange(upper); return lower; } // 计算叉积:判断三点构成的转向,>0为逆时针,<0为顺时针,=0共线 private static double CrossProduct(Point2D a, Point2D b, Point2D c) { return (b.X - a.X) * (c.Y - a.Y) - (b.Y - a.Y) * (c.X - a.X); }
非轴对齐边界框计算(PCA主成分分析)
public class OrientedBbox2D { public Point2D Center { get; set; } public Vector2D HalfLengths { get; set; } public double Rotation { get; set; } // 与X轴的夹角(弧度) } private static OrientedBbox2D CalculateOrientedBoundingBox(List<Point2D> points) { if (points.Count == 0) return null; // 计算中心点 double meanX = points.Average(p => p.X); double meanY = points.Average(p => p.Y); var center = new Point2D(meanX, meanY); // 计算协方差矩阵 double covXX = 0, covXY = 0, covYY = 0; foreach (var p in points) { double dx = p.X - meanX; double dy = p.Y - meanY; covXX += dx * dx; covXY += dx * dy; covYY += dy * dy; } covXX /= points.Count; covXY /= points.Count; covYY /= points.Count; // 求解特征值,获取主方向 double trace = covXX + covYY; double det = covXX * covYY - covXY * covXY; double sqrtDet = Math.Sqrt(Math.Abs(trace * trace / 4 - det)); double lambda1 = trace / 2 + sqrtDet; // 最大特征值对应主方向 double dirX, dirY; if (covXY == 0) { dirX = lambda1 > covYY ? 1 : 0; dirY = lambda1 > covYY ? 0 : 1; } else { dirX = lambda1 - covYY; dirY = covXY; double length = Math.Sqrt(dirX * dirX + dirY * dirY); dirX /= length; dirY /= length; } // 将点投影到主方向和垂直方向,计算边界 var u = new Vector2D(dirX, dirY); var v = new Vector2D(-dirY, dirX); // 垂直于主方向的向量 double minU = double.MaxValue, maxU = double.MinValue; double minV = double.MaxValue, maxV = double.MinValue; foreach (var p in points) { double dx = p.X - meanX; double dy = p.Y - meanY; double projU = dx * u.X + dy * u.Y; double projV = dx * v.X + dy * v.Y; minU = Math.Min(minU, projU); maxU = Math.Max(maxU, projU); minV = Math.Min(minV, projV); maxV = Math.Max(maxV, projV); } return new OrientedBbox2D { Center = center, HalfLengths = new Vector2D((maxU - minU)/2, (maxV - minV)/2), Rotation = Math.Atan2(dirY, dirX) }; }
注意事项
- 投影平面可自定义:如果构件不在XY平面,可通过
furniture.ObjectPlacement获取构件的空间定位,将3D点转换到世界坐标系后再投影到目标平面(比如楼层标高的Z平面)。 - 复杂构件处理:部分构件可能包含多个几何表示,
context.ShapeRepository.GetShape()会返回合并后的整体形状,无需额外处理。 - 卫生构件适配:只需将
OfType<IIfcFurniture>()替换为OfType<IIfcSanitaryTerminal>()即可复用所有逻辑。
内容的提问来源于stack exchange,提问作者emilaz
相关产品推荐
相关产品推荐

