diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs
index 39dd8da75f..7e4ee707d1 100644
--- a/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs
+++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/ConversionUtils.cs
@@ -271,9 +271,17 @@ public void GetAllRevitParamsAndIds(Base speckleElement, DB.Element revitElement
speckleElement["builtInCategory"] = builtInCategory.ToString();
//NOTE: adds the quantities of all materials to an element
- var qs = MaterialQuantitiesToSpeckle(revitElement, speckleElement["units"] as string);
- if (qs != null)
- speckleElement["materialQuantities"] = qs;
+ try
+ {
+ speckleElement["materialQuantities"] = MaterialQuantitiesToSpeckle(
+ revitElement,
+ speckleElement["units"] as string)?
+ .ToList();
+ }
+ catch (Autodesk.Revit.Exceptions.ApplicationException ex)
+ {
+ SpeckleLog.Logger.Error(ex, "An exception occurred in the Revit API while retrieving material quantities from element of type {elementType} and category {elementCategory}", revitElement.GetType(), revitElement.Category);
+ }
}
private void AddElementParamsToDict(
@@ -1077,7 +1085,7 @@ public static RenderMaterial GetMEPSystemMaterial(Element e)
///
/// Revit element to parse
/// Revit material of the element, null if no material found
- public static DB.Material GetMEPSystemRevitMaterial(Element e)
+ public static DB.Material? GetMEPSystemRevitMaterial(Element e)
{
ElementId idType = ElementId.InvalidElementId;
@@ -1089,21 +1097,16 @@ public static DB.Material GetMEPSystemRevitMaterial(Element e)
idType = system.GetTypeId();
}
}
- else if (IsSupportedMEPCategory(e))
+ else if (e.GetConnectorManager() is ConnectorManager connectorManager)
{
- MEPModel m = ((DB.FamilyInstance)e).MEPModel;
-
- if (m != null && m.ConnectorManager != null)
+ //retrieve the first material from first connector. Could go wrong, but better than nothing ;-)
+ foreach (Connector item in connectorManager.Connectors)
{
- //retrieve the first material from first connector. Could go wrong, but better than nothing ;-)
- foreach (Connector item in m.ConnectorManager.Connectors)
+ var system = item.MEPSystem;
+ if (system != null)
{
- var system = item.MEPSystem;
- if (system != null)
- {
- idType = system.GetTypeId();
- break;
- }
+ idType = system.GetTypeId();
+ break;
}
}
}
@@ -1119,22 +1122,6 @@ public static DB.Material GetMEPSystemRevitMaterial(Element e)
return null;
}
- private static bool IsSupportedMEPCategory(Element e)
- {
- var categories = e.Document.Settings.Categories;
-
- var supportedCategories = new[]
- {
- BuiltInCategory.OST_PipeFitting,
- BuiltInCategory.OST_DuctFitting,
- BuiltInCategory.OST_DuctAccessory,
- BuiltInCategory.OST_PipeAccessory,
- //BuiltInCategory.OST_MechanicalEquipment,
- };
-
- return supportedCategories.Any(cat => e.Category.Id == categories.get_Item(cat).Id);
- }
-
#endregion
diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ElementExtensions.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ElementExtensions.cs
index 45211b2db3..a16fe4297b 100644
--- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ElementExtensions.cs
+++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Extensions/ElementExtensions.cs
@@ -1,9 +1,7 @@
+#nullable enable
using System.Collections.Generic;
using System.Linq;
using Autodesk.Revit.DB;
-using Autodesk.Revit.DB.Electrical;
-using Autodesk.Revit.DB.Mechanical;
-using Autodesk.Revit.DB.Plumbing;
namespace ConverterRevitShared.Extensions
{
@@ -12,18 +10,22 @@ public static class ElementExtensions
public static IEnumerable GetConnectorSet(this Element element)
{
var empty = Enumerable.Empty();
+ return element.GetConnectorManager()?.Connectors?.Cast() ?? empty;
+ }
+
+ public static ConnectorManager? GetConnectorManager(this Element element)
+ {
return element switch
{
- CableTray o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- Conduit o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- Duct o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- FamilyInstance o => o.MEPModel?.ConnectorManager?.Connectors?.Cast() ?? empty,
- FlexDuct o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- FlexPipe o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- Pipe o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- Wire o => o.ConnectorManager?.Connectors?.Cast() ?? empty,
- _ => Enumerable.Empty(),
+ MEPCurve o => o.ConnectorManager,
+ FamilyInstance o => o.MEPModel?.ConnectorManager,
+ _ => null,
};
}
+
+ public static bool IsMEPElement(this Element element)
+ {
+ return element.GetConnectorManager() != null;
+ }
}
}
diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMaterialQuantities.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMaterialQuantities.cs
index 43bed2c31b..c2df744013 100644
--- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMaterialQuantities.cs
+++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMaterialQuantities.cs
@@ -1,8 +1,7 @@
+#nullable enable
using Autodesk.Revit.DB;
-using Autodesk.Revit.DB.Structure;
-using Objects.BuiltElements;
-using Objects.BuiltElements.Revit;
-using Speckle.Core.Models;
+using ConverterRevitShared.Extensions;
+using Objects.Other;
using System.Collections.Generic;
using System.Linq;
using DB = Autodesk.Revit.DB;
@@ -11,69 +10,153 @@ namespace Objects.Converter.Revit
{
public partial class ConverterRevit
{
- #region MaterialQuantity
///
- /// Gets the quantitiy of a material in one element
+ /// Material Quantities in Revit are stored in different ways and therefore need to be retrieved
+ /// using different methods. According to this forum post https://forums.autodesk.com/t5/revit-api-forum/method-getmaterialarea-appears-to-use-different-formulas-for/td-p/11988215
+ /// "Hosts" (whatever that means) will return the area of a single side of the object while other
+ /// objects will return the combined area of every side of the element. Certain MEP element materials
+ /// are attached to the MEP system that the element belongs to.
///
///
- ///
+ ///
///
- public Objects.Other.MaterialQuantity MaterialQuantityToSpeckle(DB.Element element, DB.Material material, string units)
+ public IEnumerable MaterialQuantitiesToSpeckle(DB.Element element, string units)
{
- if (material == null || element == null)
+ if (MaterialAreaAPICallWillReportSingleFace(element))
+ {
+ return GetMaterialQuantitiesFromAPICall(element, units);
+ }
+ else if (MaterialIsAttachedToMEPSystem(element))
+ {
+ MaterialQuantity quantity = GetMaterialQuantityForMEPElement(element, units);
+ return quantity == null ? Enumerable.Empty() : new List() { quantity };
+ }
+ else
+ {
+ return GetMaterialQuantitiesFromSolids(element, units);
+ }
+ }
+
+ private IEnumerable GetMaterialQuantitiesFromAPICall(DB.Element element, string units)
+ {
+ foreach (ElementId matId in element.GetMaterialIds(false))
+ {
+ double volume = element.GetMaterialVolume(matId);
+ double area = element.GetMaterialArea(matId, false);
+ yield return CreateMaterialQuantity(element, matId, area, volume, units);
+ }
+ }
+
+ private MaterialQuantity? GetMaterialQuantityForMEPElement(DB.Element element, string units)
+ {
+ DB.Material material = GetMEPSystemRevitMaterial(element);
+ if (material == null)
+ {
return null;
+ }
- // To-Do: These methods from the Revit API appear to have bugs.
- double volume = element.GetMaterialVolume(material.Id);
- double area = element.GetMaterialArea(material.Id, false);
+ DB.Options options = new() { DetailLevel = ViewDetailLevel.Fine };
+ var (solids, _) = GetSolidsAndMeshesFromElement(element, options);
- // Convert revit interal units to speckle commit units
+ (double area, double volume) = GetAreaAndVolumeFromSolids(solids);
+ return CreateMaterialQuantity(element, material.Id, area, volume, units);
+ }
+
+ private IEnumerable GetMaterialQuantitiesFromSolids(DB.Element element, string units)
+ {
+ DB.Options options = new() { DetailLevel = ViewDetailLevel.Fine };
+ var (solids, _) = GetSolidsAndMeshesFromElement(element, options);
+
+ foreach (ElementId matId in GetMaterialsFromSolids(solids))
+ {
+ (double area, double volume) = GetAreaAndVolumeFromSolids(solids, matId);
+ yield return CreateMaterialQuantity(element, matId, area, volume, units);
+ }
+ }
+
+ private MaterialQuantity CreateMaterialQuantity(
+ Element element,
+ ElementId materialId,
+ double areaRevitInternalUnits,
+ double volumeRevitInternalUnits,
+ string units)
+ {
+ Other.Material speckleMaterial = ConvertAndCacheMaterial(materialId, element.Document);
double factor = ScaleToSpeckle(1);
- volume *= factor * factor * factor;
- area *= factor * factor;
+ double area = factor * factor * areaRevitInternalUnits;
+ double volume = factor * factor * factor * volumeRevitInternalUnits;
+ MaterialQuantity materialQuantity = new(speckleMaterial, volume, area, units);
+
+ switch (element)
+ {
+ case DB.Architecture.Railing railing:
+ materialQuantity["length"] = railing.GetPath().Sum(e => e.Length) * factor;
+ break;
+
+ case DB.Architecture.ContinuousRail continuousRail:
+ materialQuantity["length"] = continuousRail.GetPath().Sum(e => e.Length) * factor;
+ break;
- var speckleMaterial = ConvertAndCacheMaterial(material.Id, material.Document);
- var materialQuantity = new Objects.Other.MaterialQuantity(speckleMaterial, volume, area, units);
+ default:
+ if (LocationToSpeckle(element) is ICurve curve)
+ {
+ materialQuantity["length"] = curve.length;
+ }
+ break;
+ };
- if (LocationToSpeckle(element) is ICurve curve)
- materialQuantity["length"] = curve.length;
return materialQuantity;
}
- #endregion
-
- #region MaterialQuantities
- public IEnumerable MaterialQuantitiesToSpeckle(DB.Element element, string units)
+ private (double, double) GetAreaAndVolumeFromSolids(List solids, ElementId? materialId = null)
{
-
- var matIDs = element?.GetMaterialIds(false);
- // Does not return the correct materials for some categories
- // Need to take different approach for MEP-Elements
- if (matIDs == null || !matIDs.Any() && element is MEPCurve)
+ if (materialId != null)
{
- DB.Material mepMaterial = ConverterRevit.GetMEPSystemRevitMaterial(element);
- if (mepMaterial != null) matIDs.Add(mepMaterial.Id);
+ solids = solids
+ .Where(
+ solid => solid.Volume > 0
+ && !solid.Faces.IsEmpty
+ && solid.Faces.get_Item(0).MaterialElementId == materialId)
+ .ToList();
}
- if (matIDs == null || !matIDs.Any())
- return null;
+ double volume = solids.Sum(solid => solid.Volume);
+ IEnumerable areaOfLargestFaceInEachSolid = solids
+ .Select(solid => solid.Faces.Cast().Select(face => face.Area)
+ .Max());
+ double area = areaOfLargestFaceInEachSolid.Sum();
+ return (area, volume);
+ }
- var materials = matIDs.Select(material => element.Document.GetElement(material) as DB.Material);
- return MaterialQuantitiesToSpeckle(element, materials, units);
+ private IEnumerable GetMaterialsFromSolids(List solids)
+ {
+ return solids
+ .Where(solid => solid.Volume > 0 && !solid.Faces.IsEmpty)
+ .Select(m => m.Faces.get_Item(0).MaterialElementId)
+ .Distinct();
}
- public IEnumerable MaterialQuantitiesToSpeckle(DB.Element element, IEnumerable materials, string units)
+
+ private bool MaterialAreaAPICallWillReportSingleFace(Element element)
{
- if (materials == null || !materials.Any()) return null;
- List quantities = new List();
-
- foreach (var material in materials)
- quantities.Add(MaterialQuantityToSpeckle(element, material, units));
-
- return quantities;
+ return element switch
+ {
+ DB.CeilingAndFloor
+ or DB.Wall
+ or DB.RoofBase => true,
+ _ => false
+ };
}
-
- #endregion
+ private bool MaterialIsAttachedToMEPSystem(Element element)
+ {
+ return element switch
+ {
+ DB.Mechanical.Duct
+ or DB.Mechanical.FlexDuct
+ or DB.Plumbing.Pipe
+ or DB.Plumbing.FlexPipe => true,
+ _ => false
+ };
+ }
}
-
}
diff --git a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMeshUtils.cs b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMeshUtils.cs
index 7e828d50d8..7e1c6a40a3 100644
--- a/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMeshUtils.cs
+++ b/Objects/Converters/ConverterRevit/ConverterRevitShared/Partial Classes/ConvertMeshUtils.cs
@@ -49,9 +49,24 @@ public List GetElementDisplayValue(
return displayMeshes;
}
+ var (solids, meshes) = GetSolidsAndMeshesFromElement(element, options, transform);
+
+ // convert meshes and solids
+ displayMeshes.AddRange(ConvertMeshesByRenderMaterial(meshes, element.Document, isConvertedAsInstance));
+ displayMeshes.AddRange(ConvertSolidsByRenderMaterial(solids, element.Document, isConvertedAsInstance));
+
+ return displayMeshes;
+ }
+
+ public (List, List) GetSolidsAndMeshesFromElement(
+ Element element,
+ Options options,
+ Transform? transform = null
+ )
+ {
options = ViewSpecificOptions ?? options ?? new Options() { DetailLevel = DetailLevelSetting };
- GeometryElement geom = null;
+ GeometryElement geom;
try
{
geom = element.get_Geometry(options);
@@ -62,20 +77,16 @@ public List GetElementDisplayValue(
geom = element.get_Geometry(options);
}
- if (geom == null)
- return displayMeshes;
-
- // retrieves all meshes and solids from a geometry element
var solids = new List();
var meshes = new List();
- SortGeometry(element, solids, meshes, geom, transform?.Inverse);
-
- // convert meshes and solids
- displayMeshes.AddRange(ConvertMeshesByRenderMaterial(meshes, element.Document, isConvertedAsInstance));
- displayMeshes.AddRange(ConvertSolidsByRenderMaterial(solids, element.Document, isConvertedAsInstance));
+ if (geom != null)
+ {
+ // retrieves all meshes and solids from a geometry element
+ SortGeometry(element, solids, meshes, geom, transform?.Inverse);
+ }
- return displayMeshes;
+ return (solids, meshes);
}
private static void LogInstanceMeshRetrievalWarnings(