应用程序名称:MaterialQuantities

Revit平台:所有版本

Revit版本:2011.0

首次发布版本:2011.0

编程语言:C#

技能水平:高级

类别:几何

类型:外部命令

主题:材料数量数据提取。

概要:输出构成墙、地板和屋顶的材料的分析,并在Excel中显示输出。

类:

System.Diagnostics.Process

System.Collections.Generic.Dictionary

Autodesk.Revit.DB.FilteredElementCollector

Autodesk.Revit.DB.LogicalAndFilter

Autodesk.Revit.DB.ElementClassFilter

Autodesk.Revit.DB.ElementCategoryFilter

Autodesk.Revit.DB.Material

Autodesk.Revit.DB.Transaction

项目文件:MaterialQuantities.cs

描述:

1. 收集所有屋顶元素。

2. 迭代每个屋顶中发现的每种材料。

3. 查找材料的净体积和面积。

4. 存储材料数量。

5. 将结果写入输出文件。

6. 查找毛材料数量

    a.开始一个新的事务。

    b.删除切割宿主(门、窗、开口)的元素。

    c.查找每种材料的体积和面积。

    d.存储材料数量。

    e.将结果写入输出文件。

    f.回滚事务。

7. 重复上述步骤,用于墙壁和地板。

8. Microsoft Excel中打开输出文件。

说明:

1. 打开MaterialQTestModel.rvtRAC_DS_01_Door_Schedule_i.rvt

2. 运行外部命令。

3. Microsoft Excel将打开一个包含模型中所使用材料信息的电子表格。

源代码

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

MaterialQuantities.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.IO;

using System.Diagnostics;

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

namespace Revit.Samples.MaterialQuantities
{
    /// <summary>
    /// Outputs an analysis of the materials that make up walls, floors, and roofs, and displays the output in Excel.
    /// </summary>
    [Autodesk.Revit.Attributes.Transaction(Autodesk.Revit.Attributes.TransactionMode.Manual)]
    [Autodesk.Revit.Attributes.Regeneration(Autodesk.Revit.Attributes.RegenerationOption.Manual)]
    [Autodesk.Revit.Attributes.Journaling(Autodesk.Revit.Attributes.JournalingMode.NoCommandData)]

    public class Command : IExternalCommand
    {
        static AddInId appId = new AddInId(new Guid("7E5CAC0D-F3D8-4040-89D6-0828D681561B"));
        /// <summary>
        /// The top level command.
        /// </summary>
        /// <param name="revit">An object that is passed to the external application 
        /// which contains data related to the command, 
        /// such as the application object and active view.</param>
        /// <param name="message">A message that can be set by the external application 
        /// which will be displayed if a failure or cancellation is returned by 
        /// the external command.</param>
        /// <param name="elements">A set of elements to which the external application 
        /// can add elements that are to be highlighted in case of failure or cancellation.</param>
        /// <returns>Return the status of the external command. 
        /// A result of Succeeded means that the API external method functioned as expected. 
        /// Cancelled can be used to signify that the user cancelled the external operation 
        /// at some point. Failure should be returned if the application is unable to proceed with 
        /// the operation.</returns>
        public Result Execute(ExternalCommandData revit,
                                                              ref string message,
                                                              ElementSet elements)
        {
            Autodesk.Revit.ApplicationServices.Application app = revit.Application.Application;
            m_doc = revit.Application.ActiveUIDocument.Document;

            String filename = "CalculateMaterialQuantities.txt";

            m_writer = new StreamWriter(filename);

            ExecuteCalculationsWith<RoofMaterialQuantityCalculator>();
            ExecuteCalculationsWith<WallMaterialQuantityCalculator>();
            ExecuteCalculationsWith<FloorMaterialQuantityCalculator>();

            m_writer.Close();

            // This operation doesn't change the model, so return cancelled to cancel the transaction
            return Result.Cancelled;
        }

        /// <summary>
        /// Executes a calculator for one type of Revit element.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        private void ExecuteCalculationsWith<T>() where T : MaterialQuantityCalculator, new()
        {
            T calculator = new T();
            calculator.SetDocument(m_doc);
            calculator.CalculateMaterialQuantities();
            calculator.ReportResults(m_writer);
        }

        #region Basic Command Data
        private Document m_doc;

