原文链接:Dimension Walls using FindReferencesByDirection
几天前我展示了一个新的 Building Coder 命令 CmdDimensionWallsIterateFaces。它可以在两面墙体的相对面之间创建尺寸标注。方法是首先遍历墙体的实体几何特征和面,然后调用 Revit 几何库来计算安全的引用用以附着尺寸标注元素。遍历几何特征,搜索需要的几何面并提取引用都可以通过
使用 FindReferencesByDirection 方法来简化。
我进一步研究了这个主题并且实现了新的使用 FindReferencesByDirection 的解决方案和一个相关的外部命令:CmdDimensionWallsFindRefs。
新的方案解决了之前方案的多个缺陷:
按照新方案实现的外部命令更加可靠和易用。
新的外部命令还展示了如何使用 PickObject 方法和一个实现了 ISelectionFilter 接口的选择过滤器来选取墙体。新命令还展示了 PickObject 方法如何在选择第二面墙体时如何返回一个选取点,然后要求用户选择尺寸标注线的位置。
下面是选择过滤器的实现,它只允许用户在一面墙体上选择一个点。
class WallSelectionFilter : ISelectionFilter { public bool AllowElement( Element e ) { return e is Wall; } public bool AllowReference( Reference r, XYZ p ) { return true; } }
调用 FindReferencesByDirection 方法要求一个 3D 视图。我们首先假设文档有一个适合的 3D 视图,并且已经实现了下面的帮助方法(调用元素过滤收集器)来获取第一个符合条件的 3D 视图。
private View3D Get3DView( Document doc ) { FilteredElementCollector collector = new FilteredElementCollector( doc ); collector.OfClass( typeof( View3D ) ); foreach( View3D v in collector ) { // skip view templates here because they // are invisible in project browsers: if( v != null && !v.IsTemplate && v.Name == "{3D}" ) { return v; } } return null; }
有了上面两个帮助方法,再加上老方案的外部命令 CmdDimensionWallsIterateFaces 中实现的 CreateDimensionElement 方法,新方案的实现代码就呼之欲出了。
/// <summary> /// Dimension two opposing parallel walls. /// Prompt user to select the first wall, and /// the second at the point at which to create /// the dimensioning. Use FindReferencesByDirection /// to determine the wall face references. /// </summary> [Transaction( TransactionMode.Manual )] [Regeneration( RegenerationOption.Manual )] class CmdDimensionWallsFindRefs : IExternalCommand { const string _prompt = "Please select two parallel straight walls" + " with a partial projected overlap."; public Result Execute( ExternalCommandData commandData, ref string message, ElementSet elements ) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; // select two walls and the dimension line point: Selection sel = uidoc.Selection; ReferenceArray refs = new ReferenceArray(); try { WallSelectionFilter f = new WallSelectionFilter(); refs.Append( sel.PickObject( ObjectType.Element, f, "Please select first wall" ) ); refs.Append( sel.PickObject( ObjectType.Element, f, "Please pick dimension line " + "point on second wall" ) ); } catch( OperationCanceledException ) { message = "No two walls selected"; return Result.Failed; } // ensure the two selected walls are straight and // parallel; determine their mutual normal vector // and a point on each wall for distance // calculations: Wall[] walls = new Wall[2]; List<int> ids = new List<int>( 2 ); XYZ[] pts = new XYZ[2]; Line[] lines = new Line[2]; IntersectionResult ir; XYZ normal = null; int i = 0; foreach( Reference r in refs ) { Wall wall = r.Element as Wall; walls[i] = wall; ids.Add( wall.Id.IntegerValue ); // obtain location curve and // check that it is straight: LocationCurve lc = wall.Location as LocationCurve; Curve curve = lc.Curve; lines[i] = curve as Line; if( null == lines[i] ) { message = _prompt; return Result.Failed; } // obtain normal vectors // and ensure that they are equal, // i.e. walls are parallel: if( null == normal ) { normal = Util.Normal( lines[i] ); } else { if( !Util.IsParallel( normal, Util.Normal( lines[i] ) ) ) { message = _prompt; return Result.Failed; } } // obtain pick points and project // onto wall location lines: XYZ p = r.GlobalPoint; ir = lines[i].Project( p ); if( null == ir ) { message = string.Format( "Unable to project pick point {0} " + "onto wall location line.", i ); return Result.Failed; } pts[i] = ir.XYZPoint; Debug.Print( "Wall {0} id {1} at {2}, {3} --> point {4}", i, wall.Id.IntegerValue, Util.PointString( lines[i].get_EndPoint( 0 ) ), Util.PointString( lines[i].get_EndPoint( 1 ) ), Util.PointString( pts[i] ) ); if( 0 < i ) { // project dimension point selected on second wall // back onto first wall, and ensure that normal // points from second wall to first: ir = lines[0].Project( pts[1] ); if( null == ir ) { message = string.Format( "Unable to project selected dimension " + "line point {0} on second wall onto " + "first wall's location line.", Util.PointString( pts[1] ) ); return Result.Failed; } pts[0] = ir.XYZPoint; } ++i; } XYZ v = pts[0] - pts[1]; if( 0 > v.DotProduct( normal ) ) { normal = -normal; } // invoke FindReferencesByDirection, shooting ray // back from second picked wall towards first: View3D view = Get3DView( doc ); refs = doc.FindReferencesByDirection( pts[1], normal, view ); Debug.Print( "Shooting ray from {0} direction " + "{1} returns {2} references", Util.PointString( pts[1] ), Util.PointString( normal ), refs.Size ); // store the references to the wall surfaces: Reference[] surfrefs = new Reference[2] { null, null }; // find the two closest intersection // points on each of the two walls: double[] minDistance = new double[2] { double.MaxValue, double.MaxValue }; foreach( Reference r in refs ) { Element e = r.Element; if( e is Wall ) { i = ids.IndexOf( e.Id.IntegerValue ); if( -1 < i && ElementReferenceType.REFERENCE_TYPE_SURFACE == r.ElementReferenceType ) { GeometryObject g = r.GeometryObject; if( g is PlanarFace ) { PlanarFace face = g as PlanarFace; Line line = ( e.Location as LocationCurve ) .Curve as Line; Debug.Print( "Wall {0} at {1}, {2} surface {3} " + "normal {4} proximity {5}", e.Id.IntegerValue, Util.PointString( line.get_EndPoint( 0 ) ), Util.PointString( line.get_EndPoint( 1 ) ), Util.PointString( face.Origin ), Util.PointString( face.Normal ), r.ProximityParameter ); // first reference: assert it is a face on this wall // and the distance is half the wall thickness // // second reference: the first reference on the other // wall; assert the distance between the two references // equals the distance between the wall location lines // minus half of the sum of the two wall thicknesses. if( r.ProximityParameter < minDistance[i] ) { surfrefs[i] = r; minDistance[i] = r.ProximityParameter; } } } } } if( null == surfrefs[0] ) { message = "No suitable face intersection " + "points found on first wall."; return Result.Failed; } if( null == surfrefs[1] ) { message = "No suitable face intersection " + "points found on second wall."; return Result.Failed; } CmdDimensionWallsIterateFaces .CreateDimensionElement( doc.ActiveView, pts[0], surfrefs[0], pts[1], surfrefs[1] ); return Result.Succeeded; } }
下图就是应用新方案绘制的水平尺寸标注和垂直尺寸标注。
http://thebuildingcoder.typepad.com/.a/6a00e553e1689788330147e2bff619970b-800wi
这里是 2011.0.86.0:http://thebuildingcoder.typepad.com/files/bc_11_86.zip2011.0.86.0 版本的 Building Coder 例程,里面包含了上面提到的新老两种解决方案对应的外部命令。