应用程序名称:CurtainSystem

Revit 平台:所有

Revit 版本:2011.0

首次发布于:2009.0

编程语言:C#

技能级别:中等

类别:元素

类型:外部命令

主题:创建幕墙系统

概要:本示例演示了 4 个主要功能:

1. 如何在指定的面上创建幕墙系统。

2. 如何删除幕墙系统。

3. 如何向幕墙系统添加幕墙网格。

4. 如何从幕墙系统中删除幕墙网格。

相关类:

Autodesk.Revit.UI.IExternalCommand

Autodesk.Revit.DB.Document

Autodesk.Revit.Creation.Document

Autodesk.Revit.DB.CurtainSystem

项目文件

Command.cs

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

Data\Document.cs

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

CurtainSystem\MassChecker.cs

该文件包含了“MassChecker”类,用于检查用户是否选择了一个长方体体量。

CurtainSystem\SystemData.cs

该文件包含了“SystemData”类,用于存储本示例中创建的所有幕墙系统并管理它们的行为。

Utility\MathTools.cs

该文件包含了一个名为“Vector4”的类,用于管理数学计算。

功能:

- 使用指定的面创建幕墙系统。

- 删除所选幕墙系统。

- 向未被覆盖的面添加幕墙网格。

- 从幕墙系统中删除幕墙网格。

实施:

1. 准备您的Revit项目。

打开或新建一个Revit项目,并确保其中包含一个长方体体量。示例项目文件CurtainSystem.rvt 可在示例文件夹中找到。

2. 选择文档中的体量。

3. 运行此命令,主对话框将弹出。

4. 单击“创建幕墙系统”按钮,会弹出一个新对话框,该对话框用于用户创建幕墙系统。

5. 通过选中列表框中的项目或右侧的按钮选择一些面。

6. :让用户选择创建幕墙系统的方式:如果选中,则该幕墙系统将通过内部代码中的面阵列创建,用户在创建后无法添加/删除幕墙网格;如果不选中,则该幕墙系统将通过面的参照创建,用户可以稍后添加/删除幕墙网格。

7. 单击“创建幕墙系统”按钮以创建幕墙系统,然后该对话框将关闭,主对话框将再次弹出。

8. 选择一个幕墙系统;其幕墙网格信息将在列表框的右侧显示。

9. 如果幕墙系统是通过面阵列创建的,则“Add>>”和“<<Remove”按钮将被禁用;如果是通过面的参照创建的,则按钮将启用。

10. 通过选中幕墙系统的参照,选择一些未被覆盖的面,并单击“Add>>”按钮,这些面将用幕墙网格覆盖。

11. 通过选中幕墙系统的参照,选择一些幕墙网格并单击“<<Remove”按钮,这些幕墙网格将从面中移除。

12. 选择一些幕墙系统并选中它们,单击“删除幕墙系统”按钮以删除这些幕墙系统。

13. 单击“退出”以退出对话框。

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

MassChecker.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 Autodesk.Revit;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI.Selection;
using Revit.SDK.Samples.CurtainSystem.CS.Data;

namespace Revit.SDK.Samples.CurtainSystem.CS.CurtainSystem
{
   /// <summary>
   /// check whether the selected element is a mass and whether the mass is kind of parallelepiped
   /// (only mass of parallelepiped supported in this sample)
   /// </summary>
   class MassChecker
   {
      // the document containing all the data used in the sample
      MyDocument m_mydocument;

      /// <summary>
      /// Constructor
      /// </summary>
      /// <param name="mydoc">
      /// the document of the sample
      /// </param>
      public MassChecker(MyDocument mydoc)
      {
         m_mydocument = mydoc;
      }

      /// <summary>
      /// check whether the selection is a parallelepiped mass
      /// </summary>
      public bool CheckSelectedMass()
      {
         // get the selected mass
         FamilyInstance mass = GetSelectedMass();
         // start the sample without making a parallelepiped mass selected
         if (null == mass)
         {
            m_mydocument.FatalErrorMsg = Properties.Resources.MSG_InvalidSelection;
            return false;
         }

         // check whether the mass is parallelepiped
         bool isMassParallelepiped = IsMassParallelepiped(mass);
         if (false == isMassParallelepiped)
         {
            m_mydocument.FatalErrorMsg = Properties.Resources.MSG_InvalidSelection;
            return false;
         }

         return true;
      }

