using FeMM.Common.Helpers;
using FeMM.Common.Models;
using FeMM.Grasshopper.DataTypes.FeMM;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using Rhino.Geometry;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.IO.Pipes;
using System.Linq;
using System.Runtime.Versioning;
using System.Threading;
using System.Xml;

namespace FeMM.Grasshopper.Components.Export
{
#if NETCOREAPP
    [SupportedOSPlatform("windows")]
#endif
    public class Fem2BimComponent : GH_Component
    {
        protected bool _run;
        protected ModelModel _model;
        protected List<Curve> _perimeters;
        protected GH_Structure<GH_Curve> _holes;
        protected List<double> _thickness;
        protected List<GH_Group> _groups;
        protected List<double> _offsets;
        BackgroundWorker _worker;
        ManualResetEvent _signal;

        /// <summary>
        /// Initializes a new instance of the Fem2BimComponent class.
        /// </summary>
        public Fem2BimComponent()
          : base("Fem2Bim", "F2B", "Export the model to Revit through the Fem2Bim Revit plug-in", Helpers.CategoryNameConstants.CATEGORY_FEMM, Helpers.CategoryNameConstants.SUBCATEGORY_EXPORT)
        {
            _run = false;
            _model = null;
            _perimeters = null;
            _holes = null;
            _thickness = null;
            _groups = null;
            _worker = null;
            _signal = null;
        }

        public override void CreateAttributes()
        {
            var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Send");
            attr.ButtonPressed += () =>
            {
                if (Params.Input[6].SourceCount > 0)
                {
                    _run = true;
                    ExpireSolution(true);
                }
                else
                {
                    if (_worker == null)
                    {
                        Go();
                        attr.Text = "Stop";
                    }
                    else
                    {
                        _worker.CancelAsync();
                        _signal.Set();
                        attr.Text = "Send";
                    }
                }
            };
            m_attributes = attr;
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddGenericParameter("Model", "M", "The FEM model", GH_ParamAccess.item);
            pManager.AddCurveParameter("Perimeters", "Ps", "Slabs or walls perimeters", GH_ParamAccess.list);
            pManager[pManager.ParamCount - 1].Optional = true;
            pManager.AddCurveParameter("Holes", "Hs", "Slabs or walls holes, one branch for each list item", GH_ParamAccess.tree);
            pManager[pManager.ParamCount - 1].Optional = true;
            pManager.AddNumberParameter("Thickness", "Ts", "Thickness of each slab or wall", GH_ParamAccess.list);
            pManager[pManager.ParamCount - 1].Optional = true;
            pManager.AddGenericParameter("Groups", "Gs", "Group of each slab or wall", GH_ParamAccess.list);
            pManager[pManager.ParamCount - 1].Optional = true;
            pManager.AddGenericParameter("Offsets", "Os", "Offsets of each slab or wall", GH_ParamAccess.list);
            pManager[pManager.ParamCount - 1].Optional = true;
            pManager.AddTextParameter("Output Path", "O", "The full path of the file model to create", GH_ParamAccess.item);
            pManager[pManager.ParamCount - 1].Optional = true;
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
        }

        /// <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)
        {
            Message = "";
            GH_Model model = null;
            var perimeters = new List<Curve>();
            GH_Structure<GH_Curve> holes;
            var thickness = new List<double>();
            var groups = new List<GH_Group>();
            var offsets = new List<double>();
            string outputPath = "";
            double toll;

            if (!DA.GetData(0, ref model))
                return;
            DA.GetDataList(1, perimeters);
            DA.GetDataTree(2, out holes);
            DA.GetDataList(3, thickness);
            DA.GetDataList(4, groups);
            DA.GetDataList(5, offsets);

            bool has_out_file = DA.GetData("Output Path", ref outputPath);

            if (has_out_file)
            {
                ((ComponentAttributes.ComponentOneButtonAttributes)m_attributes).Text = "Save";
                if (Path.GetExtension(outputPath).ToLower() != ".f2b")
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid extension in file name");
                    return;
                }
            }

            if (perimeters != null && (perimeters.Count != holes.Branches.Count || perimeters.Count != thickness.Count ||
                perimeters.Count != groups.Count || perimeters.Count != offsets.Count))
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Incongruence in datas dimension");
                return;
            }

            toll = 0.1;//mm
            switch (model.Value.Units)
            {
                case ModelModel.ModelUnits.kNm:
                    toll /= 1000d;
                    break;
                case ModelModel.ModelUnits.Nmm:
                    break;
                case ModelModel.ModelUnits.SI:
                    toll /= 1000d;
                    break;
                default:
                    throw new NotSupportedException();
            }

            if (perimeters != null)
            {
                foreach (Curve per in perimeters)
                {
                    if (!per.IsPolyline() || !per.IsPlanar(toll))
                    {
                        AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Perimeters have to be polylines");
                        return;
                    }
                }
            }

            if (holes != null)
            {
                for (int i = 0; i < holes.Branches.Count; i++)
                {
                    if (holes[i] != null)
                    {
                        for (int j = 0; j < holes[i].Count; j++)
                        {
                            GH_Curve c = holes[i][j];
                            if (c != null && (!c.Value.IsPolyline() || !c.Value.IsPlanar(toll)))
                            {
                                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Holes have to be polylines");
                                return;
                            }
                        }
                    }
                }
            }

            //Congruence in offsets dimensions. Actually only offset 0 or +/- 0.5*thickness are handled
            //if (perimeters != null)
            //{
            //for (int i = 0; i < perimeters.Count; i++)
            //{
            //    if ((System.Math.Abs(offsets[i]) > toll) &&
            //        (System.Math.Abs(offsets[i]) - 0.5 * thickness[i] > toll))
            //    {
            //        AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Offsets have to be 0 or +/-0.5*thickness");
            //        return;
            //    }
            //}
            //}

            _model = model.Value;
            _perimeters = perimeters;
            _holes = holes;
            _thickness = thickness;
            _groups = groups;
            _offsets = offsets;