        private TextWriter m_writer;
        #endregion

    }

    /// <summary>
    /// The wall material quantity calculator specialized class.
    /// </summary>
    class WallMaterialQuantityCalculator : MaterialQuantityCalculator
    {
        protected override void CollectElements()
        {
            // filter for non-symbols that match the desired category so that inplace elements will also be found
            FilteredElementCollector collector = new FilteredElementCollector(m_doc);
            m_elementsToProcess = collector.OfCategory(BuiltInCategory.OST_Walls).WhereElementIsNotElementType().ToElements();
        }

        protected override string GetElementTypeName()
        {
            return "Wall";
        }
    }

    /// <summary>
    /// The floor material quantity calculator specialized class.
    /// </summary>
    class FloorMaterialQuantityCalculator : MaterialQuantityCalculator
    {
        protected override void CollectElements()
        {
            FilteredElementCollector collector = new FilteredElementCollector(m_doc);
            m_elementsToProcess = collector.OfCategory(BuiltInCategory.OST_Floors).WhereElementIsNotElementType().ToElements();
        }

        protected override string GetElementTypeName()
        {
            return "Floor";
        }
    }

    /// <summary>
    /// The roof material quantity calculator specialized class.
    /// </summary>
    class RoofMaterialQuantityCalculator : MaterialQuantityCalculator
    {
        protected override void CollectElements()
        {
            FilteredElementCollector collector = new FilteredElementCollector(m_doc);
            m_elementsToProcess = collector.OfCategory(BuiltInCategory.OST_Roofs).WhereElementIsNotElementType().ToElements();
        }

        protected override string GetElementTypeName()
        {
            return "Roof";
        }
    }

    /// <summary>
    /// The base material quantity calculator for all element types.
    /// </summary>
    abstract class MaterialQuantityCalculator
    {
        /// <summary>
        /// The list of elements for material quantity extraction.
        /// </summary>
        protected IList<Element> m_elementsToProcess;

        /// <summary>
        /// Override this to populate the list of elements for material quantity extraction.
        /// </summary>
        protected abstract void CollectElements();

        /// <summary>
        /// Override this to return the name of the element type calculated by this calculator.
        /// </summary>
        protected abstract String GetElementTypeName();

        /// <summary>
        /// Sets the document for the calculator class.
        /// </summary>
        public void SetDocument(Document d)
        {
            m_doc = d;
            Autodesk.Revit.ApplicationServices.Application app = d.Application;
        }

        /// <summary>
        /// Executes the calculation.
        /// </summary>
        public void CalculateMaterialQuantities()
        {
            CollectElements();
            CalculateNetMaterialQuantities();
            CalculateGrossMaterialQuantities();
        }

        /// <summary>
        /// Calculates net material quantities for the target elements.
        /// </summary>
        private void CalculateNetMaterialQuantities()
        {
            foreach (Element e in m_elementsToProcess)
            {
                CalculateMaterialQuantitiesOfElement(e);
            }
        }

        /// <summary>
        /// Calculates gross material quantities for the target elements (material quantities with 
        /// all openings, doors and windows removed). 
        /// </summary>
        private void CalculateGrossMaterialQuantities()
        {
            m_calculatingGrossQuantities = true;
            Transaction t = new Transaction(m_doc);
            t.SetName("Delete Cutting Elements");
            t.Start();
            DeleteAllCuttingElements();
            m_doc.Regenerate();
            foreach (Element e in m_elementsToProcess)
            {
                CalculateMaterialQuantitiesOfElement(e);
            }
            t.RollBack();
        }

