diff --git a/Revit_Core_Engine/Compute/GeneratePadFoundation.cs b/Revit_Core_Engine/Compute/GeneratePadFoundation.cs new file mode 100644 index 000000000..182e78247 --- /dev/null +++ b/Revit_Core_Engine/Compute/GeneratePadFoundation.cs @@ -0,0 +1,223 @@ +/* + * 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.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; + +namespace BH.Revit.Engine.Core +{ + public static partial class Compute + { + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + [Description("Generates a Revit family type (and its parent family, if not loaded yet) that represents the profile of a given BHoM pad foundation." + + "\nThe profile is created based on the template families stored in C:\\ProgramData\\Resources\\Revit.")] + [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 profile of the input BHoM pad foundation.")] + public static FamilySymbol GeneratePadFoundation(this PadFoundation padFoundation, Document document, RevitSettings settings = null) + { + if (padFoundation == null) + { + BH.Engine.Base.Compute.RecordError($"The BHoM pad foundation is null. BHoM_Guid: {padFoundation?.BHoM_Guid}"); + return null; + } + + string familyName = padFoundation.PadFoundationFamilyName(); + if (string.IsNullOrWhiteSpace(familyName)) + { + BH.Engine.Base.Compute.RecordError($"Creation of a Revit pad foundation family failed because the BHoM pad foundation does not have a name. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return null; + } + + settings = settings.DefaultIfNull(); + + 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 == padFoundation.Name); + if (result == null && symbols.Count != 0) + { + result = symbols[0].Duplicate(padFoundation.Name) as FamilySymbol; + result.Activate(); + padFoundation.ICopyFoundationDimensions(result, settings); + } + + return result; + } + else + { + family = document.GenerateFamilyFromTemplate(padFoundation, familyName, settings); + if (family == null) + { + family = document.GenerateFreeformPadFoundation(padFoundation, familyName, settings); + if (family == null) + return null; + } + + FamilySymbol result = document.GetElement(family.GetFamilySymbolIds().FirstOrDefault()) as FamilySymbol; + if (result == null) + { + BH.Engine.Base.Compute.RecordWarning($"Generation of a Revit family representing the BHoM pad foundation failed due to an internal error. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return null; + } + + result.Activate(); + result.Name = padFoundation.PadFoundationTypeName() + " " + padFoundation.PadFoundationFamilyName(); + return result; + } + } + + + /***************************************************/ + /**** Private methods ****/ + /***************************************************/ + + private static Family GenerateFamilyFromTemplate(this Document document, PadFoundation padFoundation, string familyName, RevitSettings settings = null) + { + string templateFamilyName = padFoundation.PadFoundationFamilyName(); + if (string.IsNullOrWhiteSpace(templateFamilyName)) + return null; + + string path = Path.Combine(m_FamilyDirectory, $"{templateFamilyName}.rfa"); + + Family result = null; + UIDocument uidoc = new UIDocument(document); + Document familyDocument = uidoc.Application.Application.OpenDocumentFile(path); + + try + { + result = SaveAndLoadFamily(document, familyDocument, familyName); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Creation of a freeform Revit profile geometry failed with the following error: {ex.Message}"); + } + finally + { + familyDocument.Close(false); + } + + if (result != null) + { + FamilySymbol symbol = document.GetElement(result?.GetFamilySymbolIds().FirstOrDefault()) as FamilySymbol; + if (symbol != null) + padFoundation.ICopyFoundationDimensions(symbol, settings); + } + + return result; + } + + /***************************************************/ + + private static Family GenerateFreeformPadFoundation(this Document document, PadFoundation padFoundation, string familyName, RevitSettings settings = null) + { + throw new NotImplementedException("GenerateFreeformPadFoundation is not implemented."); + } + + /**************************************************/ + + + private static string PadFoundationFamilyName(this PadFoundation padFoundation) + { + string name = padFoundation?.Name; + if (string.IsNullOrWhiteSpace(name)) + return null; + + if (name.Contains(':')) + return name.Split(':')[0].Trim(); + else + { + Regex pattern = new Regex(@"\d([\d\.\/\-xX ])*\d"); + return $"BHE_StructuralFoundations_{pattern.Replace(name, "").Replace(" ", " ").Trim()}"; + } + } + + /***************************************************/ + + private static string PadFoundationTypeName(this PadFoundation padFoundation) + { + string name = padFoundation?.Name; + if (string.IsNullOrWhiteSpace(name)) + return null; + + if (name.Contains(':')) + return name.Split(':')[1].Trim(); + else + return name; + } + + /***************************************************/ + + private static void ICopyFoundationDimensions(this PadFoundation padFoundation, FamilySymbol targetSymbol, RevitSettings settings = null) + { + CopyFoundationDimensions(padFoundation as dynamic, targetSymbol, settings); + } + + /***************************************************/ + + private static void CopyFoundationDimensions(this PadFoundation padFoundation, FamilySymbol targetSymbol, RevitSettings settings = null) + { + Polyline outline = padFoundation.ExtractBoundary(); + if (outline == null) + { + BH.Engine.Base.Compute.RecordError($"PadFoundation outline extraction failed. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return; + } + + var (width, length) = outline.GetRectangleDimensions(); + double depth = padFoundation.GetThicknessFromConstr(); + + Parameter widthParam = targetSymbol.LookupParameter("BHE_Width"); + if (widthParam != null) + widthParam.Set(width); + + Parameter lengthParam = targetSymbol.LookupParameter("BHE_Length"); + if (lengthParam != null) + lengthParam.Set(length); + + Parameter depthParam = targetSymbol.LookupParameter("BHE_Depth"); + if (depthParam != null) + depthParam.Set(depth); + } + + /***************************************************/ + } +} + + + diff --git a/Revit_Core_Engine/Compute/GenerateProfile.cs b/Revit_Core_Engine/Compute/GenerateProfile.cs index d9948f03d..38d623b90 100644 --- a/Revit_Core_Engine/Compute/GenerateProfile.cs +++ b/Revit_Core_Engine/Compute/GenerateProfile.cs @@ -26,6 +26,7 @@ using BH.Engine.Geometry; using BH.Engine.Spatial; using BH.oM.Adapters.Revit.Settings; +using BH.oM.Base; using BH.oM.Base.Attributes; using BH.oM.Physical.Elements; using BH.oM.Physical.FramingProperties; @@ -116,7 +117,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 +130,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) { @@ -174,7 +175,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) { @@ -273,7 +274,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) { diff --git a/Revit_Core_Engine/Convert/Physical/FromRevit/PadFoundation.cs b/Revit_Core_Engine/Convert/Physical/FromRevit/PadFoundation.cs new file mode 100644 index 000000000..7ae9caa1b --- /dev/null +++ b/Revit_Core_Engine/Convert/Physical/FromRevit/PadFoundation.cs @@ -0,0 +1,77 @@ +/* + * 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.oM.Adapters.Revit.Settings; +using BH.oM.Base; +using BH.oM.Physical.Elements; +using BH.oM.Physical.FramingProperties; +using BH.oM.Base.Attributes; +using System.Collections.Generic; +using System.ComponentModel; + +namespace BH.Revit.Engine.Core +{ + public static partial class Convert + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Converts a Revit FamilyInstance to BH.oM.Physical.Elements..")] + [Input("familyInstance", "Revit FamilyInstance to be converted.")] + [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("padFoundation", "BH.oM.Physical.Elements.Pile resulting from converting the input Revit FamilyInstance.")] + public static PadFoundation PadFoundationFromRevit(this FamilyInstance familyInstance, RevitSettings settings = null, Dictionary> refObjects = null) + { + settings = settings.DefaultIfNull(); + + PadFoundation padFoundation = refObjects.GetValue(familyInstance.Id); + if (padFoundation != null) + return padFoundation; + + oM.Geometry.ICurve locationCurve = familyInstance.LocationCurvePadFoundation(settings); + if (locationCurve == null) + { + BH.Engine.Base.Compute.RecordError($"Failed to extract geometry from pad foundation. ElementId: {familyInstance.Id.Value()}"); + return null; + } + + BoundingBoxXYZ bbox = familyInstance.get_BoundingBox(null); + double depth = bbox.Max.Z - bbox.Min.Z; + + padFoundation = BH.Engine.Physical.Create.PadFoundation((oM.Geometry.PlanarSurface)locationCurve, null, familyInstance.FamilyTypeFullName()); + + //Set identifiers, parameters & custom data + padFoundation.SetIdentifiers(familyInstance); + padFoundation.CopyParameters(familyInstance, settings.MappingSettings); + padFoundation.SetProperties(familyInstance, settings.MappingSettings); + + refObjects.AddOrReplace(familyInstance.Id, padFoundation); + return padFoundation; + } + + /***************************************************/ + } +} diff --git a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs index e876bdb4f..13304512e 100644 --- a/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs +++ b/Revit_Core_Engine/Convert/Physical/ToRevit/FamilyInstance.cs @@ -223,6 +223,57 @@ public static FamilyInstance ToRevitFamilyInstance(this Pile framingElement, Doc /***************************************************/ + [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(); + + XYZ origin = padFoundation.GetFoundationOrigin(); + if (origin == null) + { + BH.Engine.Base.Compute.RecordError($"PadFoundation boundary extraction failed or foundation is not rectangular. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return null; + } + + Level level = document.LevelAbove(origin.Z, settings); + if (level == null) + return null; + + FamilySymbol familySymbol = padFoundation.ElementType(document, settings); + if (familySymbol == null) + { + Compute.ElementTypeNotFoundWarning(padFoundation); + return null; + } + + familyInstance = document.Create.NewFamilyInstance(origin, familySymbol, level, StructuralType.Footing); + document.Regenerate(); + + familyInstance.CheckIfNullPush(padFoundation); + if (familyInstance == null) + return null; + + 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.")] [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 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/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index 1fa10dc84..82b16824d 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -22,12 +22,14 @@ using Autodesk.Revit.DB; using BH.Engine.Geometry; +using BH.Engine.Spatial; using BH.oM.Adapters.Revit.Elements; using BH.oM.Adapters.Revit.Settings; using BH.oM.Base; using BH.oM.Base.Attributes; using BH.oM.Environment.Elements; using BH.oM.Geometry; +using BH.oM.Geometry.CoordinateSystem; using BH.oM.Physical.Elements; using BH.oM.Physical.FramingProperties; using System; @@ -341,6 +343,133 @@ public static bool SetLocation(this FamilyInstance element, Pile pile, RevitSett return updated; } + /***************************************************/ + + //TO DO + //Teset script with rectangles (create & update) for push & pull + public static bool SetLocation(this FamilyInstance element, PadFoundation padFoundation, RevitSettings settings) + { + if (element == null || padFoundation == null) + return false; + + //CS + oM.Geometry.Point centerPoint = padFoundation.Centroid(); + if (centerPoint == null) + { + BH.Engine.Base.Compute.RecordError($"Failed to calculate centroid for PadFoundation. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return false; + } + + Cartesian coordinateLocalSystem = null; + try + { + Polyline boundary = padFoundation.ExtractBoundary(); + if (boundary != null) + { + coordinateLocalSystem = boundary.LocalCs(); + } + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Failed to create local coordinate system for PadFoundation. BHoM_Guid: {padFoundation.BHoM_Guid}"); + } + + if (coordinateLocalSystem == null || coordinateLocalSystem.Origin == null) + { + BH.Engine.Base.Compute.RecordWarning($"CoordinateSystem is invalid. BHoM_Guid: {padFoundation.BHoM_Guid}"); + return false; + } + + // 1) Compare padFoundation vs element + LocationPoint elementLocation = element.Location as LocationPoint; + if (elementLocation == null) + { + BH.Engine.Base.Compute.RecordError($"Element does not have a valid LocationPoint. ElementId: {element.Id.Value()}"); + return false; + } + + XYZ currentLocation = elementLocation.Point; + XYZ targetLocation = coordinateLocalSystem.Origin.ToRevit(); + + XYZ translation = targetLocation.Subtract(currentLocation); + if (translation.GetLength() > settings.DistanceTolerance) + { + using (Transaction transaction = new Transaction(element.Document, "Move Element")) + { + transaction.Start(); + ElementTransformUtils.MoveElement(element.Document, element.Id, translation); + transaction.Commit(); + } + } + + // 3) ElementTransformUtils.RotateElement(); + double rotationAngleNew = coordinateLocalSystem.ComputeRotationAngle(); + double rotationAngleOld = element.CoordinateSystem().ComputeRotationAngle(); + if (Math.Abs(rotationAngleNew) > settings.AngleTolerance) + { + using (Transaction transaction = new Transaction(element.Document, "Rotate Element")) + { + transaction.Start(); + Autodesk.Revit.DB.Line rotationAxis = Autodesk.Revit.DB.Line.CreateBound( + targetLocation, targetLocation.Add(XYZ.BasisZ)); // BH.Engine.Geometry.Create.Line() - check if it can be used instead + + ElementTransformUtils.RotateElement(element.Document, element.Id, rotationAxis, rotationAngleNew); + transaction.Commit(); + } + } + + // 4) Handle reference level, offset + Level level = element.Document.GetElement(element.LevelId) as Level; + if (level != null) + { + double levelElevation = level.ProjectElevation.ToSI(SpecTypeId.Length); + double targetZ = coordinateLocalSystem.Origin.Z; + double offset = targetZ - levelElevation; + + Parameter baseOffsetParam = element.get_Parameter(BuiltInParameter.FAMILY_BASE_LEVEL_OFFSET_PARAM); + if (baseOffsetParam != null && !baseOffsetParam.IsReadOnly) + { + baseOffsetParam.Set(offset.FromSI(SpecTypeId.Length)); + } + } + + return true; + } + + //check + private static Cartesian LocalCs(this Polyline outline) + { + if (outline == null) + return null; + + var subParts = outline.SubParts(); + if (subParts == null) + return null; + + Dictionary lenByDir = new Dictionary(); + + foreach (var ln in subParts) + { + var len = ln.Length(); + var dir = ln.Direction(); + + var key = lenByDir.Keys.FirstOrDefault(l => Math.Abs(Math.Abs(l.DotProduct(dir)) - 1) <= Tolerance.Angle); + + if (key != null) + lenByDir[key] += len; + else + lenByDir[dir] = len; + } + + if (lenByDir.Count == 0) + return null; + + var x = lenByDir.OrderByDescending(kvp => kvp.Value).First().Key; + var y = new Vector { X = -x.Y, Y = x.X }; + + return BH.Engine.Geometry.Create.CartesianCoordinateSystem(outline.Centroid(), x, y); + } + /***************************************************/ [Description("Sets the location of a given Revit Space based on a given BHoM Space.")] diff --git a/Revit_Core_Engine/Query/BHoMType.cs b/Revit_Core_Engine/Query/BHoMType.cs index 9f28b534e..c42b3298e 100644 --- a/Revit_Core_Engine/Query/BHoMType.cs +++ b/Revit_Core_Engine/Query/BHoMType.cs @@ -96,8 +96,7 @@ public static Type BHoMType(this FamilyInstance familyInstance, Discipline disci double width = Math.Max(bbox.Max.X - bbox.Min.X, bbox.Max.Y - bbox.Min.Y); double height = bbox.Max.Z - bbox.Min.Z; if (height < width) - //return typeof(BH.oM.Physical.Elements.PadFoundation); - return null; // Temporarily disabled until PadFoundation is properly implemented in the Revit adapter. + return typeof(BH.oM.Physical.Elements.PadFoundation); else if (familyInstance.GetSubComponentIds().Count != 0) //return typeof(BH.oM.Physical.Elements.PileFoundation); return null; // Temporarily disabled until PileFoundation is properly implemented in the Revit adapter. diff --git a/Revit_Core_Engine/Query/BuiltInCategories.cs b/Revit_Core_Engine/Query/BuiltInCategories.cs index 53f726c67..8386e7406 100644 --- a/Revit_Core_Engine/Query/BuiltInCategories.cs +++ b/Revit_Core_Engine/Query/BuiltInCategories.cs @@ -118,6 +118,12 @@ public static HashSet BuiltInCategories(this Type bHoMType) Autodesk.Revit.DB.BuiltInCategory.OST_StructuralFoundation } }, + { + typeof (BH.oM.Physical.Elements.PadFoundation), new BuiltInCategory[] + { + Autodesk.Revit.DB.BuiltInCategory.OST_StructuralFoundation, + } + }, { typeof (Column), new BuiltInCategory[] { diff --git a/Revit_Core_Engine/Query/ElementType.cs b/Revit_Core_Engine/Query/ElementType.cs index 636da66b8..6baade4a0 100644 --- a/Revit_Core_Engine/Query/ElementType.cs +++ b/Revit_Core_Engine/Query/ElementType.cs @@ -78,6 +78,25 @@ public static FamilySymbol ElementType(this BH.oM.Physical.Elements.IFramingElem /***************************************************/ + [Description("Returns the Revit element type to be used when converting a given BHoM pad foundation to Revit.")] + [Input("padFoundation", "BHoM pad foundation to find a correspondent Revit element type for.")] + [Input("document", "Revit document to parse in search for the element type.")] + [Input("settings", "Revit adapter settings to be used while performing the query.")] + [Output("padFoundationType", "Revit element type to be used when converting the input BHoM pad foundation to Revit.")] + public static FamilySymbol ElementType(this BH.oM.Physical.Elements.PadFoundation padFoundation, Document document, RevitSettings settings = null) + { + HashSet categories = padFoundation.BuiltInCategories(); + + FamilySymbol result = padFoundation.ElementTypeInclProperty(null, document, categories, settings) as FamilySymbol; + + if (result == null) + result = padFoundation.GeneratePadFoundation(document, settings); + + 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/FoundationGeometry.cs b/Revit_Core_Engine/Query/FoundationGeometry.cs new file mode 100644 index 000000000..b7a64b865 --- /dev/null +++ b/Revit_Core_Engine/Query/FoundationGeometry.cs @@ -0,0 +1,273 @@ +/* + * 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.Geometry; +using BH.oM.Geometry.CoordinateSystem; +using BH.oM.Physical.Elements; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace BH.Revit.Engine.Core +{ + public static partial class Query + { + + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + public static Polyline ExtractBoundary(this PadFoundation element) + { + if (element.Location == null) + return null; + + if (element.Location.InternalBoundaries.Count != 0) + { + BH.Engine.Base.Compute.RecordError("InternalBoundaries error"); + } + + List segments = element.Location.ExternalBoundary.ISubParts().ToList(); + if (segments.Any(x => !(x is BH.oM.Geometry.Line))) + { + BH.Engine.Base.Compute.RecordError(""); + } + + return BH.Engine.Geometry.Create.Polyline(segments.Cast().ToList()); + } + + /***************************************************/ + + public static bool IsRectangular(Polyline polyline) + { + if (polyline == null || polyline.ControlPoints == null) + return false; + + var points = polyline.ControlPoints; + int pointCount = points.Count; + + //Reecrtangle check closing point + if (pointCount != 5) + return false; + + List edges = new List(); + for (int i = 0; i < 4; i++) + { + edges.Add(new BH.oM.Geometry.Line { Start = points[i], End = points[(i + 1) % pointCount] }); + } + + double[] lengths = new double[4]; + for (int i = 0; i < 4; i++) + lengths[i] = edges[i].Length(); + + double tolerance = 0.001; + if (Math.Abs(lengths[0] - lengths[2]) > tolerance || Math.Abs(lengths[1] - lengths[3]) > tolerance) + return false; + + BH.oM.Geometry.Point p1 = edges[0].Start; + BH.oM.Geometry.Point p2 = edges[0].End; + BH.oM.Geometry.Point p3 = edges[1].End; + BH.oM.Geometry.Point p4 = edges[2].End; + + var d1 = (p1 - p3).Length(); + var d2 = (p2 - p4).Length(); + + return Math.Abs(d1 - d2) <= tolerance; + } + + /***************************************************/ + + public static double ComputeRotationAngle(this Cartesian localCS) + { + if (localCS == null) + return 0.0; + + Basis basis = (Basis)localCS; + + var gx = BH.oM.Geometry.Vector.XAxis; // Global X Axis + var lx = basis.X; // Local X Axis + //if (1 - Math.Abs(gx.DotProduct(lx)) <= Tolerance.Angle) + // gx = Vector.YAxis; + + var global = new BH.oM.Geometry.Vector { X = gx.X, Y = gx.Y, Z = 0 }.Normalise(); + var local = new BH.oM.Geometry.Vector { X = lx.X, Y = lx.Y, Z = 0 }.Normalise(); + + //return Math.Atan2(Vector.ZAxis, global.DotProduct(local)); + return Math.Atan2(global.CrossProduct(local).Z, global.DotProduct(local)); + } + + /***************************************************/ + + public static double GetThicknessFromConstr(this PadFoundation element) + { + BH.oM.Physical.Constructions.Construction constr = element.Construction as oM.Physical.Constructions.Construction; + + if (constr == null || constr.Layers == null || constr.Layers.Count == 0) + { + BH.Engine.Base.Compute.RecordError("Invalid input"); + return double.NaN; + } + + double totalDepth = 0.0; + foreach (var layer in constr.Layers) + { + totalDepth += layer.Thickness; + } + return totalDepth; + } + + /***************************************************/ + + public static (double width, double length) GetRectangleDimensions(this Polyline polyline) + { + if (polyline == null || polyline.ControlPoints == null) + { + BH.Engine.Base.Compute.RecordWarning("Cannot get rectangle dimensions: polyline or control points are null."); + } + + var points = polyline.ControlPoints; + int pointCount = points.Count; + + if (pointCount != 5) + { + BH.Engine.Base.Compute.RecordWarning("Cannot get rectangle dimensions: polyline or control points are null."); + } + + double[] lengths = new double[4]; + for (int i = 0; i < 4; i++) + { + var line = new BH.oM.Geometry.Line { Start = points[i], End = points[(i + 1) % pointCount] }; + lengths[i] = line.Length(); + } + + double side1 = lengths[0]; + double side2 = lengths[1]; + + double width = Math.Min(side1, side2); + double length = Math.Max(side1, side2); + + return (width, length); + } + + /***************************************************/ + + public static FamilySymbol LoadPadRectangleTemplate(Document document, RevitSettings settings) + { + string familyName = "BHE_StructuralFoundations_Pad-Rectangular"; + string typeName = "1000x1000x500 DP"; + + return settings.FamilyLoadSettings.LoadFamilySymbol(document, "Structural Foundations", familyName, typeName); + } + + /***************************************************/ + + public static FamilySymbol GenerateFoundationType(this PadFoundation element, Document document, RevitSettings settings = null) + { + settings = settings.DefaultIfNull(); + + FamilySymbol baseSymbol = LoadPadRectangleTemplate(document, settings); + if (baseSymbol == null) + { + BH.Engine.Base.Compute.RecordError("Could not load rectangular foundation template."); + return null; + } + + string newTypeName = $"PadFoundation_{Guid.NewGuid().ToString().Substring(0, 8)}"; + FamilySymbol foundationSymbol = baseSymbol.Duplicate(newTypeName) as FamilySymbol; + if (foundationSymbol == null) + { + BH.Engine.Base.Compute.RecordError("Could not duplicate foundation family symbol."); + return null; + } + + foundationSymbol.Activate(); + + Polyline outline = ExtractBoundary(element); + if (outline == null || !IsRectangular(outline)) + { + BH.Engine.Base.Compute.RecordError("Foundation outline must be rectangular."); + return null; + } + + var (width, length) = GetRectangleDimensions(outline); + double depth = GetThicknessFromConstr(element); + + Parameter widthParam = foundationSymbol.LookupParameter("BHE_Width"); + widthParam?.Set(width); + + Parameter lengthParam = foundationSymbol.LookupParameter("BHE_Length"); + lengthParam?.Set(length); + + Parameter depthParam = foundationSymbol.LookupParameter("BHE_Depth"); + depthParam?.Set(depth); + + return foundationSymbol; + } + + /***************************************************/ + + public static XYZ GetFoundationOrigin(this PadFoundation element) + { + Polyline outline = ExtractBoundary(element); + // Validate the outline shape + if (outline?.ControlPoints == null) + return null; + + var points = outline.ControlPoints; + int pointCount = points.Count; + + if (pointCount != 5) + { + BH.Engine.Base.Compute.RecordError($"Foundation outline should have exactly 5 points for a rectangle, but has {pointCount}."); + return null; + } + + // Check if the shape is closed + if (!outline.IsClosed( BH.oM.Geometry.Tolerance.Distance)) + { + BH.Engine.Base.Compute.RecordError("Foundation outline is not properly closed."); + return null; + } + + var centerPoint = outline.Centroid(); + + if (centerPoint == null) + { + BH.Engine.Base.Compute.RecordError("Could not calculate centroid of foundation outline."); + return null; + } + + return centerPoint.ToRevit(); + } + } +} + + + + + + + diff --git a/Revit_Core_Engine/Query/LocationCurve.cs b/Revit_Core_Engine/Query/LocationCurve.cs index 0023b7504..71571344f 100644 --- a/Revit_Core_Engine/Query/LocationCurve.cs +++ b/Revit_Core_Engine/Query/LocationCurve.cs @@ -246,6 +246,27 @@ public static BH.oM.Geometry.Line LocationCurvePile(this FamilyInstance familyIn } /***************************************************/ + + [Description("Queries a FamilyInstance to find its top face boundary curve, intended usage for Pad Foundations.")] + [Input("familyInstance", "Revit FamilyInstance to be queried.")] + [Input("settings", "Optional, the RevitSettings to be used.")] + [Output("locationCurvePadFoundation", "BHoM curve representing the top boundary of the pad foundation.")] + public static BH.oM.Geometry.ICurve LocationCurvePadFoundation(this FamilyInstance familyInstance, RevitSettings settings = null) + { + settings = settings.DefaultIfNull(); + + BoundingBoxXYZ bbox = familyInstance.get_BoundingBox(null); + if (bbox == null) return null; + + oM.Geometry.Point p1 = new XYZ(bbox.Min.X, bbox.Min.Y, bbox.Max.Z).PointFromRevit(); + oM.Geometry.Point p2 = new XYZ(bbox.Max.X, bbox.Min.Y, bbox.Max.Z).PointFromRevit(); + oM.Geometry.Point p3 = new XYZ(bbox.Max.X, bbox.Max.Y, bbox.Max.Z).PointFromRevit(); + oM.Geometry.Point p4 = new XYZ(bbox.Min.X, bbox.Max.Y, bbox.Max.Z).PointFromRevit(); + + return new BH.oM.Geometry.Polyline { ControlPoints = new List { p1, p2, p3, p4, p1 } }; + } + + /***************************************************/ } }