using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.Linq;
using TensileLib.FormFinding;
using TensileLib.Geometry;

namespace FeMM.Grasshopper.Components.Patterning
{
    public class FlatteningComponent : GH_Component
    {
        /// <summary>
        /// Initializes a new instance of the FlatteningComponent class.
        /// </summary>
        public FlatteningComponent()
          : base("Flattening", "F", "Cut the mesh on the geodesics lines and creates the flatten the panels", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_PATTERNING)
        {
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            //pManager.AddMeshParameter("Mesh", "M", "The mesh", GH_ParamAccess.item);
            //pManager.AddCurveParameter("Geodesics", "G", "The geodesics lines for cutting the mesh", GH_ParamAccess.list);
            pManager.AddMeshParameter("Mesh", "M", "The mesh to flatten", GH_ParamAccess.item);
            pManager.AddNumberParameter("Border Weight", "BW", "The weight to assign to the border during the flattening operation", GH_ParamAccess.item, 1);
            pManager.AddIntegerParameter("Max Iter NL", "MI,NL", "Max number of iterations in non linear solution (used in non linear solver or updating pressures direction)", GH_ParamAccess.item, 20);//reduced at default
            pManager.AddNumberParameter("Tolerance NL", "T NL", "Tolerance on relative error durign non linear solution (used in non linear solver or updating pressures direction)", GH_ParamAccess.item, 0.01);
            pManager.AddIntegerParameter("Max Iter,s", "MI,s", "Max number of iterations for the solver", GH_ParamAccess.item, 10000);
            pManager.AddNumberParameter("Tolerance,s", "T,s", "Tolerance for the solver", GH_ParamAccess.item, 0);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            //pManager.AddMeshParameter("Panels 3D", "3D", "The 3D panels", GH_ParamAccess.list);
            //pManager.AddMeshParameter("Panels 2D", "2D", "The flatten panels", GH_ParamAccess.list);
            //pManager.AddVectorParameter("Warp directions", "W", "The warp direction of each panel", GH_ParamAccess.list);
            pManager.AddMeshParameter("Flatten", "F", "The flatten mesh", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            GH_Mesh mesh = null;
            double weigth = 0;
            int max_iter = 0;
            double tol = 0;
            int maxIterSolver = 0;
            double epsS = 0;
            if (!DA.GetData(0, ref mesh))
                return;
            if (!DA.GetData(1, ref weigth))
                return;
            if (!DA.GetData(2, ref max_iter))
                return;
            if (!DA.GetData(3, ref tol))
                return;
            if (!DA.GetData(4, ref maxIterSolver))
                return;
            if (!DA.GetData(5, ref epsS))
                return;

            Mesh flat;

            try
            {
                GetFlattenedMesh(mesh.Value, weigth, max_iter, tol, epsS, maxIterSolver, out Solver.ResultCodes ret, out flat);
                switch (ret)
                {
                    case Solver.ResultCodes.Converged:
                    case Solver.ResultCodes.ConvergedUsingSlowMethodWithoutWeigths:
                        {
                            break;
                        }
                    case Solver.ResultCodes.NotconvergedAtMaxIterationNumber:
                        {
                            AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Not converged at max iteration number. Test internal lengths and area.");
                            break;
                        }
                    default:
                        throw new NotSupportedException();
                }
            }
            catch (Exception ex)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
                return;
            }

            DA.SetData(0, flat);
        }