        /// <summary>
        /// Delete all elements that cut out of target elements, to allow for calculation of gross material quantities.
        /// </summary>
        private void DeleteAllCuttingElements()
        {
            IList<ElementFilter> filterList = new List<ElementFilter>();
            FilteredElementCollector collector = new FilteredElementCollector(m_doc);

            // (Type == FamilyInstance && (Category == Door || Category == Window) || Type == Opening
            ElementClassFilter filterFamilyInstance = new ElementClassFilter(typeof(FamilyInstance));
            ElementCategoryFilter filterWindowCategory = new ElementCategoryFilter(BuiltInCategory.OST_Windows);
            ElementCategoryFilter filterDoorCategory = new ElementCategoryFilter(BuiltInCategory.OST_Doors);
            LogicalOrFilter filterDoorOrWindowCategory = new LogicalOrFilter(filterWindowCategory, filterDoorCategory);
            LogicalAndFilter filterDoorWindowInstance = new LogicalAndFilter(filterDoorOrWindowCategory, filterFamilyInstance);

            ElementClassFilter filterOpening = new ElementClassFilter(typeof(Opening));

            LogicalOrFilter filterCuttingElements = new LogicalOrFilter(filterOpening, filterDoorWindowInstance);
            ICollection<Element> cuttingElementsList = collector.WherePasses(filterCuttingElements).ToElements();

            foreach (Element e in cuttingElementsList)
            {
                // Doors in curtain grid systems cannot be deleted.  This doesn't actually affect the calculations because
                // material quantities are not extracted for curtain systems.
                if (e.Category != null)
                {
                    if (e.Category.Id.IntegerValue == (int)BuiltInCategory.OST_Doors)
                    {
                        FamilyInstance door = e as FamilyInstance;
                        Wall host = door.Host as Wall;

                        if (host.CurtainGrid != null)
                            continue;
                    }
                    ICollection<ElementId> deletedElements = m_doc.Delete(e.Id);

                    // Log failed deletion attempts to the output.  (These may be other situations where deletion is not possible but 
                    // the failure doesn't really affect the results.
                    if (deletedElements == null || deletedElements.Count < 1)
                    {
                        m_warningsForGrossQuantityCalculations.Add(
                                 String.Format("   The tool was unable to delete the {0} named {2} (id {1})", e.GetType().Name, e.Id, e.Name));
                    }
                }
            }
        }

        /// <summary>
        /// Store calculated material quantities in the storage collection.
        /// </summary>
        /// <param name="materialId">The material id.</param>
        /// <param name="volume">The extracted volume.</param>
        /// <param name="area">The extracted area.</param>
        /// <param name="quantities">The storage collection.</param>
        private void StoreMaterialQuantities(ElementId materialId, double volume, double area,
                                            Dictionary<ElementId, MaterialQuantities> quantities)
        {
            MaterialQuantities materialQuantityPerElement;
            bool found = quantities.TryGetValue(materialId, out materialQuantityPerElement);
            if (found)
            {
                if (m_calculatingGrossQuantities)
                {
                    materialQuantityPerElement.GrossVolume += volume;
                    materialQuantityPerElement.GrossArea += area;
                }
                else
                {
                    materialQuantityPerElement.NetVolume += volume;
                    materialQuantityPerElement.NetArea += area;
                }
            }
            else
            {
                materialQuantityPerElement = new MaterialQuantities();
                if (m_calculatingGrossQuantities)
                {
                    materialQuantityPerElement.GrossVolume = volume;
                    materialQuantityPerElement.GrossArea = area;
                }
                else
                {
                    materialQuantityPerElement.NetVolume = volume;
                    materialQuantityPerElement.NetArea = area;
                }
                quantities.Add(materialId, materialQuantityPerElement);
            }
        }

        /// <summary>
        /// Calculate and store material quantities for a given element.
        /// </summary>
        /// <param name="e">The element.</param>
        private void CalculateMaterialQuantitiesOfElement(Element e)
        {
            ElementId elementId = e.Id;
            ICollection<ElementId> materials = e.GetMaterialIds(false);

            foreach (ElementId materialId in materials)
            {
                double volume = e.GetMaterialVolume(materialId);
                double area = e.GetMaterialArea(materialId, false);

                if (volume > 0.0 || area > 0.0)
                {
                    StoreMaterialQuantities(materialId, volume, area, m_totalQuantities);

                    Dictionary<ElementId, MaterialQuantities> quantityPerElement;
                    bool found = m_quantitiesPerElement.TryGetValue(elementId, out quantityPerElement);
                    if (found)
                    {
                        StoreMaterialQuantities(materialId, volume, area, quantityPerElement);
                    }
                    else
                    {
                        quantityPerElement = new Dictionary<ElementId, MaterialQuantities>();
                        StoreMaterialQuantities(materialId, volume, area, quantityPerElement);
                        m_quantitiesPerElement.Add(elementId, quantityPerElement);
                    }
                }
            }
        }

