应用程序名称:CurtainWallGrid

Revit 平台:所有

Revit 版本:2011.0

首次发布于:2009.0

编程语言:C#

技能级别:中等

类别:元素

类型:外部命令

主题:创建幕墙并操作其幕墙网格。

概要:

本示例演示了五个主要功能:

1. 如何根据特定的层数和墙体类型创建幕墙。

2. 如何检索幕墙的幕墙网格信息。

3. 如何编辑幕墙网格的数据。

4. 如何检索幕墙网格的网格线。

5. 如何编辑幕墙网格的网格线和格栅横梃。

项目文件:

Command.cs

该文件包含了“Command”类,该类继承自“IExternalCommand”接口,实现了“Execute”方法。

MyDocument.cs

该文件包含了“MyDocument”类,用于管理本示例中使用的所有数据。

WallGeometry.cs

包含了一个名为“WallGeometry”的类,用于管理幕墙数据。它可以创建新的幕墙,并获取幕墙的数据。

WallDrawing.cs

包含了一个名为“WallDrawing”的类,用于在画布上指定幕墙的基线。

GridGeometry.cs

包含了一个名为“GridGeometry”的类,用于管理幕墙中的幕墙网格和幕墙网格线信息。它可以检索幕墙网格和幕墙网格线的数据;也可以编辑它们的数据。

GridDrawing.cs

包含了一个名为“GridDrawing”的类,用于管理画布上幕墙网格和幕墙网格线的2D视图。

GridCoordinates.cs

包含了一个名为“GridCoordinates”的类,用于管理幕墙网格在3D和2D格式之间的变换矩阵。

MathTools.cs

包含了一个名为“MathTools”的类,用于管理变换中使用的数学类。

功能:

- 创建指定层和墙类型的幕墙。

- 检索幕墙的幕布网格。

- 检索所有幕布网格的属性。

- 检索幕布网格线并编辑它们:添加新的网格线,锁定/解锁网格线,移动网格线,添加/删除段,添加所有段。(注意:它模仿部分UI操作,如面板自动合并功能等,但仅支持简单情况。如果对幕布网格进行复杂操作,样本的外观可能与UI不同。)

- 检索所有附加的柱子并操纵它们:将柱子添加到幕布网格段中并删除幕布网格的柱子。(注意:由于Mullion类的限制,只允许将柱子添加到网格线的段中,不能将柱子添加到幕布网格的边缘。)

实现:

- 使用元素过滤器获取活动文档中的所有视图。

- 使用适当的幕墙类型通过Autodesk.Revit.Creation.Document.NewWall(Curve curve, WallType wallType, Level level, double height, double offset, bool flip, bool structural)方法创建幕墙。

- 通过CurtainGridLine AddGridLine(bool isConstVisUGridLine, Revit.Geometry.XYZ position,bool oneSegmentOnly)添加幕布网格线。

- 通过bool Move (Revit.Geometry.XYZ moveVec)移动幕布网格线。

- 通过void AddSegment(Revit.Geometry.Curve^ curve)添加一个段。

- 通过void RemoveSegment(Revit.Geometry.Curve^ curve)移除一个段。

- 通过ElementSet AddMullions(Curve segmentCurve, MullionType mullionType,bool oneSegmentOnly)添加一个柱子。

指南:

1. 打开Revit Architecture 2009并创建一个新文档。

2. 运行此命令。

3. 弹出一个“Curtain Wall”对话框,其中有2个选项卡,第一个是“Create Curtain Wall”,另一个是“Curtain Grid”,此时仅限于第一个选项卡。

4. 所有视图和墙型在两个下拉列表中列出,通过从列表中选择一个来指定它们,指定的视图和墙型将用于幕墙的创建。

5. 幕墙创建需要一个基准线。左键单击画布左侧以指定基准线。在绘制基准线时按“ESC”键停止绘制基准线。单击“清除基准线”按钮以重新绘制基准线,单击“创建幕墙”按钮以使用指定的基准线、视图和墙型创建幕墙。完成后,可以切换到“Curtain Grid”选项卡页。

6. 幕布网格的所有属性都列在右侧的属性网格中。

7. 幕墙的幕布网格在左侧的画布上绘制出来。

8. 所有支持的幕布网格和幕布网格线操作:添加水平网格线、添加竖直网格线、锁定或解锁网格线、移动网格线、添加段、删除段和添加所有段

9. 所有幕布网格和幕布网格线的操作都限制在幕布网格的边界内,这意味着鼠标指针必须移动到边界内,操作才能正常工作。

10. 连续操作:默认情况下,在一次操作完成后,操作将停止并改为“等待”。例如:在绘制一条水平网格线后,用户应该从组合框中指定另一个操作。如果用户想一次性绘制多条水平网格线,他/她应该按下“Ctrl”键并保持连续操作。例如:从下拉列表中选择“添加水平网格线”,按下“Ctrl”键,在画布上绘制一条水平线,之后用户可以在没有再次指定操作的情况下绘制另一条水平线。如果用户想停止连续操作,他/她可以释放“Ctrl”键并执行最后一个操作,在最后一个操作完成后,操作将停止并改为“等待”;他/她也可以按“ESC”键强制停止当前操作,并将操作改为“等待”。

11. 对于“添加水平网格线”,将鼠标移动到幕布网格区域内,将显示一条虚线水平网格线,它是即将添加的新水平网格线,单击左键将其添加到幕布网格中。不允许添加一个网格线重叠另一个网格线。支持Ctrl键和ESC键。

12. 对于“添加竖直网格线”,与“添加水平网格线”类似,只是虚线是竖直的。支持Ctrl键和ESC键。

13. 对于“锁定或解锁网格线”,将鼠标指针移动到网格线上悬停,网格线的锁定状态将显示为“已锁定”或“未锁定”提示,单击左键切换锁定状态,提示将更新。

14. 对于“移动网格线”,仅允许移动未锁定的网格线,因此在移动网格线之前确保网格线未锁定。单击未锁定的网格线来决定要移动哪条网格线,然后移动鼠标来决定将网格线移动到哪个位置。再次单击左键完成移动网格线。支持Ctrl键和ESC键。

15. 对于“添加段”,只允许在先前删除段的地方添加段,因此默认情况下无法执行“添加段”操作,因为还没有删除段。首先删除一些段,然后使用此操作将它们添加回去。支持Ctrl键和ESC键。

16. 对于“删除段”,一条段不允许被删除两次,这意味着我们只能删除之前没有删除的段。支持Ctrl键和ESC键。

17. 删除一个段可能会导致其他“共线-悬挂”段自动被删除。例如,在图1中,删除“段A”(水平的、红色高亮显示的段)将使“段B”(水平的、棕色的)自动被删除,因为“段B”是一条“共线-悬挂”段。

18.在“删除分段”功能和“自动删除”功能中,不允许删除窗帘网格线的最后一个分段。例如,在图2中,由于“分段B”是窗帘网格线的最后一个分段,所以不允许删除它。 在图3中,删除“分段A”将导致“分段B”成为“共线悬挂”,并尝试删除“分段B”,但是由于“分段C”已经被删除,所以“分段B”将成为其窗帘网格线的最后一个分段,所以不允许删除“分段A”。因此,也适用于“自动删除”功能。

19.在“删除分段”功能中,删除一个分段可能会自动隐藏其他“交叉悬挂”的分段。例如,在图4中,删除“分段A”会使“分段B”成为“交叉悬挂”,因此“分段B”将被标记为灰色,表明它被自动隐藏了(图5),就像Revit UI一样(在界面上,它是不可见的,只有在鼠标悬停时才会显示)。

20. “添加整个网格线的分段”功能类似于“添加分段”,只允许将此操作应用于已删除分段的网格线。支持Ctrl键和ESC键。

21. “添加所有分段的框条”功能将在窗帘网格的所有可见分段上添加框条。(注意:由于Mullion类的限制,无法在窗帘网格的边缘添加框条。)

22. “删除所有框条”功能将删除窗帘网格中的所有框条。

23. 单击任一选项卡页面中的“退出”按钮将关闭对话框。

完整的源代码请加入QQ群649037449,在群文件中下载RevitSDK.exe,解压后在文件夹中搜索本文中应用程序名称即可获得完整源码

