├── .gitignore ├── README.md ├── glTFRevitExport.sln └── glTFRevitExport ├── App.cs ├── Command.cs ├── Containers.cs ├── Embedded Media ├── large.png └── small.png ├── 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 /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Aa][Rr][Mm]/ 27 | [Aa][Rr][Mm]64/ 28 | bld/ 29 | [Bb]in/ 30 | [Oo]bj/ 31 | [Ll]og/ 32 | [Ll]ogs/ 33 | 34 | # Visual Studio 2015/2017 cache/options directory 35 | .vs/ 36 | # Uncomment if you have tasks that create the project's static files in wwwroot 37 | #wwwroot/ 38 | 39 | # Visual Studio 2017 auto generated files 40 | Generated\ Files/ 41 | 42 | # MSTest test Results 43 | [Tt]est[Rr]esult*/ 44 | [Bb]uild[Ll]og.* 45 | 46 | # NUnit 47 | *.VisualState.xml 48 | TestResult.xml 49 | nunit-*.xml 50 | 51 | # Build Results of an ATL Project 52 | [Dd]ebugPS/ 53 | [Rr]eleasePS/ 54 | dlldata.c 55 | 56 | # Benchmark Results 57 | BenchmarkDotNet.Artifacts/ 58 | 59 | # .NET Core 60 | project.lock.json 61 | project.fragment.lock.json 62 | artifacts/ 63 | 64 | # StyleCop 65 | StyleCopReport.xml 66 | 67 | # Files built by Visual Studio 68 | *_i.c 69 | *_p.c 70 | *_h.h 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.iobj 75 | *.pch 76 | *.pdb 77 | *.ipdb 78 | *.pgc 79 | *.pgd 80 | *.rsp 81 | *.sbr 82 | *.tlb 83 | *.tli 84 | *.tlh 85 | *.tmp 86 | *.tmp_proj 87 | *_wpftmp.csproj 88 | *.log 89 | *.vspscc 90 | *.vssscc 91 | .builds 92 | *.pidb 93 | *.svclog 94 | *.scc 95 | 96 | # Chutzpah Test files 97 | _Chutzpah* 98 | 99 | # Visual C++ cache files 100 | ipch/ 101 | *.aps 102 | *.ncb 103 | *.opendb 104 | *.opensdf 105 | *.sdf 106 | *.cachefile 107 | *.VC.db 108 | *.VC.VC.opendb 109 | 110 | # Visual Studio profiler 111 | *.psess 112 | *.vsp 113 | *.vspx 114 | *.sap 115 | 116 | # Visual Studio Trace Files 117 | *.e2e 118 | 119 | # TFS 2012 Local Workspace 120 | $tf/ 121 | 122 | # Guidance Automation Toolkit 123 | *.gpState 124 | 125 | # ReSharper is a .NET coding add-in 126 | _ReSharper*/ 127 | *.[Rr]e[Ss]harper 128 | *.DotSettings.user 129 | 130 | # TeamCity is a build add-in 131 | _TeamCity* 132 | 133 | # DotCover is a Code Coverage Tool 134 | *.dotCover 135 | 136 | # AxoCover is a Code Coverage Tool 137 | .axoCover/* 138 | !.axoCover/settings.json 139 | 140 | # Coverlet is a free, cross platform Code Coverage Tool 141 | coverage*[.json, .xml, .info] 142 | 143 | # Visual Studio code coverage results 144 | *.coverage 145 | *.coveragexml 146 | 147 | # NCrunch 148 | _NCrunch_* 149 | .*crunch*.local.xml 150 | nCrunchTemp_* 151 | 152 | # MightyMoose 153 | *.mm.* 154 | AutoTest.Net/ 155 | 156 | # Web workbench (sass) 157 | .sass-cache/ 158 | 159 | # Installshield output folder 160 | [Ee]xpress/ 161 | 162 | # DocProject is a documentation generator add-in 163 | DocProject/buildhelp/ 164 | DocProject/Help/*.HxT 165 | DocProject/Help/*.HxC 166 | DocProject/Help/*.hhc 167 | DocProject/Help/*.hhk 168 | DocProject/Help/*.hhp 169 | DocProject/Help/Html2 170 | DocProject/Help/html 171 | 172 | # Click-Once directory 173 | publish/ 174 | 175 | # Publish Web Output 176 | *.[Pp]ublish.xml 177 | *.azurePubxml 178 | # Note: Comment the next line if you want to checkin your web deploy settings, 179 | # but database connection strings (with potential passwords) will be unencrypted 180 | *.pubxml 181 | *.publishproj 182 | 183 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 184 | # checkin your Azure Web App publish settings, but sensitive information contained 185 | # in these scripts will be unencrypted 186 | PublishScripts/ 187 | 188 | # NuGet Packages 189 | *.nupkg 190 | # NuGet Symbol Packages 191 | *.snupkg 192 | # The packages folder can be ignored because of Package Restore 193 | **/[Pp]ackages/* 194 | # except build/, which is used as an MSBuild target. 195 | !**/[Pp]ackages/build/ 196 | # Uncomment if necessary however generally it will be regenerated when needed 197 | #!**/[Pp]ackages/repositories.config 198 | # NuGet v3's project.json files produces more ignorable files 199 | *.nuget.props 200 | *.nuget.targets 201 | 202 | # Microsoft Azure Build Output 203 | csx/ 204 | *.build.csdef 205 | 206 | # Microsoft Azure Emulator 207 | ecf/ 208 | rcf/ 209 | 210 | # Windows Store app package directories and files 211 | AppPackages/ 212 | BundleArtifacts/ 213 | Package.StoreAssociation.xml 214 | _pkginfo.txt 215 | *.appx 216 | *.appxbundle 217 | *.appxupload 218 | 219 | # Visual Studio cache files 220 | # files ending in .cache can be ignored 221 | *.[Cc]ache 222 | # but keep track of directories ending in .cache 223 | !?*.[Cc]ache/ 224 | 225 | # Others 226 | ClientBin/ 227 | ~$* 228 | *~ 229 | *.dbmdl 230 | *.dbproj.schemaview 231 | *.jfm 232 | *.pfx 233 | *.publishsettings 234 | orleans.codegen.cs 235 | 236 | # Including strong name files can present a security risk 237 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 238 | #*.snk 239 | 240 | # Since there are multiple workflows, uncomment next line to ignore bower_components 241 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 242 | #bower_components/ 243 | 244 | # RIA/Silverlight projects 245 | Generated_Code/ 246 | 247 | # Backup & report files from converting an old project file 248 | # to a newer Visual Studio version. Backup files are not needed, 249 | # because we have git ;-) 250 | _UpgradeReport_Files/ 251 | Backup*/ 252 | UpgradeLog*.XML 253 | UpgradeLog*.htm 254 | ServiceFabricBackup/ 255 | *.rptproj.bak 256 | 257 | # SQL Server files 258 | *.mdf 259 | *.ldf 260 | *.ndf 261 | 262 | # Business Intelligence projects 263 | *.rdl.data 264 | *.bim.layout 265 | *.bim_*.settings 266 | *.rptproj.rsuser 267 | *- [Bb]ackup.rdl 268 | *- [Bb]ackup ([0-9]).rdl 269 | *- [Bb]ackup ([0-9][0-9]).rdl 270 | 271 | # Microsoft Fakes 272 | FakesAssemblies/ 273 | 274 | # GhostDoc plugin setting file 275 | *.GhostDoc.xml 276 | 277 | # Node.js Tools for Visual Studio 278 | .ntvs_analysis.dat 279 | node_modules/ 280 | 281 | # Visual Studio 6 build log 282 | *.plg 283 | 284 | # Visual Studio 6 workspace options file 285 | *.opt 286 | 287 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 288 | *.vbw 289 | 290 | # Visual Studio LightSwitch build output 291 | **/*.HTMLClient/GeneratedArtifacts 292 | **/*.DesktopClient/GeneratedArtifacts 293 | **/*.DesktopClient/ModelManifest.xml 294 | **/*.Server/GeneratedArtifacts 295 | **/*.Server/ModelManifest.xml 296 | _Pvt_Extensions 297 | 298 | # Paket dependency manager 299 | .paket/paket.exe 300 | paket-files/ 301 | 302 | # FAKE - F# Make 303 | .fake/ 304 | 305 | # CodeRush personal settings 306 | .cr/personal 307 | 308 | # Python Tools for Visual Studio (PTVS) 309 | __pycache__/ 310 | *.pyc 311 | 312 | # Cake - Uncomment if you are using it 313 | # tools/** 314 | # !tools/packages.config 315 | 316 | # Tabs Studio 317 | *.tss 318 | 319 | # Telerik's JustMock configuration file 320 | *.jmconfig 321 | 322 | # BizTalk build output 323 | *.btp.cs 324 | *.btm.cs 325 | *.odx.cs 326 | *.xsd.cs 327 | 328 | # OpenCover UI analysis results 329 | OpenCover/ 330 | 331 | # Azure Stream Analytics local run output 332 | ASALocalRun/ 333 | 334 | # MSBuild Binary and Structured Log 335 | *.binlog 336 | 337 | # NVidia Nsight GPU debugger configuration file 338 | *.nvuser 339 | 340 | # MFractors (Xamarin productivity tool) working folder 341 | .mfractor/ 342 | 343 | # Local History for Visual Studio 344 | .localhistory/ 345 | 346 | # BeatPulse healthcheck temp database 347 | healthchecksdb 348 | 349 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 350 | MigrationBackup/ 351 | 352 | # Ionide (cross platform F# VS Code tools) working folder 353 | .ionide/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Revit2glTF - A Revit glTF Exporter 2 | 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. 3 | 4 | ## Current To-Do's 5 | - [x] Handle basic material export 6 | - [ ] Handle textured material export 7 | - [ ] Handle normals export 8 | - [ ] Add toggle for exporting each element as a seperate .bin vs a single .glb. 9 | - [x] Add element properties to extras on glTF nodes. 10 | - [ ] Add element properties to a sqlite file referenced by glTF nodes. 11 | -------------------------------------------------------------------------------- /glTFRevitExport.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30320.27 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "glTFRevitExport", "glTFRevitExport\glTFRevitExport.csproj", "{4E089540-E5C0-45D9-BB33-3CBA0E4373B5}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug2017|Any CPU = Debug2017|Any CPU 11 | Debug2018|Any CPU = Debug2018|Any CPU 12 | Debug2019|Any CPU = Debug2019|Any CPU 13 | Debug2020|Any CPU = Debug2020|Any CPU 14 | Debug2021|Any CPU = Debug2021|Any CPU 15 | Release2017|Any CPU = Release2017|Any CPU 16 | Release2018|Any CPU = Release2018|Any CPU 17 | Release2019|Any CPU = Release2019|Any CPU 18 | Release2020|Any CPU = Release2020|Any CPU 19 | Release2021|Any CPU = Release2021|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2017|Any CPU.ActiveCfg = Debug2017|x64 23 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2018|Any CPU.ActiveCfg = Debug2018|x64 24 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2019|Any CPU.ActiveCfg = Debug2019|x64 25 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2020|Any CPU.ActiveCfg = Debug2020|x64 26 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Debug2021|Any CPU.ActiveCfg = Debug2021|x64 27 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2017|Any CPU.ActiveCfg = Release2017|x64 28 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2018|Any CPU.ActiveCfg = Release2018|x64 29 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2019|Any CPU.ActiveCfg = Release2019|x64 30 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2020|Any CPU.ActiveCfg = Release2020|x64 31 | {4E089540-E5C0-45D9-BB33-3CBA0E4373B5}.Release2021|Any CPU.ActiveCfg = Release2021|x64 32 | EndGlobalSection 33 | GlobalSection(SolutionProperties) = preSolution 34 | HideSolutionNode = FALSE 35 | EndGlobalSection 36 | GlobalSection(ExtensibilityGlobals) = postSolution 37 | SolutionGuid = {3549B740-9439-4093-BF66-9FCF49742047} 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /glTFRevitExport/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using System.IO; 7 | using System.Reflection; 8 | using System.Windows.Media; 9 | using System.Windows.Media.Imaging; 10 | using Autodesk.Revit.ApplicationServices; 11 | using Autodesk.Revit.Attributes; 12 | using Autodesk.Revit.DB; 13 | using Autodesk.Revit.UI; 14 | using Autodesk.Revit.UI.Selection; 15 | 16 | namespace glTFRevitExport 17 | { 18 | class App : IExternalApplication 19 | { 20 | public Result OnStartup(UIControlledApplication a) 21 | { 22 | string tabName = "Andersen Tools"; 23 | try 24 | { 25 | a.CreateRibbonTab(tabName); 26 | } 27 | catch (Autodesk.Revit.Exceptions.ArgumentException) 28 | { 29 | // Do nothing. 30 | } 31 | // Add a new ribbon panel 32 | RibbonPanel newPanel = a.CreateRibbonPanel(tabName, "glTF Export"); 33 | string thisAssemblyPath = Assembly.GetExecutingAssembly().Location; 34 | 35 | PushButtonData button1Data = new PushButtonData("command", 36 | "GO", thisAssemblyPath, "glTFRevitExport.Command"); 37 | PushButton pushButton1 = newPanel.AddItem(button1Data) as PushButton; 38 | pushButton1.LargeImage = BmpImageSource(@"glTFRevitExport.Embedded_Media.large.png"); 39 | 40 | ///////////////////// ADD STACKED BUTTONS //////////////////////////////////////////// 41 | //PushButtonData tagSData = new PushButtonData("sButton1", 42 | // "Button1", thisAssemblyPath, "glTFRevitExport.class"); 43 | //PushButtonData tagLData = new PushButtonData("sButton2", 44 | // "Button2", thisAssemblyPath, "glTFRevitExport.class"); 45 | 46 | //newPanel.AddSeparator(); 47 | 48 | //IList stackedItems = newPanel.AddStackedItems(tagSData, tagLData); 49 | //if(stackedItems.Count>1) 50 | //{ 51 | // PushButton tagS = stackedItems[0] as PushButton; 52 | // tagS.Image = BmpImageSource(@"glTFRevitExport.Embedded_Media.small.png"); 53 | // PushButton tagL = stackedItems[1] as PushButton; 54 | // tagL.Image = BmpImageSource(@"glTFRevitExport.Embedded_Media.small.png"); 55 | //} 56 | /////////////////////////////////////////////////////////////////////////////////////////// 57 | 58 | 59 | return Result.Succeeded; 60 | } 61 | 62 | public Result OnShutdown(UIControlledApplication a) 63 | { 64 | return Result.Succeeded; 65 | } 66 | 67 | private ImageSource BmpImageSource(string embeddedPath) 68 | { 69 | System.IO.Stream manifestResourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(embeddedPath); 70 | PngBitmapDecoder pngBitmapDecoder = new PngBitmapDecoder(manifestResourceStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); 71 | return pngBitmapDecoder.Frames[0]; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /glTFRevitExport/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using Autodesk.Revit.ApplicationServices; 4 | using Autodesk.Revit.Attributes; 5 | using Autodesk.Revit.DB; 6 | using Autodesk.Revit.UI; 7 | using Microsoft.Win32; 8 | 9 | namespace glTFRevitExport 10 | { 11 | [Transaction(TransactionMode.Manual)] 12 | class Command : IExternalCommand 13 | { 14 | public void ExportView3D(View3D view3d, string filename, string directory) 15 | { 16 | Document doc = view3d.Document; 17 | 18 | // Use our custom implementation of IExportContext as the exporter context. 19 | glTFExportContext ctx = new glTFExportContext(doc, filename, directory); 20 | // Create a new custom exporter with the context. 21 | CustomExporter exporter = new CustomExporter(doc, ctx); 22 | 23 | exporter.ShouldStopOnError = true; 24 | exporter.Export(view3d); 25 | } 26 | 27 | public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) 28 | { 29 | UIApplication uiapp = commandData.Application; 30 | UIDocument uidoc = uiapp.ActiveUIDocument; 31 | Application app = uiapp.Application; 32 | Document doc = uidoc.Document; 33 | 34 | View3D view = doc.ActiveView as View3D; 35 | if (view == null) 36 | { 37 | TaskDialog.Show("glTFRevitExport", "You must be in a 3D view to export."); 38 | return Result.Failed; 39 | } 40 | 41 | SaveFileDialog fileDialog = new SaveFileDialog(); 42 | fileDialog.FileName = "NewProject"; // default file name 43 | fileDialog.DefaultExt = ".gltf"; // default file extension 44 | 45 | bool? dialogResult = fileDialog.ShowDialog(); 46 | if (dialogResult == true) 47 | { 48 | string filename = fileDialog.FileName; 49 | string directory = Path.GetDirectoryName(filename) + "\\"; 50 | 51 | ExportView3D(view, filename, directory); 52 | } 53 | 54 | return Result.Succeeded; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /glTFRevitExport/Containers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Autodesk.Revit.DB; 4 | 5 | namespace glTFRevitExport 6 | { 7 | /// 8 | /// Intermediate data format for 9 | /// converting between Revit Polymesh 10 | /// and glTF buffers. 11 | /// 12 | public class GeometryData 13 | { 14 | public VertexLookupInt vertDictionary = new VertexLookupInt(); 15 | public List vertices = new List(); 16 | public List normals = new List(); 17 | public List uvs = new List(); 18 | public List faces = new List(); 19 | } 20 | 21 | /// 22 | /// Container for holding a strict set of items 23 | /// that is also addressable by a unique ID. 24 | /// 25 | /// The type of item contained. 26 | public class IndexedDictionary 27 | { 28 | private Dictionary _dict = new Dictionary(); 29 | public List List { get; } = new List(); 30 | public string CurrentKey { get; private set; } 31 | public Dictionary Dict 32 | { 33 | get 34 | { 35 | var output = new Dictionary(); 36 | foreach (var kvp in _dict) 37 | { 38 | output.Add(kvp.Key, List[kvp.Value]); 39 | } 40 | return output; 41 | } 42 | } 43 | 44 | /// 45 | /// The most recently accessed item (not effected by GetElement()). 46 | /// 47 | public T CurrentItem 48 | { 49 | get { return List[_dict[CurrentKey]]; } 50 | } 51 | 52 | /// 53 | /// The index of the most recently accessed item (not effected by GetElement()). 54 | /// 55 | public int CurrentIndex 56 | { 57 | get { return _dict[CurrentKey]; } 58 | } 59 | 60 | /// 61 | /// Add a new item to the list, if it already exists then the 62 | /// current item will be set to this item. 63 | /// 64 | /// Unique identifier for the item. 65 | /// The item to add. 66 | /// true if item did not already exist. 67 | public bool AddOrUpdateCurrent(string uuid, T elem) 68 | { 69 | if (!_dict.ContainsKey(uuid)) 70 | { 71 | List.Add(elem); 72 | _dict.Add(uuid, (List.Count - 1)); 73 | CurrentKey = uuid; 74 | return true; 75 | } 76 | 77 | CurrentKey = uuid; 78 | return false; 79 | } 80 | 81 | /// 82 | /// Check if the container already has an item with this key. 83 | /// 84 | /// Unique identifier for the item. 85 | /// 86 | public bool Contains(string uuid) 87 | { 88 | return _dict.ContainsKey(uuid); 89 | } 90 | 91 | /// 92 | /// Returns the index for an item given it's unique identifier. 93 | /// 94 | /// Unique identifier for the item. 95 | /// index of item or -1 96 | public int GetIndexFromUUID(string uuid) 97 | { 98 | if (!Contains(uuid)) throw new Exception("Specified item could not be found."); 99 | return _dict[uuid]; 100 | } 101 | 102 | /// 103 | /// Returns an item given it's unique identifier. 104 | /// 105 | /// Unique identifier for the item 106 | /// the item 107 | public T GetElement(string uuid) 108 | { 109 | int index = GetIndexFromUUID(uuid); 110 | return List[index]; 111 | } 112 | 113 | /// 114 | /// Returns as item given it's index location. 115 | /// 116 | /// The item's index location. 117 | /// the item 118 | public T GetElement(int index) 119 | { 120 | if (index < 0 || index > List.Count - 1) throw new Exception("Specified item could not be found."); 121 | return List[index]; 122 | } 123 | } 124 | 125 | /// 126 | /// From Jeremy Tammik's RvtVa3c exporter: 127 | /// https://github.com/va3c/RvtVa3c 128 | /// An integer-based 3D point class. 129 | /// 130 | public class PointInt : IComparable 131 | { 132 | public long X { get; set; } 133 | public long Y { get; set; } 134 | public long Z { get; set; } 135 | 136 | /// 137 | /// Consider a Revit length zero 138 | /// if is smaller than this. 139 | /// 140 | const double _eps = 1.0e-9; 141 | 142 | /// 143 | /// Conversion factor from feet to millimetres. 144 | /// 145 | const double _feet_to_mm = 25.4 * 12; 146 | 147 | /// 148 | /// Conversion a given length value 149 | /// from feet to millimetre. 150 | /// 151 | public static long ConvertFeetToMillimetres(double d) 152 | { 153 | if (0 < d) 154 | { 155 | return _eps > d 156 | ? 0 157 | : (long)(_feet_to_mm * d + 0.5); 158 | } 159 | else 160 | { 161 | return _eps > -d 162 | ? 0 163 | : (long)(_feet_to_mm * d - 0.5); 164 | } 165 | } 166 | 167 | public PointInt(XYZ p, bool switch_coordinates) 168 | { 169 | X = ConvertFeetToMillimetres(p.X); 170 | Y = ConvertFeetToMillimetres(p.Y); 171 | Z = ConvertFeetToMillimetres(p.Z); 172 | 173 | if (switch_coordinates) 174 | { 175 | X = -X; 176 | long tmp = Y; 177 | Y = Z; 178 | Z = tmp; 179 | } 180 | } 181 | 182 | public int CompareTo(PointInt a) 183 | { 184 | long d = X - a.X; 185 | if (0 == d) 186 | { 187 | d = Y - a.Y; 188 | if (0 == d) 189 | { 190 | d = Z - a.Z; 191 | } 192 | } 193 | return (0 == d) ? 0 : ((0 < d) ? 1 : -1); 194 | } 195 | } 196 | 197 | /// 198 | /// From Jeremy Tammik's RvtVa3c exporter: 199 | /// https://github.com/va3c/RvtVa3c 200 | /// A vertex lookup class to eliminate 201 | /// duplicate vertex definitions. 202 | /// 203 | public class VertexLookupInt : Dictionary 204 | { 205 | /// 206 | /// Define equality for integer-based PointInt. 207 | /// 208 | class PointIntEqualityComparer : IEqualityComparer 209 | { 210 | public bool Equals(PointInt p, PointInt q) 211 | { 212 | return 0 == p.CompareTo(q); 213 | } 214 | 215 | public int GetHashCode(PointInt p) 216 | { 217 | return (p.X.ToString() 218 | + "," + p.Y.ToString() 219 | + "," + p.Z.ToString()) 220 | .GetHashCode(); 221 | } 222 | } 223 | 224 | public VertexLookupInt() : base(new PointIntEqualityComparer()) 225 | { 226 | } 227 | 228 | /// 229 | /// Return the index of the given vertex, 230 | /// adding a new entry if required. 231 | /// 232 | public int AddVertex(PointInt p) 233 | { 234 | return ContainsKey(p) 235 | ? this[p] 236 | : this[p] = Count; 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /glTFRevitExport/Embedded Media/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/McCulloughRT/Revit2glTF/564515d46e55144f2c4769762773ba77a1970a6b/glTFRevitExport/Embedded Media/large.png -------------------------------------------------------------------------------- /glTFRevitExport/Embedded Media/small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/McCulloughRT/Revit2glTF/564515d46e55144f2c4769762773ba77a1970a6b/glTFRevitExport/Embedded Media/small.png -------------------------------------------------------------------------------- /glTFRevitExport/GLTFManager.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.DB; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using System.Security.Cryptography; 8 | using System.Runtime.Serialization.Formatters.Binary; 9 | using System.IO; 10 | using System.Diagnostics; 11 | 12 | namespace glTFRevitExport 13 | { 14 | static class ManagerUtils 15 | { 16 | static public List ConvertXForm(Transform xform) 17 | { 18 | if (xform == null || xform.IsIdentity) return null; 19 | 20 | var BasisX = xform.BasisX; 21 | var BasisY = xform.BasisY; 22 | var BasisZ = xform.BasisZ; 23 | var Origin = xform.Origin; 24 | var OriginX = PointInt.ConvertFeetToMillimetres(Origin.X); 25 | var OriginY = PointInt.ConvertFeetToMillimetres(Origin.Y); 26 | var OriginZ = PointInt.ConvertFeetToMillimetres(Origin.Z); 27 | 28 | List glXform = new List(16) { 29 | BasisX.X, BasisX.Y, BasisX.Z, 0, 30 | BasisY.X, BasisY.Y, BasisY.Z, 0, 31 | BasisZ.X, BasisZ.Y, BasisZ.Z, 0, 32 | OriginX, OriginY, OriginZ, 1 33 | }; 34 | 35 | return glXform; 36 | } 37 | 38 | public class HashSearch 39 | { 40 | string _S; 41 | public HashSearch(string s) 42 | { 43 | _S = s; 44 | } 45 | public bool EqualTo(HashedType d) 46 | { 47 | return d.hashcode.Equals(_S); 48 | } 49 | } 50 | 51 | static public string GenerateSHA256Hash(T data) 52 | { 53 | var binFormatter = new BinaryFormatter(); 54 | var mStream = new MemoryStream(); 55 | binFormatter.Serialize(mStream, data); 56 | 57 | using (SHA256 hasher = SHA256.Create()) 58 | { 59 | mStream.Position = 0; 60 | byte[] byteHash = hasher.ComputeHash(mStream); 61 | 62 | var sBuilder = new StringBuilder(); 63 | for (int i = 0; i < byteHash.Length; i++) 64 | { 65 | sBuilder.Append(byteHash[i].ToString("x2")); 66 | } 67 | 68 | return sBuilder.ToString(); 69 | } 70 | } 71 | } 72 | 73 | class GLTFManager 74 | { 75 | 76 | /// 77 | /// Flag to write coords as Z up instead of Y up (if true). 78 | /// CAUTION: With local coordinate systems and transforms, this no longer 79 | /// produces expected results. TODO on fixing it, however there is a larger 80 | /// philisophical debtate to be had over whether flipping coordinates in 81 | /// source CAD applications should EVER be the correct thing to do (as opposed to 82 | /// flipping the camera in the viewer). 83 | /// 84 | private bool _flipCoords = false; 85 | /// 86 | /// Toggles the export of JSON properties as a glTF Extras 87 | /// object on each node. 88 | /// 89 | private bool _exportProperties = true; 90 | 91 | /// 92 | /// Stateful, uuid indexable list of all materials in the export. 93 | /// 94 | private IndexedDictionary materialDict = new IndexedDictionary(); 95 | /// 96 | /// Dictionary of nodes keyed to their unique id. 97 | /// 98 | private Dictionary nodeDict = new Dictionary(); 99 | /// 100 | /// Hashable container for mesh data, to aid instancing. 101 | /// 102 | private List meshContainers = new List(); 103 | 104 | /// 105 | /// List of root nodes defining scenes. 106 | /// 107 | public List scenes = new List(); 108 | /// 109 | /// List of all buffers referencing the binary file data. 110 | /// 111 | public List buffers = new List(); 112 | /// 113 | /// List of all BufferViews referencing the buffers. 114 | /// 115 | public List bufferViews = new List(); 116 | /// 117 | /// List of all Accessors referencing the BufferViews. 118 | /// 119 | public List accessors = new List(); 120 | /// 121 | /// Container for the vertex/face/normal information 122 | /// that will be serialized into a binary format 123 | /// for the final *.bin files. 124 | /// 125 | public List binaryFileData = new List(); 126 | 127 | /// 128 | /// Ordered list of all nodes 129 | /// 130 | public List nodes { 131 | get { 132 | var list = nodeDict.Values.ToList(); 133 | return list.OrderBy(x => x.index).Select(x => x.ToGLTFNode()).ToList(); 134 | } 135 | } 136 | 137 | /// 138 | /// Returns true if the unique id is already present in the list of nodes. 139 | /// 140 | /// 141 | /// 142 | public bool containsNode(string uniqueId) 143 | { 144 | return nodeDict.ContainsKey(uniqueId); 145 | } 146 | 147 | /// 148 | /// List of all materials referenced by meshes. 149 | /// 150 | public List materials { 151 | get { 152 | return materialDict.List; 153 | } 154 | } 155 | 156 | /// 157 | /// List of all meshes referenced by nodes. 158 | /// 159 | public List meshes { 160 | get { 161 | return meshContainers.Select(x => x.contents).ToList(); 162 | } 163 | } 164 | 165 | /// 166 | /// Stack maintaining the uniqueId's of each node down 167 | /// the current scene graph branch. 168 | /// 169 | private Stack parentStack = new Stack(); 170 | /// 171 | /// The uniqueId of the currently open node. 172 | /// 173 | private string currentNodeId { 174 | get { 175 | return parentStack.Peek(); 176 | } 177 | } 178 | 179 | /// 180 | /// Stack maintaining the geometry containers for each 181 | /// node down the current scene graph branch. These are popped 182 | /// as we retreat back up the graph. 183 | /// 184 | private Stack> geometryStack = new Stack>(); 185 | /// 186 | /// The geometry container for the currently open node. 187 | /// 188 | private Dictionary currentGeom { 189 | get { 190 | return geometryStack.Peek(); 191 | } 192 | } 193 | 194 | /// 195 | /// Returns proper tab alignment for displaying element 196 | /// hierarchy in debug printing. 197 | /// 198 | public string formatDebugHeirarchy 199 | { 200 | get 201 | { 202 | string spaces = ""; 203 | for (int i = 0; i < parentStack.Count; i++) 204 | { 205 | spaces += " "; 206 | } 207 | return spaces; 208 | } 209 | } 210 | 211 | public void Start(bool exportProperties = true) 212 | { 213 | this._exportProperties = exportProperties; 214 | 215 | Node rootNode = new Node(0); 216 | rootNode.children = new List(); 217 | nodeDict.Add(rootNode.id, rootNode); 218 | parentStack.Push(rootNode.id); 219 | 220 | glTFScene defaultScene = new glTFScene(); 221 | defaultScene.nodes.Add(0); 222 | scenes.Add(defaultScene); 223 | } 224 | 225 | public glTFContainer Finish() 226 | { 227 | glTF model = new glTF(); 228 | model.asset = new glTFVersion(); 229 | model.scenes = scenes; 230 | model.nodes = nodes; 231 | model.meshes = meshes; 232 | model.materials = materials; 233 | model.buffers = buffers; 234 | model.bufferViews = bufferViews; 235 | model.accessors = accessors; 236 | 237 | glTFContainer container = new glTFContainer(); 238 | container.glTF = model; 239 | container.binaries = binaryFileData; 240 | 241 | return container; 242 | } 243 | 244 | public void OpenNode(Element elem, Transform xform = null, bool isInstance = false) 245 | { 246 | //// TODO: [RM] Commented out because this is likely to be very buggy and not the 247 | //// correct solution intent is to prevent creation of new nodes when a symbol 248 | //// is a child of an instance of the same type. 249 | //// Witness: parking spaces and stair railings for examples of two 250 | //// different issues with the behavior 251 | //if (isInstance == true && elem is FamilySymbol) 252 | //{ 253 | // FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance; 254 | // if ( 255 | // parentInstance != null && 256 | // parentInstance.Symbol != null && 257 | // elem.Name == parentInstance.Symbol.Name 258 | // ) 259 | // { 260 | // nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform); 261 | // return; 262 | // } 263 | 264 | // //nodeDict[currentNodeId].matrix = ManagerUtils.ConvertXForm(xform); 265 | // //return; 266 | //} 267 | bool exportNodeProperties = _exportProperties; 268 | if (isInstance == true && elem is FamilySymbol) exportNodeProperties = false; 269 | 270 | Node node = new Node(elem, nodeDict.Count, exportNodeProperties, isInstance, formatDebugHeirarchy); 271 | 272 | if (parentStack.Count > 0) 273 | { 274 | string parentId = parentStack.Peek(); 275 | Node parentNode = nodeDict[parentId]; 276 | if (parentNode.children == null) parentNode.children = new List(); 277 | nodeDict[parentId].children.Add(node.index); 278 | } 279 | 280 | parentStack.Push(node.id); 281 | if (xform != null) 282 | { 283 | node.matrix = ManagerUtils.ConvertXForm(xform); 284 | } 285 | 286 | nodeDict.Add(node.id, node); 287 | 288 | OpenGeometry(); 289 | Debug.WriteLine(String.Format("{0}Node Open", formatDebugHeirarchy)); 290 | } 291 | 292 | public void CloseNode(Element elem = null, bool isInstance = false) 293 | { 294 | //// TODO: [RM] Commented out because this is likely to be very buggy and not the 295 | //// correct solution intent is to prevent creation of new nodes when a symbol 296 | //// is a child of an instance of the same type. 297 | //// Witness: parking spaces and stair railings for examples of two 298 | //// different issues with the behavior 299 | //if (isInstance && elem is FamilySymbol) 300 | //{ 301 | // FamilyInstance parentInstance = nodeDict[currentNodeId].element as FamilyInstance; 302 | // if ( 303 | // parentInstance != null && 304 | // parentInstance.Symbol != null && 305 | // elem.Name == parentInstance.Symbol.Name 306 | // ) 307 | // { 308 | // return; 309 | // } 310 | // //return; 311 | //} 312 | 313 | Debug.WriteLine(String.Format("{0}Closing Node", formatDebugHeirarchy)); 314 | 315 | if (currentGeom != null) 316 | { 317 | CloseGeometry(); 318 | } 319 | 320 | Debug.WriteLine(String.Format("{0} Node Closed", formatDebugHeirarchy)); 321 | parentStack.Pop(); 322 | } 323 | 324 | public void SwitchMaterial(MaterialNode matNode, string name = null, string id = null) 325 | { 326 | glTFMaterial gl_mat = new glTFMaterial(); 327 | gl_mat.name = name; 328 | 329 | glTFPBR pbr = new glTFPBR(); 330 | pbr.baseColorFactor = new List() { 331 | matNode.Color.Red / 255f, 332 | matNode.Color.Green / 255f, 333 | matNode.Color.Blue / 255f, 334 | 1f - (float)matNode.Transparency 335 | }; 336 | pbr.metallicFactor = 0f; 337 | pbr.roughnessFactor = 1f; 338 | gl_mat.pbrMetallicRoughness = pbr; 339 | 340 | materialDict.AddOrUpdateCurrent(id, gl_mat); 341 | } 342 | 343 | public void OpenGeometry() 344 | { 345 | geometryStack.Push(new Dictionary()); 346 | } 347 | 348 | public void OnGeometry(PolymeshTopology polymesh) 349 | { 350 | if (currentNodeId == null) throw new Exception(); 351 | 352 | string vertex_key = currentNodeId + "_" + materialDict.CurrentKey; 353 | if (currentGeom.ContainsKey(vertex_key) == false) 354 | { 355 | currentGeom.Add(vertex_key, new GeometryData()); 356 | } 357 | 358 | // Populate normals from this polymesh 359 | IList norms = polymesh.GetNormals(); 360 | foreach (XYZ norm in norms) 361 | { 362 | currentGeom[vertex_key].normals.Add(norm.X); 363 | currentGeom[vertex_key].normals.Add(norm.Y); 364 | currentGeom[vertex_key].normals.Add(norm.Z); 365 | } 366 | 367 | // Populate vertex and faces data 368 | IList pts = polymesh.GetPoints(); 369 | foreach (PolymeshFacet facet in polymesh.GetFacets()) 370 | { 371 | int v1 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V1], _flipCoords)); 372 | int v2 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V2], _flipCoords)); 373 | int v3 = currentGeom[vertex_key].vertDictionary.AddVertex(new PointInt(pts[facet.V3], _flipCoords)); 374 | 375 | currentGeom[vertex_key].faces.Add(v1); 376 | currentGeom[vertex_key].faces.Add(v2); 377 | currentGeom[vertex_key].faces.Add(v3); 378 | } 379 | } 380 | 381 | public void CloseGeometry() 382 | { 383 | Debug.WriteLine(String.Format("{0} Closing Geometry", formatDebugHeirarchy)); 384 | // Create the new mesh and populate the primitives with GeometryData 385 | glTFMesh mesh = new glTFMesh(); 386 | mesh.primitives = new List(); 387 | 388 | // transfer ordered vertices from vertex dictionary to vertices list 389 | foreach (KeyValuePair key_geom in currentGeom) 390 | { 391 | string key = key_geom.Key; 392 | GeometryData geom = key_geom.Value; 393 | foreach (KeyValuePair point_index in geom.vertDictionary) 394 | { 395 | PointInt point = point_index.Key; 396 | geom.vertices.Add(point.X); 397 | geom.vertices.Add(point.Y); 398 | geom.vertices.Add(point.Z); 399 | } 400 | 401 | // convert GeometryData objects into glTFMeshPrimitive 402 | string material_key = key.Split('_')[1]; 403 | 404 | glTFBinaryData bufferMeta = processGeometry(geom, key); 405 | if (bufferMeta.hashcode != null) 406 | { 407 | binaryFileData.Add(bufferMeta); 408 | } 409 | 410 | glTFMeshPrimitive primative = new glTFMeshPrimitive(); 411 | 412 | primative.attributes.POSITION = bufferMeta.vertexAccessorIndex; 413 | primative.indices = bufferMeta.indexAccessorIndex; 414 | primative.material = materialDict.GetIndexFromUUID(material_key); 415 | // TODO: Add normal attribute accessor index here 416 | 417 | mesh.primitives.Add(primative); 418 | } 419 | 420 | // glTF entity can not be empty 421 | if (mesh.primitives.Count() > 0) { 422 | // Prevent mesh duplication by hash checking 423 | string meshHash = ManagerUtils.GenerateSHA256Hash(mesh); 424 | ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(meshHash); 425 | int idx = meshContainers.FindIndex(hs.EqualTo); 426 | 427 | if (idx != -1) { 428 | // set the current nodes mesh index to the already 429 | // created mesh location. 430 | nodeDict[currentNodeId].mesh = idx; 431 | } 432 | else { 433 | // create new mesh and add it's index to the current node. 434 | MeshContainer mc = new MeshContainer(); 435 | mc.hashcode = meshHash; 436 | mc.contents = mesh; 437 | meshContainers.Add(mc); 438 | nodeDict[currentNodeId].mesh = meshContainers.Count - 1; 439 | } 440 | 441 | } 442 | 443 | geometryStack.Pop(); 444 | return; 445 | } 446 | 447 | /// 448 | /// Takes the intermediate geometry data and performs the calculations 449 | /// to convert that into glTF buffers, views, and accessors. 450 | /// 451 | /// 452 | /// Unique name for the .bin file that will be produced. 453 | /// 454 | private glTFBinaryData processGeometry(GeometryData geom, string name) 455 | { 456 | // TODO: rename this type to glTFBufferMeta ? 457 | glTFBinaryData bufferData = new glTFBinaryData(); 458 | glTFBinaryBufferContents bufferContents = new glTFBinaryBufferContents(); 459 | 460 | foreach (var coord in geom.vertices) 461 | { 462 | float vFloat = Convert.ToSingle(coord); 463 | bufferContents.vertexBuffer.Add(vFloat); 464 | } 465 | foreach (var index in geom.faces) 466 | { 467 | bufferContents.indexBuffer.Add(index); 468 | } 469 | 470 | // Prevent buffer duplication by hash checking 471 | string calculatedHash = ManagerUtils.GenerateSHA256Hash(bufferContents); 472 | ManagerUtils.HashSearch hs = new ManagerUtils.HashSearch(calculatedHash); 473 | var match = binaryFileData.Find(hs.EqualTo); 474 | 475 | if (match != null) 476 | { 477 | // return previously created buffer metadata 478 | bufferData.vertexAccessorIndex = match.vertexAccessorIndex; 479 | bufferData.indexAccessorIndex = match.indexAccessorIndex; 480 | return bufferData; 481 | } 482 | else 483 | { 484 | // add a buffer 485 | glTFBuffer buffer = new glTFBuffer(); 486 | buffer.uri = name + ".bin"; 487 | buffers.Add(buffer); 488 | int bufferIdx = buffers.Count - 1; 489 | 490 | /** 491 | * Buffer Data 492 | **/ 493 | bufferData.name = buffer.uri; 494 | bufferData.contents = bufferContents; 495 | // TODO: Uncomment for normals 496 | //foreach (var normal in geomData.normals) 497 | //{ 498 | // bufferData.normalBuffer.Add((float)normal); 499 | //} 500 | 501 | // Get max and min for vertex data 502 | float[] vertexMinMax = Util.GetVec3MinMax(bufferContents.vertexBuffer); 503 | // Get max and min for index data 504 | int[] faceMinMax = Util.GetScalarMinMax(bufferContents.indexBuffer); 505 | // TODO: Uncomment for normals 506 | // Get max and min for normal data 507 | //float[] normalMinMax = getVec3MinMax(bufferData.normalBuffer); 508 | 509 | /** 510 | * BufferViews 511 | **/ 512 | // Add a vec3 buffer view 513 | int elementsPerVertex = 3; 514 | int bytesPerElement = 4; 515 | int bytesPerVertex = elementsPerVertex * bytesPerElement; 516 | int numVec3 = (geom.vertices.Count) / elementsPerVertex; 517 | int sizeOfVec3View = numVec3 * bytesPerVertex; 518 | glTFBufferView vec3View = new glTFBufferView(); 519 | vec3View.buffer = bufferIdx; 520 | vec3View.byteOffset = 0; 521 | vec3View.byteLength = sizeOfVec3View; 522 | vec3View.target = Targets.ARRAY_BUFFER; 523 | bufferViews.Add(vec3View); 524 | int vec3ViewIdx = bufferViews.Count - 1; 525 | 526 | // TODO: Add a normals (vec3) buffer view 527 | 528 | // Add a faces / indexes buffer view 529 | int elementsPerIndex = 1; 530 | int bytesPerIndexElement = 4; 531 | int bytesPerIndex = elementsPerIndex * bytesPerIndexElement; 532 | int numIndexes = geom.faces.Count; 533 | int sizeOfIndexView = numIndexes * bytesPerIndex; 534 | glTFBufferView facesView = new glTFBufferView(); 535 | facesView.buffer = bufferIdx; 536 | facesView.byteOffset = vec3View.byteLength; 537 | facesView.byteLength = sizeOfIndexView; 538 | facesView.target = Targets.ELEMENT_ARRAY_BUFFER; 539 | bufferViews.Add(facesView); 540 | int facesViewIdx = bufferViews.Count - 1; 541 | 542 | buffers[bufferIdx].byteLength = vec3View.byteLength + facesView.byteLength; 543 | 544 | /** 545 | * Accessors 546 | **/ 547 | // add a position accessor 548 | glTFAccessor positionAccessor = new glTFAccessor(); 549 | positionAccessor.bufferView = vec3ViewIdx; 550 | positionAccessor.byteOffset = 0; 551 | positionAccessor.componentType = ComponentType.FLOAT; 552 | positionAccessor.count = geom.vertices.Count / elementsPerVertex; 553 | positionAccessor.type = "VEC3"; 554 | positionAccessor.max = new List() { vertexMinMax[1], vertexMinMax[3], vertexMinMax[5] }; 555 | positionAccessor.min = new List() { vertexMinMax[0], vertexMinMax[2], vertexMinMax[4] }; 556 | accessors.Add(positionAccessor); 557 | bufferData.vertexAccessorIndex = accessors.Count - 1; 558 | 559 | // TODO: Uncomment for normals 560 | // add a normals accessor 561 | //glTFAccessor normalsAccessor = new glTFAccessor(); 562 | //normalsAccessor.bufferView = vec3ViewIdx; 563 | //normalsAccessor.byteOffset = (positionAccessor.count) * bytesPerVertex; 564 | //normalsAccessor.componentType = ComponentType.FLOAT; 565 | //normalsAccessor.count = geom.data.normals.Count / elementsPerVertex; 566 | //normalsAccessor.type = "VEC3"; 567 | //normalsAccessor.max = new List() { normalMinMax[1], normalMinMax[3], normalMinMax[5] }; 568 | //normalsAccessor.min = new List() { normalMinMax[0], normalMinMax[2], normalMinMax[4] }; 569 | //this.accessors.Add(normalsAccessor); 570 | //bufferData.normalsAccessorIndex = this.accessors.Count - 1; 571 | 572 | // add a face accessor 573 | glTFAccessor faceAccessor = new glTFAccessor(); 574 | faceAccessor.bufferView = facesViewIdx; 575 | faceAccessor.byteOffset = 0; 576 | faceAccessor.componentType = ComponentType.UNSIGNED_INT; 577 | faceAccessor.count = numIndexes; 578 | faceAccessor.type = "SCALAR"; 579 | faceAccessor.max = new List() { faceMinMax[1] }; 580 | faceAccessor.min = new List() { faceMinMax[0] }; 581 | accessors.Add(faceAccessor); 582 | bufferData.indexAccessorIndex = accessors.Count - 1; 583 | 584 | bufferData.hashcode = calculatedHash; 585 | 586 | return bufferData; 587 | } 588 | } 589 | } 590 | 591 | class Node : glTFNode 592 | { 593 | public int index; 594 | public string id; 595 | public bool isFinalized = false; 596 | public Element element; 597 | 598 | public Node(Element elem, int index, bool exportProperties = true, bool isInstance = false, string heirarchyFormat = "") 599 | { 600 | Debug.WriteLine(String.Format("{1} Creating new node: {0}", elem, heirarchyFormat)); 601 | 602 | this.element = elem; 603 | this.name = Util.ElementDescription(elem); 604 | this.id = isInstance ? elem.UniqueId + "::" + Guid.NewGuid().ToString() : elem.UniqueId; 605 | this.index = index; 606 | Debug.WriteLine(String.Format("{1} Name:{0}", this.name, heirarchyFormat)); 607 | 608 | if (exportProperties) 609 | { 610 | // get the extras for this element 611 | glTFExtras extras = new glTFExtras(); 612 | extras.UniqueId = elem.UniqueId; 613 | 614 | //var properties = Util.GetElementProperties(elem, true); 615 | //if (properties != null) extras.Properties = properties; 616 | extras.Properties = Util.GetElementProperties(elem, true); 617 | this.extras = extras; 618 | } 619 | Debug.WriteLine(String.Format("{0} Exported Properties", heirarchyFormat)); 620 | } 621 | public Node(int index) 622 | { 623 | this.name = "::rootNode::"; 624 | this.id = System.Guid.NewGuid().ToString(); 625 | this.index = index; 626 | } 627 | 628 | public glTFNode ToGLTFNode() 629 | { 630 | glTFNode node = new glTFNode(); 631 | node.name = this.name; 632 | node.mesh = this.mesh; 633 | node.matrix = this.matrix; 634 | node.extras = this.extras; 635 | node.children = this.children; 636 | return node; 637 | } 638 | } 639 | } 640 | -------------------------------------------------------------------------------- /glTFRevitExport/GlTFExportContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.IO; 5 | using Autodesk.Revit.DB; 6 | using Newtonsoft.Json; 7 | using System.Diagnostics; 8 | using System.Runtime.Serialization.Formatters.Binary; 9 | using System.Security.Cryptography; 10 | using System.Text; 11 | 12 | namespace glTFRevitExport 13 | { 14 | public class glTFExportConfigs { 15 | /// 16 | /// Flag to export all buffers into a single .bin file (if true). 17 | /// 18 | public bool SingleBinary = true; 19 | 20 | /// 21 | /// Flag to export all the properties for each element. 22 | /// 23 | public bool ExportProperties = true; 24 | 25 | /// 26 | /// Flag to write coords as Z up instead of Y up (if true). 27 | /// 28 | public bool FlipCoords = true; 29 | 30 | /// 31 | /// Include non-standard elements that are not part of 32 | /// official glTF spec. If false, non-standard elements will be excluded 33 | /// 34 | public bool IncludeNonStdElements = true; 35 | } 36 | 37 | public class glTFExportContext : IExportContext 38 | { 39 | private glTFExportConfigs _cfgs = new glTFExportConfigs(); 40 | 41 | /// 42 | /// The name for the export files 43 | /// 44 | private string _filename; 45 | 46 | /// 47 | /// The directory for the export files 48 | /// 49 | private string _directory; 50 | 51 | private bool _skipElementFlag = false; 52 | 53 | private GLTFManager manager = new GLTFManager(); 54 | private Stack documentStack = new Stack(); 55 | private Document _doc 56 | { 57 | get 58 | { 59 | return documentStack.Peek(); 60 | } 61 | } 62 | 63 | public glTFExportContext(Document doc, string filename, string directory, glTFExportConfigs configs = null) 64 | { 65 | documentStack.Push(doc); 66 | 67 | // ensure filename is really a file name and no extension 68 | _filename = Path.GetFileNameWithoutExtension(filename); 69 | _directory = directory; 70 | _cfgs = configs is null ? _cfgs : configs; 71 | } 72 | 73 | /// 74 | /// Runs once at beginning of export. Sets up the root node 75 | /// and scene. 76 | /// 77 | /// 78 | public bool Start() 79 | { 80 | Debug.WriteLine("Starting..."); 81 | manager.Start(_cfgs.ExportProperties); 82 | return true; 83 | } 84 | 85 | /// 86 | /// Runs once at end of export. Serializes the gltf 87 | /// properties and wites out the *.gltf and *.bin files. 88 | /// 89 | public void Finish() 90 | { 91 | Debug.WriteLine("Finishing..."); 92 | 93 | glTFContainer container = manager.Finish(); 94 | 95 | if (_cfgs.IncludeNonStdElements) { 96 | // TODO: [RM] Standardize what non glTF spec elements will go into 97 | // this "BIM glTF superset" and write a spec for it. Gridlines below 98 | // are an example. 99 | 100 | // Add gridlines as gltf nodes in the format: 101 | // Origin {Vec3}, Direction {Vec3}, Length {double} 102 | FilteredElementCollector col = new FilteredElementCollector(_doc) 103 | .OfClass(typeof(Grid)); 104 | 105 | var grids = col.ToElements(); 106 | foreach (Grid g in grids) { 107 | Line l = g.Curve as Line; 108 | 109 | var origin = l.Origin; 110 | var direction = l.Direction; 111 | var length = l.Length; 112 | 113 | var xtras = new glTFExtras(); 114 | var grid = new GridParameters(); 115 | grid.origin = new List() { origin.X, origin.Y, origin.Z }; 116 | grid.direction = new List() { direction.X, direction.Y, direction.Z }; 117 | grid.length = length; 118 | xtras.GridParameters = grid; 119 | xtras.UniqueId = g.UniqueId; 120 | xtras.Properties = Util.GetElementProperties(g, true); 121 | 122 | var gridNode = new glTFNode(); 123 | gridNode.name = g.Name; 124 | gridNode.extras = xtras; 125 | 126 | container.glTF.nodes.Add(gridNode); 127 | container.glTF.nodes[0].children.Add(container.glTF.nodes.Count - 1); 128 | } 129 | } 130 | 131 | if (_cfgs.SingleBinary) 132 | { 133 | int bytePosition = 0; 134 | int currentBuffer = 0; 135 | foreach (var view in container.glTF.bufferViews) 136 | { 137 | if (view.buffer == 0) 138 | { 139 | bytePosition += view.byteLength; 140 | continue; 141 | } 142 | 143 | if (view.buffer != currentBuffer) 144 | { 145 | view.buffer = 0; 146 | view.byteOffset = bytePosition; 147 | bytePosition += view.byteLength; 148 | } 149 | } 150 | 151 | glTFBuffer buffer = new glTFBuffer(); 152 | buffer.uri = _filename + ".bin"; 153 | buffer.byteLength = bytePosition; 154 | container.glTF.buffers.Clear(); 155 | container.glTF.buffers.Add(buffer); 156 | 157 | using (FileStream f = File.Create(Path.Combine(_directory, buffer.uri))) 158 | { 159 | using (BinaryWriter writer = new BinaryWriter(f)) 160 | { 161 | foreach (var bin in container.binaries) 162 | { 163 | foreach (var coord in bin.contents.vertexBuffer) 164 | { 165 | writer.Write((float)coord); 166 | } 167 | // TODO: add writer for normals buffer 168 | foreach (var index in bin.contents.indexBuffer) 169 | { 170 | writer.Write((int)index); 171 | } 172 | } 173 | } 174 | } 175 | } 176 | else 177 | { 178 | // Write the *.bin files 179 | foreach (var bin in container.binaries) 180 | { 181 | using (FileStream f = File.Create(Path.Combine(_directory, bin.name))) 182 | { 183 | using (BinaryWriter writer = new BinaryWriter(f)) 184 | { 185 | foreach (var coord in bin.contents.vertexBuffer) 186 | { 187 | writer.Write((float)coord); 188 | } 189 | // TODO: add writer for normals buffer 190 | foreach (var index in bin.contents.indexBuffer) 191 | { 192 | writer.Write((int)index); 193 | } 194 | } 195 | } 196 | } 197 | } 198 | 199 | // Write the *.gltf file 200 | string serializedModel = JsonConvert.SerializeObject(container.glTF, new JsonSerializerSettings { NullValueHandling = NullValueHandling.Ignore }); 201 | File.WriteAllText(Path.Combine(_directory, _filename + ".gltf"), serializedModel); 202 | } 203 | 204 | /// 205 | /// Runs once for each element. 206 | /// 207 | /// ElementId of Element being processed 208 | /// 209 | public RenderNodeAction OnElementBegin(ElementId elementId) 210 | { 211 | Element e = _doc.GetElement(elementId); 212 | Debug.WriteLine(String.Format("{2}OnElementBegin: {1}-{0}", e.Name, elementId, manager.formatDebugHeirarchy)); 213 | 214 | if (manager.containsNode(e.UniqueId)) 215 | { 216 | // Duplicate element, skip adding. 217 | Debug.WriteLine(String.Format("{0} Duplicate Element!", manager.formatDebugHeirarchy)); 218 | _skipElementFlag = true; 219 | return RenderNodeAction.Skip; 220 | } 221 | 222 | manager.OpenNode(e); 223 | 224 | return RenderNodeAction.Proceed; 225 | } 226 | 227 | /// 228 | /// Runs every time, and immediately prior to, a mesh being processed (OnPolymesh). 229 | /// It supplies the material for the mesh, and we use this to create a new material 230 | /// in our material container, or switch the current material if it already exists. 231 | /// TODO: Handle more complex materials. 232 | /// 233 | /// 234 | public void OnMaterial(MaterialNode matNode) 235 | { 236 | Debug.WriteLine(String.Format("{0} OnMaterial", manager.formatDebugHeirarchy)); 237 | string matName; 238 | string uniqueId; 239 | 240 | ElementId id = matNode.MaterialId; 241 | if (id != ElementId.InvalidElementId) 242 | { 243 | Element m = _doc.GetElement(matNode.MaterialId); 244 | matName = m.Name; 245 | uniqueId = m.UniqueId; 246 | } 247 | else 248 | { 249 | uniqueId = string.Format("r{0}g{1}b{2}", matNode.Color.Red.ToString(), matNode.Color.Green.ToString(), matNode.Color.Blue.ToString()); 250 | matName = string.Format("MaterialNode_{0}_{1}", Util.ColorToInt(matNode.Color), Util.RealString(matNode.Transparency * 100)); 251 | } 252 | 253 | Debug.WriteLine(String.Format("{1} Material: {0}", matName, manager.formatDebugHeirarchy)); 254 | manager.SwitchMaterial(matNode, matName, uniqueId); 255 | } 256 | 257 | /// 258 | /// Runs for every polymesh being processed. Typically this is a single face 259 | /// of an element's mesh. Vertices and faces are keyed on the element/material combination 260 | /// (this is important because within a single element, materials can be changed and 261 | /// repeated in unknown order). 262 | /// 263 | /// 264 | public void OnPolymesh(PolymeshTopology polymesh) 265 | { 266 | Debug.WriteLine(String.Format("{0} OnPolymesh", manager.formatDebugHeirarchy)); 267 | manager.OnGeometry(polymesh); 268 | } 269 | 270 | /// 271 | /// Runs at the end of an element being processed, after all other calls for that element. 272 | /// 273 | /// 274 | public void OnElementEnd(ElementId elementId) 275 | { 276 | Debug.WriteLine(String.Format("{0}OnElementEnd", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2))); 277 | if (_skipElementFlag) 278 | { 279 | _skipElementFlag = false; 280 | return; 281 | } 282 | 283 | manager.CloseNode(); 284 | } 285 | 286 | /// 287 | /// This is called when family instances are encountered, after OnElementBegin. 288 | /// We're using it here to maintain the transform stack for that element's heirarchy. 289 | /// 290 | /// 291 | /// 292 | public RenderNodeAction OnInstanceBegin(InstanceNode node) 293 | { 294 | Debug.WriteLine(String.Format("{0}OnInstanceBegin", manager.formatDebugHeirarchy)); 295 | 296 | ElementId symId = node.GetSymbolId(); 297 | Element symElem = _doc.GetElement(symId); 298 | 299 | Debug.WriteLine(String.Format("{2}OnInstanceBegin: {0}-{1}", symId, symElem.Name, manager.formatDebugHeirarchy)); 300 | 301 | var nodeXform = node.GetTransform(); 302 | manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true); 303 | 304 | return RenderNodeAction.Proceed; 305 | } 306 | 307 | /// 308 | /// This is called when family instances are encountered, before OnElementEnd. 309 | /// We're using it here to maintain the transform stack for that element's heirarchy. 310 | /// 311 | /// 312 | public void OnInstanceEnd(InstanceNode node) 313 | { 314 | Debug.WriteLine(String.Format("{0}OnInstanceEnd", manager.formatDebugHeirarchy.Substring(0,manager.formatDebugHeirarchy.Count() - 2))); 315 | 316 | ElementId symId = node.GetSymbolId(); 317 | Element symElem = _doc.GetElement(symId); 318 | 319 | manager.CloseNode(symElem, true); 320 | } 321 | 322 | public bool IsCanceled() 323 | { 324 | // This method is invoked many times during the export process. 325 | return false; 326 | } 327 | 328 | public RenderNodeAction OnViewBegin(ViewNode node) 329 | { 330 | // TODO: we could use this to handle multiple scenes in the gltf file. 331 | return RenderNodeAction.Proceed; 332 | } 333 | 334 | public void OnViewEnd(ElementId elementId) 335 | { 336 | // do nothing 337 | } 338 | 339 | public RenderNodeAction OnLinkBegin(LinkNode node) 340 | { 341 | ElementId symId = node.GetSymbolId(); 342 | Element symElem = _doc.GetElement(symId); 343 | 344 | Debug.WriteLine(String.Format("{2}OnLinkBegin: {0}-{1}", symId, symElem.Name, manager.formatDebugHeirarchy)); 345 | 346 | var nodeXform = node.GetTransform(); 347 | manager.OpenNode(symElem, nodeXform.IsIdentity ? null : nodeXform, true); 348 | 349 | documentStack.Push(node.GetDocument()); 350 | return RenderNodeAction.Proceed; 351 | } 352 | 353 | public void OnLinkEnd(LinkNode node) 354 | { 355 | Debug.WriteLine(String.Format("{0}OnLinkEnd", manager.formatDebugHeirarchy.Substring(0, manager.formatDebugHeirarchy.Count() - 2))); 356 | manager.CloseNode(); 357 | 358 | documentStack.Pop(); 359 | } 360 | 361 | public RenderNodeAction OnFaceBegin(FaceNode node) 362 | { 363 | return RenderNodeAction.Proceed; 364 | } 365 | 366 | public void OnFaceEnd(FaceNode node) 367 | { 368 | // This method is invoked only if the 369 | // custom exporter was set to include faces. 370 | } 371 | 372 | public void OnRPC(RPCNode node) 373 | { 374 | // do nothing 375 | } 376 | 377 | public void OnLight(LightNode node) 378 | { 379 | // do nothing 380 | } 381 | } 382 | } 383 | -------------------------------------------------------------------------------- /glTFRevitExport/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Resources; 3 | using System.Runtime.CompilerServices; 4 | using System.Runtime.InteropServices; 5 | using System.Windows; 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [assembly: AssemblyTitle("glTFRevitExport")] 11 | [assembly: AssemblyDescription("")] 12 | [assembly: AssemblyConfiguration("")] 13 | [assembly: AssemblyCompany("")] 14 | [assembly: AssemblyProduct("glTFRevitExport")] 15 | [assembly: AssemblyCopyright("Copyright © 2015")] 16 | [assembly: AssemblyTrademark("")] 17 | [assembly: AssemblyCulture("")] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [assembly: ComVisible(false)] 23 | 24 | //In order to begin building localizable applications, set 25 | //CultureYouAreCodingWith in your .csproj file 26 | //inside a . For example, if you are using US english 27 | //in your source files, set the to en-US. Then uncomment 28 | //the NeutralResourceLanguage attribute below. Update the "en-US" in 29 | //the line below to match the UICulture setting in the project file. 30 | 31 | //[assembly: NeutralResourcesLanguage("en-US", UltimateResourceFallbackLocation.Satellite)] 32 | 33 | 34 | [assembly: ThemeInfo( 35 | ResourceDictionaryLocation.None, //where theme specific resource dictionaries are located 36 | //(used if a resource is not found in the page, 37 | // or application resource dictionaries) 38 | ResourceDictionaryLocation.SourceAssembly //where the generic resource dictionary is located 39 | //(used if a resource is not found in the page, 40 | // app, or any theme specific resource dictionaries) 41 | )] 42 | 43 | 44 | // Version information for an assembly consists of the following four values: 45 | // 46 | // Major Version 47 | // Minor Version 48 | // Build Number 49 | // Revision 50 | // 51 | // You can specify all the values or you can default the Build and Revision Numbers 52 | // by using the '*' as shown below: 53 | // [assembly: AssemblyVersion("1.0.*")] 54 | [assembly: AssemblyVersion("1.0.0.0")] 55 | [assembly: AssemblyFileVersion("1.0.0.0")] 56 | -------------------------------------------------------------------------------- /glTFRevitExport/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34209 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace glTFRevitExport.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("glTFRevitExport.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /glTFRevitExport/Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /glTFRevitExport/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.34209 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace glTFRevitExport.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /glTFRevitExport/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /glTFRevitExport/Util.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Autodesk.Revit.DB; 5 | 6 | namespace glTFRevitExport 7 | { 8 | class Util 9 | { 10 | public static int[] GetVec3MinMax(List vec3) 11 | { 12 | int minVertexX = int.MaxValue; 13 | int minVertexY = int.MaxValue; 14 | int minVertexZ = int.MaxValue; 15 | int maxVertexX = int.MinValue; 16 | int maxVertexY = int.MinValue; 17 | int maxVertexZ = int.MinValue; 18 | for (int i = 0; i < vec3.Count; i += 3) 19 | { 20 | if (vec3[i] < minVertexX) minVertexX = vec3[i]; 21 | if (vec3[i] > maxVertexX) maxVertexX = vec3[i]; 22 | 23 | if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1]; 24 | if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1]; 25 | 26 | if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2]; 27 | if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2]; 28 | } 29 | return new int[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ }; 30 | } 31 | 32 | public static long[] GetVec3MinMax(List vec3) 33 | { 34 | long minVertexX = long.MaxValue; 35 | long minVertexY = long.MaxValue; 36 | long minVertexZ = long.MaxValue; 37 | long maxVertexX = long.MinValue; 38 | long maxVertexY = long.MinValue; 39 | long maxVertexZ = long.MinValue; 40 | for (int i = 0; i < (vec3.Count / 3); i += 3) 41 | { 42 | if (vec3[i] < minVertexX) minVertexX = vec3[i]; 43 | if (vec3[i] > maxVertexX) maxVertexX = vec3[i]; 44 | 45 | if (vec3[i + 1] < minVertexY) minVertexY = vec3[i + 1]; 46 | if (vec3[i + 1] > maxVertexY) maxVertexY = vec3[i + 1]; 47 | 48 | if (vec3[i + 2] < minVertexZ) minVertexZ = vec3[i + 2]; 49 | if (vec3[i + 2] > maxVertexZ) maxVertexZ = vec3[i + 2]; 50 | } 51 | return new long[] { minVertexX, maxVertexX, minVertexY, maxVertexY, minVertexZ, maxVertexZ }; 52 | } 53 | 54 | public static float[] GetVec3MinMax(List vec3) 55 | { 56 | 57 | List xValues = new List(); 58 | List yValues = new List(); 59 | List zValues = new List(); 60 | for (int i = 0; i < vec3.Count; i++) 61 | { 62 | if ((i % 3) == 0) xValues.Add(vec3[i]); 63 | if ((i % 3) == 1) yValues.Add(vec3[i]); 64 | if ((i % 3) == 2) zValues.Add(vec3[i]); 65 | } 66 | 67 | float maxX = xValues.Max(); 68 | float minX = xValues.Min(); 69 | float maxY = yValues.Max(); 70 | float minY = yValues.Min(); 71 | float maxZ = zValues.Max(); 72 | float minZ = zValues.Min(); 73 | 74 | return new float[] { minX, maxX, minY, maxY, minZ, maxZ }; 75 | } 76 | 77 | public static int[] GetScalarMinMax(List scalars) 78 | { 79 | int minFaceIndex = int.MaxValue; 80 | int maxFaceIndex = int.MinValue; 81 | for (int i = 0; i < scalars.Count; i++) 82 | { 83 | int currentMin = Math.Min(minFaceIndex, scalars[i]); 84 | if (currentMin < minFaceIndex) minFaceIndex = currentMin; 85 | 86 | int currentMax = Math.Max(maxFaceIndex, scalars[i]); 87 | if (currentMax > maxFaceIndex) maxFaceIndex = currentMax; 88 | } 89 | return new int[] { minFaceIndex, maxFaceIndex }; 90 | } 91 | 92 | /// 93 | /// From Jeremy Tammik's RvtVa3c exporter: 94 | /// https://github.com/va3c/RvtVa3c 95 | /// Return a string for a real number 96 | /// formatted to two decimal places. 97 | /// 98 | public static string RealString(double a) 99 | { 100 | return a.ToString("0.##"); 101 | } 102 | 103 | /// 104 | /// From Jeremy Tammik's RvtVa3c exporter: 105 | /// https://github.com/va3c/RvtVa3c 106 | /// Return a string for an XYZ point 107 | /// or vector with its coordinates 108 | /// formatted to two decimal places. 109 | /// 110 | public static string PointString(XYZ p) 111 | { 112 | return string.Format("({0},{1},{2})", 113 | RealString(p.X), 114 | RealString(p.Y), 115 | RealString(p.Z)); 116 | } 117 | 118 | /// 119 | /// From Jeremy Tammik's RvtVa3c exporter: 120 | /// https://github.com/va3c/RvtVa3c 121 | /// Return an integer value for a Revit Color. 122 | /// 123 | public static int ColorToInt(Color color) 124 | { 125 | return ((int)color.Red) << 16 126 | | ((int)color.Green) << 8 127 | | (int)color.Blue; 128 | } 129 | 130 | /// 131 | /// From Jeremy Tammik's RvtVa3c exporter: 132 | /// https://github.com/va3c/RvtVa3c 133 | /// Extract a true or false value from the given 134 | /// string, accepting yes/no, Y/N, true/false, T/F 135 | /// and 1/0. We are extremely tolerant, i.e., any 136 | /// value starting with one of the characters y, n, 137 | /// t or f is also accepted. Return false if no 138 | /// valid Boolean value can be extracted. 139 | /// 140 | public static bool GetTrueOrFalse(string s, out bool val) 141 | { 142 | val = false; 143 | 144 | if (s.Equals(Boolean.TrueString, 145 | StringComparison.OrdinalIgnoreCase)) 146 | { 147 | val = true; 148 | return true; 149 | } 150 | if (s.Equals(Boolean.FalseString, 151 | StringComparison.OrdinalIgnoreCase)) 152 | { 153 | return true; 154 | } 155 | if (s.Equals("1")) 156 | { 157 | val = true; 158 | return true; 159 | } 160 | if (s.Equals("0")) 161 | { 162 | return true; 163 | } 164 | s = s.ToLower(); 165 | 166 | if ('t' == s[0] || 'y' == s[0]) 167 | { 168 | val = true; 169 | return true; 170 | } 171 | if ('f' == s[0] || 'n' == s[0]) 172 | { 173 | return true; 174 | } 175 | return false; 176 | } 177 | 178 | /// 179 | /// From Jeremy Tammik's RvtVa3c exporter: 180 | /// https://github.com/va3c/RvtVa3c 181 | /// Return a string describing the given element: 182 | /// .NET type name, 183 | /// category name, 184 | /// family and symbol name for a family instance, 185 | /// element id and element name. 186 | /// 187 | public static string ElementDescription(Element e) 188 | { 189 | if (null == e) 190 | { 191 | return ""; 192 | } 193 | 194 | // For a wall, the element name equals the 195 | // wall type name, which is equivalent to the 196 | // family name ... 197 | 198 | FamilyInstance fi = e as FamilyInstance; 199 | 200 | string typeName = e.GetType().Name; 201 | 202 | string categoryName = (null == e.Category) 203 | ? string.Empty 204 | : e.Category.Name + " "; 205 | 206 | string familyName = (null == fi) 207 | ? string.Empty 208 | : fi.Symbol.Family.Name + " "; 209 | 210 | string symbolName = (null == fi 211 | || e.Name.Equals(fi.Symbol.Name)) 212 | ? string.Empty 213 | : fi.Symbol.Name + " "; 214 | 215 | return string.Format("{0} {1}{2}{3}<{4} {5}>", 216 | typeName, categoryName, familyName, 217 | symbolName, e.Id.IntegerValue, e.Name); 218 | } 219 | 220 | /// 221 | /// From Jeremy Tammik's RvtVa3c exporter: 222 | /// https://github.com/va3c/RvtVa3c 223 | /// Return a dictionary of all the given 224 | /// element parameter names and values. 225 | /// 226 | public static Dictionary GetElementProperties(Element e, bool includeType) 227 | { 228 | IList parameters 229 | = e.GetOrderedParameters(); 230 | 231 | Dictionary a = new Dictionary(parameters.Count); 232 | 233 | // Add element category 234 | if (e.Category != null) 235 | { 236 | a.Add("Element Category", e.Category.Name); 237 | } 238 | 239 | 240 | 241 | foreach (Parameter p in parameters) 242 | { 243 | string key = p.Definition.Name; 244 | 245 | if (!a.ContainsKey(key)) 246 | { 247 | string val; 248 | if (StorageType.String == p.StorageType) 249 | { 250 | val = p.AsString(); 251 | } 252 | else 253 | { 254 | val = p.AsValueString(); 255 | } 256 | if (!string.IsNullOrEmpty(val)) 257 | { 258 | a.Add(key, val); 259 | } 260 | } 261 | } 262 | 263 | if (includeType) 264 | { 265 | ElementId idType = e.GetTypeId(); 266 | 267 | if (idType != null && ElementId.InvalidElementId != idType) 268 | { 269 | Document doc = e.Document; 270 | Element typ = doc.GetElement(idType); 271 | parameters = typ.GetOrderedParameters(); 272 | foreach (Parameter p in parameters) 273 | { 274 | string key = "Type " + p.Definition.Name; 275 | 276 | if (!a.ContainsKey(key)) 277 | { 278 | string val; 279 | if (StorageType.String == p.StorageType) 280 | { 281 | val = p.AsString(); 282 | } 283 | else 284 | { 285 | val = p.AsValueString(); 286 | } 287 | if (!string.IsNullOrEmpty(val)) 288 | { 289 | a.Add(key, val); 290 | } 291 | } 292 | } 293 | } 294 | } 295 | 296 | if (a.Count == 0) return null; 297 | else return a; 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /glTFRevitExport/WPF/MainWindow.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 |