├── .gitignore
├── LICENSE
├── README.md
├── RoomVolumeDirectShape.sln
├── RoomVolumeDirectShape
├── Command.cs
├── GltfNodeData.cs
├── IntPoint3d.cs
├── Properties
│ └── AssemblyInfo.cs
├── RoomVolumeDirectShape.addin
├── RoomVolumeDirectShape.csproj
├── TriangleIndices.cs
└── Util.cs
└── img
├── rac_basic_sample_project.png
└── rac_basic_sample_project_room_volumes.png
/.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 | *.suo
8 | *.user
9 | *.userosscache
10 | *.sln.docstates
11 |
12 | # User-specific files (MonoDevelop/Xamarin Studio)
13 | *.userprefs
14 |
15 | # Build results
16 | [Dd]ebug/
17 | [Dd]ebugPublic/
18 | [Rr]elease/
19 | [Rr]eleases/
20 | x64/
21 | x86/
22 | bld/
23 | [Bb]in/
24 | [Oo]bj/
25 | [Ll]og/
26 |
27 | # Visual Studio 2015/2017 cache/options directory
28 | .vs/
29 | # Uncomment if you have tasks that create the project's static files in wwwroot
30 | #wwwroot/
31 |
32 | # Visual Studio 2017 auto generated files
33 | Generated\ Files/
34 |
35 | # MSTest test Results
36 | [Tt]est[Rr]esult*/
37 | [Bb]uild[Ll]og.*
38 |
39 | # NUNIT
40 | *.VisualState.xml
41 | TestResult.xml
42 |
43 | # Build Results of an ATL Project
44 | [Dd]ebugPS/
45 | [Rr]eleasePS/
46 | dlldata.c
47 |
48 | # Benchmark Results
49 | BenchmarkDotNet.Artifacts/
50 |
51 | # .NET Core
52 | project.lock.json
53 | project.fragment.lock.json
54 | artifacts/
55 | **/Properties/launchSettings.json
56 |
57 | # StyleCop
58 | StyleCopReport.xml
59 |
60 | # Files built by Visual Studio
61 | *_i.c
62 | *_p.c
63 | *_i.h
64 | *.ilk
65 | *.meta
66 | *.obj
67 | *.iobj
68 | *.pch
69 | *.pdb
70 | *.ipdb
71 | *.pgc
72 | *.pgd
73 | *.rsp
74 | *.sbr
75 | *.tlb
76 | *.tli
77 | *.tlh
78 | *.tmp
79 | *.tmp_proj
80 | *.log
81 | *.vspscc
82 | *.vssscc
83 | .builds
84 | *.pidb
85 | *.svclog
86 | *.scc
87 |
88 | # Chutzpah Test files
89 | _Chutzpah*
90 |
91 | # Visual C++ cache files
92 | ipch/
93 | *.aps
94 | *.ncb
95 | *.opendb
96 | *.opensdf
97 | *.sdf
98 | *.cachefile
99 | *.VC.db
100 | *.VC.VC.opendb
101 |
102 | # Visual Studio profiler
103 | *.psess
104 | *.vsp
105 | *.vspx
106 | *.sap
107 |
108 | # Visual Studio Trace Files
109 | *.e2e
110 |
111 | # TFS 2012 Local Workspace
112 | $tf/
113 |
114 | # Guidance Automation Toolkit
115 | *.gpState
116 |
117 | # ReSharper is a .NET coding add-in
118 | _ReSharper*/
119 | *.[Rr]e[Ss]harper
120 | *.DotSettings.user
121 |
122 | # JustCode is a .NET coding add-in
123 | .JustCode
124 |
125 | # TeamCity is a build add-in
126 | _TeamCity*
127 |
128 | # DotCover is a Code Coverage Tool
129 | *.dotCover
130 |
131 | # AxoCover is a Code Coverage Tool
132 | .axoCover/*
133 | !.axoCover/settings.json
134 |
135 | # Visual Studio code coverage results
136 | *.coverage
137 | *.coveragexml
138 |
139 | # NCrunch
140 | _NCrunch_*
141 | .*crunch*.local.xml
142 | nCrunchTemp_*
143 |
144 | # MightyMoose
145 | *.mm.*
146 | AutoTest.Net/
147 |
148 | # Web workbench (sass)
149 | .sass-cache/
150 |
151 | # Installshield output folder
152 | [Ee]xpress/
153 |
154 | # DocProject is a documentation generator add-in
155 | DocProject/buildhelp/
156 | DocProject/Help/*.HxT
157 | DocProject/Help/*.HxC
158 | DocProject/Help/*.hhc
159 | DocProject/Help/*.hhk
160 | DocProject/Help/*.hhp
161 | DocProject/Help/Html2
162 | DocProject/Help/html
163 |
164 | # Click-Once directory
165 | publish/
166 |
167 | # Publish Web Output
168 | *.[Pp]ublish.xml
169 | *.azurePubxml
170 | # Note: Comment the next line if you want to checkin your web deploy settings,
171 | # but database connection strings (with potential passwords) will be unencrypted
172 | *.pubxml
173 | *.publishproj
174 |
175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
176 | # checkin your Azure Web App publish settings, but sensitive information contained
177 | # in these scripts will be unencrypted
178 | PublishScripts/
179 |
180 | # NuGet Packages
181 | *.nupkg
182 | # The packages folder can be ignored because of Package Restore
183 | **/[Pp]ackages/*
184 | # except build/, which is used as an MSBuild target.
185 | !**/[Pp]ackages/build/
186 | # Uncomment if necessary however generally it will be regenerated when needed
187 | #!**/[Pp]ackages/repositories.config
188 | # NuGet v3's project.json files produces more ignorable files
189 | *.nuget.props
190 | *.nuget.targets
191 |
192 | # Microsoft Azure Build Output
193 | csx/
194 | *.build.csdef
195 |
196 | # Microsoft Azure Emulator
197 | ecf/
198 | rcf/
199 |
200 | # Windows Store app package directories and files
201 | AppPackages/
202 | BundleArtifacts/
203 | Package.StoreAssociation.xml
204 | _pkginfo.txt
205 | *.appx
206 |
207 | # Visual Studio cache files
208 | # files ending in .cache can be ignored
209 | *.[Cc]ache
210 | # but keep track of directories ending in .cache
211 | !*.[Cc]ache/
212 |
213 | # Others
214 | ClientBin/
215 | ~$*
216 | *~
217 | *.dbmdl
218 | *.dbproj.schemaview
219 | *.jfm
220 | *.pfx
221 | *.publishsettings
222 | orleans.codegen.cs
223 |
224 | # Including strong name files can present a security risk
225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
226 | #*.snk
227 |
228 | # Since there are multiple workflows, uncomment next line to ignore bower_components
229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
230 | #bower_components/
231 |
232 | # RIA/Silverlight projects
233 | Generated_Code/
234 |
235 | # Backup & report files from converting an old project file
236 | # to a newer Visual Studio version. Backup files are not needed,
237 | # because we have git ;-)
238 | _UpgradeReport_Files/
239 | Backup*/
240 | UpgradeLog*.XML
241 | UpgradeLog*.htm
242 | ServiceFabricBackup/
243 | *.rptproj.bak
244 |
245 | # SQL Server files
246 | *.mdf
247 | *.ldf
248 | *.ndf
249 |
250 | # Business Intelligence projects
251 | *.rdl.data
252 | *.bim.layout
253 | *.bim_*.settings
254 | *.rptproj.rsuser
255 |
256 | # Microsoft Fakes
257 | FakesAssemblies/
258 |
259 | # GhostDoc plugin setting file
260 | *.GhostDoc.xml
261 |
262 | # Node.js Tools for Visual Studio
263 | .ntvs_analysis.dat
264 | node_modules/
265 |
266 | # Visual Studio 6 build log
267 | *.plg
268 |
269 | # Visual Studio 6 workspace options file
270 | *.opt
271 |
272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
273 | *.vbw
274 |
275 | # Visual Studio LightSwitch build output
276 | **/*.HTMLClient/GeneratedArtifacts
277 | **/*.DesktopClient/GeneratedArtifacts
278 | **/*.DesktopClient/ModelManifest.xml
279 | **/*.Server/GeneratedArtifacts
280 | **/*.Server/ModelManifest.xml
281 | _Pvt_Extensions
282 |
283 | # Paket dependency manager
284 | .paket/paket.exe
285 | paket-files/
286 |
287 | # FAKE - F# Make
288 | .fake/
289 |
290 | # JetBrains Rider
291 | .idea/
292 | *.sln.iml
293 |
294 | # CodeRush
295 | .cr/
296 |
297 | # Python Tools for Visual Studio (PTVS)
298 | __pycache__/
299 | *.pyc
300 |
301 | # Cake - Uncomment if you are using it
302 | # tools/**
303 | # !tools/packages.config
304 |
305 | # Tabs Studio
306 | *.tss
307 |
308 | # Telerik's JustMock configuration file
309 | *.jmconfig
310 |
311 | # BizTalk build output
312 | *.btp.cs
313 | *.btm.cs
314 | *.odx.cs
315 | *.xsd.cs
316 |
317 | # OpenCover UI analysis results
318 | OpenCover/
319 |
320 | # Azure Stream Analytics local run output
321 | ASALocalRun/
322 |
323 | # MSBuild Binary and Structured Log
324 | *.binlog
325 |
326 | # NVidia Nsight GPU debugger configuration file
327 | *.nvuser
328 |
329 | # MFractors (Xamarin productivity tool) working folder
330 | .mfractor/
331 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RoomVolumeDirectShape
2 |
3 | Revit C# .NET add-in creating DirectShape elements representing room volume.
4 |
5 | The RoomVolumeDirectShape add-in performs the following simple steps:
6 |
7 | - Retrieve all rooms in the BIM using a filtered element collector, then, for each room:
8 | - Query each room for its closed shell using
9 | the [ClosedShell API call](https://www.revitapidocs.com/2020/1a510aef-63f6-4d32-c0ff-a8071f5e23b8.htm)
10 | - Generate a [DirectShape element](https://www.revitapidocs.com/2020/bfbd137b-c2c2-71bb-6f4a-992d0dcf6ea8.htm) representing the geometry
11 | - Query each room for all its properties, mostly stored in parameters
12 | (cf., [getting all parameter values](https://thebuildingcoder.typepad.com/blog/2018/05/getting-all-parameter-values.html)
13 | and [retrieving parameter values from an element](https://thebuildingcoder.typepad.com/blog/2018/05/getting-all-parameter-values.html#5))
14 | - Generate a JSON string representing a dictionary of the room properties
15 | - Store the room property JSON string in the DirectShape Comment property
16 |
17 | ## Motivation
18 |
19 | This add-in was inspired by the following request:
20 |
21 | The context: We are building digital twins out of BIM data. To do so, we use Revit, Dynamo, and Forge.
22 |
23 | The issue: We rely on the rooms in Revit to perform a bunch of tasks (reassign equipment localization, rebuild a navigation tree, and so on).
24 |
25 | Unfortunately, theses rooms are not displayed in the Revit 3D view.
26 |
27 | Therefore, they are nowhere to be found in the Forge SVF file.
28 |
29 | Our (so-so) solution: The original solution was developed with Autodesk consulting.
30 |
31 | We use Dynamo to extract the room geometry and build Revit volumes.
32 |
33 | It works, but it is:
34 |
35 | - Not very robust: Some rooms has to be recreated manually, Dynamo crashes, geometry with invalid faces is produced, etc.
36 | - Not very fast: The actual script exports SAT files and reimports them.
37 | - Manual: Obviously, and also tedious and error-prone.
38 |
39 | The whole process amounts to several hours of manual work.
40 |
41 | We want to fix this.
42 |
43 | Our goal: A robust implementation that will get rid of Dynamo, automate the process in Revit, and in the end, run that in a Forge Design Automation process.
44 |
45 | The ideal way forward is exactly what you describe: A native C# Revit API that find the rooms, creates a direct shape volume for them, and copy their properties to that.
46 |
47 | No intermediate formats, no UI, just straight automation work.
48 |
49 |
50 | ## Solution
51 |
52 | The solution is explained in detail
53 | in [The Building Coder](https://thebuildingcoder.typepad.com) discussion
54 | on [`DirectShape` element to represent room volume](https://thebuildingcoder.typepad.com/blog/2019/05/generate-directshape-element-to-represent-room-volume.html).
55 |
56 |
57 | ## Sample Run
58 |
59 | I tested this in the well-known standard Revit *rac_basic_sample_project.rvt* sample model:
60 |
61 |
62 |
63 |
64 |
65 | Isolated, the resulting direct shapes look like this:
66 |
67 |
68 |
69 |
70 |
71 |
72 | ## Cleaning up the Solid for the Forge Viewer
73 |
74 | The solid returned by `Room.GetClosedShell` does not display properly in the Forge viewer; in fact, the generic model direct shape elements are completely ignored and do not even appear in the Forge viewer model browser.
75 |
76 | Implemented `CopyGeometry` to fix that, and tried various approaches to recreate the solid myself, iterating over the solid faces and building new faces with a `TessellatedShapeBuilder`.
77 |
78 | Three approaches attemtped, using:
79 |
80 | - `Face.Triangulate`
81 | - `Face.EdgeLoops`
82 | - `Face.GetEdgesAsCurveLoops`
83 |
84 | Using the unoriented edge loops does not work. It would require reorienting the edges properly to define a valid new solid.
85 |
86 | Using `GetEdgesAsCurveLoops` creates a good-looking solid in Revit, but it has some weird normals in the Forge viewer.
87 |
88 | Currently, the triangulation approach seems be the only one that deliver reliable results in the Forge viewer.
89 |
90 |
91 | ## Author
92 |
93 | Jeremy Tammik, [The Building Coder](http://thebuildingcoder.typepad.com), [ADN](http://www.autodesk.com/adn) [Open](http://www.autodesk.com/adnopen), [Autodesk Inc.](http://www.autodesk.com)
94 |
95 |
96 | ## License
97 |
98 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT).
99 | Please see the [LICENSE](LICENSE) file for full details.
100 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoomVolumeDirectShape", "RoomVolumeDirectShape\RoomVolumeDirectShape.csproj", "{338EE02A-603F-468D-B3EB-CF2AD9E8F245}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {338EE02A-603F-468D-B3EB-CF2AD9E8F245}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {338EE02A-603F-468D-B3EB-CF2AD9E8F245}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {338EE02A-603F-468D-B3EB-CF2AD9E8F245}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {338EE02A-603F-468D-B3EB-CF2AD9E8F245}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/Command.cs:
--------------------------------------------------------------------------------
1 | //#define USE_FACE_TRIANGULATION
2 | #define CREATE_NEW_SOLID_USING_TESSELATION
3 |
4 | #region Namespaces
5 | using System;
6 | using System.Linq;
7 | using System.Collections.Generic;
8 | using System.Diagnostics;
9 | using Autodesk.Revit.ApplicationServices;
10 | using Autodesk.Revit.Attributes;
11 | using Autodesk.Revit.DB;
12 | using Autodesk.Revit.DB.Architecture;
13 | using Autodesk.Revit.UI;
14 | using System.IO;
15 | #endregion
16 |
17 | namespace RoomVolumeDirectShape
18 | {
19 | [Transaction( TransactionMode.Manual )]
20 | public class Command : IExternalCommand
21 | {
22 | // Cannot use OST_Rooms; DirectShape.CreateElement
23 | // throws ArgumentExceptionL: Element id categoryId
24 | // may not be used as a DirectShape category.
25 |
26 | ///
27 | /// Category assigned to the room volume direct shape
28 | ///
29 | ElementId _id_category_for_direct_shape
30 | = new ElementId( BuiltInCategory.OST_GenericModel );
31 |
32 | ///
33 | /// DirectShape parameter to populate with JSON
34 | /// dictionary containing all room properies
35 | ///
36 | BuiltInParameter _bip_properties
37 | = BuiltInParameter.ALL_MODEL_INSTANCE_COMMENTS;
38 |
39 | ///
40 | /// Return a JSON string representing a dictionary
41 | /// mapping string key to string value.
42 | ///
43 | static string FormatDictAsJson(
44 | Dictionary d )
45 | {
46 | List keys = new List( d.Keys );
47 | keys.Sort();
48 |
49 | List key_vals = new List(
50 | keys.Count );
51 |
52 | foreach( string key in keys )
53 | {
54 | key_vals.Add(
55 | string.Format( "\"{0}\" : \"{1}\"",
56 | key, d[key] ) );
57 | }
58 | return "{" + string.Join( ", ", key_vals ) + "}";
59 | }
60 |
61 | ///
62 | /// Return parameter storage type abbreviation
63 | ///
64 | static char ParameterStorageTypeChar(
65 | Parameter p )
66 | {
67 | if( null == p )
68 | {
69 | throw new ArgumentNullException(
70 | "p", "expected non-null parameter" );
71 | }
72 |
73 | char abbreviation = '?';
74 |
75 | switch( p.StorageType )
76 | {
77 | case StorageType.Double:
78 | abbreviation = 'r'; // real number
79 | break;
80 | case StorageType.Integer:
81 | abbreviation = 'n'; // integer number
82 | break;
83 | case StorageType.String:
84 | abbreviation = 's'; // string
85 | break;
86 | case StorageType.ElementId:
87 | abbreviation = 'e'; // element id
88 | break;
89 | case StorageType.None:
90 | throw new ArgumentOutOfRangeException(
91 | "p", "expected valid parameter "
92 | + "storage type, not 'None'" );
93 | }
94 | return abbreviation;
95 | }
96 |
97 | ///
98 | /// Return parameter value formatted as string
99 | ///
100 | static string ParameterToString( Parameter p )
101 | {
102 | string s = "null";
103 |
104 | if( p != null )
105 | {
106 | switch( p.StorageType )
107 | {
108 | case StorageType.Double:
109 | s = p.AsDouble().ToString( "0.##" );
110 | break;
111 | case StorageType.Integer:
112 | s = p.AsInteger().ToString();
113 | break;
114 | case StorageType.String:
115 | s = p.AsString();
116 | break;
117 | case StorageType.ElementId:
118 | s = p.AsElementId().IntegerValue.ToString();
119 | break;
120 | case StorageType.None:
121 | s = "none";
122 | break;
123 | }
124 | }
125 | return s;
126 | }
127 |
128 | ///
129 | /// Return all the element parameter values in a
130 | /// dictionary mapping parameter names to values
131 | ///
132 | static Dictionary GetParamValues(
133 | Element e )
134 | {
135 | // Two choices:
136 | // Element.Parameters property -- Retrieves
137 | // a set containing all the parameters.
138 | // GetOrderedParameters method -- Gets the
139 | // visible parameters in order.
140 |
141 | //IList ps = e.GetOrderedParameters();
142 |
143 | ParameterSet pset = e.Parameters;
144 |
145 | Dictionary d
146 | = new Dictionary( pset.Size );
147 |
148 | foreach( Parameter p in pset )
149 | {
150 | // AsValueString displays the value as the
151 | // user sees it. In some cases, the underlying
152 | // database value returned by AsInteger, AsDouble,
153 | // etc., may be more relevant, as done by
154 | // ParameterToString
155 |
156 | string key = string.Format( "{0}({1})",
157 | p.Definition.Name,
158 | ParameterStorageTypeChar( p ) );
159 |
160 | string val = ParameterToString( p );
161 |
162 | if( d.ContainsKey( key ) )
163 | {
164 | if( d[key] != val )
165 | {
166 | d[key] += " | " + val;
167 | }
168 | }
169 | else
170 | {
171 | d.Add( key, val );
172 | }
173 | }
174 | return d;
175 | }
176 |
177 | static string GetRoomPropertiesJson( Room r )
178 | {
179 | Dictionary param_values
180 | = GetParamValues( r );
181 |
182 | // These room properties are all stored in
183 | // parameters and therefore already captured
184 |
185 | //double baseOffset = r.BaseOffset;
186 | //double limitOffset = r.LimitOffset;
187 | //double unboundedHeight = r.UnboundedHeight;
188 | //string upperLimit = r.UpperLimit.Name;
189 | //double volume = r.Volume;
190 |
191 | return FormatDictAsJson( param_values );
192 | }
193 |
194 | ///
195 | /// XYZ equality comparer to eliminate
196 | /// slightly differing vertices
197 | ///
198 | class XyzEqualityComparer : IEqualityComparer
199 | {
200 | ///
201 | /// Tolerance.
202 | /// 0.003 imperial feet is ca. 0.9 mm
203 | ///
204 | double _tol = 0.003;
205 |
206 | public bool Equals( XYZ a, XYZ b )
207 | {
208 | return _tol > a.DistanceTo( b );
209 | }
210 |
211 | public int GetHashCode( XYZ a )
212 | {
213 | string format = "0.####";
214 | string s = a.X.ToString( format )
215 | + "," + a.Y.ToString( format )
216 | + "," + a.Z.ToString( format );
217 | return s.GetHashCode();
218 | }
219 | }
220 |
221 | ///
222 | /// Create a new list of geometry objects from the
223 | /// given input. As input, we supply the result of
224 | /// Room.GetClosedShell. The output is the exact
225 | /// same solid lacking whatever flaws are present
226 | /// in the input solid.
227 | ///
228 | static IList CopyGeometry(
229 | GeometryElement geo,
230 | ElementId materialId,
231 | List coords,
232 | List indices )
233 | {
234 | TessellatedShapeBuilderResult result = null;
235 |
236 | TessellatedShapeBuilder builder
237 | = new TessellatedShapeBuilder();
238 |
239 | // Need to include the key in the value, otherwise
240 | // no way to access it later, cf.
241 | // https://stackoverflow.com/questions/1619090/getting-a-keyvaluepair-directly-from-a-dictionary
242 |
243 | Dictionary> pts
244 | = new Dictionary>(
245 | new XyzEqualityComparer() );
246 |
247 | int nSolids = 0;
248 | //int nFaces = 0;
249 | int nTriangles = 0;
250 | //int nVertices = 0;
251 | List vertices = new List( 3 );
252 |
253 | foreach( GeometryObject obj in geo )
254 | {
255 | Solid solid = obj as Solid;
256 |
257 | if( null != solid )
258 | {
259 | if( 0 < solid.Volume )
260 | {
261 | ++nSolids;
262 |
263 | builder.OpenConnectedFaceSet( false );
264 |
265 | #region Create a new solid based on tessellation of the invalid room closed shell solid
266 | #if CREATE_NEW_SOLID_USING_TESSELATION
267 |
268 | Debug.Assert(
269 | SolidUtils.IsValidForTessellation( solid ),
270 | "expected a valid solid for room closed shell" );
271 |
272 | SolidOrShellTessellationControls controls
273 | = new SolidOrShellTessellationControls()
274 | {
275 | //
276 | // Summary:
277 | // A positive real number specifying how accurately a triangulation should approximate
278 | // a solid or shell.
279 | //
280 | // Exceptions:
281 | // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException:
282 | // When setting this property: The given value for accuracy must be greater than
283 | // 0 and no more than 30000 feet.
284 | // This statement is not true. I set Accuracy = 0.003 and an exception was thrown.
285 | // Setting it to 0.006 was acceptable. 0.03 is a bit over 9 mm.
286 | //
287 | // Remarks:
288 | // The maximum distance from a point on the triangulation to the nearest point on
289 | // the solid or shell should be no greater than the specified accuracy. This constraint
290 | // may be approximately enforced.
291 | Accuracy = 0.03,
292 | //
293 | // Summary:
294 | // An number between 0 and 1 (inclusive) specifying the level of detail for the
295 | // triangulation of a solid or shell.
296 | //
297 | // Exceptions:
298 | // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException:
299 | // When setting this property: The given value for levelOfDetail must lie between
300 | // 0 and 1 (inclusive).
301 | //
302 | // Remarks:
303 | // Smaller values yield coarser triangulations (fewer triangles), while larger values
304 | // yield finer triangulations (more triangles).
305 | LevelOfDetail = 0.1,
306 | //
307 | // Summary:
308 | // A non-negative real number specifying the minimum allowed angle for any triangle
309 | // in the triangulation, in radians.
310 | //
311 | // Exceptions:
312 | // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException:
313 | // When setting this property: The given value for minAngleInTriangle must be at
314 | // least 0 and less than 60 degrees, expressed in radians. The value 0 means to
315 | // ignore the minimum angle constraint.
316 | //
317 | // Remarks:
318 | // A small value can be useful when triangulating long, thin objects, in order to
319 | // keep the number of triangles small, but it can result in long, thin triangles,
320 | // which are not acceptable for all applications. If the value is too large, this
321 | // constraint may not be satisfiable, causing the triangulation to fail. This constraint
322 | // may be approximately enforced. A value of 0 means to ignore the minimum angle
323 | // constraint.
324 | MinAngleInTriangle = 3 * Math.PI / 180.0,
325 | //
326 | // Summary:
327 | // A positive real number specifying the minimum allowed value for the external
328 | // angle between two adjacent triangles, in radians.
329 | //
330 | // Exceptions:
331 | // T:Autodesk.Revit.Exceptions.ArgumentOutOfRangeException:
332 | // When setting this property: The given value for minExternalAngleBetweenTriangles
333 | // must be greater than 0 and no more than 30000 feet.
334 | //
335 | // Remarks:
336 | // A small value yields more smoothly curved triangulated surfaces, usually at the
337 | // expense of an increase in the number of triangles. Note that this setting has
338 | // no effect for planar surfaces. This constraint may be approximately enforced.
339 | MinExternalAngleBetweenTriangles = 0.2 * Math.PI
340 | };
341 |
342 | TriangulatedSolidOrShell shell
343 | = SolidUtils.TessellateSolidOrShell( solid, controls );
344 |
345 | int n = shell.ShellComponentCount;
346 |
347 | Debug.Assert( 1 == n,
348 | "expected just one shell component in room closed shell" );
349 |
350 | TriangulatedShellComponent component
351 | = shell.GetShellComponent( 0 );
352 |
353 | int coordsBase = coords.Count;
354 | int indicesBase = indices.Count;
355 |
356 | n = component.VertexCount;
357 |
358 | for( int i = 0; i < n; ++i )
359 | {
360 | XYZ v = component.GetVertex( i );
361 | coords.Add( new IntPoint3d( v ) );
362 | }
363 |
364 | n = component.TriangleCount;
365 |
366 | for( int i = 0; i < n; ++i )
367 | {
368 | TriangleInShellComponent t
369 | = component.GetTriangle( i );
370 |
371 | vertices.Clear();
372 |
373 | vertices.Add( component.GetVertex( t.VertexIndex0 ) );
374 | vertices.Add( component.GetVertex( t.VertexIndex1 ) );
375 | vertices.Add( component.GetVertex( t.VertexIndex2 ) );
376 |
377 | indices.Add( new TriangleIndices(
378 | coordsBase + t.VertexIndex0,
379 | coordsBase + t.VertexIndex1,
380 | coordsBase + t.VertexIndex2 ) );
381 |
382 | TessellatedFace tf = new TessellatedFace(
383 | vertices, materialId );
384 |
385 | if( builder.DoesFaceHaveEnoughLoopsAndVertices( tf ) )
386 | {
387 | builder.AddFace( tf );
388 | ++nTriangles;
389 | }
390 | }
391 | #else
392 | // Iterate over the individual solid faces
393 |
394 | foreach( Face f in solid.Faces )
395 | {
396 | vertices.Clear();
397 |
398 | #region Use face triangulation
399 | #if USE_FACE_TRIANGULATION
400 |
401 | Mesh mesh = f.Triangulate();
402 | int n = mesh.NumTriangles;
403 |
404 | for( int i = 0; i < n; ++i )
405 | {
406 | MeshTriangle triangle = mesh.get_Triangle( i );
407 |
408 | XYZ p1 = triangle.get_Vertex( 0 );
409 | XYZ p2 = triangle.get_Vertex( 1 );
410 | XYZ p3 = triangle.get_Vertex( 2 );
411 |
412 | vertices.Clear();
413 | vertices.Add( p1 );
414 | vertices.Add( p2 );
415 | vertices.Add( p3 );
416 |
417 | TessellatedFace tf
418 | = new TessellatedFace(
419 | vertices, materialId );
420 |
421 | if( builder.DoesFaceHaveEnoughLoopsAndVertices( tf ) )
422 | {
423 | builder.AddFace( tf );
424 | ++nTriangles;
425 | }
426 | }
427 | #endif // USE_FACE_TRIANGULATION
428 | #endregion // Use face triangulation
429 |
430 | #region Use original solid and its EdgeLoops
431 | #if USE_EDGELOOPS
432 | // This returns arbitrarily ordered and
433 | // oriented edges, so no solid can be
434 | // generated.
435 |
436 | foreach( EdgeArray loop in f.EdgeLoops )
437 | {
438 | foreach( Edge e in loop )
439 | {
440 | XYZ p = e.AsCurve().GetEndPoint( 0 );
441 | XYZ q = p;
442 |
443 | if( pts.ContainsKey( p ) )
444 | {
445 | KeyValuePair kv = pts[p];
446 | q = kv.Key;
447 | int n = kv.Value;
448 | pts[p] = new KeyValuePair(
449 | q, ++n );
450 |
451 | Debug.Print( "Ignoring vertex at {0} "
452 | + "with distance {1} to existing "
453 | + "vertex {2}",
454 | p, p.DistanceTo( q ), q );
455 | }
456 | else
457 | {
458 | pts[p] = new KeyValuePair(
459 | p, 1 );
460 | }
461 |
462 | vertices.Add( q );
463 | ++nVertices;
464 | }
465 | }
466 | #endif // USE_EDGELOOPS
467 | #endregion // Use original solid and its EdgeLoops
468 |
469 | #region Use original solid and GetEdgesAsCurveLoops
470 | #if USE_AS_CURVE_LOOPS
471 |
472 | // The solids generated by this have some weird
473 | // normals, so they do not render correctly in
474 | // the Forge viewer. Revert to triangles again.
475 |
476 | IList loops
477 | = f.GetEdgesAsCurveLoops();
478 |
479 | foreach( CurveLoop loop in loops )
480 | {
481 | foreach( Curve c in loop )
482 | {
483 | XYZ p = c.GetEndPoint( 0 );
484 | XYZ q = p;
485 |
486 | if( pts.ContainsKey( p ) )
487 | {
488 | KeyValuePair kv = pts[p];
489 | q = kv.Key;
490 | int n = kv.Value;
491 | pts[p] = new KeyValuePair(
492 | q, ++n );
493 |
494 | Debug.Print( "Ignoring vertex at {0} "
495 | + "with distance {1} to existing "
496 | + "vertex {2}",
497 | p, p.DistanceTo( q ), q );
498 | }
499 | else
500 | {
501 | pts[p] = new KeyValuePair(
502 | p, 1 );
503 | }
504 |
505 | vertices.Add( q );
506 | ++nVertices;
507 | }
508 | }
509 | #endif // USE_AS_CURVE_LOOPS
510 | #endregion // Use original solid and GetEdgesAsCurveLoops
511 |
512 | builder.AddFace( new TessellatedFace(
513 | vertices, materialId ) );
514 |
515 | ++nFaces;
516 | }
517 |
518 | #endif // CREATE_NEW_SOLID_USING_TESSELATION
519 | #endregion // Create a new solid based on tessellation of the invalid room closed shell solid
520 |
521 | builder.CloseConnectedFaceSet();
522 | builder.Target = TessellatedShapeBuilderTarget.AnyGeometry; // Solid failed
523 | builder.Fallback = TessellatedShapeBuilderFallback.Mesh; // use Abort if target is Solid
524 | builder.Build();
525 | result = builder.GetBuildResult();
526 |
527 | // Debug printout log of current solid's glTF facet data
528 |
529 | n = coords.Count - coordsBase;
530 |
531 | Debug.Print( "{0} glTF vertex coordinates "
532 | + "in millimetres:", n );
533 |
534 | Debug.Print( string.Join( " ", coords
535 | .TakeWhile( ( p, i ) => coordsBase <= i )
536 | .Select( p => p.ToString() ) ) );
537 |
538 | n = indices.Count - indicesBase;
539 |
540 | Debug.Print( "{0} glTF triangles:", n );
541 |
542 | Debug.Print( string.Join( " ", indices
543 | .TakeWhile( ( ti, i ) => indicesBase <= i )
544 | .Select( ti => ti.ToString() ) ) );
545 | }
546 | }
547 | }
548 | return result.GetGeometricalObjects();
549 | }
550 |
551 | public Result Execute(
552 | ExternalCommandData commandData,
553 | ref string message,
554 | ElementSet elements )
555 | {
556 | UIApplication uiapp = commandData.Application;
557 | UIDocument uidoc = uiapp.ActiveUIDocument;
558 | Application app = uiapp.Application;
559 | Document doc = uidoc.Document;
560 |
561 | string id_addin = uiapp.ActiveAddInId.GetGUID()
562 | .ToString();
563 |
564 | IEnumerable rooms
565 | = new FilteredElementCollector( doc )
566 | .WhereElementIsNotElementType()
567 | .OfClass( typeof( SpatialElement ) )
568 | .Where( e => e.GetType() == typeof( Room ) )
569 | .Cast();
570 |
571 | // Collect room data for glTF export
572 |
573 | List room_data = new List(
574 | rooms.Count() );
575 |
576 | // Collect geometry data for glTF: a list of
577 | // vertex coordinates in millimetres, and a list
578 | // of triangle vertex indices into the coord list.
579 |
580 | List gltf_coords = new List();
581 | List gltf_indices = new List();
582 |
583 | using( Transaction tx = new Transaction( doc ) )
584 | {
585 | tx.Start( "Generate Direct Shape Elements "
586 | + "Representing Room Volumes" );
587 |
588 | foreach( Room r in rooms )
589 | {
590 | Debug.Print( "Processing "
591 | + r.Name + "..." );
592 |
593 | // Collect data for current room
594 |
595 | GltfNodeData rd = new GltfNodeData( r );
596 |
597 | GeometryElement geo = r.ClosedShell;
598 |
599 | Debug.Assert(
600 | geo.First() is Solid,
601 | "expected a solid for room closed shell" );
602 |
603 | Solid solid = geo.First() as Solid;
604 |
605 | #region Fix the shape
606 | #if FIX_THE_SHAPE_SOMEHOW
607 | // Create IList step by step
608 |
609 | Solid solid = geo.First()
610 | as Solid;
611 |
612 | // The room closed shell solid faces have a
613 | // non-null graphics style id:
614 | // Interior Fill 106074
615 | // The sphere faces' graphics style id is null.
616 | // Maybe this graphics style does something
617 | // weird in the Forge viewer?
618 | // Let's create a copy of the room solid and
619 | // see whether that resets the graphics style.
620 |
621 | solid = SolidUtils.CreateTransformed(
622 | solid, Transform.Identity );
623 |
624 | shape = new GeometryObject[] { solid };
625 |
626 | // Create a sphere
627 |
628 | var center = XYZ.Zero;
629 | double radius = 2.0;
630 |
631 | var p = center + radius * XYZ.BasisY;
632 | var q = center - radius * XYZ.BasisY;
633 |
634 | var profile = new List();
635 | profile.Add( Line.CreateBound( p, q ) );
636 | profile.Add( Arc.Create( q, p,
637 | center + radius * XYZ.BasisX ) );
638 |
639 | var curveLoop = CurveLoop.Create( profile );
640 |
641 | var options = new SolidOptions(
642 | ElementId.InvalidElementId, // material
643 | ElementId.InvalidElementId ); // graphics style
644 |
645 | var frame = new Frame( center,
646 | XYZ.BasisX, -XYZ.BasisZ, XYZ.BasisY );
647 |
648 | var sphere = GeometryCreationUtilities
649 | .CreateRevolvedGeometry( frame,
650 | new CurveLoop[] { curveLoop },
651 | 0, 2 * Math.PI, options );
652 |
653 | shape = new GeometryObject[] { solid, sphere };
654 | #endif // #if FIX_THE_SHAPE_SOMEHOW
655 | #endregion // Fix the shape
656 |
657 | IList shape
658 | = geo.ToList();
659 |
660 | // Previous counts define offsets
661 | // to new binary data
662 |
663 | rd.CoordinatesBegin = gltf_coords.Count;
664 | rd.TriangleVertexIndicesBegin
665 | = gltf_indices.Count;
666 |
667 | // Create a new solid to use for the direct
668 | // shape from the flawed solid returned by
669 | // GetClosedShell and gather glTF facet data
670 |
671 | shape = CopyGeometry( geo,
672 | ElementId.InvalidElementId,
673 | gltf_coords, gltf_indices );
674 |
675 | rd.CoordinatesCount = gltf_coords.Count
676 | - rd.CoordinatesBegin;
677 |
678 | rd.TriangleVertexIndexCount
679 | = gltf_indices.Count
680 | - rd.TriangleVertexIndicesBegin;
681 |
682 | IEnumerable pts
683 | = gltf_coords.Skip(
684 | rd.CoordinatesBegin );
685 |
686 | rd.Min = new IntPoint3d(
687 | pts.Min( p => p.X ),
688 | pts.Min( p => p.Y ),
689 | pts.Min( p => p.Z ) );
690 | rd.Max = new IntPoint3d(
691 | pts.Max( p => p.X ),
692 | pts.Max( p => p.Y ),
693 | pts.Max( p => p.Z ) );
694 |
695 | Dictionary param_values
696 | = GetParamValues( r );
697 |
698 | string json = FormatDictAsJson( param_values );
699 |
700 | DirectShape ds = DirectShape.CreateElement(
701 | doc, _id_category_for_direct_shape );
702 |
703 | ds.ApplicationId = id_addin;
704 | ds.ApplicationDataId = r.UniqueId;
705 | ds.SetShape( shape );
706 | ds.get_Parameter( _bip_properties ).Set( json );
707 | ds.Name = "Room volume for " + r.Name;
708 | room_data.Add( rd );
709 | }
710 | tx.Commit();
711 | }
712 |
713 | // Save glTF text and binary data to two files;
714 | // metadata, min, max, buffer information;
715 | // vertex coordinates and triangle indices
716 |
717 | // Path.GetTempPath() returns a weird subdirectory
718 | // created by Revit, so we will not use that here, e.g.,
719 | // C:\Users\tammikj\AppData\Local\Temp\bfd59506-2dff-4b0f-bbe4-31587fcaf508
720 |
721 | //string path = Path.GetTempPath();
722 |
723 | string path = "C:/tmp";
724 |
725 | path = Path.Combine( path, doc.Title + "_gltf" );
726 |
727 | using( StreamWriter s = new StreamWriter(
728 | path + ".txt" ) )
729 | {
730 | int n = room_data.Count;
731 |
732 | s.WriteLine( "{0} room{1}", n, ( ( 1 == n ) ? "" : "s" ) );
733 | s.WriteLine( "{0},{1},{2},{3},{4},{5},{6},{7},{8}",
734 | "id", // "ElementId",
735 | "uid", // "UniqueId",
736 | "name", // "RoomName",
737 | "min",
738 | "max",
739 | "coord begin", // "CoordinatesBegin",
740 | "count", // "CoordinatesCount",
741 | "indices begin", // "TriangleVertexIndicesBegin",
742 | "count" ); // "TriangleVertexIndexCount"
743 |
744 | foreach( GltfNodeData rd in room_data )
745 | {
746 | s.WriteLine( rd.ToString() );
747 | }
748 | }
749 |
750 | using( FileStream f = File.Create( path + ".bin" ) )
751 | {
752 | using( BinaryWriter writer = new BinaryWriter( f ) )
753 | {
754 | foreach( IntPoint3d p in gltf_coords )
755 | {
756 | writer.Write( (float) p.X );
757 | writer.Write( (float) p.Y );
758 | writer.Write( (float) p.Z );
759 | }
760 | foreach( TriangleIndices ti in gltf_indices )
761 | {
762 | foreach( int i in ti.Indices )
763 | {
764 | Debug.Assert( ushort.MaxValue > i,
765 | "expected vertex index to fit into unsigned short" );
766 |
767 | writer.Write( (ushort) i );
768 | }
769 | }
770 | }
771 | }
772 | return Result.Succeeded;
773 | }
774 | }
775 | }
776 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/GltfNodeData.cs:
--------------------------------------------------------------------------------
1 | using Autodesk.Revit.DB.Architecture;
2 |
3 | namespace RoomVolumeDirectShape
4 | {
5 | ///
6 | /// Room data for glTF export:
7 | /// element id and guid, room name,
8 | /// coordinateoffset, coordinatecount,
9 | /// vertexindexoffset, vertexcount
10 | /// (byte or object coount?),
11 | /// min and max x, y, z coord values
12 | ///
13 | class GltfNodeData
14 | {
15 | public int ElementId { get; set; }
16 | public string RoomName { get; set; }
17 | public string UniqueId { get; set; }
18 | public IntPoint3d Min { get; set; }
19 | public IntPoint3d Max { get; set; }
20 | public int CoordinatesBegin { get; set; }
21 | public int CoordinatesCount { get; set; }
22 | public int TriangleVertexIndicesBegin { get; set; }
23 | public int TriangleVertexIndexCount { get; set; }
24 |
25 | public GltfNodeData( Room r )
26 | {
27 | ElementId = r.Id.IntegerValue;
28 | UniqueId = r.UniqueId;
29 | RoomName = r.Name;
30 | }
31 |
32 | ///
33 | /// Display as a string.
34 | ///
35 | public override string ToString()
36 | {
37 | return string.Format(
38 | "{0},{1},{2},{3},{4},{5},{6},{7},{8}",
39 | ElementId,
40 | UniqueId,
41 | RoomName,
42 | Min,
43 | Max,
44 | CoordinatesBegin,
45 | CoordinatesCount,
46 | TriangleVertexIndicesBegin,
47 | TriangleVertexIndexCount );
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/IntPoint3d.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using Autodesk.Revit.DB;
3 |
4 | namespace RoomVolumeDirectShape
5 | {
6 | ///
7 | /// An integer-based 3D point class.
8 | ///
9 | class IntPoint3d : IComparable
10 | {
11 | public int X { get; set; }
12 | public int Y { get; set; }
13 | public int Z { get; set; }
14 |
15 | ///
16 | /// Initialise a 2D millimetre integer
17 | /// point to the given values.
18 | ///
19 | public IntPoint3d( int x, int y, int z )
20 | {
21 | X = x;
22 | Y = y;
23 | Z = z;
24 | }
25 |
26 | ///
27 | /// Convert a 2D Revit UV to a 3D millimetre
28 | /// integer point by scaling from feet to mm.
29 | ///
30 | public IntPoint3d( UV p )
31 | {
32 | X = Util.FootToMmInt( p.U );
33 | Y = Util.FootToMmInt( p.V );
34 | Z = 0;
35 | }
36 |
37 | ///
38 | /// Convert a 3D Revit XYZ to a 3D millimetre
39 | /// integer point, scaling from feet to mm.
40 | ///
41 | public IntPoint3d( XYZ p )
42 | {
43 | X = Util.FootToMmInt( p.X );
44 | Y = Util.FootToMmInt( p.Y );
45 | Z = Util.FootToMmInt( p.Z );
46 | }
47 |
48 | ///
49 | /// Convert Revit coordinates XYZ to a 2D
50 | /// millimetre integer point by scaling
51 | /// from feet to mm.
52 | ///
53 | public IntPoint3d( double x, double y, double z )
54 | {
55 | X = Util.FootToMmInt( x );
56 | Y = Util.FootToMmInt( y );
57 | Z = Util.FootToMmInt( z );
58 | }
59 |
60 | ///
61 | /// Comparison with another point, important
62 | /// for dictionary lookup support.
63 | ///
64 | public int CompareTo( IntPoint3d a )
65 | {
66 | int d = X - a.X;
67 |
68 | if( 0 == d )
69 | {
70 | d = Y - a.Y;
71 |
72 | if( 0 == d )
73 | {
74 | d = Z - a.Z;
75 | }
76 | }
77 | return d;
78 | }
79 |
80 | ///
81 | /// Display as a string.
82 | ///
83 | public override string ToString()
84 | {
85 | return string.Format( "({0},{1},{2})", X, Y, Z );
86 | }
87 |
88 | ///
89 | /// Display as a string.
90 | ///
91 | public string ToString(
92 | bool onlySpaceSeparator )
93 | {
94 | string format_string = onlySpaceSeparator
95 | ? "{0} {1} {2}"
96 | : "({0},{1},{2})";
97 |
98 | return string.Format( format_string, X, Y, Z );
99 | }
100 |
101 | ///
102 | /// Add two points, i.e. treat one of
103 | /// them as a translation vector.
104 | ///
105 | public static IntPoint3d operator +(
106 | IntPoint3d a,
107 | IntPoint3d b )
108 | {
109 | return new IntPoint3d(
110 | a.X + b.X, a.Y + b.Y, a.Z + b.Z );
111 | }
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle( "RoomVolumeDirectShape" )]
9 | [assembly: AssemblyDescription( "Revit C# .NET Add-In generating DirectShape elements to represent room volumes" )]
10 | [assembly: AssemblyConfiguration( "" )]
11 | [assembly: AssemblyCompany( "Autodesk Inc." )]
12 | [assembly: AssemblyProduct( "RoomVolumeDirectShape Revit C# .NET Add-In" )]
13 | [assembly: AssemblyCopyright( "Copyright 2019 (C) Jeremy Tammik, Autodesk Inc." )]
14 | [assembly: AssemblyTrademark( "" )]
15 | [assembly: AssemblyCulture( "" )]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible( false )]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid( "321044f7-b0b2-4b1c-af18-e71a19252be0" )]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | //
36 | // History:
37 | //
38 | // 2019-05-27 2020.0.0.0 initial rough draft ready for testing and refining
39 | // 2019-05-27 2020.0.0.1 implemented first working version
40 | // 2019-05-27 2020.0.0.2 properly set direct shape `Name` property
41 | // 2019-06-26 2020.0.0.3 fixing for Forge viewer, updated for eason
42 | // 2019-06-26 2020.0.0.4 the solid returned by Room.GetClosedShell does not display in the Forge viewer; implemented CopyGeometry to create a new solid to replace it; triangles work; EdgeLoops does not; GetEdgesAsCurveLoops appeared to work
43 | // 2019-06-27 2020.0.0.5 the solid copied from the room closed shell generated using GetEdgesAsCurveLoops does not display properly in the Forge viewer; reverted to triangulation again
44 | // 2019-06-27 2020.0.0.6 added assertions
45 | // 2019-06-27 2020.0.0.6 created a new solid from the room closed shell using SolidUtils.TessellateSolidOrShell
46 | // 2019-06-27 2020.0.0.7 added code to generate glTF facet data
47 | // 2019-06-27 2020.0.0.7 store glTF facet data to binary file
48 | // 2019-06-29 2020.0.0.8 implemented gltf data export for multiple rooms
49 | // 2019-06-29 2020.0.0.9 corrected min max calculation
50 | // 2019-06-29 2020.0.0.10 corrected min max calculation
51 | //
52 | [assembly: AssemblyVersion( "2020.0.0.10" )]
53 | [assembly: AssemblyFileVersion( "2020.0.0.10" )]
54 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/RoomVolumeDirectShape.addin:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Command RoomVolumeDirectShape
5 | Some description for RoomVolumeDirectShape
6 | RoomVolumeDirectShape.dll
7 | RoomVolumeDirectShape.Command
8 | fb982ab3-1c51-419a-a790-94058590438c
9 | com.typepad.thebuildingcoder
10 | The Building Coder, http://thebuildingcoder.typepad.com
11 |
12 |
13 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/RoomVolumeDirectShape.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | None
7 |
8 |
9 |
10 |
11 | Debug
12 | AnyCPU
13 | {338EE02A-603F-468D-B3EB-CF2AD9E8F245}
14 | Library
15 | Properties
16 | RoomVolumeDirectShape
17 | RoomVolumeDirectShape
18 | v4.7
19 | 512
20 |
21 |
22 | true
23 | full
24 | false
25 | bin\Debug\
26 | DEBUG;TRACE
27 | prompt
28 | 4
29 | Program
30 | $(ProgramW6432)\Autodesk\Revit 2020\Revit.exe
31 |
32 |
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE
37 | prompt
38 | 4
39 | Program
40 | $(ProgramW6432)\Autodesk\Revit 2020\Revit.exe
41 |
42 |
43 |
44 | $(ProgramW6432)\Autodesk\Revit 2020\RevitAPI.dll
45 | False
46 |
47 |
48 | $(ProgramW6432)\Autodesk\Revit 2020\RevitAPIUI.dll
49 | False
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 | if exist "$(AppData)\Autodesk\REVIT\Addins\2020" copy "$(ProjectDir)*.addin" "$(AppData)\Autodesk\REVIT\Addins\2020"
67 | if exist "$(AppData)\Autodesk\REVIT\Addins\2020" copy "$(ProjectDir)$(OutputPath)*.dll" "$(AppData)\Autodesk\REVIT\Addins\2020"
68 |
69 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/TriangleIndices.cs:
--------------------------------------------------------------------------------
1 | namespace RoomVolumeDirectShape
2 | {
3 | class TriangleIndices
4 | {
5 | public int [] Indices;
6 |
7 | public TriangleIndices( int i, int j, int k )
8 | {
9 | Indices = new int[3] { i, j, k };
10 | }
11 |
12 | ///
13 | /// Display as a string.
14 | ///
15 | public override string ToString()
16 | {
17 | return string.Format( "({0},{1},{2})",
18 | Indices[0], Indices[1], Indices[2] );
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/RoomVolumeDirectShape/Util.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using Autodesk.Revit.DB;
5 | using System.Diagnostics;
6 |
7 | namespace RoomVolumeDirectShape
8 | {
9 | class Util
10 | {
11 | #region Geometrical Comparison
12 | public const double _eps = 1.0e-9;
13 |
14 | public static double Eps
15 | {
16 | get
17 | {
18 | return _eps;
19 | }
20 | }
21 |
22 | public static double MinLineLength
23 | {
24 | get
25 | {
26 | return _eps;
27 | }
28 | }
29 |
30 | public static double TolPointOnPlane
31 | {
32 | get
33 | {
34 | return _eps;
35 | }
36 | }
37 |
38 | public static bool IsZero(
39 | double a,
40 | double tolerance = _eps )
41 | {
42 | return tolerance > Math.Abs( a );
43 | }
44 |
45 | public static bool IsEqual(
46 | double a,
47 | double b,
48 | double tolerance = _eps )
49 | {
50 | return IsZero( b - a, tolerance );
51 | }
52 |
53 | public static int Compare(
54 | double a,
55 | double b,
56 | double tolerance = _eps )
57 | {
58 | return IsEqual( a, b, tolerance )
59 | ? 0
60 | : ( a < b ? -1 : 1 );
61 | }
62 |
63 | public static int Compare(
64 | XYZ p,
65 | XYZ q,
66 | double tolerance = _eps )
67 | {
68 | int d = Compare( p.X, q.X, tolerance );
69 |
70 | if( 0 == d )
71 | {
72 | d = Compare( p.Y, q.Y, tolerance );
73 |
74 | if( 0 == d )
75 | {
76 | d = Compare( p.Z, q.Z, tolerance );
77 | }
78 | }
79 | return d;
80 | }
81 |
82 | ///
83 | /// Implement a comparison operator for lines
84 | /// in the XY plane useful for sorting into
85 | /// groups of parallel lines.
86 | ///
87 | public static int Compare( Line a, Line b )
88 | {
89 | XYZ pa = a.GetEndPoint( 0 );
90 | XYZ qa = a.GetEndPoint( 1 );
91 | XYZ pb = b.GetEndPoint( 0 );
92 | XYZ qb = b.GetEndPoint( 1 );
93 | XYZ va = qa - pa;
94 | XYZ vb = qb - pb;
95 |
96 | // Compare angle in the XY plane
97 |
98 | double ang_a = Math.Atan2( va.Y, va.X );
99 | double ang_b = Math.Atan2( vb.Y, vb.X );
100 |
101 | int d = Compare( ang_a, ang_b );
102 |
103 | if( 0 == d )
104 | {
105 | // Compare distance of unbounded line to origin
106 |
107 | double da = ( qa.X * pa.Y - qa.Y * pa.Y )
108 | / va.GetLength();
109 |
110 | double db = ( qb.X * pb.Y - qb.Y * pb.Y )
111 | / vb.GetLength();
112 |
113 | d = Compare( da, db );
114 |
115 | if( 0 == d )
116 | {
117 | // Compare distance of start point to origin
118 |
119 | d = Compare( pa.GetLength(), pb.GetLength() );
120 |
121 | if( 0 == d )
122 | {
123 | // Compare distance of end point to origin
124 |
125 | d = Compare( qa.GetLength(), qb.GetLength() );
126 | }
127 | }
128 | }
129 | return d;
130 | }
131 |
132 | ///
133 | /// Predicate to test whewther two points or
134 | /// vectors can be considered equal with the
135 | /// given tolerance.
136 | ///
137 | public static bool IsEqual(
138 | XYZ p,
139 | XYZ q,
140 | double tolerance = _eps )
141 | {
142 | return 0 == Compare( p, q, tolerance );
143 | }
144 |
145 | ///
146 | /// Return true if the given bounding box bb
147 | /// contains the given point p in its interior.
148 | ///
149 | public bool BoundingBoxXyzContains(
150 | BoundingBoxXYZ bb,
151 | XYZ p )
152 | {
153 | return 0 < Compare( bb.Min, p )
154 | && 0 < Compare( p, bb.Max );
155 | }
156 |
157 | ///
158 | /// Return true if the vectors v and w
159 | /// are non-zero and perpendicular.
160 | ///
161 | bool IsPerpendicular( XYZ v, XYZ w )
162 | {
163 | double a = v.GetLength();
164 | double b = v.GetLength();
165 | double c = Math.Abs( v.DotProduct( w ) );
166 | return _eps < a
167 | && _eps < b
168 | && _eps > c;
169 | // c * c < _eps * a * b
170 | }
171 |
172 | public static bool IsParallel( XYZ p, XYZ q )
173 | {
174 | return p.CrossProduct( q ).IsZeroLength();
175 | }
176 |
177 | public static bool IsCollinear( Line a, Line b )
178 | {
179 | XYZ v = a.Direction;
180 | XYZ w = b.Origin - a.Origin;
181 | return IsParallel( v, b.Direction )
182 | && IsParallel( v, w );
183 | }
184 |
185 | public static bool IsHorizontal( XYZ v )
186 | {
187 | return IsZero( v.Z );
188 | }
189 |
190 | public static bool IsVertical( XYZ v )
191 | {
192 | return IsZero( v.X ) && IsZero( v.Y );
193 | }
194 |
195 | public static bool IsVertical( XYZ v, double tolerance )
196 | {
197 | return IsZero( v.X, tolerance )
198 | && IsZero( v.Y, tolerance );
199 | }
200 |
201 | public static bool IsHorizontal( Edge e )
202 | {
203 | XYZ p = e.Evaluate( 0 );
204 | XYZ q = e.Evaluate( 1 );
205 | return IsHorizontal( q - p );
206 | }
207 |
208 | public static bool IsHorizontal( PlanarFace f )
209 | {
210 | return IsVertical( f.FaceNormal );
211 | }
212 |
213 | public static bool IsVertical( PlanarFace f )
214 | {
215 | return IsHorizontal( f.FaceNormal );
216 | }
217 |
218 | public static bool IsVertical( CylindricalFace f )
219 | {
220 | return IsVertical( f.Axis );
221 | }
222 |
223 | ///
224 | /// Minimum slope for a vector to be considered
225 | /// to be pointing upwards. Slope is simply the
226 | /// relationship between the vertical and
227 | /// horizontal components.
228 | ///
229 | const double _minimumSlope = 0.3;
230 |
231 | ///
232 | /// Return true if the Z coordinate of the
233 | /// given vector is positive and the slope
234 | /// is larger than the minimum limit.
235 | ///
236 | public static bool PointsUpwards( XYZ v )
237 | {
238 | double horizontalLength = v.X * v.X + v.Y * v.Y;
239 | double verticalLength = v.Z * v.Z;
240 |
241 | return 0 < v.Z
242 | && _minimumSlope
243 | < verticalLength / horizontalLength;
244 |
245 | //return _eps < v.Normalize().Z;
246 | //return _eps < v.Normalize().Z && IsVertical( v.Normalize(), tolerance );
247 | }
248 |
249 | ///
250 | /// Return the maximum value from an array of real numbers.
251 | ///
252 | public static double Max( double[] a )
253 | {
254 | Debug.Assert( 1 == a.Rank, "expected one-dimensional array" );
255 | Debug.Assert( 0 == a.GetLowerBound( 0 ), "expected zero-based array" );
256 | Debug.Assert( 0 < a.GetUpperBound( 0 ), "expected non-empty array" );
257 | double max = a[0];
258 | for( int i = 1; i <= a.GetUpperBound( 0 ); ++i )
259 | {
260 | if( max < a[i] )
261 | {
262 | max = a[i];
263 | }
264 | }
265 | return max;
266 | }
267 | #endregion // Geometrical Comparison
268 |
269 | #region Unit Handling
270 |
271 | const double _inchToMm = 25.4;
272 | const double _footToMm = 12 * _inchToMm;
273 | const double _footToMeter = 0.001 * _footToMm;
274 | const double _sqfToSqm = _footToMeter * _footToMeter;
275 |
276 |
277 | ///
278 | /// Convert a given length in feet to millimetres,
279 | /// rounded to the closest millimetre.
280 | ///
281 | public static int FootToMmInt( double length )
282 | {
283 | return (int) Math.Round( _footToMm * length,
284 | MidpointRounding.AwayFromZero );
285 | }
286 |
287 | ///
288 | /// Convert a given length in square feet
289 | /// to square metres.
290 | ///
291 | public static double SquareFootToSquareMeter(
292 | double area )
293 | {
294 | return _sqfToSqm * area;
295 | }
296 | #endregion // Unit Handling
297 |
298 | #region Formatting
299 | ///
300 | /// Return an English plural suffix for the given
301 | /// number of items, i.e. 's' for zero or more
302 | /// than one, and nothing for exactly one.
303 | ///
304 | public static string PluralSuffix( int n )
305 | {
306 | return 1 == n ? "" : "s";
307 | }
308 |
309 | ///
310 | /// Return a string for a real number
311 | /// formatted to two decimal places.
312 | ///
313 | public static string RealString( double a )
314 | {
315 | return a.ToString( "0.##" );
316 | }
317 |
318 | ///
319 | /// Return a string listing the space-delimited X
320 | /// and Y coordinates converted from feet to millimetres
321 | /// from a list of XYZ vertices in imperial feet.
322 | ///
323 | static string XyzListTo3dPointString(
324 | List vertices )
325 | {
326 | return string.Join( " ",
327 | vertices.Select( p
328 | => new IntPoint3d( p.X, p.Y, p.Z )
329 | .ToString( true ) ) );
330 | }
331 | #endregion // Formatting
332 | }
333 | }
334 |
--------------------------------------------------------------------------------
/img/rac_basic_sample_project.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomVolumeDirectShape/57cb677cee5a530f13226faea361b114bda019e0/img/rac_basic_sample_project.png
--------------------------------------------------------------------------------
/img/rac_basic_sample_project_room_volumes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomVolumeDirectShape/57cb677cee5a530f13226faea361b114bda019e0/img/rac_basic_sample_project_room_volumes.png
--------------------------------------------------------------------------------