      /// <summary>
      /// check whether the mass is a parallelepiped mass by checking the faces' normals
      /// (if it's a parallelepiped mass, it will have 3 groups of parallel faces)
      /// </summary>
      /// <param name="mass">
      /// the mass to be checked
      /// </param>
      /// <returns>
      /// return true if the mass is parallelepiped; otherwise false
      /// </returns>
      private bool IsMassParallelepiped(FamilyInstance mass)
      {
         FaceArray faces = GetMassFaceArray(mass);

         // a parallelepiped always has 6 faces
         if (null == faces ||
             6 != faces.Size)
         {
            return false;
         }

         bool isFacesParallel = IsFacesParallel(faces);

         // store the face array
         if (true == isFacesParallel)
         {
            m_mydocument.MassFaceArray = faces;
         }

         return isFacesParallel;
      }

      /// <summary>
      /// check whether the faces of the mass are one-one parallel
      /// </summary>
      /// <param name="faces">
      /// the 6 faces of the mass
      /// </param>
      /// <returns>
      /// if the 6 faces are one-one parallel, return true; otherwise false
      /// </returns>
      private bool IsFacesParallel(FaceArray faces)
      {
         // step1: get the normals of the 6 faces
         List<Utility.Vector4> normals = new List<Utility.Vector4>();
         foreach (Face face in faces)
         {
            EdgeArrayArray edgeArrayArray = face.EdgeLoops;
            EdgeArray edges = edgeArrayArray.get_Item(0);

            if (null == edges ||
                2 > edges.Size)
            {
               return false;
            }

            // we use the cross product of 2 non-parallel vectors as the normal
            for (int i = 0; i < edges.Size - 1; i++)
            {
               Edge edgeA = edges.get_Item(i);
               Edge edgeB = edges.get_Item(i + 1);

               // if edgeA & edgeB are parallel, can't compute  the cross product
               bool isLinesParallel = IsLinesParallel(edgeA, edgeB);

               if (true == isLinesParallel)
               {
                  continue;
               }

               Utility.Vector4 vec4 = ComputeCrossProduct(edgeA, edgeB);
               normals.Add(vec4);
               break;
            }
         }

         // step 2: the 6 normals should be one-one parallel pairs
         if (null == normals ||
             6 != normals.Count)
         {
            return false;
         }

         bool[] matchedList = new bool[6];
         for (int i = 0; i < matchedList.Length; i++)
         {
            matchedList[i] = false;
         }

         // check whether the normal has another matched parallel normal
         for (int i = 0; i < matchedList.Length; i++)
         {
            if (true == matchedList[i])
            {
               continue;
            }

            Utility.Vector4 vec4A = normals[i];

            for (int j = 0; j < matchedList.Length; j++)
            {
               if (j == i ||
                   true == matchedList[j])
               {
                  continue;
               }

               Utility.Vector4 vec4B = normals[j];

               if (true == IsLinesParallel(vec4A, vec4B))
               {
                  matchedList[i] = true;
                  matchedList[j] = true;
                  break;
               }
            }
         }

         // step 3: check each of the 6 normals has matched parallel normal
         for (int i = 0; i < matchedList.Length; i++)
         {
            if (false == matchedList[i])
            {
               return false;
            }
         }

         // all the normals have matched parallel normals
         return true;
      }

      /// <summary>
      /// check whether 2 edges are parallel
      /// </summary>
      /// <param name="edgeA">
      /// the edge to be checked
      /// </param>
      /// <param name="edgeB">
      /// the edge to be checked
      /// </param>
      /// <returns>
      /// if they're parallel, return true; otherwise false
      /// </returns>
      private bool IsLinesParallel(Edge edgeA, Edge edgeB)
      {
         List<XYZ> pointsA = edgeA.Tessellate() as List<XYZ>;
         List<XYZ> pointsB = edgeB.Tessellate() as List<XYZ>;
         Autodesk.Revit.DB.XYZ vectorA = pointsA[1] - pointsA[0];
         Autodesk.Revit.DB.XYZ vectorB = pointsB[1] - pointsB[0];
         Utility.Vector4 vec4A = new Utility.Vector4(vectorA);
         Utility.Vector4 vec4B = new Utility.Vector4(vectorB);
         return IsLinesParallel(vec4A, vec4B);
      }

