public static AdjacencyCluster AdjacencyCluster(IEnumerable <Space> spaces, IEnumerable <Panel> panels, out List <Topology> topologies, out List <Panel> redundantPanels, double minArea = Tolerance.MacroDistance, bool updatePanels = true, bool tryCellComplexByCells = true, Log log = null, double silverSpacing = Tolerance.MacroDistance, double tolerance = Tolerance.Distance)
        {
            Core.Modify.Add(log, "Method Name: {0}, Tolerance: {1}, Update Panels: {2}", "SAM.Analytical.Topologic.Create.AdjacencyCluster", tolerance, updatePanels);

            topologies      = null;
            redundantPanels = null;

            AdjacencyCluster result = new AdjacencyCluster();

            result.AddObjects(spaces);
            result.AddObjects(panels);

            List <Face> faces = new List <Face>();

            int index = 1;

            foreach (Panel panel in result.GetObjects <Panel>())
            {
                if (panel == null)
                {
                    continue;
                }

                Face face = Convert.ToTopologic(panel);
                if (face == null)
                {
                    continue;
                }

                faces.Add(face);
                Core.Modify.Add(log, "Face {0:D4} added. Panel [{1}]", index, panel.Guid);
                index++;
            }

            if (faces == null || faces.Count == 0)
            {
                return(null);
            }

            topologies = new List <Topology>();
            List <Cell> cells = null;

            if (tryCellComplexByCells)
            {
                try
                {
                    Core.Modify.Add(log, "Trying to make CellComplex By Cells");
                    Cluster cluster = Cluster.ByTopologies(faces as IList <Topology>);
                    Core.Modify.Add(log, "Cluster.ByTopologies Done");
                    Topology topology = cluster.SelfMerge();
                    Core.Modify.Add(log, "Cluster SelfMerge Done");
                    if (topology.Cells != null && topology.Cells.Count != 0)
                    {
                        cells = topology.Cells?.ToList();
                        CellComplex cellComplex = null;
                        try
                        {
                            cellComplex = CellComplex.ByCells(cells);
                        }
                        catch (Exception exception)
                        {
                            Core.Modify.Add(log, "Cells could not be taken from CellComplex");
                            Core.Modify.Add(log, "Exception Message: {0}", exception.Message);
                        }

                        if (cellComplex != null && cellComplex.Cells != null && cellComplex.Cells.Count != 0)
                        {
                            topologies.Add(cellComplex);

                            cells = cellComplex.Cells?.ToList();
                        }
                        else
                        {
                            topologies.Add(topology);
                            Core.Modify.Add(log, "Cells taken from Cluster");
                        }
                    }
                }
                catch (Exception exception)
                {
                    Core.Modify.Add(log, "Cannot create CellComplex By Cells or Cells form Cluster SelfMerge");
                    Core.Modify.Add(log, "Exception Message: {0}", exception.Message);
                    cells = null;
                }
            }

            if (cells == null)
            {
                try
                {
                    Core.Modify.Add(log, "Trying to make CellComplex By Faces");
                    CellComplex cellComplex = CellComplex.ByFaces(faces, tolerance);
                    topologies.Add(cellComplex);
                    cells = cellComplex.Cells?.ToList();
                    Core.Modify.Add(log, "CellComplex By Faces Created");
                }
                catch (Exception exception)
                {
                    Core.Modify.Add(log, "Cannot create CellComplex By Faces");
                    Core.Modify.Add(log, "Exception Message: {0}", exception.Message);

                    cells = null;
                }
            }

            if (cells == null || cells.Count == 0)
            {
                Core.Modify.Add(log, "No cells found");
                return(null);
            }

            List <Geometry.Spatial.Shell> shells = cells.ToSAM();

            Core.Modify.Add(log, "Single CellComplex converted to shells");

            if (shells == null)
            {
                return(null);
            }

            //Matching spaces with shells
            Dictionary <Geometry.Spatial.Shell, List <Space> > dictionary_Spaces = new Dictionary <Geometry.Spatial.Shell, List <Space> >();

            if (spaces != null)
            {
                foreach (Space space in spaces)
                {
                    if (space == null || space.Location == null || !space.IsPlaced())
                    {
                        continue;
                    }

                    List <Geometry.Spatial.Shell> shells_Temp = Analytical.Query.SpaceShells(shells, space.Location, silverSpacing, tolerance);
                    if (shells_Temp == null || shells_Temp.Count == 0)
                    {
                        continue;
                    }

                    foreach (Geometry.Spatial.Shell shell in shells_Temp)
                    {
                        if (!dictionary_Spaces.TryGetValue(shell, out List <Space> spaces_Shell))
                        {
                            spaces_Shell             = new List <Space>();
                            dictionary_Spaces[shell] = spaces_Shell;
                        }
                        spaces_Shell.Add(space);
                    }
                }
            }

            HashSet <Guid> guids_Updated = new HashSet <Guid>();

            Dictionary <Panel, Face3D> dictionary_Panel_Face3D = new Dictionary <Panel, Face3D>();

            result.GetObjects <Panel>().ForEach(x => dictionary_Panel_Face3D[x] = x.GetFace3D());

            index = 1;
            List <Tuple <Panel, Point3D> > tuples_InternalPoint3D = new List <Tuple <Panel, Point3D> >();

            for (int i = 0; i < shells.Count; i++)
            {
                Geometry.Spatial.Shell shell = shells[i];
                if (shell == null)
                {
                    return(null);
                }

                Core.Modify.Add(log, "Simplifying shell");
                //shell.Simplify(tolerance); // Low tolerance cause of rounding issues
                shell.Simplify();

                Core.Modify.Add(log, "Extracting faces from shell");
                List <Face3D> face3Ds = shell?.Face3Ds;
                if (face3Ds == null)
                {
                    Core.Modify.Add(log, "No face2Ds found in Shell");
                    continue;
                }

                dictionary_Spaces.TryGetValue(shell, out List <Space> spaces_Shell);

                if (spaces_Shell == null || spaces_Shell.Count == 0)
                {
                    Core.Modify.Add(log, "Creating new Space");

                    Point3D location = shell.CalculatedInternalPoint3D(silverSpacing, tolerance);
                    if (location == null)
                    {
                        continue;
                    }

                    Space space = new Space("Cell " + index, location);
                    index++;

                    if (!result.AddObject(space))
                    {
                        continue;
                    }

                    spaces_Shell = new List <Space>()
                    {
                        space
                    };
                    dictionary_Spaces[shell] = spaces_Shell;
                }

                if (spaces_Shell == null || spaces_Shell.Count == 0)
                {
                    continue;
                }

                double volume = double.NaN;
                if (cells[i] != null)
                {
                    Core.Modify.Add(log, "Calculating Volume");
                    volume = CellUtility.Volume(cells[i]);

                    foreach (Space space_Shell in spaces_Shell)
                    {
                        space_Shell.SetValue(SpaceParameter.Volume, volume);
                    }
                }

                Core.Modify.Add(log, "Upadting Panels");
                foreach (Face3D face3D in face3Ds)
                {
                    if (minArea != 0 && face3D.GetArea() <= minArea)
                    {
                        Core.Modify.Add(log, "Face3D is too small");
                        continue;
                    }

                    Core.Modify.Add(log, "Looking for existing Panel");
                    Tuple <Panel, Point3D> tuple_InternalPoint3D = tuples_InternalPoint3D.Find(x => face3D.Inside(x.Item2, tolerance));
                    if (tuple_InternalPoint3D != null)
                    {
                        Core.Modify.Add(log, "Existing Panel found: {0}", tuple_InternalPoint3D.Item1.Guid);
                        foreach (Space space in spaces_Shell)
                        {
                            if (result.AddRelation(space, tuple_InternalPoint3D.Item1))
                            {
                                Core.Modify.Add(log, "Space [{0}] and Panel [{1}] relation added", space.Guid, tuple_InternalPoint3D.Item1.Guid);
                            }
                        }
                        continue;
                    }

                    Core.Modify.Add(log, "Looking for old Panel");
                    //Panel panel_Old = Query.SimilarPanel(face3D, dictionary_Panel_Face3D);
                    //if (panel_Old == null)
                    //    continue;

                    List <Panel> panels_Old = Query.SimilarPanels(face3D, dictionary_Panel_Face3D);
                    if (panels_Old == null || panels_Old.Count == 0)
                    {
                        continue;
                    }

                    Panel panel_Old = panels_Old.First();
                    if (panels_Old.Count > 1)
                    {
                        if (redundantPanels == null)
                        {
                            redundantPanels = new List <Panel>();
                        }

                        panels_Old.RemoveAt(0);
                        redundantPanels.AddRange(panels_Old);
                    }

                    Core.Modify.Add(log, "Old Panel found: {0}", panel_Old.Guid);

                    Panel panel_New = null;

                    if (updatePanels)
                    {
                        if (guids_Updated.Contains(panel_Old.Guid))
                        {
                            panel_New = Analytical.Create.Panel(Guid.NewGuid(), panel_Old, face3D);
                            Core.Modify.Add(log, "Creating new Panel for Old Panel [{0}]. New Panel [{1}]", panel_Old.Guid, panel_New.Guid);
                        }
                        else
                        {
                            panel_New = Analytical.Create.Panel(panel_Old.Guid, panel_Old, face3D);
                            guids_Updated.Add(panel_Old.Guid);
                            Core.Modify.Add(log, "Updating Panel [{0}] with new geometry", panel_New.Guid);
                        }

                        result.AddObject(panel_New);
                    }
                    else
                    {
                        panel_New = Analytical.Create.Panel(panel_Old.Guid, panel_Old, face3D);
                        Core.Modify.Add(log, "Creating temporary Panel for Panel [{0}]", panel_New.Guid);
                    }

                    if (panel_New == null)
                    {
                        continue;
                    }

                    tuples_InternalPoint3D.Add(new Tuple <Panel, Point3D>(panel_New, face3D.InternalPoint3D(tolerance)));

                    foreach (Space space in spaces_Shell)
                    {
                        if (result.AddRelation(space, panel_New))
                        {
                            Core.Modify.Add(log, "Space [{0}] and Panel [{1}] relation added", space.Guid, panel_New.Guid);
                        }
                    }

                    Core.Modify.Add(log, "Adding face finished");
                }
            }

            if (redundantPanels != null && redundantPanels.Count != 0)
            {
                Core.Modify.Add(log, "Solving Redundant Panels");
                foreach (Panel panel in redundantPanels)
                {
                    result.RemoveObject <Panel>(panel.Guid);
                }
            }

            List <Panel> panels_Shading = Analytical.Query.CutShading(result.GetPanels(), panels, tolerance);

            if (panels_Shading != null || panels_Shading.Count != 0)
            {
                foreach (Panel panel_Shading in panels_Shading)
                {
                    result.AddObject(panel_Shading);
                }
            }

            Core.Modify.Add(log, "AdjacencyCluster verification");
            Log log_AdjacencyCluster = Analytical.Create.Log(result);

            if (log != null)
            {
                log.AddRange(log_AdjacencyCluster);
            }

            Core.Modify.Add(log, "Process completed");
            return(result);
        }