├── img ├── gargoyle2.png ├── cart_groups_3.png ├── high_ball_glass.png ├── sandal_with_gaps.png ├── DirectObjLoader_app.png ├── DirectObjLoader_app_2.png ├── fire_hydrant_closed_render.jpg ├── sandal_with_gaps_anygeometry.png ├── fire_hydrant_closed_directshape_rvt.jpg └── fire_hydrant_closed_directshape_rvt.png ├── DirectObjLoader ├── ImgDirectObjLoader16.png ├── ImgDirectObjLoader32.png ├── Properties │ └── AssemblyInfo.cs ├── packages.config ├── DirectObjLoader.addin ├── JtWindowHandle.cs ├── App.cs ├── DirectObjLoader.csproj ├── Util.cs ├── Config.cs └── Command.cs ├── DirectObjLoader.sln ├── LICENSE ├── README.md └── .gitignore /img/gargoyle2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/gargoyle2.png -------------------------------------------------------------------------------- /img/cart_groups_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/cart_groups_3.png -------------------------------------------------------------------------------- /img/high_ball_glass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/high_ball_glass.png -------------------------------------------------------------------------------- /img/sandal_with_gaps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/sandal_with_gaps.png -------------------------------------------------------------------------------- /img/DirectObjLoader_app.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/DirectObjLoader_app.png -------------------------------------------------------------------------------- /img/DirectObjLoader_app_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/DirectObjLoader_app_2.png -------------------------------------------------------------------------------- /img/fire_hydrant_closed_render.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/fire_hydrant_closed_render.jpg -------------------------------------------------------------------------------- /img/sandal_with_gaps_anygeometry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/sandal_with_gaps_anygeometry.png -------------------------------------------------------------------------------- /DirectObjLoader/ImgDirectObjLoader16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/DirectObjLoader/ImgDirectObjLoader16.png -------------------------------------------------------------------------------- /DirectObjLoader/ImgDirectObjLoader32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/DirectObjLoader/ImgDirectObjLoader32.png -------------------------------------------------------------------------------- /DirectObjLoader/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/DirectObjLoader/Properties/AssemblyInfo.cs -------------------------------------------------------------------------------- /img/fire_hydrant_closed_directshape_rvt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/fire_hydrant_closed_directshape_rvt.jpg -------------------------------------------------------------------------------- /img/fire_hydrant_closed_directshape_rvt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jeremytammik/DirectObjLoader/HEAD/img/fire_hydrant_closed_directshape_rvt.png -------------------------------------------------------------------------------- /DirectObjLoader/packages.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /DirectObjLoader/DirectObjLoader.addin: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DirectObjLoader 5 | DirectObjLoader.dll 6 | DirectObjLoader.App 7 | a6f224dc-7342-4ac8-8474-3a0e11325442 8 | TBC_ 9 | The Building Coder, http://thebuildingcoder.typepad.com 10 | 11 | -------------------------------------------------------------------------------- /DirectObjLoader/JtWindowHandle.cs: -------------------------------------------------------------------------------- 1 | #region Namespaces 2 | using System; 3 | using System.Diagnostics; 4 | using System.Windows.Forms; 5 | #endregion 6 | 7 | namespace DirectObjLoader 8 | { 9 | /// 10 | /// Wrapper class for converting 11 | /// IntPtr to IWin32Window. 12 | /// 13 | public class JtWindowHandle : IWin32Window 14 | { 15 | IntPtr _hwnd; 16 | 17 | public JtWindowHandle( IntPtr h ) 18 | { 19 | Debug.Assert( IntPtr.Zero != h, 20 | "expected non-null window handle" ); 21 | 22 | _hwnd = h; 23 | } 24 | 25 | public IntPtr Handle 26 | { 27 | get 28 | { 29 | return _hwnd; 30 | } 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /DirectObjLoader.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DirectObjLoader", "DirectObjLoader\DirectObjLoader.csproj", "{DB3221B7-87A0-4B7C-8F11-D8F071A90BC0}" 5 | EndProject 6 | Global 7 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 8 | Debug|Any CPU = Debug|Any CPU 9 | Release|Any CPU = Release|Any CPU 10 | EndGlobalSection 11 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 12 | {DB3221B7-87A0-4B7C-8F11-D8F071A90BC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 13 | {DB3221B7-87A0-4B7C-8F11-D8F071A90BC0}.Debug|Any CPU.Build.0 = Debug|Any CPU 14 | {DB3221B7-87A0-4B7C-8F11-D8F071A90BC0}.Release|Any CPU.ActiveCfg = Release|Any CPU 15 | {DB3221B7-87A0-4B7C-8F11-D8F071A90BC0}.Release|Any CPU.Build.0 = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(SolutionProperties) = preSolution 18 | HideSolutionNode = FALSE 19 | EndGlobalSection 20 | EndGlobal 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Jeremy Tammik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /DirectObjLoader/App.cs: -------------------------------------------------------------------------------- 1 | #region Namespaces 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Windows.Media.Imaging; 6 | using System.Reflection; 7 | using Autodesk.Revit.ApplicationServices; 8 | using Autodesk.Revit.Attributes; 9 | using Autodesk.Revit.DB; 10 | using Autodesk.Revit.UI; 11 | #endregion 12 | 13 | namespace DirectObjLoader 14 | { 15 | class App : IExternalApplication 16 | { 17 | /// 18 | /// Caption used in messages etc. 19 | /// 20 | public const string Caption = "Direct OBJ Loader"; 21 | 22 | #region Load bitmap from embedded resources 23 | static string _namespace_prefix 24 | = typeof( App ).Namespace + "."; 25 | 26 | /// 27 | /// Load a new icon bitmap from embedded resources. 28 | /// For the BitmapImage, make sure you reference 29 | /// WindowsBase and PresentationCore, and import 30 | /// the System.Windows.Media.Imaging namespace. 31 | /// 32 | BitmapImage NewBitmapImage( 33 | Assembly a, 34 | string imageName ) 35 | { 36 | Stream s = a.GetManifestResourceStream( 37 | _namespace_prefix + imageName ); 38 | 39 | BitmapImage img = new BitmapImage(); 40 | 41 | img.BeginInit(); 42 | img.StreamSource = s; 43 | img.EndInit(); 44 | 45 | return img; 46 | } 47 | #endregion // Load bitmap from embedded resources 48 | 49 | void CreateRibbonPanel( 50 | UIControlledApplication a ) 51 | { 52 | Assembly exe = Assembly.GetExecutingAssembly(); 53 | string path = exe.Location; 54 | 55 | string className = GetType().FullName.Replace( 56 | "App", "Command" ); 57 | 58 | RibbonPanel p = a.CreateRibbonPanel( 59 | "DirectShape OBJ Loader" ); 60 | 61 | PushButtonData d = new PushButtonData( 62 | "DirectObjLoader_Command", 63 | "DirectShape\r\nOBJ Loader", 64 | path, "DirectObjLoader.Command" ); 65 | 66 | d.ToolTip = "Load a WaveFront OBJ model mesh " 67 | + "into a DirectShape Revit element"; 68 | 69 | d.Image = NewBitmapImage( exe, 70 | "ImgDirectObjLoader16.png" ); 71 | 72 | d.LargeImage = NewBitmapImage( exe, 73 | "ImgDirectObjLoader32.png" ); 74 | 75 | d.LongDescription = d.ToolTip; 76 | 77 | d.SetContextualHelp( new ContextualHelp( 78 | ContextualHelpType.Url, 79 | Command.TroubleshootingUrl ) ); 80 | 81 | p.AddItem( d ); 82 | } 83 | 84 | public Result OnStartup( 85 | UIControlledApplication a ) 86 | { 87 | CreateRibbonPanel( a ); 88 | 89 | return Result.Succeeded; 90 | } 91 | 92 | public Result OnShutdown( 93 | UIControlledApplication a ) 94 | { 95 | return Result.Succeeded; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | DirectObjLoader 2 | =============== 3 | 4 | Revit add-in to load a WaveFront OBJ model and generate a DirectShape element from it. 5 | 6 | The Building Coder provides a dedicated topic group where you 7 | can [read all there is to know about the DirectShape Element](http://thebuildingcoder.typepad.com/blog/about-the-author.html#5.50). 8 | 9 | The initial implementation and development history is documented in the article 10 | [From Hack to App - OBJ Mesh Import to DirectShape](http://thebuildingcoder.typepad.com/blog/2015/02/from-hack-to-app-obj-mesh-import-to-directshape.html). 11 | 12 | ![Image](img/DirectObjLoader_app_2.png) 13 | 14 | Sample fire hydrant OBJ file: 15 | 16 | ![Image](img/fire_hydrant_closed_render.jpg) 17 | 18 | Resulting DirectShape element in Revit model: 19 | 20 | ![Image](img/fire_hydrant_closed_directshape_rvt.jpg) 21 | 22 | Input scaling factor 1 versus 0.5 happily produces a gargoyle and a half: 23 | 24 | ![Image](img/gargoyle2.png) 25 | 26 | OBJ files defining groups generate a separate DirectShape element for each one: 27 | 28 | ![Image](img/cart_groups_3.png) 29 | 30 | After adding support for faces with more than four vertices, the sandal.obj test file is loaded successfully, albeit with some missing faces: 31 | 32 | ![Image](img/sandal_with_gaps.png) 33 | 34 | Switched from TessellatedShapeBuilder target Mesh to AnyGeometry generated more internal model structure from the sandal.obj test file, still with some missing faces: 35 | 36 | ![Image](img/sandal_with_gaps_anygeometry.png) 37 | 38 | Release 2015.0.0.17 improved error handling on degenerate faces: 39 | 40 | ![Image](img/high_ball_glass.png) 41 | 42 | 43 | TessellatedShapeBuilder Creates a Mesh with Slits 44 | --------- 45 | 46 | In case of slits between faces with more than 3 vertices, please refer to this solution: 47 | 48 | - [TessellatedShapeBuilder creates a mesh with slits](https://forums.autodesk.com/t5/revit-api-forum/tessellatedshapebuilder-creates-a-mesh-with-slits/td-p/12641002) 49 | 50 | Wish List 51 | --------- 52 | 53 | - Progress bar 54 | - Support for materials, minimally colour, preferably textures 55 | - Support for the options provided by the [StlImport](https://github.com/jeremytammik/StlImport) StlImportProperties class 56 | 57 | 58 | Author 59 | ------ 60 | 61 | Jeremy Tammik, 62 | [The Building Coder](http://thebuildingcoder.typepad.com) and 63 | [The 3D Web Coder](http://the3dwebcoder.typepad.com), 64 | [ADN](http://www.autodesk.com/adn) 65 | [Open](http://www.autodesk.com/adnopen), 66 | [Autodesk Inc.](http://www.autodesk.com) 67 | 68 | 69 | 70 | Dependencies 71 | ------------ 72 | 73 | DirectObjLoader uses the 74 | [FileFormatWavefront](http://nugetmusthaves.com/Package/FileFormatWavefront) NuGet package based on Dave Kerr's 75 | [file-format-wavefront](https://github.com/dwmkerr/file-format-wavefront) GitHub library. 76 | 77 | 78 | License 79 | ------- 80 | 81 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT). Please see the [LICENSE](LICENSE) file for full details. 82 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | [Rr]eleases/ 14 | x64/ 15 | x86/ 16 | build/ 17 | bld/ 18 | [Bb]in/ 19 | [Oo]bj/ 20 | 21 | # Roslyn cache directories 22 | *.ide/ 23 | 24 | # MSTest test Results 25 | [Tt]est[Rr]esult*/ 26 | [Bb]uild[Ll]og.* 27 | 28 | #NUNIT 29 | *.VisualState.xml 30 | TestResult.xml 31 | 32 | # Build Results of an ATL Project 33 | [Dd]ebugPS/ 34 | [Rr]eleasePS/ 35 | dlldata.c 36 | 37 | *_i.c 38 | *_p.c 39 | *_i.h 40 | *.ilk 41 | *.meta 42 | *.obj 43 | *.pch 44 | *.pdb 45 | *.pgc 46 | *.pgd 47 | *.rsp 48 | *.sbr 49 | *.tlb 50 | *.tli 51 | *.tlh 52 | *.tmp 53 | *.tmp_proj 54 | *.log 55 | *.vspscc 56 | *.vssscc 57 | .builds 58 | *.pidb 59 | *.svclog 60 | *.scc 61 | 62 | # Chutzpah Test files 63 | _Chutzpah* 64 | 65 | # Visual C++ cache files 66 | ipch/ 67 | *.aps 68 | *.ncb 69 | *.opensdf 70 | *.sdf 71 | *.cachefile 72 | 73 | # Visual Studio profiler 74 | *.psess 75 | *.vsp 76 | *.vspx 77 | 78 | # TFS 2012 Local Workspace 79 | $tf/ 80 | 81 | # Guidance Automation Toolkit 82 | *.gpState 83 | 84 | # ReSharper is a .NET coding add-in 85 | _ReSharper*/ 86 | *.[Rr]e[Ss]harper 87 | *.DotSettings.user 88 | 89 | # JustCode is a .NET coding addin-in 90 | .JustCode 91 | 92 | # TeamCity is a build add-in 93 | _TeamCity* 94 | 95 | # DotCover is a Code Coverage Tool 96 | *.dotCover 97 | 98 | # NCrunch 99 | _NCrunch_* 100 | .*crunch*.local.xml 101 | 102 | # MightyMoose 103 | *.mm.* 104 | AutoTest.Net/ 105 | 106 | # Web workbench (sass) 107 | .sass-cache/ 108 | 109 | # Installshield output folder 110 | [Ee]xpress/ 111 | 112 | # DocProject is a documentation generator add-in 113 | DocProject/buildhelp/ 114 | DocProject/Help/*.HxT 115 | DocProject/Help/*.HxC 116 | DocProject/Help/*.hhc 117 | DocProject/Help/*.hhk 118 | DocProject/Help/*.hhp 119 | DocProject/Help/Html2 120 | DocProject/Help/html 121 | 122 | # Click-Once directory 123 | publish/ 124 | 125 | # Publish Web Output 126 | *.[Pp]ublish.xml 127 | *.azurePubxml 128 | # TODO: Comment the next line if you want to checkin your web deploy settings 129 | # but database connection strings (with potential passwords) will be unencrypted 130 | *.pubxml 131 | *.publishproj 132 | 133 | # NuGet Packages 134 | *.nupkg 135 | # The packages folder can be ignored because of Package Restore 136 | **/packages/* 137 | # except build/, which is used as an MSBuild target. 138 | !**/packages/build/ 139 | # If using the old MSBuild-Integrated Package Restore, uncomment this: 140 | #!**/packages/repositories.config 141 | 142 | # Windows Azure Build Output 143 | csx/ 144 | *.build.csdef 145 | 146 | # Windows Store app package directory 147 | AppPackages/ 148 | 149 | # Others 150 | sql/ 151 | *.Cache 152 | ClientBin/ 153 | [Ss]tyle[Cc]op.* 154 | ~$* 155 | *~ 156 | *.dbmdl 157 | *.dbproj.schemaview 158 | *.pfx 159 | *.publishsettings 160 | node_modules/ 161 | 162 | # RIA/Silverlight projects 163 | Generated_Code/ 164 | 165 | # Backup & report files from converting an old project file 166 | # to a newer Visual Studio version. Backup files are not needed, 167 | # because we have git ;-) 168 | _UpgradeReport_Files/ 169 | Backup*/ 170 | UpgradeLog*.XML 171 | UpgradeLog*.htm 172 | 173 | # SQL Server files 174 | *.mdf 175 | *.ldf 176 | 177 | # Business Intelligence projects 178 | *.rdl.data 179 | *.bim.layout 180 | *.bim_*.settings 181 | 182 | # Microsoft Fakes 183 | FakesAssemblies/ 184 | 185 | # Added by Jeremy 186 | packages/ 187 | test/ -------------------------------------------------------------------------------- /DirectObjLoader/DirectObjLoader.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | None 6 | 7 | 8 | 9 | 10 | Debug 11 | AnyCPU 12 | 13 | 14 | 15 | 16 | {DB3221B7-87A0-4B7C-8F11-D8F071A90BC0} 17 | Library 18 | Properties 19 | DirectObjLoader 20 | v4.5.2 21 | 512 22 | 23 | 24 | true 25 | full 26 | false 27 | bin\Debug\ 28 | DEBUG;TRACE 29 | prompt 30 | 4 31 | Program 32 | $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe 33 | false 34 | 35 | 36 | pdbonly 37 | true 38 | bin\Release\ 39 | TRACE 40 | prompt 41 | 4 42 | Program 43 | $(ProgramW6432)\Autodesk\Revit 2015\Revit.exe 44 | false 45 | 46 | 47 | 48 | ..\..\..\..\Program Files\Autodesk\Revit 2017\AdWindows.dll 49 | False 50 | 51 | 52 | ..\packages\FileFormatWavefront.1.0.3.0\lib\net40\FileFormatWavefront.dll 53 | True 54 | 55 | 56 | 57 | ..\..\..\..\Program Files\Autodesk\Revit 2017\RevitAPI.dll 58 | False 59 | 60 | 61 | ..\..\..\..\Program Files\Autodesk\Revit 2017\RevitAPIUI.dll 62 | False 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 | copy "$(ProjectDir)DirectObjLoader.addin" "$(AppData)\Autodesk\REVIT\Addins\2017" 93 | copy "$(ProjectDir)bin\debug\DirectObjLoader.dll" "$(AppData)\Autodesk\REVIT\Addins\2017" 94 | copy "$(ProjectDir)..\packages\FileFormatWavefront.1.0.3.0\lib\net40\FileFormatWavefront.dll" "$(AppData)\Autodesk\REVIT\Addins\2017" 95 | 96 | -------------------------------------------------------------------------------- /DirectObjLoader/Util.cs: -------------------------------------------------------------------------------- 1 | #region Namespaces 2 | using System; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Windows.Forms; 6 | using Autodesk.Revit.DB; 7 | #endregion // Namespaces 8 | 9 | namespace DirectObjLoader 10 | { 11 | class Util 12 | { 13 | /// 14 | /// Return an English plural suffix 's' or 15 | /// nothing for the given number of items. 16 | /// 17 | public static string PluralSuffix( int n ) 18 | { 19 | return 1 == n ? "" : "s"; 20 | } 21 | 22 | // JavaScript sample implementations: 23 | // capitalize:function(){return this.replace(/\b[a-z]/g,function(match){return match.toUpperCase();});} 24 | // capitalize: function() { return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); } 25 | 26 | /// 27 | /// Ensure that each space delimited word in the 28 | /// given string has an upper case first character. 29 | /// 30 | public static string Capitalize( string s ) 31 | { 32 | return string.Join( " ", s.Split( null ) 33 | .Select( a 34 | => a.Substring( 0, 1 ).ToUpper() 35 | + a.Substring( 1 ) ) ); 36 | } 37 | 38 | public static string RealString( double a ) 39 | { 40 | return a.ToString( "0.##" ); 41 | } 42 | 43 | public static string PointString( XYZ p ) 44 | { 45 | return string.Format( "({0},{1},{2})", 46 | RealString( p.X ), RealString( p.Y ), 47 | RealString( p.Z ) ); 48 | } 49 | 50 | /// 51 | /// Extract a true or false value from the given 52 | /// string, accepting yes/no, Y/N, true/false, T/F 53 | /// and 1/0. We are extremely tolerant, i.e., any 54 | /// value starting with one of the characters y, n, 55 | /// t or f is also accepted. Return false if no 56 | /// valid Boolean value can be extracted. 57 | /// 58 | public static bool GetTrueOrFalse( 59 | string s, 60 | out bool val ) 61 | { 62 | val = false; 63 | 64 | if( s.Equals( Boolean.TrueString, 65 | StringComparison.OrdinalIgnoreCase ) ) 66 | { 67 | val = true; 68 | return true; 69 | } 70 | if( s.Equals( Boolean.FalseString, 71 | StringComparison.OrdinalIgnoreCase ) ) 72 | { 73 | return true; 74 | } 75 | if( s.Equals( "1" ) ) 76 | { 77 | val = true; 78 | return true; 79 | } 80 | if( s.Equals( "0" ) ) 81 | { 82 | return true; 83 | } 84 | s = s.ToLower(); 85 | 86 | if( 't' == s[0] || 'y' == s[0] ) 87 | { 88 | val = true; 89 | return true; 90 | } 91 | if( 'f' == s[0] || 'n' == s[0] ) 92 | { 93 | return true; 94 | } 95 | return false; 96 | } 97 | 98 | /// 99 | /// Select a specified file in the given folder. 100 | /// 101 | /// Initial folder. 102 | /// Selected filename on 103 | /// success. 104 | /// Return true if a file was successfully 105 | /// selected. 106 | static bool FileSelect( 107 | string folder, 108 | string title, 109 | string filter, 110 | ref string filename ) 111 | { 112 | OpenFileDialog dlg = new OpenFileDialog(); 113 | dlg.Title = title; 114 | dlg.CheckFileExists = true; 115 | dlg.CheckPathExists = true; 116 | dlg.InitialDirectory = folder; 117 | dlg.FileName = filename; 118 | dlg.Filter = filter; 119 | bool rc = ( DialogResult.OK == dlg.ShowDialog() ); 120 | filename = dlg.FileName; 121 | return rc; 122 | } 123 | 124 | /// 125 | /// Select a WaveFront OBJ file in the given folder. 126 | /// 127 | /// Initial folder. 128 | /// Selected filename on 129 | /// success. 130 | /// Return true if a file was successfully 131 | /// selected. 132 | static public bool FileSelectObj( 133 | string folder, 134 | ref string filename ) 135 | { 136 | return FileSelect( folder, 137 | "Select WaveFront OBJ file or Cancel to Exit", 138 | "WaveFront OBJ Files (*.obj)|*.obj", 139 | ref filename ); 140 | } 141 | 142 | /// 143 | /// Return the size in bytes of the given file. 144 | /// 145 | static public long GetFileSize( string filename ) 146 | { 147 | long fileSize = 0L; 148 | 149 | using( FileStream file = File.Open( 150 | filename, FileMode.Open ) ) 151 | { 152 | fileSize = file.Seek( 0L, SeekOrigin.End ); 153 | file.Close(); 154 | } 155 | return fileSize; 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /DirectObjLoader/Config.cs: -------------------------------------------------------------------------------- 1 | #region Namespaces 2 | using System; 3 | using System.Configuration; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Reflection; 8 | #endregion // Namespaces 9 | 10 | namespace DirectObjLoader 11 | { 12 | class Config 13 | { 14 | const string _defaultFolderObj = "defaultFolderObj"; 15 | const string _inputScaleFactor = "inputScaleFactor"; 16 | const string _maxFileSize = "maxFileSize"; 17 | const string _maxNumberOfVertices = "maxNumberOfVertices"; 18 | const string _tryToCreateSolids = "tryToCreateSolids"; 19 | 20 | static Configuration _config = null; 21 | 22 | static Configuration GetConfig() 23 | { 24 | string path = Assembly.GetExecutingAssembly().Location; 25 | string configPath = path + ".config"; 26 | 27 | Configuration config = 28 | ConfigurationManager.OpenMappedExeConfiguration( 29 | new ExeConfigurationFileMap { ExeConfigFilename = configPath }, 30 | ConfigurationUserLevel.None ); 31 | 32 | string[] keys = config.AppSettings.Settings.AllKeys; 33 | 34 | if( !keys.Contains( _defaultFolderObj ) ) 35 | { 36 | config.AppSettings.Settings.Add( 37 | _defaultFolderObj, Path.GetTempPath() ); 38 | } 39 | if( !keys.Contains( _inputScaleFactor ) ) 40 | { 41 | config.AppSettings.Settings.Add( 42 | _inputScaleFactor, "1.0" ); 43 | } 44 | if( !keys.Contains( _maxFileSize ) ) 45 | { 46 | config.AppSettings.Settings.Add( 47 | _maxFileSize, "50000000" ); 48 | } 49 | if( !keys.Contains( _maxNumberOfVertices ) ) 50 | { 51 | config.AppSettings.Settings.Add( 52 | _maxNumberOfVertices, "100000" ); 53 | } 54 | if( !keys.Contains( _tryToCreateSolids ) ) 55 | { 56 | config.AppSettings.Settings.Add( 57 | _tryToCreateSolids, "true" ); 58 | } 59 | return config; 60 | } 61 | 62 | static KeyValueConfigurationCollection Settings 63 | { 64 | get 65 | { 66 | if( null == _config ) 67 | { 68 | _config = GetConfig(); 69 | } 70 | return _config.AppSettings.Settings; 71 | } 72 | } 73 | 74 | public static string DefaultFolderObj 75 | { 76 | get 77 | { 78 | return Settings[_defaultFolderObj].Value; 79 | } 80 | set 81 | { 82 | string oldVal = DefaultFolderObj; 83 | if( !value.Equals( oldVal ) ) 84 | { 85 | Settings[_defaultFolderObj].Value = value; 86 | _config.Save( ConfigurationSaveMode.Modified ); 87 | } 88 | } 89 | } 90 | 91 | public static double InputScaleFactor 92 | { 93 | get 94 | { 95 | double f; 96 | try 97 | { 98 | f = double.Parse( Settings[_inputScaleFactor].Value ); 99 | } 100 | catch( System.FormatException ) 101 | { 102 | f = 1.0; 103 | } 104 | return f; 105 | } 106 | set 107 | { 108 | double oldVal = InputScaleFactor; 109 | if( !value.Equals( oldVal ) ) 110 | { 111 | Settings[_inputScaleFactor].Value = value.ToString(); 112 | _config.Save( ConfigurationSaveMode.Modified ); 113 | } 114 | } 115 | } 116 | 117 | public static int MaxFileSize 118 | { 119 | get 120 | { 121 | int n; 122 | try 123 | { 124 | n = int.Parse( Settings[_maxFileSize].Value ); 125 | } 126 | catch( System.FormatException ) 127 | { 128 | n = 100000; 129 | } 130 | return n; 131 | } 132 | set 133 | { 134 | int oldVal = MaxFileSize; 135 | if( !value.Equals( oldVal ) ) 136 | { 137 | Settings[_maxFileSize].Value = value.ToString(); 138 | _config.Save( ConfigurationSaveMode.Modified ); 139 | } 140 | } 141 | } 142 | 143 | public static int MaxNumberOfVertices 144 | { 145 | get 146 | { 147 | int n; 148 | try 149 | { 150 | n = int.Parse( Settings[_maxNumberOfVertices].Value ); 151 | } 152 | catch( System.FormatException ) 153 | { 154 | n = 100000; 155 | } 156 | return n; 157 | } 158 | set 159 | { 160 | int oldVal = MaxNumberOfVertices; 161 | if( !value.Equals( oldVal ) ) 162 | { 163 | Settings[_maxNumberOfVertices].Value = value.ToString(); 164 | _config.Save( ConfigurationSaveMode.Modified ); 165 | } 166 | } 167 | } 168 | 169 | static bool TryToCreateSolids 170 | { 171 | get 172 | { 173 | bool rc; 174 | 175 | Util.GetTrueOrFalse( 176 | Settings[_tryToCreateSolids].Value, 177 | out rc ); 178 | 179 | return rc; 180 | } 181 | set 182 | { 183 | bool oldVal; 184 | 185 | Util.GetTrueOrFalse( 186 | Settings[_tryToCreateSolids].Value, 187 | out oldVal ); 188 | 189 | if( !value.Equals( oldVal ) ) 190 | { 191 | Settings[_tryToCreateSolids].Value = value 192 | ? Boolean.TrueString.ToLower() 193 | : Boolean.FalseString.ToLower(); 194 | 195 | _config.Save( ConfigurationSaveMode.Modified ); 196 | } 197 | } 198 | } 199 | } 200 | } 201 | -------------------------------------------------------------------------------- /DirectObjLoader/Command.cs: -------------------------------------------------------------------------------- 1 | #region Namespaces 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Windows.Forms; 8 | using Autodesk.Revit.ApplicationServices; 9 | using Autodesk.Revit.Attributes; 10 | using Autodesk.Revit.DB; 11 | using Autodesk.Revit.UI; 12 | using FileFormatWavefront; 13 | using FileFormatWavefront.Model; 14 | using Face = FileFormatWavefront.Model.Face; 15 | using Group = FileFormatWavefront.Model.Group; 16 | using FaceCollection = System.Collections.ObjectModel.ReadOnlyCollection; 17 | //using RvtFace = Autodesk.Revit.DB.Face; 18 | #endregion 19 | 20 | namespace DirectObjLoader 21 | { 22 | [Transaction( TransactionMode.Manual )] 23 | public class Command : IExternalCommand 24 | { 25 | /// 26 | /// Remember last selected filename for the 27 | /// duration of the current session. 28 | /// 29 | static string _filename = string.Empty; 30 | 31 | /// 32 | /// Category to use for the DirectShape elements 33 | /// generated. 34 | /// 35 | static ElementId _categoryId = new ElementId( 36 | BuiltInCategory.OST_GenericModel ); 37 | 38 | /// 39 | /// Troubleshooting web page URL for use in 40 | /// warning message on too many mesh vertices. 41 | /// 42 | public const string TroubleshootingUrl 43 | = "http://truevis.com/troubleshoot-revit-mesh-import"; 44 | 45 | /// 46 | /// Create a new DirectShape element from given 47 | /// list of faces and return the number of faces 48 | /// processed. 49 | /// Return -1 if a face vertex index exceeds the 50 | /// total number of available vertices, 51 | /// representing a fatal error. 52 | /// 53 | static int NewDirectShape( 54 | List vertices, 55 | FaceCollection faces, 56 | Document doc, 57 | ElementId graphicsStyleId, 58 | string appGuid, 59 | string shapeName ) 60 | { 61 | int nFaces = 0; 62 | int nFacesFailed = 0; 63 | 64 | TessellatedShapeBuilder builder 65 | = new TessellatedShapeBuilder(); 66 | 67 | builder.LogString = shapeName; 68 | 69 | List corners = new List( 4 ); 70 | 71 | builder.OpenConnectedFaceSet( false ); 72 | 73 | foreach( Face f in faces ) 74 | { 75 | builder.LogInteger = nFaces; 76 | 77 | if( corners.Capacity < f.Indices.Count ) 78 | { 79 | corners = new List( f.Indices.Count ); 80 | } 81 | 82 | corners.Clear(); 83 | 84 | foreach( Index i in f.Indices ) 85 | { 86 | Debug.Assert( vertices.Count > i.vertex, 87 | "how can the face vertex index be larger " 88 | + "than the total number of vertices?" ); 89 | 90 | if( i.vertex >= vertices.Count ) 91 | { 92 | return -1; 93 | } 94 | corners.Add( vertices[i.vertex] ); 95 | } 96 | 97 | try 98 | { 99 | builder.AddFace( new TessellatedFace( corners, 100 | ElementId.InvalidElementId ) ); 101 | 102 | ++nFaces; 103 | } 104 | catch( Autodesk.Revit.Exceptions.ArgumentException ex ) 105 | { 106 | // Remember something went wrong here. 107 | 108 | ++nFacesFailed; 109 | 110 | Debug.Print( 111 | "Revit API argument exception {0}\r\n" 112 | + "Failed to add face with {1} corners: {2}", 113 | ex.Message, corners.Count, 114 | string.Join( ", ", 115 | corners.Select( 116 | p => Util.PointString( p ) ) ) ); 117 | } 118 | } 119 | builder.CloseConnectedFaceSet(); 120 | 121 | // Refer to StlImport sample for more clever 122 | // handling of target and fallback and the 123 | // possible combinations. 124 | 125 | //TessellatedShapeBuilderResult r // 2015 126 | // = builder.Build( 127 | // TessellatedShapeBuilderTarget.AnyGeometry, 128 | // TessellatedShapeBuilderFallback.Mesh, 129 | // graphicsStyleId ); 130 | 131 | builder.Target = TessellatedShapeBuilderTarget.AnyGeometry; // 2017 132 | builder.Fallback = TessellatedShapeBuilderFallback.Mesh; // 2017 133 | builder.GraphicsStyleId = graphicsStyleId; // 2017 134 | 135 | builder.Build(); // 2017 136 | 137 | //TessellatedShapeBuilderResult r // 2015 138 | 139 | DirectShape ds = DirectShape.CreateElement( 140 | //doc, _categoryId, appGuid, shapeName ); // 2015 141 | doc, _categoryId ); // 2017 142 | 143 | ds.ApplicationId = appGuid; // 2017 144 | ds.ApplicationDataId = shapeName; // 2017 145 | 146 | //ds.SetShape( r.GetGeometricalObjects() ); // 2015 147 | 148 | ds.SetShape( builder.GetBuildResult().GetGeometricalObjects() ); // 2017 149 | 150 | ds.Name = shapeName; 151 | 152 | Debug.Print( 153 | "Shape '{0}': added {1} face{2}, {3} face{4} failed.", 154 | shapeName, nFaces, Util.PluralSuffix( nFaces ), 155 | nFacesFailed, Util.PluralSuffix( nFacesFailed ) ); 156 | 157 | return nFaces; 158 | } 159 | 160 | /// 161 | /// External command mainline. 162 | /// 163 | public Result Execute( 164 | ExternalCommandData commandData, 165 | ref string message, 166 | ElementSet elements ) 167 | { 168 | IWin32Window revit_window 169 | = new JtWindowHandle( Autodesk.Windows 170 | .ComponentManager.ApplicationWindow ); 171 | 172 | if( !Util.FileSelectObj( 173 | Config.DefaultFolderObj, 174 | ref _filename ) ) 175 | { 176 | return Result.Cancelled; 177 | } 178 | 179 | Config.DefaultFolderObj 180 | = Path.GetDirectoryName( _filename ); 181 | 182 | long fileSize = Util.GetFileSize( _filename ); 183 | 184 | if( fileSize > Config.MaxFileSize ) 185 | { 186 | string msg = string.Format( "Excuse me, but " 187 | + "you are attempting to load a file that is " 188 | + "{0} bytes in size. We suggest ensuring " 189 | + "that the file size is no larger than {1} " 190 | + "bytes, since Revit will refuse to handle " 191 | + "meshes exceeding a certain size anyway. " 192 | + "Please refer to the troubleshooting page " 193 | + "at\r\n\r\n{2}\r\n\r\n" 194 | + "for suggestions on how to optimise the " 195 | + "mesh and thus reduce file size.", 196 | fileSize, Config.MaxFileSize, 197 | TroubleshootingUrl ); 198 | 199 | TaskDialog.Show( App.Caption, msg ); 200 | 201 | return Result.Failed; 202 | } 203 | 204 | FileLoadResult obj_load_result = null; 205 | List vertices = null; 206 | 207 | try 208 | { 209 | bool loadTextureImages = true; 210 | 211 | obj_load_result = FileFormatObj.Load( 212 | _filename, loadTextureImages ); 213 | 214 | foreach( var m in obj_load_result.Messages ) 215 | { 216 | Debug.Print( "{0}: {1} line {2} in {3}", 217 | m.MessageType, m.Details, 218 | m.FileName, m.LineNumber ); 219 | } 220 | 221 | // Convert OBJ vertices to Revit XYZ. 222 | // OBJ assumes X to the right, Y up and Z out of the screen. 223 | // Revit 3D view assumes X right, Y away 224 | // from the screen and Z up. 225 | 226 | double scale = Config.InputScaleFactor; 227 | 228 | int n = obj_load_result.Model.Vertices.Count; 229 | 230 | vertices = new List( n ); 231 | XYZ w; 232 | 233 | foreach( Vertex v in obj_load_result.Model.Vertices ) 234 | { 235 | w = new XYZ( v.x * scale, 236 | -v.z * scale, v.y * scale ); 237 | 238 | Debug.Print( "({0},{1},{2}) --> {3}", 239 | Util.RealString( v.x ), 240 | Util.RealString( v.y ), 241 | Util.RealString( v.z ), 242 | Util.PointString( w ) ); 243 | 244 | vertices.Add( w ); 245 | } 246 | 247 | foreach( Face f in obj_load_result.Model.UngroupedFaces ) 248 | { 249 | n = f.Indices.Count; 250 | 251 | Debug.Assert( 3 == n || 4 == n, 252 | "expected triangles or quadrilaterals" ); 253 | 254 | Debug.Print( string.Join( ", ", 255 | f.Indices.Select( 256 | i => i.vertex.ToString() ) ) ); 257 | } 258 | } 259 | catch( System.Exception ex ) 260 | { 261 | message = string.Format( 262 | "Exception reading '{0}':" 263 | + "\r\n{1}:\r\n{2}", 264 | _filename, 265 | ex.GetType().FullName, 266 | ex.Message ); 267 | 268 | return Result.Failed; 269 | } 270 | 271 | if( vertices.Count > Config.MaxNumberOfVertices ) 272 | { 273 | string msg = string.Format( "Excuse me, but " 274 | + "you are attempting to load a mesh defining " 275 | + "{0} vertices. We suggest using no more than " 276 | + "{1}, since Revit will refuse to handle such " 277 | + "a large mesh anyway. " 278 | + "Please refer to the troubleshooting page at " 279 | + "\r\n\r\n{2}\r\n\r\n" 280 | + "for suggestions on how to optimise the mesh " 281 | + "and thus reduce its size.", 282 | vertices.Count, Config.MaxNumberOfVertices, 283 | TroubleshootingUrl ); 284 | 285 | TaskDialog.Show( App.Caption, msg ); 286 | 287 | return Result.Failed; 288 | } 289 | 290 | UIApplication uiapp = commandData.Application; 291 | UIDocument uidoc = uiapp.ActiveUIDocument; 292 | Document doc = uidoc.Document; 293 | 294 | string appGuid 295 | = uiapp.ActiveAddInId.GetGUID().ToString(); 296 | 297 | string shapeName = Util.Capitalize( 298 | Path.GetFileNameWithoutExtension( _filename ) 299 | .Replace( '_', ' ' ) ); 300 | 301 | // Retrieve "" graphics style, 302 | // if it exists. 303 | 304 | FilteredElementCollector collector 305 | = new FilteredElementCollector( doc ) 306 | .OfClass( typeof( GraphicsStyle ) ); 307 | 308 | GraphicsStyle style 309 | = collector.Cast() 310 | .FirstOrDefault( gs 311 | => gs.Name.Equals( "" ) ); 312 | 313 | ElementId graphicsStyleId = null; 314 | 315 | if( style != null ) 316 | { 317 | graphicsStyleId = style.Id; 318 | } 319 | 320 | Result rc = Result.Failed; 321 | 322 | try 323 | { 324 | using( Transaction tx = new Transaction( doc ) ) 325 | { 326 | tx.Start( "Create DirectShape from OBJ" ); 327 | 328 | int nFaces = 0; // set to -1 on fatal error 329 | int nFacesTotal = 0; 330 | 331 | if( 0 < obj_load_result.Model.UngroupedFaces.Count ) 332 | { 333 | nFacesTotal = nFaces = NewDirectShape( vertices, 334 | obj_load_result.Model.UngroupedFaces, doc, 335 | graphicsStyleId, appGuid, shapeName ); 336 | } 337 | 338 | if( -1 < nFaces ) 339 | { 340 | foreach( Group g in obj_load_result.Model.Groups ) 341 | { 342 | string s = string.Join( ".", g.Names ); 343 | 344 | if( 0 < s.Length ) { s = "." + s; } 345 | 346 | nFaces = NewDirectShape( vertices, g.Faces, 347 | doc, graphicsStyleId, appGuid, 348 | shapeName + s ); 349 | 350 | if( -1 == nFaces ) 351 | { 352 | break; 353 | } 354 | 355 | nFacesTotal += nFaces; 356 | } 357 | } 358 | 359 | if( -1 == nFaces ) 360 | { 361 | message = "Invalid OBJ file. Error: face " 362 | + "vertex index exceeds total vertex count."; 363 | } 364 | else if( 0 == nFacesTotal ) 365 | { 366 | message = "Invalid OBJ file. Zero faces found."; 367 | } 368 | else 369 | { 370 | tx.Commit(); 371 | 372 | rc = Result.Succeeded; 373 | } 374 | } 375 | } 376 | catch( System.Exception ex ) 377 | { 378 | message = string.Format( 379 | "Exception generating DirectShape '{0}':" 380 | + "\r\n{1}:\r\n{2}", 381 | shapeName, 382 | ex.GetType().FullName, 383 | ex.Message ); 384 | 385 | return Result.Failed; 386 | } 387 | return rc; 388 | } 389 | } 390 | } 391 | --------------------------------------------------------------------------------