diff --git a/Etabs_Adapter/CRUD/Read/_Read.cs b/Etabs_Adapter/CRUD/Read/_Read.cs index 1f706111..769049f0 100644 --- a/Etabs_Adapter/CRUD/Read/_Read.cs +++ b/Etabs_Adapter/CRUD/Read/_Read.cs @@ -26,6 +26,7 @@ using System.Collections; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Text; using System.Threading.Tasks; using BH.oM.Base; @@ -39,6 +40,8 @@ using BH.oM.Adapter; using System.ComponentModel; using BH.oM.Data.Requests; +using BH.oM.Spatial.SettingOut; +using BH.Adapter.ETABS.Types; namespace BH.Adapter.ETABS { @@ -99,6 +102,120 @@ protected override IEnumerable IRead(Type type, IList ids, ActionCo return new List();//<--- returning null will throw error in replace method of BHOM_Adapter line 34: can't do typeof(null) - returning null does seem the most sensible to return though } + /***************************************************/ + + public IEnumerable Read (T request, ActionConfig actionConfig = null) where T : ILogicalRequest //- **GENERIC TYPES** + { + + // Use of GENERIC TYPES, HASH TABLES, STREAMS and RECURSION + + /* Use a HashSet data structure to make sure no collected elements are duplicate and to make access/search as fast as possible - **HASH TABLES ** */ + HashSet < IBHoMObject > bhomObjects= new HashSet(); + + /* 1. Handle the LogicalANDRequest */ + if (request is LogicalAndRequest) + { + // 1.1 Initialize List of Requests to be extracted from LogicalRequest + List requests = (request as LogicalAndRequest).Requests; + // 1.2 Initialize DynamicComparer class instance allowing to check equality between IBHoMObject class instances + DynamicComparer iBHoMETABSComparer = new DynamicComparer(); + + // 1.3 Add to bhomObjects List all objects abiding by the FIRST request in the list... - **STREAMS** + IRequest req = requests[0]; + // ...when it's a FilterRequest... + if (req.GetType() == typeof(FilterRequest)) Read((FilterRequest)req, actionConfig).ToList().ForEach(bhomObj => bhomObjects.Add(bhomObj)); + // ...when it's a SelectionRequest... + if (req.GetType() == typeof(SelectionRequest)) Read((SelectionRequest)req, actionConfig).ToList().ForEach(bhomObj => bhomObjects.Add(bhomObj)); + // ...when it's a LogicalRequest...call the method recursively! - **RECURSION** + if (req is ILogicalRequest) bhomObjects=Read((ILogicalRequest)req, actionConfig).ToHashSet(); + + + // 1.4 Add to bhomObjects List all objects abiding by ALL THE OTHER requests in the list... - **STREAMS** + for (int i = 1; i < requests.Count; i++) + { + // ...when they are FilterRequests... + if (requests[i].GetType() == typeof(FilterRequest)) bhomObjects = (bhomObjects.ToList().Intersect(Read((FilterRequest)requests[i], actionConfig).ToList(), iBHoMETABSComparer)).ToHashSet(); + // ...when they are SelectionRequests... + if (requests[i].GetType() == typeof(SelectionRequest)) bhomObjects = (bhomObjects.ToList().Intersect(Read((SelectionRequest)requests[i], actionConfig).ToList(), iBHoMETABSComparer)).ToHashSet(); + // ...when they are LogicalRequests...call the method recursively! - **RECURSION** + if (requests[i] is ILogicalRequest) bhomObjects = (bhomObjects.ToList().Intersect(Read((ILogicalRequest)requests[i], actionConfig))).ToHashSet(); + } + + // 1.5 Return list of bhomObjects + return bhomObjects; + } + + /* 2. Handle the LogicalORRequest */ + else if (request is LogicalOrRequest) + { + // 2.1 Initialize List of Requests to be extracted from LogicalRequest + List requests = (request as LogicalOrRequest).Requests; + + // 2.2 Add to bhomObjects List all objects abiding by ALL requests in the list... - **STREAMS** + // ...when they are FilterRequests... + requests.ForEach(req => { if (req.GetType() == typeof(FilterRequest)) Read((FilterRequest)req, actionConfig).ToList().ForEach(bhomObj => bhomObjects.Add(bhomObj)); + // ...when they are SelectionRequests... + if (req.GetType() == typeof(SelectionRequest)) Read((SelectionRequest)req, actionConfig).ToList().ForEach(bhomObj => bhomObjects.Add(bhomObj)); + // ...when they are LogicalRequests...call the method recursively! - **RECURSION** + if (req is ILogicalRequest) bhomObjects=Read((ILogicalRequest)req, actionConfig).ToHashSet(); }); + + // 2.3 Return list of bhomObjects + return bhomObjects; + } + + /* 3. Handle the LogicalNOTRequest */ + else if (request is LogicalNotRequest) + { + // 3.1 Initialize Lists and Hashsets for collecting all bhomObjects - **HASH TABLES ** + IRequest iRequest = (request as LogicalNotRequest).Request; + HashSet notBhomObjects = new HashSet(); + List allBhomObjects = new List(); + + // 3.2 Add to NOTbhomObjects HashSet all unique objects abiding by the Request input in the LogicalNOTRequest... - **STREAMS** + // ...when it's a FilterRequest... + if (iRequest.GetType() == typeof(FilterRequest)) Read((FilterRequest)iRequest, actionConfig).ToList().ForEach(bhomObj => notBhomObjects.Add(bhomObj)); + // ...when it's a SelectionRequest... + if (iRequest.GetType() == typeof(SelectionRequest)) Read((SelectionRequest)iRequest, actionConfig).ToList().ForEach(bhomObj => notBhomObjects.Add(bhomObj)); + // ...when it's a LogicalRequest...call the method recursively! - **RECURSION** + if (iRequest is ILogicalRequest) Read((ILogicalRequest)iRequest, actionConfig).ToList().ForEach(bhomObj => notBhomObjects.Add(bhomObj)); + + + // 3.3 Get all bhomObjects of ANY kind from ETABS Model... - **STREAMS** + Type[] bhomTypes = {typeof(Node),typeof(Bar),typeof(ISectionProperty), typeof(IMaterialFragment), typeof(Panel), + typeof(ISurfaceProperty), typeof(LoadCombination), typeof(Loadcase), typeof(ILoad), typeof(RigidLink), + typeof(LinkConstraint),typeof(oM.Spatial.SettingOut.Level),typeof(oM.Spatial.SettingOut.Grid),typeof(FEMesh)}; + + allBhomObjects = bhomTypes.ToList() + .Select(bhomType =>{ FilterRequest fr = new FilterRequest(); + fr.Type = bhomType; + return fr;}) + .Select(filtReq => Read(filtReq)) + .SelectMany(x=>x) // Streams function allowing to flatten multidimensional lists! + .ToList(); + + // 3.4 Return the difference between ALL Objects and the ones NOT to be taken - **STREAMS** + // - .Except() Streams function returns the difference between two lists/data structures based on a specified EqualityComparer class) + return allBhomObjects.Except(notBhomObjects,new DynamicComparer()); + } + + BH.Engine.Base.Compute.RecordError($"Read for {request.GetType().Name} is not implemented in {(this as dynamic).GetType().Name}."); + return new List(); + } + + + public IEnumerable Read(FilterRequest filterRequest, ActionConfig actionConfig = null) + { + // Extract the Ids from the FilterRequest + IList objectIds = null; + object idObject; + if (filterRequest.Equalities.TryGetValue("ObjectIds", out idObject) && idObject is IList) + objectIds = idObject as IList; + + return IRead(filterRequest.Type, objectIds, actionConfig); + } + + + /***************************************************/ public IEnumerable Read(SelectionRequest request, ActionConfig actionConfig = null) @@ -120,7 +237,7 @@ public Dictionary> SelectedElements() int numItems = 0; int[] objectTypes = new int[0]; string[] objectIds = new string[0]; - + m_model.SelectObj.GetSelected(ref numItems, ref objectTypes, ref objectIds); // Replace Panels' type numbers with Openings' type numbers @@ -186,8 +303,17 @@ private static List FilterIds(IEnumerable ids, IEnumerable GetClassesInNamespace(Assembly assembly, string nameSpace) + { + return assembly.GetTypes() + .Where(t => t.IsClass && t.Namespace == nameSpace) + .ToList(); + } + + } } diff --git a/Etabs_Adapter/Etabs_Adapter.csproj b/Etabs_Adapter/Etabs_Adapter.csproj index ce6419c3..383486bb 100644 --- a/Etabs_Adapter/Etabs_Adapter.csproj +++ b/Etabs_Adapter/Etabs_Adapter.csproj @@ -381,6 +381,7 @@ + diff --git a/Etabs_Adapter/Types/DynamicComparer.cs b/Etabs_Adapter/Types/DynamicComparer.cs new file mode 100644 index 00000000..cf348d6d --- /dev/null +++ b/Etabs_Adapter/Types/DynamicComparer.cs @@ -0,0 +1,103 @@ +/* + * This file is part of the Buildings and Habitats object Model (BHoM) + * Copyright (c) 2015 - 2021, 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.Adapters.ETABS; +using BH.oM.Base; +using BH.oM.Spatial.SettingOut; +using BH.oM.Structure.Constraints; +using BH.oM.Structure.Elements; +using BH.oM.Structure.Loads; +using BH.oM.Structure.MaterialFragments; +using BH.oM.Structure.SectionProperties; +using BH.oM.Structure.SurfaceProperties; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace BH.Adapter.ETABS.Types +{ + public class DynamicComparer : IEqualityComparer + { + + // Use of STREAMS and REFLECTIONS + + // 1. Equality based on ETABS obj Id or Name + public bool Equals(IBHoMObject obj1, IBHoMObject obj2) + { + if (obj1 == null || obj2 == null) return false; + if (obj1.GetType() != obj2.GetType()) return false; + + + Type objType = obj1.GetType(); + + // For physical objects, use Id as it is never null + if (objType == typeof(Node) || objType == typeof(Bar) || objType == typeof(Panel) || objType == typeof(RigidLink) || objType == typeof(FEMesh)) + return GetEtabsId(obj1).Id.ToString() == GetEtabsId(obj2).Id.ToString(); + // For all other items, use Name as it is never null + if (objType == typeof(ISectionProperty) || objType == typeof(IMaterialFragment) || objType == typeof(ISurfaceProperty) || + objType == typeof(Loadcase) || objType == typeof(LoadCombination) || objType == typeof(ILoad) || objType == typeof(LinkConstraint) || + objType == typeof(Level) || objType == typeof(Grid)) + return obj1.Name.ToString() == obj2.Name.ToString(); + + return false; + + } + + // 2. HashCode based on Hash function of ETABS obj Id+Label or Type+Name + public int GetHashCode(IBHoMObject obj) + { + Type objType = obj.GetType(); + + // For physical objects, use Id and Label as they are never null + if (objType == typeof(Node) || objType == typeof(Bar) || objType == typeof(Panel) || objType == typeof(RigidLink) || objType == typeof(FEMesh)) + return (GetEtabsId(obj).Id.ToString() + GetEtabsId(obj).Label.ToString()).GetHashCode(); + // For all other items, use Name as Id/Label can be null + else if (objType == typeof(ISectionProperty) || objType == typeof(IMaterialFragment) || objType == typeof(ISurfaceProperty) || + objType == typeof(Loadcase) || objType == typeof(LoadCombination) || objType == typeof(ILoad) || objType == typeof(LinkConstraint) || + objType == typeof(Level) || objType == typeof(Grid)) + return (obj.GetType().ToString() + obj.Name.ToString()).GetHashCode(); + + return 0; + + } + + // 3. Get ETABS Id using REFLECTION - **REFLECTION** + private ETABSId GetEtabsId(IBHoMObject obj) + { + // 1. Get the object Type + Type objsType = obj.GetType(); + // 2. Get the Property Fragments - via REFLECTION + PropertyInfo fragmentsProperty = objsType.GetProperty("Fragments"); + // 3. Downcast the Fragments Property to FragmentSet Class - via REFLECTION + FragmentSet fragments = (FragmentSet)fragmentsProperty.GetValue(obj); + // 4. Get the ETABSId object contained in the FragmentSet - via STREAMS + ETABSId etabsId = (ETABSId)(fragments.ToList().Find(frag => frag.GetType() == typeof(ETABSId))); + // 5. Return the Etabs Id of the object + return etabsId; + } + + + } +}