// // (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 { /// /// Outputs an analysis of the materials that make up walls, floors, and roofs, and displays the output in Excel. /// [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, IDisposable { static AddInId appId = new AddInId(new Guid("7E5CAC0D-F3D8-4040-89D6-0828D681561B")); /// /// The top level command. /// /// An object that is passed to the external application /// which contains data related to the command, /// such as the application object and active view. /// 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. /// A set of elements to which the external application /// can add elements that are to be highlighted in case of failure or cancellation. /// 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. 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(); ExecuteCalculationsWith(); ExecuteCalculationsWith(); m_writer.Close(); // This operation doesn't change the model, so return cancelled to cancel the transaction return Result.Cancelled; } /// /// Executes a calculator for one type of Revit element. /// /// private void ExecuteCalculationsWith() 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 protected virtual void Dispose(bool disposing) { if (disposing) { if (m_writer != null) m_writer.Dispose(); } } public void Dispose() { Dispose(disposing: true); GC.SuppressFinalize(this); } } /// /// The wall material quantity calculator specialized class. /// 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"; } } /// /// The floor material quantity calculator specialized class. /// 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"; } } /// /// The roof material quantity calculator specialized class. /// 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"; } } /// /// The base material quantity calculator for all element types. /// abstract class MaterialQuantityCalculator { /// /// The list of elements for material quantity extraction. /// protected IList m_elementsToProcess; /// /// Override this to populate the list of elements for material quantity extraction. /// protected abstract void CollectElements(); /// /// Override this to return the name of the element type calculated by this calculator. /// protected abstract String GetElementTypeName(); /// /// Sets the document for the calculator class. /// public void SetDocument(Document d) { m_doc = d; Autodesk.Revit.ApplicationServices.Application app = d.Application; } /// /// Executes the calculation. /// public void CalculateMaterialQuantities() { CollectElements(); CalculateNetMaterialQuantities(); CalculateGrossMaterialQuantities(); } /// /// Calculates net material quantities for the target elements. /// private void CalculateNetMaterialQuantities() { foreach (Element e in m_elementsToProcess) { CalculateMaterialQuantitiesOfElement(e); } } /// /// Calculates gross material quantities for the target elements (material quantities with /// all openings, doors and windows removed). /// 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(); } /// /// Delete all elements that cut out of target elements, to allow for calculation of gross material quantities. /// private void DeleteAllCuttingElements() { IList filterList = new List(); 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 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.BuiltInCategory == BuiltInCategory.OST_Doors) { FamilyInstance door = e as FamilyInstance; Wall host = door.Host as Wall; if (host.CurtainGrid != null) continue; } ICollection 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)); } } } } /// /// Store calculated material quantities in the storage collection. /// /// The material id. /// The extracted volume. /// The extracted area. /// The storage collection. private void StoreMaterialQuantities(ElementId materialId, double volume, double area, Dictionary 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); } } /// /// Calculate and store material quantities for a given element. /// /// The element. private void CalculateMaterialQuantitiesOfElement(Element e) { ElementId elementId = e.Id; ICollection 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 quantityPerElement; bool found = m_quantitiesPerElement.TryGetValue(elementId, out quantityPerElement); if (found) { StoreMaterialQuantities(materialId, volume, area, quantityPerElement); } else { quantityPerElement = new Dictionary(); StoreMaterialQuantities(materialId, volume, area, quantityPerElement); m_quantitiesPerElement.Add(elementId, quantityPerElement); } } } } /// /// Write results in CSV format to the indicated output writer. /// /// The output text writer. 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.ToString(), legendLine)); Dictionary quantities = m_quantitiesPerElement[id]; ReportResultsFor(quantities, writer); } } /// /// Write the contents of one storage collection to the indicated output writer. /// /// The storage collection for material quantities. /// The output writer. private void ReportResultsFor(Dictionary 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 /// /// A storage of material quantities per individual element. /// private Dictionary> m_quantitiesPerElement = new Dictionary>(); /// /// A storage of material quantities for the entire project. /// private Dictionary m_totalQuantities = new Dictionary(); /// /// Flag indicating the mode of the calculation. /// private bool m_calculatingGrossQuantities = false; /// /// A collection of warnings generated due to failure to delete elements in advance of gross quantity calculations. /// private List m_warningsForGrossQuantityCalculations = new List(); #endregion protected Document m_doc; } /// /// A storage class for the extracted material quantities. /// class MaterialQuantities { /// /// Gross volume (cubic ft) /// public double GrossVolume { get; set; } /// /// Gross area (sq. ft) /// public double GrossArea { get; set; } /// /// Net volume (cubic ft) /// public double NetVolume { get; set; } /// /// Net area (sq. ft) /// public double NetArea { get; set; } } }