        internal static void GetFlattenedMesh(Mesh mesh, double borderWeigth, int max_iter, double tol, double epsS, int maxIterSolver, out Solver.ResultCodes ret, out Mesh flat)
        {
            mesh.Faces.ConvertQuadsToTriangles();

            //Moving vertices to a fitted plane througth the 3d mesh
            Plane startingSdr = Plane.WorldXY;
            Vector3d normal = Vector3d.Zero;

            mesh.RebuildNormals();
            for (int i = 0; i < mesh.FaceNormals.Count; i++)
            {
                normal += mesh.FaceNormals[i];
            }

            normal /= mesh.FaceNormals.Count;
            Point3d origin = Point3d.Origin;

            for (int i = 0; i < mesh.Vertices.Count; i++)
            {
                Point3d v = (Point3d)mesh.Vertices[i];
                origin += v;
            }
            origin /= mesh.Vertices.Count;

            if (normal * Vector3d.ZAxis < 0)
            {
                normal *= -1;
            }

            Vector3d xDir, yDir;
            if (Math.Abs(normal * Vector3d.YAxis) > 0.9999)
            {
                yDir = Vector3d.CrossProduct(normal, Vector3d.XAxis);
                xDir = Vector3d.CrossProduct(yDir, normal);
            }
            else
            {
                xDir = Vector3d.CrossProduct(Vector3d.YAxis, normal);
                yDir = Vector3d.CrossProduct(normal, xDir);
            }

            var fitSdr = new Plane(origin, origin + xDir, origin + yDir);
            Transform trans = Transform.ChangeBasis(startingSdr, fitSdr);

            // Create the model for the flattening solver starting from the mesh.
            var model = new Model();

            for (int i = 0; i < mesh.Vertices.Count; i++)
            {
                Point3d pt = (Point3d)mesh.Vertices[i];
                //Inserting the local points
                Point3d fitPoint = trans * pt;
                model.AddNewNode(fitPoint.X, fitPoint.Y, fitPoint.Z);
            }

            Polyline[] ne = mesh.GetNakedEdges();

            for (int k = 0; k < mesh.Faces.Count; k++)
            {
                MeshFace face = mesh.Faces[k];
                bool is_e1_naked = false;
                bool is_e2_naked = false;
                bool is_e3_naked = false;
                bool is_e4_naked = false;

                if (ne != null)
                {
                    for (int j = 0; j < ne.Length; j++)
                    {
                        Polyline p = ne[j];
                        for (int i = 0; i < p.SegmentCount; i++)
                        {
                            if (p.SegmentAt(i).From == mesh.Vertices[face.A] && p.SegmentAt(i).To == mesh.Vertices[face.B] ||
                                p.SegmentAt(i).From == mesh.Vertices[face.B] && p.SegmentAt(i).To == mesh.Vertices[face.A])
                                is_e1_naked = true;
                            if (p.SegmentAt(i).From == mesh.Vertices[face.B] && p.SegmentAt(i).To == mesh.Vertices[face.C] ||
                                p.SegmentAt(i).From == mesh.Vertices[face.C] && p.SegmentAt(i).To == mesh.Vertices[face.B])
                                is_e2_naked = true;
                            if (face.IsQuad)
                            {
                                if (p.SegmentAt(i).From == mesh.Vertices[face.C] && p.SegmentAt(i).To == mesh.Vertices[face.D] ||
                                    p.SegmentAt(i).From == mesh.Vertices[face.D] && p.SegmentAt(i).To == mesh.Vertices[face.C])
                                    is_e3_naked = true;
                                if (p.SegmentAt(i).From == mesh.Vertices[face.D] && p.SegmentAt(i).To == mesh.Vertices[face.A] ||
                                    p.SegmentAt(i).From == mesh.Vertices[face.A] && p.SegmentAt(i).To == mesh.Vertices[face.D])
                                    is_e4_naked = true;
                            }
                            else
                            {
                                if (p.SegmentAt(i).From == mesh.Vertices[face.A] && p.SegmentAt(i).To == mesh.Vertices[face.C] ||
                                    p.SegmentAt(i).From == mesh.Vertices[face.C] && p.SegmentAt(i).To == mesh.Vertices[face.A])
                                    is_e3_naked = true;
                            }
                        }
                    }
                }

                Node id1 = null;
                Node id2 = null;
                Node id3 = null;
                Node id4 = null;

                AddBeamToModel(model, face.A, face.B, is_e1_naked, borderWeigth, ref id1, ref id2);
                AddBeamToModel(model, face.B, face.C, is_e2_naked, borderWeigth, ref id2, ref id3);
                if (face.IsQuad)
                {
                    AddBeamToModel(model, face.C, face.D, is_e3_naked, borderWeigth, ref id3, ref id4);
                    AddBeamToModel(model, face.D, face.A, is_e4_naked, borderWeigth, ref id4, ref id1);
                }
                else
                {
                    AddBeamToModel(model, face.C, face.A, is_e3_naked, borderWeigth, ref id3, ref id1);
                }

                model.AddNewPlate(id1, id2, id3, id4);
            }

            // Solve 
            var solver = new PlanarFixedLengthsSolver(model, max_iter, tol, epsS, maxIterSolver);
            ret = solver.Solve(out PlanarFixedLengthsSolver.PlanarFixedLengthsResult planarFixedLengthsResult);

            // If success make a new flatten mesh
            flat = new Mesh();

            //Changeing coords
            var newOrigin = new Point3d(fitSdr.Origin.X, fitSdr.Origin.Y, 0);
            Transform transPlane = Transform.ChangeBasis(new Plane(newOrigin, Vector3d.ZAxis), startingSdr);
            for (int i = 0; i < model.Nodes.Count; i++)
            {
                Node node = model.Nodes.ElementAt(i);
                Point3d planePt = transPlane * new Point3d(node.X, node.Y, node.Z);
                flat.Vertices.Add(planePt);
            }

            for (int i = 0; i < model.Plates.Count; i++)
            {
                Plate plate = model.Plates.ElementAt(i);
                int i1 = model.Nodes.IndexOf(plate.Node1);
                int i2 = model.Nodes.IndexOf(plate.Node2);
                int i3 = model.Nodes.IndexOf(plate.Node3);
                int i4 = model.Nodes.IndexOf(plate.Node4);

                if (plate.PlateType == Plate.PlateTypes.Quad4)
                    flat.Faces.AddFace(i1, i2, i3, i4);
                else if (plate.PlateType == Plate.PlateTypes.Tri3)
                    flat.Faces.AddFace(i1, i2, i3);
            }

            flat.Normals.ComputeNormals();
            flat.Compact();
        }

        protected static bool AddBeamToModel(Model model, int i1, int i2, bool isNaked, double borderWeigth, ref Node id1, ref Node id2)
        {
            Node node1 = model.Nodes.ElementAt(i1);
            Node node2 = model.Nodes.ElementAt(i2);
            double len = new Point3d(node1.X, node1.Y, node1.Z).DistanceTo(new Point3d(node2.X, node2.Y, node2.Z));

            if (node1 != null && node2 != null)
            {
                id1 = node1;
                id2 = node2;

                IEnumerable<Beam> beams = model.Beams.Where(b => b.Node1.Id == node1.Id && b.Node2.Id == node2.Id ||
                    b.Node1.Id == node2.Id && b.Node2.Id == node1.Id);
                if (beams.Count() > 0)
                    return false;

                int id = model.AddNewBeam(id1, id2);
                if (isNaked)
                    model.Beams[id].FixNLLength(len, borderWeigth);
                else
                    model.Beams[id].FixNLLength(len, 1);
                return true;
            }

            return false;
        }

        public override GH_Exposure Exposure => GH_Exposure.tertiary;

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon => Properties.Resources.FlatteningIcon;

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid => new("29b336bf-5f02-44bc-bce9-90eefd10c433");
    }
}
303 files24 directories