[
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*[.json, .xml, .info]\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/"
  },
  {
    "path": "README.md",
    "content": "# Revit2glTF - A Revit glTF Exporter\nThis is currently a work in progress but the end goal is to create an open source implementation of an extensible exporter from Autodesk Revit to the glTF model format.\n\n## Current To-Do's\n- [x] Handle basic material export\n- [ ] Handle textured material export\n- [ ] Handle normals export\n- [ ] Add toggle for exporting each element as a seperate .bin vs a single .glb.\n- [x] Add element properties to extras on glTF nodes.\n- [ ] Add element properties to a sqlite file referenced by glTF nodes.\n"
  },
  {
    "path": "glTFRevitExport/App.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.IO;\nusing System.Reflection;\nusing System.Windows.Media;\nusing System.Windows.Media.Imaging;\nusing Autodesk.Revit.ApplicationServices;\nusing Autodesk.Revit.Attributes;\nusing Autodesk.Revit.DB;\nusing Autodesk.Revit.UI;\nusing Autodesk.Revit.UI.Selection;\n\nnamespace glTFRevitExport\n{\n    class App : IExternalApplication\n    {\n        public Result OnStartup(UIControlledApplication a)\n        {\n            string tabName = \"Andersen Tools\";\n            try\n            {\n                a.CreateRibbonTab(tabName);\n            }\n            catch (Autodesk.Revit.Exceptions.ArgumentException)\n            {\n                // Do nothing.\n            }\n            // Add a new ribbon panel\n            RibbonPanel newPanel = a.CreateRibbonPanel(tabName, \"glTF Export\");\n            string thisAssemblyPath = Assembly.GetExecutingAssembly().Location;\n\n            PushButtonData button1Data = new PushButtonData(\"command\",\n                \"GO\", thisAssemblyPath, \"glTFRevitExport.Command\");\n            PushButton pushButton1 = newPanel.AddItem(button1Data) as PushButton;\n            pushButton1.LargeImage = BmpImageSource(@\"glTFRevitExport.Embedded_Media.large.png\");\n\n            /////////////////////   ADD STACKED BUTTONS   ////////////////////////////////////////////\n            //PushButtonData tagSData = new PushButtonData(\"sButton1\",\n            //    \"Button1\", thisAssemblyPath, \"glTFRevitExport.class\");\n            //PushButtonData tagLData = new PushButtonData(\"sButton2\",\n            //    \"Button2\", thisAssemblyPath, \"glTFRevitExport.class\");\n\n            //newPanel.AddSeparator();\n\n            //IList<RibbonItem> stackedItems = newPanel.AddStackedItems(tagSData, tagLData);\n            //if(stackedItems.Count>1)\n            //{\n            //    PushButton tagS = stackedItems[0] as PushButton;\n            //    tagS.Image = BmpImageSource(@\"glTFRevitExport.Embedded_Media.small.png\");\n            //    PushButton tagL = stackedItems[1] as PushButton;\n            //    tagL.Image = BmpImageSource(@\"glTFRevitExport.Embedded_Media.small.png\");\n            //}\n            ///////////////////////////////////////////////////////////////////////////////////////////\n\n\n            return Result.Succeeded;\n        }\n\n        public Result OnShutdown(UIControlledApplication a)\n        {\n            return Result.Succeeded;\n        }\n\n        private ImageSource BmpImageSource(string embeddedPath)\n        {\n            System.IO.Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedPath);\n            PngBitmapDecoder pngBitmapDecoder = new PngBitmapDecoder(manifestResourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default);\n            return pngBitmapDecoder.Frames[0];\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/Command.cs",
    "content": "﻿using System;\nusing System.IO;\nusing Autodesk.Revit.ApplicationServices;\nusing Autodesk.Revit.Attributes;\nusing Autodesk.Revit.DB;\nusing Autodesk.Revit.UI;\nusing Microsoft.Win32;\n\nnamespace glTFRevitExport\n{\n    [Transaction(TransactionMode.Manual)]\n    class Command : IExternalCommand\n    {\n        public void ExportView3D(View3D view3d, string filename, string directory)\n        {\n            Document doc = view3d.Document;\n\n            // Use our custom implementation of IExportContext as the exporter context.\n            glTFExportContext ctx = new glTFExportContext(doc, filename, directory);\n            // Create a new custom exporter with the context.\n            CustomExporter exporter = new CustomExporter(doc, ctx);\n\n            exporter.ShouldStopOnError = true;\n            exporter.Export(view3d);\n        }\n\n        public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)\n        {\n            UIApplication uiapp = commandData.Application;\n            UIDocument uidoc = uiapp.ActiveUIDocument;\n            Application app = uiapp.Application;\n            Document doc = uidoc.Document;\n\n            View3D view = doc.ActiveView as View3D;\n            if (view == null)\n            {\n                TaskDialog.Show(\"glTFRevitExport\", \"You must be in a 3D view to export.\");\n                return Result.Failed;\n            }\n\n            SaveFileDialog fileDialog = new SaveFileDialog();\n            fileDialog.FileName = \"NewProject\"; // default file name\n            fileDialog.DefaultExt = \".gltf\"; // default file extension\n\n            bool? dialogResult = fileDialog.ShowDialog();\n            if (dialogResult == true)\n            {\n                string filename = fileDialog.FileName;\n                string directory = Path.GetDirectoryName(filename) + \"\\\\\";\n\n                ExportView3D(view, filename, directory);\n            }\n\n            return Result.Succeeded;\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/Containers.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing Autodesk.Revit.DB;\n\nnamespace glTFRevitExport\n{\n    /// <summary>\n    /// Intermediate data format for \n    /// converting between Revit Polymesh\n    /// and glTF buffers.\n    /// </summary>\n    public class GeometryData\n    {\n        public VertexLookupInt vertDictionary = new VertexLookupInt();\n        public List<long> vertices = new List<long>();\n        public List<double> normals = new List<double>();\n        public List<double> uvs = new List<double>();\n        public List<int> faces = new List<int>();\n    }\n\n    /// <summary>\n    /// Container for holding a strict set of items\n    /// that is also addressable by a unique ID.\n    /// </summary>\n    /// <typeparam name=\"T\">The type of item contained.</typeparam>\n    public class IndexedDictionary<T>\n    {\n        private Dictionary<string, int> _dict = new Dictionary<string, int>();\n        public List<T> List { get; } = new List<T>();\n        public string CurrentKey { get; private set; }\n        public Dictionary<string,T> Dict\n        {\n            get\n            {\n                var output = new Dictionary<string, T>();\n                foreach (var kvp in _dict)\n                {\n                    output.Add(kvp.Key, List[kvp.Value]);\n                }\n                return output;\n            }\n        }\n\n        /// <summary>\n        /// The most recently accessed item (not effected by GetElement()).\n        /// </summary>\n        public T CurrentItem\n        {\n            get { return List[_dict[CurrentKey]]; }\n        }\n\n        /// <summary>\n        /// The index of the most recently accessed item (not effected by GetElement()).\n        /// </summary>\n        public int CurrentIndex\n        {\n            get { return _dict[CurrentKey]; }\n        }\n\n        /// <summary>\n        /// Add a new item to the list, if it already exists then the \n        /// current item will be set to this item.\n        /// </summary>\n        /// <param name=\"uuid\">Unique identifier for the item.</param>\n        /// <param name=\"elem\">The item to add.</param>\n        /// <returns>true if item did not already exist.</returns>\n        public bool AddOrUpdateCurrent(string uuid, T elem)\n        {\n            if (!_dict.ContainsKey(uuid))\n            {\n                List.Add(elem);\n                _dict.Add(uuid, (List.Count - 1));\n                CurrentKey = uuid;\n                return true;\n            }\n\n            CurrentKey = uuid;\n            return false;\n        }\n\n        /// <summary>\n        /// Check if the container already has an item with this key.\n        /// </summary>\n        /// <param name=\"uuid\">Unique identifier for the item.</param>\n        /// <returns></returns>\n        public bool Contains(string uuid)\n        {\n            return _dict.ContainsKey(uuid);\n        }\n\n        /// <summary>\n        /// Returns the index for an item given it's unique identifier.\n        /// </summary>\n        /// <param name=\"uuid\">Unique identifier for the item.</param>\n        /// <returns>index of item or -1</returns>\n        public int GetIndexFromUUID(string uuid)\n        {\n            if (!Contains(uuid)) throw new Exception(\"Specified item could not be found.\");\n            return _dict[uuid];\n        }\n\n        /// <summary>\n        /// Returns an item given it's unique identifier.\n        /// </summary>\n        /// <param name=\"uuid\">Unique identifier for the item</param>\n        /// <returns>the item</returns>\n        public T GetElement(string uuid)\n        {\n            int index = GetIndexFromUUID(uuid);\n            return List[index];\n        }\n\n        /// <summary>\n        /// Returns as item given it's index location.\n        /// </summary>\n        /// <param name=\"index\">The item's index location.</param>\n        /// <returns>the item</returns>\n        public T GetElement(int index)\n        {\n            if (index < 0 || index > List.Count - 1) throw new Exception(\"Specified item could not be found.\");\n            return List[index];\n        }\n    }\n\n    /// <summary>\n    /// From Jeremy Tammik's RvtVa3c exporter:\n    /// https://github.com/va3c/RvtVa3c\n    /// An integer-based 3D point class.\n    /// </summary>\n    public class PointInt : IComparable<PointInt>\n    {\n        public long X { get; set; }\n        public long Y { get; set; }\n        public long Z { get; set; }\n\n        /// <summary>\n        /// Consider a Revit length zero \n        /// if is smaller than this.\n        /// </summary>\n        const double _eps = 1.0e-9;\n\n        /// <summary>\n        /// Conversion factor from feet to millimetres.\n        /// </summary>\n        const double _feet_to_mm = 25.4 * 12;\n\n        /// <summary>\n        /// Conversion a given length value \n        /// from feet to millimetre.\n        /// </summary>\n        public static long ConvertFeetToMillimetres(double d)\n        {\n            if (0 < d)\n            {\n                return _eps > d\n                  ? 0\n                  : (long)(_feet_to_mm * d + 0.5);\n            }\n            else\n            {\n                return _eps > -d\n                  ? 0\n                  : (long)(_feet_to_mm * d - 0.5);\n            }\n        }\n\n        public PointInt(XYZ p, bool switch_coordinates)\n        {\n            X = ConvertFeetToMillimetres(p.X);\n            Y = ConvertFeetToMillimetres(p.Y);\n            Z = ConvertFeetToMillimetres(p.Z);\n\n            if (switch_coordinates)\n            {\n                X = -X;\n                long tmp = Y;\n                Y = Z;\n                Z = tmp;\n            }\n        }\n\n        public int CompareTo(PointInt a)\n        {\n            long d = X - a.X;\n            if (0 == d)\n            {\n                d = Y - a.Y;\n                if (0 == d)\n                {\n                    d = Z - a.Z;\n                }\n            }\n            return (0 == d) ? 0 : ((0 < d) ? 1 : -1);\n        }\n    }\n\n    /// <summary>\n    /// From Jeremy Tammik's RvtVa3c exporter:\n    /// https://github.com/va3c/RvtVa3c\n    /// A vertex lookup class to eliminate \n    /// duplicate vertex definitions.\n    /// </summary>\n    public class VertexLookupInt : Dictionary<PointInt, int>\n    {\n        /// <summary>\n        /// Define equality for integer-based PointInt.\n        /// </summary>\n        class PointIntEqualityComparer : IEqualityComparer<PointInt>\n        {\n            public bool Equals(PointInt p, PointInt q)\n            {\n                return 0 == p.CompareTo(q);\n            }\n\n            public int GetHashCode(PointInt p)\n            {\n                return (p.X.ToString()\n                  + \",\" + p.Y.ToString()\n                  + \",\" + p.Z.ToString())\n                  .GetHashCode();\n            }\n        }\n\n        public VertexLookupInt() : base(new PointIntEqualityComparer())\n        {\n        }\n\n        /// <summary>\n        /// Return the index of the given vertex,\n        /// adding a new entry if required.\n        /// </summary>\n        public int AddVertex(PointInt p)\n        {\n            return ContainsKey(p)\n              ? this[p]\n              : this[p] = Count;\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/GLTFManager.cs",
    "content": "﻿using Autodesk.Revit.DB;\nusing System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Security.Cryptography;\nusing System.Runtime.Serialization.Formatters.Binary;\nusing System.IO;\nusing System.Diagnostics;\n\nnamespace glTFRevitExport\n{\n    static class ManagerUtils\n    {\n        static public List<double> ConvertXForm(Transform xform)\n        {\n            if (xform == null || xform.IsIdentity) return null;\n\n            var BasisX = xform.BasisX;\n            var BasisY = xform.BasisY;\n            var BasisZ = xform.BasisZ;\n            var Origin = xform.Origin;\n            var OriginX = PointInt.ConvertFeetToMillimetres(Origin.X);\n            var OriginY = PointInt.ConvertFeetToMillimetres(Origin.Y);\n            var OriginZ = PointInt.ConvertFeetToMillimetres(Origin.Z);\n\n            List<double> glXform = new List<double>(16) {\n                BasisX.X, BasisX.Y, BasisX.Z, 0,\n                BasisY.X, BasisY.Y, BasisY.Z, 0,\n                BasisZ.X, BasisZ.Y, BasisZ.Z, 0,\n                OriginX, OriginY, OriginZ, 1\n            };\n\n            return glXform;\n        }\n\n        public class HashSearch\n        {\n            string _S;\n            public HashSearch(string s)\n            {\n                _S = s;\n            }\n            public bool EqualTo(HashedType d)\n            {\n                return d.hashcode.Equals(_S);\n            }\n        }\n\n        static public string GenerateSHA256Hash<T>(T data)\n        {\n            var binFormatter = new BinaryFormatter();\n            var mStream = new MemoryStream();\n            binFormatter.Serialize(mStream, data);\n\n            using (SHA256 hasher = SHA256.Create())\n            {\n                mStream.Position = 0;\n                byte[] byteHash = hasher.ComputeHash(mStream);\n\n                var sBuilder = new StringBuilder();\n                for (int i = 0; i < byteHash.Length; i++)\n                {\n                    sBuilder.Append(byteHash[i].ToString(\"x2\"));\n                }\n\n                return sBuilder.ToString();\n            }\n        }\n    }\n\n    class GLTFManager\n    {\n\n        /// <summary>\n        /// Flag to write coords as Z up instead of Y up (if true).\n        /// CAUTION: With local coordinate systems and transforms, this no longer\n        /// produces expected results. TODO on fixing it, however there is a larger\n        /// philisophical debtate to be had over whether flipping coordinates in\n        /// source CAD applications should EVER be the correct thing to do (as opposed to\n        /// flipping the camera in the viewer).\n        /// </summary>\n        private bool _flipCoords = false;\n        /// <summary>\n        /// Toggles the export of JSON properties as a glTF Extras\n        /// object on each node.\n        /// </summary>\n        private bool _exportProperties = true;\n\n        /// <summary>\n        /// Stateful, uuid indexable list of all materials in the export.\n        /// </summary>\n        private IndexedDictionary<glTFMaterial> materialDict = new IndexedDictionary<glTFMaterial>();\n        /// <summary>\n        /// Dictionary of nodes keyed to their unique id.\n        /// </summary>\n        private Dictionary<string, Node> nodeDict = new Dictionary<string, Node>();\n        /// <summary>\n        /// Hashable container for mesh data, to aid instancing.\n        /// </summary>\n        private List<MeshContainer> meshContainers = new List<MeshContainer>();\n\n        /// <summary>\n        /// List of root nodes defining scenes.\n        /// </summary>\n        public List<glTFScene> scenes = new List<glTFScene>();\n        /// <summary>\n        /// List of all buffers referencing the binary file data.\n        /// </summary>\n        public List<glTFBuffer> buffers = new List<glTFBuffer>();\n        /// <summary>\n        /// List of all BufferViews referencing the buffers.\n        /// </summary>\n        public List<glTFBufferView> bufferViews = new List<glTFBufferView>();\n        /// <summary>\n        /// List of all Accessors referencing the BufferViews.\n        /// </summary>\n        public List<glTFAccessor> accessors = new List<glTFAccessor>();\n        /// <summary>\n        /// Container for the vertex/face/normal information\n        /// that will be serialized into a binary format\n        /// for the final *.bin files.\n        /// </summary>\n        public List<glTFBinaryData> binaryFileData = new List<glTFBinaryData>();\n\n        /// <summary>\n        /// Ordered list of all nodes\n        /// </summary>\n        public List<glTFNode> nodes {\n            get {\n                var list = nodeDict.Values.ToList();\n                return list.OrderBy(x => x.index).Select(x => x.ToGLTFNode()).ToList();\n            }\n        }\n\n        /// <summary>\n        /// Returns true if the unique id is already present in the list of nodes.\n        /// </summary>\n        /// <param name=\"uniqueId\"></param>\n        /// <returns></returns>\n        public bool containsNode(string uniqueId)\n        {\n            return nodeDict.ContainsKey(uniqueId);\n        }\n\n        /// <summary>\n        /// List of all materials referenced by meshes.\n        /// </summary>\n        public List<glTFMaterial> materials {\n            get {\n                return materialDict.List;\n            }\n        }\n\n        /// <summary>\n        /// List of all meshes referenced by nodes.\n        /// </summary>\n        public List<glTFMesh> meshes {\n            get {\n                return meshContainers.Select(x => x.contents).ToList();\n            }\n        }\n\n        /// <summary>\n        /// Stack maintaining the uniqueId's of each node down\n        /// the current scene graph branch.\n        /// </summary>\n        private Stack<string> parentStack = new Stack<string>();\n        /// <summary>\n        /// The uniqueId of the currently open node.\n        /// </summary>\n        private string currentNodeId {\n            get {\n                return parentStack.Peek();\n            }\n        }\n\n        /// <summary>\n        /// Stack maintaining the geometry containers for each\n        /// node down the current scene graph branch. These are popped\n        /// as we retreat back up the graph.\n        /// </summary>\n        private Stack<Dictionary<string, GeometryData>> geometryStack = new Stack<Dictionary<string, GeometryData>>();\n        /// <summary>\n        /// The geometry container for the currently open node.\n        /// </summary>\n        private Dictionary<string, GeometryData> currentGeom {\n            get {\n                return geometryStack.Peek();\n            }\n        }\n\n        /// <summary>\n        /// Returns proper tab alignment for displaying element\n        /// hierarchy in debug printing.\n        /// </summary>\n        public string formatDebugHeirarchy\n        {\n            get\n            {\n                string spaces = \"\";\n                for (int i = 0; i < parentStack.Count; i++)\n                {\n                    spaces += \"  \";\n                }\n                return spaces;\n            }\n        }\n\n        public void Start(bool exportProperties = true)\n        {\n            this._exportProperties = exportProperties;\n\n            Node rootNode = new Node(0);\n            rootNode.children = new List<int>();\n            nodeDict.Add(rootNode.id, rootNode);\n            parentStack.Push(rootNode.id);\n\n            glTFScene defaultScene = new glTFScene();\n            defaultScene.nodes.Add(0);\n            scenes.Add(defaultScene);\n        }\n\n        public glTFContainer Finish()\n        {\n            glTF model = new glTF();\n            model.asset = new glTFVersion();\n            model.scenes = scenes;\n            model.nodes = nodes;\n            model.meshes = meshes;\n            model.materials = materials;\n            model.buffers = buffers;\n            model.bufferViews = bufferViews;\n            model.accessors = accessors;\n\n            glTFContainer container = new glTFContainer();\n            container.glTF = model;\n            container.binaries = binaryFileData;\n\n            return container;\n        }\n\n        public void OpenNode(Element elem, Transform xform = null, bool isInstance = false)\n        {\n            //// TODO: [RM] Commented out because this is likely to be very buggy and not the \n            //// correct solution intent is to prevent creation of new nodes when a symbol \n            //// is a child of an instance of the same type.\n            //// Witness: parking spaces and stair railings for examples of two\n            //// different issues with the behavior\n            //if (isInstance == true && elem is FamilySymbol)\n            //{\n            //    FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance;\n            //    if (\n            //        parentInstance != null &&\n            //        parentInstance.Symbol != null &&\n            //        elem.Name == parentInstance.Symbol.Name\n            //    )\n            //    {\n            //        nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform);\n            //        return;\n            //    }\n\n            //    //nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform);\n            //    //return;\n            //}\n            bool exportNodeProperties = _exportProperties;\n            if (isInstance == true && elem is FamilySymbol) exportNodeProperties = false;\n\n            Node node = new Node(elem, nodeDict.Count, exportNodeProperties, isInstance, formatDebugHeirarchy);\n\n            if (parentStack.Count > 0)\n            {\n                string parentId = parentStack.Peek();\n                Node parentNode = nodeDict[parentId];\n                if (parentNode.children == null) parentNode.children = new List<int>();\n                nodeDict[parentId].children.Add(node.index);\n            }\n\n            parentStack.Push(node.id);\n            if (xform != null)\n            {\n                node.matrix = ManagerUtils.ConvertXForm(xform);\n            }\n\n            nodeDict.Add(node.id, node);\n\n            OpenGeometry();\n            Debug.WriteLine(String.Format(\"{0}Node Open\", formatDebugHeirarchy));\n        }\n\n        public void CloseNode(Element elem = null, bool isInstance = false)\n        {\n            //// TODO: [RM] Commented out because this is likely to be very buggy and not the \n            //// correct solution intent is to prevent creation of new nodes when a symbol \n            //// is a child of an instance of the same type.\n            //// Witness: parking spaces and stair railings for examples of two\n            //// different issues with the behavior\n            //if (isInstance && elem is FamilySymbol)\n            //{\n            //    FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance;\n            //    if (\n            //        parentInstance != null &&\n            //        parentInstance.Symbol != null &&\n            //        elem.Name == parentInstance.Symbol.Name\n            //    )\n            //    {\n            //        return;\n            //    }\n            //    //return;\n            //}\n\n            Debug.WriteLine(String.Format(\"{0}Closing Node\", formatDebugHeirarchy));\n\n            if (currentGeom != null)\n            {\n                CloseGeometry();\n            }\n\n            Debug.WriteLine(String.Format(\"{0}  Node Closed\", formatDebugHeirarchy));\n            parentStack.Pop();\n        }\n\n        public void SwitchMaterial(MaterialNode matNode, string name = null, string id = null)\n        {\n            glTFMaterial gl_mat = new glTFMaterial();\n            gl_mat.name = name;\n\n            glTFPBR pbr = new glTFPBR();\n            pbr.baseColorFactor = new List<float>() {\n                matNode.Color.Red / 255f,\n                matNode.Color.Green / 255f,\n                matNode.Color.Blue / 255f,\n                1f - (float)matNode.Transparency\n            };\n            pbr.metallicFactor = 0f;\n            pbr.roughnessFactor = 1f;\n            gl_mat.pbrMetallicRoughness = pbr;\n\n            materialDict.AddOrUpdateCurrent(id, gl_mat);\n        }\n\n        public void OpenGeometry()\n        {\n            geometryStack.Push(new Dictionary<string, GeometryData>());\n        }\n\n        public void OnGeometry(PolymeshTopology polymesh)\n        {\n            if (currentNodeId == null) throw new Exception();\n\n            string vertex_key = currentNodeId + \"_\" + materialDict.CurrentKey;\n            if (currentGeom.ContainsKey(vertex_key) == false)\n            {\n                currentGeom.Add(vertex_key, new GeometryData());\n            }\n\n            // Populate normals from this polymesh\n            IList<XYZ> norms = polymesh.GetNormals();\n            foreach (XYZ norm in norms)\n            {\n                currentGeom[vertex_key].normals.Add(norm.X);\n                currentGeom[vertex_key].normals.Add(norm.Y);\n                currentGeom[vertex_key].normals.Add(norm.Z);\n            }\n\n            // Populate vertex and faces data\n            IList<XYZ> pts = polymesh.GetPoints();\n            foreach (PolymeshFacet facet in polymesh.GetFacets())\n            {\n                int v1 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V1], _flipCoords));\n                int v2 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V2], _flipCoords));\n                int v3 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V3], _flipCoords));\n\n                currentGeom[vertex_key].faces.Add(v1);\n                currentGeom[vertex_key].faces.Add(v2);\n                currentGeom[vertex_key].faces.Add(v3);\n            }\n        }\n\n        public void CloseGeometry()\n        {\n            Debug.WriteLine(String.Format(\"{0}  Closing Geometry\", formatDebugHeirarchy));\n            // Create the new mesh and populate the primitives with GeometryData\n            glTFMesh mesh = new glTFMesh();\n            mesh.primitives = new List<glTFMeshPrimitive>();\n\n            // transfer ordered vertices from vertex dictionary to vertices list\n            foreach (KeyValuePair<string,GeometryData> key_geom in currentGeom)\n            {\n                string key = key_geom.Key;\n                GeometryData geom = key_geom.Value;\n                foreach (KeyValuePair<PointInt, int> point_index in geom.vertDictionary)\n                {\n                    PointInt point = point_index.Key;\n                    geom.vertices.Add(point.X);\n                    geom.vertices.Add(point.Y);\n                    geom.vertices.Add(point.Z);\n                }\n\n                // convert GeometryData objects into glTFMeshPrimitive\n                string material_key = key.Split('_')[1];\n\n                glTFBinaryData bufferMeta = processGeometry(geom, key);\n                if (bufferMeta.hashcode != null)\n                {\n                    binaryFileData.Add(bufferMeta);\n                }\n\n                glTFMeshPrimitive primative = new glTFMeshPrimitive();\n\n                primative.attributes.POSITION = bufferMeta.vertexAccessorIndex;\n                primative.indices = bufferMeta.indexAccessorIndex;\n                primative.material = materialDict.GetIndexFromUUID(material_key);\n                // TODO: Add normal attribute accessor index here\n\n                mesh.primitives.Add(primative);\n            }\n\n            // glTF entity can not be empty\n            if (mesh.primitives.Count() > 0) {\r\n                // Prevent mesh duplication by hash checking\r\n                string meshHash = ManagerUtils.GenerateSHA256Hash(mesh);\r\n                ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(meshHash);\r\n                int idx = meshContainers.FindIndex(hs.EqualTo);\r\n\r\n                if (idx != -1) {\r\n                    // set the current nodes mesh index to the already\r\n                    // created mesh location.\r\n                    nodeDict[currentNodeId].mesh = idx;\r\n                }\r\n                else {\r\n                    // create new mesh and add it's index to the current node.\r\n                    MeshContainer mc = new MeshContainer();\r\n                    mc.hashcode = meshHash;\r\n                    mc.contents = mesh;\r\n                    meshContainers.Add(mc);\r\n                    nodeDict[currentNodeId].mesh = meshContainers.Count - 1;\r\n                }\n\n            }\n\n            geometryStack.Pop();\n            return;\n        }\n\n        /// <summary>\n        /// Takes the intermediate geometry data and performs the calculations\n        /// to convert that into glTF buffers, views, and accessors.\n        /// </summary>\n        /// <param name=\"geomData\"></param>\n        /// <param name=\"name\">Unique name for the .bin file that will be produced.</param>\n        /// <returns></returns>\n        private glTFBinaryData processGeometry(GeometryData geom, string name)\n        {\n            // TODO: rename this type to glTFBufferMeta ?\n            glTFBinaryData bufferData = new glTFBinaryData();\n            glTFBinaryBufferContents bufferContents = new glTFBinaryBufferContents();\n\n            foreach (var coord in geom.vertices)\n            {\n                float vFloat = Convert.ToSingle(coord);\n                bufferContents.vertexBuffer.Add(vFloat);\n            }\n            foreach (var index in geom.faces)\n            {\n                bufferContents.indexBuffer.Add(index);\n            }\n\n            // Prevent buffer duplication by hash checking\n            string calculatedHash = ManagerUtils.GenerateSHA256Hash(bufferContents);\n            ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(calculatedHash);\n            var match = binaryFileData.Find(hs.EqualTo);\n\n            if (match != null)\n            {\n                // return previously created buffer metadata\n                bufferData.vertexAccessorIndex = match.vertexAccessorIndex;\n                bufferData.indexAccessorIndex = match.indexAccessorIndex;\n                return bufferData;\n            }\n            else\n            {\n                // add a buffer\n                glTFBuffer buffer = new glTFBuffer();\n                buffer.uri = name + \".bin\";\n                buffers.Add(buffer);\n                int bufferIdx = buffers.Count - 1;\n\n                /**\n                 * Buffer Data\n                 **/\n                bufferData.name = buffer.uri;\n                bufferData.contents = bufferContents;\n                // TODO: Uncomment for normals\n                //foreach (var normal in geomData.normals)\n                //{\n                //    bufferData.normalBuffer.Add((float)normal);\n                //}\n\n                // Get max and min for vertex data\n                float[] vertexMinMax = Util.GetVec3MinMax(bufferContents.vertexBuffer);\n                // Get max and min for index data\n                int[] faceMinMax = Util.GetScalarMinMax(bufferContents.indexBuffer);\n                // TODO: Uncomment for normals\n                // Get max and min for normal data\n                //float[] normalMinMax = getVec3MinMax(bufferData.normalBuffer);\n\n                /**\n                 * BufferViews\n                 **/\n                // Add a vec3 buffer view\n                int elementsPerVertex = 3;\n                int bytesPerElement = 4;\n                int bytesPerVertex = elementsPerVertex * bytesPerElement;\n                int numVec3 = (geom.vertices.Count) / elementsPerVertex;\n                int sizeOfVec3View = numVec3 * bytesPerVertex;\n                glTFBufferView vec3View = new glTFBufferView();\n                vec3View.buffer = bufferIdx;\n                vec3View.byteOffset = 0;\n                vec3View.byteLength = sizeOfVec3View;\n                vec3View.target = Targets.ARRAY_BUFFER;\n                bufferViews.Add(vec3View);\n                int vec3ViewIdx = bufferViews.Count - 1;\n\n                // TODO: Add a normals (vec3) buffer view\n\n                // Add a faces / indexes buffer view\n                int elementsPerIndex = 1;\n                int bytesPerIndexElement = 4;\n                int bytesPerIndex = elementsPerIndex * bytesPerIndexElement;\n                int numIndexes = geom.faces.Count;\n                int sizeOfIndexView = numIndexes * bytesPerIndex;\n                glTFBufferView facesView = new glTFBufferView();\n                facesView.buffer = bufferIdx;\n                facesView.byteOffset = vec3View.byteLength;\n                facesView.byteLength = sizeOfIndexView;\n                facesView.target = Targets.ELEMENT_ARRAY_BUFFER;\n                bufferViews.Add(facesView);\n                int facesViewIdx = bufferViews.Count - 1;\n\n                buffers[bufferIdx].byteLength = vec3View.byteLength + facesView.byteLength;\n\n                /**\n                 * Accessors\n                 **/\n                // add a position accessor\n                glTFAccessor positionAccessor = new glTFAccessor();\n                positionAccessor.bufferView = vec3ViewIdx;\n                positionAccessor.byteOffset = 0;\n                positionAccessor.componentType = ComponentType.FLOAT;\n                positionAccessor.count = geom.vertices.Count / elementsPerVertex;\n                positionAccessor.type = \"VEC3\";\n                positionAccessor.max = new List<float>() { vertexMinMax[1], vertexMinMax[3], vertexMinMax[5] };\n                positionAccessor.min = new List<float>() { vertexMinMax[0], vertexMinMax[2], vertexMinMax[4] };\n                accessors.Add(positionAccessor);\n                bufferData.vertexAccessorIndex = accessors.Count - 1;\n\n                // TODO: Uncomment for normals\n                // add a normals accessor\n                //glTFAccessor normalsAccessor = new glTFAccessor();\n                //normalsAccessor.bufferView = vec3ViewIdx;\n                //normalsAccessor.byteOffset = (positionAccessor.count) * bytesPerVertex;\n                //normalsAccessor.componentType = ComponentType.FLOAT;\n                //normalsAccessor.count = geom.data.normals.Count / elementsPerVertex;\n                //normalsAccessor.type = \"VEC3\";\n                //normalsAccessor.max = new List<float>() { normalMinMax[1], normalMinMax[3], normalMinMax[5] };\n                //normalsAccessor.min = new List<float>() { normalMinMax[0], normalMinMax[2], normalMinMax[4] };\n                //this.accessors.Add(normalsAccessor);\n                //bufferData.normalsAccessorIndex = this.accessors.Count - 1;\n\n                // add a face accessor\n                glTFAccessor faceAccessor = new glTFAccessor();\n                faceAccessor.bufferView = facesViewIdx;\n                faceAccessor.byteOffset = 0;\n                faceAccessor.componentType = ComponentType.UNSIGNED_INT;\n                faceAccessor.count = numIndexes;\n                faceAccessor.type = \"SCALAR\";\n                faceAccessor.max = new List<float>() { faceMinMax[1] };\n                faceAccessor.min = new List<float>() { faceMinMax[0] };\n                accessors.Add(faceAccessor);\n                bufferData.indexAccessorIndex = accessors.Count - 1;\n\n                bufferData.hashcode = calculatedHash;\n\n                return bufferData;\n            }\n        }\n    }\n\n    class Node : glTFNode\n    {\n        public int index;\n        public string id;\n        public bool isFinalized = false;\n        public Element element;\n\n        public Node(Element elem, int index, bool exportProperties = true, bool isInstance = false, string heirarchyFormat = \"\")\n        {\n            Debug.WriteLine(String.Format(\"{1}  Creating new node: {0}\", elem, heirarchyFormat));\n\n            this.element = elem;\n            this.name = Util.ElementDescription(elem);\n            this.id = isInstance ? elem.UniqueId + \"::\" + Guid.NewGuid().ToString() : elem.UniqueId;\n            this.index = index;\n            Debug.WriteLine(String.Format(\"{1}    Name:{0}\", this.name, heirarchyFormat));\n\n            if (exportProperties)\n            {\n                // get the extras for this element\n                glTFExtras extras = new glTFExtras();\n                extras.UniqueId = elem.UniqueId;\n\n                //var properties = Util.GetElementProperties(elem, true);\n                //if (properties != null) extras.Properties = properties;\n                extras.Properties = Util.GetElementProperties(elem, true);\n                this.extras = extras;\n            }\n            Debug.WriteLine(String.Format(\"{0}    Exported Properties\", heirarchyFormat));\n        }\n        public Node(int index)\n        {\n            this.name = \"::rootNode::\";\n            this.id = System.Guid.NewGuid().ToString();\n            this.index = index;\n        }\n\n        public glTFNode ToGLTFNode()\n        {\n            glTFNode node = new glTFNode();\n            node.name = this.name;\n            node.mesh = this.mesh;\n            node.matrix = this.matrix;\n            node.extras = this.extras;\n            node.children = this.children;\n            return node;\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/GlTFExportContext.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.IO;\nusing Autodesk.Revit.DB;\nusing Newtonsoft.Json;\nusing System.Diagnostics;\nusing System.Runtime.Serialization.Formatters.Binary;\nusing System.Security.Cryptography;\nusing System.Text;\n\nnamespace glTFRevitExport\n{\n    public class glTFExportConfigs {\r\n        /// <summary>\n        /// Flag to export all buffers into a single .bin file (if true).\n        /// </summary>\n        public bool SingleBinary = true;\r\n\r\n        /// <summary>\n        /// Flag to export all the properties for each element.\n        /// </summary>\n        public bool ExportProperties = true;\r\n\r\n        /// <summary>\n        /// Flag to write coords as Z up instead of Y up (if true).\n        /// </summary>\n        public bool FlipCoords = true;\r\n\r\n        /// <summary>\r\n        /// Include non-standard elements that are not part of\r\n        /// official glTF spec. If false, non-standard elements will be excluded\r\n        /// </summary>\r\n        public bool IncludeNonStdElements = true;\r\n    }\n\n    public class glTFExportContext : IExportContext\n    {\n        private glTFExportConfigs _cfgs = new glTFExportConfigs();\n\n        /// <summary>\n        /// The name for the export files\n        /// </summary>\n        private string _filename;\n        \n        /// <summary>\n        /// The directory for the export files\n        /// </summary>\n        private string _directory;\r\n\r\n        private bool _skipElementFlag = false;\n\n        private GLTFManager manager = new GLTFManager();\n        private Stack<Document> documentStack = new Stack<Document>();\n        private Document _doc\n        {\n            get\n            {\n                return documentStack.Peek();\n            }\n        }\n\n        public glTFExportContext(Document doc, string filename, string directory, glTFExportConfigs configs = null)\n        {\n            documentStack.Push(doc);\n\n            // ensure filename is really a file name and no extension\n            _filename = Path.GetFileNameWithoutExtension(filename);\n            _directory = directory;\n            _cfgs = configs is null ? _cfgs : configs;\n        }\n\n        /// <summary>\n        /// Runs once at beginning of export. Sets up the root node\n        /// and scene.\n        /// </summary>\n        /// <returns></returns>\n        public bool Start()\n        {\n            Debug.WriteLine(\"Starting...\");\n            manager.Start(_cfgs.ExportProperties);\n            return true;\n        }\n\n        /// <summary>\n        /// Runs once at end of export. Serializes the gltf\n        /// properties and wites out the *.gltf and *.bin files.\n        /// </summary>\n        public void Finish()\n        {\n            Debug.WriteLine(\"Finishing...\");\n\n            glTFContainer container = manager.Finish();\n\n            if (_cfgs.IncludeNonStdElements) {\r\n                // TODO: [RM] Standardize what non glTF spec elements will go into\r\n                // this \"BIM glTF superset\" and write a spec for it. Gridlines below\r\n                // are an example.\r\n\r\n                // Add gridlines as gltf nodes in the format:\r\n                // Origin {Vec3<double>}, Direction {Vec3<double>}, Length {double}\r\n                FilteredElementCollector col = new FilteredElementCollector(_doc)\r\n                    .OfClass(typeof(Grid));\r\n\r\n                var grids = col.ToElements();\r\n                foreach (Grid g in grids) {\r\n                    Line l = g.Curve as Line;\r\n\r\n                    var origin = l.Origin;\r\n                    var direction = l.Direction;\r\n                    var length = l.Length;\r\n\r\n                    var xtras = new glTFExtras();\r\n                    var grid = new GridParameters();\r\n                    grid.origin = new List<double>() { origin.X, origin.Y, origin.Z };\r\n                    grid.direction = new List<double>() { direction.X, direction.Y, direction.Z };\r\n                    grid.length = length;\r\n                    xtras.GridParameters = grid;\r\n                    xtras.UniqueId = g.UniqueId;\r\n                    xtras.Properties = Util.GetElementProperties(g, true);\r\n\r\n                    var gridNode = new glTFNode();\r\n                    gridNode.name = g.Name;\r\n                    gridNode.extras = xtras;\r\n\r\n                container.glTF.nodes.Add(gridNode);\n                container.glTF.nodes[0].children.Add(container.glTF.nodes.Count - 1);\n                }\n            }\n\n            if (_cfgs.SingleBinary)\n            {\n                int bytePosition = 0;\n                int currentBuffer = 0;\n                foreach (var view in container.glTF.bufferViews)\n                {\n                    if (view.buffer == 0)\n                    {\n                        bytePosition += view.byteLength;\n                        continue;\n                    }\n\n                    if (view.buffer != currentBuffer)\n                    {\n                        view.buffer = 0;\n                        view.byteOffset = bytePosition;\n                        bytePosition += view.byteLength;\n                    }\n                }\n\n                glTFBuffer buffer = new glTFBuffer();\n                buffer.uri = _filename + \".bin\";\n                buffer.byteLength = bytePosition;\n                container.glTF.buffers.Clear();\n                container.glTF.buffers.Add(buffer);\n\n                using (FileStream f = File.Create(Path.Combine(_directory, buffer.uri)))\n                {\n                    using (BinaryWriter writer = new BinaryWriter(f))\n                    {\n                        foreach (var bin in container.binaries)\n                        {\n                            foreach (var coord in bin.contents.vertexBuffer)\n                            {\n                                writer.Write((float)coord);\n                            }\n                            // TODO: add writer for normals buffer\n                            foreach (var index in bin.contents.indexBuffer)\n                            {\n                                writer.Write((int)index);\n                            }\n                        }\n                    }\n                }\n            }\n            else\n            {\n                // Write the *.bin files\n                foreach (var bin in container.binaries)\n                {\n                    using (FileStream f = File.Create(Path.Combine(_directory, bin.name)))\n                    {\n                        using (BinaryWriter writer = new BinaryWriter(f))\n                        {\n                            foreach (var coord in bin.contents.vertexBuffer)\n                            {\n                                writer.Write((float)coord);\n                            }\n                            // TODO: add writer for normals buffer\n                            foreach (var index in bin.contents.indexBuffer)\n                            {\n                                writer.Write((int)index);\n                            }\n                        }\n                    }\n                }\n            }\n\n            // Write the *.gltf file\n            string serializedModel = JsonConvert.SerializeObject(container.glTF, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore });\n            File.WriteAllText(Path.Combine(_directory, _filename + \".gltf\"), serializedModel);\n        }\n\n        /// <summary>\n        /// Runs once for each element.\n        /// </summary>\n        /// <param name=\"elementId\">ElementId of Element being processed</param>\n        /// <returns></returns>\n        public RenderNodeAction OnElementBegin(ElementId elementId)\n        {\n            Element e = _doc.GetElement(elementId);\n            Debug.WriteLine(String.Format(\"{2}OnElementBegin: {1}-{0}\", e.Name, elementId, manager.formatDebugHeirarchy));\n\n            if (manager.containsNode(e.UniqueId))\n            {\n                // Duplicate element, skip adding.\n                Debug.WriteLine(String.Format(\"{0}  Duplicate Element!\", manager.formatDebugHeirarchy));\n                _skipElementFlag = true;\n                return RenderNodeAction.Skip;\n            }\n\n            manager.OpenNode(e);\n\n            return RenderNodeAction.Proceed;\n        }\n\n        /// <summary>\n        /// Runs every time, and immediately prior to, a mesh being processed (OnPolymesh).\n        /// It supplies the material for the mesh, and we use this to create a new material\n        /// in our material container, or switch the current material if it already exists.\n        /// TODO: Handle more complex materials.\n        /// </summary>\n        /// <param name=\"node\"></param>\n        public void OnMaterial(MaterialNode matNode)\n        {\n            Debug.WriteLine(String.Format(\"{0}  OnMaterial\", manager.formatDebugHeirarchy));\n            string matName;\n            string uniqueId;\n\n            ElementId id = matNode.MaterialId;\n            if (id != ElementId.InvalidElementId)\n            {\n                Element m = _doc.GetElement(matNode.MaterialId);\n                matName = m.Name;\n                uniqueId = m.UniqueId;\n            }\n            else\n            {\n                uniqueId = string.Format(\"r{0}g{1}b{2}\", matNode.Color.Red.ToString(), matNode.Color.Green.ToString(), matNode.Color.Blue.ToString());\n                matName = string.Format(\"MaterialNode_{0}_{1}\", Util.ColorToInt(matNode.Color), Util.RealString(matNode.Transparency * 100));\n            }\n\n            Debug.WriteLine(String.Format(\"{1}  Material: {0}\", matName, manager.formatDebugHeirarchy));\n            manager.SwitchMaterial(matNode, matName, uniqueId);\n        }\n\n        /// <summary>\n        /// Runs for every polymesh being processed. Typically this is a single face\n        /// of an element's mesh. Vertices and faces are keyed on the element/material combination \n        /// (this is important because within a single element, materials can be changed and \n        /// repeated in unknown order).\n        /// </summary>\n        /// <param name=\"polymesh\"></param>\n        public void OnPolymesh(PolymeshTopology polymesh)\n        {\n            Debug.WriteLine(String.Format(\"{0}  OnPolymesh\", manager.formatDebugHeirarchy));\n            manager.OnGeometry(polymesh);\n        }\n\n        /// <summary>\n        /// Runs at the end of an element being processed, after all other calls for that element.\n        /// </summary>\n        /// <param name=\"elementId\"></param>\n        public void OnElementEnd(ElementId elementId)\n        {\n            Debug.WriteLine(String.Format(\"{0}OnElementEnd\", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2)));\n            if (_skipElementFlag)\n            {\n                _skipElementFlag = false;\n                return;\n            }\n\n            manager.CloseNode();\n        }\n\n        /// <summary>\n        /// This is called when family instances are encountered, after OnElementBegin.\n        /// We're using it here to maintain the transform stack for that element's heirarchy.\n        /// </summary>\n        /// <param name=\"node\"></param>\n        /// <returns></returns>\n        public RenderNodeAction OnInstanceBegin(InstanceNode node)\n        {\n            Debug.WriteLine(String.Format(\"{0}OnInstanceBegin\", manager.formatDebugHeirarchy));\n            \n            ElementId symId = node.GetSymbolId();\n            Element symElem = _doc.GetElement(symId);\n\n            Debug.WriteLine(String.Format(\"{2}OnInstanceBegin: {0}-{1}\", symId, symElem.Name, manager.formatDebugHeirarchy));\n\n            var nodeXform = node.GetTransform();\n            manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true);\n\n            return RenderNodeAction.Proceed;\n        }\n\n        /// <summary>\n        /// This is called when family instances are encountered, before OnElementEnd.\n        /// We're using it here to maintain the transform stack for that element's heirarchy.\n        /// </summary>\n        /// <param name=\"node\"></param>\n        public void OnInstanceEnd(InstanceNode node)\n        {\n            Debug.WriteLine(String.Format(\"{0}OnInstanceEnd\", manager.formatDebugHeirarchy.Substring(0,manager.formatDebugHeirarchy.Count() - 2)));\n\n            ElementId symId = node.GetSymbolId();\n            Element symElem = _doc.GetElement(symId);\n\n            manager.CloseNode(symElem, true);\n        }\n\n        public bool IsCanceled()\n        {\n            // This method is invoked many times during the export process.\n            return false;\n        }\n\n        public RenderNodeAction OnViewBegin(ViewNode node)\n        {\n            // TODO: we could use this to handle multiple scenes in the gltf file.\n            return RenderNodeAction.Proceed;\n        }\n\n        public void OnViewEnd(ElementId elementId)\n        {\n            // do nothing\n        }\n\n        public RenderNodeAction OnLinkBegin(LinkNode node)\n        {\n            ElementId symId = node.GetSymbolId();\n            Element symElem = _doc.GetElement(symId);\n\n            Debug.WriteLine(String.Format(\"{2}OnLinkBegin: {0}-{1}\", symId, symElem.Name, manager.formatDebugHeirarchy));\n\n            var nodeXform = node.GetTransform();\n            manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true);\n\n            documentStack.Push(node.GetDocument());\n            return RenderNodeAction.Proceed;\n        }\n\n        public void OnLinkEnd(LinkNode node)\n        {\n            Debug.WriteLine(String.Format(\"{0}OnLinkEnd\", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2)));\n            manager.CloseNode();\n\n            documentStack.Pop();\n        }\n\n        public RenderNodeAction OnFaceBegin(FaceNode node)\n        {\n            return RenderNodeAction.Proceed;\n        }\n\n        public void OnFaceEnd(FaceNode node)\n        {\n            // This method is invoked only if the \n            // custom exporter was set to include faces.\n        }\n\n        public void OnRPC(RPCNode node)\n        {\n            // do nothing\n        }\n\n        public void OnLight(LightNode node)\n        {\n            // do nothing\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/Properties/AssemblyInfo.cs",
    "content": "﻿using System.Reflection;\nusing System.Resources;\nusing System.Runtime.CompilerServices;\nusing System.Runtime.InteropServices;\nusing System.Windows;\n\n// General Information about an assembly is controlled through the following \n// set of attributes. Change these attribute values to modify the information\n// associated with an assembly.\n[assembly: AssemblyTitle(\"glTFRevitExport\")]\n[assembly: AssemblyDescription(\"\")]\n[assembly: AssemblyConfiguration(\"\")]\n[assembly: AssemblyCompany(\"\")]\n[assembly: AssemblyProduct(\"glTFRevitExport\")]\n[assembly: AssemblyCopyright(\"Copyright ©  2015\")]\n[assembly: AssemblyTrademark(\"\")]\n[assembly: AssemblyCulture(\"\")]\n\n// Setting ComVisible to false makes the types in this assembly not visible \n// to COM components.  If you need to access a type in this assembly from \n// COM, set the ComVisible attribute to true on that type.\n[assembly: ComVisible(false)]\n\n//In order to begin building localizable applications, set \n//<UICulture>CultureYouAreCodingWith</UICulture> in your .csproj file\n//inside a <PropertyGroup>.  For example, if you are using US english\n//in your source files, set the <UICulture> to en-US.  Then uncomment\n//the NeutralResourceLanguage attribute below.  Update the \"en-US\" in\n//the line below to match the UICulture setting in the project file.\n\n//[assembly: NeutralResourcesLanguage(\"en-US\", UltimateResourceFallbackLocation.Satellite)]\n\n\n[assembly: ThemeInfo(\n    ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located\n                                     //(used if a resource is not found in the page, \n                                     // or application resource dictionaries)\n    ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located\n                                              //(used if a resource is not found in the page, \n                                              // app, or any theme specific resource dictionaries)\n)]\n\n\n// Version information for an assembly consists of the following four values:\n//\n//      Major Version\n//      Minor Version \n//      Build Number\n//      Revision\n//\n// You can specify all the values or you can default the Build and Revision Numbers \n// by using the '*' as shown below:\n// [assembly: AssemblyVersion(\"1.0.*\")]\n[assembly: AssemblyVersion(\"1.0.0.0\")]\n[assembly: AssemblyFileVersion(\"1.0.0.0\")]\n"
  },
  {
    "path": "glTFRevitExport/Properties/Resources.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.34209\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace glTFRevitExport.Properties\n{\n\n\n    /// <summary>\n    ///   A strongly-typed resource class, for looking up localized strings, etc.\n    /// </summary>\n    // This class was auto-generated by the StronglyTypedResourceBuilder\n    // class via a tool like ResGen or Visual Studio.\n    // To add or remove a member, edit your .ResX file then rerun ResGen\n    // with the /str option, or rebuild your VS project.\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"System.Resources.Tools.StronglyTypedResourceBuilder\", \"4.0.0.0\")]\n    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    internal class Resources\n    {\n\n        private static global::System.Resources.ResourceManager resourceMan;\n\n        private static global::System.Globalization.CultureInfo resourceCulture;\n\n        [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute(\"Microsoft.Performance\", \"CA1811:AvoidUncalledPrivateCode\")]\n        internal Resources()\n        {\n        }\n\n        /// <summary>\n        ///   Returns the cached ResourceManager instance used by this class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Resources.ResourceManager ResourceManager\n        {\n            get\n            {\n                if ((resourceMan == null))\n                {\n                    global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager(\"glTFRevitExport.Properties.Resources\", typeof(Resources).Assembly);\n                    resourceMan = temp;\n                }\n                return resourceMan;\n            }\n        }\n\n        /// <summary>\n        ///   Overrides the current thread's CurrentUICulture property for all\n        ///   resource lookups using this strongly typed resource class.\n        /// </summary>\n        [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]\n        internal static global::System.Globalization.CultureInfo Culture\n        {\n            get\n            {\n                return resourceCulture;\n            }\n            set\n            {\n                resourceCulture = value;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/Properties/Resources.resx",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<root>\n  <!-- \n    Microsoft ResX Schema \n    \n    Version 2.0\n    \n    The primary goals of this format is to allow a simple XML format \n    that is mostly human readable. The generation and parsing of the \n    various data types are done through the TypeConverter classes \n    associated with the data types.\n    \n    Example:\n    \n    ... ado.net/XML headers & schema ...\n    <resheader name=\"resmimetype\">text/microsoft-resx</resheader>\n    <resheader name=\"version\">2.0</resheader>\n    <resheader name=\"reader\">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>\n    <resheader name=\"writer\">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>\n    <data name=\"Name1\"><value>this is my long string</value><comment>this is a comment</comment></data>\n    <data name=\"Color1\" type=\"System.Drawing.Color, System.Drawing\">Blue</data>\n    <data name=\"Bitmap1\" mimetype=\"application/x-microsoft.net.object.binary.base64\">\n        <value>[base64 mime encoded serialized .NET Framework object]</value>\n    </data>\n    <data name=\"Icon1\" type=\"System.Drawing.Icon, System.Drawing\" mimetype=\"application/x-microsoft.net.object.bytearray.base64\">\n        <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>\n        <comment>This is a comment</comment>\n    </data>\n                \n    There are any number of \"resheader\" rows that contain simple \n    name/value pairs.\n    \n    Each data row contains a name, and value. The row also contains a \n    type or mimetype. Type corresponds to a .NET class that support \n    text/value conversion through the TypeConverter architecture. \n    Classes that don't support this are serialized and stored with the \n    mimetype set.\n    \n    The mimetype is used for serialized objects, and tells the \n    ResXResourceReader how to depersist the object. This is currently not \n    extensible. For a given mimetype the value must be set accordingly:\n    \n    Note - application/x-microsoft.net.object.binary.base64 is the format \n    that the ResXResourceWriter will generate, however the reader can \n    read any of the formats listed below.\n    \n    mimetype: application/x-microsoft.net.object.binary.base64\n    value   : The object must be serialized with \n            : System.Serialization.Formatters.Binary.BinaryFormatter\n            : and then encoded with base64 encoding.\n    \n    mimetype: application/x-microsoft.net.object.soap.base64\n    value   : The object must be serialized with \n            : System.Runtime.Serialization.Formatters.Soap.SoapFormatter\n            : and then encoded with base64 encoding.\n\n    mimetype: application/x-microsoft.net.object.bytearray.base64\n    value   : The object must be serialized into a byte array \n            : using a System.ComponentModel.TypeConverter\n            : and then encoded with base64 encoding.\n    -->\n  <xsd:schema id=\"root\" xmlns=\"\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:msdata=\"urn:schemas-microsoft-com:xml-msdata\">\n    <xsd:element name=\"root\" msdata:IsDataSet=\"true\">\n      <xsd:complexType>\n        <xsd:choice maxOccurs=\"unbounded\">\n          <xsd:element name=\"metadata\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"assembly\">\n            <xsd:complexType>\n              <xsd:attribute name=\"alias\" type=\"xsd:string\" />\n              <xsd:attribute name=\"name\" type=\"xsd:string\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"data\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n                <xsd:element name=\"comment\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"2\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" msdata:Ordinal=\"1\" />\n              <xsd:attribute name=\"type\" type=\"xsd:string\" msdata:Ordinal=\"3\" />\n              <xsd:attribute name=\"mimetype\" type=\"xsd:string\" msdata:Ordinal=\"4\" />\n            </xsd:complexType>\n          </xsd:element>\n          <xsd:element name=\"resheader\">\n            <xsd:complexType>\n              <xsd:sequence>\n                <xsd:element name=\"value\" type=\"xsd:string\" minOccurs=\"0\" msdata:Ordinal=\"1\" />\n              </xsd:sequence>\n              <xsd:attribute name=\"name\" type=\"xsd:string\" use=\"required\" />\n            </xsd:complexType>\n          </xsd:element>\n        </xsd:choice>\n      </xsd:complexType>\n    </xsd:element>\n  </xsd:schema>\n  <resheader name=\"resmimetype\">\n    <value>text/microsoft-resx</value>\n  </resheader>\n  <resheader name=\"version\">\n    <value>2.0</value>\n  </resheader>\n  <resheader name=\"reader\">\n    <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n  <resheader name=\"writer\">\n    <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>\n  </resheader>\n</root>"
  },
  {
    "path": "glTFRevitExport/Properties/Settings.Designer.cs",
    "content": "﻿//------------------------------------------------------------------------------\n// <auto-generated>\n//     This code was generated by a tool.\n//     Runtime Version:4.0.30319.34209\n//\n//     Changes to this file may cause incorrect behavior and will be lost if\n//     the code is regenerated.\n// </auto-generated>\n//------------------------------------------------------------------------------\n\nnamespace glTFRevitExport.Properties\n{\n\n\n    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]\n    [global::System.CodeDom.Compiler.GeneratedCodeAttribute(\"Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator\", \"11.0.0.0\")]\n    internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase\n    {\n\n        private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));\n\n        public static Settings Default\n        {\n            get\n            {\n                return defaultInstance;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/Properties/Settings.settings",
    "content": "﻿<?xml version='1.0' encoding='utf-8'?>\n<SettingsFile xmlns=\"uri:settings\" CurrentProfile=\"(Default)\">\n  <Profiles>\n    <Profile Name=\"(Default)\" />\n  </Profiles>\n  <Settings />\n</SettingsFile>"
  },
  {
    "path": "glTFRevitExport/Util.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing Autodesk.Revit.DB;\n\nnamespace glTFRevitExport\n{\n    class Util\n    {\n        public static int[] GetVec3MinMax(List<int> vec3)\n        {\n            int minVertexX = int.MaxValue;\n            int minVertexY = int.MaxValue;\n            int minVertexZ = int.MaxValue;\n            int maxVertexX = int.MinValue;\n            int maxVertexY = int.MinValue;\n            int maxVertexZ = int.MinValue;\n            for (int i = 0; i < vec3.Count; i += 3)\n            {\n                if (vec3[i] < minVertexX) minVertexX = vec3[i];\n                if (vec3[i] > maxVertexX) maxVertexX = vec3[i];\n\n                if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1];\n                if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1];\n\n                if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2];\n                if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2];\n            }\n            return new int[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ };\n        }\n\n        public static long[] GetVec3MinMax(List<long> vec3)\n        {\n            long minVertexX = long.MaxValue;\n            long minVertexY = long.MaxValue;\n            long minVertexZ = long.MaxValue;\n            long maxVertexX = long.MinValue;\n            long maxVertexY = long.MinValue;\n            long maxVertexZ = long.MinValue;\n            for (int i = 0; i < (vec3.Count / 3); i += 3)\n            {\n                if (vec3[i] < minVertexX) minVertexX = vec3[i];\n                if (vec3[i] > maxVertexX) maxVertexX = vec3[i];\n\n                if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1];\n                if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1];\n\n                if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2];\n                if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2];\n            }\n            return new long[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ };\n        }\n\n        public static float[] GetVec3MinMax(List<float> vec3)\n        {\n            \n            List<float> xValues = new List<float>();\n            List<float> yValues = new List<float>();\n            List<float> zValues = new List<float>();\n            for (int i = 0; i < vec3.Count; i++)\n            {\n                if ((i % 3) == 0) xValues.Add(vec3[i]);\n                if ((i % 3) == 1) yValues.Add(vec3[i]);\n                if ((i % 3) == 2) zValues.Add(vec3[i]);\n            }\n\n            float maxX = xValues.Max();\n            float minX = xValues.Min();\n            float maxY = yValues.Max();\n            float minY = yValues.Min();\n            float maxZ = zValues.Max();\n            float minZ = zValues.Min();\n\n            return new float[] { minX, maxX, minY, maxY, minZ, maxZ };\n        }\n\n        public static int[] GetScalarMinMax(List<int> scalars)\n        {\n            int minFaceIndex = int.MaxValue;\n            int maxFaceIndex = int.MinValue;\n            for (int i = 0; i < scalars.Count; i++)\n            {\n                int currentMin = Math.Min(minFaceIndex, scalars[i]);\n                if (currentMin < minFaceIndex) minFaceIndex = currentMin;\n\n                int currentMax = Math.Max(maxFaceIndex, scalars[i]);\n                if (currentMax > maxFaceIndex) maxFaceIndex = currentMax;\n            }\n            return new int[] { minFaceIndex, maxFaceIndex };\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Return a string for a real number\n        /// formatted to two decimal places.\n        /// </summary>\n        public static string RealString(double a)\n        {\n            return a.ToString(\"0.##\");\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Return a string for an XYZ point\n        /// or vector with its coordinates\n        /// formatted to two decimal places.\n        /// </summary>\n        public static string PointString(XYZ p)\n        {\n            return string.Format(\"({0},{1},{2})\",\n              RealString(p.X),\n              RealString(p.Y),\n              RealString(p.Z));\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Return an integer value for a Revit Color.\n        /// </summary>\n        public static int ColorToInt(Color color)\n        {\n            return ((int)color.Red) << 16\n              | ((int)color.Green) << 8\n              | (int)color.Blue;\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Extract a true or false value from the given\n        /// string, accepting yes/no, Y/N, true/false, T/F\n        /// and 1/0. We are extremely tolerant, i.e., any\n        /// value starting with one of the characters y, n,\n        /// t or f is also accepted. Return false if no \n        /// valid Boolean value can be extracted.\n        /// </summary>\n        public static bool GetTrueOrFalse(string s, out bool val)\n        {\n            val = false;\n\n            if (s.Equals(Boolean.TrueString,\n              StringComparison.OrdinalIgnoreCase))\n            {\n                val = true;\n                return true;\n            }\n            if (s.Equals(Boolean.FalseString,\n              StringComparison.OrdinalIgnoreCase))\n            {\n                return true;\n            }\n            if (s.Equals(\"1\"))\n            {\n                val = true;\n                return true;\n            }\n            if (s.Equals(\"0\"))\n            {\n                return true;\n            }\n            s = s.ToLower();\n\n            if ('t' == s[0] || 'y' == s[0])\n            {\n                val = true;\n                return true;\n            }\n            if ('f' == s[0] || 'n' == s[0])\n            {\n                return true;\n            }\n            return false;\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Return a string describing the given element:\n        /// .NET type name,\n        /// category name,\n        /// family and symbol name for a family instance,\n        /// element id and element name.\n        /// </summary>\n        public static string ElementDescription(Element e)\n        {\n            if (null == e)\n            {\n                return \"<null>\";\n            }\n\n            // For a wall, the element name equals the\n            // wall type name, which is equivalent to the\n            // family name ...\n\n            FamilyInstance fi = e as FamilyInstance;\n\n            string typeName = e.GetType().Name;\n\n            string categoryName = (null == e.Category)\n              ? string.Empty\n              : e.Category.Name + \" \";\n\n            string familyName = (null == fi)\n              ? string.Empty\n              : fi.Symbol.Family.Name + \" \";\n\n            string symbolName = (null == fi\n              || e.Name.Equals(fi.Symbol.Name))\n                ? string.Empty\n                : fi.Symbol.Name + \" \";\n\n            return string.Format(\"{0} {1}{2}{3}<{4} {5}>\",\n              typeName, categoryName, familyName,\n              symbolName, e.Id.IntegerValue, e.Name);\n        }\n\n        /// <summary>\n        /// From Jeremy Tammik's RvtVa3c exporter:\n        /// https://github.com/va3c/RvtVa3c\n        /// Return a dictionary of all the given \n        /// element parameter names and values.\n        /// </summary>\n        public static Dictionary<string, string> GetElementProperties(Element e, bool includeType)\n        {\n            IList<Parameter> parameters\n              = e.GetOrderedParameters();\n\n            Dictionary<string, string> a = new Dictionary<string, string>(parameters.Count);\n\n            // Add element category\n            if (e.Category != null)\n            {\n                a.Add(\"Element Category\", e.Category.Name);\n            }\n\n\n\n            foreach (Parameter p in parameters)\n            {\n                string key = p.Definition.Name;\n\n                if (!a.ContainsKey(key))\n                {\n                    string val;\n                    if (StorageType.String == p.StorageType)\n                    {\n                        val = p.AsString();\n                    }\n                    else\n                    {\n                        val = p.AsValueString();\n                    }\n                    if (!string.IsNullOrEmpty(val))\n                    {\n                        a.Add(key, val);\n                    }\n                }\n            }\n\n            if (includeType)\n            {\n                ElementId idType = e.GetTypeId();\n\n                if (idType != null && ElementId.InvalidElementId != idType)\n                {\n                    Document doc = e.Document;\n                    Element typ = doc.GetElement(idType);\n                    parameters = typ.GetOrderedParameters();\n                    foreach (Parameter p in parameters)\n                    {\n                        string key = \"Type \" + p.Definition.Name;\n\n                        if (!a.ContainsKey(key))\n                        {\n                            string val;\n                            if (StorageType.String == p.StorageType)\n                            {\n                                val = p.AsString();\n                            }\n                            else\n                            {\n                                val = p.AsValueString();\n                            }\n                            if (!string.IsNullOrEmpty(val))\n                            {\n                                a.Add(key, val);\n                            }\n                        }\n                    }\n                }\n            }\n\n            if (a.Count == 0) return null;\n            else return a;\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/WPF/MainWindow.xaml",
    "content": "﻿<Window x:Class=\"glTFRevitExport.MainWindow\"\n        xmlns=\"http://schemas.microsoft.com/winfx/2006/xaml/presentation\"\n        xmlns:x=\"http://schemas.microsoft.com/winfx/2006/xaml\"\n        Title=\"MainWindow\" Height=\"350\" Width=\"525\" Background=\"#FFF0EFEE\">\n    <Grid>\n        <Button x:Name=\"btnOk\" Content=\"OK\" HorizontalAlignment=\"Left\" Margin=\"432,288,0,0\" VerticalAlignment=\"Top\" Width=\"75\"/>\n\n    </Grid>\n</Window>\n"
  },
  {
    "path": "glTFRevitExport/WPF/MainWindow.xaml.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\nusing System.Linq;\nusing System.Text;\nusing System.Threading.Tasks;\nusing System.Windows;\nusing System.Windows.Controls;\nusing System.Windows.Data;\nusing System.Windows.Documents;\nusing System.Windows.Input;\nusing System.Windows.Media;\nusing System.Windows.Media.Imaging;\nusing System.Windows.Navigation;\nusing System.Windows.Shapes;\nusing Autodesk.Revit.ApplicationServices;\nusing Autodesk.Revit.Attributes;\nusing Autodesk.Revit.DB;\nusing Autodesk.Revit.UI;\nusing Autodesk.Revit.UI.Selection;\n\nnamespace glTFRevitExport\n{\n    /// <summary>\n    /// Interaction logic for MainWindow.xaml\n    /// </summary>\n    public partial class MainWindow : Window\n    {\n        public MainWindow(Document doc)\n        {\n            InitializeComponent();\n            WindowStartupLocation = System.Windows.WindowStartupLocation.CenterScreen;\n            btnOk.Click += (sender, e) => btnOk_Click(sender, e, doc);\n        }\n\n        private void btnOk_Click(object sender, RoutedEventArgs e, Document doc)\n        {\n            Close();\n        }\n    }\n}\n"
  },
  {
    "path": "glTFRevitExport/glTF.cs",
    "content": "﻿using System;\nusing System.Collections.Generic;\n\nnamespace glTFRevitExport\n{\n    /// <summary>\n    /// Magic numbers to differentiate scalar and vector \n    /// array buffers.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#buffers-and-buffer-views\n    /// </summary>\n    public enum Targets\n    {\n        ARRAY_BUFFER = 34962, // signals vertex data\n        ELEMENT_ARRAY_BUFFER = 34963 // signals index or face data\n    }\n\n    /// <summary>\n    /// Magic numbers to differentiate array buffer component\n    /// types.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessor-element-size\n    /// </summary>\n    public enum ComponentType\n    {\n        BYTE = 5120,\n        UNSIGNED_BYTE = 5121,\n        SHORT = 5122,\n        UNSIGNED_SHORT = 5123,\n        UNSIGNED_INT = 5125,\n        FLOAT = 5126\n    }\n\n    public struct glTFContainer\n    {\n        public glTF glTF;\n        public List<glTFBinaryData> binaries;\n    }\n\n    /// <summary>\n    /// The json serializable glTF file format.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0\n    /// </summary>\n    public struct glTF\n    {\n        public glTFVersion asset;\n        public List<glTFScene> scenes;\n        public List<glTFNode> nodes;\n        public List<glTFMesh> meshes;\n        public List<glTFBuffer> buffers;\n        public List<glTFBufferView> bufferViews;\n        public List<glTFAccessor> accessors;\n        public List<glTFMaterial> materials;\n    }\n\n    /// <summary>\n    /// A binary data store serialized to a *.bin file\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#binary-data-storage\n    /// </summary>\n    public class glTFBinaryData : HashedType\n    {\n        public glTFBinaryBufferContents contents { get; set; }\n        //public List<float> vertexBuffer { get; set; } = new List<float>();\n        //public List<int> indexBuffer { get; set; } = new List<int>();\n        //public List<float> normalBuffer { get; set; } = new List<float>();\n        public int vertexAccessorIndex { get; set; }\n        public int indexAccessorIndex { get; set; }\n        //public int normalsAccessorIndex { get; set; }\n        public string name { get; set; }\n        //public string hashcode { get; set; }\n    }\n\n    [Serializable]\n    public class glTFBinaryBufferContents\n    {\n        public List<float> vertexBuffer { get; set; } = new List<float>();\n        public List<int> indexBuffer { get; set; } = new List<int>();\n    }\n\n    /// <summary>\n    /// Required glTF asset information\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#asset\n    /// </summary>\n    public class glTFVersion\n    {\n        public string version = \"2.0\";\n    }\n\n    /// <summary>\n    /// The scenes available to render.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#scenes\n    /// </summary>\n    public class glTFScene\n    {\n        public List<int> nodes = new List<int>();\n    }\n\n    /// <summary>\n    /// The nodes defining individual (or nested) elements in the scene.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#nodes-and-hierarchy\n    /// </summary>\n    public class glTFNode\n    {\n        /// <summary>\n        /// The user-defined name of this object\n        /// </summary>\n        public string name { get; set; }\n        /// <summary>\n        /// The index of the mesh in this node.\n        /// </summary>\n        public int? mesh { get; set; } = null;\n        /// <summary>\n        /// A floating-point 4x4 transformation matrix stored in column major order.\n        /// </summary>\n        public List<double> matrix { get; set; }\n        /// <summary>\n        /// The indices of this node's children.\n        /// </summary>\n        public List<int> children { get; set; }\n        /// <summary>\n        /// The extras describing this node.\n        /// </summary>\n        public glTFExtras extras { get; set; }\n    }\n\n    public class HashedType\n    {\n        public string hashcode { get; set; }\n    }\n\n    public class MeshContainer : HashedType\n    {\n        //public string hashcode { get; set; }\n        public glTFMesh contents { get; set; }\n    }\n\n    /// <summary>\n    /// The array of primitives defining the mesh of an object.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#meshes\n    /// </summary>\n    [Serializable]\n    public class glTFMesh\n    {\n        public List<glTFMeshPrimitive> primitives { get; set; }\n    }\n\n    /// <summary>\n    /// Properties defining where the GPU should look to find the mesh and material data.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#meshes\n    /// </summary>\n    [Serializable]\n    public class glTFMeshPrimitive\n    {\n        public glTFAttribute attributes { get; set; } = new glTFAttribute();\n        public int indices { get; set; }\n        public int? material { get; set; } = null;\n        public int mode { get; set; } = 4; // 4 is triangles\n    }\n\n    /// <summary>\n    /// The glTF PBR Material format.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#materials\n    /// </summary>\n    public class glTFMaterial\n    {\n        public string name { get; set; }\n        public glTFPBR pbrMetallicRoughness { get; set; }\n    }\n    public class glTFPBR\n    {\n        public List<float> baseColorFactor { get; set; }\n        public float metallicFactor { get; set; }\n        public float roughnessFactor { get; set; }\n    }\n\n    /// <summary>\n    /// The list of accessors available to the renderer for a particular mesh.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#meshes\n    /// </summary>\n    [Serializable]\n    public class glTFAttribute\n    {\n        /// <summary>\n        /// The index of the accessor for position data.\n        /// </summary>\n        public int POSITION { get; set; }\n        //public int NORMAL { get; set; }\n    }\n\n    /// <summary>\n    /// A reference to the location and size of binary data.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#buffers-and-buffer-views\n    /// </summary>\n    public class glTFBuffer\n    {\n        /// <summary>\n        /// The uri of the buffer.\n        /// </summary>\n        public string uri { get; set; }\n        /// <summary>\n        /// The total byte length of the buffer.\n        /// </summary>\n        public int byteLength { get; set; }\n    }\n\n    /// <summary>\n    /// A reference to a subsection of a buffer containing either vector or scalar data.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#buffers-and-buffer-views\n    /// </summary>\n    public class glTFBufferView\n    {\n        /// <summary>\n        /// The index of the buffer.\n        /// </summary>\n        public int buffer { get; set; }\n        /// <summary>\n        /// The offset into the buffer in bytes.\n        /// </summary>\n        public int byteOffset { get; set; }\n        /// <summary>\n        /// The length of the bufferView in bytes.\n        /// </summary>\n        public int byteLength { get; set; }\n        /// <summary>\n        /// The target that the GPU buffer should be bound to.\n        /// </summary>\n        public Targets target { get; set; }\n        /// <summary>\n        /// A user defined name for this view.\n        /// </summary>\n        public string name { get; set; }\n    }\n\n    /// <summary>\n    /// A reference to a subsection of a BufferView containing a particular data type.\n    /// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#accessors\n    /// </summary>\n    public class glTFAccessor\n    {\n        /// <summary>\n        /// The index of the bufferView.\n        /// </summary>\n        public int bufferView { get; set; }\n        /// <summary>\n        /// The offset relative to the start of the bufferView in bytes.\n        /// </summary>\n        public int byteOffset { get; set; }\n        /// <summary>\n        /// the datatype of the components in the attribute\n        /// </summary>\n        public ComponentType componentType { get; set; }\n        /// <summary>\n        /// The number of attributes referenced by this accessor.\n        /// </summary>\n        public int count { get; set; }\n        /// <summary>\n        /// Specifies if the attribute is a scalar, vector, or matrix\n        /// </summary>\n        public string type { get; set; }\n        /// <summary>\n        /// Maximum value of each component in this attribute.\n        /// </summary>\n        public List<float> max { get; set; }\n        /// <summary>\n        /// Minimum value of each component in this attribute.\n        /// </summary>\n        public List<float> min { get; set; }\n        /// <summary>\n        /// A user defined name for this accessor.\n        /// </summary>\n        public string name { get; set; }\n    }\n\n    public class glTFExtras\n    {\n        /// <summary>\n        /// The Revit created UniqueId for this object\n        /// </summary>\n        public string UniqueId { get; set; }\n        public GridParameters GridParameters { get; set; }\n        public Dictionary<string, string> Properties { get; set; }\n    }\n\n    public class GridParameters\n    {\n        public List<double> origin { get; set; }\n        public List<double> direction { get; set; }\n        public double length { get; set; }\n    }\n\n    //public class glTFFunctions\n    //{\n    //    public static glTFBinaryData getMeshData(glTFNode node, glTF gltf)\n    //    {\n    //        if(node.mesh.HasValue)\n    //        {\n    //            glTFMesh mesh = gltf.meshes[node.mesh.Value];\n    //            mesh.\n    //        }\n    //    }\n    //}\n}\n"
  },
  {
    "path": "glTFRevitExport/glTFRevitExport.addin",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RevitAddIns>\n  <AddIn Type=\"Command\">\n    <Text>Command glTFRevitExport</Text>\n    <Description>Some description for glTFRevitExport</Description>\n    <Assembly>glTFRevitExport.dll</Assembly>\n    <FullClassName>glTFRevitExport.Command</FullClassName>\n    <ClientId>38555A79-E66E-49B6-8278-191F17E931B2</ClientId>\n    <VendorId>_RTM</VendorId>\n    <VendorDescription>Ryan T. McCullough</VendorDescription>\n  </AddIn>\n  <AddIn Type=\"Application\">\n    <Name>Application glTFRevitExport</Name>\n    <Assembly>glTFRevitExport.dll</Assembly>\n    <FullClassName>glTFRevitExport.App</FullClassName>\n    <ClientId>DBBCC867-53D7-4B55-A620-88637AD9CC7E</ClientId>\n    <VendorId>_RTM</VendorId>\n    <VendorDescription>Ryan T. McCullough</VendorDescription>\n  </AddIn>\n</RevitAddIns>\n"
  },
  {
    "path": "glTFRevitExport/glTFRevitExport.csproj",
    "content": "﻿<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<Project ToolsVersion=\"12.0\" DefaultTargets=\"Build\" xmlns=\"http://schemas.microsoft.com/developer/msbuild/2003\">\r\n  <Import Project=\"$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props\" Condition=\"Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')\" />\r\n  <PropertyGroup>\r\n    <Configuration Condition=\" '$(Configuration)' == '' \">Debug</Configuration>\r\n    <Platform Condition=\" '$(Platform)' == '' \">AnyCPU</Platform>\r\n    <ProjectGuid>{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}</ProjectGuid>\r\n    <OutputType>Library</OutputType>\r\n    <AppDesignerFolder>Properties</AppDesignerFolder>\r\n    <RootNamespace>glTFRevitExport</RootNamespace>\r\n    <AssemblyName>glTFRevitExport</AssemblyName>\r\n    <FileAlignment>512</FileAlignment>\r\n    <ProjectTypeGuids>{60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>\r\n    <WarningLevel>4</WarningLevel>\r\n    <TargetFrameworkProfile />\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"$(Configuration.Contains('2017'))\">\r\n    <TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>\r\n    <RevitVersion>2017</RevitVersion>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"$(Configuration.Contains('2018'))\">\r\n    <TargetFrameworkVersion>v4.6.2</TargetFrameworkVersion>\r\n    <RevitVersion>2018</RevitVersion>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"$(Configuration.Contains('2019'))\">\r\n    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>\r\n    <RevitVersion>2019</RevitVersion>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"$(Configuration.Contains('2020'))\">\r\n    <TargetFrameworkVersion>v4.7</TargetFrameworkVersion>\r\n    <RevitVersion>2020</RevitVersion>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"$(Configuration.Contains('2021'))\">\r\n    <TargetFrameworkVersion>v4.8</TargetFrameworkVersion>\r\n    <RevitVersion>2021</RevitVersion>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug2017|x64'\">\r\n    <OutputPath>bin\\Debug2017\\</OutputPath>\r\n    <DebugSymbols>true</DebugSymbols>\r\n    <Optimize>false</Optimize>\r\n    <DefineConstants>TRACE;DEBUG;REVIT2017</DefineConstants>\r\n    <DebugType>full</DebugType>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug2018|x64'\">\r\n    <OutputPath>bin\\Debug2018\\</OutputPath>\r\n    <DebugSymbols>true</DebugSymbols>\r\n    <Optimize>false</Optimize>\r\n    <DefineConstants>TRACE;DEBUG;REVIT2018</DefineConstants>\r\n    <DebugType>full</DebugType>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug2019|x64'\">\r\n    <OutputPath>bin\\Debug2019\\</OutputPath>\r\n    <DebugSymbols>true</DebugSymbols>\r\n    <Optimize>false</Optimize>\r\n    <DefineConstants>TRACE;DEBUG;REVIT2019</DefineConstants>\r\n    <DebugType>full</DebugType>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug2020|x64'\">\r\n    <OutputPath>bin\\Debug2020\\</OutputPath>\r\n    <DebugSymbols>true</DebugSymbols>\r\n    <Optimize>false</Optimize>\r\n    <DefineConstants>TRACE;DEBUG;REVIT2020</DefineConstants>\r\n    <DebugType>full</DebugType>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Debug2021|x64'\">\r\n    <OutputPath>bin\\Debug2021\\</OutputPath>\r\n    <DebugSymbols>true</DebugSymbols>\r\n    <Optimize>false</Optimize>\r\n    <DefineConstants>TRACE;DEBUG;REVIT2021</DefineConstants>\r\n    <DebugType>full</DebugType>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release2017|x64'\">\r\n    <OutputPath>bin\\Release2017\\</OutputPath>\r\n    <Optimize>true</Optimize>\r\n    <DefineConstants>REVIT2017</DefineConstants>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release2018|x64'\">\r\n    <OutputPath>bin\\Release2018\\</OutputPath>\r\n    <Optimize>true</Optimize>\r\n    <DefineConstants>REVIT2018</DefineConstants>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release2019|x64'\">\r\n    <OutputPath>bin\\Release2019\\</OutputPath>\r\n    <Optimize>true</Optimize>\r\n    <DefineConstants>REVIT2019</DefineConstants>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release2020|x64'\">\r\n    <OutputPath>bin\\Release2020\\</OutputPath>\r\n    <Optimize>true</Optimize>\r\n    <DefineConstants>REVIT2020</DefineConstants>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup Condition=\"'$(Configuration)|$(Platform)' == 'Release2021|x64'\">\r\n    <OutputPath>bin\\Release2021\\</OutputPath>\r\n    <Optimize>true</Optimize>\r\n    <DefineConstants>REVIT2021</DefineConstants>\r\n    <PlatformTarget>x64</PlatformTarget>\r\n    <ErrorReport>prompt</ErrorReport>\r\n    <WarningLevel>4</WarningLevel>\r\n  </PropertyGroup>\r\n  <PropertyGroup>\r\n    <StartupObject />\r\n  </PropertyGroup>\r\n  <ItemGroup>\r\n    <Reference Include=\"System\" />\r\n    <Reference Include=\"System.Data\" />\r\n    <Reference Include=\"System.Web.Extensions\" />\r\n    <Reference Include=\"System.Xml\" />\r\n    <Reference Include=\"Microsoft.CSharp\" />\r\n    <Reference Include=\"System.Core\" />\r\n    <Reference Include=\"System.Xml.Linq\" />\r\n    <Reference Include=\"System.Data.DataSetExtensions\" />\r\n    <Reference Include=\"System.Xaml\">\r\n      <RequiredTargetFramework>4.0</RequiredTargetFramework>\r\n    </Reference>\r\n    <Reference Include=\"WindowsBase\" />\r\n    <Reference Include=\"PresentationCore\" />\r\n    <Reference Include=\"PresentationFramework\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Page Include=\"WPF\\MainWindow.xaml\">\r\n      <Generator>MSBuild:Compile</Generator>\r\n      <SubType>Designer</SubType>\r\n    </Page>\r\n    <Compile Include=\"App.cs\" />\r\n    <Compile Include=\"Command.cs\" />\r\n    <Compile Include=\"glTF.cs\" />\r\n    <Compile Include=\"GlTFExportContext.cs\" />\r\n    <Compile Include=\"Containers.cs\" />\r\n    <Compile Include=\"GLTFManager.cs\" />\r\n    <Compile Include=\"Util.cs\" />\r\n    <Compile Include=\"WPF\\MainWindow.xaml.cs\">\r\n      <DependentUpon>MainWindow.xaml</DependentUpon>\r\n      <SubType>Code</SubType>\r\n    </Compile>\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Compile Include=\"Properties\\AssemblyInfo.cs\">\r\n      <SubType>Code</SubType>\r\n    </Compile>\r\n    <Compile Include=\"Properties\\Resources.Designer.cs\">\r\n      <AutoGen>True</AutoGen>\r\n      <DesignTime>True</DesignTime>\r\n      <DependentUpon>Resources.resx</DependentUpon>\r\n    </Compile>\r\n    <Compile Include=\"Properties\\Settings.Designer.cs\">\r\n      <AutoGen>True</AutoGen>\r\n      <DependentUpon>Settings.settings</DependentUpon>\r\n      <DesignTimeSharedInput>True</DesignTimeSharedInput>\r\n    </Compile>\r\n    <EmbeddedResource Include=\"Properties\\Resources.resx\">\r\n      <Generator>ResXFileCodeGenerator</Generator>\r\n      <LastGenOutput>Resources.Designer.cs</LastGenOutput>\r\n    </EmbeddedResource>\r\n    <None Include=\"Properties\\Settings.settings\">\r\n      <Generator>SettingsSingleFileGenerator</Generator>\r\n      <LastGenOutput>Settings.Designer.cs</LastGenOutput>\r\n    </None>\r\n    <AppDesigner Include=\"Properties\\\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <Content Include=\"glTFRevitExport.addin\">\r\n      <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>\r\n    </Content>\r\n    <EmbeddedResource Include=\"Embedded Media\\small.png\" />\r\n    <EmbeddedResource Include=\"Embedded Media\\large.png\" />\r\n  </ItemGroup>\r\n  <ItemGroup>\r\n    <PackageReference Include=\"Newtonsoft.Json\">\r\n      <Version>12.0.3</Version>\r\n    </PackageReference>\r\n    <PackageReference Include=\"Revit_All_Main_Versions_API_x64\" Version=\"$(RevitVersion).0.0\" />\r\n  </ItemGroup>\r\n  <Import Project=\"$(MSBuildToolsPath)\\Microsoft.CSharp.targets\" />\r\n  <!-- To modify your build process, add your task inside one of the targets below and uncomment it. \r\n       Other similar extension points exist, see Microsoft.Common.targets.\r\n  <Target Name=\"BeforeBuild\">\r\n  </Target>\r\n  <Target Name=\"AfterBuild\">\r\n  </Target>\r\n  -->\r\n</Project>"
  },
  {
    "path": "glTFRevitExport.sln",
    "content": "﻿\r\nMicrosoft Visual Studio Solution File, Format Version 12.00\r\n# Visual Studio Version 16\r\nVisualStudioVersion = 16.0.30320.27\r\nMinimumVisualStudioVersion = 10.0.40219.1\r\nProject(\"{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}\") = \"glTFRevitExport\", \"glTFRevitExport\\glTFRevitExport.csproj\", \"{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}\"\r\nEndProject\r\nGlobal\r\n\tGlobalSection(SolutionConfigurationPlatforms) = preSolution\r\n\t\tDebug2017|Any CPU = Debug2017|Any CPU\r\n\t\tDebug2018|Any CPU = Debug2018|Any CPU\r\n\t\tDebug2019|Any CPU = Debug2019|Any CPU\r\n\t\tDebug2020|Any CPU = Debug2020|Any CPU\r\n\t\tDebug2021|Any CPU = Debug2021|Any CPU\r\n\t\tRelease2017|Any CPU = Release2017|Any CPU\r\n\t\tRelease2018|Any CPU = Release2018|Any CPU\r\n\t\tRelease2019|Any CPU = Release2019|Any CPU\r\n\t\tRelease2020|Any CPU = Release2020|Any CPU\r\n\t\tRelease2021|Any CPU = Release2021|Any CPU\r\n\tEndGlobalSection\r\n\tGlobalSection(ProjectConfigurationPlatforms) = postSolution\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2017|Any CPU.ActiveCfg = Debug2017|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2018|Any CPU.ActiveCfg = Debug2018|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2019|Any CPU.ActiveCfg = Debug2019|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2020|Any CPU.ActiveCfg = Debug2020|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2021|Any CPU.ActiveCfg = Debug2021|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2017|Any CPU.ActiveCfg = Release2017|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2018|Any CPU.ActiveCfg = Release2018|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2019|Any CPU.ActiveCfg = Release2019|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2020|Any CPU.ActiveCfg = Release2020|x64\r\n\t\t{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2021|Any CPU.ActiveCfg = Release2021|x64\r\n\tEndGlobalSection\r\n\tGlobalSection(SolutionProperties) = preSolution\r\n\t\tHideSolutionNode = FALSE\r\n\tEndGlobalSection\r\n\tGlobalSection(ExtensibilityGlobals) = postSolution\r\n\t\tSolutionGuid = {3549B740-9439-4093-BF66-9FCF49742047}\r\n\tEndGlobalSection\r\nEndGlobal\r\n"
  }
]