      /// <summary>
      /// check whether 2 vectors are parallel
      /// </summary>
      /// <param name="vec4A">
      /// the vector to be checked
      /// </param>
      /// <param name="vec4B">
      /// the vector to be checked
      /// </param>
      /// <returns>
      /// if they're parallel, return true; otherwise false
      /// </returns>
      private bool IsLinesParallel(Utility.Vector4 vec4A, Utility.Vector4 vec4B)
      {
         // if 2 vectors are parallel, they should be like the following formula:
         // vec4A.X    vec4A.Y    vec4A.Z
         // ------- == ------- == -------
         // vec4B.X    vec4B.Y    vec4B.Z
         // change to multiply, it's 
         // vec4A.X * vec4B.Y == vec4A.Y * vec4B.X &&
         // vec4A.Y * vec4B.Z == vec4A.Z * vec4B.Y
         double aa = vec4A.X * vec4B.Y;
         double bb = vec4A.Y * vec4B.X;
         double cc = vec4A.Y * vec4B.Z;
         double dd = vec4A.Z * vec4B.Y;
         double ee = vec4A.X * vec4B.Z;
         double ff = vec4A.Z * vec4B.X;

         const double tolerance = 0.0001d;

         if (Math.Abs(aa - bb) < tolerance &&
             Math.Abs(cc - dd) < tolerance &&
             Math.Abs(ee - ff) < tolerance)
         {
            return true;
         }

         return false;
      }

      /// <summary>
      /// compute the cross product of 2 edges
      /// </summary>
      /// <param name="edgeA">
      /// the edge for the cross product
      /// </param>
      /// <param name="edgeB">
      /// the edge for the cross product
      /// </param>
      /// <returns>
      /// the cross product of 2 edges
      /// </returns>
      private Utility.Vector4 ComputeCrossProduct(Edge edgeA, Edge edgeB)
      {
         List<XYZ> pointsA = edgeA.Tessellate() as List<XYZ>;
         List<XYZ> pointsB = edgeB.Tessellate() as List<XYZ>;
         Autodesk.Revit.DB.XYZ vectorA = pointsA[1] - pointsA[0];
         Autodesk.Revit.DB.XYZ vectorB = pointsB[1] - pointsB[0];
         Utility.Vector4 vec4A = new Utility.Vector4(vectorA);
         Utility.Vector4 vec4B = new Utility.Vector4(vectorB);
         return Utility.Vector4.CrossProduct(vec4A, vec4B);
      }

      /// <summary>
      /// get the faces of the mass
      /// </summary>
      /// <param name="mass">
      /// the source mass
      /// </param>
      /// <returns>
      /// the faces of the mass
      /// </returns>
      private FaceArray GetMassFaceArray(FamilyInstance mass)
      {
         // Obtain the gemotry information of the mass
         Autodesk.Revit.DB.Options opt = m_mydocument.CommandData.Application.Application.Create.NewGeometryOptions();
         opt.DetailLevel = ViewDetailLevel.Fine;
         opt.ComputeReferences = true;
         Autodesk.Revit.DB.GeometryElement geoElement = null;
         try
         {
            geoElement = mass.get_Geometry(opt);
         }
         catch (System.Exception)
         {
            return null;
         }

         if (null == geoElement)
         {
            return null;
         }

         //GeometryObjectArray objectarray = geoElement.Objects;
         //foreach (GeometryObject obj in objectarray)
         IEnumerator<GeometryObject> Objects = geoElement.GetEnumerator();
         while (Objects.MoveNext())
         {
            GeometryObject obj = Objects.Current;

            Solid solid = obj as Solid;

            if (null != solid &&
                null != solid.Faces &&
                0 != solid.Faces.Size)
            {
               return solid.Faces;
            }
         }

         return null;
      }

      /// <summary>
      /// get the selected mass
      /// </summary>
      /// <returns>
      /// return the selected mass; if it's not a mass, return null
      /// </returns>
      private FamilyInstance GetSelectedMass()
      {
         FamilyInstance resultMass = null;

         // check whether a mass was selected before launching this sample
         Selection selection = m_mydocument.UIDocument.Selection;
         ElementSet elementSet = new ElementSet();
         foreach (ElementId elementId in selection.GetElementIds())
         {
            elementSet.Insert(m_mydocument.UIDocument.Document.GetElement(elementId));
         }
         if (null == selection ||
             null == elementSet ||
             true == elementSet.IsEmpty ||
             1 != elementSet.Size)
         {
            //m_mydocument.FatalErrorMsg = Properties.Resources.MSG_InvalidSelection;
            return null;
         }

         foreach (Autodesk.Revit.DB.ElementId selElementId in selection.GetElementIds())
         {
            Autodesk.Revit.DB.Element selElement = m_mydocument.UIDocument.Document.GetElement(selElementId);
            FamilyInstance inst = selElement as FamilyInstance;
            if (null != inst &&
                "Mass" == inst.Category.Name)
            {
               resultMass = inst;
               break;
            }
         }

         // nothing selected or the selected element is not a mass
         if (null == resultMass)
         {
            //m_mydocument.FatalErrorMsg = Properties.Resources.MSG_InvalidSelection;
            return null;
         }

         return resultMass;
      }

   }
}