WallGeometry.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace Revit.SDK.Samples.CurtainWallGrid.CS
{
   /// <summary>
   /// the class manages the creation operation for the curtain wall
   /// </summary>
   public class WallGeometry
   {
      #region Fields
      // the document of this sample
      MyDocument m_myDocument;

      // the refferred drawing class for the curtain wall
      private WallDrawing m_drawing;

      // the selected ViewPlan used for curtain wall creation
      Autodesk.Revit.DB.ViewPlan m_selectedView;

      // the selected wall type
      WallType m_selectedWallType;

      // store the start point of baseline (in PointD format)
      PointD m_startPointD;

      //store the start point of baseline (in Autodesk.Revit.DB.XYZ format)
      Autodesk.Revit.DB.XYZ m_startXYZ;

      // store the end point of baseline (in PointD format)
      PointD m_endPointD;

      //store the end point of baseline (in Autodesk.Revit.DB.XYZ format)
      Autodesk.Revit.DB.XYZ m_endXYZ;
      #endregion

      #region Properties
      /// <summary>
      /// the document of this sample
      /// </summary>
      public MyDocument MyDocument
      {
         get
         {
            return m_myDocument;
         }
      }

      /// <summary>
      /// the refferred drawing class for the curtain wall
      /// </summary>
      public WallDrawing Drawing
      {
         get
         {
            return m_drawing;
         }
      }

      /// <summary>
      /// the selected ViewPlan used for curtain wall creation
      /// </summary>
      public Autodesk.Revit.DB.ViewPlan SelectedView
      {
         get
         {
            return m_selectedView;
         }
         set
         {
            m_selectedView = value;
         }
      }

      /// <summary>
      /// the selected wall type
      /// </summary>
      public WallType SelectedWallType
      {
         get
         {
            return m_selectedWallType;
         }
         set
         {
            m_selectedWallType = value;
         }
      }

      /// <summary>
      /// store the start point of baseline (in PointD format)
      /// </summary>
      public PointD StartPointD
      {
         get
         {
            return m_startPointD;
         }
         set
         {
            m_startPointD = value;
         }
      }

      /// <summary>
      /// Get start point of baseline
      /// </summary>
      public Autodesk.Revit.DB.XYZ StartXYZ
      {
         get
         {
            return m_startXYZ;
         }
         set
         {
            m_startXYZ = value;
         }
      }

      /// <summary>
      /// store the end point of baseline (in PointD format)
      /// </summary>
      public PointD EndPointD
      {
         get
         {
            return m_endPointD;
         }
         set
         {
            m_endPointD = value;
         }
      }

      /// <summary>
      /// Get end point of baseline
      /// </summary>
      public Autodesk.Revit.DB.XYZ EndXYZ
      {
         get
         {
            return m_endXYZ;
         }
         set
         {
            m_endXYZ = value;
         }
      }
      #endregion

      #region Constructors
      /// <summary>
      /// default constructor
      /// </summary>
      /// <param name="myDoc">
      /// the document of the sample
      /// </param>
      public WallGeometry(MyDocument myDoc)
      {
         m_myDocument = myDoc;
         m_drawing = new WallDrawing(this);
      }
      #endregion

      #region Public methods
      /// <summary>
      /// create the curtain wall to the active document of Revit
      /// </summary>
      /// <returns>
      /// the created curtain wall
      /// </returns>
      public Wall CreateCurtainWall()
      {
         if (null == m_selectedWallType || null == m_selectedView)
         {
            return null;
         }

         Autodesk.Revit.Creation.Document createDoc = m_myDocument.Document.Create;
         Autodesk.Revit.Creation.Application createApp = m_myDocument.CommandData.Application.Application.Create;
         //baseline
         System.Drawing.Point point0 = m_drawing.WallLine2D.StartPoint;
         System.Drawing.Point point1 = m_drawing.WallLine2D.EndPoint;
         //new baseline and transform coordinate on windows UI to Revit UI
         m_startXYZ = new Autodesk.Revit.DB.XYZ(m_startPointD.X, m_startPointD.Y, 0);
         m_endXYZ = new Autodesk.Revit.DB.XYZ(m_endPointD.X, m_endPointD.Y, 0);
         Autodesk.Revit.DB.Line baseline = null;
         try
         {
            baseline = Line.CreateBound(m_startXYZ, m_endXYZ);
         }
         catch (System.ArgumentException)
         {
            TaskDialog.Show("Revit", "The start point and the end point of the line are too close, please re-draw it.");
         }
         Transaction act = new Transaction(m_myDocument.Document);
         act.Start(Guid.NewGuid().GetHashCode().ToString());
         Wall wall = Wall.Create(m_myDocument.Document, baseline, m_selectedWallType.Id,
             m_selectedView.GenLevel.Id, 20, 0, false, false);
         act.Commit();
         Transaction act2 = new Transaction(m_myDocument.Document);
         act2.Start(Guid.NewGuid().GetHashCode().ToString());
         m_myDocument.UIDocument.ShowElements(wall);
         act2.Commit();
         return wall;
      }
      #endregion
   }// end of class
}

WallDrawing.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Resources;
using System.Reflection;

namespace Revit.SDK.Samples.CurtainWallGrid.CS
{
   /// <summary>
   /// maintain the baseline of the curtain wall
   /// </summary>
   public class WallDrawing
   {
      #region Fields
      // zoom the baseline to a suitable length
      public double SCALEFACTOR = 5.0;

      // the boundary of the canvas for the baseline drawing
      Rectangle m_boundary;

      // the origin for the baseline (it's the center of the canvas)
      Point m_origin;

      // the referred parent wall geometry
      WallGeometry m_refGeometry;

      // store the document of this sample
      MyDocument m_myDocument;

      // the font used in drawing the baseline of the curtain wall
      Font m_coordinateFont;

      // the baseline of the curtain wall
      WallBaseline2D m_wallLine2D;
      #endregion

      #region Properties
      /// <summary>
      /// the boundary of the canvas for the baseline drawing
      /// </summary>
      public Rectangle Boundary
      {
         get
         {
            return m_boundary;
         }
         set
         {
            m_boundary = value;
         }
      }

      /// <summary>
      /// the origin for the baseline (it's the center of the canvas)
      /// </summary>
      public Point Origin
      {
         get
         {
            return m_origin;
         }
         set
         {
            m_origin = value;
         }
      }

      /// <summary>
      /// the baseline of the curtain wall
      /// </summary>
      public WallBaseline2D WallLine2D
      {
         get
         {
            return m_wallLine2D;
         }
      }
      #endregion

      #region Constructors
      /// <summary>
      /// default constructor
      /// </summary>
      /// <param name="wallGeo">
      /// the mapped wall geometry information
      /// </param>
      public WallDrawing(WallGeometry wallGeo)
      {
         m_coordinateFont = new Font("Verdana", 10, FontStyle.Regular);
         m_wallLine2D = new WallBaseline2D();
         m_myDocument = wallGeo.MyDocument;
         m_refGeometry = wallGeo;
      }
      #endregion

      #region Public methods
      /// <summary>
      /// Add point to baseline of the curtain wall
      /// </summary>
      /// <param name="mousePosition">
      /// the location of the mouse cursor
      /// </param>
      public void AddPoint(Point mousePosition)
      {
         // both end points of the baseline specified, can't add more points
         if (Point.Empty != m_wallLine2D.StartPoint &&
             Point.Empty != m_wallLine2D.EndPoint)
         {
            return;
         }

         // start point isn't specified, specify it
         if (Point.Empty == m_wallLine2D.StartPoint)
         {
            m_wallLine2D.StartPoint = mousePosition;
            m_refGeometry.StartPointD = ConvertToPointD(mousePosition);
         }
         // start point specified, end point isn't, so specify the end point
         else if (Point.Empty == m_wallLine2D.EndPoint)
         {
            // don't let the length of the 2 points too small
            if (Math.Abs(m_wallLine2D.StartPoint.X - mousePosition.X) >= 2 ||
                Math.Abs(m_wallLine2D.StartPoint.Y - mousePosition.Y) >= 2)
            {
               m_wallLine2D.EndPoint = mousePosition;
               m_refGeometry.EndPointD = ConvertToPointD(mousePosition);
               m_wallLine2D.AssistantPoint = Point.Empty;
            }
         }
      }

      /// <summary>
      /// store mouse position when mouse moves (the location will be the candidate end points of the baseline)
      /// </summary>
      /// <param name="mousePosition">
      /// the location of the mouse cursor
      /// </param>
      public void AddMousePosition(Point mousePosition)
      {
         // both endpoints for the baseline have been confirmed, no need to record the mouse location
         if (Point.Empty != m_wallLine2D.StartPoint &&
             Point.Empty != m_wallLine2D.EndPoint)
         {
            return;
         }

         // we just start to draw the baseline, no end points are specified
         // or just the start point was specified, so the mouse position will be the "candidate end point"
         m_wallLine2D.AssistantPoint = mousePosition;
      }

      /// <summary>
      /// draw the baseline for the curtain wall creation in the picture box
      /// in the "Create Curtain Wall" tab page, user needs to draw the baseline for wall creation
      /// </summary>
      /// <param name="graphics">
      /// form graphic
      /// </param>
      /// <param name="pen">
      /// pen used to draw line in pictureBox
      /// </param>
      public void Draw(Graphics graphics, Pen pen)
      {
         // draw the coordinate system origin
         DrawCoordinateOrigin(graphics, pen);

         // draw the baseline
         DrawBaseline(graphics, pen);
      }

      /// <summary>
      /// Clear points in baseline
      /// </summary>
      public void RemovePoints()
      {
         m_wallLine2D.Clear();
      }
      #endregion

