├── 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 | 
13 |
14 | Sample fire hydrant OBJ file:
15 |
16 | 
17 |
18 | Resulting DirectShape element in Revit model:
19 |
20 | 
21 |
22 | Input scaling factor 1 versus 0.5 happily produces a gargoyle and a half:
23 |
24 | 
25 |
26 | OBJ files defining groups generate a separate DirectShape element for each one:
27 |
28 | 
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 | 
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 | 
37 |
38 | Release 2015.0.0.17 improved error handling on degenerate faces:
39 |
40 | 
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 |
--------------------------------------------------------------------------------