From 2cc8dee8b324747ac618cee8bfb561bf9bc01e6e Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Fri, 19 Jan 2024 18:50:51 +0000 Subject: [PATCH 01/19] First stub of IsValidComparisonConfig --- BHoM_Engine/Query/IsValidComparisonConfig.cs | 36 ++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 BHoM_Engine/Query/IsValidComparisonConfig.cs diff --git a/BHoM_Engine/Query/IsValidComparisonConfig.cs b/BHoM_Engine/Query/IsValidComparisonConfig.cs new file mode 100644 index 000000000..900daba63 --- /dev/null +++ b/BHoM_Engine/Query/IsValidComparisonConfig.cs @@ -0,0 +1,36 @@ +using BH.oM.Base; +using BH.oM.Geometry; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BH.Engine.Base +{ + public static partial class Query + { + public static bool IsValidComparisonConfig(BaseComparisonConfig cc) + { + if (cc == null) { return false; } + + bool isValid = true; + + if (cc.UseGeometryHash) + { + if (cc.TypeExceptions.Where(t => typeof(IGeometry).IsAssignableFrom(t)).Any()) + return InvalidComparisonConfig(nameof(cc.TypeExceptions)); + + if (cc.PropertyExceptions.Where(p => p.Contains("Geometry")).Any()) + return InvalidComparisonConfig(nameof(cc.TypeExceptions)); + } + } + + private static bool InvalidComparisonConfig(string invalidProp) + { + BH.Engine.Base.Compute.RecordError($"The comparison config is not valid because it has {nameof(BaseComparisonConfig.UseGeometryHash)} set to true while specifying also a {invalidProp} that applies to geometric types." + + $"If you want to specify this {invalidProp}, you need to toggle off {nameof(BaseComparisonConfig.UseGeometryHash)}."); + + return false; + } + } +} From ca77a8683e5d2ec2154f1884a5838e586e445715 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Tue, 23 Jan 2024 12:04:30 +0000 Subject: [PATCH 02/19] wip --- BHoM_Engine/Query/Hash.cs | 31 ++++++++++------ BHoM_Engine/Query/IsValidComparisonConfig.cs | 38 ++++++++++++++++---- Geometry_Engine/Query/GeometryHash.cs | 1 + 3 files changed, 52 insertions(+), 18 deletions(-) diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index b876996bb..8ec7793b9 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -43,6 +43,8 @@ public static partial class Query /**** Public Methods ****/ /***************************************************/ + private static bool m_isCompatibleWithGeometryHash = true; + [Description("Computes a Hash code for the iObject. The hash uniquely represents an object's state, based on its properties and their values. It can be used for comparisons." + "\nYou can change how the hash is computed by changing the settings in the ComparisonConfig.")] [Input("iObj", "iObject the hash code should be calculated for.")] @@ -73,10 +75,13 @@ public static string Hash(this IObject iObj, BaseComparisonConfig comparisonConf // Parse the ComparisonConfig's `PropertiesToConsider` and `PropertyExceptions` and get them all as Full Names. Modify.PropertyNamesToFullNames(cc, iObj); + // Verify that no numerical approximation is requested for objects belonging to Geometrical types. + m_isCompatibleWithGeometryHash = cc.IsCompatibleWithGeometryHash(); + // ----- HASH ----- // Compute the defining string. - string hashString = HashString(iObj, cc, 0); + string hashString = HashString(iObj, cc, 0, typeof(System.Object)); if (string.IsNullOrWhiteSpace(hashString)) { @@ -117,7 +122,7 @@ private static string SHA256Hash(string str) [Input("cc", "HashConfig, options for the hash calculation.")] [Input("nestingLevel", "Nesting level of the property.")] [Input("currentPropertyFullName", "(Optional) Indicates the 'property path' of the current object, e.g. `BH.oM.Structure.Elements.Bar.Start.Point.X`")] - private static string HashString(object obj, BaseComparisonConfig cc, int nestingLevel, string currentPropertyFullName = null) + private static string HashString(object obj, BaseComparisonConfig cc, int nestingLevel, Type parentType, string currentPropertyFullName = null) { string definingString = ""; @@ -142,7 +147,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin { return ""; } - else if (type.IsNumeric() && type.BaseType != typeof(System.Enum)) + else if (type.IsNumeric(enumsAsNumbers: false)) { // If we didn't specify any custom tolerance/significant figures, just return the input. if (cc.NumericTolerance == double.MinValue && cc.SignificantFigures == int.MaxValue @@ -171,7 +176,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type.IsArray) { foreach (var element in (obj as dynamic)) - definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, currentPropertyFullName); + definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, type, currentPropertyFullName); } else if (typeof(IDictionary).IsAssignableFrom(type)) { @@ -193,9 +198,6 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin // we want to consider its keys as if they were object properties for UX/UI consistency. cc.CustomdataKeysExceptions.AddRange(cc.PropertyExceptions); cc.CustomdataKeysToConsider.AddRange(cc.PropertiesToConsider); - - cc.CustomdataKeysExceptions = cc.CustomdataKeysExceptions.Distinct().ToList(); - cc.CustomdataKeysToConsider = cc.CustomdataKeysToConsider.Distinct().ToList(); } // Get the custom data Key, so we can check if it belongs to the exceptions. @@ -209,13 +211,13 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin continue; } - definingString += $"\n{tabs}" + $"[{entry.Key.GetType().FullName}]\n{tabs}{entry.Key}:\n { HashString(entry.Value, cc, nestingLevel + 1, currentPropertyFullName)}"; + definingString += $"\n{tabs}" + $"[{entry.Key.GetType().FullName}]\n{tabs}{entry.Key}:\n {HashString(entry.Value, cc, nestingLevel + 1, type, currentPropertyFullName)}"; } } else if (typeof(IEnumerable).IsAssignableFrom(type)) { foreach (var element in (obj as dynamic)) - definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, currentPropertyFullName); + definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, type, currentPropertyFullName); } else if (type.FullName.Contains("System.Collections.Generic.ObjectEqualityComparer`1")) { @@ -224,7 +226,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type == typeof(System.Data.DataTable)) { DataTable dt = obj as DataTable; - return definingString += $"{type.FullName} {string.Join(", ", dt.Columns.OfType().Select(c => c.ColumnName))}\n{tabs}" + HashString(dt.AsEnumerable(), cc, nestingLevel + 1, currentPropertyFullName); + return definingString += $"{type.FullName} {string.Join(", ", dt.Columns.OfType().Select(c => c.ColumnName))}\n{tabs}" + HashString(dt.AsEnumerable(), cc, nestingLevel + 1, type, currentPropertyFullName); } else if (typeof(IObject).IsAssignableFrom(type)) { @@ -234,6 +236,13 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin if (BH.Engine.Base.Compute.TryRunExtensionMethod(obj, "HashString", parameters, out hashStringFromExtensionMethod)) return (string)hashStringFromExtensionMethod; + if (cc.UseGeometryHash && typeof(IGeometry).IsAssignableFrom(type)) + { + // Verify that no numerical approximation is requested for objects belonging to Geometrical types. + if (cc.IsCompatibleWithGeometryHash()) + return GeometryHash((IGeometry)obj).ToString(); + } + // If the object is an IObject (= a BHoM class), let's look at its properties. // We only do this for IObjects (BHoM types) since we cannot guarantee full compatibility of the following procedure with any possible (non-BHoM) type. PropertyInfo[] properties = type.GetProperties(); @@ -266,7 +275,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin if (propertyValue != null) { // Recurse for this property. - propHashString = HashString(propertyValue, cc, nestingLevel + 1, propFullName) ?? ""; + propHashString = HashString(propertyValue, cc, nestingLevel + 1, type, propFullName) ?? ""; if (!string.IsNullOrWhiteSpace(propHashString)) definingString += $"\n{tabs}" + $"{type.FullName}.{propName}:\n{tabs}{propHashString} "; } diff --git a/BHoM_Engine/Query/IsValidComparisonConfig.cs b/BHoM_Engine/Query/IsValidComparisonConfig.cs index 900daba63..d71127bf9 100644 --- a/BHoM_Engine/Query/IsValidComparisonConfig.cs +++ b/BHoM_Engine/Query/IsValidComparisonConfig.cs @@ -1,7 +1,9 @@ using BH.oM.Base; +using BH.oM.Base.Attributes; using BH.oM.Geometry; using System; using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using System.Text; @@ -9,20 +11,42 @@ namespace BH.Engine.Base { public static partial class Query { - public static bool IsValidComparisonConfig(BaseComparisonConfig cc) + [Description("Verifies that, when the ComparisonConfig requests the GeometryHash workflow, it is NOT at the same time requesting a numerical approximation for objects belonging to Geometrical types.")] + [Output("True if the ComparisonConfig is compatible with the GeometryHash worfklow.")] + public static bool IsCompatibleWithGeometryHash(this BaseComparisonConfig cc, bool raiseWarning = true) { if (cc == null) { return false; } - bool isValid = true; + bool isCompatible = true; - if (cc.UseGeometryHash) + if (!cc.UseGeometryHash) + return true; + + if (cc.NumericTolerance != double.MinValue) + { + if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.NumericTolerance)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); + isCompatible = false; + } + + if (cc.SignificantFigures != int.MaxValue) + { + if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.SignificantFigures)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); + isCompatible = false; + } + + if (cc.PropertyNumericTolerances?.Where(p => p.Name.Contains("Geometry")).Any() ?? false) { - if (cc.TypeExceptions.Where(t => typeof(IGeometry).IsAssignableFrom(t)).Any()) - return InvalidComparisonConfig(nameof(cc.TypeExceptions)); + if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.PropertyNumericTolerances)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); + isCompatible = false; + } - if (cc.PropertyExceptions.Where(p => p.Contains("Geometry")).Any()) - return InvalidComparisonConfig(nameof(cc.TypeExceptions)); + if (cc.PropertySignificantFigures?.Where(p => p.Name.Contains("Geometry")).Any() ?? false) + { + if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.PropertySignificantFigures)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); + isCompatible = false; } + + return isCompatible; } private static bool InvalidComparisonConfig(string invalidProp) diff --git a/Geometry_Engine/Query/GeometryHash.cs b/Geometry_Engine/Query/GeometryHash.cs index e37389c76..47691e621 100644 --- a/Geometry_Engine/Query/GeometryHash.cs +++ b/Geometry_Engine/Query/GeometryHash.cs @@ -87,6 +87,7 @@ public static string GeometryHash(this IGeometry igeometry) return sb.ToString(); } + /***************************************************/ /**** Private Methods ****/ /***************************************************/ From af6c36cce5482aec80eb14dac786a208e9aa7a13 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 25 Jan 2024 16:54:54 +0000 Subject: [PATCH 03/19] BaseComparisonConfig exceptions to HashSet --- .../Create/Graph/ComparisonConfig.cs | 3 ++- BHoM_Engine/Modify/PropertyNamesToFullNames.cs | 15 +++++++++++---- BHoM_Engine/Query/Hash.cs | 4 ++-- Diffing_Engine/Compute/IDiffing.cs | 2 +- Diffing_Engine/Create/DiffingConfig.cs | 2 +- Diffing_Engine/Query/ObjectDifferences.cs | 2 +- .../Query/HasMergeablePropertiesWith.cs | 6 +++--- Physical_Engine/Query/UniqueConstructions.cs | 2 +- 8 files changed, 22 insertions(+), 14 deletions(-) diff --git a/Analytical_Engine/Create/Graph/ComparisonConfig.cs b/Analytical_Engine/Create/Graph/ComparisonConfig.cs index 3dc9cdaf7..9c7ff41c2 100644 --- a/Analytical_Engine/Create/Graph/ComparisonConfig.cs +++ b/Analytical_Engine/Create/Graph/ComparisonConfig.cs @@ -45,12 +45,13 @@ public static partial class Create "\nHere you can specify a list of property names. Only the properties with a name matching any of this list will be considered." + "\nWorks only for top-level properties." + "\nE.g., if you input 'Name' only the differences in terms of name will be returned.")] - public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geometry.Tolerance.Distance, List propertyNamesToConsider = null) + public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geometry.Tolerance.Distance, HashSet propertyNamesToConsider = null) { ComparisonConfig cc = new ComparisonConfig() { NumericTolerance = numericTolerance, PropertiesToConsider = propertyNamesToConsider ?? new List(), + PropertiesToConsider = propertyNamesToConsider ?? new HashSet(), }; return cc; diff --git a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs index f643f6f68..3e8dad7ca 100644 --- a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs +++ b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs @@ -127,9 +127,9 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison } // Add the results to the ComparisonConfig. - comparisonConfig.PropertiesToConsider.AddRange(propertiesToConsider_fullNames); - comparisonConfig.PropertyExceptions.AddRange(propertyExceptions_fullNames); comparisonConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); + comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); + comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); m_ComparisonConfig_Type_processed[new Tuple(type, comparisonConfig)] = comparisonConfig; } @@ -183,11 +183,18 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison } // Add the results to the ComparisonConfig. - comparisonConfig.PropertiesToConsider = comparisonConfig.PropertiesToConsider?.Concat(propertiesToConsider_fullNames).ToList() ?? propertiesToConsider_fullNames; - comparisonConfig.PropertyExceptions = comparisonConfig.PropertyExceptions?.Concat(propertyExceptions_fullNames).ToList() ?? propertyExceptions_fullNames; comparisonConfig.PropertyNumericTolerances = comparisonConfig.PropertyNumericTolerances == null ? new HashSet(comparisonConfig.PropertyNumericTolerances?.Union(propertyNumericTolerances_fullNames)) : propertyNumericTolerances_fullNames; + if (comparisonConfig.PropertiesToConsider != null) + comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); + else + comparisonConfig.PropertiesToConsider = new HashSet(propertiesToConsider_fullNames); + + if (comparisonConfig.PropertyExceptions != null) + comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); + else + comparisonConfig.PropertiesToConsider = new HashSet(propertyExceptions_fullNames); } /***************************************************/ diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index 8ec7793b9..e15ca8a7f 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -196,8 +196,8 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin { // If the owner of this CustomData Dictionary is a CustomObject, // we want to consider its keys as if they were object properties for UX/UI consistency. - cc.CustomdataKeysExceptions.AddRange(cc.PropertyExceptions); - cc.CustomdataKeysToConsider.AddRange(cc.PropertiesToConsider); + cc.CustomdataKeysExceptions.UnionWith(cc.PropertyExceptions); + cc.CustomdataKeysToConsider.UnionWith(cc.PropertiesToConsider); } // Get the custom data Key, so we can check if it belongs to the exceptions. diff --git a/Diffing_Engine/Compute/IDiffing.cs b/Diffing_Engine/Compute/IDiffing.cs index 0c55680ab..bbb2b9f46 100644 --- a/Diffing_Engine/Compute/IDiffing.cs +++ b/Diffing_Engine/Compute/IDiffing.cs @@ -160,7 +160,7 @@ public static Diff IDiffing(IEnumerable pastObjs, IEnumerable fo [Input("followingObjs", "Set of objects belonging to a following revision.")] [Input("propertiesToConsider", "(Optional) Properties to be considered by the Diffing when determining what objects changed. See the DiffingConfig tooltip for more info.")] [Output("diff", "Object holding the detected changes.")] - public static Diff IDiffing(IEnumerable pastObjs, IEnumerable followingObjs, List propertiesToConsider = null) + public static Diff IDiffing(IEnumerable pastObjs, IEnumerable followingObjs, HashSet propertiesToConsider = null) { DiffingConfig dc = null; if (propertiesToConsider?.Any() ?? false) diff --git a/Diffing_Engine/Create/DiffingConfig.cs b/Diffing_Engine/Create/DiffingConfig.cs index b9d31b65e..5ca145ce4 100644 --- a/Diffing_Engine/Create/DiffingConfig.cs +++ b/Diffing_Engine/Create/DiffingConfig.cs @@ -53,7 +53,7 @@ public static DiffingConfig DiffingConfig(bool enablePropertyDiffing, bool store [Description("Defines configurations for the diffing.")] [Input("enablePropertyDiffing", "Enables the property-level diffing: differences in object properties are stored in the `ModifiedPropsPerObject` dictionary.")] [Input("storeUnchangedObjects", "If enabled, the Diff stores also the objects that did not change (`Unchanged` property).")] - public static DiffingConfig DiffingConfig(bool enablePropertyDiffing = false, bool storeUnchangedObjects = true, List propertyNamesToConsider = null) + public static DiffingConfig DiffingConfig(bool enablePropertyDiffing = false, bool storeUnchangedObjects = true, HashSet propertyNamesToConsider = null) { return new DiffingConfig() { diff --git a/Diffing_Engine/Query/ObjectDifferences.cs b/Diffing_Engine/Query/ObjectDifferences.cs index 8fab6938e..7ab463cd5 100644 --- a/Diffing_Engine/Query/ObjectDifferences.cs +++ b/Diffing_Engine/Query/ObjectDifferences.cs @@ -75,7 +75,7 @@ public static ObjectDifferences ObjectDifferences(this object pastObject, object kellermanComparer.Config.TypesToIgnore.Add(typeof(HashFragment)); // Never include the changes in HashFragment. kellermanComparer.Config.TypesToIgnore.Add(typeof(RevisionFragment)); // Never include the changes in RevisionFragment. kellermanComparer.Config.TypesToIgnore.AddRange(cc.TypeExceptions); - kellermanComparer.Config.MembersToIgnore = cc.PropertyExceptions; + kellermanComparer.Config.MembersToIgnore = cc.PropertyExceptions.ToList(); // Kellerman configuration for tolerance. // Setting Custom Tolerance for specific properties is complex with Kellerman. diff --git a/Environment_Engine/Query/HasMergeablePropertiesWith.cs b/Environment_Engine/Query/HasMergeablePropertiesWith.cs index 6e73c3f4d..e5a5783f2 100644 --- a/Environment_Engine/Query/HasMergeablePropertiesWith.cs +++ b/Environment_Engine/Query/HasMergeablePropertiesWith.cs @@ -58,7 +58,7 @@ public static bool HasMergeablePropertiesWith(this Panel element, Panel other) { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new List + PropertyExceptions = new HashSet { "ExternalEdges", "Openings", @@ -81,7 +81,7 @@ public static bool HasMergeablePropertiesWith(this Opening element, Opening othe { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new List + PropertyExceptions = new HashSet { "Edges", "FrameFactorValue", @@ -116,7 +116,7 @@ public static bool HasMergeablePropertiesWith(this Space element, Space other) { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new List + PropertyExceptions = new HashSet { "Location", "Type", diff --git a/Physical_Engine/Query/UniqueConstructions.cs b/Physical_Engine/Query/UniqueConstructions.cs index e5f9554c2..b4cf14953 100644 --- a/Physical_Engine/Query/UniqueConstructions.cs +++ b/Physical_Engine/Query/UniqueConstructions.cs @@ -46,7 +46,7 @@ public static List UniqueConstructions(this List con { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new List + PropertyExceptions = new HashSet { "CustomData" }, From 5623d981811ec581c22af278d45270d526fd0484 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 25 Jan 2024 16:57:28 +0000 Subject: [PATCH 04/19] Align to https://github.com/BHoM/BHoM/issues/1591 (Introducing NumericalApproximationConfig) --- .../Create/Graph/ComparisonConfig.cs | 3 +-- .../Modify/PropertyNamesToFullNames.cs | 21 +++++++++++-------- BHoM_Engine/Query/Hash.cs | 21 +++++++------------ BHoM_Engine/Query/NumericTolerance.cs | 8 +++---- BHoM_Engine/Query/NumericalApproximation.cs | 16 +++++++------- BHoM_Engine/Query/SignificantFigures.cs | 8 +++---- .../Query/NumericalDifferenceInclusion.cs | 8 +++---- Diffing_Engine/Query/ObjectDifferences.cs | 2 +- .../Query/HasMergeablePropertiesWith.cs | 7 +++---- Physical_Engine/Query/UniqueConstructions.cs | 2 +- 10 files changed, 45 insertions(+), 51 deletions(-) diff --git a/Analytical_Engine/Create/Graph/ComparisonConfig.cs b/Analytical_Engine/Create/Graph/ComparisonConfig.cs index 9c7ff41c2..7e47f56c1 100644 --- a/Analytical_Engine/Create/Graph/ComparisonConfig.cs +++ b/Analytical_Engine/Create/Graph/ComparisonConfig.cs @@ -49,8 +49,7 @@ public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geo { ComparisonConfig cc = new ComparisonConfig() { - NumericTolerance = numericTolerance, - PropertiesToConsider = propertyNamesToConsider ?? new List(), + NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = numericTolerance }, PropertiesToConsider = propertyNamesToConsider ?? new HashSet(), }; diff --git a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs index 3e8dad7ca..4c6076404 100644 --- a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs +++ b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs @@ -66,7 +66,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison // We need to process only those properties that have not been specified in FullName form, or that contain wildcards. List propertiesToConsiderToParse = comparisonConfig.PropertiesToConsider?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); List propertyExceptionsToParse = comparisonConfig.PropertyExceptions?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); - List propertyNumericTolerancesToParse = comparisonConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); + List propertyNumericTolerancesToParse = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); // If all the property names are already in FullName form and without wildcards, return. if (!propertiesToConsiderToParse.Any() && !propertyExceptionsToParse.Any() && !propertyNumericTolerancesToParse.Any()) @@ -87,7 +87,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison bool processPropertiesExceptions = propertyExceptionsToParse.Any() && (!cache || !m_cachedPropertyExceptions.TryGetValue(propertyExceptionsCacheKey, out propertyExceptions_fullNames)); // Check if we already have encountered and cached this same object Type and PropertyNumericTolerances. - string propertyNumericTolerancesCacheKey = $"{type.FullName}:{string.Join(",", comparisonConfig.PropertyNumericTolerances?.Select(ct => ct.Name + ct.Tolerance))}"; + string propertyNumericTolerancesCacheKey = $"{type.FullName}:{string.Join(",", comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(ct => ct.Name + ct.Tolerance))}"; bool processPropertyNumericTolerances = propertyNumericTolerancesToParse.Any() && (!cache || !m_cachedPropertyNumericTolerances.TryGetValue(propertyNumericTolerancesCacheKey, out propertyNumericTolerances_fullNames)); // Safety clauses because C#'s Dictionary TryGetValue sets the out variable to `null` if it failed. @@ -120,16 +120,16 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison { foreach (var propNumericTolerance in propertyNumericTolerancesToParse) if (IsMatchingInclusion(propertyFullName, propNumericTolerance)) - propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); + propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); if (cache) m_cachedPropertyNumericTolerances[propertyNumericTolerancesCacheKey] = propertyNumericTolerances_fullNames; } } // Add the results to the ComparisonConfig. - comparisonConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); + comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); m_ComparisonConfig_Type_processed[new Tuple(type, comparisonConfig)] = comparisonConfig; } @@ -150,7 +150,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison // We need to execute this method only if we have properties in the ComparisonConfig that have NOT been specified in FullName form, or that contain wildcards. List propertiesToConsiderToParse = comparisonConfig.PropertiesToConsider?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); List propertyExceptionsToParse = comparisonConfig.PropertyExceptions?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); - List propertyNumericTolerancesToParse = comparisonConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); + List propertyNumericTolerancesToParse = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); // If all the property names in the ComparisonConfig are already in FullName form and without wildcards, return. if (!propertiesToConsiderToParse.Any() && !propertyExceptionsToParse.Any() && !propertyNumericTolerancesToParse.Any()) @@ -179,13 +179,10 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison foreach (var propNumericTolerance in propertyNumericTolerancesToParse) if (IsMatchingInclusion(propertyFullName, propNumericTolerance)) - propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); + propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); } // Add the results to the ComparisonConfig. - comparisonConfig.PropertyNumericTolerances = comparisonConfig.PropertyNumericTolerances == null ? - new HashSet(comparisonConfig.PropertyNumericTolerances?.Union(propertyNumericTolerances_fullNames)) - : propertyNumericTolerances_fullNames; if (comparisonConfig.PropertiesToConsider != null) comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); else @@ -195,6 +192,12 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); else comparisonConfig.PropertiesToConsider = new HashSet(propertyExceptions_fullNames); + + comparisonConfig.NumericalApproximationConfig = comparisonConfig.NumericalApproximationConfig ?? new NumericalApproximationConfig(); + if (comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances != null) + comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); + else + comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances = new HashSet(propertyNumericTolerances_fullNames); } /***************************************************/ diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index e15ca8a7f..d42933628 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -75,9 +75,6 @@ public static string Hash(this IObject iObj, BaseComparisonConfig comparisonConf // Parse the ComparisonConfig's `PropertiesToConsider` and `PropertyExceptions` and get them all as Full Names. Modify.PropertyNamesToFullNames(cc, iObj); - // Verify that no numerical approximation is requested for objects belonging to Geometrical types. - m_isCompatibleWithGeometryHash = cc.IsCompatibleWithGeometryHash(); - // ----- HASH ----- // Compute the defining string. @@ -150,23 +147,23 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type.IsNumeric(enumsAsNumbers: false)) { // If we didn't specify any custom tolerance/significant figures, just return the input. - if (cc.NumericTolerance == double.MinValue && cc.SignificantFigures == int.MaxValue - && (!cc.PropertyNumericTolerances?.Any() ?? true) && (!cc.PropertySignificantFigures?.Any() ?? true)) + if (cc.NumericalApproximationConfig.NumericTolerance == double.MinValue && cc.NumericalApproximationConfig.SignificantFigures == int.MaxValue + && (!cc.NumericalApproximationConfig.PropertyNumericTolerances?.Any() ?? true) && (!cc.NumericalApproximationConfig.PropertySignificantFigures?.Any() ?? true)) return $"\n{tabs}" + obj.ToString(); if (type == typeof(double)) - return $"\n{tabs}" + NumericalApproximation((double)obj, currentPropertyFullName, cc).ToString(); + return $"\n{tabs}" + NumericalApproximation((double)obj, currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); if (type == typeof(int)) - return $"\n{tabs}" + NumericalApproximation((int)obj, currentPropertyFullName, cc).ToString(); + return $"\n{tabs}" + NumericalApproximation((int)obj, currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); // Fallback for any other floating-point numeric type. if (type.IsNumericFloatingPointType()) - return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc).ToString(); + return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); // Fallback for any other integral numeric type. if (type.IsNumericIntegralType()) - return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc).ToString(); + return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); } else if (type.IsPrimitive || type == typeof(String)) @@ -237,11 +234,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin return (string)hashStringFromExtensionMethod; if (cc.UseGeometryHash && typeof(IGeometry).IsAssignableFrom(type)) - { - // Verify that no numerical approximation is requested for objects belonging to Geometrical types. - if (cc.IsCompatibleWithGeometryHash()) - return GeometryHash((IGeometry)obj).ToString(); - } + return GeometryHash((IGeometry)obj).ToString(); // If the object is an IObject (= a BHoM class), let's look at its properties. // We only do this for IObjects (BHoM types) since we cannot guarantee full compatibility of the following procedure with any possible (non-BHoM) type. diff --git a/BHoM_Engine/Query/NumericTolerance.cs b/BHoM_Engine/Query/NumericTolerance.cs index 46031cfaf..59faf7d15 100644 --- a/BHoM_Engine/Query/NumericTolerance.cs +++ b/BHoM_Engine/Query/NumericTolerance.cs @@ -38,13 +38,13 @@ public static partial class Query "If a CustomTolerance match is found for this property Full Name, then return it. " + "If multiple matches are found, return the most sensistive tolerance among the matches. " + "If no match is found, return the most sensitive tolerance (double.MinValue).")] - [Input("comparisonConfig", "Comparison Config from where tolerance information should be extracted.")] + [Input("numericalApproxConfig", "Numerical approximation Config containing the settings for Numerical Tolerance.")] [Input("propertyFullName", "Full name (path) of the property for which we want to extract the numerical Tolerance.")] - public static double NumericTolerance(this BaseComparisonConfig comparisonConfig, string propertyFullName) + public static double NumericTolerance(this NumericalApproximationConfig numericalApproxConfig, string propertyFullName) { - comparisonConfig = comparisonConfig ?? new ComparisonConfig(); + numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); - return NumericTolerance(comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, propertyFullName, false); + return NumericTolerance(numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, propertyFullName, false); } /***************************************************/ diff --git a/BHoM_Engine/Query/NumericalApproximation.cs b/BHoM_Engine/Query/NumericalApproximation.cs index dc2a66ff0..9bd591560 100644 --- a/BHoM_Engine/Query/NumericalApproximation.cs +++ b/BHoM_Engine/Query/NumericalApproximation.cs @@ -45,12 +45,12 @@ public static partial class Query [Description("Compute the approximation of a floating-point number for its comparison with other numbers, given specific ComparisonConfig settings.")] [Input("number", "Number to approximate.")] [Input("fullName", "Name of the number or of the property that holds this number. This name will be used to seek any matching custom tolerance/significant figure to apply for this approximation in the `comparisonConfig` input.")] - [Input("comparisonConfig", "Object that stores the settings that will used for the approximation.")] - public static double NumericalApproximation(this double number, string fullName = null, BaseComparisonConfig comparisonConfig = null) + [Input("numericalApproxConfig", "Object that stores the settings that will used for the approximation.")] + public static double NumericalApproximation(this double number, string fullName = null, NumericalApproximationConfig numericalApproxConfig = null) { - comparisonConfig = comparisonConfig ?? new ComparisonConfig(); + numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); - return NumericalApproximation(number, fullName, comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); + return NumericalApproximation(number, fullName, numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); } /***************************************************/ @@ -58,12 +58,12 @@ public static double NumericalApproximation(this double number, string fullName [Description("Compute the approximation of an integer number for its comparison with other numbers, given specific ComparisonConfig settings.")] [Input("number", "Number to approximate.")] [Input("fullName", "Name of the number or of the property that holds this number. This name will be used to seek any matching custom tolerance/significant figure to apply for this approximation in the `comparisonConfig` input.")] - [Input("comparisonConfig", "Object that stores the settings that will used for the approximation.")] - public static double NumericalApproximation(this int number, string fullName = null, BaseComparisonConfig comparisonConfig = null) + [Input("numericalApproxConfig", "Object that stores the settings that will used for the approximation.")] + public static double NumericalApproximation(this int number, string fullName = null, NumericalApproximationConfig numericalApproxConfig = null) { - comparisonConfig = comparisonConfig ?? new ComparisonConfig(); + numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); - return NumericalApproximation(number, fullName, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); + return NumericalApproximation(number, fullName, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); } /***************************************************/ diff --git a/BHoM_Engine/Query/SignificantFigures.cs b/BHoM_Engine/Query/SignificantFigures.cs index 981f4716c..838b9034f 100644 --- a/BHoM_Engine/Query/SignificantFigures.cs +++ b/BHoM_Engine/Query/SignificantFigures.cs @@ -38,13 +38,13 @@ public static partial class Query "If a CustomTolerance match is found for this property Full Name, then return it. " + "If multiple matches are found, return the most sensistive among the matches. " + "If no match is found, return `ComparisonConfig.SignificantFigures`.")] - [Input("comparisonConfig", "Comparison Config from where tolerance information should be extracted.")] + [Input("numericalApproxConfig", "Numerical approximation Config containing the settings for significant figures.")] [Input("propertyFullName", "Full name (path) of the property for which we want to extract the numerical Tolerance.")] - public static int SignificantFigures(this BaseComparisonConfig comparisonConfig, string propertyFullName) + public static int SignificantFigures(this NumericalApproximationConfig numericalApproxConfig, string propertyFullName) { - comparisonConfig = comparisonConfig ?? new ComparisonConfig(); + numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); - return SignificantFigures(comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures, propertyFullName); + return SignificantFigures(numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures, propertyFullName); } /***************************************************/ diff --git a/Diffing_Engine/Query/NumericalDifferenceInclusion.cs b/Diffing_Engine/Query/NumericalDifferenceInclusion.cs index d41aeb648..be72b65d3 100644 --- a/Diffing_Engine/Query/NumericalDifferenceInclusion.cs +++ b/Diffing_Engine/Query/NumericalDifferenceInclusion.cs @@ -46,13 +46,13 @@ public static partial class Query [Input("number1", "First number to compare.")] [Input("number2", "Second number to compare.")] [Input("propertyFullName", "If the numbers are part of an object, full name of the property that owns them. This name will be used to seek matches in the ComparisoConfig named numeric tolerance/significant figures.")] - [Input("comparisonConfig", "Object containing the settings for this numerical comparison.")] + [Input("numericalApproxConfig", "Object containing the settings for this numerical comparison.")] [Output("seenAsDifferent", "Whether the input numbers are seen as different, given the numerical approximations specified in the comparisonConfig.")] - public static bool NumericalDifferenceInclusion(this object number1, object number2, string propertyFullName = null, BaseComparisonConfig comparisonConfig = null) + public static bool NumericalDifferenceInclusion(this object number1, object number2, string propertyFullName = null, NumericalApproximationConfig numericalApproxConfig = null) { - comparisonConfig = comparisonConfig ?? new ComparisonConfig(); + numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); - return NumericalDifferenceInclusion(number1, number2, propertyFullName, comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); + return NumericalDifferenceInclusion(number1, number2, propertyFullName, numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); } /***************************************************/ diff --git a/Diffing_Engine/Query/ObjectDifferences.cs b/Diffing_Engine/Query/ObjectDifferences.cs index 7ab463cd5..e6b4e2685 100644 --- a/Diffing_Engine/Query/ObjectDifferences.cs +++ b/Diffing_Engine/Query/ObjectDifferences.cs @@ -181,7 +181,7 @@ public static ObjectDifferences ObjectDifferences(this object pastObject, object continue; // Check if this difference is numerical, and if so whether it should be included or not given the input tolerance/significant figures. - if (!NumericalDifferenceInclusion(kellermanPropertyDifference.Object1, kellermanPropertyDifference.Object2, propertyFullName_noIndexes, cc)) + if (!NumericalDifferenceInclusion(kellermanPropertyDifference.Object1, kellermanPropertyDifference.Object2, propertyFullName_noIndexes, cc.NumericalApproximationConfig)) continue; // Add to the final result. diff --git a/Environment_Engine/Query/HasMergeablePropertiesWith.cs b/Environment_Engine/Query/HasMergeablePropertiesWith.cs index e5a5783f2..5312cc11d 100644 --- a/Environment_Engine/Query/HasMergeablePropertiesWith.cs +++ b/Environment_Engine/Query/HasMergeablePropertiesWith.cs @@ -67,7 +67,7 @@ public static bool HasMergeablePropertiesWith(this Panel element, Panel other) "BHoM_Guid", "CustomData", }, - NumericTolerance = BH.oM.Geometry.Tolerance.Distance + NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance } }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; @@ -90,7 +90,7 @@ public static bool HasMergeablePropertiesWith(this Opening element, Opening othe "BHoM_Guid", "CustomData", }, - NumericTolerance = BH.oM.Geometry.Tolerance.Distance + NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance } }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; @@ -123,8 +123,7 @@ public static bool HasMergeablePropertiesWith(this Space element, Space other) "BHoM_Guid", "CustomData", }, - NumericTolerance = BH.oM.Geometry.Tolerance.Distance, - + NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance }, }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; diff --git a/Physical_Engine/Query/UniqueConstructions.cs b/Physical_Engine/Query/UniqueConstructions.cs index b4cf14953..4be098648 100644 --- a/Physical_Engine/Query/UniqueConstructions.cs +++ b/Physical_Engine/Query/UniqueConstructions.cs @@ -50,7 +50,7 @@ public static List UniqueConstructions(this List con { "CustomData" }, - NumericTolerance = BH.oM.Geometry.Tolerance.Distance + NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance }, }; if (!includeConstructionName) From d379f03723378c977cf5d212f2b2f77eec7a27ab Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 25 Jan 2024 16:57:33 +0000 Subject: [PATCH 05/19] Delete IsValidComparisonConfig.cs --- BHoM_Engine/Query/IsValidComparisonConfig.cs | 60 -------------------- 1 file changed, 60 deletions(-) delete mode 100644 BHoM_Engine/Query/IsValidComparisonConfig.cs diff --git a/BHoM_Engine/Query/IsValidComparisonConfig.cs b/BHoM_Engine/Query/IsValidComparisonConfig.cs deleted file mode 100644 index d71127bf9..000000000 --- a/BHoM_Engine/Query/IsValidComparisonConfig.cs +++ /dev/null @@ -1,60 +0,0 @@ -using BH.oM.Base; -using BH.oM.Base.Attributes; -using BH.oM.Geometry; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; - -namespace BH.Engine.Base -{ - public static partial class Query - { - [Description("Verifies that, when the ComparisonConfig requests the GeometryHash workflow, it is NOT at the same time requesting a numerical approximation for objects belonging to Geometrical types.")] - [Output("True if the ComparisonConfig is compatible with the GeometryHash worfklow.")] - public static bool IsCompatibleWithGeometryHash(this BaseComparisonConfig cc, bool raiseWarning = true) - { - if (cc == null) { return false; } - - bool isCompatible = true; - - if (!cc.UseGeometryHash) - return true; - - if (cc.NumericTolerance != double.MinValue) - { - if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.NumericTolerance)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); - isCompatible = false; - } - - if (cc.SignificantFigures != int.MaxValue) - { - if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.SignificantFigures)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); - isCompatible = false; - } - - if (cc.PropertyNumericTolerances?.Where(p => p.Name.Contains("Geometry")).Any() ?? false) - { - if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.PropertyNumericTolerances)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); - isCompatible = false; - } - - if (cc.PropertySignificantFigures?.Where(p => p.Name.Contains("Geometry")).Any() ?? false) - { - if (raiseWarning) Compute.RecordWarning($"Please note that the input {nameof(ComparisonConfig)}.{nameof(cc.PropertySignificantFigures)} will not be considered for Geometry objects because {nameof(cc.UseGeometryHash)} was set to true."); - isCompatible = false; - } - - return isCompatible; - } - - private static bool InvalidComparisonConfig(string invalidProp) - { - BH.Engine.Base.Compute.RecordError($"The comparison config is not valid because it has {nameof(BaseComparisonConfig.UseGeometryHash)} set to true while specifying also a {invalidProp} that applies to geometric types." + - $"If you want to specify this {invalidProp}, you need to toggle off {nameof(BaseComparisonConfig.UseGeometryHash)}."); - - return false; - } - } -} From 7883d0758ff586d821ec6c02cfe0b1f812806b17 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 25 Jan 2024 18:31:30 +0000 Subject: [PATCH 06/19] Hash(): Update signature of dynamic call to string --- BHoM_Engine/Query/Hash.cs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index d42933628..3620c7217 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -287,21 +287,24 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin /***************************************************/ - private static double GeometryHash(this IGeometry igeom) + private static string GeometryHash(this IGeometry igeom) { if (igeom == null) - return default(double); + return null; if (m_GeomHashFunc == null) { var mis = Query.ExtensionMethods(typeof(IGeometry), "GeometryHash"); - m_GeomHashFunc = (Func)Delegate.CreateDelegate(typeof(Func), mis.First()); + if (!mis?.Any() ?? true) + throw new InvalidOperationException("Could not dynamically load the GeometryHash method."); + + m_GeomHashFunc = (Func)Delegate.CreateDelegate(typeof(Func), mis.First()); } return m_GeomHashFunc(igeom); } - private static Func m_GeomHashFunc = null; + private static Func m_GeomHashFunc = null; } } From 747c601607a7044aa7193737df5d686981c08768 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Fri, 26 Jan 2024 16:38:39 +0000 Subject: [PATCH 07/19] Added HashArray methods --- Geometry_Engine/Query/HashArray.cs | 41 ++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index c011d380c..b0fc2bb73 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -408,7 +408,7 @@ private static double[] HashArray(this object obj, double translationFactor) /**** Other methods for "conceptual" geometry ****/ /***************************************************/ - [Description("The GeometryHash for a CompositeGeometry is given as the concatenated GeometryHash of the single elements composing it.")] + [Description("The GeometryHash for a Vector is given as the concatenated GeometryHash of the single elements composing it.")] private static double[] HashArray(this Vector obj, double translationFactor) { if (translationFactor == (double)TypeTranslationFactor.Point) @@ -422,6 +422,41 @@ private static double[] HashArray(this Vector obj, double translationFactor) }; } + /***************************************************/ + + [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] + private static double[] HashArray(this Basis obj, double translationFactor) + { + translationFactor = (double)TypeTranslationFactor.Basis; + + var x = obj.X.HashArray(translationFactor); + var y = obj.Y.HashArray(translationFactor); + var z = obj.Z.HashArray(translationFactor); + return x.Concat(y).Concat(z).ToArray(); + } + + /***************************************************/ + + [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] + private static double[] HashArray(this Cartesian obj, double translationFactor) + { + translationFactor = (double)TypeTranslationFactor.Cartesian; + + var x = obj.X.HashArray(translationFactor); + var y = obj.Y.HashArray(translationFactor); + var z = obj.Z.HashArray(translationFactor); + var o = obj.Origin.HashArray(translationFactor); + return x.Concat(y).Concat(z).Concat(o).ToArray(); + } + + /***************************************************/ + + [Description("The GeometryHash for a TransformMatrix is given as the concatenated numbers of the matrix.")] + private static double[] HashArray(this TransformMatrix obj, double translationFactor) + { + return obj.Matrix.Cast().ToArray(); + } + /***************************************************/ /**** Private fields ****/ /***************************************************/ @@ -434,7 +469,9 @@ private static double[] HashArray(this Vector obj, double translationFactor) "like e.g. a 3-point Polyline and an Arc that passes through the same points.")] private enum TypeTranslationFactor { - Vector = -1, + Cartesian = -3 * m_ToleranceMultiplier, + Basis = -2 * m_ToleranceMultiplier, + Vector = -1 * m_ToleranceMultiplier, Point = 0, Plane = 1 * m_ToleranceMultiplier, Line = 2 * m_ToleranceMultiplier, From 674a2e7aa8c9d462a6500bcfe87560fc0dfa9833 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Fri, 26 Jan 2024 16:44:01 +0000 Subject: [PATCH 08/19] Revert "Align to https://github.com/BHoM/BHoM/issues/1591" This reverts commit 5623d981811ec581c22af278d45270d526fd0484. --- .../Create/Graph/ComparisonConfig.cs | 3 ++- .../Modify/PropertyNamesToFullNames.cs | 21 ++++++++----------- BHoM_Engine/Query/Hash.cs | 14 +++++++------ BHoM_Engine/Query/NumericTolerance.cs | 8 +++---- BHoM_Engine/Query/NumericalApproximation.cs | 16 +++++++------- BHoM_Engine/Query/SignificantFigures.cs | 8 +++---- .../Query/NumericalDifferenceInclusion.cs | 8 +++---- Diffing_Engine/Query/ObjectDifferences.cs | 2 +- .../Query/HasMergeablePropertiesWith.cs | 7 ++++--- Physical_Engine/Query/UniqueConstructions.cs | 2 +- 10 files changed, 45 insertions(+), 44 deletions(-) diff --git a/Analytical_Engine/Create/Graph/ComparisonConfig.cs b/Analytical_Engine/Create/Graph/ComparisonConfig.cs index 7e47f56c1..9c7ff41c2 100644 --- a/Analytical_Engine/Create/Graph/ComparisonConfig.cs +++ b/Analytical_Engine/Create/Graph/ComparisonConfig.cs @@ -49,7 +49,8 @@ public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geo { ComparisonConfig cc = new ComparisonConfig() { - NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = numericTolerance }, + NumericTolerance = numericTolerance, + PropertiesToConsider = propertyNamesToConsider ?? new List(), PropertiesToConsider = propertyNamesToConsider ?? new HashSet(), }; diff --git a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs index 4c6076404..3e8dad7ca 100644 --- a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs +++ b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs @@ -66,7 +66,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison // We need to process only those properties that have not been specified in FullName form, or that contain wildcards. List propertiesToConsiderToParse = comparisonConfig.PropertiesToConsider?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); List propertyExceptionsToParse = comparisonConfig.PropertyExceptions?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); - List propertyNumericTolerancesToParse = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); + List propertyNumericTolerancesToParse = comparisonConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); // If all the property names are already in FullName form and without wildcards, return. if (!propertiesToConsiderToParse.Any() && !propertyExceptionsToParse.Any() && !propertyNumericTolerancesToParse.Any()) @@ -87,7 +87,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison bool processPropertiesExceptions = propertyExceptionsToParse.Any() && (!cache || !m_cachedPropertyExceptions.TryGetValue(propertyExceptionsCacheKey, out propertyExceptions_fullNames)); // Check if we already have encountered and cached this same object Type and PropertyNumericTolerances. - string propertyNumericTolerancesCacheKey = $"{type.FullName}:{string.Join(",", comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(ct => ct.Name + ct.Tolerance))}"; + string propertyNumericTolerancesCacheKey = $"{type.FullName}:{string.Join(",", comparisonConfig.PropertyNumericTolerances?.Select(ct => ct.Name + ct.Tolerance))}"; bool processPropertyNumericTolerances = propertyNumericTolerancesToParse.Any() && (!cache || !m_cachedPropertyNumericTolerances.TryGetValue(propertyNumericTolerancesCacheKey, out propertyNumericTolerances_fullNames)); // Safety clauses because C#'s Dictionary TryGetValue sets the out variable to `null` if it failed. @@ -120,16 +120,16 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison { foreach (var propNumericTolerance in propertyNumericTolerancesToParse) if (IsMatchingInclusion(propertyFullName, propNumericTolerance)) - propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); + propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); if (cache) m_cachedPropertyNumericTolerances[propertyNumericTolerancesCacheKey] = propertyNumericTolerances_fullNames; } } // Add the results to the ComparisonConfig. + comparisonConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); - comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); m_ComparisonConfig_Type_processed[new Tuple(type, comparisonConfig)] = comparisonConfig; } @@ -150,7 +150,7 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison // We need to execute this method only if we have properties in the ComparisonConfig that have NOT been specified in FullName form, or that contain wildcards. List propertiesToConsiderToParse = comparisonConfig.PropertiesToConsider?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); List propertyExceptionsToParse = comparisonConfig.PropertyExceptions?.Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); - List propertyNumericTolerancesToParse = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); + List propertyNumericTolerancesToParse = comparisonConfig.PropertyNumericTolerances?.Select(p => p.Name).Where(p => !p.StartsWith("BH.") || p.Contains("*")).ToList() ?? new List(); // If all the property names in the ComparisonConfig are already in FullName form and without wildcards, return. if (!propertiesToConsiderToParse.Any() && !propertyExceptionsToParse.Any() && !propertyNumericTolerancesToParse.Any()) @@ -179,10 +179,13 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison foreach (var propNumericTolerance in propertyNumericTolerancesToParse) if (IsMatchingInclusion(propertyFullName, propNumericTolerance)) - propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); + propertyNumericTolerances_fullNames.Add(new NamedNumericTolerance() { Name = propertyFullName, Tolerance = comparisonConfig.PropertyNumericTolerances.Where(pnc => pnc.Name == propNumericTolerance).First().Tolerance }); } // Add the results to the ComparisonConfig. + comparisonConfig.PropertyNumericTolerances = comparisonConfig.PropertyNumericTolerances == null ? + new HashSet(comparisonConfig.PropertyNumericTolerances?.Union(propertyNumericTolerances_fullNames)) + : propertyNumericTolerances_fullNames; if (comparisonConfig.PropertiesToConsider != null) comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); else @@ -192,12 +195,6 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); else comparisonConfig.PropertiesToConsider = new HashSet(propertyExceptions_fullNames); - - comparisonConfig.NumericalApproximationConfig = comparisonConfig.NumericalApproximationConfig ?? new NumericalApproximationConfig(); - if (comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances != null) - comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); - else - comparisonConfig.NumericalApproximationConfig.PropertyNumericTolerances = new HashSet(propertyNumericTolerances_fullNames); } /***************************************************/ diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index 3620c7217..e047a2259 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -147,23 +147,23 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type.IsNumeric(enumsAsNumbers: false)) { // If we didn't specify any custom tolerance/significant figures, just return the input. - if (cc.NumericalApproximationConfig.NumericTolerance == double.MinValue && cc.NumericalApproximationConfig.SignificantFigures == int.MaxValue - && (!cc.NumericalApproximationConfig.PropertyNumericTolerances?.Any() ?? true) && (!cc.NumericalApproximationConfig.PropertySignificantFigures?.Any() ?? true)) + if (cc.NumericTolerance == double.MinValue && cc.SignificantFigures == int.MaxValue + && (!cc.PropertyNumericTolerances?.Any() ?? true) && (!cc.PropertySignificantFigures?.Any() ?? true)) return $"\n{tabs}" + obj.ToString(); if (type == typeof(double)) - return $"\n{tabs}" + NumericalApproximation((double)obj, currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); + return $"\n{tabs}" + NumericalApproximation((double)obj, currentPropertyFullName, cc).ToString(); if (type == typeof(int)) - return $"\n{tabs}" + NumericalApproximation((int)obj, currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); + return $"\n{tabs}" + NumericalApproximation((int)obj, currentPropertyFullName, cc).ToString(); // Fallback for any other floating-point numeric type. if (type.IsNumericFloatingPointType()) - return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); + return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc).ToString(); // Fallback for any other integral numeric type. if (type.IsNumericIntegralType()) - return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc.NumericalApproximationConfig).ToString(); + return $"\n{tabs}" + NumericalApproximation(double.Parse(obj.ToString()), currentPropertyFullName, cc).ToString(); } else if (type.IsPrimitive || type == typeof(String)) @@ -234,7 +234,9 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin return (string)hashStringFromExtensionMethod; if (cc.UseGeometryHash && typeof(IGeometry).IsAssignableFrom(type)) + { return GeometryHash((IGeometry)obj).ToString(); + } // If the object is an IObject (= a BHoM class), let's look at its properties. // We only do this for IObjects (BHoM types) since we cannot guarantee full compatibility of the following procedure with any possible (non-BHoM) type. diff --git a/BHoM_Engine/Query/NumericTolerance.cs b/BHoM_Engine/Query/NumericTolerance.cs index 59faf7d15..46031cfaf 100644 --- a/BHoM_Engine/Query/NumericTolerance.cs +++ b/BHoM_Engine/Query/NumericTolerance.cs @@ -38,13 +38,13 @@ public static partial class Query "If a CustomTolerance match is found for this property Full Name, then return it. " + "If multiple matches are found, return the most sensistive tolerance among the matches. " + "If no match is found, return the most sensitive tolerance (double.MinValue).")] - [Input("numericalApproxConfig", "Numerical approximation Config containing the settings for Numerical Tolerance.")] + [Input("comparisonConfig", "Comparison Config from where tolerance information should be extracted.")] [Input("propertyFullName", "Full name (path) of the property for which we want to extract the numerical Tolerance.")] - public static double NumericTolerance(this NumericalApproximationConfig numericalApproxConfig, string propertyFullName) + public static double NumericTolerance(this BaseComparisonConfig comparisonConfig, string propertyFullName) { - numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); + comparisonConfig = comparisonConfig ?? new ComparisonConfig(); - return NumericTolerance(numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, propertyFullName, false); + return NumericTolerance(comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, propertyFullName, false); } /***************************************************/ diff --git a/BHoM_Engine/Query/NumericalApproximation.cs b/BHoM_Engine/Query/NumericalApproximation.cs index 9bd591560..dc2a66ff0 100644 --- a/BHoM_Engine/Query/NumericalApproximation.cs +++ b/BHoM_Engine/Query/NumericalApproximation.cs @@ -45,12 +45,12 @@ public static partial class Query [Description("Compute the approximation of a floating-point number for its comparison with other numbers, given specific ComparisonConfig settings.")] [Input("number", "Number to approximate.")] [Input("fullName", "Name of the number or of the property that holds this number. This name will be used to seek any matching custom tolerance/significant figure to apply for this approximation in the `comparisonConfig` input.")] - [Input("numericalApproxConfig", "Object that stores the settings that will used for the approximation.")] - public static double NumericalApproximation(this double number, string fullName = null, NumericalApproximationConfig numericalApproxConfig = null) + [Input("comparisonConfig", "Object that stores the settings that will used for the approximation.")] + public static double NumericalApproximation(this double number, string fullName = null, BaseComparisonConfig comparisonConfig = null) { - numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); + comparisonConfig = comparisonConfig ?? new ComparisonConfig(); - return NumericalApproximation(number, fullName, numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); + return NumericalApproximation(number, fullName, comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); } /***************************************************/ @@ -58,12 +58,12 @@ public static double NumericalApproximation(this double number, string fullName [Description("Compute the approximation of an integer number for its comparison with other numbers, given specific ComparisonConfig settings.")] [Input("number", "Number to approximate.")] [Input("fullName", "Name of the number or of the property that holds this number. This name will be used to seek any matching custom tolerance/significant figure to apply for this approximation in the `comparisonConfig` input.")] - [Input("numericalApproxConfig", "Object that stores the settings that will used for the approximation.")] - public static double NumericalApproximation(this int number, string fullName = null, NumericalApproximationConfig numericalApproxConfig = null) + [Input("comparisonConfig", "Object that stores the settings that will used for the approximation.")] + public static double NumericalApproximation(this int number, string fullName = null, BaseComparisonConfig comparisonConfig = null) { - numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); + comparisonConfig = comparisonConfig ?? new ComparisonConfig(); - return NumericalApproximation(number, fullName, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); + return NumericalApproximation(number, fullName, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); } /***************************************************/ diff --git a/BHoM_Engine/Query/SignificantFigures.cs b/BHoM_Engine/Query/SignificantFigures.cs index 838b9034f..981f4716c 100644 --- a/BHoM_Engine/Query/SignificantFigures.cs +++ b/BHoM_Engine/Query/SignificantFigures.cs @@ -38,13 +38,13 @@ public static partial class Query "If a CustomTolerance match is found for this property Full Name, then return it. " + "If multiple matches are found, return the most sensistive among the matches. " + "If no match is found, return `ComparisonConfig.SignificantFigures`.")] - [Input("numericalApproxConfig", "Numerical approximation Config containing the settings for significant figures.")] + [Input("comparisonConfig", "Comparison Config from where tolerance information should be extracted.")] [Input("propertyFullName", "Full name (path) of the property for which we want to extract the numerical Tolerance.")] - public static int SignificantFigures(this NumericalApproximationConfig numericalApproxConfig, string propertyFullName) + public static int SignificantFigures(this BaseComparisonConfig comparisonConfig, string propertyFullName) { - numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); + comparisonConfig = comparisonConfig ?? new ComparisonConfig(); - return SignificantFigures(numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures, propertyFullName); + return SignificantFigures(comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures, propertyFullName); } /***************************************************/ diff --git a/Diffing_Engine/Query/NumericalDifferenceInclusion.cs b/Diffing_Engine/Query/NumericalDifferenceInclusion.cs index be72b65d3..d41aeb648 100644 --- a/Diffing_Engine/Query/NumericalDifferenceInclusion.cs +++ b/Diffing_Engine/Query/NumericalDifferenceInclusion.cs @@ -46,13 +46,13 @@ public static partial class Query [Input("number1", "First number to compare.")] [Input("number2", "Second number to compare.")] [Input("propertyFullName", "If the numbers are part of an object, full name of the property that owns them. This name will be used to seek matches in the ComparisoConfig named numeric tolerance/significant figures.")] - [Input("numericalApproxConfig", "Object containing the settings for this numerical comparison.")] + [Input("comparisonConfig", "Object containing the settings for this numerical comparison.")] [Output("seenAsDifferent", "Whether the input numbers are seen as different, given the numerical approximations specified in the comparisonConfig.")] - public static bool NumericalDifferenceInclusion(this object number1, object number2, string propertyFullName = null, NumericalApproximationConfig numericalApproxConfig = null) + public static bool NumericalDifferenceInclusion(this object number1, object number2, string propertyFullName = null, BaseComparisonConfig comparisonConfig = null) { - numericalApproxConfig = numericalApproxConfig ?? new NumericalApproximationConfig(); + comparisonConfig = comparisonConfig ?? new ComparisonConfig(); - return NumericalDifferenceInclusion(number1, number2, propertyFullName, numericalApproxConfig.PropertyNumericTolerances, numericalApproxConfig.NumericTolerance, numericalApproxConfig.PropertySignificantFigures, numericalApproxConfig.SignificantFigures); + return NumericalDifferenceInclusion(number1, number2, propertyFullName, comparisonConfig.PropertyNumericTolerances, comparisonConfig.NumericTolerance, comparisonConfig.PropertySignificantFigures, comparisonConfig.SignificantFigures); } /***************************************************/ diff --git a/Diffing_Engine/Query/ObjectDifferences.cs b/Diffing_Engine/Query/ObjectDifferences.cs index e6b4e2685..7ab463cd5 100644 --- a/Diffing_Engine/Query/ObjectDifferences.cs +++ b/Diffing_Engine/Query/ObjectDifferences.cs @@ -181,7 +181,7 @@ public static ObjectDifferences ObjectDifferences(this object pastObject, object continue; // Check if this difference is numerical, and if so whether it should be included or not given the input tolerance/significant figures. - if (!NumericalDifferenceInclusion(kellermanPropertyDifference.Object1, kellermanPropertyDifference.Object2, propertyFullName_noIndexes, cc.NumericalApproximationConfig)) + if (!NumericalDifferenceInclusion(kellermanPropertyDifference.Object1, kellermanPropertyDifference.Object2, propertyFullName_noIndexes, cc)) continue; // Add to the final result. diff --git a/Environment_Engine/Query/HasMergeablePropertiesWith.cs b/Environment_Engine/Query/HasMergeablePropertiesWith.cs index 5312cc11d..e5a5783f2 100644 --- a/Environment_Engine/Query/HasMergeablePropertiesWith.cs +++ b/Environment_Engine/Query/HasMergeablePropertiesWith.cs @@ -67,7 +67,7 @@ public static bool HasMergeablePropertiesWith(this Panel element, Panel other) "BHoM_Guid", "CustomData", }, - NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance } + NumericTolerance = BH.oM.Geometry.Tolerance.Distance }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; @@ -90,7 +90,7 @@ public static bool HasMergeablePropertiesWith(this Opening element, Opening othe "BHoM_Guid", "CustomData", }, - NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance } + NumericTolerance = BH.oM.Geometry.Tolerance.Distance }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; @@ -123,7 +123,8 @@ public static bool HasMergeablePropertiesWith(this Space element, Space other) "BHoM_Guid", "CustomData", }, - NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance }, + NumericTolerance = BH.oM.Geometry.Tolerance.Distance, + }; return !Diffing.Query.DifferentProperties(element, other, cc)?.Any() ?? true; diff --git a/Physical_Engine/Query/UniqueConstructions.cs b/Physical_Engine/Query/UniqueConstructions.cs index 4be098648..b4cf14953 100644 --- a/Physical_Engine/Query/UniqueConstructions.cs +++ b/Physical_Engine/Query/UniqueConstructions.cs @@ -50,7 +50,7 @@ public static List UniqueConstructions(this List con { "CustomData" }, - NumericalApproximationConfig = new NumericalApproximationConfig() { NumericTolerance = BH.oM.Geometry.Tolerance.Distance }, + NumericTolerance = BH.oM.Geometry.Tolerance.Distance }; if (!includeConstructionName) From 8951a856da3fb3d4d7642a4125498e2bb5778f25 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Fri, 26 Jan 2024 16:48:43 +0000 Subject: [PATCH 09/19] Revert "BaseComparisonConfig exceptions to HashSet" This reverts commit af6c36cce5482aec80eb14dac786a208e9aa7a13. --- .../Create/Graph/ComparisonConfig.cs | 3 +-- BHoM_Engine/Modify/PropertyNamesToFullNames.cs | 15 ++++----------- BHoM_Engine/Query/Hash.cs | 4 ++-- Diffing_Engine/Compute/IDiffing.cs | 2 +- Diffing_Engine/Create/DiffingConfig.cs | 2 +- Diffing_Engine/Query/ObjectDifferences.cs | 2 +- .../Query/HasMergeablePropertiesWith.cs | 6 +++--- Physical_Engine/Query/UniqueConstructions.cs | 2 +- 8 files changed, 14 insertions(+), 22 deletions(-) diff --git a/Analytical_Engine/Create/Graph/ComparisonConfig.cs b/Analytical_Engine/Create/Graph/ComparisonConfig.cs index 9c7ff41c2..3dc9cdaf7 100644 --- a/Analytical_Engine/Create/Graph/ComparisonConfig.cs +++ b/Analytical_Engine/Create/Graph/ComparisonConfig.cs @@ -45,13 +45,12 @@ public static partial class Create "\nHere you can specify a list of property names. Only the properties with a name matching any of this list will be considered." + "\nWorks only for top-level properties." + "\nE.g., if you input 'Name' only the differences in terms of name will be returned.")] - public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geometry.Tolerance.Distance, HashSet propertyNamesToConsider = null) + public static ComparisonConfig ComparisonConfig(double numericTolerance = oM.Geometry.Tolerance.Distance, List propertyNamesToConsider = null) { ComparisonConfig cc = new ComparisonConfig() { NumericTolerance = numericTolerance, PropertiesToConsider = propertyNamesToConsider ?? new List(), - PropertiesToConsider = propertyNamesToConsider ?? new HashSet(), }; return cc; diff --git a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs index 3e8dad7ca..f643f6f68 100644 --- a/BHoM_Engine/Modify/PropertyNamesToFullNames.cs +++ b/BHoM_Engine/Modify/PropertyNamesToFullNames.cs @@ -127,9 +127,9 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison } // Add the results to the ComparisonConfig. + comparisonConfig.PropertiesToConsider.AddRange(propertiesToConsider_fullNames); + comparisonConfig.PropertyExceptions.AddRange(propertyExceptions_fullNames); comparisonConfig.PropertyNumericTolerances.UnionWith(propertyNumericTolerances_fullNames); - comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); - comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); m_ComparisonConfig_Type_processed[new Tuple(type, comparisonConfig)] = comparisonConfig; } @@ -183,18 +183,11 @@ public static void PropertyNamesToFullNames(this BaseComparisonConfig comparison } // Add the results to the ComparisonConfig. + comparisonConfig.PropertiesToConsider = comparisonConfig.PropertiesToConsider?.Concat(propertiesToConsider_fullNames).ToList() ?? propertiesToConsider_fullNames; + comparisonConfig.PropertyExceptions = comparisonConfig.PropertyExceptions?.Concat(propertyExceptions_fullNames).ToList() ?? propertyExceptions_fullNames; comparisonConfig.PropertyNumericTolerances = comparisonConfig.PropertyNumericTolerances == null ? new HashSet(comparisonConfig.PropertyNumericTolerances?.Union(propertyNumericTolerances_fullNames)) : propertyNumericTolerances_fullNames; - if (comparisonConfig.PropertiesToConsider != null) - comparisonConfig.PropertiesToConsider.UnionWith(propertiesToConsider_fullNames); - else - comparisonConfig.PropertiesToConsider = new HashSet(propertiesToConsider_fullNames); - - if (comparisonConfig.PropertyExceptions != null) - comparisonConfig.PropertyExceptions.UnionWith(propertyExceptions_fullNames); - else - comparisonConfig.PropertiesToConsider = new HashSet(propertyExceptions_fullNames); } /***************************************************/ diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index e047a2259..b1da228d9 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -193,8 +193,8 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin { // If the owner of this CustomData Dictionary is a CustomObject, // we want to consider its keys as if they were object properties for UX/UI consistency. - cc.CustomdataKeysExceptions.UnionWith(cc.PropertyExceptions); - cc.CustomdataKeysToConsider.UnionWith(cc.PropertiesToConsider); + cc.CustomdataKeysExceptions.AddRange(cc.PropertyExceptions); + cc.CustomdataKeysToConsider.AddRange(cc.PropertiesToConsider); } // Get the custom data Key, so we can check if it belongs to the exceptions. diff --git a/Diffing_Engine/Compute/IDiffing.cs b/Diffing_Engine/Compute/IDiffing.cs index bbb2b9f46..0c55680ab 100644 --- a/Diffing_Engine/Compute/IDiffing.cs +++ b/Diffing_Engine/Compute/IDiffing.cs @@ -160,7 +160,7 @@ public static Diff IDiffing(IEnumerable pastObjs, IEnumerable fo [Input("followingObjs", "Set of objects belonging to a following revision.")] [Input("propertiesToConsider", "(Optional) Properties to be considered by the Diffing when determining what objects changed. See the DiffingConfig tooltip for more info.")] [Output("diff", "Object holding the detected changes.")] - public static Diff IDiffing(IEnumerable pastObjs, IEnumerable followingObjs, HashSet propertiesToConsider = null) + public static Diff IDiffing(IEnumerable pastObjs, IEnumerable followingObjs, List propertiesToConsider = null) { DiffingConfig dc = null; if (propertiesToConsider?.Any() ?? false) diff --git a/Diffing_Engine/Create/DiffingConfig.cs b/Diffing_Engine/Create/DiffingConfig.cs index 5ca145ce4..b9d31b65e 100644 --- a/Diffing_Engine/Create/DiffingConfig.cs +++ b/Diffing_Engine/Create/DiffingConfig.cs @@ -53,7 +53,7 @@ public static DiffingConfig DiffingConfig(bool enablePropertyDiffing, bool store [Description("Defines configurations for the diffing.")] [Input("enablePropertyDiffing", "Enables the property-level diffing: differences in object properties are stored in the `ModifiedPropsPerObject` dictionary.")] [Input("storeUnchangedObjects", "If enabled, the Diff stores also the objects that did not change (`Unchanged` property).")] - public static DiffingConfig DiffingConfig(bool enablePropertyDiffing = false, bool storeUnchangedObjects = true, HashSet propertyNamesToConsider = null) + public static DiffingConfig DiffingConfig(bool enablePropertyDiffing = false, bool storeUnchangedObjects = true, List propertyNamesToConsider = null) { return new DiffingConfig() { diff --git a/Diffing_Engine/Query/ObjectDifferences.cs b/Diffing_Engine/Query/ObjectDifferences.cs index 7ab463cd5..8fab6938e 100644 --- a/Diffing_Engine/Query/ObjectDifferences.cs +++ b/Diffing_Engine/Query/ObjectDifferences.cs @@ -75,7 +75,7 @@ public static ObjectDifferences ObjectDifferences(this object pastObject, object kellermanComparer.Config.TypesToIgnore.Add(typeof(HashFragment)); // Never include the changes in HashFragment. kellermanComparer.Config.TypesToIgnore.Add(typeof(RevisionFragment)); // Never include the changes in RevisionFragment. kellermanComparer.Config.TypesToIgnore.AddRange(cc.TypeExceptions); - kellermanComparer.Config.MembersToIgnore = cc.PropertyExceptions.ToList(); + kellermanComparer.Config.MembersToIgnore = cc.PropertyExceptions; // Kellerman configuration for tolerance. // Setting Custom Tolerance for specific properties is complex with Kellerman. diff --git a/Environment_Engine/Query/HasMergeablePropertiesWith.cs b/Environment_Engine/Query/HasMergeablePropertiesWith.cs index e5a5783f2..6e73c3f4d 100644 --- a/Environment_Engine/Query/HasMergeablePropertiesWith.cs +++ b/Environment_Engine/Query/HasMergeablePropertiesWith.cs @@ -58,7 +58,7 @@ public static bool HasMergeablePropertiesWith(this Panel element, Panel other) { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new HashSet + PropertyExceptions = new List { "ExternalEdges", "Openings", @@ -81,7 +81,7 @@ public static bool HasMergeablePropertiesWith(this Opening element, Opening othe { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new HashSet + PropertyExceptions = new List { "Edges", "FrameFactorValue", @@ -116,7 +116,7 @@ public static bool HasMergeablePropertiesWith(this Space element, Space other) { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new HashSet + PropertyExceptions = new List { "Location", "Type", diff --git a/Physical_Engine/Query/UniqueConstructions.cs b/Physical_Engine/Query/UniqueConstructions.cs index b4cf14953..e5f9554c2 100644 --- a/Physical_Engine/Query/UniqueConstructions.cs +++ b/Physical_Engine/Query/UniqueConstructions.cs @@ -46,7 +46,7 @@ public static List UniqueConstructions(this List con { ComparisonConfig cc = new ComparisonConfig() { - PropertyExceptions = new HashSet + PropertyExceptions = new List { "CustomData" }, From b01df19ec56174bd7600fabcec73d54851ac4630 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Fri, 26 Jan 2024 17:09:13 +0000 Subject: [PATCH 10/19] Reverting some WIP changes --- BHoM_Engine/Query/Hash.cs | 21 +++++++++++---------- Geometry_Engine/Query/GeometryHash.cs | 1 - 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index b1da228d9..58275b8d6 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -43,8 +43,6 @@ public static partial class Query /**** Public Methods ****/ /***************************************************/ - private static bool m_isCompatibleWithGeometryHash = true; - [Description("Computes a Hash code for the iObject. The hash uniquely represents an object's state, based on its properties and their values. It can be used for comparisons." + "\nYou can change how the hash is computed by changing the settings in the ComparisonConfig.")] [Input("iObj", "iObject the hash code should be calculated for.")] @@ -78,7 +76,7 @@ public static string Hash(this IObject iObj, BaseComparisonConfig comparisonConf // ----- HASH ----- // Compute the defining string. - string hashString = HashString(iObj, cc, 0, typeof(System.Object)); + string hashString = HashString(iObj, cc, 0); if (string.IsNullOrWhiteSpace(hashString)) { @@ -119,7 +117,7 @@ private static string SHA256Hash(string str) [Input("cc", "HashConfig, options for the hash calculation.")] [Input("nestingLevel", "Nesting level of the property.")] [Input("currentPropertyFullName", "(Optional) Indicates the 'property path' of the current object, e.g. `BH.oM.Structure.Elements.Bar.Start.Point.X`")] - private static string HashString(object obj, BaseComparisonConfig cc, int nestingLevel, Type parentType, string currentPropertyFullName = null) + private static string HashString(object obj, BaseComparisonConfig cc, int nestingLevel, string currentPropertyFullName = null) { string definingString = ""; @@ -144,7 +142,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin { return ""; } - else if (type.IsNumeric(enumsAsNumbers: false)) + else if (type.IsNumeric() && type.BaseType != typeof(System.Enum)) { // If we didn't specify any custom tolerance/significant figures, just return the input. if (cc.NumericTolerance == double.MinValue && cc.SignificantFigures == int.MaxValue @@ -173,7 +171,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type.IsArray) { foreach (var element in (obj as dynamic)) - definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, type, currentPropertyFullName); + definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, currentPropertyFullName); } else if (typeof(IDictionary).IsAssignableFrom(type)) { @@ -195,6 +193,9 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin // we want to consider its keys as if they were object properties for UX/UI consistency. cc.CustomdataKeysExceptions.AddRange(cc.PropertyExceptions); cc.CustomdataKeysToConsider.AddRange(cc.PropertiesToConsider); + + cc.CustomdataKeysExceptions = cc.CustomdataKeysExceptions.Distinct().ToList(); + cc.CustomdataKeysToConsider = cc.CustomdataKeysToConsider.Distinct().ToList(); } // Get the custom data Key, so we can check if it belongs to the exceptions. @@ -208,13 +209,13 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin continue; } - definingString += $"\n{tabs}" + $"[{entry.Key.GetType().FullName}]\n{tabs}{entry.Key}:\n {HashString(entry.Value, cc, nestingLevel + 1, type, currentPropertyFullName)}"; + definingString += $"\n{tabs}" + $"[{entry.Key.GetType().FullName}]\n{tabs}{entry.Key}:\n { HashString(entry.Value, cc, nestingLevel + 1, currentPropertyFullName)}"; } } else if (typeof(IEnumerable).IsAssignableFrom(type)) { foreach (var element in (obj as dynamic)) - definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, type, currentPropertyFullName); + definingString += $"\n{tabs}" + HashString(element, cc, nestingLevel + 1, currentPropertyFullName); } else if (type.FullName.Contains("System.Collections.Generic.ObjectEqualityComparer`1")) { @@ -223,7 +224,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin else if (type == typeof(System.Data.DataTable)) { DataTable dt = obj as DataTable; - return definingString += $"{type.FullName} {string.Join(", ", dt.Columns.OfType().Select(c => c.ColumnName))}\n{tabs}" + HashString(dt.AsEnumerable(), cc, nestingLevel + 1, type, currentPropertyFullName); + return definingString += $"{type.FullName} {string.Join(", ", dt.Columns.OfType().Select(c => c.ColumnName))}\n{tabs}" + HashString(dt.AsEnumerable(), cc, nestingLevel + 1, currentPropertyFullName); } else if (typeof(IObject).IsAssignableFrom(type)) { @@ -270,7 +271,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin if (propertyValue != null) { // Recurse for this property. - propHashString = HashString(propertyValue, cc, nestingLevel + 1, type, propFullName) ?? ""; + propHashString = HashString(propertyValue, cc, nestingLevel + 1, propFullName) ?? ""; if (!string.IsNullOrWhiteSpace(propHashString)) definingString += $"\n{tabs}" + $"{type.FullName}.{propName}:\n{tabs}{propHashString} "; } diff --git a/Geometry_Engine/Query/GeometryHash.cs b/Geometry_Engine/Query/GeometryHash.cs index 47691e621..e37389c76 100644 --- a/Geometry_Engine/Query/GeometryHash.cs +++ b/Geometry_Engine/Query/GeometryHash.cs @@ -87,7 +87,6 @@ public static string GeometryHash(this IGeometry igeometry) return sb.ToString(); } - /***************************************************/ /**** Private Methods ****/ /***************************************************/ From 014fb8395e1a81d51a11d91ff6930c546f882285 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 29 Jan 2024 10:49:35 +0000 Subject: [PATCH 11/19] GeometryHash: added BaseComparisonConfig input --- Geometry_Engine/Query/GeometryHash.cs | 8 +- Geometry_Engine/Query/HashArray.cs | 135 +++++++++++++------------- 2 files changed, 72 insertions(+), 71 deletions(-) diff --git a/Geometry_Engine/Query/GeometryHash.cs b/Geometry_Engine/Query/GeometryHash.cs index e37389c76..e96f28471 100644 --- a/Geometry_Engine/Query/GeometryHash.cs +++ b/Geometry_Engine/Query/GeometryHash.cs @@ -52,11 +52,11 @@ public static partial class Query "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] [Input("bhomObj", "Input BHoMObject whose geometry will be queried by IGeometry() and which will be used for computing a Geometry Hash.")] [Output("geomHash", "Number representing a unique signature of the input object's geometry.")] - public static string GeometryHash(this IBHoMObject bhomObj) + public static string GeometryHash(this IBHoMObject bhomObj, BaseComparisonConfig comparisonConfig) { IGeometry igeom = bhomObj.IGeometry(); - return GeometryHash(igeom); + return GeometryHash(igeom, comparisonConfig); } /***************************************************/ @@ -67,12 +67,12 @@ public static string GeometryHash(this IBHoMObject bhomObj) "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] [Output("geomHash", "Number representing a unique signature of the input geometry.")] - public static string GeometryHash(this IGeometry igeometry) + public static string GeometryHash(this IGeometry igeometry, BaseComparisonConfig comparisonConfig = null) { if (igeometry == null) return null; - double[] hashArray = IHashArray(igeometry); + double[] hashArray = IHashArray(igeometry, comparisonConfig); byte[] byteArray = GetBytes(hashArray); if (m_SHA256Algorithm == null) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index b0fc2bb73..bd616ab92 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -46,12 +46,12 @@ public static partial class Query "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] [Output("hashArray", "Array of numbers representing a unique signature of the input geometry.")] - public static double[] IHashArray(this IGeometry igeometry) + public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig comparisonConfig) { if (igeometry == null) return new double[] { }; - return HashArray(igeometry as dynamic, 0); + return HashArray(igeometry as dynamic, 0, comparisonConfig); } @@ -66,7 +66,7 @@ public static double[] IHashArray(this IGeometry igeometry) [Description("The geometry hash of a Curve is obtained by first retrieving any Sub-part of the curve, if present." + "The ISubParts() methods is able to return the 'primitive' curves that a curve is composed of. " + "The GeometryHashes are then calculated for the individual parts and concatenated.")] - private static double[] HashArray(this ICurve curve, double translationFactor) + private static double[] HashArray(this ICurve curve, double translationFactor, BaseComparisonConfig comparisonConfig = null) { List subParts = curve.ISubParts().ToList(); @@ -75,10 +75,11 @@ private static double[] HashArray(this ICurve curve, double translationFactor) //Add hash ignoring endpoint for all but last curve for (int i = 0; i < subParts.Count - 1; i++) { - hashes.AddRange(HashArray(subParts[i] as dynamic, translationFactor, true)); + hashes.AddRange(HashArray(subParts[i] as dynamic, translationFactor, comparisonConfig, true)); } - //Include endpoint for hasing for last curve - hashes.AddRange(HashArray(subParts.Last() as dynamic, translationFactor, false)); + + //Include endpoint for hashing for last curve + hashes.AddRange(HashArray(subParts.Last() as dynamic, translationFactor, comparisonConfig, false)); return hashes.ToArray(); } @@ -86,15 +87,15 @@ private static double[] HashArray(this ICurve curve, double translationFactor) /***************************************************/ [Description("The GeometryHash for an Arc is calculated as the GeometryHash of the start, end and middle point of the Arc.")] - private static double[] HashArray(this Arc curve, double translationFactor, bool skipEndPoint = false) + private static double[] HashArray(this Arc curve, double translationFactor, bool skipEndPoint = false, BaseComparisonConfig comparisonConfig = null) { translationFactor += (int)TypeTranslationFactor.Arc; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor) - .Concat(curve.PointAtParameter(0.5).HashArray(translationFactor)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) + .Concat(curve.PointAtParameter(0.5).HashArray(translationFactor, comparisonConfig)); if (!skipEndPoint) - hash = hash.Concat(curve.EndPoint().HashArray(translationFactor)); + hash = hash.Concat(curve.EndPoint().HashArray(translationFactor, comparisonConfig)); return hash.ToArray(); } @@ -102,15 +103,15 @@ private static double[] HashArray(this Arc curve, double translationFactor, bool /***************************************************/ [Description("The GeometryHash for an Circle is calculated as the GeometryHash of the start, 1/3rd and 2/3rd points of the Circle.")] - private static double[] HashArray(this Circle curve, double translationFactor) + private static double[] HashArray(this Circle curve, double translationFactor, BaseComparisonConfig comparisonConfig) { // The input `skipEndPoint` is not used here because Circles do not have a clearly defined endpoint to be used in a chain of segment curves. translationFactor += (int)TypeTranslationFactor.Circle; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor) - .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor)) - .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) + .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, comparisonConfig)) + .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, comparisonConfig)); return hash.ToArray(); } @@ -118,15 +119,15 @@ private static double[] HashArray(this Circle curve, double translationFactor) /***************************************************/ [Description("The GeometryHash for an Ellipse is calculated as the GeometryHash of the start, 1/3rd and 2/3rd points of the Ellipse.")] - private static double[] HashArray(this Ellipse curve, double translationFactor) + private static double[] HashArray(this Ellipse curve, double translationFactor, BaseComparisonConfig comparisonConfig) { // The input `skipEndPoint` is not used here because Ellipses do not have a clearly defined endpoint to be used in a chain of segment curves. translationFactor += (int)TypeTranslationFactor.Ellipse; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor) - .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor)) - .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) + .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, comparisonConfig)) + .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, comparisonConfig)); return hash.ToArray(); } @@ -134,15 +135,15 @@ private static double[] HashArray(this Ellipse curve, double translationFactor) /***************************************************/ [Description("The GeometryHash for a Line is calculated as the GeometryHash of the start and end point of the Line.")] - private static double[] HashArray(this Line curve, double translationFactor, bool skipEndPoint = false) + private static double[] HashArray(this Line curve, double translationFactor, BaseComparisonConfig comparisonConfig, bool skipEndPoint = false) { translationFactor += (int)TypeTranslationFactor.Line; if (skipEndPoint) - return curve.StartPoint().HashArray(translationFactor); + return curve.StartPoint().HashArray(translationFactor, comparisonConfig); - return curve.StartPoint().HashArray(translationFactor) - .Concat(curve.EndPoint().HashArray(translationFactor)) + return curve.StartPoint().HashArray(translationFactor, comparisonConfig) + .Concat(curve.EndPoint().HashArray(translationFactor, comparisonConfig)) .ToArray(); } @@ -151,7 +152,7 @@ private static double[] HashArray(this Line curve, double translationFactor, boo [Description("The GeometryHash for a NurbsCurve is obtained by getting moving the control points " + "by a translation factor composed by the weights and a subarray of the knot vector. " + "The subarray is made by picking as many elements from the knot vector as the curve degree value.")] - private static double[] HashArray(this NurbsCurve curve, double translationFactor) + private static double[] HashArray(this NurbsCurve curve, double translationFactor, BaseComparisonConfig comparisonConfig) { // The input `skipEndPoint` is not used here because Nurbs may well extend or end before the last ControlPoint. // Also consider complex situations like Periodic curves. @@ -159,7 +160,7 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto int curveDegree = curve.Degree(); if (curveDegree == 1) - return BH.Engine.Geometry.Create.Polyline(curve.ControlPoints).HashArray(translationFactor); + return BH.Engine.Geometry.Create.Polyline(curve.ControlPoints).HashArray(translationFactor, comparisonConfig); translationFactor += (int)TypeTranslationFactor.NurbsCurve; @@ -170,7 +171,7 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto for (int i = 0; i < controlPointsCount; i++) { double sum = curve.Knots.GetRange(i, curveDegree).Sum(); - double[] doubles = curve.ControlPoints[i].HashArray(sum + curve.Weights[i] + translationFactor); + double[] doubles = curve.ControlPoints[i].HashArray(sum + curve.Weights[i] + translationFactor, comparisonConfig); concatenated.AddRange(doubles); } @@ -187,20 +188,20 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto /**** Surfaces ****/ /***************************************************/ - private static double[] HashArray(this ISurface obj, double translationFactor) + private static double[] HashArray(this ISurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) { - return HashArray(obj as dynamic, translationFactor); + return HashArray(obj as dynamic, translationFactor, comparisonConfig); } /***************************************************/ [Description("The GeometryHash for a PlanarSurface is calculated as the GeometryHash of the External and Internal boundary curves, then concatenated.")] - private static double[] HashArray(this PlanarSurface obj, double translationFactor) + private static double[] HashArray(this PlanarSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.PlanarSurface; - return obj.ExternalBoundary.HashArray(translationFactor) - .Concat(obj.InternalBoundaries.SelectMany(ib => ib.HashArray(translationFactor))).ToArray(); + return obj.ExternalBoundary.HashArray(translationFactor, comparisonConfig) + .Concat(obj.InternalBoundaries.SelectMany(ib => ib.HashArray(translationFactor, comparisonConfig))).ToArray(); } /***************************************************/ @@ -208,22 +209,22 @@ private static double[] HashArray(this PlanarSurface obj, double translationFact [Description("The GeometryHash for an Extrusion is calculated by translating the extrusion curve with the extrusion direction vector." + "A first GeometryHash is calculated for this translated curve. " + "Then, the GeometryHash of the (non-translated) extrusion curve is concatenated to the first hash to make it more reliable.")] - private static double[] HashArray(this Extrusion obj, double translationFactor) + private static double[] HashArray(this Extrusion obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.Extrusion; - return obj.Curve.ITranslate(obj.Direction).HashArray(translationFactor) - .Concat(obj.Curve.HashArray(translationFactor)).ToArray(); + return obj.Curve.ITranslate(obj.Direction).HashArray(translationFactor, comparisonConfig) + .Concat(obj.Curve.HashArray(translationFactor, comparisonConfig)).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Loft is calculated as the GeometryHash of its curves.")] - private static double[] HashArray(this Loft obj, double translationFactor) + private static double[] HashArray(this Loft obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.Loft; - return obj.Curves.SelectMany(c => c.HashArray(translationFactor)).ToArray(); + return obj.Curves.SelectMany(c => c.HashArray(translationFactor, comparisonConfig)).ToArray(); } /***************************************************/ @@ -231,7 +232,7 @@ private static double[] HashArray(this Loft obj, double translationFactor) [Description("Moving control points by a translation factor composed by the weights " + "and a subarray of the knot vector. " + "The subarray is made by picking as many elements from the knot vector as the curve degree value.")] - private static double[] HashArray(this NurbsSurface obj, double translationFactor) + private static double[] HashArray(this NurbsSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.NurbsSurface; @@ -248,28 +249,28 @@ private static double[] HashArray(this NurbsSurface obj, double translationFacto { int ptIndex = i * uv[1] + j; double vSum = vKnots.GetRange(j, obj.VDegree).Sum(); - double[] doubles = obj.ControlPoints[ptIndex].HashArray(uSum + vSum + obj.Weights[ptIndex] + translationFactor); + double[] doubles = obj.ControlPoints[ptIndex].HashArray(uSum + vSum + obj.Weights[ptIndex] + translationFactor, comparisonConfig); concatenated.AddRange(doubles); } } return concatenated - .Concat(obj.InnerTrims.SelectMany(it => it.HashArray(translationFactor))) - .Concat(obj.OuterTrims.SelectMany(it => it.HashArray(translationFactor))).ToArray(); + .Concat(obj.InnerTrims.SelectMany(it => it.HashArray(translationFactor, comparisonConfig))) + .Concat(obj.OuterTrims.SelectMany(it => it.HashArray(translationFactor, comparisonConfig))).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Pipe is calculated as the GeometryHash of its centreline translated by its radius," + "then concatenated with the GeometryHash of its centreline's StartPoint for extra reliability.")] - private static double[] HashArray(this Pipe obj, double translationFactor) + private static double[] HashArray(this Pipe obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.Pipe; - double[] result = obj.Centreline.HashArray(translationFactor + obj.Radius); + double[] result = obj.Centreline.HashArray(translationFactor + obj.Radius, comparisonConfig); if (obj.Capped) - result.Concat(obj.Centreline.IStartPoint().HashArray(translationFactor + obj.Radius)); + result.Concat(obj.Centreline.IStartPoint().HashArray(translationFactor + obj.Radius, comparisonConfig)); return result; } @@ -277,22 +278,22 @@ private static double[] HashArray(this Pipe obj, double translationFactor) /***************************************************/ [Description("The GeometryHash for a PolySurface is calculated as the GeometryHash of the individual surfaces.")] - private static double[] HashArray(this PolySurface obj, double translationFactor) + private static double[] HashArray(this PolySurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) { - return obj.Surfaces.SelectMany(s => s.HashArray(translationFactor)).ToArray(); + return obj.Surfaces.SelectMany(s => s.HashArray(translationFactor, comparisonConfig)).ToArray(); } /***************************************************/ [Description("The GeometryHash for a SurfaceTrim is calculated as the GeometryHash of its Curve3d.")] - private static double[] HashArray(this SurfaceTrim obj, double translationFactor) + private static double[] HashArray(this SurfaceTrim obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.SurfaceTrim; // We only consider the Curve3D in order to avoid being redundant with the Curve2D, // and allow distancing comparisons. - return obj.Curve3d.HashArray(translationFactor).ToArray(); + return obj.Curve3d.HashArray(translationFactor, comparisonConfig).ToArray(); } @@ -302,7 +303,7 @@ private static double[] HashArray(this SurfaceTrim obj, double translationFactor [Description("The GeometryHash for a Mesh is obtained by getting the number of faces that are attached to each control point, " + "and use that count as a translation factor for control points.")] - private static double[] HashArray(this Mesh obj, double translationFactor) + private static double[] HashArray(this Mesh obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.Mesh; @@ -327,7 +328,7 @@ private static double[] HashArray(this Mesh obj, double translationFactor) if (!dic.TryGetValue(i, out pointTranslationFactor)) pointTranslationFactor = 0; - result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor)); + result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, comparisonConfig)); } return result.ToArray(); @@ -337,7 +338,7 @@ private static double[] HashArray(this Mesh obj, double translationFactor) [Description("The GeometryHash for a Mesh3D is obtained by getting the number of faces that are attached to each control point, " + "and using that count as a translation factor for control points.")] - private static double[] HashArray(this Mesh3D obj, double translationFactor) + private static double[] HashArray(this Mesh3D obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor += (int)TypeTranslationFactor.Mesh3D; @@ -362,7 +363,7 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor) if (!dic.TryGetValue(i, out pointTranslationFactor)) pointTranslationFactor = 0; - result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor)); + result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, comparisonConfig)); } return result.ToArray(); @@ -370,23 +371,23 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor) /***************************************************/ - private static double[] HashArray(this IEnumerable points, double typeTranslationFactor) + private static double[] HashArray(this IEnumerable points, double typeTranslationFactor, BaseComparisonConfig comparisonConfig) { - return points.SelectMany(p => p.HashArray(typeTranslationFactor)).ToArray(); + return points.SelectMany(p => p.HashArray(typeTranslationFactor, comparisonConfig)).ToArray(); } /***************************************************/ [Description("The GeometryHash for a CompositeGeometry is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this CompositeGeometry obj, double translationFactor) + private static double[] HashArray(this CompositeGeometry obj, double translationFactor, BaseComparisonConfig comparisonConfig) { - return obj.Elements.SelectMany(c => c.IHashArray()).ToArray(); + return obj.Elements.SelectMany(c => c.IHashArray(comparisonConfig)).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Point is simply an array of 3 numbers composed by the Point X, Y and Z coordinates.")] - private static double[] HashArray(this Point p, double translationFactor) + private static double[] HashArray(this Point p, double translationFactor, BaseComparisonConfig comparisonConfig) { return new double[] { @@ -399,7 +400,7 @@ private static double[] HashArray(this Point p, double translationFactor) /***************************************************/ // Fallback - private static double[] HashArray(this object obj, double translationFactor) + private static double[] HashArray(this object obj, double translationFactor, BaseComparisonConfig comparisonConfig) { throw new NotImplementedException($"Could not find a {nameof(HashArray)} method for type {obj.GetType().FullName}."); } @@ -409,7 +410,7 @@ private static double[] HashArray(this object obj, double translationFactor) /***************************************************/ [Description("The GeometryHash for a Vector is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Vector obj, double translationFactor) + private static double[] HashArray(this Vector obj, double translationFactor, BaseComparisonConfig comparisonConfig) { if (translationFactor == (double)TypeTranslationFactor.Point) translationFactor = (double)TypeTranslationFactor.Vector; @@ -425,34 +426,34 @@ private static double[] HashArray(this Vector obj, double translationFactor) /***************************************************/ [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Basis obj, double translationFactor) + private static double[] HashArray(this Basis obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor = (double)TypeTranslationFactor.Basis; - var x = obj.X.HashArray(translationFactor); - var y = obj.Y.HashArray(translationFactor); - var z = obj.Z.HashArray(translationFactor); + var x = obj.X.HashArray(translationFactor, comparisonConfig); + var y = obj.Y.HashArray(translationFactor, comparisonConfig); + var z = obj.Z.HashArray(translationFactor, comparisonConfig); return x.Concat(y).Concat(z).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Cartesian obj, double translationFactor) + private static double[] HashArray(this Cartesian obj, double translationFactor, BaseComparisonConfig comparisonConfig) { translationFactor = (double)TypeTranslationFactor.Cartesian; - var x = obj.X.HashArray(translationFactor); - var y = obj.Y.HashArray(translationFactor); - var z = obj.Z.HashArray(translationFactor); - var o = obj.Origin.HashArray(translationFactor); + var x = obj.X.HashArray(translationFactor, comparisonConfig); + var y = obj.Y.HashArray(translationFactor, comparisonConfig); + var z = obj.Z.HashArray(translationFactor, comparisonConfig); + var o = obj.Origin.HashArray(translationFactor, comparisonConfig); return x.Concat(y).Concat(z).Concat(o).ToArray(); } /***************************************************/ [Description("The GeometryHash for a TransformMatrix is given as the concatenated numbers of the matrix.")] - private static double[] HashArray(this TransformMatrix obj, double translationFactor) + private static double[] HashArray(this TransformMatrix obj, double translationFactor, BaseComparisonConfig comparisonConfig) { return obj.Matrix.Cast().ToArray(); } From 2158354a57177d1fff870cac8f9b7573b4aa7989 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 29 Jan 2024 18:39:01 +0000 Subject: [PATCH 12/19] Implementation of ComparisonConfig options --- Geometry_Engine/Query/GeometryHash.cs | 24 +- Geometry_Engine/Query/HashArray.cs | 434 ++++++++++++++++++++------ 2 files changed, 363 insertions(+), 95 deletions(-) diff --git a/Geometry_Engine/Query/GeometryHash.cs b/Geometry_Engine/Query/GeometryHash.cs index e96f28471..755946dcc 100644 --- a/Geometry_Engine/Query/GeometryHash.cs +++ b/Geometry_Engine/Query/GeometryHash.cs @@ -51,8 +51,8 @@ public static partial class Query "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] [Input("bhomObj", "Input BHoMObject whose geometry will be queried by IGeometry() and which will be used for computing a Geometry Hash.")] - [Output("geomHash", "Number representing a unique signature of the input object's geometry.")] - public static string GeometryHash(this IBHoMObject bhomObj, BaseComparisonConfig comparisonConfig) + [Output("geomHash", "Value representing a unique signature of the input object's geometry.")] + public static string GeometryHash(this IBHoMObject bhomObj, BaseComparisonConfig comparisonConfig = null) { IGeometry igeom = bhomObj.IGeometry(); @@ -66,13 +66,29 @@ public static string GeometryHash(this IBHoMObject bhomObj, BaseComparisonConfig "\nThe number of points is reduced to the minimum essential to determine uniquely any geometry." + "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] - [Output("geomHash", "Number representing a unique signature of the input geometry.")] + [Output("geomHash", "Value representing a unique signature of the input geometry.")] public static string GeometryHash(this IGeometry igeometry, BaseComparisonConfig comparisonConfig = null) + { + return GeometryHash(igeometry, comparisonConfig, null); + } + + /***************************************************/ + + [Description("Returns a signature of the input geometry, useful for diffing." + + "\nThe hash is computed as a serialised array representing the coordinate of significant points taken on the geometry." + + "\nThe number of points is reduced to the minimum essential to determine uniquely any geometry." + + "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] + [Input("igeometry", "Geometry you want to compute the hash for.")] + [Input("comparisonConfig", "Configurations on how the hash is computed, with options for numerical approximation, type exceptions and many others.")] + [Input("fullName", "Name of the property that holds the target object to calculate the hash for. This name will be used to seek any matching custom configuration to apply against the `comparisonConfig` input.")] + [Output("geomHash", "Value representing a unique signature of the input geometry.")] + public static string GeometryHash(this IGeometry igeometry, BaseComparisonConfig comparisonConfig, string fullName) { if (igeometry == null) return null; - double[] hashArray = IHashArray(igeometry, comparisonConfig); + double[] hashArray = IHashArray(igeometry, comparisonConfig, fullName); byte[] byteArray = GetBytes(hashArray); if (m_SHA256Algorithm == null) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index bd616ab92..3697c1d2b 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -45,13 +45,33 @@ public static partial class Query "\nThe number of points is reduced to the minimum essential to determine uniquely any geometry." + "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] + [Input("igeometry", "Geometry you want to compute the hash array for.")] + [Input("comparisonConfig", "Configurations on how the hash array is computed, with options for numerical approximation, type exceptions and many others.")] [Output("hashArray", "Array of numbers representing a unique signature of the input geometry.")] - public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig comparisonConfig) + public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig comparisonConfig = null) + { + return HashArray(igeometry as dynamic, 0, comparisonConfig, null); + } + + /***************************************************/ + + [Description("Returns a signature of the input geometry, useful for distance-based comparisons and diffing." + + "\nThe hash is computed as an array representing the coordinate of significant points taken on the geometry." + + "\nThe number of points is reduced to the minimum essential to determine uniquely any geometry." + + "\nAdditionally, the resulting points are transformed based on the source geometry type, to remove or minimize collisions." + + "\n(Any transformation so performed is translational only, in order to support geometrical tolerance, i.e. numerical distance, when comparing GeometryHashes downstream).")] + [Input("igeometry", "Geometry you want to compute the hash array for.")] + [Input("comparisonConfig", "Configurations on how the hash array is computed, with options for numerical approximation, type exceptions and many others.")] + [Input("fullName", "Name of the property that holds the target object to calculate the hash array for. This name will be used to seek any matching custom configuration to apply against the `comparisonConfig` input.")] + [Output("hashArray", "Array of numbers representing a unique signature of the input geometry.")] + public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig comparisonConfig, string fullName = null) { if (igeometry == null) return new double[] { }; - return HashArray(igeometry as dynamic, 0, comparisonConfig); + fullName = fullName ?? igeometry.GetType().FullName; + + return HashArray(igeometry as dynamic, 0, comparisonConfig, fullName); } @@ -66,8 +86,11 @@ public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig [Description("The geometry hash of a Curve is obtained by first retrieving any Sub-part of the curve, if present." + "The ISubParts() methods is able to return the 'primitive' curves that a curve is composed of. " + "The GeometryHashes are then calculated for the individual parts and concatenated.")] - private static double[] HashArray(this ICurve curve, double translationFactor, BaseComparisonConfig comparisonConfig = null) + private static double[] HashArray(this ICurve curve, double translationFactor, BaseComparisonConfig comparisonConfig = null, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ICurve).IsAssignableFrom(t)) ?? false) + return default; + List subParts = curve.ISubParts().ToList(); List hashes = new List(); @@ -75,11 +98,22 @@ private static double[] HashArray(this ICurve curve, double translationFactor, B //Add hash ignoring endpoint for all but last curve for (int i = 0; i < subParts.Count - 1; i++) { - hashes.AddRange(HashArray(subParts[i] as dynamic, translationFactor, comparisonConfig, true)); + hashes.AddRange(HashArray(subParts[i] as dynamic, + translationFactor, + skipEndPoint: true, + comparisonConfig: comparisonConfig, + fullName: fullName.AppendPropertyName($"[{i}]")) + ); } //Include endpoint for hashing for last curve - hashes.AddRange(HashArray(subParts.Last() as dynamic, translationFactor, comparisonConfig, false)); + hashes.AddRange(HashArray(subParts.Last() as dynamic, + translationFactor, + skipEndPoint: false, + comparisonConfig: comparisonConfig, + fullName: fullName.AppendPropertyName($"[{subParts.Count - 1}]") + ) + ); return hashes.ToArray(); } @@ -87,15 +121,23 @@ private static double[] HashArray(this ICurve curve, double translationFactor, B /***************************************************/ [Description("The GeometryHash for an Arc is calculated as the GeometryHash of the start, end and middle point of the Arc.")] - private static double[] HashArray(this Arc curve, double translationFactor, bool skipEndPoint = false, BaseComparisonConfig comparisonConfig = null) + private static double[] HashArray(this Arc curve, double translationFactor, bool skipEndPoint = false, BaseComparisonConfig comparisonConfig = null, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ICurve).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Arc; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) - .Concat(curve.PointAtParameter(0.5).HashArray(translationFactor, comparisonConfig)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{fullName}.{nameof(StartPoint)}")) + .Concat(curve.PointAtParameter(0.5).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(PointAtParameter)}(5e-1)")) + ); if (!skipEndPoint) - hash = hash.Concat(curve.EndPoint().HashArray(translationFactor, comparisonConfig)); + hash = hash.Concat(curve.EndPoint().HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(EndPoint)}"))); return hash.ToArray(); } @@ -103,15 +145,25 @@ private static double[] HashArray(this Arc curve, double translationFactor, bool /***************************************************/ [Description("The GeometryHash for an Circle is calculated as the GeometryHash of the start, 1/3rd and 2/3rd points of the Circle.")] - private static double[] HashArray(this Circle curve, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Circle curve, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Circle).IsAssignableFrom(t)) ?? false) + return default; + // The input `skipEndPoint` is not used here because Circles do not have a clearly defined endpoint to be used in a chain of segment curves. translationFactor += (int)TypeTranslationFactor.Circle; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) - .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, comparisonConfig)) - .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, comparisonConfig)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(StartPoint)}")) + .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(PointAtParameter)}(33e-2)")) + ).Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(PointAtParameter)}(66e-2)")) + ); return hash.ToArray(); } @@ -119,15 +171,25 @@ private static double[] HashArray(this Circle curve, double translationFactor, B /***************************************************/ [Description("The GeometryHash for an Ellipse is calculated as the GeometryHash of the start, 1/3rd and 2/3rd points of the Ellipse.")] - private static double[] HashArray(this Ellipse curve, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Ellipse curve, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Ellipse).IsAssignableFrom(t)) ?? false) + return default; + // The input `skipEndPoint` is not used here because Ellipses do not have a clearly defined endpoint to be used in a chain of segment curves. translationFactor += (int)TypeTranslationFactor.Ellipse; - IEnumerable hash = curve.StartPoint().HashArray(translationFactor, comparisonConfig) - .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, comparisonConfig)) - .Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, comparisonConfig)); + IEnumerable hash = curve.StartPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(StartPoint)}")) + .Concat(curve.PointAtParameter(0.33).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(PointAtParameter)}(33e-2)")) + ).Concat(curve.PointAtParameter(0.66).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(PointAtParameter)}(66e-2)")) + ); return hash.ToArray(); } @@ -135,16 +197,25 @@ private static double[] HashArray(this Ellipse curve, double translationFactor, /***************************************************/ [Description("The GeometryHash for a Line is calculated as the GeometryHash of the start and end point of the Line.")] - private static double[] HashArray(this Line curve, double translationFactor, BaseComparisonConfig comparisonConfig, bool skipEndPoint = false) + private static double[] HashArray(this Line curve, double translationFactor, BaseComparisonConfig comparisonConfig, bool skipEndPoint = false, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Line).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Line; if (skipEndPoint) - return curve.StartPoint().HashArray(translationFactor, comparisonConfig); - - return curve.StartPoint().HashArray(translationFactor, comparisonConfig) - .Concat(curve.EndPoint().HashArray(translationFactor, comparisonConfig)) - .ToArray(); + return curve.StartPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(StartPoint)}")); + + return curve.StartPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(StartPoint)}")) + .Concat(curve.EndPoint().HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(EndPoint)}")) + ).ToArray(); } /***************************************************/ @@ -152,15 +223,20 @@ private static double[] HashArray(this Line curve, double translationFactor, Bas [Description("The GeometryHash for a NurbsCurve is obtained by getting moving the control points " + "by a translation factor composed by the weights and a subarray of the knot vector. " + "The subarray is made by picking as many elements from the knot vector as the curve degree value.")] - private static double[] HashArray(this NurbsCurve curve, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this NurbsCurve curve, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(NurbsCurve).IsAssignableFrom(t)) ?? false) + return default; + // The input `skipEndPoint` is not used here because Nurbs may well extend or end before the last ControlPoint. // Also consider complex situations like Periodic curves. int curveDegree = curve.Degree(); if (curveDegree == 1) - return BH.Engine.Geometry.Create.Polyline(curve.ControlPoints).HashArray(translationFactor, comparisonConfig); + return BH.Engine.Geometry.Create.Polyline(curve.ControlPoints).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(curve.ControlPoints)}")); translationFactor += (int)TypeTranslationFactor.NurbsCurve; @@ -171,7 +247,9 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto for (int i = 0; i < controlPointsCount; i++) { double sum = curve.Knots.GetRange(i, curveDegree).Sum(); - double[] doubles = curve.ControlPoints[i].HashArray(sum + curve.Weights[i] + translationFactor, comparisonConfig); + double[] doubles = curve.ControlPoints[i].HashArray(sum + curve.Weights[i] + translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(curve.ControlPoints)}[{i}]")); concatenated.AddRange(doubles); } @@ -188,20 +266,31 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto /**** Surfaces ****/ /***************************************************/ - private static double[] HashArray(this ISurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this ISurface obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { - return HashArray(obj as dynamic, translationFactor, comparisonConfig); + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ISurface).IsAssignableFrom(t)) ?? false) + return default; + + return HashArray(obj as dynamic, translationFactor, comparisonConfig, fullName); } /***************************************************/ [Description("The GeometryHash for a PlanarSurface is calculated as the GeometryHash of the External and Internal boundary curves, then concatenated.")] - private static double[] HashArray(this PlanarSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this PlanarSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(PlanarSurface).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.PlanarSurface; - return obj.ExternalBoundary.HashArray(translationFactor, comparisonConfig) - .Concat(obj.InternalBoundaries.SelectMany(ib => ib.HashArray(translationFactor, comparisonConfig))).ToArray(); + return obj.ExternalBoundary.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.ExternalBoundary)}")) + .Concat(obj.InternalBoundaries.SelectMany((ib, i) => ib.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.InternalBoundaries)}[{i}]")) + )).ToArray(); } /***************************************************/ @@ -209,22 +298,38 @@ private static double[] HashArray(this PlanarSurface obj, double translationFact [Description("The GeometryHash for an Extrusion is calculated by translating the extrusion curve with the extrusion direction vector." + "A first GeometryHash is calculated for this translated curve. " + "Then, the GeometryHash of the (non-translated) extrusion curve is concatenated to the first hash to make it more reliable.")] - private static double[] HashArray(this Extrusion obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Extrusion obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Extrusion).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Extrusion; - return obj.Curve.ITranslate(obj.Direction).HashArray(translationFactor, comparisonConfig) - .Concat(obj.Curve.HashArray(translationFactor, comparisonConfig)).ToArray(); + return obj.Curve.ITranslate(obj.Direction).HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Curve)}")) + .Concat(obj.Curve.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Curve)}")) + ).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Loft is calculated as the GeometryHash of its curves.")] - private static double[] HashArray(this Loft obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Loft obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Loft).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Loft; - return obj.Curves.SelectMany(c => c.HashArray(translationFactor, comparisonConfig)).ToArray(); + return obj.Curves.SelectMany((c, i) => + c.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Curves)}[{i}]") + ) + ).ToArray(); } /***************************************************/ @@ -232,8 +337,11 @@ private static double[] HashArray(this Loft obj, double translationFactor, BaseC [Description("Moving control points by a translation factor composed by the weights " + "and a subarray of the knot vector. " + "The subarray is made by picking as many elements from the knot vector as the curve degree value.")] - private static double[] HashArray(this NurbsSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this NurbsSurface obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(NurbsSurface).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.NurbsSurface; List uv = obj.UVCount(); @@ -249,28 +357,47 @@ private static double[] HashArray(this NurbsSurface obj, double translationFacto { int ptIndex = i * uv[1] + j; double vSum = vKnots.GetRange(j, obj.VDegree).Sum(); - double[] doubles = obj.ControlPoints[ptIndex].HashArray(uSum + vSum + obj.Weights[ptIndex] + translationFactor, comparisonConfig); + double[] doubles = obj.ControlPoints[ptIndex].HashArray(uSum + vSum + obj.Weights[ptIndex] + translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.ControlPoints)}[{ptIndex}]")); concatenated.AddRange(doubles); } } return concatenated - .Concat(obj.InnerTrims.SelectMany(it => it.HashArray(translationFactor, comparisonConfig))) - .Concat(obj.OuterTrims.SelectMany(it => it.HashArray(translationFactor, comparisonConfig))).ToArray(); + .Concat(obj.InnerTrims.SelectMany((it, i) => + it.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.InnerTrims)}[{i}]")) + ) + ) + .Concat(obj.OuterTrims.SelectMany((it, i) => + it.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.OuterTrims)}[{i}]")) + ) + ).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Pipe is calculated as the GeometryHash of its centreline translated by its radius," + "then concatenated with the GeometryHash of its centreline's StartPoint for extra reliability.")] - private static double[] HashArray(this Pipe obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Pipe obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Pipe).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Pipe; - double[] result = obj.Centreline.HashArray(translationFactor + obj.Radius, comparisonConfig); + double[] result = obj.Centreline.HashArray(translationFactor + obj.Radius, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Centreline)}")); if (obj.Capped) - result.Concat(obj.Centreline.IStartPoint().HashArray(translationFactor + obj.Radius, comparisonConfig)); + result.Concat( + obj.Centreline.IStartPoint().HashArray(translationFactor + obj.Radius, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Centreline)}.{nameof(StartPoint)}")) // StartPoint's method name must not be passed as IStartPoint here. + ); return result; } @@ -278,22 +405,34 @@ private static double[] HashArray(this Pipe obj, double translationFactor, BaseC /***************************************************/ [Description("The GeometryHash for a PolySurface is calculated as the GeometryHash of the individual surfaces.")] - private static double[] HashArray(this PolySurface obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this PolySurface obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { - return obj.Surfaces.SelectMany(s => s.HashArray(translationFactor, comparisonConfig)).ToArray(); + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(PolySurface).IsAssignableFrom(t)) ?? false) + return default; + + return obj.Surfaces.SelectMany((s, i) => + s.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Surfaces)}[{i}]")) + ).ToArray(); } /***************************************************/ [Description("The GeometryHash for a SurfaceTrim is calculated as the GeometryHash of its Curve3d.")] - private static double[] HashArray(this SurfaceTrim obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this SurfaceTrim obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(SurfaceTrim).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.SurfaceTrim; // We only consider the Curve3D in order to avoid being redundant with the Curve2D, // and allow distancing comparisons. - return obj.Curve3d.HashArray(translationFactor, comparisonConfig).ToArray(); + return obj.Curve3d.HashArray(translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Curve3d)}")).ToArray(); } @@ -303,22 +442,26 @@ private static double[] HashArray(this SurfaceTrim obj, double translationFactor [Description("The GeometryHash for a Mesh is obtained by getting the number of faces that are attached to each control point, " + "and use that count as a translation factor for control points.")] - private static double[] HashArray(this Mesh obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Mesh obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Mesh).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Mesh; var dic = new Dictionary(); - for (int i = 0; i < obj.Faces.Count; i++) - { - foreach (var faceIndex in obj.Faces[i].FaceIndices()) + if (!comparisonConfig?.TypeExceptions?.Any(t => typeof(Face).IsAssignableFrom(t)) ?? true) + for (int i = 0; i < obj.Faces.Count; i++) { - if (dic.ContainsKey(faceIndex)) - dic[faceIndex] += i; - else - dic[faceIndex] = i; + foreach (var faceIndex in obj.Faces[i].FaceIndices()) + { + if (dic.ContainsKey(faceIndex)) + dic[faceIndex] += i; + else + dic[faceIndex] = i; + } } - } List result = new List(); @@ -328,7 +471,13 @@ private static double[] HashArray(this Mesh obj, double translationFactor, BaseC if (!dic.TryGetValue(i, out pointTranslationFactor)) pointTranslationFactor = 0; - result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, comparisonConfig)); + result.AddRange( + obj.Vertices[i].HashArray( + pointTranslationFactor + translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Vertices)}[{i}]") + ) + ); } return result.ToArray(); @@ -338,22 +487,26 @@ private static double[] HashArray(this Mesh obj, double translationFactor, BaseC [Description("The GeometryHash for a Mesh3D is obtained by getting the number of faces that are attached to each control point, " + "and using that count as a translation factor for control points.")] - private static double[] HashArray(this Mesh3D obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Mesh3D obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Mesh3D).IsAssignableFrom(t)) ?? false) + return default; + translationFactor += (int)TypeTranslationFactor.Mesh3D; var dic = new Dictionary(); - for (int i = 0; i < obj.Faces.Count; i++) - { - foreach (var faceIndex in obj.Faces[i].FaceIndices()) + if (!comparisonConfig?.TypeExceptions?.Any(t => typeof(Face).IsAssignableFrom(t)) ?? true) + for (int i = 0; i < obj.Faces.Count; i++) { - if (dic.ContainsKey(faceIndex)) - dic[faceIndex] += i; - else - dic[faceIndex] = i; + foreach (var faceIndex in obj.Faces[i].FaceIndices()) + { + if (dic.ContainsKey(faceIndex)) + dic[faceIndex] += i; + else + dic[faceIndex] = i; + } } - } List result = new List(); @@ -363,7 +516,12 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor, Bas if (!dic.TryGetValue(i, out pointTranslationFactor)) pointTranslationFactor = 0; - result.AddRange(obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, comparisonConfig)); + result.AddRange( + obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Vertices)}[{i}]") + ) + ); } return result.ToArray(); @@ -371,36 +529,53 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor, Bas /***************************************************/ - private static double[] HashArray(this IEnumerable points, double typeTranslationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this IEnumerable points, double typeTranslationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { - return points.SelectMany(p => p.HashArray(typeTranslationFactor, comparisonConfig)).ToArray(); + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(IEnumerable).IsAssignableFrom(t)) ?? false) + return default; + + return points.SelectMany((p, i) => p.HashArray(typeTranslationFactor, comparisonConfig, fullName == null ? null : $"{fullName}[{i}]")).ToArray(); } /***************************************************/ [Description("The GeometryHash for a CompositeGeometry is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this CompositeGeometry obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this CompositeGeometry obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { - return obj.Elements.SelectMany(c => c.IHashArray(comparisonConfig)).ToArray(); + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(CompositeGeometry).IsAssignableFrom(t)) ?? false) + return default; + + return obj.Elements.SelectMany((c, i) => c.IHashArray(comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Elements)}[{i}]"))).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Point is simply an array of 3 numbers composed by the Point X, Y and Z coordinates.")] - private static double[] HashArray(this Point p, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Point p, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Point).IsAssignableFrom(t)) ?? false) + return default; + + if (comparisonConfig == null) + return new double[] + { + p.X + translationFactor, + p.Y + translationFactor, + p.Z + translationFactor + }; + return new double[] { - p.X + translationFactor, - p.Y + translationFactor, - p.Z + translationFactor + (p.X + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(p.X)}"), comparisonConfig), + (p.Y + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(p.Y)}"), comparisonConfig), + (p.Z + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(p.Z)}"), comparisonConfig) }; } /***************************************************/ // Fallback - private static double[] HashArray(this object obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this object obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { throw new NotImplementedException($"Could not find a {nameof(HashArray)} method for type {obj.GetType().FullName}."); } @@ -410,52 +585,129 @@ private static double[] HashArray(this object obj, double translationFactor, Bas /***************************************************/ [Description("The GeometryHash for a Vector is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Vector obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Vector obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Vector).IsAssignableFrom(t)) ?? false) + return default; + if (translationFactor == (double)TypeTranslationFactor.Point) translationFactor = (double)TypeTranslationFactor.Vector; + if (comparisonConfig == null) + return new double[] + { + obj.X + translationFactor, + obj.Y + translationFactor, + obj.Z + translationFactor + }; + return new double[] { - obj.X + translationFactor, - obj.Y + translationFactor, - obj.Z + translationFactor + (obj.X + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(obj.X)}"), comparisonConfig), + (obj.Y + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(obj.Y)}"), comparisonConfig), + (obj.Z + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(obj.Z)}"), comparisonConfig) }; } /***************************************************/ [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Basis obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Basis obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Basis).IsAssignableFrom(t)) ?? false) + return default; + translationFactor = (double)TypeTranslationFactor.Basis; - var x = obj.X.HashArray(translationFactor, comparisonConfig); - var y = obj.Y.HashArray(translationFactor, comparisonConfig); - var z = obj.Z.HashArray(translationFactor, comparisonConfig); + var x = obj.X.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.X)}")); + var y = obj.Y.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Y)}")); + var z = obj.Z.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Z)}")); return x.Concat(y).Concat(z).ToArray(); } /***************************************************/ [Description("The GeometryHash for a Basis is given as the concatenated GeometryHash of the single elements composing it.")] - private static double[] HashArray(this Cartesian obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this Cartesian obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Cartesian).IsAssignableFrom(t)) ?? false) + return default; + translationFactor = (double)TypeTranslationFactor.Cartesian; - var x = obj.X.HashArray(translationFactor, comparisonConfig); - var y = obj.Y.HashArray(translationFactor, comparisonConfig); - var z = obj.Z.HashArray(translationFactor, comparisonConfig); - var o = obj.Origin.HashArray(translationFactor, comparisonConfig); + var x = obj.X.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.X)}")); + var y = obj.Y.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Y)}")); + var z = obj.Z.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Z)}")); + var o = obj.Origin.HashArray(translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Origin)}")); + return x.Concat(y).Concat(z).Concat(o).ToArray(); } /***************************************************/ [Description("The GeometryHash for a TransformMatrix is given as the concatenated numbers of the matrix.")] - private static double[] HashArray(this TransformMatrix obj, double translationFactor, BaseComparisonConfig comparisonConfig) + private static double[] HashArray(this TransformMatrix obj, double translationFactor, BaseComparisonConfig comparisonConfig, string fullName = null) + { + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(TransformMatrix).IsAssignableFrom(t)) ?? false) + return default; + + if (comparisonConfig == null) + return obj.Matrix.Cast().ToArray(); + + return obj.Matrix.Cast() + .Select(n => n.ValueToInclude(fullName.AppendPropertyName($"{nameof(obj.Matrix)}"), comparisonConfig)) + .ToArray(); + } + + /***************************************************/ + /**** Other private methods ****/ + /***************************************************/ + + [Description("The GeometryHash for a TransformMatrix is given as the concatenated numbers of the matrix.")] + private static string AppendPropertyName(this string fullName, string toAppend) { - return obj.Matrix.Cast().ToArray(); + if (fullName == null) + return fullName; + + return $"{fullName}.{toAppend}"; + } + + /***************************************************/ + + [Description("Determine whether a certain object property should be included in the Hash computation. " + + "This is based on the property full name and the settings in the ComparisonConfig.")] + private static bool IsPropertyIncluded(string propFullName, BaseComparisonConfig cc) + { + if (cc == null || string.IsNullOrWhiteSpace(propFullName)) + return true; + + // Skip if the property is among the PropertyExceptions. + if ((cc.PropertyExceptions?.Any(pe => propFullName.EndsWith(pe) || propFullName.WildcardMatch(pe)) ?? false)) + return false; + + // If the PropertiesToConsider contains at least a value, ensure that this property is "compatible" with at least one of them. + // Compatible means to check not only that the current propFullName is among the propertiesToInclude; + // we need to consider this propFullName ALSO IF there is at least one PropertyToInclude that specifies a property that is a child of the current propFullName. + if ((cc.PropertiesToConsider?.Any() ?? false) && + !cc.PropertiesToConsider.Any(ptc => ptc.StartsWith(propFullName) || propFullName.StartsWith(ptc))) // we want to make sure that we do not exclude sub-properties to include, hence the OR condition. + return false; + + return true; + } + + /***************************************************/ + + [Description("Determine whether a certain object property should be included in the Hash computation. " + + "This is based on the property full name and the settings in the ComparisonConfig.")] + private static double ValueToInclude(this double number, string propFullName, BaseComparisonConfig cc) + { + if (cc == null || string.IsNullOrWhiteSpace(propFullName)) + return number; + + if (!IsPropertyIncluded(propFullName, cc)) + return 0; + + return BH.Engine.Base.Query.NumericalApproximation(number, propFullName, cc); } /***************************************************/ From 41a02f60eda71266b7c3c6ed0d90fcd95ecd9f1f Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 29 Jan 2024 18:39:35 +0000 Subject: [PATCH 13/19] Hash(): wiring of new GeometryHash signature --- BHoM_Engine/Query/Hash.cs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index 58275b8d6..93b032cad 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -236,7 +236,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin if (cc.UseGeometryHash && typeof(IGeometry).IsAssignableFrom(type)) { - return GeometryHash((IGeometry)obj).ToString(); + return GeometryHash((IGeometry)obj, cc, currentPropertyFullName).ToString(); } // If the object is an IObject (= a BHoM class), let's look at its properties. @@ -290,7 +290,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin /***************************************************/ - private static string GeometryHash(this IGeometry igeom) + private static string GeometryHash(this IGeometry igeom, BaseComparisonConfig comparisonConfig, string fullName) { if (igeom == null) return null; @@ -301,16 +301,12 @@ private static string GeometryHash(this IGeometry igeom) if (!mis?.Any() ?? true) throw new InvalidOperationException("Could not dynamically load the GeometryHash method."); - m_GeomHashFunc = (Func)Delegate.CreateDelegate(typeof(Func), mis.First()); + m_GeomHashFunc = (Func)Delegate.CreateDelegate(typeof(Func), mis.First()); } - return m_GeomHashFunc(igeom); + return m_GeomHashFunc(igeom, comparisonConfig, fullName); } - private static Func m_GeomHashFunc = null; + private static Func m_GeomHashFunc = null; } -} - - - - +} \ No newline at end of file From a2ff3037a166629775c2bc90f2943ac8c4d027f4 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 29 Jan 2024 20:36:44 +0000 Subject: [PATCH 14/19] GeometryHash: add necessary null checks --- Geometry_Engine/Query/GeometryHash.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Geometry_Engine/Query/GeometryHash.cs b/Geometry_Engine/Query/GeometryHash.cs index 755946dcc..91735d527 100644 --- a/Geometry_Engine/Query/GeometryHash.cs +++ b/Geometry_Engine/Query/GeometryHash.cs @@ -89,6 +89,9 @@ public static string GeometryHash(this IGeometry igeometry, BaseComparisonConfig return null; double[] hashArray = IHashArray(igeometry, comparisonConfig, fullName); + if (hashArray == null) + return null; + byte[] byteArray = GetBytes(hashArray); if (m_SHA256Algorithm == null) @@ -109,6 +112,9 @@ public static string GeometryHash(this IGeometry igeometry, BaseComparisonConfig private static byte[] GetBytes(this double[] values) { + if (values == null) + return default; + return values.SelectMany(value => BitConverter.GetBytes(value)).ToArray(); } From cdb77a4234b8b10a38698e88d60b79e28411b70d Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Mon, 29 Jan 2024 20:41:17 +0000 Subject: [PATCH 15/19] HashArray: necessary checks in ICurve+NurbsCurve --- Geometry_Engine/Query/HashArray.cs | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index 3697c1d2b..5e943d773 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -91,8 +91,24 @@ private static double[] HashArray(this ICurve curve, double translationFactor, B if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ICurve).IsAssignableFrom(t)) ?? false) return default; + if (curve == null) + return default; + List subParts = curve.ISubParts().ToList(); + if (!subParts.Any()) + if (curve is Polyline) + return HashArray(curve.IControlPoints(), + translationFactor, + comparisonConfig: comparisonConfig, + fullName: fullName.AppendPropertyName($"{nameof(ControlPoints)}")); + else + return HashArray(curve as dynamic, + translationFactor, + skipEndPoint: false, + comparisonConfig: comparisonConfig, + fullName: fullName); + List hashes = new List(); //Add hash ignoring endpoint for all but last curve @@ -231,7 +247,7 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto // The input `skipEndPoint` is not used here because Nurbs may well extend or end before the last ControlPoint. // Also consider complex situations like Periodic curves. - int curveDegree = curve.Degree(); + int curveDegree = Math.Abs(curve.Degree()); if (curveDegree == 1) return BH.Engine.Geometry.Create.Polyline(curve.ControlPoints).HashArray(translationFactor, @@ -246,8 +262,12 @@ private static double[] HashArray(this NurbsCurve curve, double translationFacto for (int i = 0; i < controlPointsCount; i++) { - double sum = curve.Knots.GetRange(i, curveDegree).Sum(); - double[] doubles = curve.ControlPoints[i].HashArray(sum + curve.Weights[i] + translationFactor, + // Use the sum of the knots plus the i-Weight to obtain an unique traslation factor. + double knotsSum = 0; + if (i < curve.Knots.Count - 1 && curveDegree < curve.Knots.Count - 1) + knotsSum = curve.Knots.GetRange(Math.Min(i, curve.Knots.Count), Math.Min(curve.Knots.Count - 1 - i, curveDegree)).Sum(); + + double[] doubles = curve.ControlPoints[i].HashArray(knotsSum + curve.Weights.ElementAtOrDefault(i) + translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(curve.ControlPoints)}[{i}]")); concatenated.AddRange(doubles); From 0861ca527c6bee6e12edeb746de49672cd9e04c0 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Tue, 30 Jan 2024 10:43:50 +0000 Subject: [PATCH 16/19] HashArray(Mesh): Add null checks --- Geometry_Engine/Query/HashArray.cs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index 5e943d773..2fe49d873 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -496,7 +496,7 @@ private static double[] HashArray(this Mesh obj, double translationFactor, BaseC pointTranslationFactor + translationFactor, comparisonConfig, fullName.AppendPropertyName($"{nameof(obj.Vertices)}[{i}]") - ) + ) ?? new double[0] ); } @@ -537,10 +537,11 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor, Bas pointTranslationFactor = 0; result.AddRange( - obj.Vertices[i].HashArray(pointTranslationFactor + translationFactor, - comparisonConfig, - fullName.AppendPropertyName($"{nameof(obj.Vertices)}[{i}]") - ) + obj.Vertices[i].HashArray( + pointTranslationFactor + translationFactor, + comparisonConfig, + fullName.AppendPropertyName($"{nameof(obj.Vertices)}[{i}]") + ) ?? new double[0] ); } From a8f4631a0448436e7ff194c05c4176a2a7e93439 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Tue, 30 Jan 2024 10:59:30 +0000 Subject: [PATCH 17/19] HashArray(Mesh): if Points excluded, include Faces Faces get included in the HashArray only if: 1) they were not specified as a TypeException, and 2) a TypeException for Point was specified. This is required because if both conditions are true, then at least "topological" information is included in the HashArray. --- Geometry_Engine/Query/HashArray.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index 2fe49d873..9e72e2a05 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -470,10 +470,15 @@ private static double[] HashArray(this Mesh obj, double translationFactor, BaseC translationFactor += (int)TypeTranslationFactor.Mesh; var dic = new Dictionary(); + List result = new List(); if (!comparisonConfig?.TypeExceptions?.Any(t => typeof(Face).IsAssignableFrom(t)) ?? true) for (int i = 0; i < obj.Faces.Count; i++) { + // If Points are excluded from the HashArray, include at least "topological" information i.e. the faces + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Point).IsAssignableFrom(t)) ?? false) + result.AddRange(obj.Faces[i].FaceIndices().Select(n => n)); + foreach (var faceIndex in obj.Faces[i].FaceIndices()) { if (dic.ContainsKey(faceIndex)) @@ -483,8 +488,6 @@ private static double[] HashArray(this Mesh obj, double translationFactor, BaseC } } - List result = new List(); - for (int i = 0; i < obj.Vertices.Count; i++) { int pointTranslationFactor; @@ -515,10 +518,15 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor, Bas translationFactor += (int)TypeTranslationFactor.Mesh3D; var dic = new Dictionary(); + List result = new List(); if (!comparisonConfig?.TypeExceptions?.Any(t => typeof(Face).IsAssignableFrom(t)) ?? true) for (int i = 0; i < obj.Faces.Count; i++) { + // If Points are excluded from the HashArray, include at least "topological" information i.e. the faces + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(Point).IsAssignableFrom(t)) ?? false) + result.AddRange(obj.Faces[i].FaceIndices().Select(n => n)); + foreach (var faceIndex in obj.Faces[i].FaceIndices()) { if (dic.ContainsKey(faceIndex)) @@ -528,8 +536,6 @@ private static double[] HashArray(this Mesh3D obj, double translationFactor, Bas } } - List result = new List(); - for (int i = 0; i < obj.Vertices.Count; i++) { int pointTranslationFactor; @@ -621,7 +627,7 @@ private static double[] HashArray(this Vector obj, double translationFactor, Bas obj.Y + translationFactor, obj.Z + translationFactor }; - + return new double[] { (obj.X + translationFactor).ValueToInclude(fullName.AppendPropertyName($"{nameof(obj.X)}"), comparisonConfig), From 70754ec239f5484638553ef09c1ebbf9f538a193 Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 1 Feb 2024 09:56:03 +0000 Subject: [PATCH 18/19] Update BHoM_Engine/Query/Hash.cs Co-authored-by: Pawel Baran --- BHoM_Engine/Query/Hash.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BHoM_Engine/Query/Hash.cs b/BHoM_Engine/Query/Hash.cs index 93b032cad..5488c86d8 100644 --- a/BHoM_Engine/Query/Hash.cs +++ b/BHoM_Engine/Query/Hash.cs @@ -236,7 +236,7 @@ private static string HashString(object obj, BaseComparisonConfig cc, int nestin if (cc.UseGeometryHash && typeof(IGeometry).IsAssignableFrom(type)) { - return GeometryHash((IGeometry)obj, cc, currentPropertyFullName).ToString(); + return GeometryHash((IGeometry)obj, cc, currentPropertyFullName); } // If the object is an IObject (= a BHoM class), let's look at its properties. From e360db11d702c8219f104b7448a521e131f849fe Mon Sep 17 00:00:00 2001 From: Alessio Lombardi Date: Thu, 1 Feb 2024 09:57:54 +0000 Subject: [PATCH 19/19] Applying @pawelbaran suggestion https://github.com/BHoM/BHoM_Engine/pull/3268#discussion_r1474055635 --- Geometry_Engine/Query/HashArray.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Geometry_Engine/Query/HashArray.cs b/Geometry_Engine/Query/HashArray.cs index 9e72e2a05..ada212e44 100644 --- a/Geometry_Engine/Query/HashArray.cs +++ b/Geometry_Engine/Query/HashArray.cs @@ -88,10 +88,10 @@ public static double[] IHashArray(this IGeometry igeometry, BaseComparisonConfig "The GeometryHashes are then calculated for the individual parts and concatenated.")] private static double[] HashArray(this ICurve curve, double translationFactor, BaseComparisonConfig comparisonConfig = null, string fullName = null) { - if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ICurve).IsAssignableFrom(t)) ?? false) + if (curve == null) return default; - if (curve == null) + if (comparisonConfig?.TypeExceptions?.Any(t => typeof(ICurve).IsAssignableFrom(t)) ?? false) return default; List subParts = curve.ISubParts().ToList();