diff --git a/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs b/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs index 8c708ce62..0ecbad7b5 100644 --- a/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs +++ b/Revit_Core_Engine/Compute/GeneratePadFoundationType.cs @@ -165,7 +165,7 @@ private static FamilySymbol GenerateFreeformType(PadFoundation padFoundation, Do { 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); + family = GenerateFreeFormPadFamilyFromTemplate(document, orientedOutline, thickness, newIndex, settings); } if (family == null) @@ -177,7 +177,7 @@ private static FamilySymbol GenerateFreeformType(PadFoundation padFoundation, Do /***************************************************/ - private static Family GenerateFreeFormPadFamilyFromTemplate(this Document document, Polyline orientedOutline, double thickness, int index, PadFoundation padFoundation, RevitSettings settings = null) + private static Family GenerateFreeFormPadFamilyFromTemplate(this Document document, Polyline orientedOutline, double thickness, int index, RevitSettings settings = null) { string templateFamilyName = "StructuralFoundations_Pad-Freeform"; string templatePath = Directory.GetFiles(m_FamilyDirectory, $"*{templateFamilyName}.rfa").FirstOrDefault(); @@ -188,7 +188,7 @@ private static Family GenerateFreeFormPadFamilyFromTemplate(this Document docume try { - if (!ReplaceFreeFormExtrusion(familyDocument, orientedOutline, padFoundation)) + if (!ReplaceFreeFormExtrusion(familyDocument, orientedOutline, thickness)) return null; return SaveAndLoadFamily(document, familyDocument, $"{Path.GetFileNameWithoutExtension(templatePath)}_{index}"); @@ -205,17 +205,8 @@ private static Family GenerateFreeFormPadFamilyFromTemplate(this Document docume } /***************************************************/ - 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) + private static bool ReplaceFreeFormExtrusion(Document familyDocument, Polyline orientedOutline, double thickness) { try { @@ -226,7 +217,7 @@ private static bool ReplaceFreeFormExtrusion(Document familyDocument, Polyline o using (Transaction t = new Transaction(familyDocument, "Update Freeform Pad Foundation Footprint")) { t.Start(); - familyDocument.FamilyCreate.NewExtrusion(true, profile, extrusion.Sketch.SketchPlane, -FreeformExtrusionDepth(padFoundation)); + familyDocument.FamilyCreate.NewExtrusion(true, profile, extrusion.Sketch.SketchPlane, -FreeformExtrusionDepth(thickness)); familyDocument.Delete(extrusion.Id); t.Commit(); } diff --git a/Revit_Core_Engine/Compute/GeneratePileFoundationType.cs b/Revit_Core_Engine/Compute/GeneratePileFoundationType.cs new file mode 100644 index 000000000..7978ffd2b --- /dev/null +++ b/Revit_Core_Engine/Compute/GeneratePileFoundationType.cs @@ -0,0 +1,395 @@ +/* + * 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 BH.oM.Physical.FramingProperties; +using BH.oM.Spatial.Layouts; +using BH.oM.Spatial.ShapeProfiles; +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 PileFoundation by querying existing freeform families in the document or creating a new one from a template file. Falls back to a pad foundation type when the input has no piles.")] + [Input("pileFoundation", "BHoM pile foundation to generate the Revit type 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 input BHoM pile foundation.")] + public static FamilySymbol GeneratePileFoundationType(this PileFoundation pileFoundation, Document document, RevitSettings settings = null) + { + settings = settings.DefaultIfNull(); + + if (pileFoundation?.PileCap == null) + return null; + + if (pileFoundation.Piles == null || pileFoundation.Piles.Count == 0) + { + BH.Engine.Base.Compute.RecordWarning($"PileFoundation has no piles; generating a pad foundation type instead. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return pileFoundation.PileCap.GeneratePadFoundationType(document, settings); + } + + return GenerateFreeformType(pileFoundation, document, settings); + } + + /***************************************************/ + /**** Private methods ****/ + /***************************************************/ + + private static FamilySymbol GenerateFreeformType(PileFoundation pileFoundation, Document document, RevitSettings settings) + { + string prefix = "StructuralFoundations_PileFoundation-Freeform_"; + + // Get the outline and check if valid + Polyline outline = pileFoundation.PileCap.PadFoundationOutline(); + if (outline?.IIsClosed() != true) + { + BH.Engine.Base.Compute.RecordError($"Pile foundation outline is invalid. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return null; + } + + // Get the thickness and check if valid + double thickness = pileFoundation.PileCap.PadFoundationThickness(); + if (double.IsNaN(thickness)) + return null; + + double pileDepth = pileFoundation.PileFoundationDepth(settings); + if (double.IsNaN(pileDepth)) + { + BH.Engine.Base.Compute.RecordError($"Could not determine pile depth from cap soffit to pile tip. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return null; + } + + // Get circular pile profiles and check if same diameter + List profiles = pileFoundation.Piles.Select(x => (x.Property as ConstantFramingProperty)?.Profile).OfType().ToList(); + + if (profiles.Count != pileFoundation.Piles.Count) + { + BH.Engine.Base.Compute.RecordError($"All piles must have a circular profile. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return null; + } + + double tol = settings.DistanceTolerance; + double diameter = profiles[0].Diameter; + if (profiles.Any(p => Math.Abs(p.Diameter - diameter) > tol)) + { + BH.Engine.Base.Compute.RecordError($"All piles must have the same diameter. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return null; + } + + // Orient the outline to origin + Polyline orientedOutline = outline.OrientToOrigin(); + if (orientedOutline == null) + return null; + + // Get the pile layout and check if valid + ExplicitLayout layout = pileFoundation.PileFoundationLayout(settings); + if (layout?.Points == null || layout.Points.Count == 0) + { + BH.Engine.Base.Compute.RecordError($"Pile layout is invalid. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + 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(); + + Family family = freeformFamilies.FirstOrDefault(x => x.IsMatchingOutlineAndLayout(orientedOutline, layout, diameter, 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 = GenerateFreeFormPileFoundationFamilyFromTemplate(document, orientedOutline, layout, diameter, thickness, newIndex); + } + + if (family == null) + return null; + + return family.FindOrCreateTypeWithDimensions(thickness, diameter); + } + + /***************************************************/ + + private static Family GenerateFreeFormPileFoundationFamilyFromTemplate(this Document document, Polyline orientedOutline, ExplicitLayout layout, double diameter, double thickness, int index) + { + string templateFamilyName = "StructuralFoundations_PileFoundation-Freeform"; + string templatePath = Directory.GetFiles(m_FamilyDirectory, $"*{templateFamilyName}.rfa").FirstOrDefault(); + if (string.IsNullOrEmpty(templatePath)) + { + BH.Engine.Base.Compute.RecordError($"Pile foundation template '{templateFamilyName}.rfa' not found in {m_FamilyDirectory}."); + return null; + } + + Document familyDocument = new UIDocument(document).Application.Application.OpenDocumentFile(templatePath); + if (familyDocument == null) + return null; + + try + { + if (!ReplaceFreeFormExtrusionWithAssociate(familyDocument, orientedOutline, thickness)) + return null; + + if (!PlaceNestedPiles(familyDocument, layout, diameter)) + return null; + + return SaveAndLoadFamily(document, familyDocument, $"{Path.GetFileNameWithoutExtension(templatePath)}_{index}"); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Creation of a freeform Revit pile foundation failed with the following error: {ex.Message}"); + return null; + } + finally + { + familyDocument.Close(false); + } + } + + /***************************************************/ + + private static double FreeformExtrusionDepth(double thickness) + { + double h = double.IsNaN(thickness) ? double.NaN : thickness.FromSI(SpecTypeId.Length); + if (double.IsNaN(h) || h <= 1e-6) + h = 0.5.FromSI(SpecTypeId.Length); + return h; + } + + /***************************************************/ + + private static bool ReplaceFreeFormExtrusionWithAssociate(Document familyDocument, Polyline orientedOutline, double thickness) + { + try + { + if (!ReplaceFreeFormExtrusion(familyDocument, orientedOutline, thickness)) + return false; + + using (Transaction t = new Transaction(familyDocument, "Update Freeform Pile Foundation Footprint")) + { + Extrusion extrusion = new FilteredElementCollector(familyDocument).OfClass(typeof(Extrusion)).Cast().FirstOrDefault(); + if (extrusion == null) + return false; + + t.Start(); + + FamilyManager familyManager = familyDocument.FamilyManager; + Parameter oldStartParam = extrusion.get_Parameter(BuiltInParameter.EXTRUSION_START_PARAM); + FamilyParameter associatedStartParameter = oldStartParam != null + ? familyManager.GetAssociatedFamilyParameter(oldStartParam) + : null; + + Parameter endParam = extrusion.get_Parameter(BuiltInParameter.EXTRUSION_END_PARAM); + if (endParam != null && !endParam.IsReadOnly) + endParam.Set(0.0); + + if (associatedStartParameter != null) + { + Parameter newStartParam = extrusion.get_Parameter(BuiltInParameter.EXTRUSION_START_PARAM); + familyManager.AssociateElementParameterToFamilyParameter(newStartParam, associatedStartParameter); + } + + t.Commit(); + } + + return true; + } + catch + { + return false; + } + } + + /***************************************************/ + + private static bool PlaceNestedPiles(Document familyDocument, ExplicitLayout layout, double diameter) + { + List templatePiles = new FilteredElementCollector(familyDocument).OfClass(typeof(FamilyInstance)).Cast().ToList(); + if (templatePiles.Count == 0) + return false; + + FamilyInstance templatePile = templatePiles[0]; + XYZ pileOrigin = (templatePile.Location as LocationPoint)?.Point; + if (pileOrigin == null) + return false; + + Extrusion templateVoidExtrusion = new FilteredElementCollector(familyDocument).OfClass(typeof(Extrusion)).Cast().Where(e => !e.IsSolid).OrderBy(e => + { + BoundingBoxXYZ bbox = e.get_BoundingBox(null); + if (bbox == null) + return double.MaxValue; + XYZ c = (bbox.Min + bbox.Max) / 2.0; + return c.DistanceTo(new XYZ(pileOrigin.X, pileOrigin.Y, c.Z)); + }) + .FirstOrDefault(); + + try + { + using (Transaction t = new Transaction(familyDocument, "Place Nested Piles")) + { + t.Start(); + + FamilySymbol nestType = NestedPileSymbol(templatePile.Symbol, diameter); + if (nestType == null) + { + t.RollBack(); + return false; + } + + if (templatePiles.Count > 1) + familyDocument.Delete(templatePiles.Skip(1).Select(x => x.Id).ToList()); + + List placedPiles = new List(); + List placedVoids = new List(); + + foreach (oM.Geometry.Point pt in layout.Points.Where(p => p != null)) + { + double x = pt.X.FromSI(SpecTypeId.Length); + double y = pt.Y.FromSI(SpecTypeId.Length); + XYZ delta = new XYZ(x - pileOrigin.X, y - pileOrigin.Y, 0); + + foreach (ElementId id in ElementTransformUtils.CopyElement(familyDocument, templatePile.Id, delta)) + { + FamilyInstance copy = familyDocument.GetElement(id) as FamilyInstance; + if (copy != null) + placedPiles.Add(copy); + } + + if (templateVoidExtrusion != null) + { + foreach (ElementId id in ElementTransformUtils.CopyElement(familyDocument, templateVoidExtrusion.Id, delta)) + { + Extrusion copy = familyDocument.GetElement(id) as Extrusion; + if (copy != null) + placedVoids.Add(copy); + } + } + } + + familyDocument.Delete(templatePile.Id); + if (templateVoidExtrusion != null) + familyDocument.Delete(templateVoidExtrusion.Id); + + foreach (FamilyInstance pile in placedPiles) + { + if (pile.Symbol.Id != nestType.Id && pile.IsValidType(nestType.Id)) + pile.ChangeTypeId(nestType.Id); + } + + FamilyManager fm = familyDocument.FamilyManager; + FamilyParameter radiusParam = fm.get_Parameter("Radius"); + if (radiusParam != null) + fm.Set(radiusParam, (diameter / 2.0).FromSI(SpecTypeId.Length)); + + Extrusion cap = new FilteredElementCollector(familyDocument).OfClass(typeof(Extrusion)).Cast().FirstOrDefault(e => e.IsSolid); + if (cap != null) + { + foreach (Extrusion voidExtrusion in placedVoids) + { + try + { + if (SolidSolidCutUtils.CanElementCutElement(cap, voidExtrusion, out _)) + SolidSolidCutUtils.AddCutBetweenSolids(familyDocument, cap, voidExtrusion); + } + catch { } + } + } + + t.Commit(); + } + } + catch + { + return false; + } + + return true; + } + + /***************************************************/ + + private static FamilySymbol NestedPileSymbol(FamilySymbol pileSymbol, double diameter) + { + if (pileSymbol == null || diameter <= 0) + return pileSymbol; + + string typeName = $"Ø{(long)Math.Round(diameter * 1000.0)}"; + FamilySymbol nestSymbol = pileSymbol.Family.GetFamilySymbolIds().Select(id => pileSymbol.Document.GetElement(id) as FamilySymbol).FirstOrDefault(s => s != null && s.Name.Equals(typeName, StringComparison.OrdinalIgnoreCase)); + + if (nestSymbol == null) + nestSymbol = pileSymbol.Duplicate(typeName) as FamilySymbol; + + if (nestSymbol == null) + return null; + + nestSymbol.SetParameter("Radius", diameter / 2.0); + + if (!nestSymbol.IsActive) + nestSymbol.Activate(); + + return nestSymbol; + } + + /***************************************************/ + + private static FamilySymbol FindOrCreateTypeWithDimensions(this Family family, double thickness, double diameter) + { + 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 thk. Ø{(long)Math.Round(diameter * 1000.0)}"; + 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("Depth", thickness); + } + + result?.Activate(); + return result; + } + + /***************************************************/ + } +} diff --git a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs index 167e8595f..e699e07fd 100644 --- a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs +++ b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs @@ -273,6 +273,61 @@ public static FamilyInstance ToRevitFamilyInstance(this PadFoundation padFoundat /***************************************************/ + [Description("Converts BH.oM.Physical.Elements.PileFoundation to a Revit FamilyInstance.")] + [Input("pileFoundation", "BH.oM.Physical.Elements.PileFoundation 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.PileFoundation.")] + public static FamilyInstance ToRevitFamilyInstance(this PileFoundation pileFoundation, Document document, RevitSettings settings = null, Dictionary> refObjects = null) + { + if (pileFoundation == null || document == null) + return null; + + FamilyInstance familyInstance = refObjects.GetValue(document, pileFoundation.BHoM_Guid); + if (familyInstance != null) + return familyInstance; + + settings = settings.DefaultIfNull(); + + if (pileFoundation.PileCap == null) + { + BH.Engine.Base.Compute.RecordError($"PileFoundation has no PileCap. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return null; + } + + FamilySymbol familySymbol = pileFoundation.ElementType(document, settings) as FamilySymbol; + if (familySymbol == null) + { + Compute.ElementTypeNotFoundWarning(pileFoundation); + return null; + } + + BH.oM.Geometry.Point origin = pileFoundation.PileCap.PadFoundationCentroid(); + + Level level = document.LevelAbove(origin.Z, settings); + + familyInstance = document.Create.NewFamilyInstance(origin.ToRevit(), familySymbol, level, StructuralType.Footing); + document.Regenerate(); + + double pileDepth = pileFoundation.PileFoundationDepth(settings); + if (!double.IsNaN(pileDepth)) + familyInstance.SetParameter("Pile Depth", pileDepth); + + familyInstance.SetLocation(pileFoundation, settings); + refObjects.AddOrReplace(pileFoundation, familyInstance); + + if (pileFoundation.Piles != null) + { + foreach (Pile pile in pileFoundation.Piles) + refObjects.AddOrReplace(pile, familyInstance); + } + + return familyInstance; + } + + /***************************************************/ + [Description("Converts BH.oM.Physical.Elements.IFramingElement to a Revit FamilyInstance.")] [Input("framingElement", "BH.oM.Physical.Elements.IFramingElement to be converted.")] [Input("document", "Revit document, in which the output of the convert will be created.")] diff --git a/Revit_Core_Engine/Convert/ToRevit.cs b/Revit_Core_Engine/Convert/ToRevit.cs index d46d343b5..6d2821e9b 100644 --- a/Revit_Core_Engine/Convert/ToRevit.cs +++ b/Revit_Core_Engine/Convert/ToRevit.cs @@ -286,6 +286,19 @@ public static Element ToRevit(this BH.oM.Physical.Elements.PadFoundation foundat /***************************************************/ + [Description("Converts BH.oM.Physical.Elements.PileFoundation to a Revit FamilyInstance.")] + [Input("foundation", "BH.oM.Physical.Elements.PileFoundation 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.PileFoundation.")] + public static Element ToRevit(this BH.oM.Physical.Elements.PileFoundation 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_PileFoundation-Freeform.rfa b/Revit_Core_Engine/Files/Families/StructuralFoundations_PileFoundation-Freeform.rfa new file mode 100644 index 000000000..8f06370fc Binary files /dev/null and b/Revit_Core_Engine/Files/Families/StructuralFoundations_PileFoundation-Freeform.rfa differ diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index ffa2f7af1..87c9c1473 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -345,6 +345,20 @@ public static bool SetLocation(this FamilyInstance element, Pile pile, RevitSett /***************************************************/ + [Description("Sets the location of a given Revit FamilyInstance based on a given BHoM PileFoundation.")] + [Input("element", "Revit FamilyInstance to be modified.")] + [Input("pileFoundation", "BHoM PileFoundation 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, PileFoundation pileFoundation, RevitSettings settings) + { + if (element == null || pileFoundation?.PileCap == null) + return false; + return element.SetLocation(pileFoundation.PileCap, settings); + } + + /***************************************************/ + [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.")] @@ -939,7 +953,7 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin return success; } - + /***************************************************/ private static bool UpdateRotationOfVerticalElement(this FamilyInstance element, IFramingElement bhomElement, RevitSettings settings) { diff --git a/Revit_Core_Engine/Modify/Update.cs b/Revit_Core_Engine/Modify/Update.cs index 04b1709fe..cb99a2718 100644 --- a/Revit_Core_Engine/Modify/Update.cs +++ b/Revit_Core_Engine/Modify/Update.cs @@ -173,6 +173,27 @@ public static bool Update(this FamilyInstance element, PadFoundation bHoMObject, /***************************************************/ + [Description("Updates a Revit pile foundation FamilyInstance from the BHoM PileFoundation using the standard element update, then sets instance Pile Depth.")] + [Input("element", "Revit FamilyInstance representing the pile foundation to update.")] + [Input("bHoMObject", "BHoM PileFoundation 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.")] + public static bool Update(this FamilyInstance element, PileFoundation bHoMObject, RevitSettings settings, bool setLocationOnUpdate) + { + settings = settings.DefaultIfNull(); + + bool result = ((Element)element).Update((IBHoMObject)bHoMObject, settings, setLocationOnUpdate); + + double pileDepth = bHoMObject.PileFoundationDepth(settings); + if (!double.IsNaN(pileDepth)) + element.SetParameter("Pile Depth", pileDepth); + + 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/Query/ElementType.cs b/Revit_Core_Engine/Query/ElementType.cs index 48be1da8b..12533b2d3 100644 --- a/Revit_Core_Engine/Query/ElementType.cs +++ b/Revit_Core_Engine/Query/ElementType.cs @@ -99,6 +99,27 @@ public static FamilySymbol ElementType(this BH.oM.Physical.Elements.PadFoundatio /***************************************************/ + [Description("Returns the Revit FamilySymbol to use when converting a BHoM PileFoundation.")] + [Input("pileFoundation", "BHoM pile 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 pile foundation to Revit.")] + public static FamilySymbol ElementType(this BH.oM.Physical.Elements.PileFoundation pileFoundation, Document document, RevitSettings settings = null) + { + FamilySymbol result = pileFoundation.GeneratePileFoundationType(document, settings); + pileFoundation.FamilyAndTypeNames(out string familyName, out string familyTypeName); + + if (!string.IsNullOrWhiteSpace(familyName) && result.FamilyName != familyName) + BH.Engine.Base.Compute.RecordWarning($"BHoM PileFoundation's name does not match Revit family name derived from its geometry. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + + if (!string.IsNullOrWhiteSpace(familyTypeName) && result.Name != familyTypeName) + BH.Engine.Base.Compute.RecordWarning($"BHoM PileFoundation's name does not match Revit type name derived from its geometry. BHoM_Guid: {pileFoundation.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/IsMatchingOutlineAndLayout.cs b/Revit_Core_Engine/Query/IsMatchingOutlineAndLayout.cs new file mode 100644 index 000000000..221778669 --- /dev/null +++ b/Revit_Core_Engine/Query/IsMatchingOutlineAndLayout.cs @@ -0,0 +1,145 @@ +/* + * 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.Adapters.Revit; +using BH.Engine.Geometry; +using BH.oM.Adapters.Revit.Settings; +using BH.oM.Base.Attributes; +using BH.oM.Geometry; +using BH.oM.Spatial.Layouts; +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("Checks whether a pile foundation family matches the pile-cap outline, pile layout and pile diameter.")] + [Input("family", "Revit pile foundation family to compare.")] + [Input("orientedOutline", "Oriented pile-cap outline.")] + [Input("layout", "Explicit pile layout points.")] + [Input("diameter", "Pile diameter to match against the family.")] + [Input("settings", "Revit adapter settings.")] + [Output("matches", "True if outline, layout and diameter all match.")] + public static bool IsMatchingOutlineAndLayout(this Family family, Polyline orientedOutline, ExplicitLayout layout, double diameter, RevitSettings settings) + { + if (family == null || orientedOutline == null || layout?.Points == null || diameter <= 0) + return false; + + settings = settings.DefaultIfNull(); + double tol = settings.DistanceTolerance; + + if (!family.IsMatchingOutline(orientedOutline, settings)) + return false; + + if (!family.IsMatchingPileLayout(layout, tol)) + return false; + + if (!family.IsMatchingPileDiameter(diameter, tol)) + return false; + + return true; + } + + /***************************************************/ + /**** Private methods ****/ + /***************************************************/ + + private static bool IsMatchingPileLayout(this Family family, ExplicitLayout layout, double tol) + { + List familyPilePoints = family.PileLayoutPoints(); + if (familyPilePoints == null || familyPilePoints.Count != layout.Points.Count) + return false; + + foreach (oM.Geometry.Point layoutPt in layout.Points) + { + oM.Geometry.Point xy = new oM.Geometry.Point { X = layoutPt.X, Y = layoutPt.Y, Z = 0 }; + if (!familyPilePoints.Any(fp => fp.Distance(xy) <= tol)) + return false; + } + + return true; + } + + /***************************************************/ + + private static bool IsMatchingPileDiameter(this Family family, double diameter, double tol) + { + Document doc = family.Document; + FamilyInstance host = new FilteredElementCollector(doc).OfClass(typeof(FamilyInstance)).Cast().FirstOrDefault(fi => fi.Symbol?.Family?.Id == family.Id); + if (host == null) + return false; + FamilyInstance nest = host.GetSubComponentIds().Select(id => doc.GetElement(id) as FamilyInstance).FirstOrDefault(fi => fi != null); + FamilySymbol nestSymbol = nest?.Symbol; + if (nestSymbol == null) + return false; + double radius = nestSymbol.LookupParameterDouble("Radius"); + if (!double.IsNaN(radius)) + return Math.Abs(radius * 2.0 - diameter) <= tol; + double nestDiameter = nestSymbol.LookupParameterDouble("Diameter"); + if (!double.IsNaN(nestDiameter)) + return Math.Abs(nestDiameter - diameter) <= tol; + return false; + } + + /***************************************************/ + + private static List PileLayoutPoints(this Family family) + { + Document doc = family.Document; + + FamilyInstance host = new FilteredElementCollector(doc) + .OfClass(typeof(FamilyInstance)) + .Cast() + .FirstOrDefault(fi => fi.Symbol?.Family?.Id == family.Id); + + if (host == null) + return null; + + Transform toFamily = host.GetTotalTransform().Inverse; + List points = new List(); + + foreach (ElementId id in host.GetSubComponentIds()) + { + FamilyInstance nest = doc.GetElement(id) as FamilyInstance; + XYZ projectPt = (nest?.Location as LocationPoint)?.Point; + if (projectPt == null) + continue; + + XYZ local = toFamily.OfPoint(projectPt); + oM.Geometry.Point p = local.PointFromRevit(); + points.Add(new oM.Geometry.Point { X = p.X, Y = p.Y, Z = 0 }); + } + + return points.Count > 0 ? points : null; + } + + /***************************************************/ + } +} diff --git a/Revit_Core_Engine/Query/PileFoundation.cs b/Revit_Core_Engine/Query/PileFoundation.cs new file mode 100644 index 000000000..21ddd2da6 --- /dev/null +++ b/Revit_Core_Engine/Query/PileFoundation.cs @@ -0,0 +1,134 @@ +/* + * 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.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 BH.oM.Spatial.Layouts; +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("Builds an ExplicitLayout of pile positions in the oriented cap coordinate system.")] + [Input("pileFoundation", "BHoM pile foundation to extract pile positions from.")] + [Input("settings", "Revit adapter settings.")] + [Output("layout", "Explicit layout of pile XY positions in the oriented cap coordinate system.")] + public static ExplicitLayout PileFoundationLayout(this PileFoundation pileFoundation, RevitSettings settings = null) + { + settings = settings.DefaultIfNull(); + + Polyline outline = pileFoundation.PileCap.PadFoundationOutline(); + (Vector translation, double rotation) = outline.TransformToOriginInXY(); + if (translation == null || double.IsNaN(rotation)) + return null; + + List points = new List(); + foreach (Pile pile in pileFoundation.Piles) + { + Line line = pile.Location as Line; + if (line == null) + return null; + + Point basePt = line.Start.Z < line.End.Z ? line.Start : line.End; + Point local = basePt.Translate(translation).Rotate(new Point(), Vector.ZAxis, rotation); + points.Add(new Point { X = local.X, Y = local.Y, Z = 0 }); + } + + return new ExplicitLayout(points); + } + + /***************************************************/ + + [Description("Gets the pile embedment depth below the pile cap soffit. GH pile lines are typically snapped to the top of the cap; Revit Pile Depth is measured from the bottom of the cap.")] + [Input("pileFoundation", "BHoM pile foundation to extract pile depth from.")] + [Input("settings", "Revit adapter settings (distance tolerance for the top-vs-soffit check).")] + [Output("pileDepth", "Depth from cap soffit to pile tip in SI units, or NaN if invalid.")] + public static double PileFoundationDepth(this PileFoundation pileFoundation, RevitSettings settings = null) + { + settings = settings.DefaultIfNull(); + + if (pileFoundation?.Piles == null || pileFoundation.Piles.Count == 0 || pileFoundation.PileCap == null) + return double.NaN; + + double thickness = pileFoundation.PileCap.PadFoundationThickness(); + if (double.IsNaN(thickness)) + return double.NaN; + + Point centroid = pileFoundation.PileCap.PadFoundationCentroid(); + if (centroid == null) + return double.NaN; + + double capTop = centroid.Z; + double capBottom = capTop - thickness; + + List pileLines = pileFoundation.Piles.Select(p => p.Location as Line).Where(l => l != null).ToList(); + + if (pileLines.Count == 0) + return double.NaN; + + // Get pile location lines and check if vertical and same top and bottom Z + if (pileLines.Count != pileFoundation.Piles.Count) + { + BH.Engine.Base.Compute.RecordError($"All piles must have a Line location. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return double.NaN; + } + + foreach (BH.oM.Geometry.Line line in pileLines) + { + if (1 - Math.Abs(line.Direction().DotProduct(Vector.ZAxis)) > settings.AngleTolerance) + { + BH.Engine.Base.Compute.RecordError($"Only vertical piles are supported. BHoM_Guid: {pileFoundation.BHoM_Guid}"); + return double.NaN; + } + } + + double tol = settings.DistanceTolerance; + double pileTop = pileLines.Max(l => Math.Max(l.Start.Z, l.End.Z)); + double pileBottom = pileLines.Min(l => Math.Min(l.Start.Z, l.End.Z)); + + if (Math.Abs(pileTop - capBottom) > tol) + { + BH.Engine.Base.Compute.RecordWarning($"Pile top is not at the bottom of the pile cap. " + $"Pile Depth was set from the bottom of the cap to the bottom of the pile. " + $"BHoM_Guid: {pileFoundation.BHoM_Guid}"); + } + + double pileDepth = capBottom - pileBottom; + if (pileDepth <= 0) + return double.NaN; + + return pileDepth; + } + + /***************************************************/ + } +}