SystemData.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 Autodesk.Revit;
using Autodesk.Revit.DB;
using Revit.SDK.Samples.CurtainSystem.CS.Data;

namespace Revit.SDK.Samples.CurtainSystem.CS.CurtainSystem
{
    /// <summary>
    /// the class to maintain the data and operations of the curtain system
    /// </summary>
    public class SystemData
    {
        // the data of the sample
        MyDocument m_mydocument;

        // the count of the created curtain systems
        static int m_csIndex = -1;

        // all the created curtain systems and their data
        List<SystemInfo> m_curtainSystemInfos;
        /// <summary>
        /// all the created curtain systems and their data
        /// </summary>
        public List<SystemInfo> CurtainSystemInfos
        {
            get
            {
                return m_curtainSystemInfos;
            }
        }

        /// <summary>
        /// occurs only when new curtain system added/removed
        /// the delegate method to handle the curtain system added/removed events
        /// </summary>
        public delegate void CurtainSystemChangedHandler();
        /// <summary>
        /// the event triggered when curtain system added/removed
        /// </summary>
        public event CurtainSystemChangedHandler CurtainSystemChanged;

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="mydoc">
        /// the document of the sample
        /// </param>
        public SystemData(MyDocument mydoc)
        {
            m_mydocument = mydoc;
            m_curtainSystemInfos = new List<SystemInfo>();
        }

        /// <summary>
        /// create a new curtain system
        /// </summary>
        /// <param name="faceIndices">
        /// the faces to be covered with new curtain system
        /// </param>
        /// <param name="byFaceArray">
        /// indicates whether the curtain system will be created by face array
        /// </param>
        public void CreateCurtainSystem(List<int> faceIndices, bool byFaceArray)
        {
            // just refresh the main UI
            if (null == faceIndices ||
                0 == faceIndices.Count)
            {
                if (null != CurtainSystemChanged)
                {
                    CurtainSystemChanged();
                }
                return;
            }
            SystemInfo resultInfo = new SystemInfo(m_mydocument);
            resultInfo.ByFaceArray = byFaceArray;
            resultInfo.GridFacesIndices = faceIndices;
            resultInfo.Index = ++m_csIndex;

            //
            // step 1: create the curtain system
            //
            // create the curtain system by face array
            if (true == byFaceArray)
            {
                FaceArray faceArray = new FaceArray();
                foreach (int index in faceIndices)
                {
                    faceArray.Append(m_mydocument.MassFaceArray.get_Item(index));
                }

                Autodesk.Revit.DB.CurtainSystem curtainSystem = null;
                Transaction t = new Transaction(m_mydocument.Document, Guid.NewGuid().GetHashCode().ToString());
                t.Start();
                try
                {
                    curtainSystem = m_mydocument.Document.Create.NewCurtainSystem(faceArray, m_mydocument.CurtainSystemType);
                }
                catch (System.Exception)
                {
                    m_mydocument.FatalErrorMsg = Properties.Resources.MSG_CreateCSFailed;
                    t.RollBack();
                    return;
                }

                t.Commit();

                resultInfo.CurtainForm = curtainSystem;
            }
            // create the curtain system by reference array
            else
            {
                ReferenceArray refArray = new ReferenceArray();
                foreach (int index in faceIndices)
                {
                    refArray.Append(m_mydocument.MassFaceArray.get_Item(index).Reference);
                }

                ICollection<ElementId> curtainSystems = null;
                Transaction t = new Transaction(m_mydocument.Document, Guid.NewGuid().GetHashCode().ToString());
                t.Start();
                try
                {
                    curtainSystems = m_mydocument.Document.Create.NewCurtainSystem2(refArray, m_mydocument.CurtainSystemType);
                }
                catch (System.Exception)
                {
                    m_mydocument.FatalErrorMsg = Properties.Resources.MSG_CreateCSFailed;
                    t.RollBack();
                    return;
                }
                t.Commit();

                // internal fatal error, quit the sample
                if (null == curtainSystems ||
                    1 != curtainSystems.Count)
                {
                    m_mydocument.FatalErrorMsg = Properties.Resources.MSG_MoreThan1CSCreated;
                    return;
                }

                // store the curtain system
                foreach (ElementId cs in curtainSystems)
                {
                    resultInfo.CurtainForm = m_mydocument.Document.GetElement(cs) as Autodesk.Revit.DB.CurtainSystem;
                    break;
                }
            }
            //
            // step 2: update the curtain system list in the main UI
            //
            m_curtainSystemInfos.Add(resultInfo);
            if (null != CurtainSystemChanged)
            {
                CurtainSystemChanged();
            }
        }

