From 69a3d8028b32688bc1aa61aa89331ab6946d7a9c Mon Sep 17 00:00:00 2001 From: Pawel Baran Date: Tue, 13 Jan 2026 11:09:35 +0100 Subject: [PATCH 1/7] initial commit --- Revit_Core_Engine/Modify/SetLocation.cs | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index 1fa10dc84..2c9636a83 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -445,6 +445,18 @@ public static bool SetLocation(this HostObject element, BH.oM.Physical.Elements. return false; } + /***************************************************/ + + //[Description("Sets the location of a given Revit HostObject based on a given BHoM ISurface.")] + //[Input("element", "Revit HostObject to be modified.")] + //[Input("bHoMObject", "BHoM ISurface 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 HostObject has been successfully set.")] + public static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Physical.Elements.Floor bHoMObject, RevitSettings settings) + { + return element.SetLocation(bHoMObject.Location, settings); + } + /***************************************************/ /**** Fallback Methods ****/ @@ -493,6 +505,30 @@ public static bool ISetLocation(this Element element, IBHoMObject bHoMObject, Re /**** Private Methods ****/ /***************************************************/ + private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geometry.ISurface location, RevitSettings settings) + { + PlanarSurface ps = location as PlanarSurface; + if (ps == null) + { + BH.Engine.Base.Compute.RecordWarning("..."); + return false; + } + + //Update floor location: + //- outline + //- ignore openings - check if any, if yes then warning + //- offset from level + //- floor slope + + Document doc = element.Document; + CurveArray arr = ps.ExternalBoundary.ToRevitCurveArray(); + + //use SketchEditScope + + + return true; + } + private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Point location, Basis orientation, RevitSettings settings) { LocationPoint elementLocation = element.Location as LocationPoint; From 267aa7d35a23586daf61d2b84cd31af8b789f813 Mon Sep 17 00:00:00 2001 From: Pawel Baran Date: Thu, 15 Jan 2026 16:48:20 +0100 Subject: [PATCH 2/7] inspiration how to add a follow up transaction on push --- Revit_Core_Adapter/AdapterActions/Push.cs | 28 +++++++++++++++++-- Revit_Core_Engine/Modify/SetLocation.cs | 14 ++++++++++ .../SketchUpdateFailurePreprocessor.cs | 12 ++++++++ .../Objects/SketchUpdateQueue.cs | 10 +++++++ 4 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs create mode 100644 Revit_Core_Engine/Objects/SketchUpdateQueue.cs diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index 22e8eba2b..221e23558 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -27,6 +27,7 @@ using BH.oM.Adapters.Revit.Elements; using BH.oM.Base; using BH.Revit.Engine.Core; +using System; using System.Collections.Generic; using System.Linq; @@ -60,7 +61,7 @@ public override List Push(IEnumerable objects, string tag = "", BH.Engine.Base.Compute.RecordError("BHoM objects could not be removed because another transaction is open in Revit."); return new List(); } - + // If unset, set the pushType to AdapterSettings' value (base AdapterSettings default is FullCRUD). Disallow the unsupported PushTypes. if (pushType == PushType.AdapterDefault) pushType = PushType.UpdateOrCreateOnly; @@ -69,7 +70,7 @@ public override List Push(IEnumerable objects, string tag = "", BH.Engine.Base.Compute.RecordError("Full Push is currently not supported by Revit_Toolkit, please use Create, UpdateOnly or DeleteThenCreate instead."); return new List(); } - + // Set config RevitPushConfig pushConfig = actionConfig as RevitPushConfig; if (pushConfig == null) @@ -81,7 +82,7 @@ public override List Push(IEnumerable objects, string tag = "", // Suppress warnings if (UIControlledApplication != null && pushConfig.SuppressFailureMessages) UIControlledApplication.ControlledApplication.FailuresProcessing += ControlledApplication_FailuresProcessing; - + // Process the objects (verify they are valid; DeepClone them, wrap them, etc). IEnumerable objectsToPush = ProcessObjectsForPush(objects, pushConfig); // Note: default Push only supports IBHoMObjects. @@ -150,6 +151,8 @@ public override List Push(IEnumerable objects, string tag = "", private List PushToRevit(Document document, IEnumerable objects, PushType pushType, RevitPushConfig pushConfig, string transactionName) { List pushed = new List(); + + //TODO: wrap into transaction group maybe? using (Transaction transaction = new Transaction(document, transactionName)) { transaction.Start(); @@ -200,6 +203,25 @@ private List PushToRevit(Document document, IEnumerable + { + using (SketchEditScope ses = new SketchEditScope(doc, "Update sketches")) + { + ses.Start(sketchId); + + //TODO: update sketch + + ses.Commit(new SketchUpdateFailurePreprocessor()); + + //TODO: add to group rather than commit? make sure it is shown as a single undo action in revit + } + }); + return true; } diff --git a/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs new file mode 100644 index 000000000..238ad2432 --- /dev/null +++ b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs @@ -0,0 +1,12 @@ +using Autodesk.Revit.DB; + +namespace BH.Revit.Engine.Core +{ + internal class SketchUpdateFailurePreprocessor : IFailuresPreprocessor + { + public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) + { + return FailureProcessingResult.Continue; + } + } +} diff --git a/Revit_Core_Engine/Objects/SketchUpdateQueue.cs b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs new file mode 100644 index 000000000..5cbce8c04 --- /dev/null +++ b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace BH.Revit.Engine.Core +{ + public static class SketchUpdateQueue + { + public static Queue SketchUpdates { get; } = new Queue(); + } +} From 43ec45e537c633abcce3c9fdb1f5cd6f792ca605 Mon Sep 17 00:00:00 2001 From: wojciech buda Date: Thu, 22 Jan 2026 17:02:15 +0100 Subject: [PATCH 3/7] set with SlabShapeEditor - fix with slope arrow --- Revit_Core_Adapter/AdapterActions/Push.cs | 54 ++++-- Revit_Core_Engine/Modify/SetLocation.cs | 199 +++++++++++++++++++++- 2 files changed, 233 insertions(+), 20 deletions(-) diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index 221e23558..d93c6cd13 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -203,24 +203,56 @@ private List PushToRevit(Document document, IEnumerable 0) { - try + using (TransactionGroup tg = new TransactionGroup(document, "Update floor sketches")) { + tg.Start(); + + int successCount = 0; + int errorCount = 0; + List errors = new List(); + foreach (Action call in SketchUpdateQueue.SketchUpdates) { - call.Invoke(); + try + { + call.Invoke(); + successCount++; + } + catch (Exception ex) + { + errorCount++; + string errorMsg = $"Sketch update failed: {ex.Message}"; + if (ex.InnerException != null) + errorMsg += $" Inner: {ex.InnerException.Message}"; + errors.Add(errorMsg); + BH.Engine.Base.Compute.RecordError(errorMsg); + } + } + + if (errorCount == 0) + { + tg.Assimilate(); + } + else + { + + if (successCount > 0) + { + BH.Engine.Base.Compute.RecordWarning($"Some sketch updates failed ({errorCount} of {SketchUpdateQueue.SketchUpdates.Count}). Partial updates may have been applied."); + tg.Assimilate(); + } + else + { + tg.RollBack(); + BH.Engine.Base.Compute.RecordError($"All sketch updates failed. Rolled back all changes."); + } } } - catch - { - BH.Engine.Base.Compute.RecordError("..."); - } - } - finally - { - SketchUpdateQueue.SketchUpdates.Clear(); } + + SketchUpdateQueue.SketchUpdates.Clear(); return pushed; } diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index f5f20c818..1115ec05c 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -510,7 +510,7 @@ private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geom PlanarSurface ps = location as PlanarSurface; if (ps == null) { - BH.Engine.Base.Compute.RecordWarning("..."); + BH.Engine.Base.Compute.RecordWarning("Ps error"); return false; } @@ -520,26 +520,195 @@ private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geom //- offset from level //- floor slope + if (ps.InternalBoundaries != null && ps.InternalBoundaries.Count > 0) + BH.Engine.Base.Compute.RecordWarning($"Floor has openings which will be ignored during sketch update. ElementId: {element.Id.Value()}"); + Document doc = element.Document; - CurveArray arr = ps.ExternalBoundary.ToRevitCurveArray(); + ElementId floorId = element.Id; + + // Find sketch by OwnerId + Sketch sketch = new FilteredElementCollector(doc) + .OfClass(typeof(Sketch)) + .Cast() + .FirstOrDefault(s => s.OwnerId == floorId); + ElementId sketchId = sketch?.Id; + + if (sketchId == null) + { + BH.Engine.Base.Compute.RecordError($"Floor sketch not found. ElementId: {element.Id.Value()}"); + return false; + } - //use SketchEditScope + SketchPlane sketchPlane = sketch.SketchPlane; + if (sketchPlane == null) + { + BH.Engine.Base.Compute.RecordError($"Floor sketch plane not found. ElementId: {element.Id.Value()}"); + return false; + } + + Autodesk.Revit.DB.Plane revitPlane = sketchPlane.GetPlane(); + BH.oM.Geometry.Plane bhomPlane = BH.Engine.Geometry.Create.Plane(revitPlane.Origin.PointFromRevit(), revitPlane.Normal.VectorFromRevit()); + BH.oM.Geometry.ICurve projectedBoundary = ps.ExternalBoundary.IProject(bhomPlane); + CurveLoop newOutline = projectedBoundary.ToRevitCurveLoop(); + + Level level = doc.GetElement(element.LevelId) as Level; + BH.oM.Geometry.Plane slabPlane = ps.FitPlane(); + double newOffset = (ps.IBounds().Min.Z.FromSI(SpecTypeId.Length) - level.ProjectElevation); + + XYZ n = slabPlane.Normal.ToRevit().Normalize(); + double dot = Math.Abs(n.DotProduct(XYZ.BasisZ)); + dot = Math.Min(1.0, Math.Max(-1.0, dot)); + double slopeAngleRad = Math.Acos(dot); + double tol = settings.AngleTolerance; + bool hasSlope = slopeAngleRad > tol; + Autodesk.Revit.DB.Line spanDirectionLine = null; + double slopeAngle = 0.0; + + if (hasSlope) + { + Vector normal = slabPlane.Normal; + if (normal.Z < 0) + normal = -slabPlane.Normal; + + double angle = normal.Angle(Vector.ZAxis); + slopeAngle = -Math.Tan(angle); + + Vector dir = normal.Project(oM.Geometry.Plane.XY); + BH.oM.Geometry.Line intersectionLine = slabPlane.PlaneIntersection(bhomPlane); + XYZ start = intersectionLine.ClosestPoint(projectedBoundary.IStartPoint(), true).ToRevit(); + XYZ direction = dir.ToRevit().Normalize(); + spanDirectionLine = Autodesk.Revit.DB.Line.CreateBound(start, start + direction); + } + //use SketchEditScope SketchUpdateQueue.SketchUpdates.Enqueue(() => { - using (SketchEditScope ses = new SketchEditScope(doc, "Update sketches")) + Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; + if (floor == null || !floor.IsValidObject) + { + BH.Engine.Base.Compute.RecordWarning($"Floor with ElementId {floorId.Value()} is no longer valid. Skipping sketch update."); + return; + } + + Sketch floorSketch = doc.GetElement(sketchId) as Sketch; + if (floorSketch == null) { - ses.Start(sketchId); + BH.Engine.Base.Compute.RecordWarning($"Sketch with ElementId {sketchId.Value()} not found for floor {floorId.Value()}. Skipping sketch update."); + return; + } - //TODO: update sketch + try + { + using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch")) + { + ses.Start(sketchId); + + using (Transaction t = new Transaction(doc, "Modify sketch profile")) + { + t.Start(); + + Sketch currentSketch = doc.GetElement(sketchId) as Sketch; + if (currentSketch != null) + { + IList existingElements = currentSketch.GetAllElements(); + if (existingElements != null && existingElements.Count > 0) + { + doc.Delete(existingElements); + } + + SketchPlane sketchPlane = currentSketch.SketchPlane; + if (sketchPlane != null) + { + foreach (Curve curve in newOutline) + { + doc.Create.NewModelCurve(curve, sketchPlane); + } + } + } + + t.Commit(); + } - ses.Commit(new SketchUpdateFailurePreprocessor()); + ses.Commit(new SketchUpdateFailurePreprocessor()); + } - //TODO: add to group rather than commit? make sure it is shown as a single undo action in revit + using (Transaction tOffset = new Transaction(doc, "Update floor offset and slope")) + { + tOffset.Start(); + floor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); + doc.Regenerate(); + + if (hasSlope) + { + //Maybe bug withSlabSHapeEditor due to issue #1631 + SlabShapeEditor slabShapeEditor = floor.SlabShapeEditor; + slabShapeEditor.ResetSlabShape(); + slabShapeEditor.Enable(); + doc.Regenerate(); + + IList topFaces = HostObjectUtils.GetTopFaces(floor); + if (topFaces != null && topFaces.Count > 0) + { + Autodesk.Revit.DB.Face topFace = floor.GetGeometryObjectFromReference(topFaces[0]) as Autodesk.Revit.DB.Face; + if (topFace != null) + { + List controlPoints = ps.ExternalBoundary.IControlPoints(); + if (controlPoints != null && controlPoints.Count >= 3) + { + foreach (BH.oM.Geometry.Point point in controlPoints) + { + XYZ targetPoint = point.ToRevit(); + + // Validate point coordinates + if (!IsValidPoint(targetPoint)) + { + BH.Engine.Base.Compute.RecordWarning($"Invalid point coordinates (NaN or Infinity) for floor {floorId.Value()}. Skipping point."); + continue; + } + + IntersectionResult result = topFace.Project(targetPoint); + if (result != null) + { + UV uv = result.UVPoint; + XYZ projectedPoint = topFace.Evaluate(uv); + double currentTopZ = projectedPoint.Z; + double targetZ = targetPoint.Z; + double elevationOffset = targetZ - currentTopZ; + + if (Math.Abs(elevationOffset) > settings.DistanceTolerance) + { + + XYZ pointToAdd = new XYZ(projectedPoint.X, projectedPoint.Y, targetZ); + + if (IsValidPoint(pointToAdd)) + { + try + { + slabShapeEditor.AddPoint(pointToAdd); + } + catch (Exception addPointEx) + { + BH.Engine.Base.Compute.RecordWarning($"Could not add point ({pointToAdd.X:F3}, {pointToAdd.Y:F3}, {pointToAdd.Z:F3}) to floor {floorId.Value()}: {addPointEx.Message}"); + } + } + } + } + } + } + } + } + } + + tOffset.Commit(); + } + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}"); + throw; } }); - return true; } @@ -657,6 +826,18 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin /***************************************************/ + private static bool IsValidPoint(XYZ point) + { + if (point == null) + return false; + + return !double.IsNaN(point.X) && !double.IsInfinity(point.X) && + !double.IsNaN(point.Y) && !double.IsInfinity(point.Y) && + !double.IsNaN(point.Z) && !double.IsInfinity(point.Z); + } + + /***************************************************/ + private static bool UpdateRotationOfVerticalElement(this FamilyInstance element, IFramingElement bhomElement, RevitSettings settings) { bool updated = false; From 788fc89f718c0dcfa7108079c22b9a7c9c572596 Mon Sep 17 00:00:00 2001 From: wojciech buda Date: Mon, 26 Jan 2026 11:49:29 +0100 Subject: [PATCH 4/7] slope arrow fixed --- Revit_Core_Engine/Modify/SetLocation.cs | 312 ++++++++++++------------ 1 file changed, 161 insertions(+), 151 deletions(-) diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index 1115ec05c..ee7ab8c9a 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -514,202 +514,212 @@ private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geom return false; } - //Update floor location: - //- outline - //- ignore openings - check if any, if yes then warning - //- offset from level - //- floor slope - if (ps.InternalBoundaries != null && ps.InternalBoundaries.Count > 0) BH.Engine.Base.Compute.RecordWarning($"Floor has openings which will be ignored during sketch update. ElementId: {element.Id.Value()}"); Document doc = element.Document; ElementId floorId = element.Id; - - // Find sketch by OwnerId - Sketch sketch = new FilteredElementCollector(doc) - .OfClass(typeof(Sketch)) - .Cast() - .FirstOrDefault(s => s.OwnerId == floorId); - ElementId sketchId = sketch?.Id; - - if (sketchId == null) + Sketch sketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == floorId); + if (sketch?.Id == null || sketch.SketchPlane == null) { BH.Engine.Base.Compute.RecordError($"Floor sketch not found. ElementId: {element.Id.Value()}"); return false; } SketchPlane sketchPlane = sketch.SketchPlane; - if (sketchPlane == null) - { - BH.Engine.Base.Compute.RecordError($"Floor sketch plane not found. ElementId: {element.Id.Value()}"); - return false; - } - Autodesk.Revit.DB.Plane revitPlane = sketchPlane.GetPlane(); BH.oM.Geometry.Plane bhomPlane = BH.Engine.Geometry.Create.Plane(revitPlane.Origin.PointFromRevit(), revitPlane.Normal.VectorFromRevit()); BH.oM.Geometry.ICurve projectedBoundary = ps.ExternalBoundary.IProject(bhomPlane); CurveLoop newOutline = projectedBoundary.ToRevitCurveLoop(); - Level level = doc.GetElement(element.LevelId) as Level; BH.oM.Geometry.Plane slabPlane = ps.FitPlane(); double newOffset = (ps.IBounds().Min.Z.FromSI(SpecTypeId.Length) - level.ProjectElevation); - + + bool hasSlope = CalculateSlopeInfo(slabPlane, bhomPlane, projectedBoundary, settings, out Autodesk.Revit.DB.Line spanDirectionLine, out double tan); + + SketchUpdateQueue.SketchUpdates.Enqueue(() => + { + Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; + if (floor == null || !floor.IsValidObject) return; + + Sketch floorSketch = doc.GetElement(sketch.Id) as Sketch; + if (floorSketch == null) return; + + try + { + UpdateSketchOutline(doc, sketch.Id, newOutline); + UpdateFloorOffsetAndSlope(doc, floor, sketch.Id, hasSlope, spanDirectionLine, tan, newOutline, slabPlane, projectedBoundary, newOffset, floorId, settings); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}"); + throw; + } + }); + + return true; + } + + private static bool CalculateSlopeInfo(BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.Plane bhomPlane, BH.oM.Geometry.ICurve projectedBoundary, RevitSettings settings, out Autodesk.Revit.DB.Line spanDirectionLine, out double tan) + { + spanDirectionLine = null; + tan = 0.0; XYZ n = slabPlane.Normal.ToRevit().Normalize(); double dot = Math.Abs(n.DotProduct(XYZ.BasisZ)); - dot = Math.Min(1.0, Math.Max(-1.0, dot)); - double slopeAngleRad = Math.Acos(dot); - double tol = settings.AngleTolerance; - bool hasSlope = slopeAngleRad > tol; - Autodesk.Revit.DB.Line spanDirectionLine = null; - double slopeAngle = 0.0; - + double slopeAngleRad = Math.Acos(Math.Min(1.0, Math.Max(-1.0, dot))); + bool hasSlope = slopeAngleRad > settings.AngleTolerance; + if (hasSlope) { Vector normal = slabPlane.Normal; - if (normal.Z < 0) - normal = -slabPlane.Normal; - + if (normal.Z < 0) normal = -slabPlane.Normal; double angle = normal.Angle(Vector.ZAxis); - slopeAngle = -Math.Tan(angle); - + tan = Math.Tan(angle); Vector dir = normal.Project(oM.Geometry.Plane.XY); BH.oM.Geometry.Line intersectionLine = slabPlane.PlaneIntersection(bhomPlane); XYZ start = intersectionLine.ClosestPoint(projectedBoundary.IStartPoint(), true).ToRevit(); - XYZ direction = dir.ToRevit().Normalize(); - spanDirectionLine = Autodesk.Revit.DB.Line.CreateBound(start, start + direction); + spanDirectionLine = Autodesk.Revit.DB.Line.CreateBound(start, start + dir.ToRevit().Normalize()); } + return hasSlope; + } - //use SketchEditScope - SketchUpdateQueue.SketchUpdates.Enqueue(() => + private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveLoop newOutline) + { + using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch")) { - Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; - if (floor == null || !floor.IsValidObject) + ses.Start(sketchId); + using (Transaction t = new Transaction(doc, "Modify sketch profile")) { - BH.Engine.Base.Compute.RecordWarning($"Floor with ElementId {floorId.Value()} is no longer valid. Skipping sketch update."); - return; - } - - Sketch floorSketch = doc.GetElement(sketchId) as Sketch; - if (floorSketch == null) - { - BH.Engine.Base.Compute.RecordWarning($"Sketch with ElementId {sketchId.Value()} not found for floor {floorId.Value()}. Skipping sketch update."); - return; + t.Start(); + Sketch currentSketch = doc.GetElement(sketchId) as Sketch; + if (currentSketch != null) + { + IList existingElements = currentSketch.GetAllElements(); + if (existingElements != null && existingElements.Count > 0) + doc.Delete(existingElements); + SketchPlane sketchPlane = currentSketch.SketchPlane; + if (sketchPlane != null) + foreach (Curve curve in newOutline) + doc.Create.NewModelCurve(curve, sketchPlane); + } + t.Commit(); } + ses.Commit(new SketchUpdateFailurePreprocessor()); + } + } - try + private static void UpdateFloorOffsetAndSlope(Document doc, Autodesk.Revit.DB.Floor floor, ElementId sketchId, bool hasSlope, Autodesk.Revit.DB.Line spanDirectionLine, double tan, CurveLoop newOutline, BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.ICurve projectedBoundary, double newOffset, ElementId floorId, RevitSettings settings) + { + using (Transaction tOffset = new Transaction(doc, "Update floor offset and slope")) + { + tOffset.Start(); + if (hasSlope && spanDirectionLine != null) { - using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch")) + FloorType floorType = doc.GetElement(floor.GetTypeId()) as FloorType; + Level floorLevel = doc.GetElement(floor.LevelId) as Level; + if (floorType != null && floorLevel != null && newOutline != null && !newOutline.IsOpen() && newOutline.Count() > 0) { - ses.Start(sketchId); - - using (Transaction t = new Transaction(doc, "Modify sketch profile")) - { - t.Start(); - - Sketch currentSketch = doc.GetElement(sketchId) as Sketch; - if (currentSketch != null) - { - IList existingElements = currentSketch.GetAllElements(); - if (existingElements != null && existingElements.Count > 0) - { - doc.Delete(existingElements); - } - - SketchPlane sketchPlane = currentSketch.SketchPlane; - if (sketchPlane != null) - { - foreach (Curve curve in newOutline) - { - doc.Create.NewModelCurve(curve, sketchPlane); - } - } - } - - t.Commit(); - } - - ses.Commit(new SketchUpdateFailurePreprocessor()); + Autodesk.Revit.DB.Line slopeArrowLine = CalculateSlopeArrowLine(doc, sketchId, slabPlane, projectedBoundary, newOutline, spanDirectionLine, floorId, settings); + doc.Delete(floor.Id); + doc.Regenerate(); + Autodesk.Revit.DB.Floor newFloor = CreateFloorWithSlope(doc, newOutline, floorType.Id, floorLevel.Id, slopeArrowLine, tan, newOffset, settings); + if (newFloor != null) + VerifySlopeArrow(doc, newFloor, newOutline); } - - using (Transaction tOffset = new Transaction(doc, "Update floor offset and slope")) + else if (floorType == null || floorLevel == null) { - tOffset.Start(); floor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); doc.Regenerate(); - - if (hasSlope) - { - //Maybe bug withSlabSHapeEditor due to issue #1631 - SlabShapeEditor slabShapeEditor = floor.SlabShapeEditor; - slabShapeEditor.ResetSlabShape(); - slabShapeEditor.Enable(); - doc.Regenerate(); - - IList topFaces = HostObjectUtils.GetTopFaces(floor); - if (topFaces != null && topFaces.Count > 0) - { - Autodesk.Revit.DB.Face topFace = floor.GetGeometryObjectFromReference(topFaces[0]) as Autodesk.Revit.DB.Face; - if (topFace != null) - { - List controlPoints = ps.ExternalBoundary.IControlPoints(); - if (controlPoints != null && controlPoints.Count >= 3) - { - foreach (BH.oM.Geometry.Point point in controlPoints) - { - XYZ targetPoint = point.ToRevit(); - - // Validate point coordinates - if (!IsValidPoint(targetPoint)) - { - BH.Engine.Base.Compute.RecordWarning($"Invalid point coordinates (NaN or Infinity) for floor {floorId.Value()}. Skipping point."); - continue; - } - - IntersectionResult result = topFace.Project(targetPoint); - if (result != null) - { - UV uv = result.UVPoint; - XYZ projectedPoint = topFace.Evaluate(uv); - double currentTopZ = projectedPoint.Z; - double targetZ = targetPoint.Z; - double elevationOffset = targetZ - currentTopZ; - - if (Math.Abs(elevationOffset) > settings.DistanceTolerance) - { - - XYZ pointToAdd = new XYZ(projectedPoint.X, projectedPoint.Y, targetZ); - - if (IsValidPoint(pointToAdd)) - { - try - { - slabShapeEditor.AddPoint(pointToAdd); - } - catch (Exception addPointEx) - { - BH.Engine.Base.Compute.RecordWarning($"Could not add point ({pointToAdd.X:F3}, {pointToAdd.Y:F3}, {pointToAdd.Z:F3}) to floor {floorId.Value()}: {addPointEx.Message}"); - } - } - } - } - } - } - } - } - } - - tOffset.Commit(); } } - catch (Exception ex) + else { - BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}"); - throw; + floor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); + doc.Regenerate(); + } + tOffset.Commit(); + } + } + + private static Autodesk.Revit.DB.Line CalculateSlopeArrowLine(Document doc, ElementId sketchId, BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.ICurve projectedBoundary, CurveLoop newOutline, Autodesk.Revit.DB.Line spanDirectionLine, ElementId floorId, RevitSettings settings) + { + Sketch updatedSketch = doc.GetElement(sketchId) as Sketch; + SketchPlane sketchPlaneForArrow = updatedSketch?.SketchPlane; + if (sketchPlaneForArrow == null) return spanDirectionLine; + + Autodesk.Revit.DB.Plane revitPlaneForArrow = sketchPlaneForArrow.GetPlane(); + BH.oM.Geometry.Plane bhomPlaneForArrow = BH.Engine.Geometry.Create.Plane(revitPlaneForArrow.Origin.PointFromRevit(), revitPlaneForArrow.Normal.VectorFromRevit()); + + List boundaryPoints = projectedBoundary.IControlPoints(); + if (boundaryPoints == null || boundaryPoints.Count < 2) return spanDirectionLine; + + double highestZ = double.MinValue; + double lowestZ = double.MaxValue; + BH.oM.Geometry.Point highestPoint = null; + BH.oM.Geometry.Point lowestPoint = null; + + foreach (BH.oM.Geometry.Point pt in boundaryPoints) + { + BH.oM.Geometry.Point pointOnSlab = slabPlane.ClosestPoint(pt); + double z = pointOnSlab.Z; + if (z > highestZ) + { + highestZ = z; + highestPoint = pt; } + if (z < lowestZ) + { + lowestZ = z; + lowestPoint = pt; + } + } + + if (highestPoint == null || lowestPoint == null) return spanDirectionLine; + + XYZ highestXYZ = highestPoint.ToRevit(); + XYZ lowestXYZ = lowestPoint.ToRevit(); + + XYZ highestProjected = highestXYZ.Project(revitPlaneForArrow); + XYZ lowestProjected = lowestXYZ.Project(revitPlaneForArrow); + + return Autodesk.Revit.DB.Line.CreateBound(highestProjected, lowestProjected); + } + + private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveLoop newOutline, ElementId floorTypeId, ElementId levelId, Autodesk.Revit.DB.Line slopeArrowLine, double tan, double newOffset, RevitSettings settings) + { + if (slopeArrowLine == null || slopeArrowLine.Length < settings.DistanceTolerance) + { + Autodesk.Revit.DB.Floor newFloor = Autodesk.Revit.DB.Floor.Create(doc, new List { newOutline }, floorTypeId, levelId); + if (newFloor != null) + { + newFloor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); + doc.Regenerate(); + } + return newFloor; + } + + Autodesk.Revit.DB.Floor newFloorWithSlope = Autodesk.Revit.DB.Floor.Create(doc, new List { newOutline }, floorTypeId, levelId, true, slopeArrowLine, -tan); + if (newFloorWithSlope != null) + { + newFloorWithSlope.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); + doc.Regenerate(); + } + return newFloorWithSlope; + } + + private static void VerifySlopeArrow(Document doc, Autodesk.Revit.DB.Floor newFloor, CurveLoop newOutline) + { + Sketch newFloorSketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == newFloor.Id); + if (newFloorSketch == null) return; + + int lineCount = newFloorSketch.GetAllElements().Count(elemId => + { + ModelCurve modelCurve = doc.GetElement(elemId) as ModelCurve; + return modelCurve != null && modelCurve.GeometryCurve is Autodesk.Revit.DB.Line; }); - return true; + if (lineCount <= newOutline.Count()) + BH.Engine.Base.Compute.RecordWarning($"Slope arrow may not have been created properly for floor {newFloor.Id.Value()}. Expected slope arrow in sketch but found {lineCount} lines (boundary has {newOutline.Count()} curves)."); } private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Point location, Basis orientation, RevitSettings settings) From 1ce7f7a512099d367302e40a1df71c0ac19d09dd Mon Sep 17 00:00:00 2001 From: wojciech buda Date: Mon, 2 Feb 2026 10:04:29 +0100 Subject: [PATCH 5/7] Update description attributes and correct slope calculation methods --- Revit_Core_Adapter/AdapterActions/Push.cs | 117 ++++++++---------- Revit_Core_Engine/Modify/SetLocation.cs | 104 ++++++++++++---- .../SketchUpdateFailurePreprocessor.cs | 11 ++ .../Objects/SketchUpdateQueue.cs | 8 ++ 4 files changed, 147 insertions(+), 93 deletions(-) diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index d93c6cd13..798399a70 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -152,103 +152,88 @@ private List PushToRevit(Document document, IEnumerable pushed = new List(); - //TODO: wrap into transaction group maybe? - using (Transaction transaction = new Transaction(document, transactionName)) + using (TransactionGroup tg = new TransactionGroup(document, transactionName)) { - transaction.Start(); + tg.Start(); - if (pushType == PushType.CreateOnly) - pushed = Create(objects, pushConfig); - else if (pushType == PushType.CreateNonExisting) + using (Transaction transaction = new Transaction(document, transactionName)) { - IEnumerable toCreate = objects.Where(x => x.Element(document) == null); - pushed = Create(toCreate, pushConfig); - } - else if (pushType == PushType.DeleteThenCreate) - { - List toCreate = new List(); - foreach (IBHoMObject obj in objects) + transaction.Start(); + + if (pushType == PushType.CreateOnly) + pushed = Create(objects, pushConfig); + else if (pushType == PushType.CreateNonExisting) { - Element element = obj.Element(document); - if (element == null || Delete(element.Id, document, false).Count() != 0) - toCreate.Add(obj); + IEnumerable toCreate = objects.Where(x => x.Element(document) == null); + pushed = Create(toCreate, pushConfig); } + else if (pushType == PushType.DeleteThenCreate) + { + List toCreate = new List(); + foreach (IBHoMObject obj in objects) + { + Element element = obj.Element(document); + if (element == null || Delete(element.Id, document, false).Count() != 0) + toCreate.Add(obj); + } - pushed = Create(toCreate, pushConfig); - } - else if (pushType == PushType.UpdateOnly) - { - foreach (IBHoMObject obj in objects) + pushed = Create(toCreate, pushConfig); + } + else if (pushType == PushType.UpdateOnly) { - Element element = obj.Element(document); - if (element != null && Update(element, obj, pushConfig)) - pushed.Add(obj); + foreach (IBHoMObject obj in objects) + { + Element element = obj.Element(document); + if (element != null && Update(element, obj, pushConfig)) + pushed.Add(obj); + } } - } - else if (pushType == PushType.UpdateOrCreateOnly) - { - List toCreate = new List(); - foreach (IBHoMObject obj in objects) + else if (pushType == PushType.UpdateOrCreateOnly) { - Element element = obj.Element(document); - if (element != null && Update(element, obj, pushConfig)) - pushed.Add(obj); - else if (element == null || Delete(element.Id, document, false).Count() != 0) - toCreate.Add(obj); + List toCreate = new List(); + foreach (IBHoMObject obj in objects) + { + Element element = obj.Element(document); + if (element != null && Update(element, obj, pushConfig)) + pushed.Add(obj); + else if (element == null || Delete(element.Id, document, false).Count() != 0) + toCreate.Add(obj); + } + + pushed.AddRange(Create(toCreate, pushConfig)); } - pushed.AddRange(Create(toCreate, pushConfig)); + transaction.Commit(); } - transaction.Commit(); - } - - if (SketchUpdateQueue.SketchUpdates.Count > 0) - { - using (TransactionGroup tg = new TransactionGroup(document, "Update floor sketches")) + if (SketchUpdateQueue.SketchUpdates.Count > 0) { - tg.Start(); - - int successCount = 0; - int errorCount = 0; - List errors = new List(); + bool hasErrors = false; foreach (Action call in SketchUpdateQueue.SketchUpdates) { try { call.Invoke(); - successCount++; } catch (Exception ex) { - errorCount++; + hasErrors = true; string errorMsg = $"Sketch update failed: {ex.Message}"; if (ex.InnerException != null) errorMsg += $" Inner: {ex.InnerException.Message}"; - errors.Add(errorMsg); BH.Engine.Base.Compute.RecordError(errorMsg); } } - if (errorCount == 0) - { - tg.Assimilate(); - } + if (hasErrors) + tg.RollBack(); else - { - - if (successCount > 0) - { - BH.Engine.Base.Compute.RecordWarning($"Some sketch updates failed ({errorCount} of {SketchUpdateQueue.SketchUpdates.Count}). Partial updates may have been applied."); - tg.Assimilate(); - } - else - { - tg.RollBack(); - BH.Engine.Base.Compute.RecordError($"All sketch updates failed. Rolled back all changes."); - } - } + tg.Assimilate(); + } + else + { + tg.Assimilate(); } } diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index ee7ab8c9a..b19c8b277 100644 --- a/Revit_Core_Engine/Modify/SetLocation.cs +++ b/Revit_Core_Engine/Modify/SetLocation.cs @@ -447,11 +447,11 @@ public static bool SetLocation(this HostObject element, BH.oM.Physical.Elements. /***************************************************/ - //[Description("Sets the location of a given Revit HostObject based on a given BHoM ISurface.")] - //[Input("element", "Revit HostObject to be modified.")] - //[Input("bHoMObject", "BHoM ISurface 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 HostObject has been successfully set.")] + [Description("Sets the location of a given Revit Floor based on a given BHoM Floor.")] + [Input("element", "Revit Floor to be modified.")] + [Input("bHoMObject", "BHoM Floor acting as a source of information about the new location.")] + [Input("settings", "Revit adapter settings to be used while performing the operation.")] + [Output("success", "True if location of the input Revit Floor has been successfully set.")] public static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Physical.Elements.Floor bHoMObject, RevitSettings settings) { return element.SetLocation(bHoMObject.Location, settings); @@ -535,35 +535,40 @@ private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geom BH.oM.Geometry.Plane slabPlane = ps.FitPlane(); double newOffset = (ps.IBounds().Min.Z.FromSI(SpecTypeId.Length) - level.ProjectElevation); - bool hasSlope = CalculateSlopeInfo(slabPlane, bhomPlane, projectedBoundary, settings, out Autodesk.Revit.DB.Line spanDirectionLine, out double tan); + (bool hasSlope, Autodesk.Revit.DB.Line spanDirectionLine, double tan) = CalculateSlopeInfo(slabPlane, bhomPlane, projectedBoundary, settings); - SketchUpdateQueue.SketchUpdates.Enqueue(() => + if (HasFloorChanged(element, sketch, newOutline, newOffset, hasSlope, settings)) { - Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; - if (floor == null || !floor.IsValidObject) return; + SketchUpdateQueue.SketchUpdates.Enqueue(() => + { + Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; + if (floor == null || !floor.IsValidObject) return; - Sketch floorSketch = doc.GetElement(sketch.Id) as Sketch; - if (floorSketch == null) return; + Sketch floorSketch = doc.GetElement(sketch.Id) as Sketch; + if (floorSketch == null) return; - try - { - UpdateSketchOutline(doc, sketch.Id, newOutline); - UpdateFloorOffsetAndSlope(doc, floor, sketch.Id, hasSlope, spanDirectionLine, tan, newOutline, slabPlane, projectedBoundary, newOffset, floorId, settings); - } - catch (Exception ex) - { - BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}"); - throw; - } - }); + try + { + UpdateSketchOutline(doc, sketch.Id, newOutline); + UpdateFloorOffsetAndSlope(doc, floor, sketch.Id, hasSlope, spanDirectionLine, tan, newOutline, slabPlane, projectedBoundary, newOffset, floorId, settings); + } + catch (Exception ex) + { + BH.Engine.Base.Compute.RecordError($"Failed to update floor sketch for ElementId {floorId.Value()}: {ex.Message}"); + throw; + } + }); + } return true; } - private static bool CalculateSlopeInfo(BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.Plane bhomPlane, BH.oM.Geometry.ICurve projectedBoundary, RevitSettings settings, out Autodesk.Revit.DB.Line spanDirectionLine, out double tan) + /***************************************************/ + + private static (bool hasSlope, Autodesk.Revit.DB.Line spanDirectionLine, double tan) CalculateSlopeInfo(BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.Plane bhomPlane, BH.oM.Geometry.ICurve projectedBoundary, RevitSettings settings) { - spanDirectionLine = null; - tan = 0.0; + Autodesk.Revit.DB.Line spanDirectionLine = null; + double tan = 0.0; XYZ n = slabPlane.Normal.ToRevit().Normalize(); double dot = Math.Abs(n.DotProduct(XYZ.BasisZ)); double slopeAngleRad = Math.Acos(Math.Min(1.0, Math.Max(-1.0, dot))); @@ -572,7 +577,9 @@ private static bool CalculateSlopeInfo(BH.oM.Geometry.Plane slabPlane, BH.oM.Geo if (hasSlope) { Vector normal = slabPlane.Normal; - if (normal.Z < 0) normal = -slabPlane.Normal; + if (normal.Z < 0) + normal = -slabPlane.Normal; + double angle = normal.Angle(Vector.ZAxis); tan = Math.Tan(angle); Vector dir = normal.Project(oM.Geometry.Plane.XY); @@ -580,9 +587,11 @@ private static bool CalculateSlopeInfo(BH.oM.Geometry.Plane slabPlane, BH.oM.Geo XYZ start = intersectionLine.ClosestPoint(projectedBoundary.IStartPoint(), true).ToRevit(); spanDirectionLine = Autodesk.Revit.DB.Line.CreateBound(start, start + dir.ToRevit().Normalize()); } - return hasSlope; + return (hasSlope, spanDirectionLine, tan); } + /***************************************************/ + private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveLoop newOutline) { using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch")) @@ -608,6 +617,8 @@ private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveL } } + /***************************************************/ + private static void UpdateFloorOffsetAndSlope(Document doc, Autodesk.Revit.DB.Floor floor, ElementId sketchId, bool hasSlope, Autodesk.Revit.DB.Line spanDirectionLine, double tan, CurveLoop newOutline, BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.ICurve projectedBoundary, double newOffset, ElementId floorId, RevitSettings settings) { using (Transaction tOffset = new Transaction(doc, "Update floor offset and slope")) @@ -640,6 +651,8 @@ private static void UpdateFloorOffsetAndSlope(Document doc, Autodesk.Revit.DB.Fl tOffset.Commit(); } } + + /***************************************************/ private static Autodesk.Revit.DB.Line CalculateSlopeArrowLine(Document doc, ElementId sketchId, BH.oM.Geometry.Plane slabPlane, BH.oM.Geometry.ICurve projectedBoundary, CurveLoop newOutline, Autodesk.Revit.DB.Line spanDirectionLine, ElementId floorId, RevitSettings settings) { @@ -685,6 +698,8 @@ private static Autodesk.Revit.DB.Line CalculateSlopeArrowLine(Document doc, Elem return Autodesk.Revit.DB.Line.CreateBound(highestProjected, lowestProjected); } + /***************************************************/ + private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveLoop newOutline, ElementId floorTypeId, ElementId levelId, Autodesk.Revit.DB.Line slopeArrowLine, double tan, double newOffset, RevitSettings settings) { if (slopeArrowLine == null || slopeArrowLine.Length < settings.DistanceTolerance) @@ -707,6 +722,8 @@ private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveL return newFloorWithSlope; } + /***************************************************/ + private static void VerifySlopeArrow(Document doc, Autodesk.Revit.DB.Floor newFloor, CurveLoop newOutline) { Sketch newFloorSketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == newFloor.Id); @@ -722,6 +739,39 @@ private static void VerifySlopeArrow(Document doc, Autodesk.Revit.DB.Floor newFl BH.Engine.Base.Compute.RecordWarning($"Slope arrow may not have been created properly for floor {newFloor.Id.Value()}. Expected slope arrow in sketch but found {lineCount} lines (boundary has {newOutline.Count()} curves)."); } + /***************************************************/ + private static bool HasFloorChanged(Autodesk.Revit.DB.Floor floor, Sketch sketch, CurveLoop newOutline, double newOffset, bool hasSlope, RevitSettings settings) + { + double currentOffset = floor.LookupParameterDouble(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM); + if (Math.Abs(currentOffset - newOffset.FromSI(SpecTypeId.Length)) > settings.DistanceTolerance) + return true; + + IList sketchElements = sketch.GetAllElements(); + if (sketchElements == null || sketchElements.Count == 0) + return true; + + List currentCurves = new List(); + foreach (ElementId elemId in sketchElements) + { + ModelCurve modelCurve = floor.Document.GetElement(elemId) as ModelCurve; + if (modelCurve != null && modelCurve.GeometryCurve != null && !(modelCurve.GeometryCurve is Autodesk.Revit.DB.Line && modelCurve.GeometryCurve.Length < 1.1)) + currentCurves.Add(modelCurve.GeometryCurve); + } + + if (currentCurves.Count != newOutline.Count()) + return true; + + for (int i = 0; i < currentCurves.Count; i++) + { + if (!currentCurves[i].IsSimilar(newOutline.ElementAt(i), settings)) + return true; + } + + return false; + } + + /***************************************************/ + private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Point location, Basis orientation, RevitSettings settings) { LocationPoint elementLocation = element.Location as LocationPoint; diff --git a/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs index 238ad2432..0a7b7192b 100644 --- a/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs +++ b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs @@ -1,12 +1,23 @@ using Autodesk.Revit.DB; +using BH.oM.Base.Attributes; +using System.ComponentModel; namespace BH.Revit.Engine.Core { internal class SketchUpdateFailurePreprocessor : IFailuresPreprocessor { + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + [Description("Preprocesses failures that occur during sketch updates, allowing the operation to continue despite warnings or errors.")] + [Input("failuresAccessor", "Revit failures accessor object containing failure messages from the sketch update operation.")] + [Output("failureProcessingResult", "The result of the failure processing, indicating whether to continue with the operation.")] public FailureProcessingResult PreprocessFailures(FailuresAccessor failuresAccessor) { return FailureProcessingResult.Continue; } + + /***************************************************/ } } diff --git a/Revit_Core_Engine/Objects/SketchUpdateQueue.cs b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs index 5cbce8c04..b74d02b00 100644 --- a/Revit_Core_Engine/Objects/SketchUpdateQueue.cs +++ b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs @@ -1,10 +1,18 @@ using System; using System.Collections.Generic; +using System.ComponentModel; namespace BH.Revit.Engine.Core { public static class SketchUpdateQueue { + /***************************************************/ + /**** Public Properties ****/ + /***************************************************/ + + [Description("Queue containing actions to update floor sketches. These updates are deferred until after the main push transaction to avoid conflicts with sketch editing.")] public static Queue SketchUpdates { get; } = new Queue(); + + /***************************************************/ } } From 5588c7144f60c512bb2aa45eeef9020779309ef5 Mon Sep 17 00:00:00 2001 From: wojciech buda Date: Thu, 5 Feb 2026 10:57:39 +0100 Subject: [PATCH 6/7] Tweaks - moved IsValid method to Query and changed errorMsg to warningMsg --- Revit_Core_Adapter/AdapterActions/Push.cs | 21 ++++------ Revit_Core_Engine/Modify/SetLocation.cs | 32 +++++++-------- Revit_Core_Engine/Query/IsValid.cs | 50 +++++++++++++++++++++++ 3 files changed, 74 insertions(+), 29 deletions(-) create mode 100644 Revit_Core_Engine/Query/IsValid.cs diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index 798399a70..d014f8c7f 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -150,6 +150,9 @@ public override List Push(IEnumerable objects, string tag = "", private List PushToRevit(Document document, IEnumerable objects, PushType pushType, RevitPushConfig pushConfig, string transactionName) { + + SketchUpdateQueue.SketchUpdates.Clear(); + List pushed = new List(); using (TransactionGroup tg = new TransactionGroup(document, transactionName)) @@ -208,8 +211,6 @@ private List PushToRevit(Document document, IEnumerable 0) { - bool hasErrors = false; - foreach (Action call in SketchUpdateQueue.SketchUpdates) { try @@ -218,26 +219,20 @@ private List PushToRevit(Document document, IEnumerable { Autodesk.Revit.DB.Floor floor = doc.GetElement(floorId) as Autodesk.Revit.DB.Floor; - if (floor == null || !floor.IsValidObject) return; + if (floor == null || !floor.IsValidObject) + return; Sketch floorSketch = doc.GetElement(sketch.Id) as Sketch; - if (floorSketch == null) return; + if (floorSketch == null) + return; try { @@ -606,6 +608,7 @@ private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveL IList existingElements = currentSketch.GetAllElements(); if (existingElements != null && existingElements.Count > 0) doc.Delete(existingElements); + SketchPlane sketchPlane = currentSketch.SketchPlane; if (sketchPlane != null) foreach (Curve curve in newOutline) @@ -692,9 +695,19 @@ private static Autodesk.Revit.DB.Line CalculateSlopeArrowLine(Document doc, Elem XYZ highestXYZ = highestPoint.ToRevit(); XYZ lowestXYZ = lowestPoint.ToRevit(); + if (!Query.IsValid(highestXYZ) || !Query.IsValid(lowestXYZ)) + { + return spanDirectionLine; + } + XYZ highestProjected = highestXYZ.Project(revitPlaneForArrow); XYZ lowestProjected = lowestXYZ.Project(revitPlaneForArrow); + if (!Query.IsValid(highestProjected) || !Query.IsValid(lowestProjected)) + { + return spanDirectionLine; + } + return Autodesk.Revit.DB.Line.CreateBound(highestProjected, lowestProjected); } @@ -883,21 +896,8 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin return success; } - - /***************************************************/ - - private static bool IsValidPoint(XYZ point) - { - if (point == null) - return false; - - return !double.IsNaN(point.X) && !double.IsInfinity(point.X) && - !double.IsNaN(point.Y) && !double.IsInfinity(point.Y) && - !double.IsNaN(point.Z) && !double.IsInfinity(point.Z); - } - + /***************************************************/ - private static bool UpdateRotationOfVerticalElement(this FamilyInstance element, IFramingElement bhomElement, RevitSettings settings) { bool updated = false; diff --git a/Revit_Core_Engine/Query/IsValid.cs b/Revit_Core_Engine/Query/IsValid.cs new file mode 100644 index 000000000..e34959ba0 --- /dev/null +++ b/Revit_Core_Engine/Query/IsValid.cs @@ -0,0 +1,50 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * The Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using Autodesk.Revit.DB; +using BH.oM.Base.Attributes; +using System.ComponentModel; + +namespace BH.Revit.Engine.Core +{ + public static partial class Query + { + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + [Description("Checks whether the given XYZ point is valid (not null and does not contain NaN or Infinity values).")] + [Input("point", "XYZ point to be checked for validity.")] + [Output("isValid", "True if the input XYZ point is valid (not null and all coordinates are finite numbers), otherwise false.")] + public static bool IsValid(XYZ point) + { + if (point == null) + return false; + + return !double.IsNaN(point.X) && !double.IsInfinity(point.X) && + !double.IsNaN(point.Y) && !double.IsInfinity(point.Y) && + !double.IsNaN(point.Z) && !double.IsInfinity(point.Z); + } + + /***************************************************/ + } +} From 244a8bccc5dece516ad993b3774050c3afc6342f Mon Sep 17 00:00:00 2001 From: wojciech buda Date: Tue, 10 Feb 2026 10:36:10 +0100 Subject: [PATCH 7/7] Tweaks and clean up --- Revit_Core_Adapter/AdapterActions/Push.cs | 16 +++++++--------- Revit_Core_Engine/Modify/SetLocation.cs | 23 ++++++++++++++++------- Revit_Core_Engine/Query/IsValid.cs | 2 +- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index d014f8c7f..a71b7d660 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -132,6 +132,7 @@ public override List Push(IEnumerable objects, string tag = "", { List distinctNames = group.Select(x => x.Name).Distinct().ToList(); if (distinctNames.Count > 1) + BH.Engine.Base.Compute.RecordWarning($"BHoM objects with names {string.Join(", ", distinctNames)} correspond to the same Revit assembly that has finally been named {group.Key.AssemblyTypeName}."); } } @@ -219,19 +220,16 @@ private List PushToRevit(Document document, IEnumerable boundaryPoints = projectedBoundary.IControlPoints(); - if (boundaryPoints == null || boundaryPoints.Count < 2) return spanDirectionLine; + if (boundaryPoints == null || boundaryPoints.Count < 2) + return spanDirectionLine; double highestZ = double.MinValue; double lowestZ = double.MaxValue; @@ -690,7 +693,8 @@ private static Autodesk.Revit.DB.Line CalculateSlopeArrowLine(Document doc, Elem } } - if (highestPoint == null || lowestPoint == null) return spanDirectionLine; + if (highestPoint == null || lowestPoint == null) + return spanDirectionLine; XYZ highestXYZ = highestPoint.ToRevit(); XYZ lowestXYZ = lowestPoint.ToRevit(); @@ -723,6 +727,7 @@ private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveL newFloor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); doc.Regenerate(); } + return newFloor; } @@ -732,6 +737,7 @@ private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveL newFloorWithSlope.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); doc.Regenerate(); } + return newFloorWithSlope; } @@ -740,7 +746,8 @@ private static Autodesk.Revit.DB.Floor CreateFloorWithSlope(Document doc, CurveL private static void VerifySlopeArrow(Document doc, Autodesk.Revit.DB.Floor newFloor, CurveLoop newOutline) { Sketch newFloorSketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == newFloor.Id); - if (newFloorSketch == null) return; + if (newFloorSketch == null) + return; int lineCount = newFloorSketch.GetAllElements().Count(elemId => { @@ -853,6 +860,7 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin newLocation = linkTransform.OfPoint(newLocation); if (ir.Distance > settings.DistanceTolerance) + BH.Engine.Base.Compute.RecordWarning($"The location point used on update of a family instance has been snapped to its host face. ElementId: {element.Id.Value()}"); } } @@ -883,6 +891,7 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin } if (1 - Math.Abs(revitNormal.DotProduct(bHoMNormal)) > settings.AngleTolerance) + BH.Engine.Base.Compute.RecordWarning($"The orientation applied to the family instance on update has different normal than the original one. Only in-plane rotation has been applied, the orientation out of plane has been ignored. ElementId: {element.Id.Value()}"); double angle = transform.BasisX.AngleOnPlaneTo(newX, revitNormal); diff --git a/Revit_Core_Engine/Query/IsValid.cs b/Revit_Core_Engine/Query/IsValid.cs index e34959ba0..5ec8854a1 100644 --- a/Revit_Core_Engine/Query/IsValid.cs +++ b/Revit_Core_Engine/Query/IsValid.cs @@ -35,7 +35,7 @@ public static partial class Query [Description("Checks whether the given XYZ point is valid (not null and does not contain NaN or Infinity values).")] [Input("point", "XYZ point to be checked for validity.")] [Output("isValid", "True if the input XYZ point is valid (not null and all coordinates are finite numbers), otherwise false.")] - public static bool IsValid(XYZ point) + public static bool IsValid(this XYZ point) { if (point == null) return false;