diff --git a/Matter_Engine/Modify/AssignTemplate.cs b/Matter_Engine/Modify/AssignTemplate.cs new file mode 100644 index 000000000..274f695be --- /dev/null +++ b/Matter_Engine/Modify/AssignTemplate.cs @@ -0,0 +1,131 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2022, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Base; + +using BH.Engine.Base; +using BH.oM.Physical.Materials; + +namespace BH.Engine.Matter +{ + public static partial class Modify + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Maps a set of template materials to a set of model materials.\n" + + "First atempts to match the name of the provided materials to the material maps.\n" + + "If no name match is found, atempts to instead find a material with as many matching MaterialProperties (based on type and name) as possible.\n" + + "If a unique match is found based on one of the above matching methods, all Properties from the template material is applied to the model material matched.")] + [Input("modelMaterials", "The Materials to Modify, will be evaluated based on their name and properties.")] + [Input("templateMaterials", "The template materials to match to and assign properties from onto the model materials. Should generally have unique names. Names of material as well as material properties will be used to map to the materials to be modified.")] + [Input("prioritiseTemplate", "Controls if main material or map material should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, map is prioritised, if false, main material is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the materials and found matching material map based on namespace. If false, this check is instead done on exact type.")] + [Output("materials", "Materials with modified list of properties. Materials for which no unique match could be found are unaffected.")] + public static IEnumerable AssignTemplate(this IEnumerable modelMaterials, IEnumerable templateMaterials, bool prioritiseTemplate = true, bool uniquePerNamespace = true) + { + if (modelMaterials.IsNullOrEmpty()) + return null; + + if (templateMaterials == null || !templateMaterials.Any()) + { + Base.Compute.RecordWarning($"No {nameof(templateMaterials)} provied. Unmapped {nameof(Material)}s returned."); + return modelMaterials; + } + + List materialList = modelMaterials.ToList(); + List matchedTemplates = materialList.MatchMaterials(templateMaterials.ToList()); + + List results = new List(); + + for (int i = 0; i < materialList.Count; i++) + { + if (matchedTemplates[i] != null) + results.Add(materialList[i].CombineMaterials(matchedTemplates[i], prioritiseTemplate, uniquePerNamespace)); + else + results.Add(materialList[i]); + + } + + return results; + } + + /***************************************************/ + + [Description("Maps a set of materials in the MaterialCompositions to a set of provided transdiciplinary materials.\n" + + "First atempts to match the name of the provided materials to the transdiciplinary material maps.\n" + + "If no name match is found, atempts to instead find a material with as many matching MaterialProperties (based on type and name) as possible.\n" + + "If a unique match is found based on one of the above matching methods, all Properties from the transdiciplinary material is applied to the material to be matched.")] + [Input("materialComposition", "The MaterialCompositions to Modify. Materials int he MaterialComposition will be evaluated based on the name and properties.")] + [Input("templateMaterials", "The template materials to match to and assign properties from onto the model materials. Should generally have unique names. Names of material as well as material properties will be used to map to the materials to be modified.")] + [Input("prioritiseTemplate", "Controls if main material or map material should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, map is prioritised, if false, main material is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the materials and found matching material map based on namespace. If false, this check is instead done on exact type.")] + [Output("materialCompositions", "MaterialComposition with Materials with modified list of properties. Materials for which no unique match could be found are unaffected.")] + public static MaterialComposition AssignTemplate(this MaterialComposition materialComposition, IEnumerable templateMaterials, bool prioritiseTemplate = true, bool uniquePerNamespace = true) + { + if (materialComposition == null) + return null; + + if (templateMaterials == null || !templateMaterials.Any()) + { + Base.Compute.RecordWarning($"No {nameof(templateMaterials)} provied. Unmapped {nameof(MaterialComposition)}s returned."); + return materialComposition; + } + return new MaterialComposition(materialComposition.Materials.AssignTemplate(templateMaterials, prioritiseTemplate, uniquePerNamespace), materialComposition.Ratios); + } + + /***************************************************/ + + [Description("Maps a set of materials in the MaterialCompositions to a set of provided transdiciplinary materials.\n" + + "First atempts to match the name of the provided materials to the transdiciplinary material maps.\n" + + "If no name match is found, atempts to instead find a material with as many matching MaterialProperties (based on type and name) as possible.\n" + + "If a unique match is found based on one of the above matching methods, all Properties from the transdiciplinary material is applied to the material to be matched.")] + [Input("volumetricMaterialTakeoff", "The VolumetricMaterialTakeoff to Modify. Materials int he VolumetricMaterialTakeoff will be evaluated based on the name and properties.")] + [Input("templateMaterials", "The template materials to match to and assign properties from onto the model materials. Should generally have unique names. Names of material as well as material properties will be used to map to the materials to be modified.")] + [Input("prioritiseTemplate", "Controls if main material or map material should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, map is prioritised, if false, main material is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the materials and found matching material map based on namespace. If false, this check is instead done on exact type.")] + [Output("volumetricMaterialTakeoffs", "MaterialComposition with Materials with modified list of properties. Materials for which no unique match could be found are unaffected.")] + public static VolumetricMaterialTakeoff AssignTemplate(this VolumetricMaterialTakeoff volumetricMaterialTakeoff, IEnumerable templateMaterials, bool prioritiseTemplate = true, bool uniquePerNamespace = true) + { + if (volumetricMaterialTakeoff == null) + return null; + + if (templateMaterials == null || !templateMaterials.Any()) + { + Base.Compute.RecordWarning($"No {nameof(templateMaterials)} provied. Unmapped {nameof(VolumetricMaterialTakeoff)}s returned."); + return volumetricMaterialTakeoff; + } + + return new VolumetricMaterialTakeoff(volumetricMaterialTakeoff.Materials.AssignTemplate(templateMaterials, prioritiseTemplate, uniquePerNamespace), volumetricMaterialTakeoff.Volumes); + } + + /***************************************************/ + } +} + + diff --git a/Matter_Engine/Modify/CombineMaterials.cs b/Matter_Engine/Modify/CombineMaterials.cs new file mode 100644 index 000000000..4f2bf129d --- /dev/null +++ b/Matter_Engine/Modify/CombineMaterials.cs @@ -0,0 +1,120 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2022, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.oM.Base; +using BH.oM.Base.Attributes; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +using BH.Engine.Base; +using BH.oM.Physical.Materials; + +namespace BH.Engine.Matter +{ + public static partial class Modify + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Merges the Properties of the target and source by adding all properties on the source to the target. Checks for duplicate type/namespaces and resolves any duplicates found depending on settings provided.")] + [Input("target", "The material to merge the properties of the source onto. The Returned material will take name and other properties from the target.")] + [Input("source", "The source Material to grab proeprties from.")] + [Input("prioritiseSource", "Controls if target or source should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, source is prioritised, if false, target is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the target and source based on namespace. If false, this check is instead done on exact type.")] + [Output("material", "Target material with Properties from the Source merged onto it.")] + public static Material CombineMaterials(this Material target, Material source, bool prioritiseSource, bool uniquePerNamespace) + { + if (target == null) + { + Base.Compute.RecordError($"{nameof(target)} {nameof(Material)} is null. Unable to merge properties of source onto target. Null returned."); + return null; + } + + if (source == null) + { + Base.Compute.RecordWarning($"{nameof(source)} {nameof(Material)} is null. Unmodified target material returned."); + return target; + } + + Material targetClone = target.ShallowClone(); //Clone the target to be returned + targetClone.Properties = new List(target.Properties); //Clone the target list + + if (double.IsNaN(targetClone.Density)) //If density on target is NaN, use density from source no matter the setting + targetClone.Density = source.Density; + else if (prioritiseSource && !double.IsNaN(source.Density)) //Density of target is not NaN, use source density if it is not NaN and if setting to prioritise source is true + targetClone.Density = source.Density; + + if (!uniquePerNamespace) //If not unique by namespace, check properties by type + { + List targetTypes = target.Properties.Select(x => x.GetType()).ToList(); //Get out existing types on the target + foreach (IMaterialProperties property in source.Properties) + { + Type propType = property.GetType(); //Type of the property on the source to be added + if (prioritiseSource) //If true, source object should take priority if a duplicate is found + { + if (targetTypes.Contains(propType)) //If type exists + { + targetClone.Properties.RemoveAll(x => x.GetType() == propType); //Remove all instances of the type + } + targetClone.Properties.Add(property); //Add property from source + } + else //else (prioritiseMap is false) -> only add property if a property of that type does not already exist + { + if (!targetTypes.Contains(propType)) //If property not already existing + { + targetClone.Properties.Add(property); //Add + } + } + } + } + else //If unique by namespace + { + HashSet nameSpaces = new HashSet(target.Properties.Select(x => x.GetType().Namespace)); //Get out namespaces of all Proeprties on the target + foreach (IMaterialProperties property in source.Properties) + { + string propertyNamespace = property.GetType().Namespace; //Namespace of Property to be added + if (prioritiseSource) //Prioritise source + { + if (nameSpaces.Contains(propertyNamespace)) //Target contains items from the namespace + { + targetClone.Properties.RemoveAll(x => x.GetType().Namespace == propertyNamespace); //Remove all instances in the namespace + } + targetClone.Properties.Add(property); //Add the property + } + else //Prioritise target + { + if (!nameSpaces.Contains(propertyNamespace)) //Target does not contain a property in the namespace evaluated + { + targetClone.Properties.Add(property); //Add + } + } + } + } + return targetClone; + } + + /***************************************************/ + } +} diff --git a/Matter_Engine/Modify/MapMaterial.cs b/Matter_Engine/Modify/MapMaterial.cs index bf63e474e..99f5d1a73 100644 --- a/Matter_Engine/Modify/MapMaterial.cs +++ b/Matter_Engine/Modify/MapMaterial.cs @@ -38,14 +38,14 @@ public static partial class Modify /**** Public Methods ****/ /***************************************************/ - [Description("Adds a IMaterialFragment to a material based on the mapping defined by the keys and materialFragments. \n" + + [Description("Adds a IMaterialFragment to a material based on the mapping defined by the keys and materialFragments. \n" + "i.e. The materialFragment on index 3 will be added to the Material with the same name as the key at index 3.")] [Input("materials", "The Materials to Modify, will be evaluated based on their name.")] - [Input("keys", "The key is the name of the Material to be affected. The keys index in this list relates to the index of a materialFragment to add in the other list. \n" + + [Input("keys", "The key is the name of the Material to be affected. The keys index in this list relates to the index of a materialFragment to add in the other list. \n" + "Empty keys means that its related materialFragment will be disgarded.")] [Input("materialFragments", "The materialFragments to add to the Materials, the order of which relates to the keys.")] [Output("materials", "Materials with modified list of properties. Materials whos names did not appear among the keys are unaffected.")] - public static IEnumerable MapMaterial(IEnumerable materials, List keys, List materialFragments) + public static IEnumerable MapMaterial(this IEnumerable materials, List keys, List materialFragments) { if (keys.Count > materialFragments.Count) { @@ -74,21 +74,21 @@ public static IEnumerable MapMaterial(IEnumerable materials, Engine.Base.Compute.RecordError("Non-empty keys must be distinct."); return null; } - + // Add the materialFragment to the material and return - return materials.Select( (x) => - { - for (int i = 0; i < culledKeys.Count; i++) - { - if (x.Name == culledKeys[i]) - { - Material mat = x.DeepClone(); - mat.Properties.Add(culledMaterialFragments[i]); - return mat; - } - } - return x; - }).ToList(); + return materials.Select((x) => + { + for (int i = 0; i < culledKeys.Count; i++) + { + if (x.Name == culledKeys[i]) + { + Material mat = x.DeepClone(); + mat.Properties.Add(culledMaterialFragments[i]); + return mat; + } + } + return x; + }).ToList(); } /***************************************************/ diff --git a/Matter_Engine/Query/MappedMaterialComposition.cs b/Matter_Engine/Query/MappedMaterialComposition.cs new file mode 100644 index 000000000..22a2cb745 --- /dev/null +++ b/Matter_Engine/Query/MappedMaterialComposition.cs @@ -0,0 +1,61 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2022, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Base; +using BH.oM.Dimensional; +using BH.Engine.Base; +using BH.oM.Physical.Materials; + +namespace BH.Engine.Matter +{ + public static partial class Query + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Maps a set of materials in the MaterialCompositions of the provided elements to a set of provided transdiciplinary materials.\n" + + "First atempts to match the name of the provided materials to the transdiciplinary material maps.\n" + + "If no name match is found, atempts to instead find a material with as many matching MaterialProperties (based on type and name) as possible.\n" + + "If a unique match is found based on one of the above matching methods, all Properties from the transdiciplinary material is applied to the material to be matched.")] + [Input("element", "The elements to fetch MaterialComposition from.")] + [Input("templateMaterials", "The template materials to match to and assign properties from onto the model materials. Should generally have unique names. Names of material as well as material properties will be used to map to the materials to be modified.")] + [Input("checkForTakeoffFragment", "If true and the provided element is a BHoMObject, the incoming item is checked if it has a VolumetricMaterialTakeoff fragment attached, and if so, returns that Material composition corresponding to this fragment. If false, the MaterialComposition returned will be calculated, independant of fragment attached.")] + [Input("prioritiseTemplate", "Controls if main material or map material should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, map is prioritised, if false, main material is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the materials of the element and found matching material map based on namespace. If false, this check is instead done on exact type.")] + [Output("materialComposition", "The material compositions for each element with materials mapped to the provided transdiciplinary materials.")] + public static MaterialComposition MappedMaterialComposition(this IElementM element, IEnumerable templateMaterials, bool checkForTakeoffFragment = true, bool prioritiseTemplate = true, bool uniquePerNamespace = true) + { + if (element == null) + return null; + + return element.IMaterialComposition(checkForTakeoffFragment).AssignTemplate(templateMaterials, prioritiseTemplate, uniquePerNamespace); + } + + /***************************************************/ + } +} diff --git a/Matter_Engine/Query/MappedVolumetricMaterialTakeoff.cs b/Matter_Engine/Query/MappedVolumetricMaterialTakeoff.cs new file mode 100644 index 000000000..61d12d6f1 --- /dev/null +++ b/Matter_Engine/Query/MappedVolumetricMaterialTakeoff.cs @@ -0,0 +1,61 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2022, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using System; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel; +using BH.oM.Base.Attributes; +using BH.oM.Base; +using BH.oM.Dimensional; +using BH.Engine.Base; +using BH.oM.Physical.Materials; + +namespace BH.Engine.Matter +{ + public static partial class Query + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + [Description("Maps a set of materials in the VolumetricMaterialTakeoff of the provided elements to a set of provided transdiciplinary materials.\n" + + "First atempts to match the name of the provided materials to the transdiciplinary material maps.\n" + + "If no name match is found, atempts to instead find a material with as many matching MaterialProperties (based on type and name) as possible.\n" + + "If a unique match is found based on one of the above matching methods, all Properties from the transdiciplinary material is applied to the material to be matched.")] + [Input("element", "The elements to fetch VolumetricMaterialTakeoff from.")] + [Input("templateMaterials", "The template materials to match to and assign properties from onto the model materials. Should generally have unique names. Names of material as well as material properties will be used to map to the materials to be modified.")] + [Input("checkForTakeoffFragment", "If true and the provided element is a BHoMObject, the incoming item is checked if it has a VolumetricMaterialTakeoff fragment attached, and if so, returns that Material composition corresponding to this fragment. If false, the MaterialComposition returned will be calculated, independant of fragment attached.")] + [Input("prioritiseMap", "Controls if main material or map material should be prioritised when conflicting information is found on both in terms of Density and/or Properties. If true, map is prioritised, if false, main material is prioritised.")] + [Input("uniquePerNamespace", "If true, the method is checking for similarity of MaterialProperties on the materials of the element and found matching material map based on namespace. If false, this check is instead done on exact type.")] + [Output("volumetricMaterialTakeoff", "The VolumetricMaterialTakeoff for each element with materials mapped to the provided transdiciplinary materials.")] + public static VolumetricMaterialTakeoff MappedVolumetricMaterialTakeoff(this IElementM element, IEnumerable templateMaterials, bool checkForTakeoffFragment = true, bool prioritiseMap = true, bool uniquePerNamespace = true) + { + if (element == null) + return null; + + return element.IVolumetricMaterialTakeoff(checkForTakeoffFragment).AssignTemplate(templateMaterials, prioritiseMap, uniquePerNamespace); + } + + /***************************************************/ + } +} diff --git a/Matter_Engine/Query/MatchMaterials.cs b/Matter_Engine/Query/MatchMaterials.cs new file mode 100644 index 000000000..d71227673 --- /dev/null +++ b/Matter_Engine/Query/MatchMaterials.cs @@ -0,0 +1,149 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2022, the respective contributors. All rights reserved. + * + * Each contributor holds copyright over their respective contributions. + * The project versioning (Git) records all such contribution source information. + * + * + * The BHoM is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, or + * (at your option) any later version. + * + * The BHoM is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this code. If not, see . + */ + +using BH.Engine.Base; +using BH.oM.Base; +using BH.oM.Base.Attributes; +using BH.oM.Physical.Materials; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; + +namespace BH.Engine.Matter +{ + public static partial class Query + { + /***************************************************/ + /**** Public Methods ****/ + /***************************************************/ + + + [Description("Maps a set of model materials to a set of template materials by name and returns the found tempaltes, i.e. finds a material in the list of template materials that has the same name as the material in the model material and returns it. Returns null if nothing is found. The list order of the returned materials correspond to the list order of the input materials.")] + [Input("modelMaterials", "The materials to find a matching template to.")] + [Input("templateMaterials", "The template materials to scan.")] + [Output("matchedMaterials", "The a list of template materials matched to the model materials by name. Order corresponds to the model materials. Method returns null for cases where no match is found.")] + public static List MatchMaterials(this List modelMaterials, List templateMaterials) + { + if (modelMaterials.IsNullOrEmpty()) + return null; + + if (templateMaterials == null || !templateMaterials.Any()) + { + Base.Compute.RecordWarning($"No {nameof(templateMaterials)} provied. List of nulls returned."); + return Enumerable.Repeat(null, modelMaterials.Count).ToList(); + } + + ILookup nameLookup = templateMaterials.ToLookup(x => x.Name); + + Dictionary, List> propertyMaps = new Dictionary, List>(); + + foreach (Material mat in templateMaterials) + { + foreach (IMaterialProperties property in mat.Properties) + { + Tuple key = new Tuple(property.GetType(), property.Name); + if (propertyMaps.ContainsKey(key)) + propertyMaps[key].Add(mat); + else + propertyMaps[key] = new List() { mat }; + } + } + return modelMaterials.Select(x => x.MatchMaterial(nameLookup, propertyMaps)).ToList(); + } + + + /***************************************************/ + /**** Private Methods ****/ + /***************************************************/ + + [Description("Method preforming the mapping work. Matches the Material first by name, secondly by name of properties using the provided lookup and dictionary.")] + private static Material MatchMaterial(this Material material, ILookup nameLookup, Dictionary, List> propertyMaps) + { + List matches = nameLookup[material.Name].ToList(); //Try match by name + + if (matches.Count == 0) //If no name match found, try match by material proeprties - Type and name + { + foreach (IMaterialProperties property in material.Properties) + { + Tuple key = new Tuple(property.GetType(), property.Name); + + List propMatches; + if (propertyMaps.TryGetValue(key, out propMatches)) + matches.AddRange(propMatches); + } + + matches = matches.GroupBy(x => x.Name).Select(x => x.First()).ToList(); + matches = matches.GroupBy(x => x.MatchScore(material)) //Get and group by score + .Where(x => x.Key > 0) //Only keep materials with score > 0, as negative score indicates at least one mismatch + .OrderByDescending(x => x.Key) //Order by score - Higher score first + .FirstOrDefault()?.ToList() ?? new List(); + } + + if (matches.Count == 1) //Exactly one best match. Success! + { + return matches[0]; + } + else if (matches.Count == 0) //No matches, record warning + { + Base.Compute.RecordWarning($"No map found for material named {material.Name}"); + return null; + } + else //More than one match. Record warning + { + Base.Compute.RecordWarning($"Material named {material.Name} has ambiguous matches to {string.Join(", ", matches.Select(x => x.Name))}. No mapping preformed.\nEnsure that the transdiciplinaryMaterialMaps have unique names and that your Material has a name matching the transdiciplinary material you want to match."); + return null; + } + } + + /***************************************************/ + + + [Description("Matches two materials based on how well the properties align based on name. For each aligning property the score is increased by 1. If a single misaligned property is found a score of -1 is returned.")] + private static int MatchScore(this Material mat1, Material mat2) + { + //Method matches two materials based on their properties. + //For each proeprty of the same type and same name, the score is increased by 1. + //If a property of the same type but a different name is found the score is set to -1, as that means a mismatch of properties. + int score = 0; + ILookup matLookup = mat2.Properties.ToLookup(x => x.GetType()); + foreach (IMaterialProperties prop in mat1.Properties) + { + string name = prop.Name; + Type type = prop.GetType(); + + IEnumerable prop2 = matLookup[type]; + if (prop2.Any()) + { + if (prop2.Any(x => x.Name == name)) + score++; + else + return -1; + } + + } + return score; + } + + /***************************************************/ + } +}