        /// <summary>
        /// delete the curtain systems
        /// </summary>
        /// <param name="checkedIndices">
        /// the curtain systems to be deleted
        /// </param>
        public void DeleteCurtainSystem(List<int> checkedIndices)
        {
            Transaction t = new Transaction(m_mydocument.Document, Guid.NewGuid().GetHashCode().ToString());
            t.Start();
            foreach (int index in checkedIndices)
            {
                SystemInfo info = m_curtainSystemInfos[index];
                if (null != info.CurtainForm)
                {
                    m_mydocument.Document.Delete(info.CurtainForm.Id);
                    info.CurtainForm = null;
                }
            }
            t.Commit();

            // update the list of created curtain systems
            // remove the "deleted" curtain systems out
            List<SystemInfo> infos = m_curtainSystemInfos;
            m_curtainSystemInfos = new List<SystemInfo>();

            foreach (SystemInfo info in infos)
            {
                if (null != info.CurtainForm)
                {
                    m_curtainSystemInfos.Add(info);
                }
            }

            if (null != CurtainSystemChanged)
            {
                CurtainSystemChanged();
            }
        }


    }// end of class

    /// <summary>
    /// the information of a curtain system
    /// </summary>
    public class SystemInfo
    {
        // the data of the sample
        MyDocument m_mydocument;

        // the curtain system
        Autodesk.Revit.DB.CurtainSystem m_curtainSystem;
        /// <summary>
        /// the curtain system
        /// </summary>
        public Autodesk.Revit.DB.CurtainSystem CurtainForm
        {
            get
            {
                return m_curtainSystem;
            }
            set
            {
                m_curtainSystem = value;
            }
        }

        // indicates which faces the curtain system covers
        List<int> m_gridFacesIndices;
        /// <summary>
        /// indicates which faces the curtain system covers
        /// </summary>
        public List<int> GridFacesIndices
        {
            get
            {
                return m_gridFacesIndices;
            }
            set
            {
                m_gridFacesIndices = value;

                // the faces which don't be included will be added to the m_uncoverFacesIndices collection
                for (int i = 0; i < 6; i++)
                {
                    if (false == m_gridFacesIndices.Contains(i))
                    {
                        m_uncoverFacesIndices.Add(i);
                    }
                }
            }
        }

        // the uncovered faces
        List<int> m_uncoverFacesIndices;
        /// <summary>
        /// the uncovered faces
        /// </summary>
        public List<int> UncoverFacesIndices
        {
            get
            {
                return m_uncoverFacesIndices;
            }
        }

        // indicates whether the curtain system is created by face array
        private bool m_byFaceArray;
        /// <summary>
        /// indicates whether the curtain system is created by face array
        /// </summary>
        public bool ByFaceArray
        {
            get
            {
                return m_byFaceArray;
            }
            set
            {
                m_byFaceArray = value;
            }
        }

        // the name of the curtain system, identified by its index
        private string m_name;
        /// <summary>
        /// the name of the curtain system, identified by its index
        /// </summary>
        public string Name
        {
            get
            {
                return m_name;
            }
        }

