diff --git a/Revit_Core_Adapter/AdapterActions/Push.cs b/Revit_Core_Adapter/AdapterActions/Push.cs index 22e8eba2b..a71b7d660 100644 --- a/Revit_Core_Adapter/AdapterActions/Push.cs +++ b/Revit_Core_Adapter/AdapterActions/Push.cs @@ -27,6 +27,7 @@ using BH.oM.Adapters.Revit.Elements; using BH.oM.Base; using BH.Revit.Engine.Core; +using System; using System.Collections.Generic; using System.Linq; @@ -60,7 +61,7 @@ public override List 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. @@ -131,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}."); } } @@ -149,55 +151,85 @@ 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 (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); } - - pushed = Create(toCreate, pushConfig); - } - else if (pushType == PushType.UpdateOnly) - { - foreach (IBHoMObject obj in objects) + 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) { - 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) + { + 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)); + } + + transaction.Commit(); } - else if (pushType == PushType.UpdateOrCreateOnly) + + if (SketchUpdateQueue.SketchUpdates.Count > 0) { - List toCreate = new List(); - foreach (IBHoMObject obj in objects) + foreach (Action call in SketchUpdateQueue.SketchUpdates) { - 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); + try + { + call.Invoke(); + } + catch (Exception ex) + { + string errorMsg = $"Sketch update failed: {ex.Message}"; + if (ex.InnerException != null) + errorMsg += $" Inner: {ex.InnerException.Message}"; + + BH.Engine.Base.Compute.RecordError(errorMsg); + } } - - pushed.AddRange(Create(toCreate, pushConfig)); } - transaction.Commit(); + tg.Assimilate(); } return pushed; diff --git a/Revit_Core_Engine/Modify/SetLocation.cs b/Revit_Core_Engine/Modify/SetLocation.cs index 1fa10dc84..cbf63f53f 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 Floor based on a given BHoM Floor.")] + [Input("element", "Revit Floor to be modified.")] + [Input("bHoMObject", "BHoM Floor acting as a source of information about the new location.")] + [Input("settings", "Revit adapter settings to be used while performing the operation.")] + [Output("success", "True if location of the input Revit Floor has been successfully set.")] + public static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Physical.Elements.Floor bHoMObject, RevitSettings settings) + { + return element.SetLocation(bHoMObject.Location, settings); + } + /***************************************************/ /**** Fallback Methods ****/ @@ -493,6 +505,293 @@ public static bool ISetLocation(this Element element, IBHoMObject bHoMObject, Re /**** Private Methods ****/ /***************************************************/ + private static bool SetLocation(this Autodesk.Revit.DB.Floor element, BH.oM.Geometry.ISurface location, RevitSettings settings) + { + PlanarSurface ps = location as PlanarSurface; + if (ps == null) + { + BH.Engine.Base.Compute.RecordWarning("Floor location must be a PlanarSurface"); + return false; + } + + if (ps.InternalBoundaries != null && ps.InternalBoundaries.Count > 0) + BH.Engine.Base.Compute.RecordWarning($"Floor has openings which will be ignored during sketch update. ElementId: {element.Id.Value()}"); + + Document doc = element.Document; + ElementId floorId = element.Id; + Sketch sketch = new FilteredElementCollector(doc).OfClass(typeof(Sketch)).Cast().FirstOrDefault(s => s.OwnerId == floorId); + if (sketch?.Id == null || sketch.SketchPlane == null) + { + BH.Engine.Base.Compute.RecordError($"Floor sketch not found. ElementId: {element.Id.Value()}"); + return false; + } + + SketchPlane sketchPlane = sketch.SketchPlane; + 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, Autodesk.Revit.DB.Line spanDirectionLine, double tan) = CalculateSlopeInfo(slabPlane, bhomPlane, projectedBoundary, settings); + + if (HasFloorChanged(element, sketch, newOutline, newOffset, hasSlope, settings)) + { + 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}"); + return; + } + }); + } + + return true; + } + + /***************************************************/ + + 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) + { + 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))); + bool hasSlope = slopeAngleRad > settings.AngleTolerance; + + if (hasSlope) + { + Vector 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); + BH.oM.Geometry.Line intersectionLine = slabPlane.PlaneIntersection(bhomPlane); + XYZ start = intersectionLine.ClosestPoint(projectedBoundary.IStartPoint(), true).ToRevit(); + spanDirectionLine = Autodesk.Revit.DB.Line.CreateBound(start, start + dir.ToRevit().Normalize()); + } + + return (hasSlope, spanDirectionLine, tan); + } + + /***************************************************/ + + private static void UpdateSketchOutline(Document doc, ElementId sketchId, CurveLoop newOutline) + { + using (SketchEditScope ses = new SketchEditScope(doc, "Update floor sketch")) + { + ses.Start(sketchId); + using (Transaction t = new Transaction(doc, "Modify sketch profile")) + { + t.Start(); + Sketch currentSketch = doc.GetElement(sketchId) as Sketch; + if (currentSketch != null) + { + IList existingElements = currentSketch.GetAllElements(); + if (existingElements != null && existingElements.Count > 0) + doc.Delete(existingElements); + + SketchPlane sketchPlane = currentSketch.SketchPlane; + if (sketchPlane != null) + foreach (Curve curve in newOutline) + doc.Create.NewModelCurve(curve, sketchPlane); + } + t.Commit(); + } + ses.Commit(new SketchUpdateFailurePreprocessor()); + } + } + + /***************************************************/ + + private static 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) + { + 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) + { + 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); + } + else if (floorType == null || floorLevel == null) + { + floor.SetParameter(BuiltInParameter.FLOOR_HEIGHTABOVELEVEL_PARAM, newOffset, false); + doc.Regenerate(); + } + } + else + { + 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(); + + 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); + } + + /***************************************************/ + + 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; + }); + + 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 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; @@ -561,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()}"); } } @@ -591,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); @@ -604,9 +905,8 @@ private static bool SetLocation(this FamilyInstance element, BH.oM.Geometry.Poin return success; } - + /***************************************************/ - private static bool UpdateRotationOfVerticalElement(this FamilyInstance element, IFramingElement bhomElement, RevitSettings settings) { bool updated = false; diff --git a/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs new file mode 100644 index 000000000..0a7b7192b --- /dev/null +++ b/Revit_Core_Engine/Objects/SketchUpdateFailurePreprocessor.cs @@ -0,0 +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 new file mode 100644 index 000000000..b74d02b00 --- /dev/null +++ b/Revit_Core_Engine/Objects/SketchUpdateQueue.cs @@ -0,0 +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(); + + /***************************************************/ + } +} diff --git a/Revit_Core_Engine/Query/IsValid.cs b/Revit_Core_Engine/Query/IsValid.cs new file mode 100644 index 000000000..5ec8854a1 --- /dev/null +++ b/Revit_Core_Engine/Query/IsValid.cs @@ -0,0 +1,50 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2026, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * The Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using Autodesk.Revit.DB; +using BH.oM.Base.Attributes; +using System.ComponentModel; + +namespace BH.Revit.Engine.Core +{ + public static partial class Query + { + /***************************************************/ + /**** Public methods ****/ + /***************************************************/ + + [Description("Checks whether the given XYZ point is valid (not null and does not contain NaN or Infinity values).")] + [Input("point", "XYZ point to be checked for validity.")] + [Output("isValid", "True if the input XYZ point is valid (not null and all coordinates are finite numbers), otherwise false.")] + public static bool IsValid(this XYZ point) + { + if (point == null) + return false; + + return !double.IsNaN(point.X) && !double.IsInfinity(point.X) && + !double.IsNaN(point.Y) && !double.IsInfinity(point.Y) && + !double.IsNaN(point.Z) && !double.IsInfinity(point.Z); + } + + /***************************************************/ + } +}