Repository: McCulloughRT/Revit2glTF Branch: master Commit: 564515d46e55 Files: 19 Total size: 100.4 KB Directory structure: gitextract_qlio5x88/ ├── .gitignore ├── README.md ├── glTFRevitExport/ │ ├── App.cs │ ├── Command.cs │ ├── Containers.cs │ ├── GLTFManager.cs │ ├── GlTFExportContext.cs │ ├── Properties/ │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── Util.cs │ ├── WPF/ │ │ ├── MainWindow.xaml │ │ └── MainWindow.xaml.cs │ ├── glTF.cs │ ├── glTFRevitExport.addin │ └── glTFRevitExport.csproj └── glTFRevitExport.sln ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files *.rsuser *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Mono auto generated files mono_crash.* # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ [Aa][Rr][Mm]/ [Aa][Rr][Mm]64/ bld/ [Bb]in/ [Oo]bj/ [Ll]og/ [Ll]ogs/ # Visual Studio 2015/2017 cache/options directory .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot #wwwroot/ # Visual Studio 2017 auto generated files Generated\ Files/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUnit *.VisualState.xml TestResult.xml nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results BenchmarkDotNet.Artifacts/ # .NET Core project.lock.json project.fragment.lock.json artifacts/ # StyleCop StyleCopReport.xml # Files built by Visual Studio *_i.c *_p.c *_h.h *.ilk *.meta *.obj *.iobj *.pch *.pdb *.ipdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *_wpftmp.csproj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opendb *.opensdf *.sdf *.cachefile *.VC.db *.VC.VC.opendb # Visual Studio profiler *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files *.e2e # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # AxoCover is a Code Coverage Tool .axoCover/* !.axoCover/settings.json # Coverlet is a free, cross platform Code Coverage Tool coverage*[.json, .xml, .info] # Visual Studio code coverage results *.coverage *.coveragexml # NCrunch _NCrunch_* .*crunch*.local.xml nCrunchTemp_* # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted PublishScripts/ # NuGet Packages *.nupkg # NuGet Symbol Packages *.snupkg # The packages folder can be ignored because of Package Restore **/[Pp]ackages/* # except build/, which is used as an MSBuild target. !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files *.nuget.props *.nuget.targets # Microsoft Azure Build Output csx/ *.build.csdef # Microsoft Azure Emulator ecf/ rcf/ # Windows Store app package directories and files AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml _pkginfo.txt *.appx *.appxbundle *.appxupload # Visual Studio cache files # files ending in .cache can be ignored *.[Cc]ache # but keep track of directories ending in .cache !?*.[Cc]ache/ # Others ClientBin/ ~$* *~ *.dbmdl *.dbproj.schemaview *.jfm *.pfx *.publishsettings orleans.codegen.cs # Including strong name files can present a security risk # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm ServiceFabricBackup/ *.rptproj.bak # SQL Server files *.mdf *.ldf *.ndf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings *.rptproj.rsuser *- [Bb]ackup.rdl *- [Bb]ackup ([0-9]).rdl *- [Bb]ackup ([0-9][0-9]).rdl # Microsoft Fakes FakesAssemblies/ # GhostDoc plugin setting file *.GhostDoc.xml # Node.js Tools for Visual Studio .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) *.vbw # Visual Studio LightSwitch build output **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml **/*.Server/GeneratedArtifacts **/*.Server/ModelManifest.xml _Pvt_Extensions # Paket dependency manager .paket/paket.exe paket-files/ # FAKE - F# Make .fake/ # CodeRush personal settings .cr/personal # Python Tools for Visual Studio (PTVS) __pycache__/ *.pyc # Cake - Uncomment if you are using it # tools/** # !tools/packages.config # Tabs Studio *.tss # Telerik's JustMock configuration file *.jmconfig # BizTalk build output *.btp.cs *.btm.cs *.odx.cs *.xsd.cs # OpenCover UI analysis results OpenCover/ # Azure Stream Analytics local run output ASALocalRun/ # MSBuild Binary and Structured Log *.binlog # NVidia Nsight GPU debugger configuration file *.nvuser # MFractors (Xamarin productivity tool) working folder .mfractor/ # Local History for Visual Studio .localhistory/ # BeatPulse healthcheck temp database healthchecksdb # Backup folder for Package Reference Convert tool in Visual Studio 2017 MigrationBackup/ # Ionide (cross platform F# VS Code tools) working folder .ionide/ ================================================ FILE: README.md ================================================ # Revit2glTF - A Revit glTF Exporter This 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. ## Current To-Do's - [x] Handle basic material export - [ ] Handle textured material export - [ ] Handle normals export - [ ] Add toggle for exporting each element as a seperate .bin vs a single .glb. - [x] Add element properties to extras on glTF nodes. - [ ] Add element properties to a sqlite file referenced by glTF nodes. ================================================ FILE: glTFRevitExport/App.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; using System.Reflection; using System.Windows.Media; using System.Windows.Media.Imaging; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Autodesk.Revit.UI.Selection; namespace glTFRevitExport { class App : IExternalApplication { public Result OnStartup(UIControlledApplication a) { string tabName = "Andersen Tools"; try { a.CreateRibbonTab(tabName); } catch (Autodesk.Revit.Exceptions.ArgumentException) { // Do nothing. } // Add a new ribbon panel RibbonPanel newPanel = a.CreateRibbonPanel(tabName, "glTF Export"); string thisAssemblyPath = Assembly.GetExecutingAssembly().Location; PushButtonData button1Data = new PushButtonData("command", "GO", thisAssemblyPath, "glTFRevitExport.Command"); PushButton pushButton1 = newPanel.AddItem(button1Data) as PushButton; pushButton1.LargeImage = BmpImageSource(@"glTFRevitExport.Embedded_Media.large.png"); ///////////////////// ADD STACKED BUTTONS //////////////////////////////////////////// //PushButtonData tagSData = new PushButtonData("sButton1", // "Button1", thisAssemblyPath, "glTFRevitExport.class"); //PushButtonData tagLData = new PushButtonData("sButton2", // "Button2", thisAssemblyPath, "glTFRevitExport.class"); //newPanel.AddSeparator(); //IList stackedItems = newPanel.AddStackedItems(tagSData, tagLData); //if(stackedItems.Count>1) //{ // PushButton tagS = stackedItems[0] as PushButton; // tagS.Image = BmpImageSource(@"glTFRevitExport.Embedded_Media.small.png"); // PushButton tagL = stackedItems[1] as PushButton; // tagL.Image = BmpImageSource(@"glTFRevitExport.Embedded_Media.small.png"); //} /////////////////////////////////////////////////////////////////////////////////////////// return Result.Succeeded; } public Result OnShutdown(UIControlledApplication a) { return Result.Succeeded; } private ImageSource BmpImageSource(string embeddedPath) { System.IO.Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedPath); PngBitmapDecoder pngBitmapDecoder = new PngBitmapDecoder(manifestResourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); return pngBitmapDecoder.Frames[0]; } } } ================================================ FILE: glTFRevitExport/Command.cs ================================================ using System; using System.IO; using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using Microsoft.Win32; namespace glTFRevitExport { [Transaction(TransactionMode.Manual)] class Command : IExternalCommand { public void ExportView3D(View3D view3d, string filename, string directory) { Document doc = view3d.Document; // Use our custom implementation of IExportContext as the exporter context. glTFExportContext ctx = new glTFExportContext(doc, filename, directory); // Create a new custom exporter with the context. CustomExporter exporter = new CustomExporter(doc, ctx); exporter.ShouldStopOnError = true; exporter.Export(view3d); } public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { UIApplication uiapp = commandData.Application; UIDocument uidoc = uiapp.ActiveUIDocument; Application app = uiapp.Application; Document doc = uidoc.Document; View3D view = doc.ActiveView as View3D; if (view == null) { TaskDialog.Show("glTFRevitExport", "You must be in a 3D view to export."); return Result.Failed; } SaveFileDialog fileDialog = new SaveFileDialog(); fileDialog.FileName = "NewProject"; // default file name fileDialog.DefaultExt = ".gltf"; // default file extension bool? dialogResult = fileDialog.ShowDialog(); if (dialogResult == true) { string filename = fileDialog.FileName; string directory = Path.GetDirectoryName(filename) + "\\"; ExportView3D(view, filename, directory); } return Result.Succeeded; } } } ================================================ FILE: glTFRevitExport/Containers.cs ================================================ using System; using System.Collections.Generic; using Autodesk.Revit.DB; namespace glTFRevitExport { /// /// Intermediate data format for /// converting between Revit Polymesh /// and glTF buffers. /// public class GeometryData { public VertexLookupInt vertDictionary = new VertexLookupInt(); public List vertices = new List(); public List normals = new List(); public List uvs = new List(); public List faces = new List(); } /// /// Container for holding a strict set of items /// that is also addressable by a unique ID. /// /// The type of item contained. public class IndexedDictionary { private Dictionary _dict = new Dictionary(); public List List { get; } = new List(); public string CurrentKey { get; private set; } public Dictionary Dict { get { var output = new Dictionary(); foreach (var kvp in _dict) { output.Add(kvp.Key, List[kvp.Value]); } return output; } } /// /// The most recently accessed item (not effected by GetElement()). /// public T CurrentItem { get { return List[_dict[CurrentKey]]; } } /// /// The index of the most recently accessed item (not effected by GetElement()). /// public int CurrentIndex { get { return _dict[CurrentKey]; } } /// /// Add a new item to the list, if it already exists then the /// current item will be set to this item. /// /// Unique identifier for the item. /// The item to add. /// true if item did not already exist. public bool AddOrUpdateCurrent(string uuid, T elem) { if (!_dict.ContainsKey(uuid)) { List.Add(elem); _dict.Add(uuid, (List.Count - 1)); CurrentKey = uuid; return true; } CurrentKey = uuid; return false; } /// /// Check if the container already has an item with this key. /// /// Unique identifier for the item. /// public bool Contains(string uuid) { return _dict.ContainsKey(uuid); } /// /// Returns the index for an item given it's unique identifier. /// /// Unique identifier for the item. /// index of item or -1 public int GetIndexFromUUID(string uuid) { if (!Contains(uuid)) throw new Exception("Specified item could not be found."); return _dict[uuid]; } /// /// Returns an item given it's unique identifier. /// /// Unique identifier for the item /// the item public T GetElement(string uuid) { int index = GetIndexFromUUID(uuid); return List[index]; } /// /// Returns as item given it's index location. /// /// The item's index location. /// the item public T GetElement(int index) { if (index < 0 || index > List.Count - 1) throw new Exception("Specified item could not be found."); return List[index]; } } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// An integer-based 3D point class. /// public class PointInt : IComparable { public long X { get; set; } public long Y { get; set; } public long Z { get; set; } /// /// Consider a Revit length zero /// if is smaller than this. /// const double _eps = 1.0e-9; /// /// Conversion factor from feet to millimetres. /// const double _feet_to_mm = 25.4 * 12; /// /// Conversion a given length value /// from feet to millimetre. /// public static long ConvertFeetToMillimetres(double d) { if (0 < d) { return _eps > d ? 0 : (long)(_feet_to_mm * d + 0.5); } else { return _eps > -d ? 0 : (long)(_feet_to_mm * d - 0.5); } } public PointInt(XYZ p, bool switch_coordinates) { X = ConvertFeetToMillimetres(p.X); Y = ConvertFeetToMillimetres(p.Y); Z = ConvertFeetToMillimetres(p.Z); if (switch_coordinates) { X = -X; long tmp = Y; Y = Z; Z = tmp; } } public int CompareTo(PointInt a) { long d = X - a.X; if (0 == d) { d = Y - a.Y; if (0 == d) { d = Z - a.Z; } } return (0 == d) ? 0 : ((0 < d) ? 1 : -1); } } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// A vertex lookup class to eliminate /// duplicate vertex definitions. /// public class VertexLookupInt : Dictionary { /// /// Define equality for integer-based PointInt. /// class PointIntEqualityComparer : IEqualityComparer { public bool Equals(PointInt p, PointInt q) { return 0 == p.CompareTo(q); } public int GetHashCode(PointInt p) { return (p.X.ToString() + "," + p.Y.ToString() + "," + p.Z.ToString()) .GetHashCode(); } } public VertexLookupInt() : base(new PointIntEqualityComparer()) { } /// /// Return the index of the given vertex, /// adding a new entry if required. /// public int AddVertex(PointInt p) { return ContainsKey(p) ? this[p] : this[p] = Count; } } } ================================================ FILE: glTFRevitExport/GLTFManager.cs ================================================ using Autodesk.Revit.DB; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Security.Cryptography; using System.Runtime.Serialization.Formatters.Binary; using System.IO; using System.Diagnostics; namespace glTFRevitExport { static class ManagerUtils { static public List ConvertXForm(Transform xform) { if (xform == null || xform.IsIdentity) return null; var BasisX = xform.BasisX; var BasisY = xform.BasisY; var BasisZ = xform.BasisZ; var Origin = xform.Origin; var OriginX = PointInt.ConvertFeetToMillimetres(Origin.X); var OriginY = PointInt.ConvertFeetToMillimetres(Origin.Y); var OriginZ = PointInt.ConvertFeetToMillimetres(Origin.Z); List glXform = new List(16) { BasisX.X, BasisX.Y, BasisX.Z, 0, BasisY.X, BasisY.Y, BasisY.Z, 0, BasisZ.X, BasisZ.Y, BasisZ.Z, 0, OriginX, OriginY, OriginZ, 1 }; return glXform; } public class HashSearch { string _S; public HashSearch(string s) { _S = s; } public bool EqualTo(HashedType d) { return d.hashcode.Equals(_S); } } static public string GenerateSHA256Hash(T data) { var binFormatter = new BinaryFormatter(); var mStream = new MemoryStream(); binFormatter.Serialize(mStream, data); using (SHA256 hasher = SHA256.Create()) { mStream.Position = 0; byte[] byteHash = hasher.ComputeHash(mStream); var sBuilder = new StringBuilder(); for (int i = 0; i < byteHash.Length; i++) { sBuilder.Append(byteHash[i].ToString("x2")); } return sBuilder.ToString(); } } } class GLTFManager { /// /// Flag to write coords as Z up instead of Y up (if true). /// CAUTION: With local coordinate systems and transforms, this no longer /// produces expected results. TODO on fixing it, however there is a larger /// philisophical debtate to be had over whether flipping coordinates in /// source CAD applications should EVER be the correct thing to do (as opposed to /// flipping the camera in the viewer). /// private bool _flipCoords = false; /// /// Toggles the export of JSON properties as a glTF Extras /// object on each node. /// private bool _exportProperties = true; /// /// Stateful, uuid indexable list of all materials in the export. /// private IndexedDictionary materialDict = new IndexedDictionary(); /// /// Dictionary of nodes keyed to their unique id. /// private Dictionary nodeDict = new Dictionary(); /// /// Hashable container for mesh data, to aid instancing. /// private List meshContainers = new List(); /// /// List of root nodes defining scenes. /// public List scenes = new List(); /// /// List of all buffers referencing the binary file data. /// public List buffers = new List(); /// /// List of all BufferViews referencing the buffers. /// public List bufferViews = new List(); /// /// List of all Accessors referencing the BufferViews. /// public List accessors = new List(); /// /// Container for the vertex/face/normal information /// that will be serialized into a binary format /// for the final *.bin files. /// public List binaryFileData = new List(); /// /// Ordered list of all nodes /// public List nodes { get { var list = nodeDict.Values.ToList(); return list.OrderBy(x => x.index).Select(x => x.ToGLTFNode()).ToList(); } } /// /// Returns true if the unique id is already present in the list of nodes. /// /// /// public bool containsNode(string uniqueId) { return nodeDict.ContainsKey(uniqueId); } /// /// List of all materials referenced by meshes. /// public List materials { get { return materialDict.List; } } /// /// List of all meshes referenced by nodes. /// public List meshes { get { return meshContainers.Select(x => x.contents).ToList(); } } /// /// Stack maintaining the uniqueId's of each node down /// the current scene graph branch. /// private Stack parentStack = new Stack(); /// /// The uniqueId of the currently open node. /// private string currentNodeId { get { return parentStack.Peek(); } } /// /// Stack maintaining the geometry containers for each /// node down the current scene graph branch. These are popped /// as we retreat back up the graph. /// private Stack> geometryStack = new Stack>(); /// /// The geometry container for the currently open node. /// private Dictionary currentGeom { get { return geometryStack.Peek(); } } /// /// Returns proper tab alignment for displaying element /// hierarchy in debug printing. /// public string formatDebugHeirarchy { get { string spaces = ""; for (int i = 0; i < parentStack.Count; i++) { spaces += " "; } return spaces; } } public void Start(bool exportProperties = true) { this._exportProperties = exportProperties; Node rootNode = new Node(0); rootNode.children = new List(); nodeDict.Add(rootNode.id, rootNode); parentStack.Push(rootNode.id); glTFScene defaultScene = new glTFScene(); defaultScene.nodes.Add(0); scenes.Add(defaultScene); } public glTFContainer Finish() { glTF model = new glTF(); model.asset = new glTFVersion(); model.scenes = scenes; model.nodes = nodes; model.meshes = meshes; model.materials = materials; model.buffers = buffers; model.bufferViews = bufferViews; model.accessors = accessors; glTFContainer container = new glTFContainer(); container.glTF = model; container.binaries = binaryFileData; return container; } public void OpenNode(Element elem, Transform xform = null, bool isInstance = false) { //// TODO: [RM] Commented out because this is likely to be very buggy and not the //// correct solution intent is to prevent creation of new nodes when a symbol //// is a child of an instance of the same type. //// Witness: parking spaces and stair railings for examples of two //// different issues with the behavior //if (isInstance == true && elem is FamilySymbol) //{ // FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance; // if ( // parentInstance != null && // parentInstance.Symbol != null && // elem.Name == parentInstance.Symbol.Name // ) // { // nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform); // return; // } // //nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform); // //return; //} bool exportNodeProperties = _exportProperties; if (isInstance == true && elem is FamilySymbol) exportNodeProperties = false; Node node = new Node(elem, nodeDict.Count, exportNodeProperties, isInstance, formatDebugHeirarchy); if (parentStack.Count > 0) { string parentId = parentStack.Peek(); Node parentNode = nodeDict[parentId]; if (parentNode.children == null) parentNode.children = new List(); nodeDict[parentId].children.Add(node.index); } parentStack.Push(node.id); if (xform != null) { node.matrix = ManagerUtils.ConvertXForm(xform); } nodeDict.Add(node.id, node); OpenGeometry(); Debug.WriteLine(String.Format("{0}Node Open", formatDebugHeirarchy)); } public void CloseNode(Element elem = null, bool isInstance = false) { //// TODO: [RM] Commented out because this is likely to be very buggy and not the //// correct solution intent is to prevent creation of new nodes when a symbol //// is a child of an instance of the same type. //// Witness: parking spaces and stair railings for examples of two //// different issues with the behavior //if (isInstance && elem is FamilySymbol) //{ // FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance; // if ( // parentInstance != null && // parentInstance.Symbol != null && // elem.Name == parentInstance.Symbol.Name // ) // { // return; // } // //return; //} Debug.WriteLine(String.Format("{0}Closing Node", formatDebugHeirarchy)); if (currentGeom != null) { CloseGeometry(); } Debug.WriteLine(String.Format("{0} Node Closed", formatDebugHeirarchy)); parentStack.Pop(); } public void SwitchMaterial(MaterialNode matNode, string name = null, string id = null) { glTFMaterial gl_mat = new glTFMaterial(); gl_mat.name = name; glTFPBR pbr = new glTFPBR(); pbr.baseColorFactor = new List() { matNode.Color.Red / 255f, matNode.Color.Green / 255f, matNode.Color.Blue / 255f, 1f - (float)matNode.Transparency }; pbr.metallicFactor = 0f; pbr.roughnessFactor = 1f; gl_mat.pbrMetallicRoughness = pbr; materialDict.AddOrUpdateCurrent(id, gl_mat); } public void OpenGeometry() { geometryStack.Push(new Dictionary()); } public void OnGeometry(PolymeshTopology polymesh) { if (currentNodeId == null) throw new Exception(); string vertex_key = currentNodeId + "_" + materialDict.CurrentKey; if (currentGeom.ContainsKey(vertex_key) == false) { currentGeom.Add(vertex_key, new GeometryData()); } // Populate normals from this polymesh IList norms = polymesh.GetNormals(); foreach (XYZ norm in norms) { currentGeom[vertex_key].normals.Add(norm.X); currentGeom[vertex_key].normals.Add(norm.Y); currentGeom[vertex_key].normals.Add(norm.Z); } // Populate vertex and faces data IList pts = polymesh.GetPoints(); foreach (PolymeshFacet facet in polymesh.GetFacets()) { int v1 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V1], _flipCoords)); int v2 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V2], _flipCoords)); int v3 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V3], _flipCoords)); currentGeom[vertex_key].faces.Add(v1); currentGeom[vertex_key].faces.Add(v2); currentGeom[vertex_key].faces.Add(v3); } } public void CloseGeometry() { Debug.WriteLine(String.Format("{0} Closing Geometry", formatDebugHeirarchy)); // Create the new mesh and populate the primitives with GeometryData glTFMesh mesh = new glTFMesh(); mesh.primitives = new List(); // transfer ordered vertices from vertex dictionary to vertices list foreach (KeyValuePair key_geom in currentGeom) { string key = key_geom.Key; GeometryData geom = key_geom.Value; foreach (KeyValuePair point_index in geom.vertDictionary) { PointInt point = point_index.Key; geom.vertices.Add(point.X); geom.vertices.Add(point.Y); geom.vertices.Add(point.Z); } // convert GeometryData objects into glTFMeshPrimitive string material_key = key.Split('_')[1]; glTFBinaryData bufferMeta = processGeometry(geom, key); if (bufferMeta.hashcode != null) { binaryFileData.Add(bufferMeta); } glTFMeshPrimitive primative = new glTFMeshPrimitive(); primative.attributes.POSITION = bufferMeta.vertexAccessorIndex; primative.indices = bufferMeta.indexAccessorIndex; primative.material = materialDict.GetIndexFromUUID(material_key); // TODO: Add normal attribute accessor index here mesh.primitives.Add(primative); } // glTF entity can not be empty if (mesh.primitives.Count() > 0) { // Prevent mesh duplication by hash checking string meshHash = ManagerUtils.GenerateSHA256Hash(mesh); ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(meshHash); int idx = meshContainers.FindIndex(hs.EqualTo); if (idx != -1) { // set the current nodes mesh index to the already // created mesh location. nodeDict[currentNodeId].mesh = idx; } else { // create new mesh and add it's index to the current node. MeshContainer mc = new MeshContainer(); mc.hashcode = meshHash; mc.contents = mesh; meshContainers.Add(mc); nodeDict[currentNodeId].mesh = meshContainers.Count - 1; } } geometryStack.Pop(); return; } /// /// Takes the intermediate geometry data and performs the calculations /// to convert that into glTF buffers, views, and accessors. /// /// /// Unique name for the .bin file that will be produced. /// private glTFBinaryData processGeometry(GeometryData geom, string name) { // TODO: rename this type to glTFBufferMeta ? glTFBinaryData bufferData = new glTFBinaryData(); glTFBinaryBufferContents bufferContents = new glTFBinaryBufferContents(); foreach (var coord in geom.vertices) { float vFloat = Convert.ToSingle(coord); bufferContents.vertexBuffer.Add(vFloat); } foreach (var index in geom.faces) { bufferContents.indexBuffer.Add(index); } // Prevent buffer duplication by hash checking string calculatedHash = ManagerUtils.GenerateSHA256Hash(bufferContents); ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(calculatedHash); var match = binaryFileData.Find(hs.EqualTo); if (match != null) { // return previously created buffer metadata bufferData.vertexAccessorIndex = match.vertexAccessorIndex; bufferData.indexAccessorIndex = match.indexAccessorIndex; return bufferData; } else { // add a buffer glTFBuffer buffer = new glTFBuffer(); buffer.uri = name + ".bin"; buffers.Add(buffer); int bufferIdx = buffers.Count - 1; /** * Buffer Data **/ bufferData.name = buffer.uri; bufferData.contents = bufferContents; // TODO: Uncomment for normals //foreach (var normal in geomData.normals) //{ // bufferData.normalBuffer.Add((float)normal); //} // Get max and min for vertex data float[] vertexMinMax = Util.GetVec3MinMax(bufferContents.vertexBuffer); // Get max and min for index data int[] faceMinMax = Util.GetScalarMinMax(bufferContents.indexBuffer); // TODO: Uncomment for normals // Get max and min for normal data //float[] normalMinMax = getVec3MinMax(bufferData.normalBuffer); /** * BufferViews **/ // Add a vec3 buffer view int elementsPerVertex = 3; int bytesPerElement = 4; int bytesPerVertex = elementsPerVertex * bytesPerElement; int numVec3 = (geom.vertices.Count) / elementsPerVertex; int sizeOfVec3View = numVec3 * bytesPerVertex; glTFBufferView vec3View = new glTFBufferView(); vec3View.buffer = bufferIdx; vec3View.byteOffset = 0; vec3View.byteLength = sizeOfVec3View; vec3View.target = Targets.ARRAY_BUFFER; bufferViews.Add(vec3View); int vec3ViewIdx = bufferViews.Count - 1; // TODO: Add a normals (vec3) buffer view // Add a faces / indexes buffer view int elementsPerIndex = 1; int bytesPerIndexElement = 4; int bytesPerIndex = elementsPerIndex * bytesPerIndexElement; int numIndexes = geom.faces.Count; int sizeOfIndexView = numIndexes * bytesPerIndex; glTFBufferView facesView = new glTFBufferView(); facesView.buffer = bufferIdx; facesView.byteOffset = vec3View.byteLength; facesView.byteLength = sizeOfIndexView; facesView.target = Targets.ELEMENT_ARRAY_BUFFER; bufferViews.Add(facesView); int facesViewIdx = bufferViews.Count - 1; buffers[bufferIdx].byteLength = vec3View.byteLength + facesView.byteLength; /** * Accessors **/ // add a position accessor glTFAccessor positionAccessor = new glTFAccessor(); positionAccessor.bufferView = vec3ViewIdx; positionAccessor.byteOffset = 0; positionAccessor.componentType = ComponentType.FLOAT; positionAccessor.count = geom.vertices.Count / elementsPerVertex; positionAccessor.type = "VEC3"; positionAccessor.max = new List() { vertexMinMax[1], vertexMinMax[3], vertexMinMax[5] }; positionAccessor.min = new List() { vertexMinMax[0], vertexMinMax[2], vertexMinMax[4] }; accessors.Add(positionAccessor); bufferData.vertexAccessorIndex = accessors.Count - 1; // TODO: Uncomment for normals // add a normals accessor //glTFAccessor normalsAccessor = new glTFAccessor(); //normalsAccessor.bufferView = vec3ViewIdx; //normalsAccessor.byteOffset = (positionAccessor.count) * bytesPerVertex; //normalsAccessor.componentType = ComponentType.FLOAT; //normalsAccessor.count = geom.data.normals.Count / elementsPerVertex; //normalsAccessor.type = "VEC3"; //normalsAccessor.max = new List() { normalMinMax[1], normalMinMax[3], normalMinMax[5] }; //normalsAccessor.min = new List() { normalMinMax[0], normalMinMax[2], normalMinMax[4] }; //this.accessors.Add(normalsAccessor); //bufferData.normalsAccessorIndex = this.accessors.Count - 1; // add a face accessor glTFAccessor faceAccessor = new glTFAccessor(); faceAccessor.bufferView = facesViewIdx; faceAccessor.byteOffset = 0; faceAccessor.componentType = ComponentType.UNSIGNED_INT; faceAccessor.count = numIndexes; faceAccessor.type = "SCALAR"; faceAccessor.max = new List() { faceMinMax[1] }; faceAccessor.min = new List() { faceMinMax[0] }; accessors.Add(faceAccessor); bufferData.indexAccessorIndex = accessors.Count - 1; bufferData.hashcode = calculatedHash; return bufferData; } } } class Node : glTFNode { public int index; public string id; public bool isFinalized = false; public Element element; public Node(Element elem, int index, bool exportProperties = true, bool isInstance = false, string heirarchyFormat = "") { Debug.WriteLine(String.Format("{1} Creating new node: {0}", elem, heirarchyFormat)); this.element = elem; this.name = Util.ElementDescription(elem); this.id = isInstance ? elem.UniqueId + "::" + Guid.NewGuid().ToString() : elem.UniqueId; this.index = index; Debug.WriteLine(String.Format("{1} Name:{0}", this.name, heirarchyFormat)); if (exportProperties) { // get the extras for this element glTFExtras extras = new glTFExtras(); extras.UniqueId = elem.UniqueId; //var properties = Util.GetElementProperties(elem, true); //if (properties != null) extras.Properties = properties; extras.Properties = Util.GetElementProperties(elem, true); this.extras = extras; } Debug.WriteLine(String.Format("{0} Exported Properties", heirarchyFormat)); } public Node(int index) { this.name = "::rootNode::"; this.id = System.Guid.NewGuid().ToString(); this.index = index; } public glTFNode ToGLTFNode() { glTFNode node = new glTFNode(); node.name = this.name; node.mesh = this.mesh; node.matrix = this.matrix; node.extras = this.extras; node.children = this.children; return node; } } } ================================================ FILE: glTFRevitExport/GlTFExportContext.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.IO; using Autodesk.Revit.DB; using Newtonsoft.Json; using System.Diagnostics; using System.Runtime.Serialization.Formatters.Binary; using System.Security.Cryptography; using System.Text; namespace glTFRevitExport { public class glTFExportConfigs { /// /// Flag to export all buffers into a single .bin file (if true). /// public bool SingleBinary = true; /// /// Flag to export all the properties for each element. /// public bool ExportProperties = true; /// /// Flag to write coords as Z up instead of Y up (if true). /// public bool FlipCoords = true; /// /// Include non-standard elements that are not part of /// official glTF spec. If false, non-standard elements will be excluded /// public bool IncludeNonStdElements = true; } public class glTFExportContext : IExportContext { private glTFExportConfigs _cfgs = new glTFExportConfigs(); /// /// The name for the export files /// private string _filename; /// /// The directory for the export files /// private string _directory; private bool _skipElementFlag = false; private GLTFManager manager = new GLTFManager(); private Stack documentStack = new Stack(); private Document _doc { get { return documentStack.Peek(); } } public glTFExportContext(Document doc, string filename, string directory, glTFExportConfigs configs = null) { documentStack.Push(doc); // ensure filename is really a file name and no extension _filename = Path.GetFileNameWithoutExtension(filename); _directory = directory; _cfgs = configs is null ? _cfgs : configs; } /// /// Runs once at beginning of export. Sets up the root node /// and scene. /// /// public bool Start() { Debug.WriteLine("Starting..."); manager.Start(_cfgs.ExportProperties); return true; } /// /// Runs once at end of export. Serializes the gltf /// properties and wites out the *.gltf and *.bin files. /// public void Finish() { Debug.WriteLine("Finishing..."); glTFContainer container = manager.Finish(); if (_cfgs.IncludeNonStdElements) { // TODO: [RM] Standardize what non glTF spec elements will go into // this "BIM glTF superset" and write a spec for it. Gridlines below // are an example. // Add gridlines as gltf nodes in the format: // Origin {Vec3}, Direction {Vec3}, Length {double} FilteredElementCollector col = new FilteredElementCollector(_doc) .OfClass(typeof(Grid)); var grids = col.ToElements(); foreach (Grid g in grids) { Line l = g.Curve as Line; var origin = l.Origin; var direction = l.Direction; var length = l.Length; var xtras = new glTFExtras(); var grid = new GridParameters(); grid.origin = new List() { origin.X, origin.Y, origin.Z }; grid.direction = new List() { direction.X, direction.Y, direction.Z }; grid.length = length; xtras.GridParameters = grid; xtras.UniqueId = g.UniqueId; xtras.Properties = Util.GetElementProperties(g, true); var gridNode = new glTFNode(); gridNode.name = g.Name; gridNode.extras = xtras; container.glTF.nodes.Add(gridNode); container.glTF.nodes[0].children.Add(container.glTF.nodes.Count - 1); } } if (_cfgs.SingleBinary) { int bytePosition = 0; int currentBuffer = 0; foreach (var view in container.glTF.bufferViews) { if (view.buffer == 0) { bytePosition += view.byteLength; continue; } if (view.buffer != currentBuffer) { view.buffer = 0; view.byteOffset = bytePosition; bytePosition += view.byteLength; } } glTFBuffer buffer = new glTFBuffer(); buffer.uri = _filename + ".bin"; buffer.byteLength = bytePosition; container.glTF.buffers.Clear(); container.glTF.buffers.Add(buffer); using (FileStream f = File.Create(Path.Combine(_directory, buffer.uri))) { using (BinaryWriter writer = new BinaryWriter(f)) { foreach (var bin in container.binaries) { foreach (var coord in bin.contents.vertexBuffer) { writer.Write((float)coord); } // TODO: add writer for normals buffer foreach (var index in bin.contents.indexBuffer) { writer.Write((int)index); } } } } } else { // Write the *.bin files foreach (var bin in container.binaries) { using (FileStream f = File.Create(Path.Combine(_directory, bin.name))) { using (BinaryWriter writer = new BinaryWriter(f)) { foreach (var coord in bin.contents.vertexBuffer) { writer.Write((float)coord); } // TODO: add writer for normals buffer foreach (var index in bin.contents.indexBuffer) { writer.Write((int)index); } } } } } // Write the *.gltf file string serializedModel = JsonConvert.SerializeObject(container.glTF, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); File.WriteAllText(Path.Combine(_directory, _filename + ".gltf"), serializedModel); } /// /// Runs once for each element. /// /// ElementId of Element being processed /// public RenderNodeAction OnElementBegin(ElementId elementId) { Element e = _doc.GetElement(elementId); Debug.WriteLine(String.Format("{2}OnElementBegin: {1}-{0}", e.Name, elementId, manager.formatDebugHeirarchy)); if (manager.containsNode(e.UniqueId)) { // Duplicate element, skip adding. Debug.WriteLine(String.Format("{0} Duplicate Element!", manager.formatDebugHeirarchy)); _skipElementFlag = true; return RenderNodeAction.Skip; } manager.OpenNode(e); return RenderNodeAction.Proceed; } /// /// Runs every time, and immediately prior to, a mesh being processed (OnPolymesh). /// It supplies the material for the mesh, and we use this to create a new material /// in our material container, or switch the current material if it already exists. /// TODO: Handle more complex materials. /// /// public void OnMaterial(MaterialNode matNode) { Debug.WriteLine(String.Format("{0} OnMaterial", manager.formatDebugHeirarchy)); string matName; string uniqueId; ElementId id = matNode.MaterialId; if (id != ElementId.InvalidElementId) { Element m = _doc.GetElement(matNode.MaterialId); matName = m.Name; uniqueId = m.UniqueId; } else { uniqueId = string.Format("r{0}g{1}b{2}", matNode.Color.Red.ToString(), matNode.Color.Green.ToString(), matNode.Color.Blue.ToString()); matName = string.Format("MaterialNode_{0}_{1}", Util.ColorToInt(matNode.Color), Util.RealString(matNode.Transparency * 100)); } Debug.WriteLine(String.Format("{1} Material: {0}", matName, manager.formatDebugHeirarchy)); manager.SwitchMaterial(matNode, matName, uniqueId); } /// /// Runs for every polymesh being processed. Typically this is a single face /// of an element's mesh. Vertices and faces are keyed on the element/material combination /// (this is important because within a single element, materials can be changed and /// repeated in unknown order). /// /// public void OnPolymesh(PolymeshTopology polymesh) { Debug.WriteLine(String.Format("{0} OnPolymesh", manager.formatDebugHeirarchy)); manager.OnGeometry(polymesh); } /// /// Runs at the end of an element being processed, after all other calls for that element. /// /// public void OnElementEnd(ElementId elementId) { Debug.WriteLine(String.Format("{0}OnElementEnd", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2))); if (_skipElementFlag) { _skipElementFlag = false; return; } manager.CloseNode(); } /// /// This is called when family instances are encountered, after OnElementBegin. /// We're using it here to maintain the transform stack for that element's heirarchy. /// /// /// public RenderNodeAction OnInstanceBegin(InstanceNode node) { Debug.WriteLine(String.Format("{0}OnInstanceBegin", manager.formatDebugHeirarchy)); ElementId symId = node.GetSymbolId(); Element symElem = _doc.GetElement(symId); Debug.WriteLine(String.Format("{2}OnInstanceBegin: {0}-{1}", symId, symElem.Name, manager.formatDebugHeirarchy)); var nodeXform = node.GetTransform(); manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true); return RenderNodeAction.Proceed; } /// /// This is called when family instances are encountered, before OnElementEnd. /// We're using it here to maintain the transform stack for that element's heirarchy. /// /// public void OnInstanceEnd(InstanceNode node) { Debug.WriteLine(String.Format("{0}OnInstanceEnd", manager.formatDebugHeirarchy.Substring(0,manager.formatDebugHeirarchy.Count() - 2))); ElementId symId = node.GetSymbolId(); Element symElem = _doc.GetElement(symId); manager.CloseNode(symElem, true); } public bool IsCanceled() { // This method is invoked many times during the export process. return false; } public RenderNodeAction OnViewBegin(ViewNode node) { // TODO: we could use this to handle multiple scenes in the gltf file. return RenderNodeAction.Proceed; } public void OnViewEnd(ElementId elementId) { // do nothing } public RenderNodeAction OnLinkBegin(LinkNode node) { ElementId symId = node.GetSymbolId(); Element symElem = _doc.GetElement(symId); Debug.WriteLine(String.Format("{2}OnLinkBegin: {0}-{1}", symId, symElem.Name, manager.formatDebugHeirarchy)); var nodeXform = node.GetTransform(); manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true); documentStack.Push(node.GetDocument()); return RenderNodeAction.Proceed; } public void OnLinkEnd(LinkNode node) { Debug.WriteLine(String.Format("{0}OnLinkEnd", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2))); manager.CloseNode(); documentStack.Pop(); } public RenderNodeAction OnFaceBegin(FaceNode node) { return RenderNodeAction.Proceed; } public void OnFaceEnd(FaceNode node) { // This method is invoked only if the // custom exporter was set to include faces. } public void OnRPC(RPCNode node) { // do nothing } public void OnLight(LightNode node) { // do nothing } } } ================================================ FILE: glTFRevitExport/Properties/AssemblyInfo.cs ================================================ using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Windows; // General Information about an assembly is controlled through the following // set of attributes. Change these attribute values to modify the information // associated with an assembly. [assembly: AssemblyTitle("glTFRevitExport")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("glTFRevitExport")] [assembly: AssemblyCopyright("Copyright © 2015")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] // Setting ComVisible to false makes the types in this assembly not visible // to COM components. If you need to access a type in this assembly from // COM, set the ComVisible attribute to true on that type. [assembly: ComVisible(false)] //In order to begin building localizable applications, set //CultureYouAreCodingWith in your .csproj file //inside a . For example, if you are using US english //in your source files, set the to en-US. Then uncomment //the NeutralResourceLanguage attribute below. Update the "en-US" in //the line below to match the UICulture setting in the project file. //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] [assembly: ThemeInfo( ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located //(used if a resource is not found in the page, // or application resource dictionaries) ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located //(used if a resource is not found in the page, // app, or any theme specific resource dictionaries) )] // Version information for an assembly consists of the following four values: // // Major Version // Minor Version // Build Number // Revision // // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] [assembly: AssemblyVersion("1.0.0.0")] [assembly: AssemblyFileVersion("1.0.0.0")] ================================================ FILE: glTFRevitExport/Properties/Resources.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.34209 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace glTFRevitExport.Properties { /// /// A strongly-typed resource class, for looking up localized strings, etc. /// // This class was auto-generated by the StronglyTypedResourceBuilder // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { private static global::System.Resources.ResourceManager resourceMan; private static global::System.Globalization.CultureInfo resourceCulture; [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal Resources() { } /// /// Returns the cached ResourceManager instance used by this class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Resources.ResourceManager ResourceManager { get { if ((resourceMan == null)) { global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("glTFRevitExport.Properties.Resources", typeof(Resources).Assembly); resourceMan = temp; } return resourceMan; } } /// /// Overrides the current thread's CurrentUICulture property for all /// resource lookups using this strongly typed resource class. /// [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] internal static global::System.Globalization.CultureInfo Culture { get { return resourceCulture; } set { resourceCulture = value; } } } } ================================================ FILE: glTFRevitExport/Properties/Resources.resx ================================================  text/microsoft-resx 2.0 System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 ================================================ FILE: glTFRevitExport/Properties/Settings.Designer.cs ================================================ //------------------------------------------------------------------------------ // // This code was generated by a tool. // Runtime Version:4.0.30319.34209 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. // //------------------------------------------------------------------------------ namespace glTFRevitExport.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { return defaultInstance; } } } } ================================================ FILE: glTFRevitExport/Properties/Settings.settings ================================================  ================================================ FILE: glTFRevitExport/Util.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Autodesk.Revit.DB; namespace glTFRevitExport { class Util { public static int[] GetVec3MinMax(List vec3) { int minVertexX = int.MaxValue; int minVertexY = int.MaxValue; int minVertexZ = int.MaxValue; int maxVertexX = int.MinValue; int maxVertexY = int.MinValue; int maxVertexZ = int.MinValue; for (int i = 0; i < vec3.Count; i += 3) { if (vec3[i] < minVertexX) minVertexX = vec3[i]; if (vec3[i] > maxVertexX) maxVertexX = vec3[i]; if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1]; if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1]; if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2]; if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2]; } return new int[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ }; } public static long[] GetVec3MinMax(List vec3) { long minVertexX = long.MaxValue; long minVertexY = long.MaxValue; long minVertexZ = long.MaxValue; long maxVertexX = long.MinValue; long maxVertexY = long.MinValue; long maxVertexZ = long.MinValue; for (int i = 0; i < (vec3.Count / 3); i += 3) { if (vec3[i] < minVertexX) minVertexX = vec3[i]; if (vec3[i] > maxVertexX) maxVertexX = vec3[i]; if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1]; if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1]; if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2]; if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2]; } return new long[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ }; } public static float[] GetVec3MinMax(List vec3) { List xValues = new List(); List yValues = new List(); List zValues = new List(); for (int i = 0; i < vec3.Count; i++) { if ((i % 3) == 0) xValues.Add(vec3[i]); if ((i % 3) == 1) yValues.Add(vec3[i]); if ((i % 3) == 2) zValues.Add(vec3[i]); } float maxX = xValues.Max(); float minX = xValues.Min(); float maxY = yValues.Max(); float minY = yValues.Min(); float maxZ = zValues.Max(); float minZ = zValues.Min(); return new float[] { minX, maxX, minY, maxY, minZ, maxZ }; } public static int[] GetScalarMinMax(List scalars) { int minFaceIndex = int.MaxValue; int maxFaceIndex = int.MinValue; for (int i = 0; i < scalars.Count; i++) { int currentMin = Math.Min(minFaceIndex, scalars[i]); if (currentMin < minFaceIndex) minFaceIndex = currentMin; int currentMax = Math.Max(maxFaceIndex, scalars[i]); if (currentMax > maxFaceIndex) maxFaceIndex = currentMax; } return new int[] { minFaceIndex, maxFaceIndex }; } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Return a string for a real number /// formatted to two decimal places. /// public static string RealString(double a) { return a.ToString("0.##"); } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Return a string for an XYZ point /// or vector with its coordinates /// formatted to two decimal places. /// public static string PointString(XYZ p) { return string.Format("({0},{1},{2})", RealString(p.X), RealString(p.Y), RealString(p.Z)); } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Return an integer value for a Revit Color. /// public static int ColorToInt(Color color) { return ((int)color.Red) << 16 | ((int)color.Green) << 8 | (int)color.Blue; } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Extract a true or false value from the given /// string, accepting yes/no, Y/N, true/false, T/F /// and 1/0. We are extremely tolerant, i.e., any /// value starting with one of the characters y, n, /// t or f is also accepted. Return false if no /// valid Boolean value can be extracted. /// public static bool GetTrueOrFalse(string s, out bool val) { val = false; if (s.Equals(Boolean.TrueString, StringComparison.OrdinalIgnoreCase)) { val = true; return true; } if (s.Equals(Boolean.FalseString, StringComparison.OrdinalIgnoreCase)) { return true; } if (s.Equals("1")) { val = true; return true; } if (s.Equals("0")) { return true; } s = s.ToLower(); if ('t' == s[0] || 'y' == s[0]) { val = true; return true; } if ('f' == s[0] || 'n' == s[0]) { return true; } return false; } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Return a string describing the given element: /// .NET type name, /// category name, /// family and symbol name for a family instance, /// element id and element name. /// public static string ElementDescription(Element e) { if (null == e) { return ""; } // For a wall, the element name equals the // wall type name, which is equivalent to the // family name ... FamilyInstance fi = e as FamilyInstance; string typeName = e.GetType().Name; string categoryName = (null == e.Category) ? string.Empty : e.Category.Name + " "; string familyName = (null == fi) ? string.Empty : fi.Symbol.Family.Name + " "; string symbolName = (null == fi || e.Name.Equals(fi.Symbol.Name)) ? string.Empty : fi.Symbol.Name + " "; return string.Format("{0} {1}{2}{3}<{4} {5}>", typeName, categoryName, familyName, symbolName, e.Id.IntegerValue, e.Name); } /// /// From Jeremy Tammik's RvtVa3c exporter: /// https://github.com/va3c/RvtVa3c /// Return a dictionary of all the given /// element parameter names and values. /// public static Dictionary GetElementProperties(Element e, bool includeType) { IList parameters = e.GetOrderedParameters(); Dictionary a = new Dictionary(parameters.Count); // Add element category if (e.Category != null) { a.Add("Element Category", e.Category.Name); } foreach (Parameter p in parameters) { string key = p.Definition.Name; if (!a.ContainsKey(key)) { string val; if (StorageType.String == p.StorageType) { val = p.AsString(); } else { val = p.AsValueString(); } if (!string.IsNullOrEmpty(val)) { a.Add(key, val); } } } if (includeType) { ElementId idType = e.GetTypeId(); if (idType != null && ElementId.InvalidElementId != idType) { Document doc = e.Document; Element typ = doc.GetElement(idType); parameters = typ.GetOrderedParameters(); foreach (Parameter p in parameters) { string key = "Type " + p.Definition.Name; if (!a.ContainsKey(key)) { string val; if (StorageType.String == p.StorageType) { val = p.AsString(); } else { val = p.AsValueString(); } if (!string.IsNullOrEmpty(val)) { a.Add(key, val); } } } } } if (a.Count == 0) return null; else return a; } } } ================================================ FILE: glTFRevitExport/WPF/MainWindow.xaml ================================================