        // the index of the curtain systems
        private int m_index;
        /// <summary>
        /// the index of the curtain systems
        /// </summary>
        public int Index
        {
            get
            {
                return m_index;
            }
            set
            {
                m_index = value;
                m_name = "Curtain System " + m_index;
            }
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="mydoc">
        /// the document of the sample
        /// </param>
        public SystemInfo(MyDocument mydoc)
        {
            m_mydocument = mydoc;
            m_gridFacesIndices = new List<int>();
            m_uncoverFacesIndices = new List<int>();
            m_byFaceArray = false;
            m_index = 0;
        }

        /// <summary>
        /// add some curtain grids to the curtain system
        /// </summary>
        /// <param name="faceIndices">
        /// the faces to be covered
        /// </param>
        public void AddCurtainGrids(List<int> faceIndices)
        {
            // step 1: find out the faces to be covered
            List<Reference> refFaces = new List<Reference>();
            foreach (int index in faceIndices)
            {
                refFaces.Add(m_mydocument.MassFaceArray.get_Item(index).Reference);
            }
            // step 2: cover the selected faces with curtain grids
            Transaction t = new Transaction(m_mydocument.Document, Guid.NewGuid().GetHashCode().ToString());
            t.Start();
            try
            {
                foreach (Reference refFace in refFaces)
                {
                    m_curtainSystem.AddCurtainGrid(refFace);
                }
            }
            catch (System.Exception)
            {
                m_mydocument.FatalErrorMsg = Properties.Resources.MSG_AddCGFailed;
                t.RollBack();
                return;
            }
            t.Commit();

            // step 3: update the uncovered faces and curtain grid faces data
            foreach (int i in faceIndices)
            {
                m_uncoverFacesIndices.Remove(i);
                m_gridFacesIndices.Add(i);
            }
        }

        /// <summary>
        /// remove the selected curtain grids
        /// </summary>
        /// <param name="faceIndices">
        /// the curtain grids to be removed
        /// </param>
        public void RemoveCurtainGrids(List<int> faceIndices)
        {
            // step 1: find out the faces to be covered
            List<Reference> refFaces = new List<Reference>();
            foreach (int index in faceIndices)
            {
                refFaces.Add(m_mydocument.MassFaceArray.get_Item(index).Reference);
            }
            // step 2: remove the selected curtain grids
            Transaction t = new Transaction(m_mydocument.Document, Guid.NewGuid().GetHashCode().ToString());
            t.Start();
            try
            {
                foreach (Reference refFace in refFaces)
                {
                    m_curtainSystem.RemoveCurtainGrid(refFace);
                }
            }
            catch (System.Exception)
            {
                m_mydocument.FatalErrorMsg = Properties.Resources.MSG_RemoveCGFailed;
                t.RollBack();
                return;
            }
            t.Commit();

            // step 3: update the uncovered faces and curtain grid faces data
            foreach (int i in faceIndices)
            {
                m_gridFacesIndices.Remove(i);
                m_uncoverFacesIndices.Add(i);
            }
        }

        /// <summary>
        /// override ToString method
        /// </summary>
        /// <returns>
        /// the string value of the class
        /// </returns>
        public override string ToString()
        {
            return m_name;
        }
    }

    /// <summary>
    /// the information for the curtain grid (which face does it lay on)
    /// </summary>
    public class GridFaceInfo
    {
        // the host face of the curtain grid
        private int m_faceIndex;
        /// <summary>
        /// the host face of the curtain grid
        /// </summary>
        public int FaceIndex
        {
            get
            {
                return m_faceIndex;
            }
            set
            {
                m_faceIndex = value;
            }
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="index">
        /// the index of the host face
        /// </param>
        public GridFaceInfo(int index)
        {
            m_faceIndex = index;
        }

        /// <summary>
        /// the string value for the class
        /// </summary>
        /// <returns>
        /// the string value for the class
        /// </returns>
        public override string ToString()
        {
            return "Grid on Face " + m_faceIndex;
        }
    }

    /// <summary>
    /// the information for the faces of the mass
    /// </summary>
    public class UncoverFaceInfo
    {
        // indicates the index for the face
        private int m_faceIndex;
        /// <summary>
        /// indicates the index for the face
        /// </summary>
        public int Index
        {
            get
            {
                return m_faceIndex;
            }
            set
            {
                m_faceIndex = value;
            }
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="index">
        /// the index of the face
        /// </param>
        public UncoverFaceInfo(int index)
        {
            m_faceIndex = index;
        }

        /// <summary>
        /// the string value for the class
        /// </summary>
        /// <returns>
        /// the string value for the class
        /// </returns>
        public override string ToString()
        {
            return "Face " + m_faceIndex;
        }
    }
}

MyDocument.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.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

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

namespace Revit.SDK.Samples.CurtainSystem.CS.Data
{
    /// <summary>
    /// maintains all the data used in the sample
    /// </summary>
    public class MyDocument
    {
        // object which contains reference of Revit Applicatio
        ExternalCommandData m_commandData;
        /// <summary>
        /// object which contains reference of Revit Applicatio
        /// </summary>
        public ExternalCommandData CommandData
        {
            get
            {
                return m_commandData;
            }
            set
            {
                m_commandData = value;
            }
        }

