Building Coder(Revit 二次开发) - 使用 FindReferencesByDirection 创建墙体之间的尺寸标注

原文链接:Dimension Walls using FindReferencesByDirection


几天前我展示了一个新的 Building Coder 命令 CmdDimensionWallsIterateFaces。它可以在两面墙体的相对面之间创建尺寸标注。方法是首先遍历墙体的实体几何特征和面,然后调用 Revit 几何库来计算安全的引用用以附着尺寸标注元素。遍历几何特征,搜索需要的几何面并提取引用都可以通过
使用 FindReferencesByDirection 方法来简化。

我进一步研究了这个主题并且实现了新的使用 FindReferencesByDirection 的解决方案和一个相关的外部命令:CmdDimensionWallsFindRefs。

新的方案解决了之前方案的多个缺陷:

  1. 尺寸标注线只能连接两面墙体的中点(midpoint)。新方案中对尺寸标注线与墙体的交点的位置没有要求。用户可以在第二面墙体上选取尺寸标注线的交点位置。当然,两面墙体依然需要平行。另外第一面墙体到第二面墙体位置线的投影必须经过第二面墙体上尺寸标注线的交点。
  2. 用于创建尺寸标注线的引用数组直接从 FindReferencesByDirection 方法的返回引用中创建。该方法发出一条经过 Revit 模型的射线,并且报告与该射线相交的元素、元素的面、以及这些面与射线的相交点。我们从这些引用中提取出指定墙体的表面的引用,然后用这些引用直接创建尺寸标注。


按照新方案实现的外部命令更加可靠和易用。

新的外部命令还展示了如何使用 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 例程,里面包含了上面提到的新老两种解决方案对应的外部命令。



你可能感兴趣的:(null,application,reference,templates,parallel,distance)