        /// <summary>
        /// Write results in CSV format to the indicated output writer.
        /// </summary>
        /// <param name="writer">The output text writer.</param>
        public void ReportResults(TextWriter writer)
        {
            if (m_totalQuantities.Count == 0)
                return;

            String legendLine = "Gross volume(cubic ft),Net volume(cubic ft),Gross area(sq ft),Net area(sq ft)";

            writer.WriteLine();
            writer.WriteLine(String.Format("Totals for {0} elements,{1}", GetElementTypeName(), legendLine));

            // If unexpected deletion failures occurred, log the warnings to the output.
            if (m_warningsForGrossQuantityCalculations.Count > 0)
            {
                writer.WriteLine("WARNING: Calculations for gross volume and area may not be completely accurate due to the following warnings: ");
                foreach (String s in m_warningsForGrossQuantityCalculations)
                    writer.WriteLine(s);
                writer.WriteLine();
            }
            ReportResultsFor(m_totalQuantities, writer);

            foreach (ElementId keyId in m_quantitiesPerElement.Keys)
            {
                ElementId id = keyId;
                Element e = m_doc.GetElement(id);

                writer.WriteLine();
                writer.WriteLine(String.Format("Totals for {0} element {1} (id {2}),{3}",
                    GetElementTypeName(),
                    e.Name.Replace(',', ':'), // Element names may have ',' in them
                    id.IntegerValue, legendLine));

                Dictionary<ElementId, MaterialQuantities> quantities = m_quantitiesPerElement[id];

                ReportResultsFor(quantities, writer);
            }
        }

        /// <summary>
        /// Write the contents of one storage collection to the indicated output writer.
        /// </summary>
        /// <param name="quantities">The storage collection for material quantities.</param>
        /// <param name="writer">The output writer.</param>
        private void ReportResultsFor(Dictionary<ElementId, MaterialQuantities> quantities, TextWriter writer)
        {
            foreach (ElementId keyMaterialId in quantities.Keys)
            {
                ElementId materialId = keyMaterialId;
                MaterialQuantities quantity = quantities[materialId];

                Material material = m_doc.GetElement(materialId) as Material;

                //writer.WriteLine(String.Format("   {0} Net: [{1:F2} cubic ft {2:F2} sq. ft]  Gross: [{3:F2} cubic ft {4:F2} sq. ft]", material.Name, quantity.NetVolume, quantity.NetArea, quantity.GrossVolume, quantity.GrossArea));
                writer.WriteLine(String.Format("{0},{3:F2},{1:F2},{4:F2},{2:F2}",
                    material.Name.Replace(',', ':'),  // Element names may have ',' in them
                    quantity.NetVolume, quantity.NetArea, quantity.GrossVolume, quantity.GrossArea));
            }
        }

        #region Results Storage
        /// <summary>
        /// A storage of material quantities per individual element.
        /// </summary>
        private Dictionary<ElementId, Dictionary<ElementId, MaterialQuantities>> m_quantitiesPerElement = new Dictionary<ElementId, Dictionary<ElementId, MaterialQuantities>>();

        /// <summary>
        /// A storage of material quantities for the entire project.
        /// </summary>
        private Dictionary<ElementId, MaterialQuantities> m_totalQuantities = new Dictionary<ElementId, MaterialQuantities>();

        /// <summary>
        /// Flag indicating the mode of the calculation.
        /// </summary>
        private bool m_calculatingGrossQuantities = false;

        /// <summary>
        /// A collection of warnings generated due to failure to delete elements in advance of gross quantity calculations.
        /// </summary>
        private List<String> m_warningsForGrossQuantityCalculations = new List<string>();
        #endregion

        protected Document m_doc;
    }

    /// <summary>
    /// A storage class for the extracted material quantities.
    /// </summary>
    class MaterialQuantities
    {
        /// <summary>
        /// Gross volume (cubic ft)
        /// </summary>
        public double GrossVolume { get; set; }

        /// <summary>
        /// Gross area (sq. ft)
        /// </summary>
        public double GrossArea { get; set; }

        /// <summary>
        /// Net volume (cubic ft)
        /// </summary>
        public double NetVolume { get; set; }

        /// <summary>
        /// Net area (sq. ft)
        /// </summary>
        public double NetArea { get; set; }
    }
}