        // the active UI document of Revit
        Autodesk.Revit.UI.UIDocument m_uiDocument;

        /// <summary>
        /// the active document of Revit
        /// </summary>
        public Autodesk.Revit.UI.UIDocument UIDocument
        {
            get
            {
                return m_uiDocument;
            }
        }

        Autodesk.Revit.DB.Document m_document;
        public Autodesk.Revit.DB.Document Document
        {
            get { return m_document; }
        }

        // the data of the created curtain systems
        CurtainSystem.SystemData m_systemData;
        /// <summary>
        /// the data of the created curtain systems
        /// </summary>
        public CurtainSystem.SystemData SystemData
        {
            get
            {
                return m_systemData;
            }
            set
            {
                m_systemData = value;
            }
        }

        // all the faces of  the parallelepiped mass
        FaceArray m_massFaceArray;
        /// <summary>
        /// // all the faces of  the parallelepiped mass
        /// </summary>
        public FaceArray MassFaceArray
        {
            get
            {
                return m_massFaceArray;
            }
            set
            {
                m_massFaceArray = value;
            }
        }

        // the curtain system type of the active Revit document, used for curtain system creation
        CurtainSystemType m_curtainSystemType;
        /// <summary>
        /// the curtain system type of the active Revit document, used for curtain system creation
        /// </summary>
        public CurtainSystemType CurtainSystemType
        {
            get
            {
                return m_curtainSystemType;
            }
            set
            {
                m_curtainSystemType = value;
            }
        }
        // the message shown when there's a fatal error in the sample
        string m_fatalErrorMsg = null;
        /// <summary>
        /// the message shown when there's a fatal error in the sample
        /// </summary>
        public string FatalErrorMsg
        {
            get
            {
                return m_fatalErrorMsg;
            }
            set
            {
                m_fatalErrorMsg = value;

                if (false == string.IsNullOrEmpty(m_fatalErrorMsg) &&
                    null != FatalErrorEvent)
                {
                    FatalErrorEvent(m_fatalErrorMsg);
                }
            }
        }

        /// <summary>
        /// occurs only when the message was updated
        /// the delegate method to handle the message update event
        /// </summary>
        public delegate void MessageChangedHandler();
        /// <summary>
        /// the event triggered when message updated/changed
        /// </summary>
        public event MessageChangedHandler MessageChanged;

        /// <summary>
        /// occurs only when there's a fatal error
        /// the delegate method to handle the fatal error event
        /// </summary>
        /// <param name="errorMsg"></param>
        public delegate void FatalErrorHandler(string errorMsg);
        /// <summary>
        /// the event triggered when the sample meets a fatal error
        /// </summary>
        public event FatalErrorHandler FatalErrorEvent;

        // store the message of the sample
        private KeyValuePair<string/*msgText*/, bool/*is warningOrError*/> m_message;
        /// <summary>
        /// store the message of the sample
        /// </summary>
        public KeyValuePair<string/*msgText*/, bool/*is warningOrError*/> Message
        {
            get
            {
                return m_message;
            }
            set
            {
                m_message = value;
                if (null != MessageChanged)
                {
                    MessageChanged();
                }
            }
        }

        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="commandData">
        /// object which contains reference of Revit Application
        /// </param>
        public MyDocument(ExternalCommandData commandData)
        {
            m_commandData = commandData;
            m_uiDocument = m_commandData.Application.ActiveUIDocument;
            m_document = m_uiDocument.Document;

            // initialize the curtain system data
            m_systemData = new CurtainSystem.SystemData(this);

            // get the curtain system type of the active Revit document
            GetCurtainSystemType();
        }

        /// <summary>
        /// get the curtain system type from the active Revit document
        /// </summary>
        private void GetCurtainSystemType()
        {
            FilteredElementCollector filteredElementCollector = new FilteredElementCollector(m_document);
            filteredElementCollector.OfClass(typeof(CurtainSystemType));
            m_curtainSystemType = filteredElementCollector.FirstElement() as CurtainSystemType;
        }

    } // end of class
}