      #region Private methods
      /// <summary>
      /// scale the point and store them in PointD format
      /// </summary>
      /// <param name="srcPoint">
      /// the point to-be-zoomed
      /// </param>
      /// <returns>
      /// the scaled result point
      /// </returns>
      private PointD ConvertToPointD(Point srcPoint)
      {
         double x = -1;
         double y = -1;
         x = srcPoint.X - m_origin.X;
         y = m_origin.Y - srcPoint.Y;
         x /= SCALEFACTOR;
         y /= SCALEFACTOR;
         return new PointD(x, y);
      }

      /// <summary>
      /// draw the coordinate system origin for the baseline drawing
      /// </summary>
      /// <param name="graphics">
      /// form graphic
      /// </param>
      /// <param name="pen">
      /// pen used to draw line in pictureBox
      /// </param>
      private void DrawCoordinateOrigin(Graphics graphics, Pen pen)
      {
         // draw the coordinate system origin
         graphics.DrawLine(pen, new Point(m_origin.X - 10, m_origin.Y), new Point(m_origin.X + 10, m_origin.Y));
         graphics.DrawLine(pen, new Point(m_origin.X, m_origin.Y - 10), new Point(m_origin.X, m_origin.Y + 10));
         graphics.DrawString("(0,0)", m_coordinateFont, Brushes.Blue, new PointF(m_origin.X + 2, m_origin.Y + 2));
      }

      /// <summary>
      /// draw the baseline / the candidate baseline (start point confirmed, end point didn't)
      /// </summary>
      /// <param name="graphics">
      /// form graphic
      /// </param>
      /// <param name="pen">
      /// pen used to draw line in pictureBox
      /// </param>
      private void DrawBaseline(Graphics graphics, Pen pen)
      {
         if (Point.Empty != m_wallLine2D.AssistantPoint)
         {
            if (Point.Empty != m_wallLine2D.StartPoint)
            {
               graphics.DrawLine(pen, m_wallLine2D.StartPoint, m_wallLine2D.AssistantPoint);
            }

            // show the real-time coordinate of the mouse position 
            WriteCoordinate(graphics, pen);
         }

         if (Point.Empty != m_wallLine2D.EndPoint &&
             Point.Empty != m_wallLine2D.EndPoint)
         {
            graphics.DrawLine(pen, m_wallLine2D.StartPoint, m_wallLine2D.EndPoint);
         }
      }

      /// <summary>
      /// write the coordinate for moving mouse
      /// </summary>
      /// <param name="graphics">
      /// form graphic
      /// </param>
      /// <param name="pen">
      /// pen used to draw line in pictureBox
      /// </param>
      private void WriteCoordinate(Graphics graphics, Pen pen)
      {
         PointD assistPointD = ConvertToPointD(m_wallLine2D.AssistantPoint);
         double x = Unit.CovertFromAPI(m_myDocument.LengthUnitType, assistPointD.X);
         double y = Unit.CovertFromAPI(m_myDocument.LengthUnitType, assistPointD.Y);

         string xCoorString = Convert.ToString(Math.Round(x, 1));
         string yCoorString = Convert.ToString(Math.Round(y, 1));
         string unitType = Revit.SDK.Samples.CurtainWallGrid.CS.Properties.Resources.ResourceManager.GetString(m_myDocument.LengthUnitType.ToString());
         String coordinate = "(" + xCoorString + unitType + "," + yCoorString + unitType + ")";
         graphics.DrawString(coordinate, m_coordinateFont, Brushes.Blue,
             new PointF(m_wallLine2D.AssistantPoint.X + 2, m_wallLine2D.AssistantPoint.Y + 2));
      }
      #endregion
   }
}

GridGeometry.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Collections;
using System.Drawing.Drawing2D;

using Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace Revit.SDK.Samples.CurtainWallGrid.CS
{
    /// <summary>
    /// manages the behaviors & operations of CurtainGrid
    /// </summary>
    public class GridGeometry
    {
        #region Fields
        // the document of this sample
        private MyDocument m_myDocument;

        // stores the curtain grid information of the created curtain wall
        private CurtainGrid m_activeGrid;

        // the referred drawing class for the curtain grid
        private GridDrawing m_drawing;

        //object which contains reference of Revit Application
        private ExternalCommandData m_commandData;

        // the active document of Revit
        private Document m_activeDocument;

        // store the mullion type used in this sample
        private MullionType m_mullionType;

        // all the grid lines of U direction (in CurtainGridLine format)
        private List<CurtainGridLine> m_uGridLines;

        // all the grid lines of V direction (in CurtainGridLine format)
        private List<CurtainGridLine> m_vGridLines;

        // stores all the vertexes of the curtain grid (in Autodesk.Revit.DB.XYZ format)
        private List<Autodesk.Revit.DB.XYZ> m_GridVertexesXYZ;

        // stores all the properties of the curtain grid
        private GridProperties m_gridProperties;

        // store the grid line to be removed
        private CurtainGridLine m_lineToBeMoved = null;

        // store the offset to be moved for the specified grid line
        private int m_moveOffset = 0;
        #endregion

        #region Properties
        /// <summary>
        /// stores the curtain grid information of the created curtain wall
        /// </summary>
        public CurtainGrid ActiveGrid
        {
            get
            {
                return m_activeGrid;
            }
        }

        /// <summary>
        /// the referred drawing class for the curtain grid
        /// </summary>
        public GridDrawing Drawing
        {
            get
            {
                return m_drawing;
            }
        }

        /// <summary>
        /// store the mullion type used in this sample
        /// </summary>
        public MullionType MullionType
        {
            get
            {
                return m_mullionType;
            }
            set
            {
                m_mullionType = value;
            }
        }

        /// <summary>
        /// all the grid lines of U direction (in CurtainGridLine format)
        /// </summary>
        public List<CurtainGridLine> UGridLines
        {
            get
            {
                return m_uGridLines;
            }
        }

        /// <summary>
        /// all the grid lines of V direction (in CurtainGridLine format)
        /// </summary>
        public List<CurtainGridLine> VGridLines
        {
            get
            {
                return m_vGridLines;
            }
        }

        /// <summary>
        /// stores all the vertexes of the curtain grid (in Autodesk.Revit.DB.XYZ format)
        /// </summary>
        public List<Autodesk.Revit.DB.XYZ> GridVertexesXYZ
        {
            get
            {
                return m_GridVertexesXYZ;
            }
        }

        /// <summary>
        /// stores all the properties of the curtain grid
        /// </summary>
        public GridProperties GridProperties
        {
            get
            {
                return m_gridProperties;
            }
        }

        public CurtainGridLine LineToBeMoved
        {
            get
            {
                return m_lineToBeMoved;
            }
        }

        /// <summary>
        /// store the offset to be moved for the specified grid line
        /// </summary>
        public int MoveOffset
        {
            get
            {
                return m_moveOffset;
            }
            set
            {
                m_moveOffset = value;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="myDoc">
        /// the document of the sample
        /// </param>
        public GridGeometry(MyDocument myDoc)
        {
            m_myDocument = myDoc;
            m_commandData = myDoc.CommandData;
            m_activeDocument = myDoc.Document;
            m_gridProperties = new GridProperties();
            //m_activeGrid = grid;
            m_drawing = new GridDrawing(myDoc, this);
            m_uGridLines = new List<CurtainGridLine>();
            m_vGridLines = new List<CurtainGridLine>();
            m_GridVertexesXYZ = new List<Autodesk.Revit.DB.XYZ>();
        }
        #endregion

        #region Public methods
        /// <summary>
        /// obtain all the properties of the curtain grid
        /// </summary>
        public void ReloadGridProperties()
        {
            if (null == m_activeGrid)
            {
                if (true == m_myDocument.WallCreated)
                {
                    m_activeGrid = m_myDocument.CurtainWall.CurtainGrid;
                }
                else
                {
                    return;
                }
            }

            // horizontal grid pattern 
            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            switch (m_activeGrid.Grid2Justification)
            {
                case CurtainGridAlignType.Beginning:
                    m_gridProperties.HorizontalJustification = CurtainGridAlign.Beginning;
                    break;
                case CurtainGridAlignType.Center:
                    m_gridProperties.HorizontalJustification = CurtainGridAlign.Center;
                    break;
                case CurtainGridAlignType.End:
                    m_gridProperties.HorizontalJustification = CurtainGridAlign.End;
                    break;
                default:
                    break;
            }
            m_gridProperties.HorizontalAngle = m_activeGrid.Grid2Angle * 180.0 / Math.PI;
            m_gridProperties.HorizontalOffset = m_activeGrid.Grid2Offset;
            m_gridProperties.HorizontalLinesNumber = m_activeGrid.NumULines;
            // vertical grid pattern
            switch (m_activeGrid.Grid1Justification)
            {
                case CurtainGridAlignType.Beginning:
                    m_gridProperties.VerticalJustification = CurtainGridAlign.Beginning;
                    break;
                case CurtainGridAlignType.Center:
                    m_gridProperties.VerticalJustification = CurtainGridAlign.Center;
                    break;
                case CurtainGridAlignType.End:
                    m_gridProperties.VerticalJustification = CurtainGridAlign.End;
                    break;
                default:
                    break;
            }
            m_gridProperties.VerticalAngle = m_activeGrid.Grid1Angle * 180.0 / Math.PI;
            m_gridProperties.VerticalOffset = m_activeGrid.Grid1Offset;
            m_gridProperties.VerticalLinesNumber = m_activeGrid.NumVLines;
            // other data
            m_gridProperties.PanelNumber = m_activeGrid.NumPanels;
            m_gridProperties.UnlockedPanelsNumber = m_activeGrid.GetUnlockedPanelIds().Count;
            m_gridProperties.CellNumber = m_activeGrid.GetCurtainCells().Count;
            if (0 != m_activeGrid.GetMullionIds().Count)
            {
                m_gridProperties.MullionsNumber = m_activeGrid.GetMullionIds().Count;
                m_gridProperties.UnlockedmullionsNumber = m_activeGrid.GetUnlockedMullionIds().Count;
            }
            act.Commit();
        }

        /// <summary>
        /// reload all the geometry data of the curtain grid (grid lines, vertexes, and convert them to 2D format)
        /// </summary>
        public void ReloadGeometryData()
        {
            if (null == m_activeGrid)
            {
                if (true == m_myDocument.WallCreated)
                {
                    m_activeGrid = m_myDocument.CurtainWall.CurtainGrid;
                }
                else
                {
                    return;
                }
            }

            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            //ElementSet mullions = m_activeGrid.Mullions;
            ICollection<ElementId> mullions = m_activeGrid.GetMullionIds();

            act.Commit();
            if (0 == mullions.Count)
            {
                foreach (ElementId e in mullions)
                {
                    Mullion mullion = m_activeDocument.GetElement(e) as Mullion;

                    if (null != mullion)
                    {
                        m_mullionType = mullion.MullionType;
                        break;
                    }
                }
            }


            GetULines();
            GetVLines();
            GetCurtainGridVertexes();
            // covert those lines to 2D format
            m_drawing.GetLines2D();
        }

        /// <summary>
        /// remove the selected segment from the curtain grid
        /// </summary>
        public void RemoveSegment()
        {
            // verify that the mouse is inside the curtain grid area
            List<KeyValuePair<Line2D, Pen>> lines2D = m_drawing.DrawObject.Lines2D;
            if (lines2D.Count < 1)
            {
                return;
            }

            List<SegmentLine2D> toBeRemovedList = new List<SegmentLine2D>();
            // check whether the deletion is valid
            bool canRemove = true;
            MimicRemoveSegments(ref canRemove, toBeRemovedList);
            // in the "MimicRemove" process, we didn't find that we need to "Remove the last segment of the grid line"
            // so the "Remove" action can go on
            if (true == canRemove)
            {
                try
                {
                    Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                    act.Start();
                    foreach (SegmentLine2D seg2D in toBeRemovedList)
                    {
                        int gridLineIndex = seg2D.GridLineIndex;
                        int segIndex = seg2D.SegmentIndex;
                        bool isUSegment = seg2D.IsUSegment;

                        CurtainGridLine line;
                        if (true == isUSegment)
                        {
                            line = m_uGridLines[gridLineIndex];
                        }
                        else
                        {
                            line = m_vGridLines[gridLineIndex];
                        }
                        Curve curve = line.AllSegmentCurves.get_Item(segIndex);
                        line.RemoveSegment(curve);
                    }
                    act.Commit();
                }
                catch (System.Exception e)
                {
                    TaskDialog.Show("Exception", e.Message);
                    return;
                }
            }
            // in the "MimicRemove" process, we found that we would "Remove the last segment of the grid line"
            // so the whole "Remove" action will roll back
            else
            {
                foreach (SegmentLine2D seg2D in toBeRemovedList)
                {
                    int gridLineIndex = seg2D.GridLineIndex;
                    int segIndex = seg2D.SegmentIndex;
                    bool isUSegment = seg2D.IsUSegment;
                    GridLine2D gridLine2D;

                    if (true == isUSegment)
                    {
                        gridLine2D = m_drawing.UGridLines2D[gridLineIndex];
                    }
                    else
                    {
                        gridLine2D = m_drawing.VGridLines2D[gridLineIndex];
                    }

                    gridLine2D.RemovedNumber--;
                    SegmentLine2D segLine2D = gridLine2D.Segments[segIndex];
                    segLine2D.Removed = false;
                    gridLine2D.Segments[segIndex] = segLine2D;
                }
                string statusMsg = "Delete this segment will make some grid lines have no existent segments.";
                m_myDocument.Message = new KeyValuePair<string, bool>(statusMsg, true);
            }

            this.ReloadGeometryData();
            m_drawing.DrawObject.Clear();
        }

        /// <summary>
        /// add a new segment to the specified location
        /// </summary>
        public void AddSegment()
        {
            // verify that the mouse is inside the curtain grid area
            List<KeyValuePair<Line2D, Pen>> lines2D = m_drawing.DrawObject.Lines2D;
            if (lines2D.Count < 1)
            {
                return;
            }

            // the selected segment location is on a U grid line
            if (-1 != m_drawing.SelectedUIndex)
            {
                CurtainGridLine line = m_uGridLines[m_drawing.SelectedUIndex];
                Curve curve = line.AllSegmentCurves.get_Item(m_drawing.SelectedUSegmentIndex);
                if (null != line && null != curve)
                {
                    try
                    {
                        Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());

                        act.Start();
                        line.AddSegment(curve);
                        act.Commit();
                    }
                    catch (System.Exception e)
                    {
                        TaskDialog.Show("Exception", e.Message);
                        return;
                    }
                }

                GridLine2D gridLine2D = m_drawing.UGridLines2D[m_drawing.SelectedUIndex];
                gridLine2D.RemovedNumber--;
                SegmentLine2D segLine2D = gridLine2D.Segments[m_drawing.SelectedUSegmentIndex];
                segLine2D.Removed = false;
                gridLine2D.Segments[m_drawing.SelectedUSegmentIndex] = segLine2D;
            }
            // the selected segment location is on a V grid line
            else if (-1 != m_drawing.SelectedVIndex)
            {
                CurtainGridLine line = m_vGridLines[m_drawing.SelectedVIndex];
                Curve curve = line.AllSegmentCurves.get_Item(m_drawing.SelectedVSegmentIndex);
                if (null != line && null != curve)
                {
                    try
                    {
                        Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                        act.Start();
                        line.AddSegment(curve);
                        act.Commit();
                    }
                    catch (System.Exception e)
                    {
                        TaskDialog.Show("Exception", e.Message);
                        return;
                    }
                }

                GridLine2D gridLine2D = m_drawing.VGridLines2D[m_drawing.SelectedVIndex];
                gridLine2D.RemovedNumber--;
                SegmentLine2D segLine2D = gridLine2D.Segments[m_drawing.SelectedVSegmentIndex];
                segLine2D.Removed = false;
                gridLine2D.Segments[m_drawing.SelectedVSegmentIndex] = segLine2D;
            }

            this.ReloadGeometryData();
            m_drawing.DrawObject.Clear();
        }

        /// <summary>
        /// add all the deleted segments back for a grid line
        /// </summary>
        public void AddAllSegments()
        {
            // verify that the mouse is inside the curtain grid area
            List<KeyValuePair<Line2D, Pen>> lines2D = m_drawing.DrawObject.Lines2D;
            if (lines2D.Count < 1)
            {
                return;
            }

            if (-1 != m_drawing.SelectedUIndex)
            {
                CurtainGridLine line = m_uGridLines[m_drawing.SelectedUIndex];
                if (null != line)
                {
                    try
                    {
                        Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                        act.Start();
                        line.AddAllSegments();
                        act.Commit();
                    }
                    catch (System.Exception e)
                    {
                        TaskDialog.Show("Exception", e.Message);
                        return;
                    }
                }

                GridLine2D gridLine2D = m_drawing.UGridLines2D[m_drawing.SelectedUIndex];
                gridLine2D.RemovedNumber = 0;
                foreach (SegmentLine2D segLine2D in gridLine2D.Segments)
                {
                    segLine2D.Removed = false;
                }
            }
            else if (-1 != m_drawing.SelectedVIndex)
            {
                CurtainGridLine line = m_vGridLines[m_drawing.SelectedVIndex];
                if (null != line)
                {
                    try
                    {
                        Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                        act.Start();
                        line.AddAllSegments();
                        act.Commit();
                    }
                    catch (System.Exception e)
                    {
                        TaskDialog.Show("Exception", e.Message);
                        return;
                    }
                }

                GridLine2D gridLine2D = m_drawing.VGridLines2D[m_drawing.SelectedVIndex];
                gridLine2D.RemovedNumber = 0;
                foreach (SegmentLine2D segLine2D in gridLine2D.Segments)
                {
                    segLine2D.Removed = false;
                }
            }

            this.ReloadGeometryData();
            m_drawing.DrawObject.Clear();
        }

        /// <summary>
        /// add a new U grid line to the specified location
        /// </summary>
        public void AddUGridLine()
        {
            // verify that the mouse location is valid: it's inside the curtain grid area 
            // & it doesn't lap over another grid line (it's not allowed to add a grid line to lap over another one)
            if (false == m_drawing.MouseLocationValid)
            {
                return;
            }

            // all the assistant lines (in "Add U (Horizontal) Grid Line" operation, 
            // there's only one dash line, this line indicates the location to be added)
            List<KeyValuePair<Line2D, Pen>> lines2D = m_drawing.DrawObject.Lines2D;
            if (lines2D.Count < 1)
            {
                return;
            }

            // the dash U line shown in the sample (incidates the location to be added)
            Line2D line2D = lines2D[0].Key;
            if (System.Drawing.Point.Empty == line2D.StartPoint ||
                System.Drawing.Point.Empty == line2D.EndPoint)
            {
                return;
            }

            // get the point to be added
            int midX = (line2D.StartPoint.X + line2D.EndPoint.X) / 2;
            int midY = (line2D.StartPoint.Y + line2D.EndPoint.Y) / 2;
            // transform the 2D point to Autodesk.Revit.DB.XYZ format
            Autodesk.Revit.DB.XYZ pos = new Autodesk.Revit.DB.XYZ(midX, midY, 0);
            Vector4 vec = new Vector4(pos);
            vec = m_drawing.Coordinates.RestoreMatrix.Transform(vec);
            CurtainGridLine newLine;

            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            try
            {
                newLine = ActiveGrid.AddGridLine(true, new Autodesk.Revit.DB.XYZ(vec.X, vec.Y, vec.Z), false);
            }
            catch (System.Exception e)
            {
                TaskDialog.Show("Exception", e.Message);
                // "add U line" failed, so return directly
                return;
            }
            act.Commit();

            // U line added, the V line's segment information changed, so reload all the geometry data
            this.ReloadGeometryData();
        }

        /// <summary>
        /// add a new V grid line to the specified location
        /// </summary>
        public void AddVGridLine()
        {
            // verify that the mouse location is valid: it's inside the curtain grid area 
            // & it doesn't lap over another grid line (it's not allowed to add a grid line to lap over another one)
            if (false == m_drawing.MouseLocationValid)
            {
                return;
            }

            // all the assistant lines (in "Add V (Vertical) Grid Line" operation, 
            // there's only one dash line, this line indicates the location to be added)
            List<KeyValuePair<Line2D, Pen>> lines2D = m_drawing.DrawObject.Lines2D;
            if (lines2D.Count < 1)
            {
                return;
            }

            // the dash V line shown in the sample (incidates the location to be added)
            Line2D line2D = lines2D[0].Key;
            if (System.Drawing.Point.Empty == line2D.StartPoint ||
                System.Drawing.Point.Empty == line2D.EndPoint)
            {
                return;
            }

            // get the point to be added
            int midX = (line2D.StartPoint.X + line2D.EndPoint.X) / 2;
            int midY = (line2D.StartPoint.Y + line2D.EndPoint.Y) / 2;
            // transform the 2D point to Autodesk.Revit.DB.XYZ format
            Autodesk.Revit.DB.XYZ pos = new Autodesk.Revit.DB.XYZ(midX, midY, 0);
            Vector4 vec = new Vector4(pos);
            vec = m_drawing.Coordinates.RestoreMatrix.Transform(vec);
            CurtainGridLine newLine;

            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            try
            {
                newLine = ActiveGrid.AddGridLine(false, new Autodesk.Revit.DB.XYZ(vec.X, vec.Y, vec.Z), false);
            }
            catch (System.Exception e)
            {
                TaskDialog.Show("Exception", e.Message);
                // "add V line" failed, so return directly
                return;
            }
            act.Commit();

            // V line added, the U line's segment information changed, so reload all the geometry data
            this.ReloadGeometryData();
        }

        /// <summary>
        /// toggle the selected grid line's Lock status:  if it's locked, unlock it, vice versa
        /// </summary>
        public void LockOrUnlockSelectedGridLine()
        {
            CurtainGridLine line = null;
            GridLine2D line2D = new GridLine2D();

            // get the selected grid line
            if (-1 != m_drawing.SelectedUIndex)
            {
                line = m_uGridLines[m_drawing.SelectedUIndex];
                line2D = m_drawing.UGridLines2D[m_drawing.SelectedUIndex];
            }
            else if (-1 != m_drawing.SelectedVIndex)
            {
                line = m_vGridLines[m_drawing.SelectedVIndex];
                line2D = m_drawing.VGridLines2D[m_drawing.SelectedVIndex];
            }
            else
            {
                return;
            }

            // lock/unlock the grid line
            if (null != line)
            {
                Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                act.Start();
                line.Lock = !line.Lock;
                act.Commit();
            }

            // update the mapped line2D's data
            line2D.Locked = line.Lock;

            // clear the intermediate variables and instances
            m_drawing.DrawObject.Clear();
        }

        /// <summary>
        /// get the grid line to be removed
        /// </summary>
        /// <returns>
        /// if the line obtained, return true; otherwise false
        /// </returns>
        public bool GetLineToBeMoved()
        {
            if (-1 != m_drawing.SelectedUIndex)
            {
                m_lineToBeMoved = m_uGridLines[m_drawing.SelectedUIndex];
                return true;
            }
            else if (-1 != m_drawing.SelectedVIndex)
            {
                m_lineToBeMoved = m_vGridLines[m_drawing.SelectedVIndex];
                return true;
            }
            else
            {
                m_lineToBeMoved = null;
                return false;
            }
        }

        /// <summary>
        /// move the selected grid line to the location of the mouse cursor
        /// </summary>
        /// <param name="mousePosition">
        /// indicates the destination position of the grid line
        /// </param>
        /// <returns>
        /// return whether the grid line be moved successfully
        /// </returns>
        public bool MoveGridLine(System.Drawing.Point mousePosition)
        {
            // verify that the mouse location is valid: it's inside the curtain grid area 
            // & it doesn't lap over another grid line (it's not allowed to move a grid line to lap over another one)
            if (false == m_drawing.MouseLocationValid)
            {
                return false;
            }

            if (null == m_lineToBeMoved)
            {
                return false;
            }

            // move a U line along the V direction
            if (-1 != m_drawing.SelectedUIndex)
            {
                // convert the 2D data to 3D
                Autodesk.Revit.DB.XYZ xyz = new Autodesk.Revit.DB.XYZ(mousePosition.X, mousePosition.Y, 0);
                Vector4 vec = new Vector4(xyz);
                vec = m_drawing.Coordinates.RestoreMatrix.Transform(vec);
                double offset = vec.Z - m_lineToBeMoved.FullCurve.GetEndPoint(0).Z;
                xyz = new Autodesk.Revit.DB.XYZ(0, 0, offset);
                Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                act.Start();
                try
                {
                    ElementTransformUtils.MoveElement(m_activeDocument, m_lineToBeMoved.Id, xyz);
                }
                catch (System.Exception e)
                {
                    TaskDialog.Show("Exception", e.Message);
                    return false;
                }
                act.Commit();

                // update the grid line 2d
                GridLine2D line = m_drawing.UGridLines2D[m_drawing.SelectedUIndex];
                line.StartPoint = new System.Drawing.Point(line.StartPoint.X, line.StartPoint.Y + m_moveOffset);
                line.EndPoint = new System.Drawing.Point(line.EndPoint.X, line.EndPoint.Y + m_moveOffset);

                // update the mapped grid line graphics path
                GraphicsPath path = new GraphicsPath();
                path.AddLine(line.StartPoint, line.EndPoint);
                m_drawing.ULinePathList[m_drawing.SelectedUIndex] = path;

                // update the mapped segment line and its graphics path
                List<GraphicsPath> pathList = m_drawing.USegLinePathListList[m_drawing.SelectedUIndex];
                List<SegmentLine2D> segLineList = line.Segments;
                for (int i = 0; i < segLineList.Count; i++)
                {
                    // update the segment
                    SegmentLine2D segLine2D = segLineList[i];
                    segLine2D.StartPoint = new System.Drawing.Point(segLine2D.StartPoint.X, segLine2D.StartPoint.Y + m_moveOffset);
                    segLine2D.EndPoint = new System.Drawing.Point(segLine2D.EndPoint.X, segLine2D.EndPoint.Y + m_moveOffset);

                    // update the segment's graphics path
                    GraphicsPath gpath = new GraphicsPath();
                    path.AddLine(segLine2D.StartPoint, segLine2D.EndPoint);
                    pathList[i] = gpath;
                }
            }
            // move a V line along the U direction
            else if (-1 != m_drawing.SelectedVIndex)
            {
                // convert the 2D data to 3D
                Autodesk.Revit.DB.XYZ xyz = new Autodesk.Revit.DB.XYZ(mousePosition.X, mousePosition.Y, 0);
                Vector4 vec = new Vector4(xyz);
                vec = m_drawing.Coordinates.RestoreMatrix.Transform(vec);
                double offset = vec.X - m_lineToBeMoved.FullCurve.GetEndPoint(0).X;
                xyz = new Autodesk.Revit.DB.XYZ(offset, 0, 0);

                Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                act.Start();
                try
                {
                    ElementTransformUtils.MoveElement(m_activeDocument, m_lineToBeMoved.Id, xyz);
                }
                catch (System.Exception e)
                {
                    TaskDialog.Show("Exception", e.Message);
                    return false;
                }
                act.Commit();

                // update the grid line 2d
                GridLine2D line = m_drawing.VGridLines2D[m_drawing.SelectedVIndex];
                line.StartPoint = new System.Drawing.Point(line.StartPoint.X + m_moveOffset, line.StartPoint.Y);
                line.EndPoint = new System.Drawing.Point(line.EndPoint.X + m_moveOffset, line.EndPoint.Y);

                // update the mapped grid line graphics path
                GraphicsPath path = new GraphicsPath();
                path.AddLine(line.StartPoint, line.EndPoint);
                m_drawing.VLinePathList[m_drawing.SelectedVIndex] = path;

                // update the mapped segment line and its graphics path
                List<GraphicsPath> pathList = m_drawing.VSegLinePathListList[m_drawing.SelectedVIndex];
                List<SegmentLine2D> segLineList = line.Segments;
                for (int i = 0; i < segLineList.Count; i++)
                {
                    // update the segment
                    SegmentLine2D segLine2D = segLineList[i];
                    segLine2D.StartPoint = new System.Drawing.Point(segLine2D.StartPoint.X + m_moveOffset, segLine2D.StartPoint.Y);
                    segLine2D.EndPoint = new System.Drawing.Point(segLine2D.EndPoint.X + m_moveOffset, segLine2D.EndPoint.Y);

                    // update the segment's graphics path
                    GraphicsPath gpath = new GraphicsPath();
                    path.AddLine(segLine2D.StartPoint, segLine2D.EndPoint);
                    pathList[i] = gpath;
                }
            }
            // line moved, the segment information changed, so reload all the geometry data
            this.ReloadGeometryData();

            m_drawing.DrawObject.Clear();
            return true;
        }

        /// <summary>
        /// add mullions to all the segments of the curtain grid
        /// due to the limitations of Mullions, it's not available yet to add mullions to the 
        /// edges of the curtain grid as Revit UI does
        /// </summary>
        public void AddAllMullions()
        {
            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            try
            {
                // add mullions to all U grid lines
                foreach (CurtainGridLine line in m_uGridLines)
                {
                    line.AddMullions(line.AllSegmentCurves.get_Item(0), m_mullionType, false);
                }
                // add mullions to all V grid lines
                foreach (CurtainGridLine line in m_vGridLines)
                {
                    line.AddMullions(line.AllSegmentCurves.get_Item(0), m_mullionType, false);
                }
            }
            catch (System.Exception e)
            {
                TaskDialog.Show("Exception", e.Message);
                return;
            }
            act.Commit();
        }

        /// <summary>
        /// delete all the mullions of the curtain grid
        /// </summary>
        public void DeleteAllMullions()
        {
            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            try
            {
                ICollection<ElementId> mullions = m_activeGrid.GetMullionIds();

                foreach (ElementId e in mullions)
                {
                    Mullion mullion = m_activeDocument.GetElement(e) as Mullion;

                    // Exceptions may jump out if attempting to delete "Locked" mullions
                    if (true == mullion.Lockable && true == mullion.Lock)
                    {
                        mullion.Lock = false;
                    }

                    m_activeDocument.Delete(mullion.Id);
                }
            }
            catch (System.Exception e)
            {
                TaskDialog.Show("Exception", e.Message);
                return;
            }
            act.Commit();
        }
        #endregion

        #region Private methods
        /// <summary>
        /// get all the U grid lines' data of the curtain grid
        /// </summary>
        private void GetULines()
        {
            m_uGridLines.Clear();
            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            //ElementSet uLines = m_activeGrid.UGridLines;
            ICollection<ElementId> uLines = m_activeGrid.GetUGridLineIds();
            act.Commit();
            if (0 == uLines.Count)
            {
                return;
            }

            foreach (ElementId e in uLines)
            {
                CurtainGridLine line = m_activeDocument.GetElement(e) as CurtainGridLine;

                m_uGridLines.Add(line);
            }
        }

        /// <summary>
        /// get all the V grid lines' data of the curtain grid
        /// </summary>
        private void GetVLines()
        {
            m_vGridLines.Clear();
            Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
            act.Start();
            //ElementSet vLines = m_activeGrid.VGridLines;
            ICollection<ElementId> vLines = m_activeGrid.GetVGridLineIds();
            act.Commit();
            if (0 == vLines.Count)
            {
                return;
            }

            foreach (ElementId e in vLines)
            {
                CurtainGridLine line = m_activeDocument.GetElement(e) as CurtainGridLine;

                m_vGridLines.Add(line);
            }
        }

        /// <summary>
        /// get all of the 4 vertexes of the curtain grid
        /// </summary>
        /// <returns></returns>
        private bool GetCurtainGridVertexes()
        {
            // even in "ReloadGeometryData()" method, no need to reload the boundary information
            // (as the boundary of the curtain grid won't be changed in the sample)
            // just need to load it after the curtain wall been created
            if (null != m_GridVertexesXYZ && 0 < m_GridVertexesXYZ.Count)
            {
                return true;
            }

            // the curtain grid is from "Curtain Wall: Curtain Wall 1" (by default, the "Curtain Wall 1" has no U/V grid lines)
            if (m_uGridLines.Count <= 0 || m_vGridLines.Count <= 0)
            {
                // special handling for "Curtain Wall: Curtain Wall 1"
                // as the "Curtain Wall: Curtain Wall 1" has no U/V grid lines, so we can't compute the boundary from the grid lines
                // as that kind of curtain wall contains only one curtain cell
                // so we compute the boundary from the data of the curtain cell
                // Obtain the geometry information of the curtain wall
                // also works with some curtain grids with only U grid lines or only V grid lines
                Transaction act = new Transaction(m_activeDocument, Guid.NewGuid().GetHashCode().ToString());
                act.Start();
                ICollection<CurtainCell> cells = m_activeGrid.GetCurtainCells();
                act.Commit();
                Autodesk.Revit.DB.XYZ minXYZ = new Autodesk.Revit.DB.XYZ(Double.MaxValue, Double.MaxValue, Double.MaxValue);
                Autodesk.Revit.DB.XYZ maxXYZ = new Autodesk.Revit.DB.XYZ(Double.MinValue, Double.MinValue, Double.MinValue);
                GetVertexesByCells(cells, ref minXYZ, ref maxXYZ);

                // move the U & V lines to the boundary of the curtain grid, and get their end points as the vertexes of the curtain grid
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(minXYZ.X, minXYZ.Y, minXYZ.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(maxXYZ.X, maxXYZ.Y, minXYZ.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(maxXYZ.X, maxXYZ.Y, maxXYZ.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(minXYZ.X, minXYZ.Y, maxXYZ.Z));
                return true;
            }
            else
            {
                // handling for the other kinds of curtain walls (contains U&V grid lines by default)
                CurtainGridLine uLine = m_uGridLines[0];
                CurtainGridLine vLine = m_vGridLines[0];

                List<Autodesk.Revit.DB.XYZ> points = new List<Autodesk.Revit.DB.XYZ>();

                Autodesk.Revit.DB.XYZ uStartPoint = uLine.FullCurve.GetEndPoint(0);
                Autodesk.Revit.DB.XYZ uEndPoint = uLine.FullCurve.GetEndPoint(1);

                Autodesk.Revit.DB.XYZ vStartPoint = vLine.FullCurve.GetEndPoint(0);
                Autodesk.Revit.DB.XYZ vEndPoint = vLine.FullCurve.GetEndPoint(1);

                points.Add(uStartPoint);
                points.Add(uEndPoint);
                points.Add(vStartPoint);
                points.Add(vEndPoint);

                //move the U & V lines to the boundary of the curtain grid, and get their end points as the vertexes of the curtain grid
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(uStartPoint.X, uStartPoint.Y, vStartPoint.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(uEndPoint.X, uEndPoint.Y, vStartPoint.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(uEndPoint.X, uEndPoint.Y, vEndPoint.Z));
                m_GridVertexesXYZ.Add(new Autodesk.Revit.DB.XYZ(uStartPoint.X, uStartPoint.Y, vEndPoint.Z));

                return true;
            }
        }

        /// <summary>
        /// get all the vertexes of the curtain cells
        /// </summary>
        /// <param name="cells">
        /// the curtain cells which need to be got the vertexes
        /// </param>
        /// <returns>
        /// the vertexes of the curtain cells
        /// </returns>
        private List<Autodesk.Revit.DB.XYZ> GetPoints(ICollection<CurtainCell> cells)
        {
            List<Autodesk.Revit.DB.XYZ> points = new List<Autodesk.Revit.DB.XYZ>();

            if (null == cells || cells.Count == 0)
            {
                return points;
            }

            foreach (CurtainCell cell in cells)
            {
                if (null == cell || 0 == cell.CurveLoops.Size)
                {
                    continue;
                }

                CurveArray curves = cell.CurveLoops.get_Item(0);

                foreach (Curve curve in curves)
                {
                    points.Add(curve.GetEndPoint(0));
                    points.Add(curve.GetEndPoint(1));
                }
            }

            return points;
        }

        /// <summary>
        /// get a bounding box which covers all the input points 
        /// </summary>
        /// <param name="points">
        /// the source points
        /// </param>
        /// <param name="minXYZ">
        /// one of the bounding box points
        /// </param>
        /// <param name="maxXYZ">
        /// one of the bounding box points
        /// </param>
        private void GetVertexesByPoints(List<Autodesk.Revit.DB.XYZ> points, ref Autodesk.Revit.DB.XYZ minXYZ, ref Autodesk.Revit.DB.XYZ maxXYZ)
        {
            if (null == points || 0 == points.Count)
            {
                return;
            }

            double minX = minXYZ.X;
            double minY = minXYZ.Y;
            double minZ = minXYZ.Z;
            double maxX = maxXYZ.X;
            double maxY = maxXYZ.Y;
            double maxZ = maxXYZ.Z;

            foreach (Autodesk.Revit.DB.XYZ xyz in points)
            {
                // compare the values and update the min and max value
                if (xyz.X < minX)
                {
                    minX = xyz.X;
                    minY = xyz.Y;
                }
                if (xyz.X > maxX)
                {
                    maxX = xyz.X;
                    maxY = xyz.Y;
                }

                if (xyz.Z < minZ)
                {
                    minZ = xyz.Z;
                }
                if (xyz.Z > maxZ)
                {
                    maxZ = xyz.Z;
                }
            } // end of loop

            minXYZ = new Autodesk.Revit.DB.XYZ(minX, minY, minZ);
            maxXYZ = new Autodesk.Revit.DB.XYZ(maxX, maxY, maxZ);
        }

        /// <summary>
        /// get the vertexes of the bounding box which covers all the curtain cells
        /// </summary>
        /// <param name="cells">
        /// the source curtain cells
        /// </param>
        /// <param name="minXYZ">
        /// the result bounding point
        /// </param>
        /// <param name="maxXYZ">
        /// the result bounding point
        /// </param>
        private void GetVertexesByCells(ICollection<CurtainCell> cells, ref Autodesk.Revit.DB.XYZ minXYZ, ref Autodesk.Revit.DB.XYZ maxXYZ)
        {
            if (null == cells || cells.Count == 0)
            {
                return;
            }

            List<Autodesk.Revit.DB.XYZ> points = GetPoints(cells);
            GetVertexesByPoints(points, ref minXYZ, ref maxXYZ);
        }

        /// <summary>
        /// a simulative "Delete Segment" operation before real deletion
        /// as we may occur some situations that prevent us to delete the specific segment
        /// for example, delete the specific segment will make some other segments to be deleted automatically (the "conjoint" ones)
        /// and the "automatically deleted" segment is the last segment of its parent grid line
        /// in this situation, we should prevent deleting that specific segment and rollback all the simulative deletion
        /// </summary>
        /// <param name="removeList">
        /// the refferred to-be-removed list, in the simulative deletion operation, all the suitable (not the last segment) segments will
        /// be added to that list
        /// </param>
        private void MimicRemoveSegments(ref bool canRemove, List<SegmentLine2D> removeList)
        {
            // the currently operated is a U segment
            if (-1 != m_drawing.SelectedUIndex)
            {
                GridLine2D gridLine2D = m_drawing.UGridLines2D[m_drawing.SelectedUIndex];
                SegmentLine2D segLine2D = gridLine2D.Segments[m_drawing.SelectedUSegmentIndex];

                // the to-be-deleted segment is the last one of the grid line, it's not allowed to delete it
                int existingNumber = gridLine2D.Segments.Count - gridLine2D.RemovedNumber;
                if (1 == existingNumber)
                {
                    canRemove = false;
                    return;
                }

                // simulative deletion
                gridLine2D.RemovedNumber++;
                segLine2D.Removed = true;
                gridLine2D.Segments[m_drawing.SelectedUSegmentIndex] = segLine2D;
                removeList.Add(segLine2D);
                // the "regeneration" step: if there're only 2 segments existing in one joint and they're in the same line, delete one seg will cause the other 
                // been deleted automatically
                MimicRecursiveDelete(ref canRemove, segLine2D, removeList);
            }
            // the currently operated is a V segment
            else if (-1 != m_drawing.SelectedVIndex)
            {
                GridLine2D gridLine2D = m_drawing.VGridLines2D[m_drawing.SelectedVIndex];
                SegmentLine2D segLine2D = gridLine2D.Segments[m_drawing.SelectedVSegmentIndex];

                int existingNumber = gridLine2D.Segments.Count - gridLine2D.RemovedNumber;
                // the to-be-deleted segment is the last one of the grid line, it's not allowed to delete it
                if (1 == existingNumber)
                {
                    canRemove = false;
                    return;
                }

                // simulative deletion
                gridLine2D.RemovedNumber++;
                segLine2D.Removed = true;
                gridLine2D.Segments[m_drawing.SelectedVSegmentIndex] = segLine2D;
                removeList.Add(segLine2D);
                // the "regeneration" step: if there're only 2 segments existing in one joint and they're in the same line, delete one seg will cause the other 
                // been deleted automatically
                MimicRecursiveDelete(ref canRemove, segLine2D, removeList);
            }
        }

        /// <summary>
        /// the "regeneration" step: if there're only 2 segments existing in one joint and they're in the same line,
        /// delete one seg will cause the other been deleted automatically
        /// </summary>
        /// <param name="segLine2D">
        /// the to-be-automatically-deleted segment
        /// </param>
        /// <param name="removeList">
        /// the referred to-be-deleted list of the segments
        /// </param>
        /// <returns>
        /// returns the operation result: if there's no "last" segment in the deletion operation, return true; otherwise false
        /// </returns>
        private void MimicRecursiveDelete(ref bool canRemove, SegmentLine2D segLine2D, List<SegmentLine2D> removeList)
        {
            // the "regeneration" step: if there're only 2 segments existing in one joint 
            // and they're in the same line, delete one seg will cause the other 
            // been deleted automatically
            // get conjoint U line segments
            List<SegmentLine2D> removeSegments = new List<SegmentLine2D>();
            m_drawing.GetConjointSegments(segLine2D, removeSegments);

            // there's no isolated segment need to be removed automatically
            if (null == removeSegments || 0 == removeSegments.Count)
            {
                // didn't "remove last segment of the curtain grid line", all the operations are valid. so return true
                return;
            }

            // there're conjoint segments need to be removed automatically
            // add the segments to removeList first, and compute whether other segments need to be 
            // removed automatically because of the deletion of this newly removed segment
            if (true == segLine2D.Removed)
            {
                foreach (SegmentLine2D seg in removeSegments)
                {
                    MimicRemoveSegment(ref canRemove, seg, removeList);

                    if (false == canRemove)
                    {
                        return;
                    }
                    // recursive calling
                    MimicRecursiveDelete(ref canRemove, seg, removeList);
                }
            }
        }

        /// <summary>
        /// remove the segment from the grid line
        /// </summary>
        /// <param name="canRemove">
        /// the returned result value, indicates whether the segment can be removed (is NOT the last segment)
        /// </param>
        /// <param name="seg">
        /// the to-be-removed segment
        /// </param>
        /// <param name="removeList">
        /// the referred to-be-deleted list of the segments
        /// </param>
        private void MimicRemoveSegment(ref bool canRemove, SegmentLine2D seg, List<SegmentLine2D> removeList)
        {
            int gridLineIndex = seg.GridLineIndex;
            int segIndex = seg.SegmentIndex;

            if (-1 != gridLineIndex && -1 != segIndex)
            {
                // update the gridline2d and segmentline2d data
                GridLine2D grid;
                if (true == seg.IsUSegment)
                {
                    grid = m_drawing.UGridLines2D[gridLineIndex];
                }
                else
                {
                    grid = m_drawing.VGridLines2D[gridLineIndex];
                }

                // the last segment of the grid line
                int existingNumber = grid.Segments.Count - grid.RemovedNumber;
                if (1 == existingNumber)
                {
                    canRemove = false;
                    return;
                }

                grid.RemovedNumber++;
                SegmentLine2D seg2D = grid.Segments[segIndex];
                seg2D.Removed = true;
                grid.Segments[segIndex] = seg2D;

                removeList.Add(seg2D);
            }
        }
        #endregion
    }// end of class
}

GridCoordinates.cs

//
// (C) Copyright 2003-2019 by Autodesk, Inc.
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
//
// Use, duplication, or disclosure by the U.S. Government is subject to
// restrictions set forth in FAR 52.227-19 (Commercial Computer
// Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii)
// (Rights in Technical Data and Computer Software), as applicable.
//

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

using Autodesk.Revit.DB;
using Autodesk.Revit.UI;

namespace Revit.SDK.Samples.CurtainWallGrid.CS
{
    /// <summary>
    /// Maintain the matrixes needed by 3D & 2D operations: pan, zoom, 2D->3D, 3D->2D
    /// </summary>
    public class GridCoordinates
    {
        #region Fields
        // scale the size of the image to let it shown in the canvas
        private float m_scaleFactor = 0.85f;

        // the document of this sample
        private MyDocument m_myDocument;

        // stores the current GridDrawing data
        private GridDrawing m_drawing;

        // stores the client rectangle of the canvas of the curtain grid
        // will be used in the scale matrix and move-to-center matrix
        private System.Drawing.Rectangle m_boundary;

        // stores the midpoint of the client rectangle 
        // will be used in the scale matrix and move-to-center matrix
        private System.Drawing.Point m_center;

        // store the Matrix used to transform 3D points to 2D
        Matrix4 m_to2DMatrix = null;

        // store the Matrix used to move points to center
        Matrix4 m_moveToCenterMatrix = null;

        // store the Matrix used to scale profile fit to pictureBox
        Matrix4 m_scaleMatrix = null;

        // store the Matrix used to transform Revit coordinate to window UI
        Matrix4 m_transformMatrix = null;

        // store the Matrix used to transform window UI coordinate to Revit
        Matrix4 m_restoreMatrix = null;

        // stores the boundary of the curtain grid
        List<PointF> m_boundPoints;

        #endregion

        #region Properties
        /// <summary>
        /// stores the GridDrawing data used in the current dialog
        /// </summary>
        public GridDrawing Drawing
        {
            get
            {
                return m_drawing;
            }
            set
            {
                m_drawing = value;
            }
        }

        /// <summary>
        /// store the Matrix used to transform 3D points to 2D
        /// </summary>
        public Matrix4 To2DMatrix
        {
            get
            {
                return m_to2DMatrix;
            }
        }

        /// <summary>
        /// store the Matrix used to transform Revit coordinate to window UI
        /// </summary>
        public Matrix4 TransformMatrix
        {
            get
            {
                return m_transformMatrix;
            }
        }

        /// <summary>
        /// store the Matrix used to transform window UI coordinate to Revit
        /// </summary>
        public Matrix4 RestoreMatrix
        {
            get
            {
                return m_restoreMatrix;
            }
        }
        #endregion

        #region Constructors
        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="myDoc">
        /// the document of this sample
        /// </param>
        /// <param name="drawing">
        /// the GridDrawing data used in the dialog
        /// </param>
        public GridCoordinates(MyDocument myDoc, GridDrawing drawing)
        {
            m_myDocument = myDoc;

            if (null == drawing)
            {
                TaskDialog.Show("Revit", "Error! There's no grid information in the curtain wall.");
            }
            m_drawing = drawing;
            drawing.Coordinates = this;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// obtain the matrixes used in this dialog
        /// </summary>
        public void GetMatrix()
        {
            // initialize the class members, obtain the location information of the canvas
            m_boundary = m_drawing.Boundary;
            m_center = m_drawing.Center;

            // Get a matrix which can transform points to 2D
            m_to2DMatrix = GetTo2DMatrix();

            // get the vertexes of the canvas (in Point/PointF format)
            m_boundPoints = GetBoundsPoints();

            // get a matrix which can keep all the points in the center of the canvas
            m_moveToCenterMatrix = GetMoveToCenterMatrix();

            // get a matrix for scaling all the points and lines within the canvas
            m_scaleMatrix = GetScaleMatrix();

            // transform 3D points to 2D
            m_transformMatrix = Get3DTo2DMatrix();

            // transform from 2D to 3D
            m_restoreMatrix = Get2DTo3DMatrix();
        }
        #endregion

        #region Private Methods
        /// <summary>
        /// calculate the matrix used to transform 2D to 3D
        /// </summary>
        /// <returns>
        /// matrix used to transform 2d points to 3d
        /// </returns>
        private Matrix4 Get2DTo3DMatrix()
        {
            Matrix4 matrix = Matrix4.Multiply(
                new Matrix4(new Vector4(-m_center.X, -m_center.Y, 0)), m_scaleMatrix.Inverse());
            matrix = Matrix4.Multiply(
                matrix, m_moveToCenterMatrix);
            return Matrix4.Multiply(matrix, m_to2DMatrix);
        }

        /// <summary>
        /// calculate the matrix used to transform 3D to 2D
        /// </summary>
        /// <returns>
        /// matrix used to transform 3d points to 2d
        /// </returns>
        private Matrix4 Get3DTo2DMatrix()
        {
            Matrix4 result = Matrix4.Multiply(
               m_to2DMatrix.Inverse(), m_moveToCenterMatrix.Inverse());
            result = Matrix4.Multiply(result, m_scaleMatrix);
            return Matrix4.Multiply(result, new Matrix4(new Vector4(m_center.X, m_center.Y, 0)));
        }

        /// <summary>
        /// calculate the matrix used to scale
        /// </summary>
        /// <returns>
        /// matrix used to scale the to-be-painted image
        /// </returns>
        private Matrix4 GetScaleMatrix()
        {
            float xScale = m_boundary.Width / (m_boundPoints[1].X - m_boundPoints[0].X);
            float yScale = m_boundary.Height / (m_boundPoints[1].Y - m_boundPoints[0].Y);
            float factor = xScale <= yScale ? xScale : yScale;
            return new Matrix4((float)(factor * m_scaleFactor));
        }

        /// <summary>
        /// Get a matrix which can move points to center
        /// </summary>
        /// <returns>
        /// matrix used to move point to center of canvas
        /// </returns>
        private Matrix4 GetMoveToCenterMatrix()
        {
            //translate the origin to bound center
            PointF min = m_boundPoints[0];
            PointF max = m_boundPoints[1];
            PointF center = new PointF((min.X + max.X) / 2, (min.Y + max.Y) / 2);
            return new Matrix4(new Vector4(center.X, center.Y, 0));
        }

        /// <summary>
        /// Get max and min coordinates of all points
        /// </summary>
        /// <returns>
        /// points array stores the bounding box of all points
        /// </returns>
        private List<PointF> GetBoundsPoints()
        {
            Matrix4 matrix = m_to2DMatrix;
            Matrix4 inverseMatrix = matrix.Inverse();
            float minX = 0, maxX = 0, minY = 0, maxY = 0;
            bool bFirstPoint = true;
            List<PointF> resultPoints = new List<PointF>();

            //get the max and min point on the face
            foreach (Autodesk.Revit.DB.XYZ point in m_drawing.Geometry.GridVertexesXYZ)
            {
                Vector4 v = new Vector4(point);
                Vector4 v1 = inverseMatrix.Transform(v);

                if (bFirstPoint)
                {
                    minX = maxX = v1.X;
                    minY = maxY = v1.Y;
                    bFirstPoint = false;
                }
                else
                {
                    if (v1.X < minX) { minX = v1.X; }
                    else if (v1.X > maxX) { maxX = v1.X; }

                    if (v1.Y < minY) { minY = v1.Y; }
                    else if (v1.Y > maxY) { maxY = v1.Y; }
                }
            }
            resultPoints.Add(new PointF(minX, minY));
            resultPoints.Add(new PointF(maxX, maxY));
            return resultPoints;
        }

        /// <summary>
        /// Get a matrix which can transform points to 2D
        /// </summary>
        /// <returns>
        /// matrix which can transform points to 2D
        /// </returns>
        private Matrix4 GetTo2DMatrix()
        {
            Autodesk.Revit.DB.XYZ startXYZ = m_myDocument.WallGeometry.StartXYZ;
            Autodesk.Revit.DB.XYZ endXYZ = m_myDocument.WallGeometry.EndXYZ;
            XYZ sub = endXYZ - startXYZ;
            Vector4 xAxis = new Vector4(new Autodesk.Revit.DB.XYZ(sub.X, sub.Y, sub.Z));
            xAxis.Normalize();
            //because in the windows UI, Y axis is downward
            Vector4 yAxis = new Vector4(new Autodesk.Revit.DB.XYZ(0, 0, -1));
            yAxis.Normalize();
            Vector4 zAxis = Vector4.CrossProduct(xAxis, yAxis);
            zAxis.Normalize();
            Vector4 origin = new Vector4(m_drawing.Geometry.GridVertexesXYZ[0]);

            return new Matrix4(xAxis, yAxis, zAxis, origin);
        }
        #endregion
    } // end of class
}