diff --git a/Revit_Adapter/Revit_Adapter.csproj b/Revit_Adapter/Revit_Adapter.csproj
index 48d9b6839..622224a45 100644
--- a/Revit_Adapter/Revit_Adapter.csproj
+++ b/Revit_Adapter/Revit_Adapter.csproj
@@ -8,7 +8,7 @@
BHoM
Copyright © https://github.com/BHoM
BH.Adapter.Revit
- 9.1.0.0
+ 9.2.0.0
diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs
index 22e8eba2b..a71b7d660 100644
--- a/Revit_Core_Adapter/AdapterActions/Push.cs
+++ b/Revit_Core_Adapter/AdapterActions/Push.cs
@@ -27,6 +27,7 @@
using BH.oM.Adapters.Revit.Elements;
using BH.oM.Base;
using BH.Revit.Engine.Core;
+using System;
using System.Collections.Generic;
using System.Linq;
@@ -60,7 +61,7 @@ public override List
diff --git a/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs b/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs
new file mode 100644
index 000000000..8c708ce62
--- /dev/null
+++ b/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs
@@ -0,0 +1,269 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+using BH.Engine.Adapters.Revit;
+using BH.Engine.Geometry;
+using BH.oM.Adapters.Revit.Settings;
+using BH.oM.Base.Attributes;
+using BH.oM.Geometry;
+using BH.oM.Physical.Elements;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text.RegularExpressions;
+using Extrusion = Autodesk.Revit.DB.Extrusion;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Compute
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Generates a Revit FamilySymbol for a BHoM PadFoundation by querying existing families in the document or creating a new one from a template file with parametric dimensions.")]
+ [Input("padFoundation", "BHoM pad foundation to generate the Revit profile for.")]
+ [Input("document", "Revit document, in which the family type will be created.")]
+ [Input("settings", "Settings to be used when generating the family type.")]
+ [Output("symbol", "Created Revit family type that represents the outline of the input BHoM pad foundation.")]
+ public static FamilySymbol GeneratePadFoundationType(this PadFoundation padFoundation, Document document, RevitSettings settings = null)
+ {
+ settings = settings.DefaultIfNull();
+
+ bool isRectangle;
+ Polyline outline = padFoundation?.PadFoundationOutline();
+ if (outline != null)
+ isRectangle = outline.IsRectangular(settings.DistanceTolerance);
+ else
+ return null;
+
+ if (isRectangle)
+ return GenerateRectangularType(padFoundation, document, settings);
+ else
+ return GenerateFreeformType(padFoundation, document, settings);
+ }
+
+ /***************************************************/
+ /**** Private methods ****/
+ /***************************************************/
+
+ private static FamilySymbol GenerateRectangularType(PadFoundation padFoundation, Document document, RevitSettings settings)
+ {
+ // Check if family loaded to the document, if not, load it from resources path
+ string familyName = "StructuralFoundations_Pad-Rectangular";
+
+ Family family = new FilteredElementCollector(document).OfClass(typeof(Family)).FirstOrDefault(x => x.Name.EndsWith(familyName)) as Family;
+ if (family == null)
+ {
+ string path = Directory.GetFiles(m_FamilyDirectory, $"*{familyName}.rfa").FirstOrDefault();
+ if (!File.Exists(path))
+ return null;
+
+ if (!document.LoadFamily(path, out family) || family == null)
+ return null;
+ }
+
+ // Get dimensions of the pad foundation
+ (double, double, double) dimensions = padFoundation.RectangularDimensions();
+ if (double.IsNaN(dimensions.Item1) || double.IsNaN(dimensions.Item2) || double.IsNaN(dimensions.Item3))
+ return null;
+ return family.FindOrCreateTypeWithDimensions(dimensions.Item1, dimensions.Item2, dimensions.Item3);
+ }
+
+ /***************************************************/
+ private static FamilySymbol FindOrCreateTypeWithDimensions(this Family family, double width, double length, double thickness)
+ {
+ List symbols = family.GetFamilySymbolIds().Select(id => family.Document.GetElement(id) as FamilySymbol).Where(s => s != null).ToList();
+
+ long widthMm = (long)Math.Round(width * 1000.0);
+ long lengthMm = (long)Math.Round(length * 1000.0);
+ long depthMm = (long)Math.Round(thickness * 1000.0);
+ string typeName = $"{widthMm}x{lengthMm}x{depthMm}mm";
+
+ FamilySymbol result = symbols.FirstOrDefault(x => x?.Name == typeName);
+ if (result == null && symbols.Count != 0)
+ {
+ result = symbols.FirstOrDefault(x => !(new FilteredElementCollector(family.Document).WherePasses(new FamilyInstanceFilter(family.Document, x.Id)).Any()));
+ if (result != null)
+ result.Name = typeName;
+ else
+ result = symbols[0].Duplicate(typeName) as FamilySymbol;
+ result.SetParameter("Width", width);
+ result.SetParameter("Length", length);
+ result.SetParameter("Foundation Thickness", thickness);
+ }
+ result?.Activate();
+ return result;
+ }
+
+ /***************************************************/
+ private static (double, double, double) RectangularDimensions(this PadFoundation padFoundation)
+ {
+ Polyline outline = padFoundation.PadFoundationOutline();
+ double len1 = outline.ControlPoints[0].Distance(outline.ControlPoints[1]);
+ double len2 = outline.ControlPoints[1].Distance(outline.ControlPoints[2]);
+ double bhomLength = Math.Max(len1, len2);
+ double bhomWidth = Math.Min(len1, len2);
+
+ return (bhomWidth, bhomLength, padFoundation.PadFoundationThickness());
+ }
+
+ /***************************************************/
+
+ private static FamilySymbol GenerateFreeformType(PadFoundation padFoundation, Document document, RevitSettings settings)
+ {
+ string prefix = "StructuralFoundations_Pad-Freeform_";
+
+ // Get the outline and check if valid
+ Polyline outline = padFoundation.PadFoundationOutline();
+ if (outline?.IIsClosed() != true)
+ {
+ BH.Engine.Base.Compute.RecordError($"Pad foundation outline is invalid. BHoM_Guid: {padFoundation.BHoM_Guid}");
+ return null;
+ }
+
+ // Get the thickness and check if valid
+ double thickness = padFoundation.PadFoundationThickness();
+ if (double.IsNaN(thickness))
+ return null;
+
+ // Orient the outline to origin
+ Polyline orientedOutline = outline.OrientToOrigin();
+ if (orientedOutline == null)
+ return null;
+
+ // Get all BHoM-generated freeform families in the document
+ List freeformFamilies = new FilteredElementCollector(document).OfClass(typeof(Family)).Cast()
+ .Where(x => Regex.IsMatch(x.Name, $"{prefix}\\d+$")).ToList();
+
+ // Try to find a family with matching outline, otherwise create a new one from template
+ Family family = freeformFamilies.FirstOrDefault(x => x.IsMatchingOutline(orientedOutline, settings));
+ if (family == null)
+ {
+ List takenIndices = freeformFamilies.Select(x => Regex.Match(x.Name, $"{Regex.Escape(prefix)}(\\d+)$")).Select(x => int.Parse(x.Groups[1].Value)).ToList();
+ int newIndex = takenIndices.Count > 0 ? takenIndices.Max() + 1 : 1;
+ family = GenerateFreeFormPadFamilyFromTemplate(document, orientedOutline, thickness, newIndex, padFoundation, settings);
+ }
+
+ if (family == null)
+ return null;
+
+ // Find or create the type with matching thickness
+ return family.FindOrCreateTypeWithThickness(thickness);
+ }
+
+ /***************************************************/
+
+ private static Family GenerateFreeFormPadFamilyFromTemplate(this Document document, Polyline orientedOutline, double thickness, int index, PadFoundation padFoundation, RevitSettings settings = null)
+ {
+ string templateFamilyName = "StructuralFoundations_Pad-Freeform";
+ string templatePath = Directory.GetFiles(m_FamilyDirectory, $"*{templateFamilyName}.rfa").FirstOrDefault();
+
+ Document familyDocument = new UIDocument(document).Application.Application.OpenDocumentFile(templatePath);
+ if (familyDocument == null)
+ return null;
+
+ try
+ {
+ if (!ReplaceFreeFormExtrusion(familyDocument, orientedOutline, padFoundation))
+ return null;
+
+ return SaveAndLoadFamily(document, familyDocument, $"{Path.GetFileNameWithoutExtension(templatePath)}_{index}");
+ }
+ catch (Exception ex)
+ {
+ BH.Engine.Base.Compute.RecordError($"Creation of a freeform Revit pad foundation failed with the following error: {ex.Message}");
+ return null;
+ }
+ finally
+ {
+ familyDocument.Close(false);
+ }
+ }
+
+ /***************************************************/
+ private static double FreeformExtrusionDepth(PadFoundation padFoundation)
+ {
+ double depth = padFoundation.PadFoundationThickness();
+ double h = double.IsNaN(depth) ? double.NaN : depth.FromSI(SpecTypeId.Length);
+ if (double.IsNaN(h) || h <= 1e-6)
+ h = 0.5.FromSI(SpecTypeId.Length);
+ return h;
+ }
+
+ /***************************************************/
+ private static bool ReplaceFreeFormExtrusion(Document familyDocument, Polyline orientedOutline, PadFoundation padFoundation)
+ {
+ try
+ {
+ Extrusion extrusion = new FilteredElementCollector(familyDocument).OfClass(typeof(Extrusion)).FirstOrDefault() as Extrusion;
+ CurveArrArray profile = new CurveArrArray();
+ profile.Append(orientedOutline.ToRevitCurveArray());
+
+ using (Transaction t = new Transaction(familyDocument, "Update Freeform Pad Foundation Footprint"))
+ {
+ t.Start();
+ familyDocument.FamilyCreate.NewExtrusion(true, profile, extrusion.Sketch.SketchPlane, -FreeformExtrusionDepth(padFoundation));
+ familyDocument.Delete(extrusion.Id);
+ t.Commit();
+ }
+ }
+ catch
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ /***************************************************/
+
+ private static FamilySymbol FindOrCreateTypeWithThickness(this Family family, double thickness)
+ {
+ List symbols = family.GetFamilySymbolIds().Select(id => family.Document.GetElement(id) as FamilySymbol).Where(s => s != null).ToList();
+ if (symbols.Count == 0)
+ return null;
+
+ string typeName = $"{(long)Math.Round(thickness * 1000.0)}mm";
+ FamilySymbol result = symbols.FirstOrDefault(x => x?.Name == typeName);
+ if (result == null && symbols.Count != 0)
+ {
+ result = symbols.FirstOrDefault(x => !(new FilteredElementCollector(family.Document).WherePasses(new FamilyInstanceFilter(family.Document, x.Id)).Any()));
+ if (result != null)
+ result.Name = typeName;
+ else
+ result = symbols[0].Duplicate(typeName) as FamilySymbol;
+
+ result.SetParameter("Foundation Thickness", thickness);
+ }
+
+ result?.Activate();
+ return result;
+
+ /***************************************************/
+ }
+ }
+}
diff --git a/Revit_Core_Engine/Compute/GenerateProfile.cs b/Revit_Core_Engine/Compute/GenerateProfile.cs
index d9948f03d..19fafa165 100644
--- a/Revit_Core_Engine/Compute/GenerateProfile.cs
+++ b/Revit_Core_Engine/Compute/GenerateProfile.cs
@@ -60,7 +60,8 @@ public static FamilySymbol GenerateProfile(this IFramingElement element, Documen
return null;
}
- string familyName = element.ProfileFamilyName();
+ bool fromTemplate = element.HasTemplateFamily();
+ string familyName = element.ProfileFamilyName(!fromTemplate);
if (string.IsNullOrWhiteSpace(familyName))
{
BH.Engine.Base.Compute.RecordError($"Creation of a Revit profile family failed because neither the BHoM section property nor its profile has a name. BHoM_Guid: {property.BHoM_Guid}");
@@ -69,46 +70,47 @@ public static FamilySymbol GenerateProfile(this IFramingElement element, Documen
settings = settings.DefaultIfNull();
+ FamilySymbol result;
Family family = new FilteredElementCollector(document).OfClass(typeof(Family)).FirstOrDefault(x => x.Name == familyName) as Family;
if (family != null)
{
List symbols = family.GetFamilySymbolIds().Select(x => document.GetElement(x) as FamilySymbol).Where(x => x != null).ToList();
- FamilySymbol result = symbols.FirstOrDefault(x => x?.Name == element.Property.Name);
- if (result == null && symbols.Count != 0)
+ result = symbols.FirstOrDefault(x => x?.Name == element.Property.Name);
+ if (result != null)
+ return result;
+
+ if (fromTemplate && symbols.Count != 0)
{
result = symbols[0].Duplicate(element.Property.Name) as FamilySymbol;
result.Activate();
- property.Profile.ICopyDimensions(result, settings);
+ element.ICopyProfileDimensions(result, settings);
+ return result;
}
-
- return result;
}
- else
- {
+
+ if (fromTemplate)
family = document.GenerateFamilyFromTemplate(element, familyName, settings);
- if (family == null)
- {
- family = document.GenerateFreeformFamily(element, familyName, settings);
- if (family == null)
- return null;
+ else
+ family = document.GenerateFreeformFamily(element, familyName, settings);
- if (!(property.Profile is FreeFormProfile))
- BH.Engine.Base.Compute.RecordWarning($"Generation of profiles with shape {property.Profile.GetType().Name} is currently not fully supported - a freeform, dimensionless profile with a dedicated family has been created.");
- }
+ if (family == null)
+ return null;
- family.SetMaterialForModelBehaviour(property?.Material);
+ if (!fromTemplate && !(property.Profile is FreeFormProfile))
+ BH.Engine.Base.Compute.RecordWarning($"Generation of profiles with shape {property.Profile.GetType().Name} is currently not fully supported - a freeform, dimensionless profile with a dedicated family has been created.");
- FamilySymbol result = document.GetElement(family.GetFamilySymbolIds().FirstOrDefault()) as FamilySymbol;
- if (result == null)
- {
- BH.Engine.Base.Compute.RecordWarning($"Generation of a Revit family representing the section property of a BHoM framing element failed due to an internal error. BHoM_Guid: {element.BHoM_Guid}");
- return null;
- }
+ family.SetMaterialForModelBehaviour(property?.Material);
- result.Activate();
- result.Name = element.ProfileTypeName();
- return result;
+ result = document.GetElement(family.GetFamilySymbolIds().FirstOrDefault()) as FamilySymbol;
+ if (result == null)
+ {
+ BH.Engine.Base.Compute.RecordWarning($"Generation of a Revit family representing the section property of a BHoM framing element failed due to an internal error. BHoM_Guid: {element.BHoM_Guid}");
+ return null;
}
+
+ result.Activate();
+ result.Name = element.ProfileTypeName();
+ return result;
}
@@ -116,7 +118,7 @@ public static FamilySymbol GenerateProfile(this IFramingElement element, Documen
/**** Private methods ****/
/***************************************************/
- private static Family SaveAndLoadFamily(Document document, Document familyDocument, string familyName, IFramingElement element, RevitSettings settings)
+ private static Family SaveAndLoadFamily(Document document, Document templateDocument, string familyName)
{
Family result = null;
string tempFolder = Path.GetTempPath();
@@ -129,7 +131,7 @@ private static Family SaveAndLoadFamily(Document document, Document familyDocume
SaveAsOptions saveOptions = new SaveAsOptions();
saveOptions.OverwriteExistingFile = true;
- familyDocument.SaveAs(tempLocation, saveOptions);
+ templateDocument.SaveAs(tempLocation, saveOptions);
}
catch (Exception ex)
{
@@ -155,7 +157,7 @@ private static Family SaveAndLoadFamily(Document document, Document familyDocume
private static Family GenerateFamilyFromTemplate(this Document document, IFramingElement element, string familyName, RevitSettings settings = null)
{
- string templateFamilyName = element.TemplateProfileFamilyName();
+ string templateFamilyName = element.ITemplateProfileFamilyName();
if (string.IsNullOrWhiteSpace(templateFamilyName))
return null;
@@ -174,7 +176,7 @@ private static Family GenerateFamilyFromTemplate(this Document document, IFramin
t.Commit();
}
- result = SaveAndLoadFamily(document, familyDocument, familyName, element, settings);
+ result = SaveAndLoadFamily(document, familyDocument, familyName);
}
catch (Exception ex)
{
@@ -223,7 +225,10 @@ private static Family GenerateFreeformFamily(this Document document, IFramingEle
Family result = null;
UIDocument uidoc = new UIDocument(document);
- Document familyDocument = uidoc.Application.Application.OpenDocumentFile($"{m_FamilyDirectory}\\{element.IProfileFamilyNamePrefix()}FreeformProfile.rfa");
+
+ string familyTemplateNameMask = $"*{element.IProfileFamilyNamePrefix()}FreeformProfile.rfa";
+ string filePath = Directory.GetFiles(m_FamilyDirectory, familyTemplateNameMask).FirstOrDefault();
+ Document familyDocument = uidoc.Application.Application.OpenDocumentFile(filePath);
try
{
@@ -273,7 +278,7 @@ private static Family GenerateFreeformFamily(this Document document, IFramingEle
t.Commit();
}
- result = SaveAndLoadFamily(document, familyDocument, familyName, element, settings);
+ result = SaveAndLoadFamily(document, familyDocument, familyName);
}
catch (Exception ex)
{
@@ -289,6 +294,14 @@ private static Family GenerateFreeformFamily(this Document document, IFramingEle
/***************************************************/
+ private static bool HasTemplateFamily(this IFramingElement element)
+ {
+ string templateFamilyName = element.ITemplateProfileFamilyName();
+ return !string.IsNullOrWhiteSpace(templateFamilyName) && !templateFamilyName.Contains(m_FamilyFileNames[typeof(FreeFormProfile)]);
+ }
+
+ /***************************************************/
+
private static bool SetMaterialSubcategory(this Document familyDocument, BH.oM.Physical.Materials.Material material)
{
List structuralProperties = material?.Properties?.Where(x => x is BH.oM.Structure.MaterialFragments.IMaterialFragment)?.ToList();
@@ -373,7 +386,14 @@ private static void CopyProfileDimensions(this Pile element, FamilySymbol target
return;
}
- CopyPileDimensions(sourceProfile as dynamic, targetSymbol, settings);
+ if (!(sourceProfile is CircleProfile circle))
+ {
+ BH.Engine.Base.Compute.RecordError($"Could not copy profile dimensions because the BHoM framing element is not a pile with a circular profile. BHoM_Guid: {element.BHoM_Guid}");
+ return;
+ }
+
+ Parameter parameter = targetSymbol.ParametersMap.Cast().FirstOrDefault(x => x.Definition.Name.Contains("Diameter"));
+ parameter?.Set(Convert.FromSI(circle.Diameter, SpecTypeId.Length));
}
/***************************************************/
@@ -387,7 +407,7 @@ private static void CopyProfileDimensions(this IFramingElement element, FamilySy
return;
}
- CopyDimensions(sourceProfile as dynamic, targetSymbol, settings);
+ sourceProfile.ICopyDimensions(targetSymbol, settings);
}
/***************************************************/
@@ -594,14 +614,7 @@ private static void CopyDimensions(this GeneralisedFabricatedBoxProfile sourcePr
/***************************************************/
- private static void CopyPileDimensions(this CircleProfile sourceProfile, FamilySymbol targetSymbol, RevitSettings settings = null)
- {
- targetSymbol.SetParameter("Diameter", sourceProfile.Diameter);
- }
-
- /***************************************************/
-
- private static string ProfileFamilyName(this IFramingElement element)
+ private static string ProfileFamilyName(this IFramingElement element, bool freeform)
{
string propertyName = element?.Property?.Name;
string profileName = (element?.Property as ConstantFramingProperty)?.Profile?.Name;
@@ -617,11 +630,16 @@ private static string ProfileFamilyName(this IFramingElement element)
else
{
// Take template fam name and remove last section after underscore
- string prefix = Regex.Replace(element.TemplateProfileFamilyName(), "_[^_]*$", "");
+ string prefix = Regex.Replace(element.ITemplateProfileFamilyName(), "_[^_]*$", "");
// Add profile name
- Regex pattern = new Regex(@"\d([\d\.\/\-xX ])*\d");
- return $"{prefix}_{pattern.Replace(name, "").Replace(" ", " ").Trim()}";
+ if (!freeform)
+ {
+ Regex pattern = new Regex(@"\d([\d\.\/\-xX ])*\d");
+ name = pattern.Replace(name, "").Replace(" ", " ").Trim();
+ }
+
+ return $"{prefix}_{name}";
}
}
@@ -643,6 +661,13 @@ private static string ProfileTypeName(this IFramingElement element)
/***************************************************/
+ private static string ITemplateProfileFamilyName(this IFramingElement element)
+ {
+ return TemplateProfileFamilyName(element as dynamic);
+ }
+
+ /***************************************************/
+
private static string TemplateProfileFamilyName(this IFramingElement element)
{
Type profileType = (element?.Property as ConstantFramingProperty)?.Profile?.GetType();
@@ -659,6 +684,28 @@ private static string TemplateProfileFamilyName(this IFramingElement element)
/***************************************************/
+ private static string TemplateProfileFamilyName(this Pile element)
+ {
+ Type profileType = (element?.Property as ConstantFramingProperty)?.Profile?.GetType();
+ if (!m_FamilyFileNames.ContainsKey(profileType))
+ return null;
+
+ string profileName;
+ if (profileType == typeof(CircleProfile))
+ profileName = m_FamilyFileNames[typeof(CircleProfile)];
+ else
+ profileName = m_FamilyFileNames[typeof(FreeFormProfile)];
+
+ string name = element.IProfileFamilyNamePrefix() + profileName;
+ string path = Directory.GetFiles(m_FamilyDirectory, $"*{name}.rfa").FirstOrDefault();
+ if (!string.IsNullOrEmpty(path))
+ return Path.GetFileNameWithoutExtension(path);
+ else
+ return null;
+ }
+
+ /***************************************************/
+
private static string IProfileFamilyNamePrefix(this IFramingElement element)
{
return ProfileFamilyNamePrefix(element as dynamic);
@@ -811,4 +858,3 @@ private static void CopyDimensions(this BH.oM.Spatial.ShapeProfiles.IProfile sou
}
-
diff --git a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs
index e876bdb4f..167e8595f 100644
--- a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs
+++ b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs
@@ -221,6 +221,56 @@ public static FamilyInstance ToRevitFamilyInstance(this Pile framingElement, Doc
return familyInstance;
}
+ /***************************************************/
+ [Description("Converts BH.oM.Physical.Elements.PadFoundation to a Revit FamilyInstance.")]
+ [Input("padFoundation", "BH.oM.Physical.Elements.PadFoundation to be converted.")]
+ [Input("document", "Revit document, in which the output of the convert will be created.")]
+ [Input("settings", "Revit adapter settings to be used while performing the convert.")]
+ [Input("refObjects", "Optional, a collection of objects already processed in the current adapter action, stored to avoid processing the same object more than once.")]
+ [Output("instance", "Revit FamilyInstance resulting from converting the input BH.oM.Physical.Elements.PadFoundation.")]
+
+ public static FamilyInstance ToRevitFamilyInstance(this PadFoundation padFoundation, Document document, RevitSettings settings = null, Dictionary> refObjects = null)
+ {
+ if (padFoundation == null || document == null)
+ return null;
+
+ FamilyInstance familyInstance = refObjects.GetValue(document, padFoundation.BHoM_Guid);
+ if (familyInstance != null)
+ return familyInstance;
+
+ settings = settings.DefaultIfNull();
+
+ BH.oM.Geometry.Point origin = padFoundation.PadFoundationCentroid();
+ if (origin == null)
+ {
+ BH.Engine.Base.Compute.RecordError($"PadFoundation boundary extraction failed or foundation is not rectangular. BHoM_Guid: {padFoundation.BHoM_Guid}");
+ return null;
+ }
+
+ XYZ placementPoint = origin.ToRevit();
+
+ Level level = document.LevelAbove(placementPoint.Z, settings);
+ if (level == null)
+ return null;
+
+ FamilySymbol familySymbol = padFoundation.ElementType(document, settings) as FamilySymbol;
+ if (familySymbol == null)
+ {
+ Compute.ElementTypeNotFoundWarning(padFoundation);
+ return null;
+ }
+
+ familySymbol.Activate();
+ familyInstance = document.Create.NewFamilyInstance(placementPoint, familySymbol, level, StructuralType.Footing);
+ document.Regenerate();
+
+ familyInstance.CopyParameters(padFoundation, settings);
+ familyInstance.SetLocation(padFoundation, settings);
+
+ refObjects.AddOrReplace(padFoundation, familyInstance);
+ return familyInstance;
+ }
+
/***************************************************/
[Description("Converts BH.oM.Physical.Elements.IFramingElement to a Revit FamilyInstance.")]
@@ -349,10 +399,4 @@ public static FamilyInstance ToRevitFamilyInstance(this IFramingElement framingE
/***************************************************/
}
-}
-
-
-
-
-
-
+}
\ No newline at end of file
diff --git a/Revit_Core_Engine/Convert/Physical/ToRevit/RoofBase.cs b/Revit_Core_Engine/Convert/Physical/ToRevit/RoofBase.cs
index 26b267218..112c024e4 100644
--- a/Revit_Core_Engine/Convert/Physical/ToRevit/RoofBase.cs
+++ b/Revit_Core_Engine/Convert/Physical/ToRevit/RoofBase.cs
@@ -94,24 +94,69 @@ public static RoofBase ToRevitRoofBase(this oM.Physical.Elements.Roof roof, Docu
if (controlPoints != null && controlPoints.Count > 2)
{
+ try
+ {
#if REVIT2022 || REVIT2023 || REVIT2024 || REVIT2025
- SlabShapeEditor slabShapeEditor = roofBase.SlabShapeEditor;
-#else
- SlabShapeEditor slabShapeEditor = roofBase.GetSlabShapeEditor();
-#endif
- slabShapeEditor.ResetSlabShape();
+ SlabShapeEditor slabShapeEditor = roofBase.SlabShapeEditor;
+ slabShapeEditor.ResetSlabShape();
- foreach (oM.Geometry.Point point in controlPoints)
- {
- if (Math.Abs(point.Z - plane.Origin.Z) > settings.DistanceTolerance)
+ foreach (oM.Geometry.Point point in controlPoints)
{
- XYZ xyz = point.ToRevit();
-#if REVIT2022 || REVIT2023 || REVIT2024 || REVIT2025
- slabShapeEditor.DrawPoint(xyz);
+ if (Math.Abs(point.Z - plane.Origin.Z) > settings.DistanceTolerance)
+ {
+ XYZ xyz = point.ToRevit();
+ slabShapeEditor.DrawPoint(xyz);
+ }
+ }
#else
- slabShapeEditor.AddPoint(xyz);
-#endif
+ FootPrintRoof fpr = roofBase as FootPrintRoof;
+ ModelCurveArrArray profiles = fpr.GetProfiles();
+
+ List curves = new List();
+ foreach (ModelCurveArray array in profiles)
+ {
+ foreach (ModelCurve mc in array)
+ {
+ curves.Add(mc.GeometryCurve);
+ }
}
+
+ SlabShapeEditor slabShapeEditor = roofBase.GetSlabShapeEditor();
+
+ if (!slabShapeEditor.IsEnabled)
+ slabShapeEditor.Enable();
+
+ document.Regenerate();
+
+ foreach (oM.Geometry.Point point in controlPoints)
+ {
+ if (Math.Abs(point.Z - plane.Origin.Z) > settings.DistanceTolerance)
+ {
+ XYZ xyz = point.ToRevit();
+ XYZ projected = new XYZ(xyz.X, xyz.Y, level.Elevation);
+
+ Curve closest = null;
+ double minDist = double.MaxValue;
+ foreach (Curve crv in curves)
+ {
+ double dist = crv.Distance(projected);
+ if (dist < minDist)
+ {
+ minDist = dist;
+ closest = crv;
+ }
+ }
+
+ XYZ onCurve = closest.Project(projected).XYZPoint;
+ XYZ onCurveOnFace = new XYZ(onCurve.X, onCurve.Y, xyz.Z);
+ slabShapeEditor.AddPoint(onCurveOnFace);
+ }
+ }
+#endif
+ }
+ catch (Exception ex)
+ {
+ BH.Engine.Base.Compute.RecordError($"Failed to update roof shape {roofBase.Id.Value()}: {ex.Message}");
}
}
}
diff --git a/Revit_Core_Engine/Convert/ToRevit.cs b/Revit_Core_Engine/Convert/ToRevit.cs
index cd0357341..d46d343b5 100644
--- a/Revit_Core_Engine/Convert/ToRevit.cs
+++ b/Revit_Core_Engine/Convert/ToRevit.cs
@@ -273,6 +273,19 @@ public static Element ToRevit(this BH.oM.MEP.System.Pipe pipe, Document document
/***************************************************/
+ [Description("Converts BH.oM.Physical.Elements.PadFoundation to a Revit FamilyInstance.")]
+ [Input("foundation", "BH.oM.Physical.Elements.PadFoundation to be converted.")]
+ [Input("document", "Revit document, in which the output of the convert will be created.")]
+ [Input("settings", "Revit adapter settings to be used while performing the convert.")]
+ [Input("refObjects", "Optional, a collection of objects already processed in the current adapter action, stored to avoid processing the same object more than once.")]
+ [Output("instance", "Revit FamilyInstance resulting from converting the input BH.oM.Physical.Elements.PadFoundation.")]
+ public static Element ToRevit(this BH.oM.Physical.Elements.PadFoundation foundation, Document document, RevitSettings settings = null, Dictionary> refObjects = null)
+ {
+ return foundation.ToRevitFamilyInstance(document, settings, refObjects);
+ }
+
+ /***************************************************/
+
[Description("Converts BH.oM.MEP.System.MaterialFragments.PipeMaterial to a Revit PipeSegment.")]
[Input("pipeMaterial", "BH.oM.MEP.System.MaterialFragments.PipeMaterial to be converted.")]
[Input("document", "Revit document, in which the output of the convert will be created.")]
diff --git a/Revit_Core_Engine/Files/Families/StructuralFoundations_FreeformProfile.rfa b/Revit_Core_Engine/Files/Families/StructuralFoundations_FreeformProfile.rfa
index 9af197499..a3b763c24 100644
Binary files a/Revit_Core_Engine/Files/Families/StructuralFoundations_FreeformProfile.rfa and b/Revit_Core_Engine/Files/Families/StructuralFoundations_FreeformProfile.rfa differ
diff --git a/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Freeform.rfa b/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Freeform.rfa
new file mode 100644
index 000000000..425275255
Binary files /dev/null and b/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Freeform.rfa differ
diff --git a/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Rectangular.rfa b/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Rectangular.rfa
new file mode 100644
index 000000000..5a60fd2bc
Binary files /dev/null and b/Revit_Core_Engine/Files/Families/StructuralFoundations_Pad-Rectangular.rfa differ
diff --git a/Revit_Core_Engine/Modify/OrientToOrigin.cs b/Revit_Core_Engine/Modify/OrientToOrigin.cs
new file mode 100644
index 000000000..9fc17731f
--- /dev/null
+++ b/Revit_Core_Engine/Modify/OrientToOrigin.cs
@@ -0,0 +1,48 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using BH.Engine.Geometry;
+using BH.oM.Base.Attributes;
+using BH.oM.Geometry;
+using System.ComponentModel;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Modify
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Transforms a given Polyline into a standardized orientation by translating it to the origin and rotating it in XY plane.")]
+ [Input("outline", "Polyline to be oriented to the origin.")]
+ [Output("orientedOutline", "Oriented polyline translated to the origin and rotated in XY plane.")]
+ public static Polyline OrientToOrigin(this Polyline outline)
+ {
+ (Vector translation, double rotation) = outline.TransformToOriginInXY();
+ if (translation == null || double.IsNaN(rotation))
+ return null;
+
+ return outline.Translate(translation).Rotate(new Point(), Vector.ZAxis, rotation);
+ }
+ }
+}
diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs
index 7d23874e7..ffa2f7af1 100644
--- a/Revit_Core_Engine/Modify/SetLocation.cs
+++ b/Revit_Core_Engine/Modify/SetLocation.cs
@@ -21,6 +21,7 @@
*/
using Autodesk.Revit.DB;
+using BH.Engine.Adapters.Revit;
using BH.Engine.Geometry;
using BH.Engine.Spatial;
using BH.oM.Adapters.Revit.Elements;
@@ -344,6 +345,104 @@ public static bool SetLocation(this FamilyInstance element, Pile pile, RevitSett
/***************************************************/
+ [Description("Sets the location of a given Revit FamilyInstance based on a given BHoM PadFoundation.")]
+ [Input("element", "Revit FamilyInstance to be modified.")]
+ [Input("padFoundation", "BHoM PadFoundation acting as a source of information about the new location.")]
+ [Input("settings", "Revit adapter settings to be used while performing the operation.")]
+ [Output("success", "True if location of the input Revit FamilyInstance has been successfully set.")]
+ public static bool SetLocation(this FamilyInstance element, PadFoundation padFoundation, RevitSettings settings)
+ {
+ if (element == null || padFoundation == null)
+ return false;
+
+ settings = settings.DefaultIfNull();
+
+ // Revit location point including coordinates and rotation
+ LocationPoint revitLocation = element.Location as LocationPoint;
+ if (revitLocation == null)
+ return false;
+
+ Polyline bhomOutline = padFoundation.PadFoundationOutline();
+ if (bhomOutline == null)
+ return false;
+
+ // Transformation needed to bring BHoM outline to global XY
+ (Vector, double) bhomTransform = bhomOutline.TransformToOriginInXY();
+ if (bhomTransform.Item1 == null || double.IsNaN(bhomTransform.Item2))
+ return false;
+
+ // Translation between the origin and BHoM centroid
+ XYZ bhomXY = (new BH.oM.Geometry.Point() - bhomTransform.Item1).ToRevit();
+
+ bool updated = false;
+ XYZ referencePoint = null;
+ foreach (Autodesk.Revit.DB.Face face in element.Faces(new Options(), settings))
+ {
+ if (face is PlanarFace planarFace && Math.Abs(1 - planarFace.FaceNormal.DotProduct(XYZ.BasisZ)) <= BH.oM.Adapters.Revit.Tolerance.Angle)
+ {
+ referencePoint = planarFace.Centroid();
+ break;
+ }
+ }
+
+ if (referencePoint == null)
+ return false;
+
+ XYZ deltaXY = new XYZ(bhomXY.X - referencePoint.X, bhomXY.Y - referencePoint.Y, 0);
+
+ if (deltaXY.GetLength() > settings.DistanceTolerance)
+ {
+ ElementTransformUtils.MoveElement(element.Document, element.Id, deltaXY);
+ updated = true;
+ element.Document.Regenerate();
+ }
+
+ // Rotate if needed
+ double dRot = (-bhomTransform.Item2 - revitLocation.Rotation).NormalizeAngleDomain();
+ if (Math.Abs(dRot) > settings.AngleTolerance)
+ {
+ Autodesk.Revit.DB.Line axis = Autodesk.Revit.DB.Line.CreateBound(bhomXY, bhomXY + XYZ.BasisZ);
+ ElementTransformUtils.RotateElement(element.Document, element.Id, axis, dRot);
+ updated = true;
+ element.Document.Regenerate();
+ }
+
+ BoundingBoxXYZ bbox = element.get_BoundingBox(null);
+ double topZ = bbox.Max.Z;
+ double bhomZ = -bhomTransform.Item1.Z.FromSI(SpecTypeId.Length);
+ double dz = bhomZ - topZ;
+
+ // Move vertically if needed
+ if (Math.Abs(dz) > settings.DistanceTolerance)
+ {
+ Parameter offParam = element.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM);
+ if (offParam != null && offParam.HasValue && !offParam.IsReadOnly)
+ {
+ offParam.Set(offParam.AsDouble() + dz);
+ updated = true;
+ element.Document.Regenerate();
+ }
+ else
+ {
+ try
+ {
+ XYZ p = revitLocation.Point;
+ revitLocation.Point = new XYZ(p.X, p.Y, p.Z + dz);
+ updated = true;
+ element.Document.Regenerate();
+ }
+ catch (Exception)
+ {
+ BH.Engine.Base.Compute.RecordWarning($"Pad foundation vertical placement could not be adjusted (offset parameter missing or location read-only). ElementId: {element.Id.Value()}");
+ }
+ }
+ }
+
+ return updated;
+ }
+
+ /***************************************************/
+
[Description("Sets the location of a given Revit Space based on a given BHoM Space.")]
[Input("revitSpace", "Revit Space to be modified.")]
[Input("bHoMSpace", "BHoM Space acting as a source of information about the new location.")]
@@ -446,6 +545,18 @@ public static bool SetLocation(this HostObject element, BH.oM.Physical.Elements.
return false;
}
+ /***************************************************/
+
+ [Description("Sets the location of a given Revit Floor based on a given BHoM Floor.")]
+ [Input("element", "Revit Floor to be modified.")]
+ [Input("bHoMObject", "BHoM Floor acting as a source of information about the new location.")]
+ [Input("settings", "Revit adapter settings to be used while performing the operation.")]
+ [Output("success", "True if location of the input Revit Floor has been successfully set.")]
+ public static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Physical.Elements.Floor bHoMObject, RevitSettings settings)
+ {
+ return element.SetLocation(bHoMObject.Location, settings);
+ }
+
/***************************************************/
/**** Fallback Methods ****/
@@ -494,6 +605,227 @@ public static bool ISetLocation(this Element element, IBHoMObject bHoMObject, Re
/**** Private Methods ****/
/***************************************************/
+ private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geometry.ISurface location, RevitSettings settings)
+ {
+ PlanarSurface ps = location as PlanarSurface;
+ if (ps == null)
+ {
+ BH.Engine.Base.Compute.RecordWarning("Floor location must be a PlanarSurface");
+ return false;
+ }
+
+ if (ps.InternalBoundaries != null && ps.InternalBoundaries.Count > 0)
+ BH.Engine.Base.Compute.RecordWarning($"Floor has openings which will be ignored during sketch update. ElementId: {element.Id.Value()}");
+
+ Document doc = element.Document;
+ ElementId floorId = element.Id;
+ Sketch sketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == floorId);
+ if (sketch?.Id == null || sketch.SketchPlane == null)
+ {
+ BH.Engine.Base.Compute.RecordError($"Floor sketch not found. ElementId: {element.Id.Value()}");
+ return false;
+ }
+
+ Level level = doc.GetElement(element.LevelId) as Level;
+ BH.oM.Geometry.Plane slabPlane = ps.FitPlane();
+
+ double floorThickness = 0;
+ FloorType floorType = doc.GetElement(element.GetTypeId()) as FloorType;
+ if (floorType?.GetCompoundStructure() != null)
+ floorThickness = floorType.GetCompoundStructure().GetWidth();
+
+ double bottomElevation = BH.Engine.Geometry.Query.IBounds(ps).Min.Z;
+ oM.Geometry.Plane sketchPlaneBhom = new oM.Geometry.Plane { Origin = new BH.oM.Geometry.Point { Z = bottomElevation }, Normal = Vector.ZAxis };
+ ICurve curve = ps.ExternalBoundary.IProject(sketchPlaneBhom);
+
+ bool isFlat = 1 - Math.Abs(Vector.ZAxis.DotProduct(slabPlane.Normal)) <= settings.AngleTolerance;
+
+ double newOffset;
+ double tan = 0;
+ Autodesk.Revit.DB.Line slopeLineForCreate = null;
+
+ if (isFlat)
+ {
+ newOffset = slabPlane.Origin.Z.FromSI(SpecTypeId.Length) - level.ProjectElevation;
+ }
+ else
+ {
+ Vector normal = slabPlane.Normal;
+ if (normal.Z < 0)
+ normal = -slabPlane.Normal;
+
+ double angle = normal.Angle(Vector.ZAxis);
+ tan = Math.Tan(angle);
+
+ XYZ dir = normal.Project(oM.Geometry.Plane.XY).ToRevit().Normalize();
+ BH.oM.Geometry.Line ln = slabPlane.PlaneIntersection(sketchPlaneBhom);
+ XYZ start = ln.ClosestPoint(curve.IStartPoint(), true).ToRevit();
+
+ CurveLoop outlineForIntersection = curve.ToRevitCurveLoop();
+ XYZ centroid = curve.ICentroid().ToRevit();
+
+ double extensionLength = 10000;
+ Autodesk.Revit.DB.Line slopeThroughCentroid = Autodesk.Revit.DB.Line.CreateBound(centroid - dir * extensionLength, centroid + dir * extensionLength);
+
+ List edgePoints = slopeThroughCentroid.Intersections(outlineForIntersection);
+ if (edgePoints != null && edgePoints.Count >= 2)
+ {
+ edgePoints.Sort((a, b) => a.DotProduct(dir).CompareTo(b.DotProduct(dir)));
+ slopeLineForCreate = Autodesk.Revit.DB.Line.CreateBound(edgePoints.First(), edgePoints.Last());
+ }
+
+ double baseOffset = ln.Start.Z.FromSI(SpecTypeId.Length) - level.ProjectElevation;
+
+ if (slopeLineForCreate != null)
+ {
+ XYZ slopeTail = slopeLineForCreate.GetEndPoint(0);
+ double signedDistFromLn = (slopeTail - start).DotProduct(dir);
+ baseOffset -= signedDistFromLn * tan;
+ }
+
+ newOffset = baseOffset;
+ }
+
+ CurveLoop newOutline;
+ BH.oM.Geometry.ICurve projectedBoundary;
+ if (isFlat)
+ {
+ Autodesk.Revit.DB.Plane revitPlane = sketch.SketchPlane.GetPlane();
+ BH.oM.Geometry.Plane revitSketchBhomPlane = BH.Engine.Geometry.Create.Plane(revitPlane.Origin.PointFromRevit(), revitPlane.Normal.VectorFromRevit());
+ projectedBoundary = ps.ExternalBoundary.IProject(revitSketchBhomPlane);
+ newOutline = projectedBoundary.ToRevitCurveLoop();
+ }
+ else
+ {
+ projectedBoundary = curve;
+ newOutline = curve.ToRevitCurveLoop();
+ }
+
+ if (HasFloorChanged(element, sketch, newOutline, newOffset, settings))
+ {
+ ElementId sketchId = sketch.Id;
+ ElementId floorTypeId = element.GetTypeId();
+ ElementId levelId = element.LevelId;
+
+ Autodesk.Revit.DB.Line slopeLine = slopeLineForCreate;
+ double getTan = tan;
+ double getOffset = newOffset;
+ CurveLoop getOutline = newOutline;
+
+ SketchUpdateQueue.SketchUpdates.Enqueue(() =>
+ {
+ Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor;
+ if (floor == null || !floor.IsValidObject)
+ return;
+
+ try
+ {
+ if (!isFlat)
+ {
+ // recreate floor
+ using (Transaction tSlope = new Transaction(doc, "Recreate sloped floor"))
+ {
+ tSlope.Start();
+ doc.Delete(floor.Id);
+ doc.Regenerate();
+ Autodesk.Revit.DB.Floor newFloor = Autodesk.Revit.DB.Floor.Create(doc, new List { getOutline }, floorTypeId, levelId, true, slopeLine, -getTan);
+ if (newFloor != null)
+ {
+ newFloor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, getOffset, false);
+ doc.Regenerate();
+ }
+ tSlope.Commit();
+ }
+ }
+ else
+ {
+ Sketch floorSketch = doc.GetElement(sketchId) as Sketch;
+
+ UpdateSketchOutline(doc, sketchId, getOutline);
+
+ using (Transaction tOffset = new Transaction(doc, "Update floor offset"))
+ {
+ tOffset.Start();
+ floor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, getOffset, false);
+ doc.Regenerate();
+ tOffset.Commit();
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}");
+ return;
+ }
+ });
+ }
+
+ return true;
+ }
+
+ /***************************************************/
+
+ private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveLoop newOutline)
+ {
+ using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch"))
+ {
+ ses.Start(sketchId);
+ using (Transaction t = new Transaction(doc, "Modify sketch profile"))
+ {
+ t.Start();
+ Sketch currentSketch = doc.GetElement(sketchId) as Sketch;
+ if (currentSketch != null)
+ {
+ IList existingElements = currentSketch.GetAllElements();
+ if (existingElements != null && existingElements.Count > 0)
+ doc.Delete(existingElements);
+
+ SketchPlane sketchPlane = currentSketch.SketchPlane;
+ if (sketchPlane != null)
+ foreach (Curve curve in newOutline)
+ doc.Create.NewModelCurve(curve, sketchPlane);
+ }
+ t.Commit();
+ }
+ ses.Commit(new SketchUpdateFailurePreprocessor());
+ }
+ }
+
+ /***************************************************/
+
+ private static bool HasFloorChanged(Autodesk.Revit.DB.Floor floor, Sketch sketch, CurveLoop newOutline, double newOffset, RevitSettings settings)
+ {
+ // newOffset is in Revit internal units (feet), so read without SI conversion
+ double currentOffset = floor.LookupParameterDouble(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, false);
+ if (Math.Abs(currentOffset - newOffset) > settings.DistanceTolerance)
+ return true;
+
+ IList sketchElements = sketch.GetAllElements();
+ if (sketchElements == null || sketchElements.Count == 0)
+ return true;
+
+ List currentCurves = new List();
+ foreach (ElementId elemId in sketchElements)
+ {
+ ModelCurve modelCurve = floor.Document.GetElement(elemId) as ModelCurve;
+ if (modelCurve != null && modelCurve.GeometryCurve != null && !(modelCurve.GeometryCurve is Autodesk.Revit.DB.Line && modelCurve.GeometryCurve.Length < 1.1))
+ currentCurves.Add(modelCurve.GeometryCurve);
+ }
+
+ if (currentCurves.Count != newOutline.Count())
+ return true;
+
+ for (int i = 0; i < currentCurves.Count; i++)
+ {
+ if (!currentCurves[i].IsSimilar(newOutline.ElementAt(i), settings))
+ return true;
+ }
+
+ return false;
+ }
+
+ /***************************************************/
+
private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Point location, Basis orientation, RevitSettings settings)
{
LocationPoint elementLocation = element.Location as LocationPoint;
@@ -562,6 +894,7 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin
newLocation = linkTransform.OfPoint(newLocation);
if (ir.Distance > settings.DistanceTolerance)
+
BH.Engine.Base.Compute.RecordWarning($"The location point used on update of a family instance has been snapped to its host face. ElementId: {element.Id.Value()}");
}
}
@@ -592,6 +925,7 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin
}
if (1 - Math.Abs(revitNormal.DotProduct(bHoMNormal)) > settings.AngleTolerance)
+
BH.Engine.Base.Compute.RecordWarning($"The orientation applied to the family instance on update has different normal than the original one. Only in-plane rotation has been applied, the orientation out of plane has been ignored. ElementId: {element.Id.Value()}");
double angle = transform.BasisX.AngleOnPlaneTo(newX, revitNormal);
@@ -605,9 +939,8 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin
return success;
}
-
+
/***************************************************/
-
private static bool UpdateRotationOfVerticalElement(this FamilyInstance element, IFramingElement bhomElement, RevitSettings settings)
{
bool updated = false;
@@ -637,7 +970,6 @@ private static bool UpdateRotationOfVerticalElement(this FamilyInstance element,
return updated;
}
-
/***************************************************/
/**** Private collections ****/
/***************************************************/
diff --git a/Revit_Core_Engine/Modify/Update.cs b/Revit_Core_Engine/Modify/Update.cs
index 946ed22c6..04b1709fe 100644
--- a/Revit_Core_Engine/Modify/Update.cs
+++ b/Revit_Core_Engine/Modify/Update.cs
@@ -24,12 +24,14 @@
using Autodesk.Revit.DB.Plumbing;
using BH.Engine.Adapters.Revit;
using BH.Engine.Base;
+using BH.Engine.Geometry;
using BH.oM.Adapters.Revit.Elements;
using BH.oM.Adapters.Revit.Settings;
using BH.oM.Base;
using BH.oM.Base.Attributes;
using BH.oM.Geometry;
using BH.oM.MEP.System.MaterialFragments;
+using BH.oM.Physical.Elements;
using System;
using System.Collections.Generic;
using System.ComponentModel;
@@ -114,6 +116,63 @@ public static bool Update(this Element element, IBHoMObject bHoMObject, RevitSet
/***************************************************/
+ [Description("Updates a Revit pad foundation FamilyInstance from the BHoM PadFoundation using the standard element update.")]
+ [Input("element", "Revit FamilyInstance representing the pad foundation to update.")]
+ [Input("bHoMObject", "BHoM PadFoundation whose properties (and optionally location) should be applied to the Revit instance.")]
+ [Input("settings", "Revit adapter settings used for the underlying element update.")]
+ [Input("setLocationOnUpdate", "If false, only parameters and properties are updated; if true, the instance location is updated as well.")]
+ [Output("success", "True if the underlying Element.Update succeeded; dimension mismatch checks only emit warnings and do not change this value.")]
+ public static bool Update(this FamilyInstance element, PadFoundation bHoMObject, RevitSettings settings, bool setLocationOnUpdate)
+ {
+ settings = settings.DefaultIfNull();
+
+ // Base update
+ bool result = ((Element)element).Update((IBHoMObject)bHoMObject, settings, setLocationOnUpdate);
+
+ FamilySymbol symbol = element.Document.GetElement(element.GetTypeId()) as FamilySymbol;
+
+ // Check if outlines match after setting the type (location & orientation irrelevant)
+ Polyline outline = bHoMObject.PadFoundationOutline();
+ bool isRectangle = outline.IsRectangular(settings.DistanceTolerance);
+ bool matchingOutline;
+ if (isRectangle)
+ {
+ double len1 = outline.ControlPoints[0].Distance(outline.ControlPoints[1]);
+ double len2 = outline.ControlPoints[1].Distance(outline.ControlPoints[2]);
+ double bhomLength = Math.Max(len1, len2);
+ double bhomWidth = Math.Min(len1, len2);
+
+ double revitLength = symbol.LookupParameterDouble("Length");
+ double revitWidth = symbol.LookupParameterDouble("Width");
+
+ matchingOutline = Math.Abs(bhomLength - revitLength) <= settings.DistanceTolerance &&
+ Math.Abs(bhomWidth - revitWidth) <= settings.DistanceTolerance;
+ }
+ else
+ matchingOutline = symbol.Family.IsMatchingOutline(outline.OrientToOrigin(), settings);
+
+ if (!matchingOutline)
+ BH.Engine.Base.Compute.RecordWarning($"Pad outline had not been updated successfully, there is a mismatch between BHoM and Revit dimensions. ElementId {element.Id.Value()}");
+
+ // Check if thickness matches after setting the type
+ bool matchingThickness;
+ double bhomThickness = bHoMObject.PadFoundationThickness();
+ if (!double.IsNaN(bhomThickness))
+ {
+ double revitThickness = symbol.LookupParameterDouble("Foundation Thickness");
+ matchingThickness = Math.Abs(bhomThickness - revitThickness) <= settings.DistanceTolerance;
+ }
+ else
+ matchingThickness = false;
+
+ if (!matchingThickness)
+ BH.Engine.Base.Compute.RecordWarning($"Pad thickness had not been updated successfully, there is a mismatch between BHoM and Revit dimensions. ElementId {element.Id.Value()}");
+
+ return result;
+ }
+
+ /***************************************************/
+
[Description("Updates the existing Revit FamilyInstance based on the given BHoM builders work Opening.")]
[Input("element", "Revit FamilyInstance to be updated.")]
[Input("bHoMObject", "BHoM builders work Opening, based on which the Revit element will be updated.")]
diff --git a/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs
new file mode 100644
index 000000000..30edec6b8
--- /dev/null
+++ b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs
@@ -0,0 +1,45 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using Autodesk.Revit.DB;
+using BH.oM.Base.Attributes;
+using System.ComponentModel;
+
+namespace BH.Revit.Engine.Core
+{
+ internal class SketchUpdateFailurePreprocessor : IFailuresPreprocessor
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Preprocesses failures that occur during sketch updates, allowing the operation to continue despite warnings or errors.")]
+ [Input("failuresAccessor", "Revit failures accessor object containing failure messages from the sketch update operation.")]
+ [Output("failureProcessingResult", "The result of the failure processing, indicating whether to continue with the operation.")]
+ public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor)
+ {
+ return FailureProcessingResult.Continue;
+ }
+
+ /***************************************************/
+ }
+}
diff --git a/Revit_Core_Engine/Objects/SketchUpdateQueue.cs b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs
new file mode 100644
index 000000000..3ed034bb9
--- /dev/null
+++ b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace BH.Revit.Engine.Core
+{
+ public static class SketchUpdateQueue
+ {
+ /***************************************************/
+ /**** Public Properties ****/
+ /***************************************************/
+
+ [Description("Queue containing actions to update floor sketches. These updates are deferred until after the main push transaction to avoid conflicts with sketch editing.")]
+ public static Queue SketchUpdates { get; } = new Queue();
+
+ /***************************************************/
+ }
+}
diff --git a/Revit_Core_Engine/Query/ElementType.cs b/Revit_Core_Engine/Query/ElementType.cs
index 636da66b8..48be1da8b 100644
--- a/Revit_Core_Engine/Query/ElementType.cs
+++ b/Revit_Core_Engine/Query/ElementType.cs
@@ -78,6 +78,27 @@ public static FamilySymbol ElementType(this BH.oM.Physical.Elements.IFramingElem
/***************************************************/
+ [Description("Returns the Revit FamilySymbol to use when converting a BHoM PadFoundation. Orthogonal rectangular plans resolve by BHoM family/type name (document or library), then fall back to generation; non-rectangular plans use generation from geometry.")]
+ [Input("padFoundation", "BHoM pad foundation to find or generate a correspondent Revit family type for.")]
+ [Input("document", "Revit document to search for the element type or host generated types.")]
+ [Input("settings", "Revit adapter settings to be used while performing the query.")]
+ [Output("familySymbol", "Revit FamilySymbol to be used when converting the input pad foundation to Revit.")]
+ public static FamilySymbol ElementType(this BH.oM.Physical.Elements.PadFoundation padFoundation, Document document, RevitSettings settings = null)
+ {
+ FamilySymbol result = padFoundation.GeneratePadFoundationType(document, settings);
+ padFoundation.FamilyAndTypeNames(out string familyName, out string familyTypeName);
+
+ if (!string.IsNullOrWhiteSpace(familyName) && result.FamilyName != familyName)
+ BH.Engine.Base.Compute.RecordWarning($"BHoM PadFoundation's name does not match Revit family name derived from its geometry. BHoM_Guid: {padFoundation.BHoM_Guid}");
+
+ if (!string.IsNullOrWhiteSpace(familyTypeName) && result.Name != familyTypeName)
+ BH.Engine.Base.Compute.RecordWarning($"BHoM PadFoundation's name does not match Revit type name derived from its geometry. BHoM_Guid: {padFoundation.BHoM_Guid}");
+
+ return result;
+ }
+
+ /***************************************************/
+
[Description("Returns the Revit element type to be used when converting a given BHoM IInstance to Revit.")]
[Input("instance", "BHoM IInstance to find a correspondent Revit element type for.")]
[Input("document", "Revit document to parse in search for the element type.")]
diff --git a/Revit_Core_Engine/Query/IsMatchingOutline.cs b/Revit_Core_Engine/Query/IsMatchingOutline.cs
new file mode 100644
index 000000000..b0565f417
--- /dev/null
+++ b/Revit_Core_Engine/Query/IsMatchingOutline.cs
@@ -0,0 +1,122 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using Autodesk.Revit.DB;
+using BH.Engine.Geometry;
+using BH.oM.Adapters.Revit.Settings;
+using BH.oM.Base.Attributes;
+using BH.oM.Geometry;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Query
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Checks whether the given BHoM Polyline outline matches the outline of the padFoundationFamily freeform extrusion in Revit.")]
+ [Input("padFoundationFamily", "Revit padFoundationFamily whose extrusion outline should be compared.")]
+ [Input("orientedOutline", "BHoM polyline outline to match against the padFoundationFamily outline.")]
+ [Input("settings", "Revit adapter settings providing distance tolerance for the matching.")]
+ [Output("matches", "True if the padFoundationFamily extrusion outline matches the input outline.")]
+ public static bool IsMatchingOutline(this Family padFoundationFamily, Polyline orientedOutline, RevitSettings settings)
+ {
+ double tol = settings.DistanceTolerance;
+ try
+ {
+ List bhomEdges = orientedOutline.SubParts().Where(x => x != null && x.Length() > tol).ToList();
+ List revitEdges = padFoundationFamily.ExtrusionEdges(settings);
+
+ if (bhomEdges.Count != revitEdges.Count)
+ return false;
+
+ for (int i = 0; i < bhomEdges.Count; i++)
+ {
+ BH.oM.Geometry.Line bhomEdge = bhomEdges[i];
+ bool edgeMatch = false;
+ foreach (oM.Geometry.Line revitEdge in revitEdges)
+ {
+ oM.Geometry.Point bhomStart = bhomEdge.Start;
+ oM.Geometry.Point bhomEnd = bhomEdge.End;
+ oM.Geometry.Point revitStart = revitEdge.Start;
+ oM.Geometry.Point revitEnd = revitEdge.End;
+ if ((bhomStart.Distance(revitStart) <= tol && bhomEnd.Distance(revitEnd) <= tol)
+ || (bhomStart.Distance(revitEnd) <= tol && bhomEnd.Distance(revitStart) <= tol))
+ {
+ edgeMatch = true;
+ break;
+ }
+ }
+
+ if (!edgeMatch)
+ return false;
+ }
+
+ return true;
+ }
+ catch
+ {
+ return false;
+ }
+ }
+
+ /***************************************************/
+ /**** Private methods ****/
+ /***************************************************/
+
+ private static List ExtrusionEdges(this Family family, RevitSettings settings)
+ {
+ ISet symbolIds = family.GetFamilySymbolIds();
+ if (symbolIds == null || symbolIds.Count == 0)
+ return null;
+
+ ElementType type = family.Document.GetElement(symbolIds.First()) as ElementType;
+
+ Options options = new Options()
+ {
+ ComputeReferences = false,
+ DetailLevel = Autodesk.Revit.DB.ViewDetailLevel.Medium,
+ IncludeNonVisibleObjects = false
+ };
+
+ List faces = type.Faces(options);
+ List tops = faces.OfType().Where(x => x.FaceNormal.IsAlmostEqualTo(XYZ.BasisZ)).ToList();
+ if (tops == null || tops.Count != 1)
+ return null;
+
+ IList outlines = tops[0].GetEdgesAsCurveLoops();
+ if (outlines == null || outlines.Count != 1)
+ return null;
+
+ List edges = outlines[0].FromRevit().SubParts();
+ List lines = edges.OfType().ToList();
+ if (edges.Count != lines.Count)
+ return null;
+
+ return lines.Where(x => x.Length() > settings.DistanceTolerance).Select(x => x.Project(new oM.Geometry.Plane())).ToList();
+ }
+ }
+}
diff --git a/Revit_Core_Engine/Query/IsValid.cs b/Revit_Core_Engine/Query/IsValid.cs
new file mode 100644
index 000000000..d8cb08702
--- /dev/null
+++ b/Revit_Core_Engine/Query/IsValid.cs
@@ -0,0 +1,50 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using Autodesk.Revit.DB;
+using BH.oM.Base.Attributes;
+using System.ComponentModel;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Query
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Checks whether the given XYZ point is valid (not null and does not contain NaN or Infinity values).")]
+ [Input("point", "XYZ point to be checked for validity.")]
+ [Output("isValid", "True if the input XYZ point is valid (not null and all coordinates are finite numbers), otherwise false.")]
+ public static bool IsValid(this XYZ point)
+ {
+ if (point == null)
+ return false;
+
+ return !double.IsNaN(point.X) && !double.IsInfinity(point.X) &&
+ !double.IsNaN(point.Y) && !double.IsInfinity(point.Y) &&
+ !double.IsNaN(point.Z) && !double.IsInfinity(point.Z);
+ }
+
+ /***************************************************/
+ }
+}
diff --git a/Revit_Core_Engine/Query/PadFoundation.cs b/Revit_Core_Engine/Query/PadFoundation.cs
new file mode 100644
index 000000000..e102a4429
--- /dev/null
+++ b/Revit_Core_Engine/Query/PadFoundation.cs
@@ -0,0 +1,87 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using BH.Engine.Geometry;
+using BH.oM.Base.Attributes;
+using BH.oM.Geometry;
+using BH.oM.Physical.Elements;
+using System.ComponentModel;
+using System.Linq;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Query
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Extracts the outer rectangular boundary of a PadFoundation as a Polyline.")]
+ [Input("element", "PadFoundation element whose boundary should be extracted.")]
+ [Output("outline", "Polyline representing the PadFoundation external boundary.")]
+ public static Polyline PadFoundationOutline(this PadFoundation element)
+ {
+ ICurve outline = element?.Location?.ExternalBoundary;
+ if (outline == null)
+ return null;
+
+ if (element.Location.InternalBoundaries.Count != 0)
+ BH.Engine.Base.Compute.RecordWarning($"PadFoundation with internal boundaries are currently not supported, holes were skipped. BHoM_Guid: {element.BHoM_Guid}");
+
+ if (outline.ISubParts().Any(x => !(x is Line)))
+ {
+ BH.Engine.Base.Compute.RecordError($"PadFoundation boundary contains non-linear curve segments. Only linear segments are currently supported. BHoM_Guid: {element.BHoM_Guid}");
+ return null;
+ }
+
+ return outline.IToPolyline();
+ }
+
+ /***************************************************/
+
+ [Description("Computes the total thickness (sum of construction layers) of a PadFoundation.")]
+ [Input("element", "PadFoundation element whose thickness should be computed.")]
+ [Output("thickness", "Total thickness of all construction layers.")]
+ public static double PadFoundationThickness(this PadFoundation element)
+ {
+ oM.Physical.Constructions.Construction construction = element?.Construction as oM.Physical.Constructions.Construction;
+ if (construction?.Layers == null)
+ return double.NaN;
+
+ return construction.Layers.Sum(layer => layer.Thickness);
+ }
+
+ /***************************************************/
+
+ [Description("Returns the centroid of the PadFoundation outer boundary as a Point.")]
+ [Input("element", "PadFoundation element to compute the centroid for.")]
+ [Output("centroid", "Centroid of the PadFoundation outer boundary point.")]
+ public static Point PadFoundationCentroid(this PadFoundation element)
+ {
+ return element?.PadFoundationOutline()?.Centroid();
+ }
+
+ /***************************************************/
+ }
+}
+
+
diff --git a/Revit_Core_Engine/Query/Space.cs b/Revit_Core_Engine/Query/Space.cs
index 02c990c45..9c0fcf882 100644
--- a/Revit_Core_Engine/Query/Space.cs
+++ b/Revit_Core_Engine/Query/Space.cs
@@ -36,7 +36,6 @@ public static partial class Query
/**** Public methods ****/
/***************************************************/
- [PreviousVersion("9.1", "BH.Revit.Engine.Core.Query.Space(Autodesk.Revit.DB.Element, System.Collections.Generic.IEnumerable, System.Boolean)")]
[Description("Returns the Revit Space that contains the given element.")]
[Input("element", "The Revit element for which to find the containing Space.")]
[Input("spaces", "An optional collection of Revit Spaces to search. If not provided, all Spaces in the element's document will be used.")]
@@ -102,7 +101,17 @@ public static Space Space(this Element element, IEnumerable spaces, bool
if (!findClosestIfNotContained)
return null;
- // 4. If not found, try find closest space in connector directions (for MEP elements)
+ // 4. If still not found, try find closest above (Z direction)
+ Space closestAbove = locationPoint.FindClosestSpaceInDirection(XYZ.BasisZ, spaces, maxDistance: 1); // 1 feet max distance
+ if (closestAbove != null)
+ return closestAbove;
+
+ // 5. If still not found, try find closest below (negative Z direction)
+ Space closestBelow = locationPoint.FindClosestSpaceInDirection(-XYZ.BasisZ, spaces, maxDistance: 10); // 10 feet max distance
+ if (closestBelow != null)
+ return closestBelow;
+
+ // 6. If not found, try find closest space in connector directions (for MEP elements)
var connectors = element.Connectors()?.OrderByDescending(x => x.GetMEPConnectorInfo().IsPrimary).ToList();
if (connectors != null && connectors.Any())
{
@@ -123,16 +132,6 @@ public static Space Space(this Element element, IEnumerable spaces, bool
}
}
- // 5. If still not found, try find closest above (Z direction)
- Space closestAbove = locationPoint.FindClosestSpaceInDirection(XYZ.BasisZ, spaces, maxDistance: 1); // 1 feet max distance
- if (closestAbove != null)
- return closestAbove;
-
- // 6. If still not found, try find closest below (negative Z direction)
- Space closestBelow = locationPoint.FindClosestSpaceInDirection(-XYZ.BasisZ, spaces, maxDistance: 10); // 10 feet max distance
- if (closestBelow != null)
- return closestBelow;
-
// Not found
return null;
}
diff --git a/Revit_Core_Engine/Query/StructuralMaterial.cs b/Revit_Core_Engine/Query/StructuralMaterial.cs
index 96d769496..101486d0b 100644
--- a/Revit_Core_Engine/Query/StructuralMaterial.cs
+++ b/Revit_Core_Engine/Query/StructuralMaterial.cs
@@ -37,7 +37,6 @@ public static partial class Query
/**** Public methods ****/
/***************************************************/
- [PreviousVersion("9.1", "BH.Revit.Engine.Core.Query.FramingMaterial(Autodesk.Revit.DB.FamilyInstance, BH.oM.Adapters.Revit.Settings.RevitSettings, System.Collections.Generic.Dictionary>)")]
[Description("Extracts a BHoM representation of material from Revit FamilyInstance representing a framing element.")]
[Input("familyInstance", "Revit FamilyInstance to be queried.")]
[Input("settings", "Revit adapter settings to be used while performing the query.")]
diff --git a/Revit_Core_Engine/Query/TransformToOriginInXY.cs b/Revit_Core_Engine/Query/TransformToOriginInXY.cs
new file mode 100644
index 000000000..4cdcc071b
--- /dev/null
+++ b/Revit_Core_Engine/Query/TransformToOriginInXY.cs
@@ -0,0 +1,73 @@
+/*
+ * This file is part of the Buildings and Habitats object Model (BHoM)
+ * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved.
+ *
+ * Each contributor holds copyright over their respective contributions.
+ * The project versioning (Git) records all such contribution source information.
+ *
+ *
+ * The BHoM is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation, either version 3.0 of the License, or
+ * (at your option) any later version.
+ *
+ * The BHoM is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this code. If not, see .
+ */
+
+using BH.Engine.Geometry;
+using BH.oM.Base.Attributes;
+using BH.oM.Geometry;
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+
+namespace BH.Revit.Engine.Core
+{
+ public static partial class Query
+ {
+ /***************************************************/
+ /**** Public methods ****/
+ /***************************************************/
+
+ [Description("Computes translation and rotation to orient a Polyline to the origin in XY.")]
+ [Input("outline", "Polyline to transform.")]
+ [Output("result", "Centroid projected to XY and rotation angle in radians around Z.")]
+ public static (Vector, double) TransformToOriginInXY(this Polyline outline)
+ {
+ Vector translation = null;
+ double rotation = double.NaN;
+
+ List pts = outline?.ControlPoints;
+ if (pts == null || pts.Count < 3)
+ return (translation, rotation);
+
+ Point centroid = outline.Centroid();
+ if (centroid == null)
+ return (translation, rotation);
+
+ translation = (new Point() - centroid);
+
+ // 1. Find dominant edge direction
+ // 2. Find longest edge parallel to the dominant direction
+ // 3. Rotate the longest edge to X axis and check if the start point is above or below the centroid to determine the rotation direction
+ Vector dominantEdge = outline.DominantEdgeDirection(Tolerance.Distance);
+ if (dominantEdge != null)
+ {
+ Line longestEdge = outline.SubParts().Where(x => x.Direction().IsParallel(dominantEdge, Tolerance.Angle) != 0).OrderByDescending(x => x.Length()).First();
+ rotation = dominantEdge.SignedAngle(Vector.XAxis, Vector.ZAxis);
+ Point orientedStart = longestEdge.Start.Rotate(centroid, Vector.ZAxis, rotation);
+ if (orientedStart.Y > centroid.Y)
+ rotation -= Math.PI;
+ }
+
+ return (translation, rotation);
+ }
+ }
+}
diff --git a/Revit_Core_Engine/Query/ViewBox.cs b/Revit_Core_Engine/Query/ViewBox.cs
index 64d82d3fe..64d875cf1 100644
--- a/Revit_Core_Engine/Query/ViewBox.cs
+++ b/Revit_Core_Engine/Query/ViewBox.cs
@@ -50,6 +50,20 @@ public static BoundingBoxXYZ ViewBoxElevation(this IEnumerable elements
/***************************************************/
+ [Description("Returns the combined bounding box containing given Revit solids, aligned with the right given direction, inflated by a given offset.")]
+ [Input("solids", "Solids to find the elevation box for.")]
+ [Input("direction", "Direction, along which the elevation is meant to be made.")]
+ [Input("sideOffset", "Offset value to inflate the elevation view in width and height, in metres.")]
+ [Input("frontOffset", "Offset distance between the solid front and the origin of the view, in metres. Use negative value to create section.")]
+ [Input("backOffset", "Offset distance between the solid back and the back edge of the view, in metres.")]
+ [Output("box", "Elevation box of the input Revit solids.")]
+ public static BoundingBoxXYZ ViewBoxElevation(this IEnumerable solids, XYZ direction, double sideOffset, double frontOffset, double backOffset)
+ {
+ return solids.ViewBoxSection(direction, XYZ.BasisZ, sideOffset, frontOffset, backOffset);
+ }
+
+ /***************************************************/
+
[Description("Returns the bounding box containing a given Revit element, aligned with the given right direction, inflated by a given offset.")]
[Input("element", "Element to find the elevation box for.")]
[Input("direction", "Direction, along which the elevation is meant to be made.")]
@@ -80,6 +94,33 @@ public static BoundingBoxXYZ ViewBoxSection(this IEnumerable elements,
return null;
}
+ Options options = new Options();
+ options.DetailLevel = Autodesk.Revit.DB.ViewDetailLevel.Fine;
+ options.ComputeReferences = false;
+ options.IncludeNonVisibleObjects = false;
+
+ IEnumerable solids = elements?.SelectMany(x => x.Solids(options));
+ return solids.ViewBoxSection(rightDirection, upDirection, sideOffset, frontOffset, backOffset);
+ }
+
+ /***************************************************/
+
+ [Description("Returns the combined bounding box containing given Revit solids, aligned with the given right and up direction, inflated by a given offset.")]
+ [Input("solids", "Solids to find the elevation box for.")]
+ [Input("rightDirection", "Direction, along which the elevation is meant to be made.")]
+ [Input("upDirection", "Direction pointing up in the created section. Needs to be perpendicular to upDirection.")]
+ [Input("sideOffset", "Offset value to inflate the elevation view in width and height, in metres.")]
+ [Input("frontOffset", "Offset distance between the solid front and the origin of the view, in metres. Use negative value to create section.")]
+ [Input("backOffset", "Offset distance between the solid back and the back edge of the view, in metres.")]
+ [Output("box", "Elevation box of the input Revit solids.")]
+ public static BoundingBoxXYZ ViewBoxSection(this IEnumerable solids, XYZ rightDirection, XYZ upDirection, double sideOffset, double frontOffset, double backOffset)
+ {
+ if (solids == null || !solids.Any())
+ {
+ BH.Engine.Base.Compute.RecordError("Could create view box for null Revit solids.");
+ return null;
+ }
+
if (upDirection == null || rightDirection == null)
{
BH.Engine.Base.Compute.RecordError("Could create view box with null direction.");
@@ -108,7 +149,7 @@ public static BoundingBoxXYZ ViewBoxSection(this IEnumerable elements,
Transform transform = orientationMatrix.ToRevit().TryFixIfNonConformal();
// Bounds of the element in the provided coordinate system
- BoundingBoxXYZ boundingBox = elements.PhysicalBounds(transform.Inverse);
+ BoundingBoxXYZ boundingBox = solids.Bounds(transform.Inverse);
boundingBox.Transform = transform;
// Front offset and depth
diff --git a/Revit_Core_Engine/Revit_Core_Engine.csproj b/Revit_Core_Engine/Revit_Core_Engine.csproj
index 27fa14b91..4514c7144 100644
--- a/Revit_Core_Engine/Revit_Core_Engine.csproj
+++ b/Revit_Core_Engine/Revit_Core_Engine.csproj
@@ -6,7 +6,7 @@
BHoM
Copyright © https://github.com/BHoM
BH.Revit.Engine.Core
- 9.1.0.0
+ 9.2.0.0
diff --git a/Revit_Engine/Revit_Engine.csproj b/Revit_Engine/Revit_Engine.csproj
index aa65d7b53..38eb18538 100644
--- a/Revit_Engine/Revit_Engine.csproj
+++ b/Revit_Engine/Revit_Engine.csproj
@@ -8,7 +8,7 @@
BHoM
Copyright © https://github.com/BHoM
BH.Engine.Revit
- 9.1.0.0
+ 9.2.0.0
diff --git a/Revit_oM/Revit_oM.csproj b/Revit_oM/Revit_oM.csproj
index 7bb535e3b..26344f79e 100644
--- a/Revit_oM/Revit_oM.csproj
+++ b/Revit_oM/Revit_oM.csproj
@@ -7,7 +7,7 @@
BHoM
Copyright © https://github.com/BHoM
BH.oM.Revit
- 9.1.0.0
+ 9.2.0.0
..\Build\