diff --git a/.github/workflows/ci-beta.yml b/.github/workflows/ci-beta.yml new file mode 100644 index 000000000..b7ade0268 --- /dev/null +++ b/.github/workflows/ci-beta.yml @@ -0,0 +1,138 @@ +# Beta-tier proxy, BHoM variant. Bundles the tier's checks plus +# copyright-compliance (BHoM enforces OSS copyright; the BHE variant omits it). +# Copy to .github/workflows/ci-beta.yml in each BHoM beta repo. +# Runs on pull_request, non-blocking until a ruleset requires its checks. +# Workflow name CI keeps check contexts bare: required contexts are the job names. + +name: CI + +on: + pull_request: + branches: + - develop + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }} + cancel-in-progress: true + +jobs: + ci-build: + runs-on: windows-2025-vs2026 + timeout-minutes: 30 + steps: + - name: Run build + uses: BHoM/CI_Toolkit/.github/actions/ci-build@develop + with: + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-code-compliance: + runs-on: windows-2025-vs2026 + timeout-minutes: 20 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - name: Run code compliance + uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop + with: + check_type: code + patterns: '*.cs' + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-copyright-compliance: + runs-on: windows-2025-vs2026 + timeout-minutes: 20 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - name: Run copyright compliance + uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop + with: + check_type: copyright + patterns: '*.cs' + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-dataset-compliance: + runs-on: windows-2025-vs2026 + timeout-minutes: 20 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - name: Run dataset compliance + uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop + with: + check_type: dataset + patterns: ':(icase)*datasets*.json' + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-documentation-compliance: + runs-on: windows-2025-vs2026 + timeout-minutes: 20 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - name: Run documentation compliance + uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop + with: + check_type: documentation + patterns: '*.cs' + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-project-compliance: + runs-on: windows-2025-vs2026 + timeout-minutes: 20 + permissions: + contents: read + pull-requests: read + actions: write + steps: + - name: Run project compliance + uses: BHoM/CI_Toolkit/.github/actions/ci-compliance@develop + with: + check_type: project + patterns: '*AssemblyInfo.cs *.csproj' + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-dataset-tests: + runs-on: windows-2025-vs2026 + timeout-minutes: 30 + steps: + - name: Run dataset tests + uses: BHoM/CI_Toolkit/.github/actions/ci-dataset-tests@develop + with: + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + + ci-serialisation: + runs-on: windows-2025-vs2026 + timeout-minutes: 90 + steps: + - name: Run serialisation + uses: BHoM/CI_Toolkit/.github/actions/ci-serialisation@develop + with: + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} + base_ref: ${{ github.base_ref }} + + ci-versioning: + runs-on: windows-2025-vs2026 + timeout-minutes: 90 + steps: + - name: Run versioning + uses: BHoM/CI_Toolkit/.github/actions/ci-versioning@develop + with: + app_id: ${{ secrets.BHOM_APP_ID }} + private_key: ${{ secrets.BHOM_APP_PRIVATE_KEY }} diff --git a/README.md b/README.md index 6afde4cb9..d5d548b16 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,11 @@ A set of tools enabling exchange of information between BHoM and [Revit](https:/ - a set of utility methods supporting conversion and processing of Revit elements ### Known Versions of Software Supported -Autodesk Revit 2018 -Autodesk Revit 2019 -Autodesk Revit 2020 -Autodesk Revit 2021 Autodesk Revit 2022 Autodesk Revit 2023 +Autodesk Revit 2024 +Autodesk Revit 2025 +Autodesk Revit 2026 ### Documentation For more information about functionality see [Revit_Toolkit Wiki](https://github.com/BHoM/Revit_Toolkit/wiki). @@ -39,12 +38,11 @@ You will need the following to build Revit_Toolkit: - [Socket_Toolkit](https://github.com/BHoM/Socket_Toolkit) Revit_Toolkit needs to be built separately for each version of Revit. To switch between version and specific Revit_Toolkit configurations use Configuration Manager: -Debug2018 -> Revit 2018 -Debug2019 -> Revit 2019 -Debug2020 -> Revit 2020 -Debug2021 -> Revit 2021 Debug2022 -> Revit 2022 Debug2023 -> Revit 2023 +Debug2024 -> Revit 2024 +Debug2025 -> Revit 2025 +Debug2026 -> Revit 2026 ## Want to Contribute? ## diff --git a/Revit_Adapter/Revit_Adapter.csproj b/Revit_Adapter/Revit_Adapter.csproj index 622224a45..ca911de60 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.2.0.0 + 9.3.0.0 diff --git a/Revit_Core_Adapter/Revit_Core_Adapter.csproj b/Revit_Core_Adapter/Revit_Core_Adapter.csproj index e9e2d9f5f..89fda04ba 100644 --- a/Revit_Core_Adapter/Revit_Core_Adapter.csproj +++ b/Revit_Core_Adapter/Revit_Core_Adapter.csproj @@ -7,7 +7,7 @@ BHoM Copyright © https://github.com/BHoM BH.Revit.Adapter.Core - 9.2.0.0 + 9.3.0.0 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/Geometry/ToRevit/BoundingBoxXYZ.cs b/Revit_Core_Engine/Convert/Geometry/ToRevit/BoundingBoxXYZ.cs new file mode 100644 index 000000000..48fc1b071 --- /dev/null +++ b/Revit_Core_Engine/Convert/Geometry/ToRevit/BoundingBoxXYZ.cs @@ -0,0 +1,56 @@ +/* + * 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 BH.oM.Geometry; +using System.ComponentModel; + +namespace BH.Revit.Engine.Core +{ + public static partial class Convert + { + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + [Description("Converts BH.oM.Geometry.BoundingBox to a Revit BoundingBoxXYZ.")] + [Input("boundingBox", "BH.oM.Geometry.BoundingBox to be converted.")] + [Output("boundingBoxXYZ", "Revit BoundingBoxXYZ resulting from converting the input BH.oM.Geometry.BoundingBox.")] + public static BoundingBoxXYZ ToRevit(this BoundingBox boundingBox) + { + BoundingBoxXYZ result = new BoundingBoxXYZ(); + result.Enabled = true; + result.Min = boundingBox.Min.ToRevit(); + result.Max = boundingBox.Max.ToRevit(); + return result; + } + + /***************************************************/ + } +} + + + + + + diff --git a/Revit_Core_Engine/Convert/Physical/FromRevit/PileFoundation.cs b/Revit_Core_Engine/Convert/Physical/FromRevit/PileFoundation.cs new file mode 100644 index 000000000..7b1f8b009 --- /dev/null +++ b/Revit_Core_Engine/Convert/Physical/FromRevit/PileFoundation.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 Autodesk.Revit.DB; +using BH.Engine.Adapters.Revit; +using BH.Engine.Geometry; +using BH.oM.Adapters.Revit.Settings; +using BH.oM.Base; +using BH.oM.Base.Attributes; +using BH.oM.Geometry; +using BH.oM.Physical.Elements; +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.PileFoundation.")] + [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("pileFoundation", "BH.oM.Physical.Elements.PileFoundation resulting from converting the input Revit FamilyInstance.")] + public static PileFoundation PileFoundationFromRevit(this FamilyInstance familyInstance, RevitSettings settings = null, Dictionary> refObjects = null) + { + settings = settings.DefaultIfNull(); + + PileFoundation pileFoundation = refObjects.GetValue(familyInstance.Id); + if (pileFoundation != null) + return pileFoundation; + + PlanarSurface location = familyInstance.LocationSurfacePadFoundation(settings); + oM.Physical.Constructions.Construction construction = familyInstance.ConstructionPadFoundation(settings, refObjects); + PadFoundation pileCap = BH.Engine.Physical.Create.PadFoundation(location, construction, familyInstance.FamilyTypeFullName()); + + oM.Geometry.Plane padPlane = location.FitPlane(); + List piles = new List(); + foreach (ElementId pileId in familyInstance.GetSubComponentIds()) + { + FamilyInstance pileInstance = familyInstance.Document.GetElement(pileId) as FamilyInstance; + Pile pile = pileInstance.PileFromRevit(settings, refObjects); + + if (pile.Location is oM.Geometry.Line line) + line.End = line.End.ProjectAlong(padPlane, line.Direction()); + else + BH.Engine.Base.Compute.RecordWarning($"Could not extend the pile to the top of the cap because the pile is not linear. ElementId: {familyInstance.Id.Value()}"); + + piles.Add(pile); + } + + pileFoundation = BH.Engine.Physical.Create.PileFoundation(pileCap, piles, familyInstance.FamilyTypeFullName()); + + //Set identifiers, parameters & custom data + pileFoundation.SetIdentifiers(familyInstance); + pileFoundation.CopyParameters(familyInstance, settings.MappingSettings); + pileFoundation.SetProperties(familyInstance, settings.MappingSettings); + + refObjects.AddOrReplace(familyInstance.Id, pileFoundation); + return pileFoundation; + } + + /***************************************************/ + } +} 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/Create/View/ViewReflectedCeilingPlan.cs b/Revit_Core_Engine/Create/View/ViewReflectedCeilingPlan.cs deleted file mode 100644 index af65ddc62..000000000 --- a/Revit_Core_Engine/Create/View/ViewReflectedCeilingPlan.cs +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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 System; -using System.ComponentModel; -using BH.oM.Base.Attributes; - -namespace BH.Revit.Engine.Core -{ - public static partial class Create - { - /***************************************************/ - /**** Public Methods ****/ - /***************************************************/ - - [ToBeRemoved("6.1", "Calls to this method are to be replaced with Create.ViewPlan with 'viewFamily' parameter equal to ViewFamily.CeilingPlan.")] - [Description("Creates and returns a new Reflected Ceiling Plan view in the current Revit file.")] - [Input("document", "Revit current document to be processed.")] - [Input("level", "The level that the created Floor Plan refers to.")] - [Input("viewName", "Optional, name of the new view.")] - [Input("cropBox", "Optional, the crop region to attempt to apply to the newly created view.")] - [Input("viewTemplateId", "Optional, the View Template Id to be applied in the view.")] - [Input("viewDetailLevel", "Optional, the Detail Level of the view.")] - [Output("viewReflectedCeilingPlan", "The new view.")] - public static View ViewReflectedCeilingPlan(this Document document, Level level, string viewName = null, CurveLoop cropBox = null, ElementId viewTemplateId = null, ViewDetailLevel viewDetailLevel = ViewDetailLevel.Coarse) - { - View newView = null; - - ViewFamilyType viewFamilyType = Query.ViewFamilyType(document, ViewFamily.CeilingPlan); - - newView = Autodesk.Revit.DB.ViewPlan.Create(document, viewFamilyType.Id, level.Id); - - Modify.SetViewDetailLevel(newView, viewDetailLevel); - - if (cropBox != null) - { - try - { - ViewCropRegionShapeManager vcrShapeMgr = newView.GetCropRegionShapeManager(); - newView.CropBoxVisible = true; - vcrShapeMgr.SetCropShape(cropBox); - } - catch (Exception) - { - BH.Engine.Base.Compute.RecordWarning("Could not create the Reflected Ceiling Plan with the provided crop box. Check if the crop box is a valid geometry."); - } - } - - newView.SetViewTemplate(viewTemplateId); - - if (!string.IsNullOrEmpty(viewName)) - { - newView.SetViewName(viewName); - } - - return newView; - } - - /***************************************************/ - } -} - - - - - 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/BHoMType.cs b/Revit_Core_Engine/Query/BHoMType.cs index c42b3298e..e266e3811 100644 --- a/Revit_Core_Engine/Query/BHoMType.cs +++ b/Revit_Core_Engine/Query/BHoMType.cs @@ -92,14 +92,13 @@ public static Type BHoMType(this FamilyInstance familyInstance, Discipline disci case Discipline.Architecture: if (category == Autodesk.Revit.DB.BuiltInCategory.OST_StructuralFoundation) { - BoundingBoxXYZ bbox = familyInstance.get_BoundingBox(null); + BoundingBoxXYZ bbox = familyInstance.PhysicalBounds(); 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) + if (familyInstance.GetSubComponentIds().Count != 0) + return typeof(BH.oM.Physical.Elements.PileFoundation); + else if (height < width) 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. else return typeof(BH.oM.Physical.Elements.Pile); } diff --git a/Revit_Core_Engine/Query/Connectors.cs b/Revit_Core_Engine/Query/Connectors.cs index 280c06e41..c697c794b 100644 --- a/Revit_Core_Engine/Query/Connectors.cs +++ b/Revit_Core_Engine/Query/Connectors.cs @@ -58,7 +58,12 @@ private static List SortedConnectors(this MEPCurve mepCurve) foreach (Connector conn in connSet) connList.Add(conn); - connList = connList.OrderBy(x => x.Origin.DistanceTo(startPoint)).ToList(); + // Order by distance from the start point. Logical connectors have no Origin, so they are sorted to the end rather than crashing the query. + connList = connList.OrderBy(x => + { + try { return x.Origin.DistanceTo(startPoint); } + catch { return double.MaxValue; } + }).ToList(); return connList; } diff --git a/Revit_Core_Engine/Query/ElementIds/ElementIdsByBHoMType.cs b/Revit_Core_Engine/Query/ElementIds/ElementIdsByBHoMType.cs index a9514cdbb..c7ae58f1e 100644 --- a/Revit_Core_Engine/Query/ElementIds/ElementIdsByBHoMType.cs +++ b/Revit_Core_Engine/Query/ElementIds/ElementIdsByBHoMType.cs @@ -27,10 +27,10 @@ using BH.oM.Adapters.Revit.Properties; 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; using BH.oM.Physical.Materials; -using BH.oM.Base.Attributes; using System; using System.Collections.Generic; using System.ComponentModel; @@ -109,7 +109,7 @@ public static IEnumerable ElementIdsByBHoMType(this Document document else unsupportedAPITypes.Add(new Tuple(t, t.SupportedAPIType())); } - + elementIds.UnionWith(collector.WherePasses(new LogicalOrFilter(filters)).ToElementIds()); foreach (Tuple unsupportedAPIType in unsupportedAPITypes) { @@ -127,6 +127,10 @@ public static IEnumerable ElementIdsByBHoMType(this Document document if (types.Any(x => typeof(Autodesk.Revit.DB.Wall).IsAssignableFrom(x))) elementIds = elementIds.RemoveStackedWallParts(document); + // Filter out pile sub-components that are nested + if (type == typeof(Pile)) + elementIds = new HashSet(elementIds.Select(x => (document.GetElement(x) as FamilyInstance)).Where(x => x.SuperComponent == null).Select(x => x.Id)); + //Revit returns additional "parent" Autodesk.Revit.DB.Panel with no geometry when pulling all panels from model. This part of the code filter them out if (type == typeof(Window)) elementIds = elementIds.RemoveEmptyPanelIds(document); 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/Level.cs b/Revit_Core_Engine/Query/Level.cs index 48b3ae5c1..6db691dd9 100644 --- a/Revit_Core_Engine/Query/Level.cs +++ b/Revit_Core_Engine/Query/Level.cs @@ -42,24 +42,33 @@ public static Level Level(this Element element) if (element == null) return null; + Level level = null; Document doc = element.Document; ElementId levelId = element.LevelId; // First priority: Check LevelId property (most direct way to get level) if (levelId.Value() != -1) - return doc.GetElement(levelId) as Level; + level = doc.GetElement(levelId) as Level; + if (level != null) + return level; // Second priority: Check level parameters (LevelParameter or BaseLevelParameter) Parameter levelParameter = element.LevelParameter() ?? element.BaseLevelParameter(); if (levelParameter != null) - return doc.GetElement(levelParameter.AsElementId()) as Level; + level = doc.GetElement(levelParameter?.AsElementId()) as Level; + if (level != null) + return level; // Third priority: Check if element is work plane-based and hosted on a level if (element.IsWorkPlaneLevelBased()) - return (element as FamilyInstance)?.Host as Level; + level = (element as FamilyInstance)?.Host as Level; + if (level != null) + return level; - // No level found - return null; + // Fourth priority: Use the LevelBelow method to find the level below the element's location point + level = element.Document.LevelBelow(element.LocationPoint()); + + return level; } /***************************************************/ diff --git a/Revit_Core_Engine/Query/LevelParameter.cs b/Revit_Core_Engine/Query/LevelParameter.cs index 6cffd7f3d..c49358d5a 100644 --- a/Revit_Core_Engine/Query/LevelParameter.cs +++ b/Revit_Core_Engine/Query/LevelParameter.cs @@ -35,7 +35,7 @@ public static partial class Query [Description("Returns the element parameter that stores the level of the element. Checks multiple built-in level parameters in order of preference.")] [Input("element", "Element to get the level parameter from.")] - [Output("parameter", "Parameter that stores the level, or null if not found or parameter is read-only.")] + [Output("parameter", "Level parameter of the element, or null if not found.")] public static Parameter LevelParameter(this Element element) { if (element == null) @@ -45,14 +45,15 @@ public static Parameter LevelParameter(this Element element) .Select(x => element.get_Parameter(x)) .Where(x => x != null) .OrderByDescending(x => x.AsElementId().Value() != -1) // Valid ElementIds first - .FirstOrDefault(x => !x.IsReadOnly); + .ThenBy(x => x.IsReadOnly) // Editable parameters first + .FirstOrDefault(); } /***************************************************/ [Description("Returns the element parameter that stores the base level of the element. Checks multiple built-in base level parameters in order of preference.")] [Input("element", "Element to get the base level parameter from.")] - [Output("parameter", "Parameter that stores the base level, or null if not found or parameter is read-only.")] + [Output("parameter", "Base level parameter of the element, or null if not found.")] public static Parameter BaseLevelParameter(this Element element) { if (element == null) @@ -62,14 +63,15 @@ public static Parameter BaseLevelParameter(this Element element) .Select(x => element.get_Parameter(x)) .Where(x => x != null) .OrderByDescending(x => x.AsElementId().Value() != -1) // Valid ElementIds first - .FirstOrDefault(x => !x.IsReadOnly); + .ThenBy(x => x.IsReadOnly) // Editable parameters first + .FirstOrDefault(); } /***************************************************/ [Description("Returns the element parameter that stores the top level of the element. Checks multiple built-in top level parameters in order of preference.")] [Input("element", "Element to get the top level parameter from.")] - [Output("parameter", "Parameter that stores the top level, or null if not found or parameter is read-only.")] + [Output("parameter", "Top level parameter of the element, or null if not found.")] public static Parameter TopLevelParameter(this Element element) { if (element == null) @@ -79,7 +81,8 @@ public static Parameter TopLevelParameter(this Element element) .Select(x => element.get_Parameter(x)) .Where(x => x != null) .OrderByDescending(x => x.AsElementId().Value() != -1) // Valid ElementIds first - .FirstOrDefault(x => !x.IsReadOnly); + .ThenBy(x => x.IsReadOnly) // Editable parameters first + .FirstOrDefault(); } /***************************************************/ diff --git a/Revit_Core_Engine/Query/LocationPoint.cs b/Revit_Core_Engine/Query/LocationPoint.cs index f818a1ab9..8460a93f5 100644 --- a/Revit_Core_Engine/Query/LocationPoint.cs +++ b/Revit_Core_Engine/Query/LocationPoint.cs @@ -70,7 +70,12 @@ public static XYZ LocationPoint(this Element element, bool useRoomCalculationPoi // Fallback to bounding box center if (locationPoint == null) { - BoundingBoxXYZ bbox = element.PhysicalBounds() ?? element.get_BoundingBox(null); + BoundingBoxXYZ bbox = null; + if (element is DirectShape) + bbox = element.get_BoundingBox(null); + else + bbox = element.PhysicalBounds() ?? element.get_BoundingBox(null); + if (bbox != null) locationPoint = (bbox.Max + bbox.Min) / 2; } 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; + } + + /***************************************************/ + } +} diff --git a/Revit_Core_Engine/Revit_Core_Engine.csproj b/Revit_Core_Engine/Revit_Core_Engine.csproj index 4514c7144..ed8296864 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.2.0.0 + 9.3.0.0 @@ -68,6 +68,14 @@ + + + + + + + + ..\libs\$(RevitVersion)\RevitAPI.dll @@ -79,6 +87,11 @@ + + False + $(ProgramData)\BHoM\Assemblies\Analytical_Engine.dll + False + False $(ProgramData)\BHoM\Assemblies\Analytical_oM.dll @@ -119,6 +132,11 @@ $(ProgramData)\BHoM\Assemblies\Diffing_Engine.dll False + + False + $(ProgramData)\BHoM\Assemblies\Dimensional_Engine.dll + False + False $(ProgramData)\BHoM\Assemblies\Dimensional_oM.dll diff --git a/Revit_Engine/Create/Settings/MappingSettings.cs b/Revit_Engine/Create/Settings/MappingSettings.cs index 53750b8a4..4765f1af7 100644 --- a/Revit_Engine/Create/Settings/MappingSettings.cs +++ b/Revit_Engine/Create/Settings/MappingSettings.cs @@ -34,7 +34,8 @@ public static partial class Create /***************************************************/ /**** Public methods ****/ /***************************************************/ - + + [PreviousVersion("9.3", "BH.Engine.Adapters.Revit.Create.MappingSettings(System.Collections.Generic.IEnumerable, System.String, System.String)")] [Description("Creates an entity holding information about the enforced convert relationships between Revit families and BHoM types on Pull as well as mapping between Revit parameters and BHoM object properties.")] [InputFromProperty("parameterMaps")] [InputFromProperty("familyMaps")] @@ -57,26 +58,6 @@ public static MappingSettings MappingSettings(IEnumerable paramete } /***************************************************/ - - [ToBeRemoved("4.3", "More up to date version with a larger number of parameters has been added.")] - [Description("Created an entity holding information about conversion-specific Revit parameter names as well as relationships between object's property names (or names of RevitParameters attached to it) and Revit parameter names.")] - [InputFromProperty("parameterMaps")] - [InputFromProperty("tagsParameter")] - [InputFromProperty("materialGradeParameter")] - [Output("mappingSettings")] - public static MappingSettings MappingSettings(IEnumerable parameterMaps = null, string tagsParameter = "", string materialGradeParameter = "") - { - MappingSettings mappingSettings = new MappingSettings(); - if (parameterMaps != null) - mappingSettings = mappingSettings.AddParameterMaps(parameterMaps); - - mappingSettings.TagsParameter = tagsParameter; - mappingSettings.MaterialGradeParameter = materialGradeParameter; - - return mappingSettings; - } - - /***************************************************/ } } diff --git a/Revit_Engine/Revit_Engine.csproj b/Revit_Engine/Revit_Engine.csproj index 38eb18538..9d0388907 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.2.0.0 + 9.3.0.0 diff --git a/Revit_Engine/Versioning_93.json b/Revit_Engine/Versioning_93.json new file mode 100644 index 000000000..ad7462735 --- /dev/null +++ b/Revit_Engine/Versioning_93.json @@ -0,0 +1,51 @@ +{ + "Namespace": { + "ToNew": { + + }, + "ToOld": { + + } + }, + "Type": { + "ToNew": { + + }, + "ToOld": { + + } + }, + "Method": { + "ToNew": { + + }, + "ToOld": { + + } + }, + "Property": { + "ToNew": { + + }, + "ToOld": { + + } + }, + "Dataset": { + "ToNew": { + + }, + "ToOld": { + + }, + "MessageForDeleted": { + + } + }, + "MessageForDeleted": { + "BH.Revit.Engine.Core.Create.ViewReflectedCeilingPlan(Autodesk.Revit.DB.Document, Autodesk.Revit.DB.Level, System.String, Autodesk.Revit.DB.CurveLoop, Autodesk.Revit.DB.ElementId, Autodesk.Revit.DB.ViewDetailLevel)": "Calls to this method to be replaced with Create.ViewPlan with 'viewFamily' parameter equal to ViewFamily.CeilingPlan." + }, + "MessageForNoUpgrade": { + + } +} \ No newline at end of file diff --git a/Revit_oM/Revit_oM.csproj b/Revit_oM/Revit_oM.csproj index 26344f79e..42b38f09d 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.2.0.0 + 9.3.0.0 ..\Build\