            if (_run)
            {
                try
                {
                    XmlDocument xmlDoc = CreateExchangeFile();
                    if (xmlDoc != null)
                        xmlDoc.Save(outputPath);

                    Message = "Done";
                }
                catch (Exception e)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Error, e.Message);
                    Message = "Error";
                }
                _run = false;
            }
        }

        private void Go()
        {
            Message = "";
            // With Pipe
            _worker = new BackgroundWorker
            {
                WorkerSupportsCancellation = true
            };
            _worker.DoWork += Send;
            _worker.RunWorkerCompleted += Sent;

            _signal = new ManualResetEvent(false);

            _worker.RunWorkerAsync();
        }

        private void Send(object sender, DoWorkEventArgs e)
        {
            try
            {
                System.Globalization.CultureInfo saved = Thread.CurrentThread.CurrentCulture;
                System.Globalization.CultureInfo customCulture = (System.Globalization.CultureInfo)Thread.CurrentThread.CurrentCulture.Clone();
                customCulture.NumberFormat.NumberDecimalSeparator = ".";
                Thread.CurrentThread.CurrentCulture = customCulture;

                XmlDocument xmlDoc = CreateExchangeFile();
                if (xmlDoc == null)
                    return;

                string file_content;
                using (var stringWriter = new StringWriter())
                using (var xmlTextWriter = XmlWriter.Create(stringWriter))
                {
                    xmlDoc.Save(xmlTextWriter);
                    //xmlDoc.WriteTo(xmlTextWriter);
                    xmlTextWriter.Flush();
                    file_content = stringWriter.GetStringBuilder().ToString();
                }

                Thread.CurrentThread.CurrentCulture = saved;

                using (var pipeServer = new NamedPipeServerStream("F2BInteroptPipe", PipeDirection.InOut, 1,
                    PipeTransmissionMode.Byte, PipeOptions.Asynchronous))
                {
                    IAsyncResult ar = pipeServer.BeginWaitForConnection(_ => _signal.Set(), null);
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Remark, "Waiting for Revit connection ...");
                    _signal.WaitOne();
                    if (ar.IsCompleted)
                    {
                        pipeServer.EndWaitForConnection(ar);

                        using (var bw = new BinaryWriter(pipeServer))
                        {
                            byte[] buffer = BitConverter.GetBytes(file_content.Length);
                            bw.Write(buffer);
                            buffer = System.Text.Encoding.ASCII.GetBytes(file_content);
                            bw.Write(buffer);
                        }
                    }
                    else
                    {
                        BackgroundWorker worker = sender as BackgroundWorker;
                        if (worker.CancellationPending)
                        {
                            e.Cancel = true;
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, ex.Message);
            }
        }

        private void Sent(object sender, RunWorkerCompletedEventArgs e)
        {
            ClearRuntimeMessages();
            if (e.Cancelled)
                Message = "Job cancelled by user";
            else
                Message = "Job done with success.";
            _worker = null;
            _signal = null;
            ((ComponentAttributes.ComponentOneButtonAttributes)m_attributes).Text = "Send";
        }

        private XmlDocument CreateExchangeFile()
        {
            var xmlDoc = new XmlDocument();

            double st_conv = 0; // conversion factor to mm for section names
            double samePosToll;

            XmlNode rootNode = xmlDoc.CreateElement("model");
            XmlAttribute unitsAttr = xmlDoc.CreateAttribute("units");
            string coordAccuracy;

            switch (_model.Units)
            {
                case ModelModel.ModelUnits.kNm:
                case ModelModel.ModelUnits.SI:
                    unitsAttr.Value = "m";
                    st_conv = 1000;
                    coordAccuracy = "F5";
                    break;
                case ModelModel.ModelUnits.Nmm:
                    unitsAttr.Value = "mm";
                    st_conv = 1;
                    coordAccuracy = "F2";
                    break;
                default:
                    throw new NotSupportedException();
            }

            samePosToll = Helpers.ModelHelper.GetTolerance();

            rootNode.Attributes.Append(unitsAttr);
            xmlDoc.AppendChild(rootNode);

            // Sections
            XmlNode sectionsNode = xmlDoc.CreateElement("sections");
            rootNode.AppendChild(sectionsNode);
            var offsets = new Hashtable();
            var angles = new Hashtable();
            //var heights = new Hashtable();
            //var tapered = new Hashtable();
            BeamPropertyModel[] beamProps = [.. _model.Beams.Select(b => b.BeamProperty).Distinct()];
            int id = 1;
            for (int i = 0; i < beamProps.Length; i++)
            {
                BeamPropertyModel bp = beamProps[i];
                bp.Id = id;
                id++;
                XmlNode sectionNode = xmlDoc.CreateElement("section");
                XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
                nAttr.Value = bp.Name;
                sectionNode.Attributes.Append(nAttr);
                XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
                nameAttr.Value = bp.Name;
                sectionNode.Attributes.Append(nameAttr);

                var offset = new Point2d(0, 0);
                Point2d[] perimeterPts = null;
                var centroid = new Point2d(0, 0);

                XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
                XmlAttribute d1Attr = xmlDoc.CreateAttribute("d1");
                XmlAttribute d2Attr = xmlDoc.CreateAttribute("d2");
                XmlAttribute d3Attr = xmlDoc.CreateAttribute("d3");
                XmlAttribute t1Attr = xmlDoc.CreateAttribute("t1");
                XmlAttribute t2Attr = xmlDoc.CreateAttribute("t2");
                XmlAttribute t3Attr = xmlDoc.CreateAttribute("t3");
                XmlAttribute mirrorGapAAttribute = xmlDoc.CreateAttribute("mirrorGapA");
                switch (bp.SectionType)
                {
                    case SectionModel.SectionTypes.SolidCircle:
                        typeAttr.Value = string.Format("CSS-{0:0.#}", bp.D * st_conv);
                        d1Attr.Value = bp.D.ToString();
                        //heights.Add(bp.Name, bp.D);
                        offset.X = 0;
                        offset.Y = 0;
                        break;
                    case SectionModel.SectionTypes.HollowCircle:
                        typeAttr.Value = string.Format("CHS-{0:0.#}x{1:0.#}", bp.D * st_conv, bp.T * st_conv);
                        d1Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T.ToString();
                        //heights.Add(bp.Name, bp.D);
                        offset.X = 0;
                        offset.Y = 0;
                        break;
                    case SectionModel.SectionTypes.SolidRectangle:
                        typeAttr.Value = string.Format("RSS-{0:0.#}x{1:0.#}", bp.D * st_conv, bp.B * st_conv);
                        d1Attr.Value = bp.B.ToString();
                        d2Attr.Value = bp.D.ToString();
                        //heights.Add(bp.Name, bp.D);
                        offset.X = 0;
                        offset.Y = 0;
                        break;
                    case SectionModel.SectionTypes.HollowRectangle:
                        typeAttr.Value = string.Format("RHS-{0:0.#}x{1:0.#}x{2:0.#}-{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
                        d1Attr.Value = bp.B.ToString();
                        d2Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T1.ToString();
                        t2Attr.Value = bp.T2.ToString();
                        //heights.Add(bp.Name, bp.D);
                        offset.X = 0;
                        offset.Y = 0;
                        break;
                    case SectionModel.SectionTypes.C:
                        if (bp.Mirror == SectionModel.MirrorTypes.None)
                        {
                            typeAttr.Value = string.Format("C-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
                        }
                        else if (bp.Mirror == SectionModel.MirrorTypes.Left)
                        {
                            typeAttr.Value = string.Format("2C-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}x{4:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv, bp.MirrorGapA * st_conv);
                            mirrorGapAAttribute.Value = bp.MirrorGapA.ToString();
                        }
                        else
                        {
                            throw new NotImplementedException("Gap different from Left not implemented");
                        }
                        d1Attr.Value = bp.B.ToString();
                        d2Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T1.ToString();
                        t2Attr.Value = bp.T2.ToString();
                        //heights.Add(bp.Name, bp.D);
                        perimeterPts = new Point2d[9];
                        perimeterPts[0] = new Point2d(0, 0);
                        perimeterPts[1] = new Point2d(bp.B, 0);
                        perimeterPts[2] = new Point2d(bp.B, bp.T1);
                        perimeterPts[3] = new Point2d(bp.T2, bp.T1);
                        perimeterPts[4] = new Point2d(bp.T2, bp.D - bp.T1);
                        perimeterPts[5] = new Point2d(bp.B, bp.D - bp.T1);
                        perimeterPts[6] = new Point2d(bp.B, bp.D);
                        perimeterPts[7] = new Point2d(0, bp.D);
                        perimeterPts[8] = new Point2d(0, 0);
                        centroid = PolygonCentroid(perimeterPts);
                        offset.X = bp.B / 2 - centroid.X;
                        offset.Y = bp.D / 2 - centroid.Y;
                        break;
                    case SectionModel.SectionTypes.I:
                        typeAttr.Value = string.Format("H-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.B1 * st_conv, bp.D * st_conv, bp.T1 * st_conv, bp.T3 * st_conv);
                        d1Attr.Value = bp.B1.ToString();
                        d2Attr.Value = bp.B2.ToString();
                        d3Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T1.ToString();
                        t2Attr.Value = bp.T2.ToString();
                        t3Attr.Value = bp.T3.ToString();
                        //heights.Add(bp.Name, bp.D);
                        perimeterPts = new Point2d[13];
                        perimeterPts[0] = new Point2d(bp.B1 > bp.B2 ? 0 : bp.B2 / 2 - bp.B1 / 2, 0);
                        perimeterPts[1] = perimeterPts[0] + new Vector2d(bp.B1, 0);
                        perimeterPts[2] = perimeterPts[1] + new Vector2d(0, bp.T1);
                        perimeterPts[3] = perimeterPts[2] - new Vector2d(bp.B1 / 2 - bp.T3 / 2, 0);
                        perimeterPts[4] = perimeterPts[3] + new Vector2d(0, bp.D - bp.T1 - bp.T2);
                        perimeterPts[5] = perimeterPts[4] + new Vector2d(bp.B2 / 2 - bp.T3 / 2, 0);
                        perimeterPts[6] = perimeterPts[5] + new Vector2d(0, bp.T2);
                        perimeterPts[7] = perimeterPts[6] - new Vector2d(bp.B2, 0);
                        perimeterPts[8] = perimeterPts[7] - new Vector2d(0, bp.T2);
                        perimeterPts[9] = perimeterPts[8] + new Vector2d(bp.B2 / 2 - bp.T3 / 2, 0);
                        perimeterPts[10] = perimeterPts[9] - new Vector2d(0, bp.D - bp.T1 - bp.T2);
                        perimeterPts[11] = new Point2d(perimeterPts[0].X, bp.T1);
                        perimeterPts[12] = new Point2d(perimeterPts[0].X, perimeterPts[0].Y);
                        centroid = PolygonCentroid(perimeterPts);
                        offset.X = Math.Max(bp.B2, bp.B1) / 2 - centroid.X;
                        offset.Y = bp.D / 2 - centroid.Y;
                        break;
                    case SectionModel.SectionTypes.T:
                        typeAttr.Value = string.Format("T-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.B * st_conv, bp.D * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
                        d1Attr.Value = bp.B.ToString();
                        d2Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T1.ToString();
                        t2Attr.Value = bp.T2.ToString();
                        //heights.Add(bp.Name, bp.D);
                        perimeterPts = new Point2d[9];
                        perimeterPts[0] = new Point2d(bp.B / 2 - bp.T2 / 2, 0);
                        perimeterPts[1] = perimeterPts[0] + new Vector2d(bp.T2, 0);
                        perimeterPts[2] = perimeterPts[1] + new Vector2d(0, bp.D - bp.T1);
                        perimeterPts[3] = new Point2d(bp.B, perimeterPts[2].Y);
                        perimeterPts[4] = new Point2d(bp.B, bp.D);
                        perimeterPts[5] = new Point2d(0, bp.D);
                        perimeterPts[6] = perimeterPts[5] - new Vector2d(0, bp.T1);
                        perimeterPts[7] = perimeterPts[0] + new Vector2d(0, bp.D - bp.T1);
                        perimeterPts[8] = new Point2d(perimeterPts[0].X, perimeterPts[0].Y);
                        centroid = PolygonCentroid(perimeterPts);
                        offset.X = bp.B / 2 - centroid.X;
                        offset.Y = bp.D / 2 - centroid.Y;
                        break;
                    case SectionModel.SectionTypes.Angle:
                        if (bp.Mirror == SectionModel.MirrorTypes.None)
                        {
                            typeAttr.Value = string.Format("L-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv);
                        }
                        else if (bp.Mirror == SectionModel.MirrorTypes.Left)
                        {
                            typeAttr.Value = string.Format("2L-{0:0.#}x{1:0.#}x{2:0.#}x{3:0.#}x{4:0.#}", bp.D * st_conv, bp.B * st_conv, bp.T1 * st_conv, bp.T2 * st_conv, bp.MirrorGapA * st_conv);
                            mirrorGapAAttribute.Value = bp.MirrorGapA.ToString();
                        }
                        else
                        {
                            throw new NotImplementedException("Gap different from Left not implemented");
                        }
                        d1Attr.Value = bp.B.ToString();
                        d2Attr.Value = bp.D.ToString();
                        t1Attr.Value = bp.T1.ToString();
                        t2Attr.Value = bp.T2.ToString();
                        //heights.Add(bp.Name, bp.B);
                        perimeterPts = new Point2d[7];
                        perimeterPts[0] = new Point2d(0, 0);
                        perimeterPts[1] = new Point2d(bp.B, 0);
                        perimeterPts[2] = new Point2d(bp.B, bp.T1);
                        perimeterPts[3] = new Point2d(bp.T2, bp.T1);
                        perimeterPts[4] = new Point2d(bp.T2, bp.D);
                        perimeterPts[5] = new Point2d(0, bp.D);
                        perimeterPts[6] = new Point2d(0, 0);
                        centroid = PolygonCentroid(perimeterPts);
                        offset.X = bp.B / 2 - centroid.X;
                        offset.Y = bp.D / 2 - centroid.Y;
                        break;
                    default:
                        typeAttr.Value = "Unknown";
                        break;
                }


                if (d1Attr.Value == string.Empty)
                    d1Attr.Value = "0";
                if (d2Attr.Value == string.Empty)
                    d2Attr.Value = "0";
                if (d3Attr.Value == string.Empty)
                    d3Attr.Value = "0";
                if (t1Attr.Value == string.Empty)
                    t1Attr.Value = "0";
                if (t2Attr.Value == string.Empty)
                    t2Attr.Value = "0";
                if (t3Attr.Value == string.Empty)
                    t3Attr.Value = "0";
                if (mirrorGapAAttribute.Value == string.Empty)
                    mirrorGapAAttribute.Value = "0";

                sectionNode.Attributes.Append(typeAttr);
                sectionNode.Attributes.Append(d1Attr);
                sectionNode.Attributes.Append(d2Attr);
                sectionNode.Attributes.Append(d3Attr);
                sectionNode.Attributes.Append(t1Attr);
                sectionNode.Attributes.Append(t2Attr);
                sectionNode.Attributes.Append(t3Attr);
                sectionNode.Attributes.Append(mirrorGapAAttribute);

                XmlAttribute materialAttr = xmlDoc.CreateAttribute("material");
                materialAttr.Value = bp.Material.Name;
                sectionNode.Attributes.Append(materialAttr);

                sectionsNode.AppendChild(sectionNode);

                offsets.Add(bp.Id, offset);

                switch (bp.SectionType)
                {
                    case SectionModel.SectionTypes.C:
                        if (bp.Mirror == SectionModel.MirrorTypes.None)
                        {
                            angles.Add(bp.Id, -Math.PI / 2);
                        }
                        break;
                    case SectionModel.SectionTypes.Angle:
                        if (bp.Mirror == SectionModel.MirrorTypes.None)
                        {
                            angles.Add(bp.Id, -bp.AngleX1Rad);
                        }
                        break;
                    default:
                        angles.Add(bp.Id, 0);
                        break;
                }
            }

            // Load cases
            XmlNode loadCasesNode = xmlDoc.CreateElement("loadcases");
            rootNode.AppendChild(loadCasesNode);
            CommonModelHelper.GetLoadCases(_model, out List<LoadCaseModel> loadCases);
            id = 1;
            for (int i = 0; i < loadCases.Count; i++)
            {
                LoadCaseModel loadCase = loadCases[i];
                loadCase.Name = id.ToString();
                id++;
                XmlNode loadcaseNode = xmlDoc.CreateElement("loadcase");
                XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
                idAttr.Value = loadCase.Name;
                loadcaseNode.Attributes.Append(idAttr);
                XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
                nameAttr.Value = loadCase.Name;
                loadcaseNode.Attributes.Append(nameAttr);
                loadCasesNode.AppendChild(loadcaseNode);
            }

            // Load combinations
            XmlNode loadComboNode = xmlDoc.CreateElement("loadcombinations");
            rootNode.AppendChild(loadComboNode);
            foreach (LoadCaseCombinationModel combo in _model.Combinations)
            {
                XmlNode comboNode = xmlDoc.CreateElement("loadcombination");
                XmlAttribute nameAttr = xmlDoc.CreateAttribute("name");
                nameAttr.Value = combo.Name;
                comboNode.Attributes.Append(nameAttr);
                foreach (KeyValuePair<CaseModel, double> val in combo.Values)
                {
                    XmlNode caseNode = xmlDoc.CreateElement("loadcase");
                    comboNode.AppendChild(caseNode);
                    XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
                    idAttr.Value = loadCases.Single(lc => lc.Name == val.Key.Name).Name;
                    caseNode.Attributes.Append(idAttr);
                    XmlAttribute incrAttr = xmlDoc.CreateAttribute("value");
                    incrAttr.Value = val.Value.ToString();
                    caseNode.Attributes.Append(incrAttr);
                }
                loadComboNode.AppendChild(comboNode);
            }

            // Nodes
            XmlNode nodesNode = xmlDoc.CreateElement("nodes");
            rootNode.AppendChild(nodesNode);
            foreach (NodeModel node in _model.Nodes)
            {
                Point3d pt = node.Position;

                XmlNode nodeNode = xmlDoc.CreateElement("node");
                XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
                nAttr.Value = node.NodeId;
                nodeNode.Attributes.Append(nAttr);
                XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
                idAttr.Value = node.NodeId;
                nodeNode.Attributes.Append(idAttr);
                XmlAttribute xyzAttr = xmlDoc.CreateAttribute("xyz");
                xyzAttr.Value = pt.X.ToString(coordAccuracy) + "," + pt.Y.ToString(coordAccuracy) + "," + pt.Z.ToString(coordAccuracy);
                nodeNode.Attributes.Append(xyzAttr);
                nodesNode.AppendChild(nodeNode);

                foreach (LoadModel load in node.Loads)
                {
                    XmlNode loadNode = xmlDoc.CreateElement("load");
                    nodeNode.AppendChild(loadNode);

                    XmlAttribute loadCasedAttr = xmlDoc.CreateAttribute("loadcase");
                    loadCasedAttr.Value = loadCases.Single(lc => lc.Name == load.LoadCase.Name).Name;
                    loadNode.Attributes.Append(loadCasedAttr);
                    XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
                    typeAttr.Value = load.Description;
                    loadNode.Attributes.Append(typeAttr);

                    if (load is NodeForceLoadModel force_load)
                    {
                        XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
                        valAttr.Value = force_load.Value.ToString();
                        loadNode.Attributes.Append(valAttr);
                    }
                    else if (load is NodeMomentLoadModel moment_load)
                    {
                        XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
                        valAttr.Value = moment_load.Value.ToString();
                        loadNode.Attributes.Append(valAttr);
                    }
                    else if (load is NodeTemperatureLoadModel temp_load)
                    {
                        XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
                        valAttr.Value = temp_load.Value.ToString();
                        loadNode.Attributes.Append(valAttr);
                    }
                }
            }

            // Beams
            var added_beams = new List<string>();
            XmlNode beamsNode = xmlDoc.CreateElement("beams");
            rootNode.AppendChild(beamsNode);
            foreach (BeamModel beam in _model.Beams)
            {
                if (added_beams.Contains(beam.BeamId))
                    continue;

                string group_name = string.Join(", ", beam.Groups);

                double ang = Convert.ToDouble(angles[beam.BeamProperty.Id]);
                Point2d? cen = offsets[beam.BeamProperty.Id] as Point2d?;

                double rads = beam.AngleDeg * Math.PI / 180;


                XmlNode beamNode = xmlDoc.CreateElement("beam");

                XmlAttribute nAttr = xmlDoc.CreateAttribute("n");
                nAttr.Value = beam.BeamId;
                beamNode.Attributes.Append(nAttr);
                XmlAttribute idAttr = xmlDoc.CreateAttribute("id");
                idAttr.Value = beam.BeamId;
                beamNode.Attributes.Append(idAttr);

                NodeModel startNode = _model.Nodes.SingleOrDefault(node => node.Position.DistanceTo(beam.PointFrom) < samePosToll);
                NodeModel endNode = _model.Nodes.SingleOrDefault(node => node.Position.DistanceTo(beam.PointTo) < samePosToll);
                if (startNode == null || endNode == null)
                    continue;

                XmlAttribute fromAttr = xmlDoc.CreateAttribute("from");
                fromAttr.Value = startNode.NodeId;
                beamNode.Attributes.Append(fromAttr);
                XmlAttribute toAttr = xmlDoc.CreateAttribute("to");
                toAttr.Value = endNode.NodeId;
                beamNode.Attributes.Append(toAttr);

                XmlAttribute sectionAttr = xmlDoc.CreateAttribute("section");
                sectionAttr.Value = beam.BeamProperty.Name;
                beamNode.Attributes.Append(sectionAttr);
                XmlAttribute groupAttr = xmlDoc.CreateAttribute("group");
                groupAttr.Value = group_name.ToString();
                beamNode.Attributes.Append(groupAttr);
                XmlAttribute angleAttr = xmlDoc.CreateAttribute("angle");
                angleAttr.Value = Convert.ToString(rads + ang);
                beamNode.Attributes.Append(angleAttr);
                XmlAttribute offsetAttr = xmlDoc.CreateAttribute("offset");
                Vector2d beamOffset = beam.Offset;
                if (beamOffset == Vector2d.Unset)
                {
                    beamOffset = Vector2d.Zero;
                }
                offsetAttr.Value = string.Format("{0},{1}", cen.HasValue ? cen.Value.X + beamOffset.X : beamOffset.X,
                    cen.HasValue ? cen.Value.Y + beamOffset.Y : beamOffset.Y);
                beamNode.Attributes.Append(offsetAttr);

                //Taper
                var n = new double[] { 0, 0 };
                string[] propertyDatas = beam.BeamProperty.Name.Split(['_']);
                string[] heigthDatas = propertyDatas[propertyDatas.Length - 1].Split(['-']);
                if (heigthDatas.Length == 2)
                {
                    if (double.TryParse(heigthDatas[0], out double startHeigth))
                    {
                        n[0] = startHeigth / beam.BeamProperty.D;
                    }
                    if (double.TryParse(heigthDatas[1], out double endHeigth))
                    {
                        n[1] = endHeigth / beam.BeamProperty.D;
                    }
                }
                XmlAttribute n1Attr = xmlDoc.CreateAttribute("n1");
                n1Attr.Value = n[0] != 0 ? n[0].ToString() : "1";
                beamNode.Attributes.Append(n1Attr);
                XmlAttribute n2Attr = xmlDoc.CreateAttribute("n2");
                n2Attr.Value = n[1] != 0 ? n[1].ToString() : "1";
                beamNode.Attributes.Append(n2Attr);
                beamsNode.AppendChild(beamNode);

                foreach (LoadModel load in beam.Loads)
                {
                    XmlNode loadNode = xmlDoc.CreateElement("load");
                    beamNode.AppendChild(loadNode);

                    XmlAttribute loadCasedAttr = xmlDoc.CreateAttribute("loadcase");
                    loadCasedAttr.Value = loadCases.Single(lc => lc.Name == load.LoadCase.Name).Name;
                    loadNode.Attributes.Append(loadCasedAttr);
                    /*if (load is BeamPreLoadModel pre_load)
                    {
                        XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
                        typeAttr.Value = pre_load.PreLoadType.GetDescription();
                        loadNode.Attributes.Append(typeAttr);
                        XmlAttribute valAttr = xmlDoc.CreateAttribute("value");
                        valAttr.Value = pre_load.Value.ToString();
                        loadNode.Attributes.Append(valAttr);
                    }
                    else*/
                    if (load is BeamDistributedLoadModel distr_load)
                    {
                        XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
                        typeAttr.Value = "Distributed";
                        loadNode.Attributes.Append(typeAttr);
                        switch (distr_load.Value.LoadSchema)
                        {
                            case BeamDistributedLoadModel.LoadSchema.Uniform:
                            case BeamDistributedLoadModel.LoadSchema.Keystone:
                                XmlAttribute rd1Attr = xmlDoc.CreateAttribute("rd1");
                                rd1Attr.Value = distr_load.Value.A.ToString();
                                loadNode.Attributes.Append(rd1Attr);
                                XmlAttribute rd2Attr = xmlDoc.CreateAttribute("rd2");
                                rd2Attr.Value = distr_load.Value.B.ToString();
                                loadNode.Attributes.Append(rd2Attr);
                                // TODO : completare una volta capiti i carichi con orientamento locale e a non costanti

                                BeamDistributedLoadModel.LoadDirection dir = distr_load.Value.LoadDirection;

                                double p = 0;
                                switch (distr_load.Value.LoadSchema)
                                {
                                    case BeamDistributedLoadModel.LoadSchema.Uniform:
                                        p = distr_load.Value.PA;
                                        break;
                                    case BeamDistributedLoadModel.LoadSchema.Keystone:
                                        p = (distr_load.Value.PA + distr_load.Value.PB) / 2;
                                        break;
                                }

                                XmlAttribute f1Attr = xmlDoc.CreateAttribute("f1");
                                f1Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.X ||
                                    dir == BeamDistributedLoadModel.LoadDirection.X_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(f1Attr);
                                XmlAttribute f2Attr = xmlDoc.CreateAttribute("f2");
                                f2Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.Y ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Y_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(f2Attr);
                                XmlAttribute f3Attr = xmlDoc.CreateAttribute("f3");
                                f3Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Force &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.Z ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Z_Projected ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Gravity ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Gravity_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(f3Attr);
                                XmlAttribute m1Attr = xmlDoc.CreateAttribute("m1");
                                m1Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.X ||
                                    dir == BeamDistributedLoadModel.LoadDirection.X_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(m1Attr);
                                XmlAttribute m2Attr = xmlDoc.CreateAttribute("m2");
                                m2Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.Y ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Y_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(m2Attr);
                                XmlAttribute m3Attr = xmlDoc.CreateAttribute("m3");
                                m3Attr.Value = distr_load.DistributedLoadType == BeamDistributedLoadModel.LoadType.Moment &&
                                    (dir == BeamDistributedLoadModel.LoadDirection.Z ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Z_Projected ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Gravity ||
                                    dir == BeamDistributedLoadModel.LoadDirection.Gravity_Projected) ? p.ToString() : "0";
                                loadNode.Attributes.Append(m3Attr);

                                break;
                                //case BeamDistributedLoadModel.LoadSchema.Keystone:
                                //    break;
                        }

                        /*
                        XmlAttribute schemaAttr = xmlDoc.CreateAttribute("schema");
                        schemaAttr.Value = distr_load.Value.LoadSchema.GetDescription();
                        loadNode.Attributes.Append(schemaAttr);
                        XmlAttribute dirAttr = xmlDoc.CreateAttribute("dir");
                        double sign = 1;
                        switch (distr_load.Value.LoadDirection)
                        {
                            case BeamDistributedLoadModel.LoadDirection.X:
                            case BeamDistributedLoadModel.LoadDirection.X_Projected:
                                dirAttr.Value = "X"; 
                                break;
                            case BeamDistributedLoadModel.LoadDirection.Y:
                            case BeamDistributedLoadModel.LoadDirection.Y_Projected:
                                dirAttr.Value = "Y";
                                break;
                            case BeamDistributedLoadModel.LoadDirection.Z:
                            case BeamDistributedLoadModel.LoadDirection.Z_Projected:
                                dirAttr.Value = "Z";
                                break;
                            case BeamDistributedLoadModel.LoadDirection.Gravity:
                            case BeamDistributedLoadModel.LoadDirection.Gravity_Projected:
                                dirAttr.Value = "Z";
                                sign = -1;
                                break;
                        }
                        loadNode.Attributes.Append(dirAttr);
                        XmlAttribute p1Attr = xmlDoc.CreateAttribute("P1");
                        p1Attr.Value = (distr_load.Value.P1 * sign).ToString();
                        loadNode.Attributes.Append(p1Attr);
                        XmlAttribute p2Attr = xmlDoc.CreateAttribute("P2");
                        p2Attr.Value = (distr_load.Value.P2 * sign).ToString();
                        loadNode.Attributes.Append(p2Attr);
                        XmlAttribute paAttr = xmlDoc.CreateAttribute("PA");
                        paAttr.Value = (distr_load.Value.PA * sign).ToString();
                        loadNode.Attributes.Append(paAttr);
                        XmlAttribute pbAttr = xmlDoc.CreateAttribute("PB");
                        pbAttr.Value = (distr_load.Value.PB * sign).ToString();
                        loadNode.Attributes.Append(pbAttr);
                        XmlAttribute aAttr = xmlDoc.CreateAttribute("a");
                        aAttr.Value = distr_load.Value.A.ToString();
                        loadNode.Attributes.Append(aAttr);
                        XmlAttribute bAttr = xmlDoc.CreateAttribute("b");
                        pbAttr.Value = distr_load.Value.B.ToString();
                        loadNode.Attributes.Append(pbAttr);
                        */
                    }
                    else if (load is BeamPointLoadModel point_load)
                    {
                        XmlAttribute typeAttr = xmlDoc.CreateAttribute("type");
                        typeAttr.Value = "Point";
                        loadNode.Attributes.Append(typeAttr);
                        XmlAttribute pAttr = xmlDoc.CreateAttribute("p");
                        pAttr.Value = point_load.Value.P.ToString();
                        loadNode.Attributes.Append(pAttr);
                        XmlAttribute aAttr = xmlDoc.CreateAttribute("a");
                        aAttr.Value = point_load.Value.A.ToString();
                        loadNode.Attributes.Append(aAttr);
                    }
                }

                added_beams.Add(beam.BeamId);
            }

            // Slabs/walls 
            XmlNode slabsNode = xmlDoc.CreateElement("slabs");
            rootNode.AppendChild(slabsNode);
            XmlNode wallsNode = xmlDoc.CreateElement("walls");
            rootNode.AppendChild(wallsNode);
            if (_perimeters != null)
            {
                for (int i = 0; i < _perimeters.Count; i++)
                {
                    if (!_perimeters[i].TryGetPolyline(out Polyline perimeter))
                    {
                        throw new NotSupportedException();
                    }

                    Maffeis.Geometry.Point3d p0 = null;
                    Maffeis.Geometry.Point3d p1 = null;
                    Maffeis.Geometry.Point3d p2 = null;
                    double offset = _offsets[i];
                    for (int j = 0; j < perimeter.Count - 2; j++)
                    {
                        Vector3d dir0, dir1;
                        dir0 = perimeter[j + 1] - perimeter[j];
                        dir0.Unitize();
                        dir1 = perimeter[j + 2] - perimeter[j];
                        dir1.Unitize();

                        if (Math.Abs(dir0 * dir1) < 0.999)
                        {
                            p0 = new Maffeis.Geometry.Point3d(perimeter[j].X, perimeter[j].Y, perimeter[j].Z);
                            p1 = new Maffeis.Geometry.Point3d(perimeter[j + 1].X, perimeter[j + 1].Y, perimeter[j + 1].Z);
                            p2 = new Maffeis.Geometry.Point3d(perimeter[j + 2].X, perimeter[j + 2].Y, perimeter[j + 2].Z);
                            break;
                        }
                    }

                    if (p0 == null)
                    {
                        throw new NotSupportedException();
                    }

                    Maffeis.Geometry.Vector3d orto = p1 - p0 ^ p2 - p0;
                    //orto = Maffeis.Geometry.Geom.Direction(orto);
                    orto.Unitize();

                    bool isSlabOtherwiseWall;
                    if (Math.Abs(orto * Maffeis.Geometry.Vector3d.ZAxis) > 0.999)
                    {
                        isSlabOtherwiseWall = true;
                    }
                    else if (Math.Abs(orto * Maffeis.Geometry.Vector3d.ZAxis) < 0.001)
                    {
                        isSlabOtherwiseWall = false;
                    }
                    else
                    {
                        AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Perimeter " + i.ToString() + " inclined. Will be planarized");
                        if (orto * Maffeis.Geometry.Vector3d.ZAxis > 0.5)
                        {
                            //moving vertical
                            isSlabOtherwiseWall = true;
                        }
                        else
                        {
                            //moving planar
                            isSlabOtherwiseWall = false;
                        }
                    }

                    if (isSlabOtherwiseWall)
                    {
                        orto = Maffeis.Geometry.Vector3d.ZAxis;
                    }
                    else
                    {
                        //orto = Maffeis.Geometry.Geom.Direction(new Maffeis.Geometry.Point3d(orto.X, orto.Y, 0));
                        orto = new Maffeis.Geometry.Vector3d(orto.X, orto.Y, 0.0);
                        orto.Unitize();
                    }

                    //orienting the orto direction. Orto direction have to be positive;
                    Maffeis.Geometry.Vector3d positiveDir;
                    positiveDir = new Maffeis.Geometry.Vector3d(1, 1, 1);
                    if (orto * positiveDir < 0)
                    {
                        orto *= -1;
                    }

                    //Translating of offset. Perimeters/holes are always on centerline. After in revit the position line 
                    //is moved according to offset sign
                    p0 += orto * offset;
                    p1 += orto * offset;
                    var trans = new Maffeis.Geometry.CoordinateSystem(p0, p1, p0/*, p0 + 1000 * orto*/);

                    var perimeterPoly = new Maffeis.Geometry.Polygon2d();
                    for (int j = 0; j < perimeter.Count; j++)
                    {
                        bool add;

                        if (j == perimeter.Count - 1)
                        {
                            add = (perimeter[j] - perimeter[0]).Length > samePosToll;
                        }
                        else
                        {
                            add = true;
                        }
                        if (add)
                        {
                            Point3d p;
                            p = perimeter[j];
                            var bufferP = new Maffeis.Geometry.Point3d(p.X, p.Y, p.Z);
                            bufferP = trans.ToLocal(bufferP);
                            perimeterPoly.Add(bufferP.X, bufferP.Y);
                        }
                    }

                    List<Maffeis.Geometry.Polygon2d> holePolysCanBeNull = null;
                    if (_holes.Branches[i] != null)
                    {
                        holePolysCanBeNull = [];
                        foreach (GH_Curve c in _holes[i])
                        {
                            if (c != null)
                            {
                                Polyline hole;
                                Maffeis.Geometry.Polygon2d holePoly;

                                if (!c.Value.TryGetPolyline(out hole))
                                {
                                    throw new NotSupportedException();
                                }

                                holePoly = [];
                                for (int j = 0; j < hole.Count; j++)
                                {
                                    bool add;

                                    if (j == hole.Count - 1)
                                    {
                                        add = (hole[j] - hole[0]).Length > samePosToll;
                                    }
                                    else
                                    {
                                        add = true;
                                    }
                                    if (add)
                                    {
                                        Point3d p;
                                        Maffeis.Geometry.Point3d bufferP;
                                        p = hole[j];
                                        bufferP = new Maffeis.Geometry.Point3d(p.X, p.Y, p.Z);
                                        bufferP = trans.ToLocal(bufferP);
                                        holePoly.Add(bufferP.X, bufferP.Y);
                                    }
                                }

                                holePolysCanBeNull.Add(holePoly);
                            }
                        }
                    }

                    double thickness = _thickness[i];

                    string group = _groups[i].Value.Name;

                    string childName;
                    XmlNode platesNode;

                    if (isSlabOtherwiseWall)
                    {
                        childName = "slab";
                        platesNode = slabsNode;
                    }
                    else
                    {
                        childName = "wall";
                        platesNode = wallsNode;
                    }

                    //writing
                    XmlNode slabNode = xmlDoc.CreateElement(childName);
                    platesNode.AppendChild(slabNode);

                    //group
                    XmlAttribute iAttr = xmlDoc.CreateAttribute("index");
                    iAttr.Value = group;
                    slabNode.Attributes.Append(iAttr);

                    //thickness
                    XmlAttribute tAttr = xmlDoc.CreateAttribute("thickness");
                    tAttr.Value = thickness.ToString();
                    slabNode.Attributes.Append(tAttr);

                    //offset
                    XmlAttribute oAttr = xmlDoc.CreateAttribute("offset");
                    if (Math.Abs(offset) < samePosToll)
                    {
                        oAttr.Value = "0";
                    }
                    else if (offset > 0)
                    {
                        oAttr.Value = "1";
                    }
                    else
                    {
                        oAttr.Value = "-1";
                    }
                    slabNode.Attributes.Append(oAttr);

                    //fill perimeter
                    XmlNode fillPointsNode = xmlDoc.CreateElement("perimeter");
                    slabNode.AppendChild(fillPointsNode);
                    foreach (Maffeis.Geometry.Point2d p in perimeterPoly)
                    {
                        Maffeis.Geometry.Point3d globalP = trans.ToGlobal(p);
                        XmlNode point = xmlDoc.CreateElement("point");
                        fillPointsNode.AppendChild(point);
                        XmlAttribute xyz = xmlDoc.CreateAttribute("xyz");
                        xyz.Value = string.Format("{0:" + coordAccuracy + "},{1:" + coordAccuracy + "},{2:" + coordAccuracy + "}", globalP.X, globalP.Y, globalP.Z);
                        point.Attributes.Append(xyz);
                    }

                    //holes perimeter
                    if (holePolysCanBeNull != null)
                    {
                        int index;
                        index = 0;
                        foreach (Maffeis.Geometry.Polygon2d hole in holePolysCanBeNull)
                        {
                            XmlNode holesPointsNode = xmlDoc.CreateElement("hole");
                            slabNode.AppendChild(holesPointsNode);
                            index++;
                            XmlAttribute indexAttr = xmlDoc.CreateAttribute("index");
                            indexAttr.Value = index.ToString();
                            holesPointsNode.Attributes.Append(indexAttr);
                            foreach (Maffeis.Geometry.Point2d p in hole)
                            {
                                Maffeis.Geometry.Point3d globalP = trans.ToGlobal(p);
                                XmlNode point = xmlDoc.CreateElement("point");
                                holesPointsNode.AppendChild(point);
                                XmlAttribute xyz = xmlDoc.CreateAttribute("xyz");
                                xyz.Value = string.Format("{0:" + coordAccuracy + "},{1:" + coordAccuracy + "},{2:" + coordAccuracy + "}", globalP.X, globalP.Y, globalP.Z);
                                point.Attributes.Append(xyz);
                            }
                        }
                    }
                }
            }

            return xmlDoc;
        }

        private Point2d PolygonCentroid(Point2d[] points)
        {
            return PolygonCentroid(points, out double area);
        }

        private Point2d PolygonCentroid(Point2d[] points, out double area)
        {
            var centroid = new Point2d(0, 0);
            area = 0;

            if (PointsDistance(points[0], points.Last()) > 0.001)
            {
                Array.Resize(ref points, points.Length + 1);
                points[points.Length - 1] = new Point2d(points[0].X, points[0].Y);
            }

            for (int i = 0; i < points.Length - 1; i++)
            {
                double a = points[i].X * points[i + 1].Y - points[i + 1].X * points[i].Y;
                area += a;
                centroid.X += (points[i].X + points[i + 1].X) * a;
                centroid.Y += (points[i].Y + points[i + 1].Y) * a;
            }

            area *= 0.5;
            centroid.X /= 6.0 * area;
            centroid.Y /= 6.0 * area;

            return centroid;
        }

        private double PointsDistance(Point2d pt1, Point2d pt2)
        {
            return Math.Sqrt(Math.Pow(pt2.X - pt1.X, 2) + Math.Pow(pt2.Y - pt1.Y, 2));
        }

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

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid => new("113cb500-0ec2-48ec-9da2-0689b1263942");
    }
}
303 files24 directories