Search code examples
c#revit-apirevit

Mirroring details inside a section view using the Revit API


I have some detail lines inside a section view that I would like to mirror across the midpoint of the CropBox for the section. I'm using the ElementTransformUtils.MirrorElements to try and accomplish this. However, I have not yet been able to find the correct plane upon which to mirror the detail lines. I've tried Plane.CreateByNormalAndOrigin(section.ViewDirection, section.Origin) and detailLine.SketchPlane.GetPlane() but none of those have mirrored the detail lines.

Mirroring Detail Lines

What is the correct way to accomplish this?


Solution

  • I was able to get my details to mirror across the midpoint of the ViewSection.CropBox but it was rather complicated. I discovered that the ViewSection.Origin is a point in the project coordinate system. The ViewSection.CropBox, on the other hand, does not give you its Min and Max in the project coordinate system but instead gives you the location of the CropBox relative to the ViewSection.Origin. Using the ViewSection.Origin and ViewSection.CropBox you can calculate the Min and Max of the ViewSection.CropBox in the project coordinate system. Finally, you need to use those Min and Max values to find the midpoint of the ViewSection.CropBox and then mirror across that point.

    Here are some helper classes I use so that I can find the Min and Max using a single function

    public enum PointComponent
    {
      Invalid,
      X,
      Y,
      Z
    }
    
    public enum MinOrMax
    {
      Invalid,
      Min,
      Max
    }
    

    Here's the function to find the X, Y, or Z component of the Min or Max of the CropBox:

    private double GetSectionCropBoxOrigin(ViewSection section,
      PointComponent pointComponent, MinOrMax minOrMax)
    {
      double cropBoxOrigin;
    
      double sectionOrigin, viewDirection, upDirection, rightDirection;
    
      switch (pointComponent)
      {
        case PointComponent.X:
          sectionOrigin = section.Origin.X;
          viewDirection = section.ViewDirection.X;
          upDirection = section.UpDirection.X;
          rightDirection = section.RightDirection.X;
          break;
        case PointComponent.Y:
          sectionOrigin = section.Origin.Y;
          viewDirection = section.ViewDirection.Y;
          upDirection = section.UpDirection.Y;
          rightDirection = section.RightDirection.Y;
          break;
        case PointComponent.Z:
          sectionOrigin = section.Origin.Z;
          viewDirection = section.ViewDirection.Z;
          upDirection = section.UpDirection.Z;
          rightDirection = section.RightDirection.Z;
          break;
        default:
          throw new InvalidOperationException();
      }
    
      double cropX, cropY;
    
      switch (minOrMax)
      {
        case MinOrMax.Min:
          cropX = section.CropBox.Min.X;
          cropY = section.CropBox.Min.Y;
          break;
        case MinOrMax.Max:
          cropX = section.CropBox.Max.X;
          cropY = section.CropBox.Max.Y;
          break;
        default:
          throw new InvalidOperationException();
      }
    
      if (Math.Abs(viewDirection) == 1)
        cropBoxOrigin = sectionOrigin;
      else if (Math.Abs(upDirection) == 1)
        cropBoxOrigin = sectionOrigin + (cropY * upDirection);
      else if (Math.Abs(rightDirection) == 1)
        cropBoxOrigin = sectionOrigin + (cropX * rightDirection);
      else
        throw new InvalidOperationException();
    
      return cropBoxOrigin;
    }
    

    Notice that for PointComponent.Z we do not use the midpoint between Min and Max values and instead use the origin. This is because our detail work only exists in 2D even though we specify a CropBox depth and the detail work sits at the same depth as the ViewSection.Origin.

    Here's the code I use to get the midpoint:

    private XYZ Get3dMidpoint(XYZ start, XYZ end)
    {
        double x = (start.X + end.X) / 2.0;
        double y = (start.Y + end.Y) / 2.0;
        double z = (start.Z + end.Z) / 2.0;
    
        return new XYZ(x, y, z);
    }
    

    And finally here's the code to bring it all together and get the mirror plane:

    XYZ cropBoxMinInGlobalCoordinates = new XYZ(
        GetSectionCropBoxOrigin(section, PointComponent.X, MinOrMax.Min),
        GetSectionCropBoxOrigin(section, PointComponent.Y, MinOrMax.Min),
        GetSectionCropBoxOrigin(section, PointComponent.Z, MinOrMax.Min)
    );
    XYZ cropBoxMaxInGlobalCoordinates = new XYZ(
        GetSectionCropBoxOrigin(section, PointComponent.X, MinOrMax.Max),
        GetSectionCropBoxOrigin(section, PointComponent.Y, MinOrMax.Max),
        GetSectionCropBoxOrigin(section, PointComponent.Z, MinOrMax.Max)
    );
    XYZ cropBoxOriginInGlobalCoordinates = 
      Get3dMidpoint(cropBoxMinInGlobalCoordinates, cropBoxMaxInGlobalCoordinates);
    
    Plane mirrorPlane = 
      Plane.CreateByNormalAndOrigin(section.RightDirection, cropBoxOriginInGlobalCoordinates);