public static void Import(FullModelData fmd, string path)
        {
            AnimationFile animationFile = new AnimationFile();

            animationFile.Read(path);

            foreach (AnimationFileObject animationObject in animationFile.Objects)
            {
                Object3D object3D = fmd.GetObject3DByHash(new HashName(animationObject.Name));

                Log.Default.Info("Trying to add animation to " + animationObject.Name);
                if (object3D != null)
                {
                    Log.Default.Info("Found " + animationObject.Name);

                    object3D.Animations.Clear(); // Kill the old anims.

                    if (animationObject.RotationKeyframes.Count > 0)
                    {
                        QuatLinearRotationController quatLinearRotationController = AddRotations(object3D, animationObject.RotationKeyframes);
                        fmd.AddSection(quatLinearRotationController);
                    }

                    if (animationObject.PositionKeyframes.Count > 0)
                    {
                        LinearVector3Controller linearVector3Controller = AddPositions(object3D, animationObject.PositionKeyframes);
                        fmd.AddSection(linearVector3Controller);
                    }
                }
                else
                {
                    Log.Default.Info("Not Found " + animationObject.Name);
                }
            }
        }
        public static void ExportFile(FullModelData data, string path)
        {
            //you remove items from the parsed_sections
            //you edit items in the parsed_sections, they will get read and exported

            //Sort the sections
            List <Animation> animation_sections = new List <Animation>();
            List <Author>    author_sections    = new List <Author>();
            List <ISection>  material_sections  = new List <ISection>();
            List <Object3D>  object3D_sections  = new List <Object3D>();
            List <Model>     model_sections     = new List <Model>();
            List <ISection>  other_sections     = new List <ISection>();

            // Discard the old hashlist
            // Note that we use ToArray, which allows us to mutate the list without breaking anything
            foreach (SectionHeader header in data.sections.ToArray())
            {
                if (header.type == Tags.custom_hashlist_tag)
                {
                    data.RemoveSection(header.id);
                }
            }

            CustomHashlist hashlist = new CustomHashlist();

            data.AddSection(hashlist);

            foreach (SectionHeader sectionheader in data.sections)
            {
                if (!data.parsed_sections.Keys.Contains(sectionheader.id))
                {
                    Log.Default.Warn($"BUG: SectionHeader with id {sectionheader.id} has no counterpart in parsed_sections");
                    continue;
                }

                var section = data.parsed_sections[sectionheader.id];

                if (section is Animation)
                {
                    animation_sections.Add(section as Animation);
                }
                else if (section is Author)
                {
                    author_sections.Add(section as Author);
                }
                else if (section is MaterialGroup)
                {
                    foreach (var matsec in (section as MaterialGroup).Items)
                    {
                        if (!data.parsed_sections.ContainsKey(matsec.SectionId))
                        {
                            throw new Exception($"BUG: In MaterialGroup {section.SectionId}, Material {matsec.SectionId} isn't registered as part of the .model we're saving");
                        }
                        if (!material_sections.Contains(matsec))
                        {
                            material_sections.Add(matsec);
                        }
                    }
                    material_sections.Add(section);
                }
                else if (section is Model) // Has to be before Object3D, since it's a subclass.
                {
                    model_sections.Add(section as Model);
                }
                else if (section is Object3D)
                {
                    object3D_sections.Add(section as Object3D);
                }
                else if (section != null)
                {
                    other_sections.Add(section as ISection);
                }
                else
                {
                    Log.Default.Warn("BUG: Somehow a null or non-section found its way into the list of sections.");
                }

                if (section is IHashContainer container)
                {
                    container.CollectHashes(hashlist);
                }
            }

            var sections_to_write = Enumerable.Empty <ISection>()
                                    .Concat(animation_sections)
                                    .Concat(author_sections)
                                    .Concat(material_sections)
                                    .Concat(object3D_sections)
                                    .Concat(model_sections)
                                    .Concat(other_sections)
                                    .OrderedDistinct()
                                    .ToList();

            //after each section, you go back and enter it's new size
            using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
            {
                using (BinaryWriter bw = new BinaryWriter(fs))
                {
                    bw.Write(-1);           //the - (yyyy)
                    bw.Write((UInt32)100);  //Filesize (GO BACK AT END AND CHANGE!!!)
                    int sectionCount = data.sections.Count;
                    bw.Write(sectionCount); //Sections count

                    foreach (var sec in sections_to_write)
                    {
                        sec.StreamWrite(bw);
                    }

                    if (sections_to_write.Count != sectionCount)
                    {
                        Log.Default.Warn($"BUG : There were {sectionCount} sections to write but {sections_to_write.Count} were written");
                    }

                    if (data.leftover_data != null)
                    {
                        bw.Write(data.leftover_data);
                    }

                    fs.Position = 4;
                    bw.Write((UInt32)fs.Length);
                }
            }
        }
        public static void ImportNewObj(FullModelData fmd, String filepath, bool addNew, Func <string, Object3D> root_point, Importers.IOptionReceiver _)
        {
            Log.Default.Info("Importing new obj with file: {0}", filepath);

            //Preload the .obj
            List <obj_data> objects      = new List <obj_data>();
            List <obj_data> toAddObjects = new List <obj_data>();

            using (FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.Read))
            {
                using (StreamReader sr = new StreamReader(fs))
                {
                    string   line;
                    obj_data obj                 = new obj_data();
                    bool     reading_faces       = false;
                    int      prevMaxVerts        = 0;
                    int      prevMaxUvs          = 0;
                    int      prevMaxNorms        = 0;
                    string   current_shade_group = null;

                    while ((line = sr.ReadLine()) != null)
                    {
                        //preloading objects
                        if (line.StartsWith("#"))
                        {
                            continue;
                        }
                        else if (line.StartsWith("o ") || line.StartsWith("g "))
                        {
                            if (reading_faces)
                            {
                                reading_faces = false;
                                prevMaxVerts += obj.verts.Count;
                                prevMaxUvs   += obj.uv.Count;
                                prevMaxNorms += obj.normals.Count;

                                objects.Add(obj);
                                obj = new obj_data();
                                current_shade_group = null;
                            }

                            if (String.IsNullOrEmpty(obj.object_name))
                            {
                                obj.object_name = line.Substring(2);
                                Log.Default.Debug("Object {0} named: {1}", objects.Count + 1, obj.object_name);
                            }
                        }
                        else if (line.StartsWith("usemtl "))
                        {
                            obj.material_name = line.Substring(7);
                        }
                        else if (line.StartsWith("v "))
                        {
                            if (reading_faces)
                            {
                                reading_faces = false;
                                prevMaxVerts += obj.verts.Count;
                                prevMaxUvs   += obj.uv.Count;
                                prevMaxNorms += obj.normals.Count;

                                objects.Add(obj);
                                obj = new obj_data();
                            }

                            String[] verts = line.Replace("  ", " ").Split(' ');
                            Vector3  vert  = new Vector3();
                            vert.X = Convert.ToSingle(verts[1], CultureInfo.InvariantCulture);
                            vert.Y = Convert.ToSingle(verts[2], CultureInfo.InvariantCulture);
                            vert.Z = Convert.ToSingle(verts[3], CultureInfo.InvariantCulture);

                            obj.verts.Add(vert);
                        }
                        else if (line.StartsWith("vt "))
                        {
                            if (reading_faces)
                            {
                                reading_faces = false;
                                prevMaxVerts += obj.verts.Count;
                                prevMaxUvs   += obj.uv.Count;
                                prevMaxNorms += obj.normals.Count;

                                objects.Add(obj);
                                obj = new obj_data();
                            }

                            String[] uvs = line.Split(' ');
                            Vector2  uv  = new Vector2();
                            uv.X = Convert.ToSingle(uvs[1], CultureInfo.InvariantCulture);
                            uv.Y = Convert.ToSingle(uvs[2], CultureInfo.InvariantCulture);

                            obj.uv.Add(uv);
                        }
                        else if (line.StartsWith("vn "))
                        {
                            if (reading_faces)
                            {
                                reading_faces = false;
                                prevMaxVerts += obj.verts.Count;
                                prevMaxUvs   += obj.uv.Count;
                                prevMaxNorms += obj.normals.Count;

                                objects.Add(obj);
                                obj = new obj_data();
                            }

                            String[] norms = line.Split(' ');
                            Vector3  norm  = new Vector3();
                            norm.X = Convert.ToSingle(norms[1], CultureInfo.InvariantCulture);
                            norm.Y = Convert.ToSingle(norms[2], CultureInfo.InvariantCulture);
                            norm.Z = Convert.ToSingle(norms[3], CultureInfo.InvariantCulture);

                            obj.normals.Add(norm);
                        }
                        else if (line.StartsWith("s "))
                        {
                            current_shade_group = line.Substring(2);
                        }
                        else if (line.StartsWith("f "))
                        {
                            reading_faces = true;

                            if (current_shade_group != null)
                            {
                                if (obj.shading_groups.ContainsKey(current_shade_group))
                                {
                                    obj.shading_groups[current_shade_group].Add(obj.faces.Count);
                                }
                                else
                                {
                                    List <int> newfaces = new List <int>();
                                    newfaces.Add(obj.faces.Count);
                                    obj.shading_groups.Add(current_shade_group, newfaces);
                                }
                            }

                            String[] faces = line.Substring(2).Split(' ');
                            for (int x = 0; x < 3; x++)
                            {
                                ushort fa = 0, fb = 0, fc = 0;
                                if (obj.verts.Count > 0)
                                {
                                    fa = (ushort)(Convert.ToUInt16(faces[x].Split('/')[0]) - prevMaxVerts - 1);
                                }
                                if (obj.uv.Count > 0)
                                {
                                    fb = (ushort)(Convert.ToUInt16(faces[x].Split('/')[1]) - prevMaxUvs - 1);
                                }
                                if (obj.normals.Count > 0)
                                {
                                    fc = (ushort)(Convert.ToUInt16(faces[x].Split('/')[2]) - prevMaxNorms - 1);
                                }
                                if (fa < 0 || fb < 0 || fc < 0)
                                {
                                    throw new Exception("What the actual flapjack, something is *VERY* wrong");
                                }
                                obj.faces.Add(new Face(fa, fb, fc));
                            }
                        }
                    }

                    if (!objects.Contains(obj))
                    {
                        objects.Add(obj);
                    }
                }
            }


            //Read each object
            foreach (obj_data obj in objects)
            {
                //One would fix Tatsuto's broken shading here.

                //Locate the proper model
                var   hashname     = HashName.FromNumberOrString(obj.object_name);
                Model modelSection = fmd.parsed_sections
                                     .Where(i => i.Value is Model mod && hashname.Hash == mod.HashName.Hash)
                                     .Select(i => i.Value as Model)
                                     .FirstOrDefault();

                //Apply new changes
                if (modelSection == null)
                {
                    toAddObjects.Add(obj);
                    continue;
                }

                PassthroughGP passthrough_section = modelSection.PassthroughGP;
                Geometry      geometry_section    = passthrough_section.Geometry;
                Topology      topology_section    = passthrough_section.Topology;

                AddObject(false, obj,
                          modelSection, passthrough_section,
                          geometry_section, topology_section);
            }


            //Add new objects
            if (addNew)
            {
                foreach (obj_data obj in toAddObjects)
                {
                    //create new Model
                    Material newMat = new Material(obj.material_name);
                    fmd.AddSection(newMat);
                    MaterialGroup newMatG = new MaterialGroup(newMat);
                    fmd.AddSection(newMatG);
                    Geometry newGeom = new Geometry(obj);
                    fmd.AddSection(newGeom);
                    Topology newTopo = new Topology(obj);
                    fmd.AddSection(newTopo);

                    PassthroughGP newPassGP = new PassthroughGP(newGeom, newTopo);
                    fmd.AddSection(newPassGP);
                    TopologyIP newTopoIP = new TopologyIP(newTopo);
                    fmd.AddSection(newTopoIP);

                    Object3D parent   = root_point.Invoke(obj.object_name);
                    Model    newModel = new Model(obj, newPassGP, newTopoIP, newMatG, parent);
                    fmd.AddSection(newModel);

                    AddObject(true, obj,
                              newModel, newPassGP, newGeom, newTopo);

                    //Add new sections
                }
            }
        }