├── .gitignore
├── LICENSE
├── README.md
├── RoomEditorApp.sln
└── RoomEditorApp
├── App.cs
├── CategoryCollector.cs
├── CmdAbout.cs
├── CmdSubscribe.cs
├── CmdUpdate.cs
├── CmdUploadAllRooms.cs
├── CmdUploadRooms.cs
├── CmdUploadSheets.cs
├── ContiguousCurveSorter.cs
├── DbModel.cs
├── DbUpdater.cs
├── DbUpload.cs
├── ElementEqualityComparer.cs
├── FrmSelectCategories.Designer.cs
├── FrmSelectCategories.cs
├── FrmSelectCategories.resx
├── FrmSelectSheets.Designer.cs
├── FrmSelectSheets.cs
├── FrmSelectSheets.resx
├── GeoSnoop.cs
├── Icon
├── 1Down.png
├── 1Down16.png
├── 1Down32.png
├── 1Up.png
├── 1Up16.png
├── 1Up32.png
├── 2Up.png
├── 2Up16.png
├── 2Up32.png
├── Question.png
├── Question16.png
├── Question32.png
├── ZigZagRed.png
├── ZigZagRed16.png
└── ZigZagRed32.png
├── JtBoundingBox2dInt.cs
├── JtBoundingBoxXyz.cs
├── JtLoop.cs
├── JtLoops.cs
├── JtPlacement2dInt.cs
├── JtTimer.cs
├── JtWindowHandle.cs
├── Point2dInt.cs
├── Properties
└── AssemblyInfo.cs
├── RoomEditorApp.addin
├── RoomEditorApp.csproj
├── RoomEditorDb.cs
├── SheetModelCollections.cs
├── Util.cs
└── packages.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ._README.md
2 |
3 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs)
4 | [Bb]in/
5 | [Oo]bj/
6 |
7 | # mstest test results
8 | TestResults
9 |
10 | ## Ignore Visual Studio temporary files, build results, and
11 | ## files generated by popular Visual Studio add-ons.
12 |
13 | # User-specific files
14 | *.suo
15 | *.user
16 | *.sln.docstates
17 |
18 | # Build results
19 | [Dd]ebug/
20 | [Rr]elease/
21 | x64/
22 | *_i.c
23 | *_p.c
24 | *.ilk
25 | *.meta
26 | *.obj
27 | *.pch
28 | *.pdb
29 | *.pgc
30 | *.pgd
31 | *.rsp
32 | *.sbr
33 | *.tlb
34 | *.tli
35 | *.tlh
36 | *.tmp
37 | *.log
38 | *.vspscc
39 | *.vssscc
40 | .builds
41 |
42 | # Visual C++ cache files
43 | ipch/
44 | *.aps
45 | *.ncb
46 | *.opensdf
47 | *.sdf
48 |
49 | # Visual Studio profiler
50 | *.psess
51 | *.vsp
52 | *.vspx
53 |
54 | # Guidance Automation Toolkit
55 | *.gpState
56 |
57 | # ReSharper is a .NET coding add-in
58 | _ReSharper*
59 |
60 | # NCrunch
61 | *.ncrunch*
62 | .*crunch*.local.xml
63 |
64 | # Installshield output folder
65 | [Ee]xpress
66 |
67 | # DocProject is a documentation generator add-in
68 | DocProject/buildhelp/
69 | DocProject/Help/*.HxT
70 | DocProject/Help/*.HxC
71 | DocProject/Help/*.hhc
72 | DocProject/Help/*.hhk
73 | DocProject/Help/*.hhp
74 | DocProject/Help/Html2
75 | DocProject/Help/html
76 |
77 | # Click-Once directory
78 | publish
79 |
80 | # Publish Web Output
81 | *.Publish.xml
82 |
83 | # NuGet Packages Directory
84 | packages
85 |
86 | # Windows Azure Build Output
87 | csx
88 | *.build.csdef
89 |
90 | # Windows Store app package directory
91 | AppPackages/
92 |
93 | # Others
94 | [Bb]in
95 | [Oo]bj
96 | sql
97 | TestResults
98 | [Tt]est[Rr]esult*
99 | *.Cache
100 | ClientBin
101 | [Ss]tyle[Cc]op.*
102 | ~$*
103 | *.dbmdl
104 | Generated_Code #added for RIA/Silverlight projects
105 |
106 | # Backup & report files from converting an old project file to a newer
107 | # Visual Studio version. Backup files are not needed, because we have git ;-)
108 | _UpgradeReport_Files/
109 | Backup*/
110 | UpgradeLog*.XML
111 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013-2016 Jeremy Tammik
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | 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, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # RoomEditorApp
2 |
3 | Revit add-in part of cloud-based, real-time, round-trip, 2D Revit model editor.
4 |
5 | RoomEditorApp implements export of Revit BIM data to a web based cloud database, reimport, and subscription to automatic changes to update the BIM in real time as the user edits a simplified graphical 2D view in any browser on any device.
6 |
7 | The database part is implemented by
8 | the [roomedit](https://github.com/jeremytammik/roomedit)
9 | [CouchDB](https://couchdb.apache.org) app.
10 |
11 | Please refer to [The Building Coder](http://thebuildingcoder.typepad.com) for
12 | more information, especially in
13 | the [cloud](http://thebuildingcoder.typepad.com/blog/cloud)
14 | and [desktop](http://thebuildingcoder.typepad.com/blog/desktop) categories.
15 |
16 | Here is a
17 | recent [summary and overview description](http://thebuildingcoder.typepad.com/blog/2015/11/connecting-desktop-and-cloud-room-editor-update.html#3) of
18 | this project.
19 |
20 |
21 | ## Connecting desktop and cloud
22 |
23 | RoomEditorApp is the first and oldest member of the suite of samples connecting the desktop and the cloud.
24 |
25 | Each of the samples consists of a C# .NET Revit API desktop add-in and a web server:
26 |
27 | - [RoomEditorApp](https://github.com/jeremytammik/RoomEditorApp) and
28 | the [roomeditdb](https://github.com/jeremytammik/roomedit)
29 | [CouchDB](https://couchdb.apache.org)
30 | database and web server demonstrating real-time round-trip graphical editing of furniture family instance location and rotation plus textual editing of element properties in a simplified
31 | 2D [SVG](https://www.w3.org/Graphics/SVG/) representation of the 3D BIM.
32 | - [FireRatingCloud](https://github.com/jeremytammik/FireRatingCloud) and
33 | the [fireratingdb](https://github.com/jeremytammik/firerating)
34 | [node.js](https://nodejs.org)
35 | [MongoDB](https://www.mongodb.com) web server demonstrating real-time round-trip editing of Revit element shared parameter values stored in
36 | a globally accessible [mongolab](http://mongolab.com)-hosted db.
37 | - [Roomedit3dApp](https://github.com/jeremytammik/Roomedit3dApp) and
38 | the first [roomedit3d](https://github.com/jeremytammik/roomedit3d) Forge Viewer extension demonstrating translation of BIM elements in the viewer and updating the Revit model in real time via a 'socket.io' broadcast.
39 | - The most recent Forge sample, adding the option to select any Revit model hosted
40 | on [A360](https://a360.autodesk.com), again using
41 | the [Roomedit3dApp](https://github.com/jeremytammik/Roomedit3dApp) Revit add-in working with the
42 | new [roomedit3dv3](https://github.com/Autodesk-Forge/forge-boilers.nodejs/tree/roomedit3d)
43 | [Autodesk Forge](https://forge.autodesk.com)
44 | [Viewer](https://developer.autodesk.com/en/docs/viewer/v2/overview) extension
45 | to demonstrate translation of BIM element instances in the viewer and updating the Revit model in real time via a `socket.io` broadcast.
46 |
47 |
48 | ## Installation
49 |
50 | RoomEditorApp is a C# .NET Revit API add-in.
51 |
52 | To install it, fork the repository, clone to your local system, load the solution file in Visual Studio, compile and install in the standard Revit add-in location, for example by copying the add-in manifest file and the .NET DLL assembly to `C:\Users\tammikj\AppData\Roaming\Autodesk\Revit\Addins\2016`
53 |
54 | In order to build RoomEditorApp, you will first need to download and compile the [DreamSeat CouchDB wrapper library](https://github.com/vdaron/DreamSeat) and reference the its .NET assembly `DreamSeat.dll`. This will pull in these additional DLLs:
55 |
56 | - Autofac.dll
57 | - DreamSeat.dll
58 | - Newtonsoft.Json.dll
59 | - SgmlReaderDll.dll
60 | - log4net.dll
61 | - mindtouch.dream.dll
62 |
63 | They all need to be accessible to run the add-in. On way to achieve by copying them to the `RoomEditorApp\bin\Debug` folder and running the Revit add-in from there, cf. the path specified by
64 | the [`Assembly` tag in the add-in manifest file](./RoomEditorApp/RoomEditorApp.addin#L5).
65 |
66 | If you do not know what this means, please refer to the GitHub
67 | and [Revit programming getting started](http://thebuildingcoder.typepad.com/blog/about-the-author.html#2) guides.
68 |
69 | As said above, RoomEditorApp interacts with
70 | the [roomedit](https://github.com/jeremytammik/roomedit) CouchDB app.
71 |
72 | You can run that either locally, on your own system, or on the web, e.g., hosted by the CouchDB hosting
73 | site [Iris Couch](http://www.iriscouch.com).
74 |
75 | The choice between these two options is made by the Boolean variable `RoomEditorDb._use_local_db`.
76 |
77 | If you use the web hosted system, i.e., `_use_local_db` is set to `false`, you have no more to set up.
78 |
79 | In the former case, you need to install and
80 | run both [Apache CouchDB](http://couchdb.apache.org) and
81 | the roomedit app itself locally on your system.
82 |
83 | Good luck and have fun!
84 |
85 |
86 | ## Todo
87 |
88 | - Reimplement the roomeditor database using node.js and MongoDB instead of CouchDB, like
89 | the [FireRatingCloud](https://github.com/jeremytammik/FireRatingCloud)
90 | [fireratingdb](https://github.com/jeremytammik/firerating)
91 | [node.js](https://nodejs.org)
92 | [MongoDB](https://www.mongodb.org) web server.
93 |
94 |
95 | ## Author
96 |
97 | Jeremy Tammik,
98 | [The Building Coder](http://thebuildingcoder.typepad.com) and
99 | [The 3D Web Coder](http://the3dwebcoder.typepad.com),
100 | [Forge](http://forge.autodesk.com) [Platform](https://developer.autodesk.com) Development,
101 | [ADN](http://www.autodesk.com/adn)
102 | [Open](http://www.autodesk.com/adnopen),
103 | [Autodesk Inc.](http://www.autodesk.com)
104 |
105 |
106 | ## License
107 |
108 | This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT).
109 | Please see the [LICENSE](LICENSE) file for full details.
110 |
--------------------------------------------------------------------------------
/RoomEditorApp.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 11.00
3 | # Visual Studio 2010
4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RoomEditorApp", "RoomEditorApp\RoomEditorApp.csproj", "{7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}"
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 | {7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
13 | {7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}.Debug|Any CPU.Build.0 = Debug|Any CPU
14 | {7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}.Release|Any CPU.ActiveCfg = Release|Any CPU
15 | {7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}.Release|Any CPU.Build.0 = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/RoomEditorApp/App.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System.Diagnostics;
3 | using Autodesk.Revit.UI;
4 | using System.IO;
5 | using System.Windows.Media.Imaging;
6 | using System.Reflection;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | class App : IExternalApplication
12 | {
13 | ///
14 | /// Caption
15 | ///
16 | public const string Caption = "Room Editor";
17 |
18 | ///
19 | /// Switch between subscribe
20 | /// and unsubscribe commands.
21 | ///
22 | const string _subscribe = "Subscribe";
23 | const string _unsubscribe = "Unsubscribe";
24 |
25 | ///
26 | /// Subscription debugging benchmark timer.
27 | ///
28 | static JtTimer _timer = null;
29 |
30 | ///
31 | /// Store the Idling event handler when subscribed.
32 | ///
33 | //static EventHandler _handler = null;
34 |
35 | ///
36 | /// Store the external event.
37 | ///
38 | static ExternalEvent _event = null;
39 |
40 | ///
41 | /// Executing assembly namespace
42 | ///
43 | static string _namespace = typeof( App ).Namespace;
44 |
45 | ///
46 | /// Command name prefix
47 | ///
48 | const string _cmd_prefix = "Cmd";
49 |
50 | ///
51 | /// Currently executing assembly path
52 | ///
53 | static string _path = typeof( App )
54 | .Assembly.Location;
55 |
56 | ///
57 | /// Keep track of our ribbon buttons to toggle
58 | /// them on and off later and change their text.
59 | ///
60 | static RibbonItem[] _buttons;
61 |
62 | static int _subscribeButtonIndex = 3;
63 |
64 | ///
65 | /// Our one and only Revit-provided
66 | /// UIControlledApplication instance.
67 | ///
68 | //static UIControlledApplication _uiapp;
69 |
70 | #region Icon resource, bitmap image and ribbon panel stuff
71 | ///
72 | /// Return path to embedded resource icon
73 | ///
74 | static string IconResourcePath(
75 | string name,
76 | string size )
77 | {
78 | return _namespace
79 | + "." + "Icon" // folder name
80 | + "." + name + size // icon name
81 | + ".png"; // filename extension
82 | }
83 |
84 | ///
85 | /// Load a new icon bitmap from embedded resources.
86 | /// For the BitmapImage, make sure you reference
87 | /// WindowsBase and PresentationCore, and import
88 | /// the System.Windows.Media.Imaging namespace.
89 | ///
90 | static BitmapImage GetBitmapImage(
91 | Assembly a,
92 | string path )
93 | {
94 | // to read from an external file:
95 | //return new BitmapImage( new Uri(
96 | // Path.Combine( _imageFolder, imageName ) ) );
97 |
98 | string[] names = a.GetManifestResourceNames();
99 |
100 | Stream s = a.GetManifestResourceStream( path );
101 |
102 | Debug.Assert( null != s,
103 | "expected valid icon resource" );
104 |
105 | BitmapImage img = new BitmapImage();
106 |
107 | img.BeginInit();
108 | img.StreamSource = s;
109 | img.EndInit();
110 |
111 | return img;
112 | }
113 |
114 | ///
115 | /// Create a custom ribbon panel and populate
116 | /// it with our commands, saving the resulting
117 | /// ribbon items for later access.
118 | ///
119 | static void AddRibbonPanel(
120 | UIControlledApplication a )
121 | {
122 | string[] tooltip = new string[] {
123 | //"Upload selected sheets and categories to cloud.",
124 | "Upload selected rooms to cloud.",
125 | "Upload all rooms to cloud.",
126 | "Update furniture from the last cloud edit.",
127 | "Subscribe to or unsubscribe from updates.",
128 | "About " + Caption + ": ..."
129 | };
130 |
131 | string[] text = new string[] {
132 | //"Upload Sheets",
133 | "Upload Rooms",
134 | "Upload All Rooms",
135 | "Update Furniture",
136 | "Subscribe",
137 | "About..."
138 | };
139 |
140 | string[] classNameStem = new string[] {
141 | //"UploadSheets",
142 | "UploadRooms",
143 | "UploadAllRooms",
144 | "Update",
145 | "Subscribe",
146 | "About"
147 | };
148 |
149 | string[] iconName = new string[] {
150 | //"2Up",
151 | "1Up",
152 | "2Up",
153 | "1Down",
154 | "ZigZagRed",
155 | "Question"
156 | };
157 |
158 | int n = classNameStem.Length;
159 |
160 | Debug.Assert( text.Length == n,
161 | "expected equal number of text and class name entries" );
162 |
163 | _buttons = new RibbonItem[n];
164 |
165 | RibbonPanel panel
166 | = a.CreateRibbonPanel( Caption );
167 |
168 | SplitButtonData splitBtnData
169 | = new SplitButtonData( Caption, Caption );
170 |
171 | SplitButton splitBtn = panel.AddItem(
172 | splitBtnData ) as SplitButton;
173 |
174 | Assembly asm = typeof( App ).Assembly;
175 |
176 | //ContextualHelp contextHelp = new ContextualHelp(
177 | // ContextualHelpType.ChmFile,
178 | // "C:a/vs/RoomEditorApp/RoomEditorApp/RoomEditor.html" );
179 |
180 | for ( int i = 0; i < n; ++i )
181 | {
182 | PushButtonData d = new PushButtonData(
183 | classNameStem[i], text[i], _path,
184 | _namespace + "." + _cmd_prefix
185 | + classNameStem[i] );
186 |
187 | d.ToolTip = tooltip[i];
188 |
189 | d.Image = GetBitmapImage( asm,
190 | IconResourcePath( iconName[i], "16" ) );
191 |
192 | d.LargeImage = GetBitmapImage( asm,
193 | IconResourcePath( iconName[i], "32" ) );
194 |
195 | d.ToolTipImage = GetBitmapImage( asm,
196 | IconResourcePath( iconName[i], "" ) );
197 |
198 | //d.SetContextualHelp( contextHelp );
199 |
200 | _buttons[i] = splitBtn.AddPushButton( d );
201 | }
202 | }
203 | #endregion // Icon resource, bitmap image and ribbon panel stuff
204 |
205 | #region Idling subscription and external event creation
206 | ///
207 | /// Are we currently subscribed
208 | /// to automatic cloud updates?
209 | ///
210 | public static bool Subscribed
211 | {
212 | get
213 | {
214 | bool rc = _buttons[_subscribeButtonIndex]
215 | .ItemText.Equals( _unsubscribe );
216 |
217 | Debug.Assert( ( _event != null ) == rc,
218 | "expected synchronised handler and button text" );
219 |
220 | return rc;
221 | }
222 | }
223 |
224 | ///
225 | /// Toggle on and off subscription to automatic
226 | /// cloud updates. Return true when subscribed.
227 | ///
228 | public static bool ToggleSubscription2(
229 | // EventHandler handler
230 | IExternalEventHandler handler )
231 | {
232 | if( Subscribed )
233 | {
234 | Debug.Print( "Unsubscribing..." );
235 |
236 | //_uiapp.Idling -= _handler;
237 | //_handler = null;
238 | _event.Dispose();
239 | _event = null;
240 |
241 | _buttons[_subscribeButtonIndex].ItemText
242 | = _subscribe;
243 |
244 | _timer.Stop();
245 | _timer.Report( "Subscription timing" );
246 | _timer = null;
247 |
248 | Debug.Print( "Unsubscribed." );
249 | }
250 | else
251 | {
252 | Debug.Print( "Subscribing..." );
253 | //_uiapp.Idling += handler;
254 | //_handler = handler;
255 | _event = ExternalEvent.Create( handler );
256 |
257 | _buttons[_subscribeButtonIndex].ItemText
258 | = _unsubscribe;
259 |
260 | _timer = new JtTimer( "Subscription" );
261 | Debug.Print( "Subscribed." );
262 | }
263 | return null != _event;
264 | }
265 |
266 | ///
267 | /// Provide public read-only access to external event.
268 | ///
269 | public static ExternalEvent Event
270 | {
271 | get { return _event; }
272 | }
273 | #endregion // Idling subscription and external event creation
274 |
275 | public Result OnStartup(
276 | UIControlledApplication a )
277 | {
278 | //_uiapp = a;
279 |
280 | AddRibbonPanel( a );
281 |
282 | return Result.Succeeded;
283 | }
284 |
285 | public Result OnShutdown(
286 | UIControlledApplication a )
287 | {
288 | if( Subscribed )
289 | {
290 | //_uiapp.Idling -= _handler;
291 | //_event.Dispose();
292 |
293 | if( Subscribed )
294 | {
295 | ToggleSubscription2( null );
296 | }
297 | }
298 | return Result.Succeeded;
299 | }
300 | }
301 | }
302 |
--------------------------------------------------------------------------------
/RoomEditorApp/CategoryCollector.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.DB;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | ///
12 | /// Collect all categories of all visible
13 | /// elements in a given set of views.
14 | ///
15 | class CategoryCollector : Dictionary
16 | {
17 | #region CategoryEqualityComparer
18 | ///
19 | /// Categories with the same element id equate to
20 | /// the same category. Without this, many, many,
21 | /// many duplicates.
22 | ///
23 | class CategoryEqualityComparer
24 | : IEqualityComparer
25 | {
26 | public bool Equals( Category x, Category y )
27 | {
28 | return x.Id.IntegerValue.Equals(
29 | y.Id.IntegerValue );
30 | }
31 |
32 | public int GetHashCode( Category obj )
33 | {
34 | return obj.Id.IntegerValue.GetHashCode();
35 | }
36 | }
37 | #endregion // CategoryEqualityComparer
38 |
39 | ///
40 | /// Number of view selected.
41 | ///
42 | int _nViews;
43 |
44 | ///
45 | /// Number of elements in all selected views
46 | /// including repetitions.
47 | ///
48 | int _nElements;
49 |
50 | ///
51 | /// Number of elements whose category have
52 | /// material quantities in all selected views
53 | /// including repetitions.
54 | ///
55 | int _nElementsWithCategorMaterialQuantities;
56 |
57 | public CategoryCollector( ICollection views )
58 | : base( new CategoryEqualityComparer() )
59 | {
60 | _nViews = views.Count;
61 | _nElements = 0;
62 | _nElementsWithCategorMaterialQuantities = 0;
63 |
64 | if( 0 < _nViews )
65 | {
66 | FilteredElementCollector a;
67 |
68 | foreach( View v in views )
69 | {
70 | a = new FilteredElementCollector( v.Document, v.Id )
71 | .WhereElementIsViewIndependent();
72 |
73 | foreach( Element e in a )
74 | {
75 | ++_nElements;
76 |
77 | #region Research code
78 | #if RESEARCH_CODE
79 | // Categories of all elements in the given set of views:
80 | // 14 Categories Selected: Cameras, Curtain Panels, Curtain Wall Grids, Curtain Wall Mullions, Doors, Elevations, Furniture, Project Base Point, Room Tags, Rooms, Structural Columns, Survey Point, Views, Walls
81 |
82 | // suppressing view specific elements eliminated Room Tags
83 | //if( !e.ViewSpecific )
84 | // 13 Categories Selected: Cameras, Curtain Panels, Curtain Wall Grids, Curtain Wall Mullions, Doors, Elevations, Furniture, Project Base Point, Rooms, Structural Columns, Survey Point, Views, Walls
85 |
86 | // suppressing elements with an empty bounding box eliminated Project Base Point and Survey Point
87 | //BoundingBoxXYZ box = e.get_BoundingBox( v );
88 | //if( null != box
89 | // && !box.Max.IsAlmostEqualTo( box.Min ) )
90 | // 13 Categories Selected: Cameras, Curtain Panels, Curtain Wall Grids, Curtain Wall Mullions, Doors, Elevations, Furniture, Project Base Point, Rooms, Structural Columns, Survey Point, Views, Walls
91 | #endif // RESEARCH_CODE
92 | #endregion // Research code
93 |
94 | Category cat = e.Category;
95 |
96 | if( null != cat
97 | && cat.HasMaterialQuantities )
98 | {
99 | ++_nElementsWithCategorMaterialQuantities;
100 |
101 | if( !ContainsKey( cat ) )
102 | {
103 | Add( cat, 0 );
104 | }
105 | ++this[cat];
106 | }
107 | }
108 | }
109 | }
110 | Debug.Print( "Selected {0} from {1} displaying "
111 | + "{2}, {3} with HasMaterialQuantities=true",
112 | Util.PluralString( Count, "category" ),
113 | Util.PluralString( _nViews, "view" ),
114 | Util.PluralString( _nElements, "element" ),
115 | _nElementsWithCategorMaterialQuantities );
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/RoomEditorApp/CmdAbout.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using Autodesk.Revit.ApplicationServices;
3 | using Autodesk.Revit.Attributes;
4 | using Autodesk.Revit.DB;
5 | using Autodesk.Revit.UI;
6 | #endregion
7 |
8 | namespace RoomEditorApp
9 | {
10 | [Transaction( TransactionMode.ReadOnly )]
11 | class CmdAbout : IExternalCommand
12 | {
13 | const string _description
14 | = "Demonstrate round-trip editing a 2D rendering "
15 | + "of a Revit model on any mobile device with no "
16 | + "need for installation of any additional software "
17 | + "whatsoever beyond a browser. How can this be "
18 | + "achieved? A Revit add-in exports polygon "
19 | + "renderings of room boundaries and other elements "
20 | + "such as furniture and equipment to a cloud-based "
21 | + "repository implemented using a CouchDB NoSQL "
22 | + "database. On the mobile device, the repository "
23 | + "is queried and the data is rendered in a standard "
24 | + "browser using server-side generated JavaScript "
25 | + "and SVG. The rendering supports graphical editing, "
26 | + "specifically translation and rotation of the "
27 | + "furniture and equipment. Modified transformations "
28 | + "are saved back to the cloud database. The Revit "
29 | + "add-in picks up these changes and updates the "
30 | + "Revit model in real-time. All of the components "
31 | + "used are completely open source, except for Revit "
32 | + "itself.\r\n\r\n"
33 | + "Jeremy Tammik, Autodesk Inc.";
34 |
35 | public Result Execute(
36 | ExternalCommandData commandData,
37 | ref string message,
38 | ElementSet elements )
39 | {
40 | Util.InfoMsg2(
41 | "Room furniture and equipment editor",
42 | _description );
43 |
44 | return Result.Succeeded;
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/RoomEditorApp/CmdSubscribe.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.ApplicationServices;
7 | using Autodesk.Revit.Attributes;
8 | using Autodesk.Revit.DB;
9 | using Autodesk.Revit.UI;
10 | using Autodesk.Revit.UI.Events;
11 | using DreamSeat;
12 | #endregion
13 |
14 | namespace RoomEditorApp
15 | {
16 | [Transaction( TransactionMode.ReadOnly )]
17 | class CmdSubscribe : IExternalCommand
18 | {
19 | #region Obsolete Idling event handler replaced by external event
20 | ///
21 | /// How many Idling calls to wait before acting
22 | ///
23 | const int _update_interval = 100;
24 |
25 | ///
26 | /// How many Idling calls to wait before reporting
27 | ///
28 | const int _message_interval = 100;
29 |
30 | ///
31 | /// Number of Idling calls received in this session
32 | ///
33 | static int _counter = 0;
34 |
35 | ///
36 | /// Wait far a moment before requerying database.
37 | ///
38 | //static Stopwatch _stopwatch = null;
39 |
40 | void OnIdling(
41 | object sender,
42 | IdlingEventArgs ea )
43 | {
44 | using( JtTimer pt = new JtTimer( "OnIdling" ) )
45 | {
46 | // Use with care! This loads the CPU:
47 |
48 | ea.SetRaiseWithoutDelay();
49 |
50 | ++_counter;
51 |
52 | if( 0 == ( _counter % _update_interval ) )
53 | {
54 | if( 0 == ( _counter % _message_interval ) )
55 | {
56 | Util.Log( string.Format(
57 | "OnIdling called {0} times",
58 | _counter ) );
59 | }
60 |
61 | // Have we waited long enough since the last attempt?
62 |
63 | //if( null == _stopwatch
64 | // || _stopwatch.ElapsedMilliseconds > 500 )
65 |
66 | RoomEditorDb rdb = new RoomEditorDb();
67 | //int n = rdb.LastSequenceNumber;
68 |
69 | if( rdb.LastSequenceNumberChanged(
70 | DbUpdater.LastSequence ) )
71 | {
72 | UIApplication uiapp = sender as UIApplication;
73 | Document doc = uiapp.ActiveUIDocument.Document;
74 |
75 | Util.Log( "furniture update begin" );
76 |
77 | //FilteredElementCollector rooms
78 | // = new FilteredElementCollector( doc )
79 | // .OfClass( typeof( SpatialElement ) )
80 | // .OfCategory( BuiltInCategory.OST_Rooms );
81 |
82 | //IEnumerable roomUniqueIds
83 | // = rooms.Select(
84 | // e => e.UniqueId );
85 |
86 | //CouchDatabase db = rdb.Db;
87 |
88 | //ChangeOptions opt = new ChangeOptions();
89 |
90 | //opt.IncludeDocs = true;
91 | //opt.Since = CmdUpdate.LastSequence;
92 | //opt.View = "roomedit/map_room_to_furniture";
93 |
94 | //CouchChanges changes
95 | // = db.GetChanges( opt );
96 |
97 | //CouchChangeResult[] results
98 | // = changes.Results;
99 |
100 | //DbUpdater updater = new DbUpdater(
101 | // doc, roomUniqueIds );
102 |
103 | //foreach( CouchChangeResult result
104 | // in results )
105 | //{
106 | // updater.UpdateBimFurniture( result.Doc );
107 |
108 | // CmdUpdate.LastSequence = result.Sequence;
109 | //}
110 |
111 | DbUpdater updater = new DbUpdater( uiapp );
112 |
113 | updater.UpdateBim();
114 |
115 | Util.Log( "furniture update end" );
116 |
117 | // _stopwatch = new Stopwatch();
118 | // _stopwatch.Start();
119 | //}
120 | //catch( Exception ex )
121 | //{
122 | // //uiapp.Application.WriteJournalComment
123 |
124 | // Debug.Print(
125 | // "Room Editor: an error occurred "
126 | // + "executing the OnIdling event:\r\n"
127 | // + ex.ToString() );
128 |
129 | // Debug.WriteLine( ex );
130 | //}
131 | }
132 | }
133 | }
134 | }
135 | #endregion // Obsolete Idling event handler replaced by external event
136 |
137 | public Result Execute(
138 | ExternalCommandData commandData,
139 | ref string message,
140 | ElementSet elements )
141 | {
142 | if( !App.Subscribed
143 | && -1 == DbUpdater.LastSequence )
144 | {
145 | DbUpdater.SetLastSequence();
146 | }
147 |
148 | DbUpdater.ToggleSubscription(
149 | commandData.Application );
150 |
151 | return Result.Succeeded;
152 | }
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/RoomEditorApp/CmdUpdate.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.ApplicationServices;
7 | using Autodesk.Revit.Attributes;
8 | using Autodesk.Revit.DB;
9 | using Autodesk.Revit.UI;
10 | using DreamSeat;
11 | #endregion
12 |
13 | namespace RoomEditorApp
14 | {
15 | ///
16 | /// External command to either set the initial
17 | /// sequence number or, if already set, download
18 | /// and apply all changes from the cloud since
19 | /// then to the BIM.
20 | ///
21 | [Transaction( TransactionMode.Manual )]
22 | public class CmdUpdate : IExternalCommand
23 | {
24 | public Result Execute(
25 | ExternalCommandData commandData,
26 | ref string message,
27 | ElementSet elements )
28 | {
29 | UIApplication uiapp = commandData.Application;
30 | UIDocument uidoc = uiapp.ActiveUIDocument;
31 | Application app = uiapp.Application;
32 | Document doc = uidoc.Document;
33 |
34 | if( null == doc )
35 | {
36 | Util.ErrorMsg( "Please run this command in a valid"
37 | + " Revit project document." );
38 | return Result.Failed;
39 | }
40 |
41 | if( -1 == DbUpdater.LastSequence )
42 | {
43 | DbUpdater.SetLastSequence();
44 |
45 | return Result.Succeeded;
46 | }
47 |
48 | //// Retrieve all room unique ids in model:
49 |
50 | //FilteredElementCollector rooms
51 | // = new FilteredElementCollector( doc )
52 | // .OfClass( typeof( SpatialElement ) )
53 | // .OfCategory( BuiltInCategory.OST_Rooms );
54 |
55 | //IEnumerable roomUniqueIds
56 | // = rooms.Select(
57 | // e => e.UniqueId );
58 |
59 | ////string ids = "?keys=[%22" + string.Join(
60 | //// "%22,%22", roomUniqueIds ) + "%22]";
61 |
62 | //// Retrieve furniture transformations
63 | //// after last sequence number:
64 |
65 | //CouchDatabase db = new RoomEditorDb().Db;
66 |
67 | //ChangeOptions opt = new ChangeOptions();
68 |
69 | //opt.IncludeDocs = true;
70 | //opt.Since = LastSequence;
71 | //opt.View = "roomedit/map_room_to_furniture";
72 |
73 | //// I tried to add a filter to this view, but
74 | //// that is apparently not supported by the
75 | //// CouchDB or DreamSeat GetChanges functionality.
76 | ////+ ids; // failed attempt to filter view by room id keys
77 |
78 | //// Specify filter function defined in
79 | //// design document to get updates
80 | ////opt.Filter =
81 |
82 | //CouchChanges changes
83 | // = db.GetChanges( opt );
84 |
85 | //CouchChangeResult[] results
86 | // = changes.Results;
87 |
88 | //DbUpdater updater = new DbUpdater(
89 | // doc, roomUniqueIds );
90 |
91 | //foreach( CouchChangeResult result
92 | // in results )
93 | //{
94 | // updater.UpdateBimFurniture( result.Doc );
95 |
96 | // LastSequence = result.Sequence;
97 | //}
98 |
99 | //DbUpdater updater = new DbUpdater( doc );
100 |
101 | DbUpdater updater = new DbUpdater( uiapp );
102 |
103 | updater.UpdateBim();
104 |
105 | return Result.Succeeded;
106 | }
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/RoomEditorApp/CmdUploadAllRooms.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using Autodesk.Revit.ApplicationServices;
3 | using Autodesk.Revit.Attributes;
4 | using Autodesk.Revit.DB;
5 | using Autodesk.Revit.DB.Architecture;
6 | using Autodesk.Revit.UI;
7 | using System;
8 | #endregion
9 |
10 | namespace RoomEditorApp
11 | {
12 | [Transaction( TransactionMode.ReadOnly )]
13 | class CmdUploadAllRooms : IExternalCommand
14 | {
15 | public Result Execute(
16 | ExternalCommandData commandData,
17 | ref string message,
18 | ElementSet elements )
19 | {
20 | UIApplication uiapp = commandData.Application;
21 | UIDocument uidoc = uiapp.ActiveUIDocument;
22 | Document doc = uidoc.Document;
23 |
24 | IntPtr hwnd = uiapp.MainWindowHandle;
25 |
26 | FilteredElementCollector rooms
27 | = new FilteredElementCollector( doc )
28 | .OfClass( typeof( SpatialElement ) )
29 | .OfCategory( BuiltInCategory.OST_Rooms );
30 |
31 | foreach( Room room in rooms )
32 | {
33 | CmdUploadRooms.UploadRoom( hwnd, doc, room );
34 | }
35 |
36 | DbUpdater.SetLastSequence();
37 |
38 | return Result.Succeeded;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/RoomEditorApp/CmdUploadRooms.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.ApplicationServices;
7 | using Autodesk.Revit.Attributes;
8 | using Autodesk.Revit.DB;
9 | using Autodesk.Revit.DB.Architecture;
10 | using Autodesk.Revit.DB.IFC;
11 | using Autodesk.Revit.UI;
12 | using Autodesk.Revit.UI.Selection;
13 | using Bitmap = System.Drawing.Bitmap;
14 | using BoundarySegment = Autodesk.Revit.DB.BoundarySegment;
15 | //using ComponentManager = Autodesk.Windows.ComponentManager; pre-2020
16 | using IWin32Window = System.Windows.Forms.IWin32Window;
17 | using DreamSeat;
18 | #endregion
19 |
20 | namespace RoomEditorApp
21 | {
22 | [Transaction( TransactionMode.ReadOnly )]
23 | public class CmdUploadRooms : IExternalCommand
24 | {
25 | #region RoomSelectionFilter
26 | class RoomSelectionFilter : ISelectionFilter
27 | {
28 | public bool AllowElement( Element e )
29 | {
30 | return e is Room;
31 | }
32 |
33 | public bool AllowReference( Reference r, XYZ p )
34 | {
35 | return true;
36 | }
37 | }
38 | #endregion // RoomSelectionFilter
39 |
40 | static bool _debug_output = false;
41 |
42 | ///
43 | /// If curve tessellation is disabled, only
44 | /// straight line segments from start to end
45 | /// point are exported.
46 | ///
47 | static bool _tessellate_curves = true;
48 |
49 | ///
50 | /// Never tessellate a curve
51 | /// shorter than this length.
52 | ///
53 | const double _min_tessellation_curve_length_in_feet = 0.2;
54 |
55 | ///
56 | /// Conversion factor from foot to quarter inch.
57 | ///
58 | const double _quarter_inch = 1.0 / (12 * 4);
59 |
60 | #region Get room boundary loops
61 | ///
62 | /// Retrieve the room plan view boundary
63 | /// polygon loops and convert to 2D integer-based.
64 | /// For optimisation and consistency reasons,
65 | /// convert all coordinates to integer values in
66 | /// millimetres. Revit precision is limited to
67 | /// 1/16 of an inch, which is abaut 1.2 mm, anyway.
68 | ///
69 | static JtLoops GetRoomLoops( Room room )
70 | {
71 | SpatialElementBoundaryOptions opt
72 | = new SpatialElementBoundaryOptions();
73 |
74 | opt.SpatialElementBoundaryLocation =
75 | SpatialElementBoundaryLocation.Center; // loops closed
76 | //SpatialElementBoundaryLocation.Finish; // loops not closed
77 |
78 | IList> loops = room.
79 | GetBoundarySegments( opt );
80 |
81 | int nLoops = loops.Count;
82 |
83 | JtLoops jtloops = new JtLoops( nLoops );
84 |
85 | foreach( IList loop in loops )
86 | {
87 | int nSegments = loop.Count;
88 |
89 | JtLoop jtloop = new JtLoop( nSegments );
90 |
91 | XYZ p0 = null; // loop start point
92 | XYZ p; // segment start point
93 | XYZ q = null; // segment end point
94 |
95 | foreach( BoundarySegment seg in loop )
96 | {
97 | // Todo: handle non-linear curve.
98 | // Especially: if two long lines have a
99 | // short arc in between them, skip the arc
100 | // and extend both lines.
101 |
102 | p = seg.GetCurve().GetEndPoint( 0 );
103 |
104 | jtloop.Add( new Point2dInt( p ) );
105 |
106 | Debug.Assert( null == q || q.IsAlmostEqualTo( p ),
107 | "expected last endpoint to equal current start point" );
108 |
109 | q = seg.GetCurve().GetEndPoint( 1 );
110 |
111 | if( _debug_output )
112 | {
113 | Debug.Print( "{0} --> {1}",
114 | Util.PointString( p ),
115 | Util.PointString( q ) );
116 | }
117 | if( null == p0 )
118 | {
119 | p0 = p; // save loop start point
120 | }
121 | }
122 | Debug.Assert( q.IsAlmostEqualTo( p0 ),
123 | "expected last endpoint to equal loop start point" );
124 |
125 | jtloops.Add( jtloop );
126 | }
127 | return jtloops;
128 | }
129 |
130 | //(9.03,10.13,0) --> (-14.59,10.13,0)
131 | //(-14.59,10.13,0) --> (-14.59,1.93,0)
132 | //(-14.59,1.93,0) --> (-2.45,1.93,0)
133 | //(-2.45,1.93,0) --> (-2.45,-3.98,0)
134 | //(-2.45,-3.98,0) --> (9.03,-3.98,0)
135 | //(9.03,-3.98,0) --> (9.03,10.13,0)
136 | //(0.98,-0.37,0) --> (0.98,1.93,0)
137 | //(0.98,1.93,0) --> (5.57,1.93,0)
138 | //(5.57,1.93,0) --> (5.57,-0.37,0)
139 | //(5.57,-0.37,0) --> (0.98,-0.37,0)
140 |
141 | //(9.03,10.13) --> (-14.59,10.13)
142 | //(-14.59,10.13) --> (-14.59,1.93)
143 | //(-14.59,1.93) --> (-2.45,1.93)
144 | //(-2.45,1.93) --> (-2.45,-3.98)
145 | //(-2.45,-3.98) --> (9.03,-3.98)
146 | //(9.03,-3.98) --> (9.03,10.13)
147 | //(0.98,-0.37) --> (0.98,1.93)
148 | //(0.98,1.93) --> (5.57,1.93)
149 | //(5.57,1.93) --> (5.57,-0.37)
150 | //(5.57,-0.37) --> (0.98,-0.37)
151 |
152 | //Room Rooms <212639 Room 1> has 2 loops:
153 | // 0: (2753,3087), (-4446,3087), (-4446,587), (-746,587), (-746,-1212), (2753,-1212)
154 | // 1: (298,-112), (298,587), (1698,587), (1698,-112)
155 | #endregion // Get room boundary loops
156 |
157 | #region Get furniture contained in given room
158 | ///
159 | /// Return the element ids of all furniture and
160 | /// equipment family instances contained in the
161 | /// given room.
162 | ///
163 | static List GetFurniture( Room room )
164 | {
165 | BoundingBoxXYZ bb = room.get_BoundingBox( null );
166 |
167 | Outline outline = new Outline( bb.Min, bb.Max );
168 |
169 | BoundingBoxIntersectsFilter filter
170 | = new BoundingBoxIntersectsFilter( outline );
171 |
172 | Document doc = room.Document;
173 |
174 | // Todo: add category filters and other
175 | // properties to narrow down the results
176 |
177 | // what categories of family instances
178 | // are we interested in?
179 |
180 | BuiltInCategory[] bics = new BuiltInCategory[] {
181 | BuiltInCategory.OST_Furniture,
182 | BuiltInCategory.OST_PlumbingFixtures,
183 | BuiltInCategory.OST_SpecialityEquipment
184 | };
185 |
186 | LogicalOrFilter categoryFilter
187 | = new LogicalOrFilter( bics
188 | .Select(
189 | bic => new ElementCategoryFilter( bic ) )
190 | .ToList() );
191 |
192 | FilteredElementCollector familyInstances
193 | = new FilteredElementCollector( doc )
194 | .WhereElementIsNotElementType()
195 | .WhereElementIsViewIndependent()
196 | .OfClass( typeof( FamilyInstance ) )
197 | .WherePasses( categoryFilter )
198 | .WherePasses( filter );
199 |
200 | int roomid = room.Id.IntegerValue;
201 |
202 | List a = new List();
203 |
204 | foreach( FamilyInstance fi in familyInstances )
205 | {
206 | if( null != fi.Room
207 | && fi.Room.Id.IntegerValue.Equals( roomid ) )
208 | {
209 | Debug.Assert( fi.Location is LocationPoint,
210 | "expected all furniture to have a location point" );
211 |
212 | a.Add( fi );
213 | }
214 | }
215 | return a;
216 | }
217 | #endregion // Get furniture contained in given room
218 |
219 | ///
220 | /// Return a closed loop of integer-based points
221 | /// scaled to millimetres from a given Revit model
222 | /// face in feet.
223 | ///
224 | internal static JtLoop GetLoop(
225 | Autodesk.Revit.Creation.Application creapp,
226 | Face face )
227 | {
228 | JtLoop loop = null;
229 |
230 | foreach( EdgeArray a in face.EdgeLoops )
231 | {
232 | int nEdges = a.Size;
233 |
234 | List curves
235 | = new List( nEdges );
236 |
237 | XYZ p0 = null; // loop start point
238 | XYZ p; // edge start point
239 | XYZ q = null; // edge end point
240 |
241 | // Test ValidateCurveLoops
242 |
243 | //CurveLoop loopIfc = new CurveLoop();
244 |
245 | foreach( Edge e in a )
246 | {
247 | // This requires post-processing using
248 | // SortCurvesContiguous:
249 |
250 | Curve curve = e.AsCurve();
251 |
252 | if( _debug_output )
253 | {
254 | p = curve.GetEndPoint( 0 );
255 | q = curve.GetEndPoint( 1 );
256 | Debug.Print( "{0} --> {1}",
257 | Util.PointString( p ),
258 | Util.PointString( q ) );
259 | }
260 |
261 | // This returns the curves already
262 | // correctly oriented:
263 |
264 | curve = e.AsCurveFollowingFace(
265 | face );
266 |
267 | if( _debug_output )
268 | {
269 | p = curve.GetEndPoint( 0 );
270 | q = curve.GetEndPoint( 1 );
271 | Debug.Print( "{0} --> {1} following face",
272 | Util.PointString( p ),
273 | Util.PointString( q ) );
274 | }
275 |
276 | curves.Add( curve );
277 |
278 | // Throws an exception saying "This curve
279 | // will make the loop not contiguous.
280 | // Parameter name: pCurve"
281 |
282 | //loopIfc.Append( curve );
283 | }
284 |
285 | // We never reach this point:
286 |
287 | //List loopsIfc
288 | // = new List( 1 );
289 |
290 | //loopsIfc.Add( loopIfc );
291 |
292 | //IList loopsIfcOut = ExporterIFCUtils
293 | // .ValidateCurveLoops( loopsIfc, XYZ.BasisZ );
294 |
295 | // This is no longer needed if we use
296 | // AsCurveFollowingFace instead of AsCurve:
297 |
298 | CurveUtils.SortCurvesContiguous(
299 | creapp, curves, _debug_output );
300 |
301 | q = null;
302 |
303 | loop = new JtLoop( nEdges );
304 |
305 | foreach( Curve curve in curves )
306 | {
307 | // Todo: handle non-linear curve.
308 | // Especially: if two long lines have a
309 | // short arc in between them, skip the arc
310 | // and extend both lines.
311 |
312 | p = curve.GetEndPoint( 0 );
313 |
314 | Debug.Assert( null == q
315 | || q.IsAlmostEqualTo( p, 1e-04 ),
316 | string.Format(
317 | "expected last endpoint to equal current start point, not distance {0}",
318 | ( null == q ? 0 : p.DistanceTo( q ) ) ) );
319 |
320 | q = curve.GetEndPoint( 1 );
321 |
322 | if( _debug_output )
323 | {
324 | Debug.Print( "{0} --> {1}",
325 | Util.PointString( p ),
326 | Util.PointString( q ) );
327 | }
328 |
329 | if( null == p0 )
330 | {
331 | p0 = p; // save loop start point
332 | }
333 |
334 | int n = -1;
335 |
336 | if( _tessellate_curves
337 | && _min_tessellation_curve_length_in_feet
338 | < q.DistanceTo( p ) )
339 | {
340 | IList pts = curve.Tessellate();
341 | n = pts.Count;
342 |
343 | Debug.Assert( 1 < n, "expected at least two points" );
344 | Debug.Assert( p.IsAlmostEqualTo( pts[0] ), "expected tessellation start equal curve start point" );
345 | Debug.Assert( q.IsAlmostEqualTo( pts[n - 1] ), "expected tessellation end equal curve end point" );
346 |
347 | if( 2 == n )
348 | {
349 | n = -1; // this is a straight line
350 | }
351 | else
352 | {
353 | --n; // skip last point
354 |
355 | for( int i = 0; i < n; ++i )
356 | {
357 | loop.Add( new Point2dInt( pts[i] ) );
358 | }
359 | }
360 | }
361 |
362 | // If tessellation is disabled,
363 | // or curve is too short to tessellate,
364 | // or has only two tessellation points,
365 | // just add the start point:
366 |
367 | if( -1 == n )
368 | {
369 | loop.Add( new Point2dInt( p ) );
370 | }
371 | }
372 | Debug.Assert( q.IsAlmostEqualTo( p0, 1e-05 ),
373 | string.Format(
374 | "expected last endpoint to equal current start point, not distance {0}",
375 | p0.DistanceTo( q ) ) );
376 | }
377 | return loop;
378 | }
379 |
380 | ///
381 | /// Add all plan view boundary loops from
382 | /// given solid to the list of loops.
383 | /// The creation application argument is used to
384 | /// reverse the extrusion analyser output curves
385 | /// in case they are badly oriented.
386 | ///
387 | /// Number of loops added
388 | static int AddLoops(
389 | Autodesk.Revit.Creation.Application creapp,
390 | JtLoops loops,
391 | GeometryObject obj,
392 | ref int nExtrusionAnalysisFailures )
393 | {
394 | int nAdded = 0;
395 |
396 | Solid solid = obj as Solid;
397 |
398 | if( null != solid
399 | && 0 < solid.Faces.Size )
400 | {
401 | //Plane plane = new Plane(XYZ.BasisX,
402 | // XYZ.BasisY, XYZ.Zero); // 2016
403 |
404 | Plane plane = Plane.CreateByOriginAndBasis(
405 | XYZ.Zero, XYZ.BasisX, XYZ.BasisY ); // 2017
406 |
407 | ExtrusionAnalyzer extrusionAnalyzer = null;
408 |
409 | try
410 | {
411 | extrusionAnalyzer = ExtrusionAnalyzer.Create(
412 | solid, plane, XYZ.BasisZ );
413 | }
414 | catch( Autodesk.Revit.Exceptions
415 | .InvalidOperationException )
416 | {
417 | ++nExtrusionAnalysisFailures;
418 | return nAdded;
419 | }
420 |
421 | Face face = extrusionAnalyzer
422 | .GetExtrusionBase();
423 |
424 | loops.Add( GetLoop( creapp, face ) );
425 |
426 | ++nAdded;
427 | }
428 | return nAdded;
429 | }
430 |
431 | #region Obsolete GetPlanViewBoundaryLoopsMultiple
432 | ///
433 | /// Retrieve all plan view boundary loops from
434 | /// all solids of given element. This initial
435 | /// version passes each solid encountered in the
436 | /// given element to the ExtrusionAnalyzer one
437 | /// at a time, which obviously results in multiple
438 | /// loops, many of which are contained within the
439 | /// others. An updated version unites all the
440 | /// solids first and then uses the ExtrusionAnalyzer
441 | /// once only to obtain the true outside shadow
442 | /// contour.
443 | ///
444 | static JtLoops GetPlanViewBoundaryLoopsMultiple(
445 | Element e,
446 | ref int nFailures )
447 | {
448 | Autodesk.Revit.Creation.Application creapp
449 | = e.Document.Application.Create;
450 |
451 | JtLoops loops = new JtLoops( 1 );
452 |
453 | //int nSolids = 0;
454 |
455 | Options opt = new Options();
456 |
457 | GeometryElement geo = e.get_Geometry( opt );
458 |
459 | if( null != geo )
460 | {
461 | Document doc = e.Document;
462 |
463 | if( e is FamilyInstance )
464 | {
465 | geo = geo.GetTransformed(
466 | Transform.Identity );
467 | }
468 |
469 | //GeometryInstance inst = null;
470 |
471 | foreach( GeometryObject obj in geo )
472 | {
473 | AddLoops( creapp, loops, obj, ref nFailures );
474 |
475 | //inst = obj as GeometryInstance;
476 | }
477 |
478 | //if( 0 == nSolids && null != inst )
479 | //{
480 | // geo = inst.GetSymbolGeometry();
481 |
482 | // foreach( GeometryObject obj in geo )
483 | // {
484 | // AddLoops( creapp, loops, obj, ref nFailures );
485 | // }
486 | //}
487 | }
488 | return loops;
489 | }
490 | #endregion // Obsolete GetPlanViewBoundaryLoopsMultiple
491 |
492 | ///
493 | /// Retrieve all plan view boundary loops from
494 | /// all solids of the given element geometry
495 | /// united together.
496 | ///
497 | internal static JtLoops GetPlanViewBoundaryLoopsGeo(
498 | Autodesk.Revit.Creation.Application creapp,
499 | GeometryElement geo,
500 | ref int nFailures )
501 | {
502 | Solid union = null;
503 |
504 | Plane plane = Plane.CreateByOriginAndBasis(
505 | XYZ.Zero, XYZ.BasisX, XYZ.BasisY );
506 |
507 | foreach( GeometryObject obj in geo )
508 | {
509 | Solid solid = obj as Solid;
510 |
511 | if( null != solid
512 | && 0 < solid.Faces.Size )
513 | {
514 | // Some solids, e.g. in the standard
515 | // content 'Furniture Chair - Office'
516 | // cause an extrusion analyser failure,
517 | // so skip adding those.
518 |
519 | try
520 | {
521 | ExtrusionAnalyzer extrusionAnalyzer
522 | = ExtrusionAnalyzer.Create(
523 | solid, plane, XYZ.BasisZ );
524 | }
525 | catch( Autodesk.Revit.Exceptions
526 | .InvalidOperationException )
527 | {
528 | solid = null;
529 | ++nFailures;
530 | }
531 |
532 | if( null != solid )
533 | {
534 | if( null == union )
535 | {
536 | union = solid;
537 | }
538 | else
539 | {
540 | try
541 | {
542 | union = BooleanOperationsUtils
543 | .ExecuteBooleanOperation( union, solid,
544 | BooleanOperationsType.Union );
545 | }
546 | catch( Autodesk.Revit.Exceptions
547 | .InvalidOperationException )
548 | {
549 | ++nFailures;
550 | }
551 | }
552 | }
553 | }
554 | }
555 |
556 | JtLoops loops = new JtLoops( 1 );
557 |
558 | AddLoops( creapp, loops, union, ref nFailures );
559 |
560 | return loops;
561 | }
562 |
563 | ///
564 | /// Retrieve all plan view boundary loops from
565 | /// all solids of given element united together.
566 | /// If the element is a family instance, transform
567 | /// its loops from the instance placement
568 | /// coordinate system back to the symbol
569 | /// definition one.
570 | /// If no geometry can be determined, use the
571 | /// bounding box instead.
572 | ///
573 | static JtLoops GetPlanViewBoundaryLoops(
574 | Element e,
575 | ref int nFailures )
576 | {
577 | Autodesk.Revit.Creation.Application creapp
578 | = e.Document.Application.Create;
579 |
580 | JtLoops loops = null;
581 |
582 | Options opt = new Options();
583 |
584 | GeometryElement geo = e.get_Geometry( opt );
585 |
586 | if( null != geo )
587 | {
588 | Document doc = e.Document;
589 |
590 | if( e is FamilyInstance )
591 | {
592 | // Retrieve family instance geometry
593 | // transformed back to symbol definition
594 | // coordinate space by inverting the
595 | // family instance placement transformation
596 |
597 | LocationPoint lp = e.Location
598 | as LocationPoint;
599 |
600 | Transform t = Transform.CreateTranslation(
601 | -lp.Point );
602 |
603 | Transform r = Transform.CreateRotationAtPoint(
604 | XYZ.BasisZ, -lp.Rotation, lp.Point );
605 |
606 | geo = geo.GetTransformed( t * r );
607 | }
608 |
609 | loops = GetPlanViewBoundaryLoopsGeo(
610 | creapp, geo, ref nFailures );
611 | }
612 | if( null == loops || 0 == loops.Count )
613 | {
614 | Debug.Print(
615 | "Unable to determine geometry for "
616 | + Util.ElementDescription( e )
617 | + "; using bounding box instead." );
618 |
619 | BoundingBoxXYZ bb;
620 |
621 | if( e is FamilyInstance )
622 | {
623 | bb = ( e as FamilyInstance ).Symbol
624 | .get_BoundingBox( null );
625 | }
626 | else
627 | {
628 | bb = e.get_BoundingBox( null );
629 | }
630 | JtLoop loop = new JtLoop( 4 );
631 | loop.Add( new Point2dInt( bb.Min ) );
632 | loop.Add( new Point2dInt( bb.Max.X, bb.Min.Y ) );
633 | loop.Add( new Point2dInt( bb.Max ) );
634 | loop.Add( new Point2dInt( bb.Min.X, bb.Max.Y ) );
635 | loops.Add( loop );
636 | }
637 | return loops;
638 | }
639 |
640 | ///
641 | /// List all the loops retrieved
642 | /// from the given element.
643 | ///
644 | static void ListLoops( Element e, JtLoops loops )
645 | {
646 | int nLoops = loops.Count;
647 |
648 | Debug.Print( "{0} has {1}{2}",
649 | Util.ElementDescription( e ),
650 | Util.PluralString( nLoops, "loop" ),
651 | Util.DotOrColon( nLoops ) );
652 |
653 | int i = 0;
654 |
655 | foreach( JtLoop loop in loops )
656 | {
657 | Debug.Print( " {0}: {1}", i++,
658 | loop.ToString() );
659 | }
660 | }
661 |
662 | ///
663 | /// Upload the selected rooms and the furniture
664 | /// they contain to the cloud database.
665 | ///
666 | public static void UploadRoom(
667 | IntPtr hwnd,
668 | Document doc,
669 | Room room )
670 | {
671 | BoundingBoxXYZ bb = room.get_BoundingBox( null );
672 |
673 | if( null == bb )
674 | {
675 | Util.ErrorMsg( string.Format( "Skipping room {0} "
676 | + "because it has no bounding box.",
677 | Util.ElementDescription( room ) ) );
678 |
679 | return;
680 | }
681 |
682 | JtLoops roomLoops = GetRoomLoops( room );
683 |
684 | ListLoops( room, roomLoops );
685 |
686 | List furniture
687 | = GetFurniture( room );
688 |
689 | // Map symbol UniqueId to symbol loop
690 |
691 | Dictionary furnitureLoops
692 | = new Dictionary();
693 |
694 | // List of instances referring to symbols
695 |
696 | List furnitureInstances
697 | = new List(
698 | furniture.Count );
699 |
700 | int nFailures;
701 |
702 | foreach( FamilyInstance f in furniture )
703 | {
704 | FamilySymbol s = f.Symbol;
705 |
706 | string uid = s.UniqueId;
707 |
708 | if( !furnitureLoops.ContainsKey( uid ) )
709 | {
710 | nFailures = 0;
711 |
712 | JtLoops loops = GetPlanViewBoundaryLoops(
713 | f, ref nFailures );
714 |
715 | if( 0 < nFailures )
716 | {
717 | Debug.Print( "{0}: {1}",
718 | Util.ElementDescription( f ),
719 | Util.PluralString( nFailures,
720 | "extrusion analyser failure" ) );
721 | }
722 | ListLoops( f, loops );
723 |
724 | if( 0 < loops.Count )
725 | {
726 | // Assume first loop is outer one
727 |
728 | furnitureLoops.Add( uid, loops[0] );
729 | }
730 | }
731 | furnitureInstances.Add(
732 | new JtPlacement2dInt( f ) );
733 | }
734 | //IWin32Window revit_window
735 | // = new JtWindowHandle(
736 | // ComponentManager.ApplicationWindow ); // pre-2020
737 |
738 | IWin32Window revit_window
739 | = new JtWindowHandle( hwnd ); // 2020
740 |
741 | string caption = doc.Title
742 | + " : " + doc.GetElement( room.LevelId ).Name
743 | + " : " + room.Name;
744 |
745 | Bitmap bmp = GeoSnoop.DisplayRoom( roomLoops,
746 | furnitureLoops, furnitureInstances );
747 |
748 | GeoSnoop.DisplayImageInForm( revit_window,
749 | caption, false, bmp );
750 |
751 | DbUpload.DbUploadRoom( room, furniture,
752 | roomLoops, furnitureLoops );
753 | }
754 |
755 | #region External command mainline Execute method
756 | public Result Execute(
757 | ExternalCommandData commandData,
758 | ref string message,
759 | ElementSet elements )
760 | {
761 | UIApplication uiapp = commandData.Application;
762 | UIDocument uidoc = uiapp.ActiveUIDocument;
763 | Application app = uiapp.Application;
764 | Document doc = uidoc.Document;
765 |
766 | IntPtr hwnd = uiapp.MainWindowHandle;
767 |
768 | if( null == doc )
769 | {
770 | Util.ErrorMsg( "Please run this command in a valid"
771 | + " Revit project document." );
772 | return Result.Failed;
773 | }
774 |
775 | // Iterate over all pre-selected rooms
776 |
777 | Selection sel = uidoc.Selection;
778 |
779 | ICollection ids = sel.GetElementIds();
780 |
781 | if( 0 < ids.Count )
782 | {
783 | foreach( ElementId id in ids )
784 | {
785 | if( !( doc.GetElement( id ) is Room ) )
786 | {
787 | Util.ErrorMsg( "Please pre-select only room"
788 | + " elements before running this command." );
789 | return Result.Failed;
790 | }
791 | }
792 | }
793 |
794 | // If no rooms were pre-selected,
795 | // prompt for post-selection
796 |
797 | if( null == ids || 0 == ids.Count )
798 | {
799 | IList refs = null;
800 |
801 | try
802 | {
803 | refs = sel.PickObjects( ObjectType.Element,
804 | new RoomSelectionFilter(),
805 | "Please select rooms." );
806 | }
807 | catch( Autodesk.Revit.Exceptions
808 | .OperationCanceledException )
809 | {
810 | return Result.Cancelled;
811 | }
812 | ids = new List(
813 | refs.Select(
814 | r => r.ElementId ) );
815 | }
816 |
817 | // Upload selected rooms to cloud database
818 |
819 | foreach( ElementId id in ids )
820 | {
821 | UploadRoom( hwnd, doc,
822 | doc.GetElement( id ) as Room );
823 | }
824 |
825 | DbUpdater.SetLastSequence();
826 |
827 | return Result.Succeeded;
828 | }
829 | #endregion // External command mainline Execute method
830 | }
831 | }
832 |
--------------------------------------------------------------------------------
/RoomEditorApp/ContiguousCurveSorter.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Autodesk.Revit.DB;
6 | using System.Diagnostics;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | static class CurveGetEnpointExtension
12 | {
13 | static public XYZ GetEndPoint(
14 | this Curve curve,
15 | int i )
16 | {
17 | return curve.GetEndPoint( i );
18 | }
19 | }
20 |
21 | ///
22 | /// Curve loop utilities supporting resorting and
23 | /// orientation of curves to form a contiguous
24 | /// closed loop.
25 | ///
26 | class CurveUtils
27 | {
28 | const double _inch = 1.0 / 12.0;
29 | const double _sixteenth = _inch / 16.0;
30 |
31 | public enum FailureCondition
32 | {
33 | Success,
34 | CurvesNotContigous,
35 | CurveLoopAboveTarget,
36 | NoIntersection
37 | };
38 |
39 | ///
40 | /// Predicate to report whether the given curve
41 | /// type is supported by this utility class.
42 | ///
43 | /// The curve.
44 | /// True if the curve type is supported,
45 | /// false otherwise.
46 | public static bool IsSupported(
47 | Curve curve )
48 | {
49 | return curve is Line || curve is Arc;
50 | }
51 |
52 | ///
53 | /// Create a new curve with the same
54 | /// geometry in the reverse direction.
55 | ///
56 | /// The original curve.
57 | /// The reversed curve.
58 | /// If the
59 | /// curve type is not supported by this utility.
60 | static Curve CreateReversedCurve(
61 | Autodesk.Revit.Creation.Application creapp,
62 | Curve orig )
63 | {
64 | if( !IsSupported( orig ) )
65 | {
66 | throw new NotImplementedException(
67 | "CreateReversedCurve for type "
68 | + orig.GetType().Name );
69 | }
70 |
71 | if( orig is Line )
72 | {
73 | return Line.CreateBound(
74 | orig.GetEndPoint( 1 ),
75 | orig.GetEndPoint( 0 ) );
76 | }
77 | else if( orig is Arc )
78 | {
79 | return Arc.Create( orig.GetEndPoint( 1 ),
80 | orig.GetEndPoint( 0 ),
81 | orig.Evaluate( 0.5, true ) );
82 | }
83 | else
84 | {
85 | throw new Exception(
86 | "CreateReversedCurve - Unreachable" );
87 | }
88 | }
89 |
90 | ///
91 | /// Sort a list of curves to make them correctly
92 | /// ordered and oriented to form a closed loop.
93 | ///
94 | public static void SortCurvesContiguous(
95 | Autodesk.Revit.Creation.Application creapp,
96 | IList curves,
97 | bool debug_output )
98 | {
99 | int n = curves.Count;
100 |
101 | // Walk through each curve (after the first)
102 | // to match up the curves in order
103 |
104 | for( int i = 0; i < n; ++i )
105 | {
106 | Curve curve = curves[i];
107 | XYZ endPoint = curve.GetEndPoint( 1 );
108 |
109 | if( debug_output )
110 | {
111 | Debug.Print( "{0} endPoint {1}", i,
112 | Util.PointString( endPoint ) );
113 | }
114 |
115 | XYZ p;
116 |
117 | // Find curve with start point = end point
118 |
119 | bool found = (i + 1 >= n);
120 |
121 | for( int j = i + 1; j < n; ++j )
122 | {
123 | p = curves[j].GetEndPoint( 0 );
124 |
125 | // If there is a match end->start,
126 | // this is the next curve
127 |
128 | if( _sixteenth > p.DistanceTo( endPoint ) )
129 | {
130 | if( i + 1 == j )
131 | {
132 | if( debug_output )
133 | {
134 | Debug.Print(
135 | "{0} start point match, no need to swap",
136 | j, i + 1 );
137 | }
138 | }
139 | else
140 | {
141 | if( debug_output )
142 | {
143 | Debug.Print(
144 | "{0} start point, swap with {1}",
145 | j, i + 1 );
146 | }
147 | Curve tmp = curves[i + 1];
148 | curves[i + 1] = curves[j];
149 | curves[j] = tmp;
150 | }
151 | found = true;
152 | break;
153 | }
154 |
155 | p = curves[j].GetEndPoint( 1 );
156 |
157 | // If there is a match end->end,
158 | // reverse the next curve
159 |
160 | if( _sixteenth > p.DistanceTo( endPoint ) )
161 | {
162 | if( i + 1 == j )
163 | {
164 | if( debug_output )
165 | {
166 | Debug.Print(
167 | "{0} end point, reverse {1}",
168 | j, i + 1 );
169 | }
170 |
171 | curves[i + 1] = CreateReversedCurve(
172 | creapp, curves[j] );
173 | }
174 | else
175 | {
176 | if( debug_output )
177 | {
178 | Debug.Print(
179 | "{0} end point, swap with reverse {1}",
180 | j, i + 1 );
181 | }
182 |
183 | Curve tmp = curves[i + 1];
184 | curves[i + 1] = CreateReversedCurve(
185 | creapp, curves[j] );
186 | curves[j] = tmp;
187 | }
188 | found = true;
189 | break;
190 | }
191 | }
192 | if( !found )
193 | {
194 | throw new Exception( "SortCurvesContiguous:"
195 | + " non-contiguous input curves" );
196 | }
197 | }
198 | }
199 |
200 | ///
201 | /// Return a list of curves which are correctly
202 | /// ordered and oriented to form a closed loop.
203 | ///
204 | /// The document.
205 | /// The list of curve element references which are the boundaries.
206 | /// The list of curves.
207 | public static IList GetContiguousCurvesFromSelectedCurveElements(
208 | Document doc,
209 | IList boundaries,
210 | bool debug_output )
211 | {
212 | List curves = new List();
213 |
214 | // Build a list of curves from the curve elements
215 |
216 | foreach( Reference reference in boundaries )
217 | {
218 | CurveElement curveElement = doc.GetElement(
219 | reference ) as CurveElement;
220 |
221 | curves.Add( curveElement.GeometryCurve.Clone() );
222 | }
223 |
224 | SortCurvesContiguous( doc.Application.Create,
225 | curves, debug_output );
226 |
227 | return curves;
228 | }
229 |
230 | ///
231 | /// Identifies if the curve lies entirely in an XY plane (Z = constant)
232 | ///
233 | /// The curve.
234 | /// True if the curve lies in an XY plane, false otherwise.
235 | public static bool IsCurveInXYPlane( Curve curve )
236 | {
237 | // quick reject - are endpoints at same Z
238 |
239 | double zDelta = curve.GetEndPoint( 1 ).Z
240 | - curve.GetEndPoint( 0 ).Z;
241 |
242 | if( Math.Abs( zDelta ) > 1e-05 )
243 | return false;
244 |
245 | if( !( curve is Line ) && !curve.IsCyclic )
246 | {
247 | // Create curve loop from curve and
248 | // connecting line to get plane
249 |
250 | List curves = new List();
251 | curves.Add( curve );
252 |
253 | //curves.Add(Line.CreateBound(curve.GetEndPoint(1), curve.GetEndPoint(0)));
254 |
255 | CurveLoop curveLoop = CurveLoop.Create( curves );
256 |
257 | XYZ normal = curveLoop.GetPlane().Normal
258 | .Normalize();
259 |
260 | if( !normal.IsAlmostEqualTo( XYZ.BasisZ )
261 | && !normal.IsAlmostEqualTo( XYZ.BasisZ.Negate() ) )
262 | {
263 | return false;
264 | }
265 | }
266 | return true;
267 | }
268 | }
269 | }
270 |
--------------------------------------------------------------------------------
/RoomEditorApp/DbModel.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using DreamSeat;
5 | #endregion
6 |
7 | namespace RoomEditorApp
8 | {
9 | ///
10 | /// Base class for all Room Editor database classes.
11 | ///
12 | class DbObj : CouchDocument
13 | {
14 | protected DbObj( string uid )
15 | {
16 | Type = "obj";
17 | Id = uid;
18 | }
19 | public string Type { get; protected set; }
20 | public string Description { get; set; }
21 | public string Name { get; set; }
22 | }
23 |
24 | #region Model - Level - Room - Symbol - Furniture
25 | ///
26 | /// Current model, i.e. Revit project.
27 | ///
28 | class DbModel : DbObj
29 | {
30 | public DbModel( string uid ) : base( uid )
31 | {
32 | Type = "model";
33 | }
34 | }
35 |
36 | ///
37 | /// Level.
38 | ///
39 | class DbLevel : DbObj
40 | {
41 | public DbLevel( string uid ) : base( uid )
42 | {
43 | Type = "level";
44 | }
45 | public string ModelId { get; set; }
46 | }
47 |
48 | ///
49 | /// Room
50 | ///
51 | class DbRoom : DbObj
52 | {
53 | public DbRoom( string uid ) : base( uid )
54 | {
55 | Type = "room";
56 | }
57 | public string LevelId { get; set; }
58 | public string Loops { get; set; }
59 | public string ViewBox { get; set; }
60 | }
61 |
62 | ///
63 | /// Family symbol, i.e. element type defining
64 | /// the geometry, i.e. the 2D boundary loop.
65 | ///
66 | class DbSymbol : DbObj
67 | {
68 | public DbSymbol( string uid ) : base( uid )
69 | {
70 | Type = "symbol";
71 | }
72 | public string Loop { get; set; }
73 | }
74 |
75 | ///
76 | /// Family instance, defining placement, i.e.
77 | /// transform, i.e. translation and rotation,
78 | /// and referring to the symbol geometry.
79 | ///
80 | class DbFurniture : DbObj
81 | {
82 | public DbFurniture( string uid ) : base( uid )
83 | {
84 | Type = "furniture";
85 | }
86 | public string RoomId { get; set; }
87 | public Dictionary Properties { get; set; }
88 | public string SymbolId { get; set; }
89 | public string Transform { get; set; }
90 | }
91 | #endregion // Model - Level - Room - Symbol - Furniture
92 |
93 | #region Obj2 - Sheet - View - Part - Instance
94 | /////
95 | ///// Base class for all second-generation Room Editor classes.
96 | /////
97 | //class DbObj2 : DbObj
98 | //{
99 | // protected DbObj2( string uid ) : base( uid )
100 | // {
101 | // Type = "obj2";
102 | // }
103 | // public Dictionary Properties { get; set; }
104 | //}
105 |
106 | ///
107 | /// Sheet. Lives in a model. Contains views.
108 | ///
109 | class DbSheet : DbObj
110 | {
111 | public DbSheet( string uid ) : base( uid )
112 | {
113 | Type = "sheet";
114 | }
115 | public string ModelId { get; set; }
116 | public int Width { get; set; }
117 | public int Height { get; set; }
118 | }
119 |
120 | ///
121 | /// View. Lives on a sheet. Displays BIM elements.
122 | ///
123 | class DbView : DbObj
124 | {
125 | public DbView( string uid ) : base( uid )
126 | {
127 | Type = "view";
128 | }
129 | public string SheetId { get; set; }
130 | public int X { get; set; }
131 | public int Y { get; set; }
132 | public int Width { get; set; }
133 | public int Height { get; set; }
134 | public int BimX { get; set; }
135 | public int BimY { get; set; }
136 | public int BimWidth { get; set; }
137 | public int BimHeight { get; set; }
138 | //public string ViewBox { get; set; }
139 | }
140 |
141 | ///
142 | /// BIM element, either part or instance.
143 | /// A BIM element can appear in multiple views.
144 | ///
145 | class DbBimel : DbObj
146 | {
147 | public DbBimel( string uid ) : base( uid )
148 | {
149 | Type = "bimel";
150 | ViewIds = new List();
151 | }
152 | public Dictionary Properties { get; set; }
153 | public List ViewIds { get; set; }
154 | }
155 |
156 | ///
157 | /// Part of the building, cannot move.
158 | /// A building part can appear in multiple views.
159 | /// It has its own graphical representation in
160 | /// absolute global coordinates hence no placement.
161 | ///
162 | class DbPart : DbBimel
163 | {
164 | public DbPart( string uid ) : base( uid )
165 | {
166 | Type = "part";
167 | }
168 | public string Loop { get; set; }
169 | }
170 |
171 | ///
172 | /// Family instance, defining placement, i.e.
173 | /// transform, i.e. translation and rotation,
174 | /// and referring to the symbol geometry.
175 | ///
176 | class DbInstance : DbBimel
177 | {
178 | public DbInstance( string uid ) : base( uid )
179 | {
180 | Type = "instance";
181 | }
182 | public string SymbolId { get; set; }
183 | public string Transform { get; set; }
184 | }
185 | #endregion // Obj2 - Sheet - View - Part - Instance
186 | }
187 |
--------------------------------------------------------------------------------
/RoomEditorApp/DbUpdater.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Runtime.InteropServices;
7 | using System.Threading;
8 | using Autodesk.Revit.ApplicationServices;
9 | using Autodesk.Revit.DB;
10 | using Autodesk.Revit.UI;
11 | using Autodesk.Windows;
12 | using DreamSeat;
13 | #endregion
14 |
15 | namespace RoomEditorApp
16 | {
17 | class DbUpdater : IExternalEventHandler
18 | {
19 | static public int LastSequence
20 | {
21 | get;
22 | set;
23 | }
24 |
25 | ///
26 | /// Determine and set the last sequence
27 | /// number after updating database.
28 | ///
29 | static public int SetLastSequence()
30 | {
31 | LastSequence = new RoomEditorDb()
32 | .LastSequenceNumber;
33 |
34 | Util.InfoMsg( string.Format(
35 | "Last sequence number set to {0}."
36 | + "\nChanges from now on will be applied.",
37 | LastSequence ) );
38 |
39 | return LastSequence;
40 | }
41 |
42 | ///
43 | /// Current Revit project document.
44 | ///
45 | //Document _doc = null;
46 |
47 | ///
48 | /// Revit UI application.
49 | ///
50 | UIApplication _uiapp = null;
51 |
52 | ///
53 | /// Revit creation application for
54 | /// generating transient geometry objects.
55 | ///
56 | Autodesk.Revit.Creation.Application _creapp = null;
57 |
58 | ///
59 | /// External event to raise event
60 | /// for pending database changes.
61 | ///
62 | //static ExternalEvent _event = null;
63 |
64 | ///
65 | /// Separate thread running loop to
66 | /// check for pending database changes.
67 | ///
68 | static Thread _thread = null;
69 |
70 | ///
71 | /// Store the unique ids of all room in this model
72 | /// in a dictionary for fast lookup to check
73 | /// whether a given piece of furniture or
74 | /// equipment really belongs to this model.
75 | ///
76 | Dictionary _roomUniqueIdDict = null;
77 |
78 | public DbUpdater( UIApplication uiapp )
79 | {
80 | using( JtTimer pt = new JtTimer( "DbUpdater ctor" ) )
81 | {
82 | //_doc = doc;
83 | _uiapp = uiapp;
84 | _creapp = _uiapp.Application.Create;
85 | }
86 | }
87 |
88 | ///
89 | /// Update a piece of furniture.
90 | /// Return true if anything was changed.
91 | ///
92 | bool UpdateBimFurniture(
93 | DbFurniture f )
94 | {
95 | Document doc = _uiapp.ActiveUIDocument.Document;
96 |
97 | bool rc = false;
98 |
99 | if( !_roomUniqueIdDict.ContainsKey( f.RoomId ) )
100 | {
101 | Debug.Print( "Furniture instance '{0}' '{1}'"
102 | + " with UniqueId {2} belong to a room from"
103 | + " a different model, so ignore it.",
104 | f.Name, f.Description, f.Id );
105 |
106 | return rc;
107 | }
108 |
109 | Element e = doc.GetElement( f.Id );
110 |
111 | if( null == e )
112 | {
113 | Util.ErrorMsg( string.Format(
114 | "Unable to retrieve element '{0}' '{1}' "
115 | + "with UniqueId {2}. Are you in the right "
116 | + "Revit model?", f.Name,
117 | f.Description, f.Id ) );
118 |
119 | return rc;
120 | }
121 |
122 | if( !( e is FamilyInstance ) )
123 | {
124 | Debug.Print( "Strange, we received an "
125 | + "updated '{0}' '{1}' with UniqueId {2}, "
126 | + "which we ignore.", f.Name,
127 | f.Description, f.Id );
128 |
129 | return rc;
130 | }
131 |
132 | // Convert SVG transform from string to int
133 | // to XYZ point and rotation in radians
134 | // including flipping of Y coordinates.
135 |
136 | string svgTransform = f.Transform;
137 |
138 | char[] separators = new char[] { ',', 'R', 'T' };
139 | string[] a = svgTransform.Substring( 1 ).Split( separators );
140 | int[] trxy = a.Select( s => int.Parse( s ) ).ToArray();
141 |
142 | double r = Util.ConvertDegreesToRadians(
143 | Util.SvgFlipY( trxy[0] ) );
144 |
145 | XYZ p = new XYZ(
146 | Util.ConvertMillimetresToFeet( trxy[1] ),
147 | Util.ConvertMillimetresToFeet( Util.SvgFlipY( trxy[2] ) ),
148 | 0.0 );
149 |
150 | // Check for modified transform
151 |
152 | LocationPoint lp = e.Location as LocationPoint;
153 |
154 | XYZ translation = p - lp.Point;
155 | double rotation = r - lp.Rotation;
156 |
157 | bool modifiedTransform = ( 0.01 < translation.GetLength() )
158 | || ( 0.01 < Math.Abs( rotation ) );
159 |
160 | // Check for modified properties
161 |
162 | List modifiedPropertyKeys = new List();
163 |
164 | Dictionary dbdict
165 | = f.Properties;
166 |
167 | Dictionary eldict
168 | = Util.GetElementProperties( e );
169 |
170 | Debug.Assert( dbdict.Count == eldict.Count,
171 | "expected equal dictionary length" );
172 |
173 | string key_db; // JavaScript lowercases first char
174 | string val_db; // remove prepended "r " or "w "
175 | string val_el;
176 |
177 | foreach( string key in eldict.Keys )
178 | {
179 | Parameter pa = e.LookupParameter( key );
180 |
181 | Debug.Assert( null != pa, "expected valid parameter" );
182 |
183 | if( Util.IsModifiable( pa ) )
184 | {
185 | key_db = Util.Uncapitalise( key );
186 |
187 | Debug.Assert( dbdict.ContainsKey( key_db ),
188 | "expected same keys in Revit model and cloud database" );
189 |
190 | val_db = dbdict[key_db].Substring( 2 );
191 |
192 | if( StorageType.String == pa.StorageType )
193 | {
194 | val_el = pa.AsString() ?? string.Empty;
195 | }
196 | else
197 | {
198 | Debug.Assert( StorageType.Integer == pa.StorageType,
199 | "expected only string and integer parameters" );
200 |
201 | val_el = pa.AsInteger().ToString();
202 | }
203 |
204 | if( !val_el.Equals( val_db ) )
205 | {
206 | modifiedPropertyKeys.Add( key );
207 | }
208 | }
209 | }
210 |
211 | if( modifiedTransform || 0 < modifiedPropertyKeys.Count )
212 | {
213 | using( Transaction tx = new Transaction(
214 | doc ) )
215 | {
216 | tx.Start( "Update Furniture and "
217 | + "Equipmant Instance Placement" );
218 |
219 | if( .01 < translation.GetLength() )
220 | {
221 | ElementTransformUtils.MoveElement(
222 | doc, e.Id, translation );
223 | }
224 | if( .01 < Math.Abs( rotation ) )
225 | {
226 | Line axis = Line.CreateBound( lp.Point,
227 | lp.Point + XYZ.BasisZ );
228 |
229 | ElementTransformUtils.RotateElement(
230 | doc, e.Id, axis, rotation );
231 | }
232 | foreach( string key in modifiedPropertyKeys )
233 | {
234 | Parameter pa = e.LookupParameter( key );
235 |
236 | key_db = Util.Uncapitalise( key );
237 | val_db = dbdict[key_db].Substring( 2 );
238 |
239 | if( StorageType.String == pa.StorageType )
240 | {
241 | pa.Set( val_db );
242 | }
243 | else
244 | {
245 | try
246 | {
247 | int i = int.Parse( val_db );
248 | pa.Set( i );
249 | }
250 | catch( System.FormatException )
251 | {
252 | }
253 | }
254 | }
255 | tx.Commit();
256 | rc = true;
257 | }
258 | }
259 | return rc;
260 | }
261 |
262 | ///
263 | /// Apply all current cloud database
264 | /// changes to the BIM.
265 | ///
266 | public void UpdateBim()
267 | {
268 | Util.Log( "UpdateBim begin" );
269 |
270 | using( JtTimer pt = new JtTimer( "UpdateBim" ) )
271 | {
272 | Document doc = _uiapp.ActiveUIDocument.Document;
273 |
274 | // Retrieve all room unique ids in model:
275 |
276 | FilteredElementCollector rooms
277 | = new FilteredElementCollector( doc )
278 | .OfClass( typeof( SpatialElement ) )
279 | .OfCategory( BuiltInCategory.OST_Rooms );
280 |
281 | IEnumerable roomUniqueIds
282 | = rooms.Select(
283 | e => e.UniqueId );
284 |
285 | // Convert to a dictionary for faster lookup:
286 |
287 | _roomUniqueIdDict
288 | = new Dictionary(
289 | roomUniqueIds.Count() );
290 |
291 | foreach( string s in roomUniqueIds )
292 | {
293 | _roomUniqueIdDict.Add( s, 1 );
294 | }
295 |
296 | //string ids = "?keys=[%22" + string.Join(
297 | // "%22,%22", roomUniqueIds ) + "%22]";
298 |
299 | // Retrieve all furniture transformations
300 | // after the last sequence number:
301 |
302 | CouchDatabase db = new RoomEditorDb().Db;
303 |
304 | ChangeOptions opt = new ChangeOptions();
305 |
306 | opt.IncludeDocs = true;
307 | opt.Since = LastSequence;
308 | //opt.View = "roomedit/map_room_to_furniture"; // Why is this different now with NuGet package?
309 |
310 | // I tried to add a filter to this view, but
311 | // that is apparently not supported by the
312 | // CouchDB or DreamSeat GetChanges functionality.
313 | //+ ids; // failed attempt to filter view by room id keys
314 |
315 | // Specify filter function defined in
316 | // design document to get updates
317 | //opt.Filter =
318 |
319 | CouchChanges changes
320 | = db.GetChanges( opt );
321 |
322 | CouchChangeResult[] results
323 | = changes.Results;
324 |
325 | foreach( CouchChangeResult result
326 | in results )
327 | {
328 | UpdateBimFurniture( result.Doc );
329 |
330 | LastSequence = result.Sequence;
331 | }
332 | }
333 | Util.Log( "UpdateBim end" );
334 | }
335 |
336 | ///
337 | /// Execute method invoked by Revit via the
338 | /// external event as a reaction to a call
339 | /// to its Raise method.
340 | ///
341 | public void Execute( UIApplication a )
342 | {
343 | // As far as I can tell, the external event
344 | // should work fine even when switching between
345 | // different documents. That, however, remains
346 | // to be tested in more depth (or at all).
347 |
348 | //Document doc = a.ActiveUIDocument.Document;
349 |
350 | //Debug.Assert( doc.Title.Equals( _doc.Title ),
351 | // "oops ... different documents ... test this" );
352 |
353 | //View view = a.ActiveUIDocument.ActiveView;
354 | //Debug.Print( "Active view: " + view.Name );
355 |
356 | UpdateBim();
357 | }
358 |
359 | ///
360 | /// Required IExternalEventHandler interface
361 | /// method returning a descriptive name.
362 | ///
363 | public string GetName()
364 | {
365 | return string.Format(
366 | "{0} DbUpdater", App.Caption );
367 | }
368 |
369 | ///
370 | /// Count total number of checks for
371 | /// database updates made so far.
372 | ///
373 | static int _nLoopCount = 0;
374 |
375 | ///
376 | /// Count total number of checks for
377 | /// database updates made so far.
378 | ///
379 | static int _nCheckCount = 0;
380 |
381 | ///
382 | /// Count total number of database
383 | /// updates requested so far.
384 | ///
385 | static int _nUpdatesRequested = 0;
386 |
387 | ///
388 | /// Wait far a moment before requerying database.
389 | ///
390 | //static Stopwatch _stopwatch = null;
391 |
392 | ///
393 | /// Number of milliseconds to wait and relinquish
394 | /// CPU control before next check for pending
395 | /// database updates.
396 | ///
397 | static int _timeout = 100;
398 |
399 | // DLL imports from user32.dll to set focus to
400 | // Revit to force it to forward the external event
401 | // Raise to actually call the external event
402 | // Execute.
403 |
404 | ///
405 | /// The GetForegroundWindow function returns a
406 | /// handle to the foreground window.
407 | ///
408 | [DllImport( "user32.dll" )]
409 | static extern IntPtr GetForegroundWindow();
410 |
411 | ///
412 | /// Move the window associated with the passed
413 | /// handle to the front.
414 | ///
415 | [DllImport( "user32.dll" )]
416 | static extern bool SetForegroundWindow(
417 | IntPtr hWnd );
418 |
419 | ///
420 | /// Repeatedly check database status and raise
421 | /// external event when updates are pending.
422 | /// Relinquish control and wait for timeout
423 | /// period between each attempt. Run in a
424 | /// separate thread.
425 | ///
426 | static void CheckForPendingDatabaseChanges()
427 | {
428 | while( null != App.Event )
429 | {
430 | ++_nLoopCount;
431 |
432 | //Debug.Assert( null != _event,
433 | //"expected non-null external event" );
434 |
435 | //if( null == _event )
436 | //{
437 | // break;
438 | //}
439 |
440 | if( App.Event.IsPending )
441 | {
442 | Util.Log( string.Format(
443 | "CheckForPendingDatabaseChanges loop {0} - "
444 | + "database update event is pending",
445 | _nLoopCount ) );
446 | }
447 | else
448 | {
449 | using( JtTimer pt = new JtTimer(
450 | "CheckForPendingDatabaseChanges" ) )
451 | {
452 | ++_nCheckCount;
453 |
454 | Util.Log( string.Format(
455 | "CheckForPendingDatabaseChanges loop {0} - "
456 | + "check for changes {1}",
457 | _nLoopCount, _nCheckCount ) );
458 |
459 | RoomEditorDb rdb = new RoomEditorDb();
460 |
461 | if( rdb.LastSequenceNumberChanged(
462 | DbUpdater.LastSequence ) )
463 | {
464 | App.Event.Raise();
465 |
466 | ++_nUpdatesRequested;
467 |
468 | Util.Log( string.Format(
469 | "database update pending event raised {0} times",
470 | _nUpdatesRequested ) );
471 |
472 | #region Obsolete attempts that failed
473 | // Move the mouse in case the user does
474 | // not. Otherwise, it may take a while
475 | // before Revit forwards the event Raise
476 | // to the event handler Execute method.
477 |
478 | // Just moving the mouse is not enough:
479 |
480 | //System.Drawing.Point p = Cursor.Position;
481 | //Cursor.Position = new System.Drawing
482 | // .Point( p.X + 1, p.Y );
483 | //Cursor.Position = p;
484 |
485 | // This did not work either:
486 |
487 | //[DllImport( "user32.dll" )]
488 | //static extern IntPtr SetFocus(
489 | // IntPtr hwnd );
490 |
491 | //IWin32Window revit_window
492 | // = new JtWindowHandle(
493 | // ComponentManager.ApplicationWindow );
494 | //IntPtr hwnd = SetFocus( revit_window.Handle );
495 | //IntPtr hwnd2 = SetFocus( hwnd );
496 | //Debug.Print( "set to rvt {0} --> {1} --> {2}",
497 | // revit_window.Handle, hwnd, hwnd2 );
498 |
499 | // Try SendKeys?
500 | #endregion // Obsolete attempts that failed
501 |
502 | // Set focus to Revit for a moment.
503 | // Otherwise, it may take a while before
504 | // Revit forwards the event Raise to the
505 | // event handler Execute method.
506 |
507 | IntPtr hBefore = GetForegroundWindow();
508 |
509 | SetForegroundWindow(
510 | ComponentManager.ApplicationWindow );
511 |
512 | SetForegroundWindow( hBefore );
513 | }
514 | }
515 | }
516 |
517 | // Wait a moment and relinquish control before
518 | // next check for pending database updates.
519 |
520 | Thread.Sleep( _timeout );
521 | }
522 | }
523 |
524 | ///
525 | /// Toggle subscription to automatic database
526 | /// updates. Forward the call to the external
527 | /// application that creates the external event,
528 | /// store it and launch a separate thread checking
529 | /// for database updates. When changes are pending,
530 | /// invoke the external event Raise method.
531 | ///
532 | public static void ToggleSubscription(
533 | UIApplication uiapp )
534 | {
535 | // Todo: stop thread first!
536 |
537 | if( App.ToggleSubscription2( new DbUpdater(
538 | uiapp ) ) )
539 | {
540 | // Start a new thread to regularly check the
541 | // database status and raise the external event
542 | // when updates are pending.
543 |
544 | _thread = new Thread(
545 | CheckForPendingDatabaseChanges );
546 |
547 | _thread.Start();
548 | }
549 | else
550 | {
551 | _thread.Abort();
552 | _thread = null;
553 | }
554 | }
555 | }
556 | }
557 |
--------------------------------------------------------------------------------
/RoomEditorApp/DbUpload.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using Autodesk.Revit.DB;
6 | using Autodesk.Revit.DB.Architecture;
7 | using DreamSeat;
8 | #endregion
9 |
10 | namespace RoomEditorApp
11 | {
12 | class DbUpload
13 | {
14 | #region GetProjectInfo
15 | public static Element GetProjectInfo( Document doc )
16 | {
17 | return new FilteredElementCollector( doc )
18 | .OfClass( typeof( ProjectInfo ) )
19 | .FirstElement();
20 | }
21 | #endregion // GetProjectInfo
22 |
23 | #region GetDbModel
24 | static DbModel GetDbModel(
25 | CouchDatabase db,
26 | Element projectInfo )
27 | {
28 | string uid = projectInfo.UniqueId;
29 |
30 | DbModel dbModel;
31 |
32 | if( db.DocumentExists( uid ) )
33 | {
34 | dbModel = db.GetDocument( uid );
35 |
36 | Debug.Assert(
37 | dbModel.Id.Equals( projectInfo.UniqueId ),
38 | "expected equal ids" );
39 |
40 | dbModel.Description = Util.ElementDescription(
41 | projectInfo );
42 |
43 | dbModel.Name = projectInfo.Document.Title;
44 |
45 | dbModel = db.UpdateDocument(
46 | dbModel );
47 | }
48 | else
49 | {
50 | dbModel = new DbModel( uid );
51 |
52 | dbModel.Description = Util.ElementDescription(
53 | projectInfo );
54 |
55 | dbModel.Name = projectInfo.Name;
56 | dbModel = db.CreateDocument( dbModel );
57 | }
58 |
59 | return dbModel;
60 | }
61 | #endregion // GetDbModel
62 |
63 | #region DbUploadRoom
64 | ///
65 | /// Upload model, level, room and furniture data
66 | /// to an IrisCouch hosted CouchDB data repository.
67 | ///
68 | static public void DbUploadRoom(
69 | Room room,
70 | List furniture,
71 | JtLoops roomLoops,
72 | Dictionary furnitureLoops )
73 | {
74 | CouchDatabase db = new RoomEditorDb().Db;
75 |
76 | Document doc = room.Document;
77 |
78 | Element projectInfo = GetProjectInfo( doc );
79 |
80 | DbModel dbModel = GetDbModel( db, projectInfo );
81 |
82 | Element level = doc.GetElement( room.LevelId );
83 |
84 | string uid = level.UniqueId;
85 |
86 | DbLevel dbLevel;
87 |
88 | if( db.DocumentExists( uid ) )
89 | {
90 | dbLevel = db.GetDocument( uid );
91 |
92 | Debug.Assert(
93 | dbLevel.Id.Equals( level.UniqueId ),
94 | "expected equal ids" );
95 |
96 | dbLevel.Description = Util.ElementDescription(
97 | level );
98 |
99 | dbLevel.Name = level.Name;
100 | dbLevel.ModelId = projectInfo.UniqueId;
101 |
102 | dbLevel = db.UpdateDocument(
103 | dbLevel );
104 | }
105 | else
106 | {
107 | dbLevel = new DbLevel( uid );
108 |
109 | dbLevel.Description = Util.ElementDescription(
110 | level );
111 |
112 | dbLevel.Name = level.Name;
113 | dbLevel.ModelId = projectInfo.UniqueId;
114 |
115 | dbLevel = db.CreateDocument(
116 | dbLevel );
117 | }
118 |
119 | uid = room.UniqueId;
120 |
121 | DbRoom dbRoom;
122 |
123 | if( db.DocumentExists( uid ) )
124 | {
125 | dbRoom = db.GetDocument( uid );
126 |
127 | Debug.Assert(
128 | dbRoom.Id.Equals( room.UniqueId ),
129 | "expected equal ids" );
130 |
131 | dbRoom.Description = Util.ElementDescription(
132 | room );
133 |
134 | dbRoom.Name = room.Name;
135 | dbRoom.LevelId = level.UniqueId;
136 | dbRoom.Loops = roomLoops.SvgPath;
137 | dbRoom.ViewBox = roomLoops.BoundingBox.SvgViewBox;
138 |
139 | dbRoom = db.UpdateDocument( dbRoom );
140 | }
141 | else
142 | {
143 | dbRoom = new DbRoom( uid );
144 |
145 | dbRoom.Description = Util.ElementDescription(
146 | room );
147 |
148 | dbRoom.Name = room.Name;
149 | dbRoom.LevelId = level.UniqueId;
150 | dbRoom.Loops = roomLoops.SvgPath;
151 | dbRoom.ViewBox = roomLoops.BoundingBox.SvgViewBox;
152 |
153 | dbRoom = db.CreateDocument( dbRoom );
154 | }
155 |
156 | foreach( KeyValuePair p in furnitureLoops )
157 | {
158 | uid = p.Key;
159 | Element e = doc.GetElement( uid );
160 | if( db.DocumentExists( uid ) )
161 | {
162 | DbSymbol symbol = db.GetDocument(
163 | uid );
164 |
165 | symbol.Description = Util.ElementDescription( e );
166 | symbol.Name = e.Name;
167 | symbol.Loop = p.Value.SvgPath;
168 |
169 | symbol = db.UpdateDocument( symbol );
170 | }
171 | else
172 | {
173 | DbSymbol symbol = new DbSymbol( uid );
174 |
175 | symbol.Description = Util.ElementDescription( e );
176 | symbol.Name = e.Name;
177 | symbol.Loop = p.Value.SvgPath;
178 |
179 | symbol = db.CreateDocument( symbol );
180 | }
181 | }
182 |
183 | foreach( FamilyInstance f in furniture )
184 | {
185 | uid = f.UniqueId;
186 | if( db.DocumentExists( uid ) )
187 | {
188 | DbFurniture dbf = db.GetDocument(
189 | uid );
190 |
191 | dbf.Description = Util.ElementDescription( f );
192 | dbf.Name = f.Name;
193 | dbf.RoomId = room.UniqueId;
194 | dbf.Properties = Util.GetElementProperties( f );
195 | dbf.SymbolId = f.Symbol.UniqueId;
196 | dbf.Transform = new JtPlacement2dInt( f )
197 | .SvgTransform;
198 |
199 | dbf = db.UpdateDocument( dbf );
200 | }
201 | else
202 | {
203 | DbFurniture dbf = new DbFurniture( uid );
204 |
205 | dbf.Description = Util.ElementDescription( f );
206 | dbf.Name = f.Name;
207 | dbf.RoomId = room.UniqueId;
208 | dbf.Properties = Util.GetElementProperties( f );
209 | dbf.SymbolId = f.Symbol.UniqueId;
210 | dbf.Transform = new JtPlacement2dInt( f )
211 | .SvgTransform;
212 |
213 | dbf = db.CreateDocument( dbf );
214 | }
215 | }
216 | }
217 | #endregion // DbUploadRoom
218 |
219 | #region DbUploadSheet
220 | #region JtUidSet
221 | ///
222 | /// String list uniquifier.
223 | /// Helper class to ensure that the list of views
224 | /// that a BIM element appears in contains no
225 | /// duplicate entries.
226 | ///
227 | class JtUidSet : Dictionary
228 | {
229 | public JtUidSet( List uids )
230 | {
231 | foreach( string uid in uids )
232 | {
233 | Add( uid );
234 | }
235 | }
236 |
237 | public void Add( string uid )
238 | {
239 | if( ContainsKey( uid ) )
240 | {
241 | ++this[uid];
242 | }
243 | else
244 | {
245 | this[uid] = 1;
246 | }
247 | }
248 |
249 | public List Uids
250 | {
251 | get
252 | {
253 | List uids = new List( Keys );
254 | uids.Sort();
255 | return uids;
256 | }
257 | }
258 | }
259 | #endregion // JtUidSet
260 |
261 | ///
262 | /// Upload model, sheet, views it contains and
263 | /// their BIM elements to a CouchDB data repository.
264 | ///
265 | static public void DbUploadSheet(
266 | ViewSheet sheet,
267 | JtLoops sheetViewportLoops,
268 | SheetModelCollections modelCollections )
269 | {
270 | bool pre_existing = false;
271 |
272 | RoomEditorDb rdb = new RoomEditorDb();
273 | CouchDatabase db = rdb.Db;
274 |
275 | // Sheet
276 |
277 | Document doc = sheet.Document;
278 |
279 | Element e = GetProjectInfo( doc );
280 |
281 | DbModel dbModel = GetDbModel( db, e );
282 |
283 | DbSheet dbSheet = rdb.GetOrCreate(
284 | ref pre_existing, sheet.UniqueId );
285 |
286 | dbSheet.Description = Util.SheetDescription( sheet );
287 | dbSheet.Name = sheet.Name;
288 | dbSheet.ModelId = e.UniqueId;
289 | dbSheet.Width = sheetViewportLoops[0].BoundingBox.Width;
290 | dbSheet.Height = sheetViewportLoops[0].BoundingBox.Height;
291 |
292 | dbSheet = pre_existing
293 | ? db.UpdateDocument( dbSheet )
294 | : db.CreateDocument( dbSheet );
295 |
296 | // Symbols
297 |
298 | Dictionary geometryLookup
299 | = modelCollections.Symbols;
300 |
301 | foreach( KeyValuePair p
302 | in geometryLookup )
303 | {
304 | ElementId id = p.Key;
305 |
306 | e = doc.GetElement( id );
307 |
308 | DbSymbol symbol = rdb.GetOrCreate(
309 | ref pre_existing, e.UniqueId );
310 |
311 | symbol.Description = Util.ElementDescription( e );
312 | symbol.Name = e.Name;
313 | symbol.Loop = p.Value.Loop.SvgPath;
314 |
315 | symbol = pre_existing
316 | ? db.UpdateDocument( symbol )
317 | : db.CreateDocument( symbol );
318 | }
319 |
320 | // Views and BIM elements
321 |
322 | List views = modelCollections
323 | .ViewsInSheet[sheet.Id];
324 |
325 | View view;
326 | DbView dbView;
327 | DbBimel dbBimel;
328 | DbInstance dbInstance = null;
329 | DbPart dbPart = null;
330 | JtBoundingBox2dInt bbFrom;
331 | JtBoundingBox2dInt bbTo;
332 |
333 | foreach( ViewData viewData in views )
334 | {
335 | ElementId vid = viewData.Id;
336 |
337 | if( !modelCollections.BimelsInViews
338 | .ContainsKey( vid ) )
339 | {
340 | // This is not a floor plan view, so
341 | // we have nothing to display in it.
342 |
343 | continue;
344 | }
345 |
346 | view = doc.GetElement( vid ) as View;
347 |
348 | dbView = rdb.GetOrCreate(
349 | ref pre_existing, view.UniqueId );
350 |
351 | dbView.Description = Util.ElementDescription( view );
352 | dbView.Name = view.Name;
353 | dbView.SheetId = dbSheet.Id;
354 |
355 | bbFrom = viewData.BimBoundingBox;
356 | bbTo = viewData.ViewportBoundingBox;
357 |
358 | dbView.X = bbTo.Min.X;
359 | dbView.Y = bbTo.Min.Y;
360 | dbView.Width = bbTo.Width;
361 | dbView.Height = bbTo.Height;
362 |
363 | dbView.BimX = bbFrom.Min.X;
364 | dbView.BimY = bbFrom.Min.Y;
365 | dbView.BimWidth = bbFrom.Width;
366 | dbView.BimHeight = bbFrom.Height;
367 |
368 | dbView = pre_existing
369 | ? db.UpdateDocument( dbView )
370 | : db.CreateDocument( dbView );
371 |
372 | // Retrieve the list of BIM elements
373 | // displayed in this view.
374 |
375 | List bimels = modelCollections
376 | .BimelsInViews[vid];
377 |
378 | foreach( ObjData bimel in bimels )
379 | {
380 | e = doc.GetElement( bimel.Id );
381 |
382 | InstanceData inst = bimel as InstanceData;
383 |
384 | if( null != inst )
385 | {
386 | dbInstance = rdb.GetOrCreate(
387 | ref pre_existing, e.UniqueId );
388 |
389 | dbInstance.SymbolId = doc.GetElement(
390 | inst.Symbol ).UniqueId;
391 |
392 | dbInstance.Transform = inst.Placement
393 | .SvgTransform;
394 |
395 | dbBimel = dbInstance;
396 | }
397 | else
398 | {
399 | Debug.Assert( bimel is GeomData,
400 | "expected part with geometry" );
401 |
402 | dbPart = rdb.GetOrCreate(
403 | ref pre_existing, e.UniqueId );
404 |
405 | dbPart.Loop = ((GeomData) bimel ).Loop
406 | .SvgPath;
407 |
408 | dbBimel = dbPart;
409 | }
410 | dbBimel.Description = Util.ElementDescription( e );
411 | dbBimel.Name = e.Name;
412 | JtUidSet uids = new JtUidSet( dbBimel.ViewIds );
413 | uids.Add( view.UniqueId );
414 | dbBimel.ViewIds = uids.Uids;
415 | dbBimel.Properties = Util.GetElementProperties( e );
416 |
417 | if( null != inst )
418 | {
419 | dbInstance = pre_existing
420 | ? db.UpdateDocument( dbInstance )
421 | : db.CreateDocument( dbInstance );
422 | }
423 | else
424 | {
425 | dbPart = pre_existing
426 | ? db.UpdateDocument( dbPart )
427 | : db.CreateDocument( dbPart );
428 | }
429 |
430 | }
431 | }
432 | }
433 | #endregion // DbUploadSheet
434 | }
435 | }
436 |
--------------------------------------------------------------------------------
/RoomEditorApp/ElementEqualityComparer.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.DB;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | ///
12 | /// Elements with the same element id equate to
13 | /// the same element. Without this, many, many,
14 | /// many duplicates.
15 | ///
16 | class ElementEqualityComparer
17 | : IEqualityComparer
18 | {
19 | public bool Equals( Element x, Element y )
20 | {
21 | return x.Id.IntegerValue.Equals(
22 | y.Id.IntegerValue );
23 | }
24 |
25 | public int GetHashCode( Element obj )
26 | {
27 | return obj.Id.IntegerValue.GetHashCode();
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectCategories.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace RoomEditorApp
2 | {
3 | partial class FrmSelectCategories
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose( bool disposing )
15 | {
16 | if( disposing && ( components != null ) )
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose( disposing );
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.checkedListBox1 = new System.Windows.Forms.CheckedListBox();
32 | this.btnCancel = new System.Windows.Forms.Button();
33 | this.btnOk = new System.Windows.Forms.Button();
34 | this.SuspendLayout();
35 | //
36 | // checkedListBox1
37 | //
38 | this.checkedListBox1.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( ( ( System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom )
39 | | System.Windows.Forms.AnchorStyles.Left )
40 | | System.Windows.Forms.AnchorStyles.Right ) ) );
41 | this.checkedListBox1.FormattingEnabled = true;
42 | this.checkedListBox1.Location = new System.Drawing.Point( 8, 8 );
43 | this.checkedListBox1.Name = "checkedListBox1";
44 | this.checkedListBox1.Size = new System.Drawing.Size( 193, 139 );
45 | this.checkedListBox1.TabIndex = 0;
46 | //
47 | // btnCancel
48 | //
49 | this.btnCancel.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left ) ) );
50 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
51 | this.btnCancel.Location = new System.Drawing.Point( 8, 153 );
52 | this.btnCancel.Name = "btnCancel";
53 | this.btnCancel.Size = new System.Drawing.Size( 75, 23 );
54 | this.btnCancel.TabIndex = 1;
55 | this.btnCancel.Text = "Cancel";
56 | this.btnCancel.UseVisualStyleBackColor = true;
57 | //
58 | // btnOk
59 | //
60 | this.btnOk.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right ) ) );
61 | this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;
62 | this.btnOk.Location = new System.Drawing.Point( 126, 153 );
63 | this.btnOk.Name = "btnOk";
64 | this.btnOk.Size = new System.Drawing.Size( 75, 23 );
65 | this.btnOk.TabIndex = 2;
66 | this.btnOk.Text = "OK";
67 | this.btnOk.UseVisualStyleBackColor = true;
68 | //
69 | // FrmSelectCategories
70 | //
71 | this.AcceptButton = this.btnOk;
72 | this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
73 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
74 | this.CancelButton = this.btnCancel;
75 | this.ClientSize = new System.Drawing.Size( 209, 184 );
76 | this.Controls.Add( this.btnOk );
77 | this.Controls.Add( this.btnCancel );
78 | this.Controls.Add( this.checkedListBox1 );
79 | this.MinimumSize = new System.Drawing.Size( 200, 150 );
80 | this.Name = "FrmSelectCategories";
81 | this.Text = "Select Categories";
82 | this.Load += new System.EventHandler( this.FrmSelectCategories_Load );
83 | this.ResumeLayout( false );
84 |
85 | }
86 |
87 | #endregion
88 |
89 | private System.Windows.Forms.CheckedListBox checkedListBox1;
90 | private System.Windows.Forms.Button btnCancel;
91 | private System.Windows.Forms.Button btnOk;
92 | }
93 | }
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectCategories.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 | using Autodesk.Revit.DB;
7 | using Form = System.Windows.Forms.Form;
8 | #endregion // Namespaces
9 |
10 | namespace RoomEditorApp
11 | {
12 | ///
13 | /// Interactive category selection form.
14 | ///
15 | public partial class FrmSelectCategories : Form
16 | {
17 | ///
18 | /// Automatically accept and close immediately
19 | /// with no need for user interaction to speed
20 | /// up debugging process.
21 | ///
22 | //bool _auto_close;
23 |
24 | ///
25 | /// List of categories for user to select from.
26 | ///
27 | IList _categories;
28 |
29 | ///
30 | /// Initialise the category selector
31 | /// with the given list of categories.
32 | ///
33 | ///
34 | public FrmSelectCategories(
35 | IList categories )
36 | {
37 | InitializeComponent();
38 |
39 | _categories = categories;
40 | //_auto_close = auto_close;
41 | }
42 |
43 | ///
44 | /// Initialise the category selector with
45 | /// the list of categories passed in to
46 | /// the constructor and check them all.
47 | ///
48 | private void FrmSelectCategories_Load(
49 | object sender,
50 | EventArgs e )
51 | {
52 | checkedListBox1.DataSource = _categories;
53 | checkedListBox1.DisplayMember = "Name";
54 |
55 | // Set all entries to be initially checked.
56 |
57 | int n = checkedListBox1.Items.Count;
58 |
59 | for( int i = 0; i < n; ++i )
60 | {
61 | checkedListBox1.SetItemChecked( i, true );
62 | }
63 |
64 | // Automatically close the form with the accept
65 | // button immediately to speed up debugging.
66 | // This does not work, though:
67 | //
68 | //if( _auto_close )
69 | //{
70 | // this.DialogResult = DialogResult.OK;
71 | //}
72 | }
73 |
74 | ///
75 | /// Access the selected categories after the
76 | /// form has been successfully completed.
77 | ///
78 | public List GetSelectedCategories()
79 | {
80 | return checkedListBox1.CheckedItems
81 | .Cast().ToList();
82 | }
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectCategories.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectSheets.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace RoomEditorApp
2 | {
3 | partial class FrmSelectSheets
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose( bool disposing )
15 | {
16 | if( disposing && ( components != null ) )
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose( disposing );
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.checkedListBox1 = new System.Windows.Forms.CheckedListBox();
32 | this.btnCancel = new System.Windows.Forms.Button();
33 | this.btnOk = new System.Windows.Forms.Button();
34 | this.SuspendLayout();
35 | //
36 | // checkedListBox1
37 | //
38 | this.checkedListBox1.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( ( ( System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom )
39 | | System.Windows.Forms.AnchorStyles.Left )
40 | | System.Windows.Forms.AnchorStyles.Right ) ) );
41 | this.checkedListBox1.FormattingEnabled = true;
42 | this.checkedListBox1.Location = new System.Drawing.Point( 8, 8 );
43 | this.checkedListBox1.Name = "checkedListBox1";
44 | this.checkedListBox1.Size = new System.Drawing.Size( 193, 139 );
45 | this.checkedListBox1.TabIndex = 0;
46 | //
47 | // btnCancel
48 | //
49 | this.btnCancel.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Left ) ) );
50 | this.btnCancel.DialogResult = System.Windows.Forms.DialogResult.Cancel;
51 | this.btnCancel.Location = new System.Drawing.Point( 9, 171 );
52 | this.btnCancel.Name = "btnCancel";
53 | this.btnCancel.Size = new System.Drawing.Size( 84, 28 );
54 | this.btnCancel.TabIndex = 1;
55 | this.btnCancel.Text = "Cancel";
56 | this.btnCancel.UseVisualStyleBackColor = true;
57 | //
58 | // btnOk
59 | //
60 | this.btnOk.Anchor = ( (System.Windows.Forms.AnchorStyles) ( ( System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right ) ) );
61 | this.btnOk.DialogResult = System.Windows.Forms.DialogResult.OK;
62 | this.btnOk.Location = new System.Drawing.Point( 116, 171 );
63 | this.btnOk.Name = "btnOk";
64 | this.btnOk.Size = new System.Drawing.Size( 85, 28 );
65 | this.btnOk.TabIndex = 2;
66 | this.btnOk.Text = "OK";
67 | this.btnOk.UseVisualStyleBackColor = true;
68 | //
69 | // FrmSelectSheets
70 | //
71 | this.AcceptButton = this.btnOk;
72 | this.AutoScaleDimensions = new System.Drawing.SizeF( 6F, 13F );
73 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
74 | this.CancelButton = this.btnCancel;
75 | this.ClientSize = new System.Drawing.Size( 209, 211 );
76 | this.Controls.Add( this.btnOk );
77 | this.Controls.Add( this.btnCancel );
78 | this.Controls.Add( this.checkedListBox1 );
79 | this.MinimumSize = new System.Drawing.Size( 200, 150 );
80 | this.Name = "FrmSelectSheets";
81 | this.Text = "Select Sheets to Export";
82 | this.Load += new System.EventHandler( this.OnLoad );
83 | this.ResumeLayout( false );
84 |
85 | }
86 |
87 | #endregion
88 |
89 | private System.Windows.Forms.CheckedListBox checkedListBox1;
90 | private System.Windows.Forms.Button btnCancel;
91 | private System.Windows.Forms.Button btnOk;
92 | }
93 | }
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectSheets.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Windows.Forms;
6 | using Autodesk.Revit.DB;
7 | using Form = System.Windows.Forms.Form;
8 | #endregion // Namespaces
9 |
10 | namespace RoomEditorApp
11 | {
12 | ///
13 | /// Interactive user selection of drawing sheets.
14 | ///
15 | public partial class FrmSelectSheets : Form
16 | {
17 | ///
18 | /// The Revit input project document.
19 | ///
20 | Document _doc;
21 |
22 | ///
23 | /// Constructor initialises the Revit
24 | /// document and nothing else.
25 | ///
26 | ///
27 | public FrmSelectSheets( Document doc )
28 | {
29 | InitializeComponent();
30 |
31 | _doc = doc;
32 | }
33 |
34 | ///
35 | /// Candidate sheets are retrieved by a filtered
36 | /// element collector on loading the form.
37 | ///
38 | private void OnLoad(
39 | object sender,
40 | EventArgs e )
41 | {
42 | List sheets = new List(
43 | new FilteredElementCollector( _doc )
44 | .OfClass( typeof( ViewSheet ) )
45 | .Cast()
46 | .Where( v => v.CanBePrinted
47 | && ViewType.DrawingSheet == v.ViewType ) );
48 |
49 | checkedListBox1.DataSource = sheets;
50 | checkedListBox1.DisplayMember = "Name";
51 |
52 | // Set all entries to be initially checked.
53 |
54 | int n = checkedListBox1.Items.Count;
55 |
56 | for( int i = 0; i < n; ++i )
57 | {
58 | checkedListBox1.SetItemChecked( i, true );
59 | }
60 | }
61 |
62 | ///
63 | /// Selected sheets are accessible after the
64 | /// form has been successfully completed.
65 | ///
66 | public List GetSelectedSheets()
67 | {
68 | return checkedListBox1.CheckedItems
69 | .Cast().ToList();
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/RoomEditorApp/FrmSelectSheets.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/RoomEditorApp/GeoSnoop.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Drawing;
6 | using System.Drawing.Drawing2D;
7 | using System.Windows.Forms;
8 | using ElementId = Autodesk.Revit.DB.ElementId;
9 | #endregion
10 |
11 | namespace RoomEditorApp
12 | {
13 | // based on 06972592 [How to get the shape of Structural Framing objects]
14 | // /a/j/adn/case/sfdc/06972592/src/FramingXsecAnalyzer/FramingXsecAnalyzer/GeoSnoop.cs
15 |
16 | ///
17 | /// Display a collection of loops in a .NET form.
18 | ///
19 | class GeoSnoop
20 | {
21 | #region Constants
22 | ///
23 | /// Width of the form to generate.
24 | ///
25 | const int _form_width = 400;
26 |
27 | ///
28 | /// Pen size.
29 | ///
30 | const int _pen_size = 1;
31 |
32 | ///
33 | /// Pen colour.
34 | ///
35 | static Color _pen_color = Color.Black;
36 |
37 | ///
38 | /// Margin around graphics between
39 | /// sheet and form edge.
40 | ///
41 | const int _margin = 10;
42 |
43 | ///
44 | /// Margin around graphics between
45 | /// BIM elements and viewport edge.
46 | ///
47 | const int _margin2 = 10;
48 | #endregion // Constants
49 |
50 | #region Pen
51 | ///
52 | /// Our one and only pen.
53 | ///
54 | static Pen _pen = null;
55 |
56 | ///
57 | /// Set up and return our one and only pen.
58 | ///
59 | static Pen Pen
60 | {
61 | get
62 | {
63 | if( null == _pen )
64 | {
65 | _pen = new Pen( _pen_color, _pen_size );
66 | }
67 | return _pen;
68 | }
69 | }
70 | #endregion // Pen
71 |
72 | #region DrawLoopsOnGraphics
73 | ///
74 | /// Draw loops on graphics with the specified
75 | /// transform and graphics attributes.
76 | ///
77 | static void DrawLoopsOnGraphics(
78 | Graphics graphics,
79 | List loops,
80 | Matrix transform )
81 | {
82 | foreach( Point[] loop in loops )
83 | {
84 | GraphicsPath path = new GraphicsPath();
85 |
86 | transform.TransformPoints( loop );
87 |
88 | path.AddLines( loop );
89 |
90 | graphics.DrawPath( Pen, path );
91 | }
92 | }
93 | #endregion // DrawLoopsOnGraphics
94 |
95 | #region DisplayRoom
96 | ///
97 | /// Display room and furniture in a temporary form
98 | /// generated on the fly.
99 | ///
100 | /// Room boundary loops
101 | /// Family symbol geometry
102 | /// Family instances
103 | public static Bitmap DisplayRoom(
104 | JtLoops roomLoops,
105 | Dictionary geometryLoops,
106 | List familyInstances )
107 | {
108 | JtBoundingBox2dInt bbFrom = roomLoops.BoundingBox;
109 |
110 | // Adjust target rectangle height to the
111 | // displayee loop height.
112 |
113 | int width = _form_width;
114 | int height = (int) (width * bbFrom.AspectRatio + 0.5);
115 |
116 | //SizeF fsize = new SizeF( width, height );
117 |
118 | //SizeF scaling = new SizeF( 1, 1 );
119 | //PointF translation = new PointF( 0, 0 );
120 |
121 | //GetTransform( fsize, bbFrom,
122 | // ref scaling, ref translation, true );
123 |
124 | //Matrix transform1 = new Matrix(
125 | // new Rectangle(0,0,width,height),
126 | // bbFrom.GetParallelogramPoints());
127 | //transform1.Invert();
128 |
129 | // the bounding box fills the rectangle
130 | // perfectly and completely, inverted and
131 | // non-uniformly distorted:
132 |
133 | //Point2dInt pmin = bbFrom.Min;
134 | //Rectangle rect = new Rectangle(
135 | // pmin.X, pmin.Y, bbFrom.Width, bbFrom.Height );
136 | //Point[] parallelogramPoints = new Point [] {
137 | // new Point( 0, 0 ), // upper left
138 | // new Point( width, 0 ), // upper right
139 | // new Point( 0, height ) // lower left
140 | //};
141 |
142 | // the bounding box fills the rectangle
143 | // perfectly and completely, inverted and
144 | // non-uniformly distorted:
145 |
146 | // Specify transformation target rectangle
147 | // including a margin.
148 |
149 | int bottom = height - (_margin + _margin);
150 |
151 | Point[] parallelogramPoints = new Point[] {
152 | new Point( _margin, bottom ), // upper left
153 | new Point( width - _margin, bottom ), // upper right
154 | new Point( _margin, _margin ) // lower left
155 | };
156 |
157 | // Transform from native loop coordinate system
158 | // to target display coordinates.
159 |
160 | Matrix transform = new Matrix(
161 | bbFrom.Rectangle, parallelogramPoints );
162 |
163 | Bitmap bmp = new Bitmap( width, height );
164 | Graphics graphics = Graphics.FromImage( bmp );
165 |
166 | graphics.Clear( System.Drawing.Color.White );
167 |
168 | DrawLoopsOnGraphics( graphics,
169 | roomLoops.GetGraphicsPathLines(), transform );
170 |
171 | if( null != familyInstances )
172 | {
173 | List loops = new List( 1 );
174 | loops.Add( new Point[] { } );
175 |
176 | foreach( JtPlacement2dInt i in familyInstances )
177 | {
178 | Point2dInt v = i.Translation;
179 | Matrix placement = new Matrix();
180 | placement.Rotate( i.Rotation );
181 | placement.Translate( v.X, v.Y, MatrixOrder.Append );
182 | placement.Multiply( transform, MatrixOrder.Append );
183 | loops[0] = geometryLoops[i.SymbolId]
184 | .GetGraphicsPathLines();
185 |
186 | DrawLoopsOnGraphics( graphics, loops, placement );
187 | }
188 | }
189 | return bmp;
190 | }
191 | #endregion // DisplayRoom
192 |
193 | #region DisplaySheet
194 | ///
195 | /// Display sheet, the views it contains, the BIM
196 | /// parts and family instances they display in a
197 | /// temporary form generated on the fly.
198 | ///
199 | /// Owner window
200 | /// Form caption
201 | /// Modal versus modeless
202 | /// Sheet and viewport boundary loops
203 | /// Family symbol and part geometry
204 | /// Family instances
205 | public static Bitmap DisplaySheet(
206 | ElementId sheetId,
207 | JtLoops sheetViewportLoops,
208 | SheetModelCollections modelCollections )
209 | {
210 | // Source rectangle.
211 |
212 | JtBoundingBox2dInt bbFrom = sheetViewportLoops
213 | .BoundingBox;
214 |
215 | // Adjust target rectangle height to the
216 | // displayee loop height.
217 |
218 | int width = _form_width;
219 | int height = (int) ( width * bbFrom.AspectRatio + 0.5 );
220 |
221 | // Specify transformation target rectangle
222 | // including a margin.
223 |
224 | int top = 0;
225 | int left = 0;
226 | int bottom = height - ( _margin + _margin );
227 |
228 | Point[] parallelogramPoints = new Point[] {
229 | new Point( left + _margin, bottom ), // upper left
230 | new Point( left + width - _margin, bottom ), // upper right
231 | new Point( left + _margin, top + _margin ) // lower left
232 | };
233 |
234 | // Transform from native loop coordinate system
235 | // (sheet) to target display coordinates form).
236 |
237 | Matrix transformSheetBbToForm = new Matrix(
238 | bbFrom.Rectangle, parallelogramPoints );
239 |
240 | Bitmap bmp = new Bitmap( width, height );
241 | Graphics graphics = Graphics.FromImage( bmp );
242 |
243 | graphics.Clear( System.Drawing.Color.White );
244 |
245 | // Display sheet and viewport rectangles.
246 |
247 | DrawLoopsOnGraphics( graphics,
248 | sheetViewportLoops.GetGraphicsPathLines(),
249 | transformSheetBbToForm );
250 |
251 | // Iterate over the views and display the
252 | // elements for each one appropriately
253 | // scaled and translated to fit.
254 |
255 | List views = modelCollections
256 | .ViewsInSheet[sheetId];
257 |
258 | Dictionary geometryLookup
259 | = modelCollections.Symbols;
260 |
261 | Matrix transformBimToViewport;
262 | JtBoundingBox2dInt bbTo;
263 | JtLoop loop;
264 |
265 | foreach( ViewData view in views )
266 | {
267 | ElementId vid = view.Id;
268 |
269 | if( !modelCollections.BimelsInViews
270 | .ContainsKey( vid ) )
271 | {
272 | // This is not a floor plan view, so
273 | // we have nothing to display in it.
274 |
275 | continue;
276 | }
277 |
278 | // Determine transform from model space in mm
279 | // to the viewport associated with this view.
280 |
281 | bbFrom = view.BimBoundingBox;
282 | bbTo = view.ViewportBoundingBox;
283 |
284 | Debug.Print( view.ToString() );
285 |
286 | // Adjust target rectangle height to the
287 | // displayee loop height.
288 |
289 | //height = (int) ( width * bbFrom.AspectRatio + 0.5 );
290 |
291 | // Specify transformation target rectangle
292 | // including a margin, and center the target
293 | // rectangle vertically.
294 |
295 | top = bbTo.Min.Y + _margin2;
296 | left = bbTo.Min.X + _margin2;
297 | bottom = bbTo.Max.Y - _margin2;
298 | width = bbTo.Width - (_margin2 + _margin2);
299 |
300 | parallelogramPoints = new Point[] {
301 | new Point( left, top ), // upper left
302 | new Point( left + width, top ), // upper right
303 | new Point( left, bottom ) // lower left
304 | };
305 |
306 | // Transform from native loop coordinate system
307 | // (sheet) to target display coordinates form).
308 |
309 | transformBimToViewport = new Matrix(
310 | bbFrom.Rectangle, parallelogramPoints );
311 |
312 | // Retrieve the list of BIM elements
313 | // displayed in this view.
314 |
315 | List bimels = modelCollections
316 | .BimelsInViews[vid];
317 |
318 | List loops = new List( 1 );
319 | loops.Add( new Point[] { } );
320 |
321 | Matrix placement = new Matrix();
322 |
323 | foreach( ObjData bimel in bimels )
324 | {
325 | placement.Reset();
326 |
327 | InstanceData inst = bimel as InstanceData;
328 |
329 | if( null != inst )
330 | {
331 | loop = geometryLookup[inst.Symbol].Loop;
332 | Point2dInt v = inst.Placement.Translation;
333 | placement.Rotate( inst.Placement.Rotation );
334 | placement.Translate( v.X, v.Y, MatrixOrder.Append );
335 | }
336 | else
337 | {
338 | Debug.Assert( bimel is GeomData, "expected part with geometry" );
339 |
340 | loop = ((GeomData) bimel).Loop;
341 | }
342 | loops[0] = loop.GetGraphicsPathLines();
343 |
344 | placement.Multiply( transformBimToViewport, MatrixOrder.Append );
345 | placement.Multiply( transformSheetBbToForm, MatrixOrder.Append );
346 |
347 | DrawLoopsOnGraphics( graphics, loops, placement );
348 | }
349 | }
350 | return bmp;
351 | }
352 | #endregion // DisplaySheet
353 |
354 | #region DisplayImageInForm
355 | ///
356 | /// Generate a form on the fly and display the
357 | /// given bitmap image in it in a picture box.
358 | ///
359 | /// Owner window
360 | /// Form caption
361 | /// Modal versus modeless
362 | /// Bitmap image to display
363 | public static void DisplayImageInForm(
364 | IWin32Window owner,
365 | string caption,
366 | bool modal,
367 | Bitmap bmp )
368 | {
369 | Form form = new Form();
370 | form.Text = caption;
371 |
372 | form.Size = new Size( bmp.Width + 7,
373 | bmp.Height + 13 );
374 |
375 | form.FormBorderStyle = FormBorderStyle
376 | .FixedToolWindow;
377 |
378 | PictureBox pb = new PictureBox();
379 | pb.Location = new System.Drawing.Point( 0, 0 );
380 | pb.Dock = System.Windows.Forms.DockStyle.Fill;
381 | pb.Size = bmp.Size;
382 | pb.Parent = form;
383 | pb.Image = bmp;
384 |
385 | if( modal )
386 | {
387 | form.ShowDialog( owner );
388 | }
389 | else
390 | {
391 | form.Show( owner );
392 | }
393 | }
394 | #endregion // DisplayImageInForm
395 | }
396 | }
397 |
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Down.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Down.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Down16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Down16.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Down32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Down32.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Up.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Up16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Up16.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/1Up32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/1Up32.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/2Up.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/2Up.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/2Up16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/2Up16.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/2Up32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/2Up32.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/Question.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/Question.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/Question16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/Question16.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/Question32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/Question32.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/ZigZagRed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/ZigZagRed.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/ZigZagRed16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/ZigZagRed16.png
--------------------------------------------------------------------------------
/RoomEditorApp/Icon/ZigZagRed32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Icon/ZigZagRed32.png
--------------------------------------------------------------------------------
/RoomEditorApp/JtBoundingBox2dInt.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Diagnostics;
4 | using System.Drawing;
5 | #endregion
6 |
7 | namespace RoomEditorApp
8 | {
9 | ///
10 | /// A bounding box for a collection
11 | /// of 2D integer points.
12 | ///
13 | class JtBoundingBox2dInt
14 | {
15 | ///
16 | /// Margin around graphics when
17 | /// exporting SVG view box.
18 | ///
19 | const int _margin = 10;
20 |
21 | ///
22 | /// Minimum and maximum X and Y values.
23 | ///
24 | int xmin, ymin, xmax, ymax;
25 |
26 | ///
27 | /// Initialise to infinite values, e.g. empty box.
28 | ///
29 | public JtBoundingBox2dInt()
30 | {
31 | Init();
32 | }
33 |
34 | ///
35 | /// Initialise to infinite values, e.g. empty box.
36 | ///
37 | public void Init()
38 | {
39 | xmin = ymin = int.MaxValue;
40 | xmax = ymax = int.MinValue;
41 | }
42 |
43 | ///
44 | /// Return current lower left corner.
45 | ///
46 | public Point2dInt Min
47 | {
48 | get { return new Point2dInt( xmin, ymin ); }
49 | }
50 |
51 | ///
52 | /// Return current upper right corner.
53 | ///
54 | public Point2dInt Max
55 | {
56 | get { return new Point2dInt( xmax, ymax ); }
57 | }
58 |
59 | ///
60 | /// Return current center point.
61 | ///
62 | public Point2dInt MidPoint
63 | {
64 | get
65 | {
66 | return new Point2dInt(
67 | (int)(0.5 * ( xmin + xmax )),
68 | (int)(0.5 * ( ymin + ymax )) );
69 | }
70 | }
71 |
72 | ///
73 | /// Return current width.
74 | ///
75 | public int Width
76 | {
77 | get { return xmax - xmin; }
78 | }
79 |
80 | ///
81 | /// Return current height.
82 | ///
83 | public int Height
84 | {
85 | get { return ymax - ymin; }
86 | }
87 |
88 | ///
89 | /// Return aspect ratio, i.e. Height/Width.
90 | ///
91 | public double AspectRatio
92 | {
93 | get
94 | {
95 | return (double) Height / (double) Width;
96 | }
97 | }
98 |
99 | ///
100 | /// Return a System.Drawing.Rectangle for this.
101 | ///
102 | public Rectangle Rectangle
103 | {
104 | get
105 | {
106 | return new Rectangle( xmin, ymin,
107 | Width, Height );
108 | }
109 | }
110 |
111 | ///
112 | /// Expand bounding box to contain
113 | /// the given new point.
114 | ///
115 | public void ExpandToContain( Point2dInt p )
116 | {
117 | if( p.X < xmin ) { xmin = p.X; }
118 | if( p.Y < ymin ) { ymin = p.Y; }
119 | if( p.X > xmax ) { xmax = p.X; }
120 | if( p.Y > ymax ) { ymax = p.Y; }
121 | }
122 |
123 | ///
124 | /// Expand bounding box to contain
125 | /// the given other bounding box.
126 | ///
127 | public void ExpandToContain( JtBoundingBox2dInt b )
128 | {
129 | ExpandToContain( b.Min );
130 | ExpandToContain( b.Max );
131 | }
132 |
133 | /////
134 | ///// Instantiate a new bounding box containing
135 | ///// the given loops.
136 | /////
137 | //public JtBoundingBox2dInt( JtLoops loops )
138 | //{
139 | // foreach( JtLoop loop in loops )
140 | // {
141 | // foreach( Point2dInt p in loop )
142 | // {
143 | // ExpandToContain( p );
144 | // }
145 | // }
146 | //}
147 |
148 | ///
149 | /// Return the four bounding box corners.
150 | ///
151 | public Point2dInt[] Corners
152 | {
153 | get
154 | {
155 | return new Point2dInt[] {
156 | Min,
157 | new Point2dInt( xmax, ymin ),
158 | Max,
159 | new Point2dInt( xmin, ymax )
160 | };
161 | }
162 | }
163 |
164 | ///
165 | /// Display as a string.
166 | ///
167 | public override string ToString()
168 | {
169 | return string.Format( "({0},{1})", Min, Max );
170 | }
171 |
172 | ///
173 | /// Return the SVG viewBox
174 | /// of this bounding box.
175 | ///
176 | public string SvgViewBox
177 | {
178 | get
179 | {
180 | int left = xmin - _margin;
181 | int bottom = ymin - _margin;
182 | int w = Width + _margin + _margin;
183 | int h = Height + _margin + _margin;
184 | if( Util.SvgFlip )
185 | {
186 | bottom = Util.SvgFlipY( bottom ) - h;
187 | }
188 | return string.Format(
189 | "{0} {1} {2} {3}",
190 | left, bottom, w, h );
191 | }
192 | }
193 | }
194 | }
195 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtBoundingBoxXyz.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using Autodesk.Revit.DB;
5 | #endregion
6 |
7 | namespace RoomEditorApp
8 | {
9 | #if NEED_BOUNDING_BOX_XYZ
10 | ///
11 | /// A bounding box for a collection of XYZ instances.
12 | /// The components of a tuple are read-only and cannot
13 | /// be changed after instantiation, so I cannot use
14 | /// that easily.
15 | /// The components of an XYZ are read-only and cannot
16 | /// be changed except by re-instantiation, so I cannot
17 | /// use that easily either.
18 | ///
19 | class JtBoundingBoxXyz // : Tuple
20 | {
21 | ///
22 | /// Minimum and maximum X, Y and Z values.
23 | ///
24 | double xmin, ymin, zmin, xmax, ymax, zmax;
25 |
26 | ///
27 | /// Initialise to infinite values.
28 | ///
29 | public JtBoundingBoxXyz()
30 | //: base(
31 | // new XYZ( double.MaxValue, double.MaxValue, double.MaxValue ),
32 | // new XYZ( double.MinValue, double.MinValue, double.MinValue ) )
33 | {
34 | //Min = new XYZ( double.MaxValue, double.MaxValue, double.MaxValue );
35 | //Max = new XYZ( double.MinValue, double.MinValue, double.MinValue );
36 | xmin = ymin = zmin = double.MaxValue;
37 | xmax = ymax = zmax = double.MinValue;
38 | }
39 |
40 | public JtBoundingBoxXyz( BoundingBoxXYZ bb )
41 | {
42 | xmin = bb.Min.X;
43 | ymin = bb.Min.Y;
44 | zmin = bb.Min.Z;
45 | xmax = bb.Max.X;
46 | ymax = bb.Max.Y;
47 | zmax = bb.Max.Z;
48 | }
49 |
50 | ///
51 | /// Return current lower left corner.
52 | ///
53 | public XYZ Min
54 | {
55 | get { return new XYZ( xmin, ymin, zmin ); }
56 | }
57 |
58 | ///
59 | /// Return current upper right corner.
60 | ///
61 | public XYZ Max
62 | {
63 | get { return new XYZ( xmax, ymax, zmax ); }
64 | }
65 |
66 | public XYZ MidPoint
67 | {
68 | get { return 0.5 * ( Min + Max ); }
69 | }
70 |
71 | //public XYZ Min { get; set; }
72 | //public XYZ Max { get; set; }
73 |
74 | //public XYZ Min
75 | //{
76 | // get { return T1; }
77 | //}
78 |
79 | //public XYZ Max
80 | //{
81 | // get { return T2; }
82 | //}
83 |
84 | ///
85 | /// Expand bounding box to contain
86 | /// the given new point.
87 | ///
88 | public void ExpandToContain( XYZ p )
89 | {
90 | if( p.X < xmin ) { xmin = p.X; }
91 | if( p.Y < ymin ) { ymin = p.Y; }
92 | if( p.Z < zmin ) { zmin = p.Z; }
93 | if( p.X > xmax ) { xmax = p.X; }
94 | if( p.Y > ymax ) { ymax = p.Y; }
95 | if( p.Z > zmax ) { zmax = p.Z; }
96 |
97 | //int i = 0;
98 | //while( i < 3 )
99 | //{
100 | // if( p[i] < _a[i] ) { _a[i] = p[i]; }
101 | // ++i;
102 | //}
103 | //int j = 0;
104 | //while( i < 6 )
105 | //{
106 | // if( p[j] > _a[i] ) { _a[i] = p[j]; }
107 | // ++i;
108 | // ++j;
109 | //}
110 | }
111 |
112 | //public JtBoundingBoxXyz( List> xyzarraylist )
113 | //{
114 | // //Tuple minmax = new Tuple(
115 | // // new XYZ( double.MaxValue, double.MaxValue, double.MaxValue ),
116 | // // new XYZ( double.MinValue, double.MinValue, double.MinValue ) );
117 |
118 | // //xyzarraylist.Aggregate>( minmax, (a, p) =>
119 | // //Accu
120 |
121 | // foreach( List a in xyzarraylist )
122 | // {
123 | // foreach( XYZ p in a )
124 | // {
125 | // ExpandToContain( p );
126 | // }
127 | // }
128 | //}
129 |
130 | ///
131 | /// Return the four bounding box corners
132 | /// projected onto the XY plane.
133 | ///
134 | public UV[] XyCorners
135 | {
136 | get
137 | {
138 | return new UV[] {
139 | new UV( xmin, ymin ),
140 | new UV( xmax, ymin ),
141 | new UV( xmax, ymax ),
142 | new UV( xmin, ymax )
143 | };
144 | }
145 | }
146 | }
147 | #endif // NEED_BOUNDING_BOX_XYZ
148 | }
149 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtLoop.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.Linq;
6 | using System.Text;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | ///
12 | /// A closed or open polygon boundary loop.
13 | ///
14 | class JtLoop : List
15 | {
16 | public bool Closed { get; set; }
17 |
18 | ///
19 | /// Instantiate with a pre-initialised capacity.
20 | ///
21 | public JtLoop( int capacity )
22 | : base( capacity )
23 | {
24 | Closed = true;
25 | }
26 |
27 | ///
28 | /// Instantiate from an array of points.
29 | ///
30 | public JtLoop( Point2dInt[] pts )
31 | : base( pts.Length )
32 | {
33 | Closed = true;
34 |
35 | Add( pts );
36 | }
37 |
38 | ///
39 | /// Add another point to the collection.
40 | /// If the new point is identical to the last,
41 | /// ignore it. This will automatically suppress
42 | /// really small boundary segment fragments.
43 | ///
44 | public new void Add( Point2dInt p )
45 | {
46 | if( 0 == Count
47 | || 0 != p.CompareTo( this[Count - 1] ) )
48 | {
49 | base.Add( p );
50 | }
51 | }
52 |
53 | ///
54 | /// Add a point array to the collection.
55 | /// If the new point is identical to the last,
56 | /// ignore it. This will automatically suppress
57 | /// really small boundary segment fragments.
58 | ///
59 | public void Add( Point2dInt[] pts )
60 | {
61 | foreach( Point2dInt p in pts )
62 | {
63 | Add( p );
64 | }
65 | }
66 |
67 | ///
68 | /// Return a bounding box
69 | /// containing this loop.
70 | ///
71 | public JtBoundingBox2dInt BoundingBox
72 | {
73 | get
74 | {
75 | JtBoundingBox2dInt bb = new JtBoundingBox2dInt();
76 |
77 | foreach( Point2dInt p in this )
78 | {
79 | bb.ExpandToContain( p );
80 | }
81 | return bb;
82 | }
83 | }
84 |
85 | ///
86 | /// Display as a string.
87 | ///
88 | public override string ToString()
89 | {
90 | return string.Join( ", ", this );
91 | }
92 |
93 | ///
94 | /// Return suitable input for the .NET
95 | /// GraphicsPath.AddLines method to display this
96 | /// loop in a form. Note that a closing segment
97 | /// to connect the last point back to the first
98 | /// is added.
99 | ///
100 | public Point[] GetGraphicsPathLines()
101 | {
102 | int i, n;
103 |
104 | n = Count;
105 |
106 | if( Closed ) { ++n; }
107 |
108 | Point[] loop = new Point[n];
109 |
110 | i = 0;
111 | foreach( Point2dInt p in this )
112 | {
113 | loop[i++] = new Point( p.X, p.Y );
114 | }
115 |
116 | if( Closed ) { loop[i] = loop[0]; }
117 |
118 | return loop;
119 | }
120 |
121 | ///
122 | /// Return an SVG path specification, c.f.
123 | /// http://www.w3.org/TR/SVG/paths.html
124 | /// M [0] L [1] [2] ... [n-1] Z
125 | ///
126 | public string SvgPath
127 | {
128 | get
129 | {
130 | return
131 | string.Join( " ",
132 | this.Select(
133 | ( p, i ) => p.SvgPath( i ) ) )
134 | + ( Closed ? "Z" : "" );
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtLoops.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Drawing;
5 | using System.Linq;
6 | using System.Text;
7 | #endregion
8 |
9 | namespace RoomEditorApp
10 | {
11 | ///
12 | /// A list of boundary loops.
13 | ///
14 | class JtLoops : List
15 | {
16 | public JtLoops( int capacity )
17 | : base( capacity )
18 | {
19 | }
20 |
21 | ///
22 | /// Unite two collections of boundary
23 | /// loops into one single one.
24 | ///
25 | public static JtLoops operator+( JtLoops a, JtLoops b )
26 | {
27 | int na = a.Count;
28 | int nb = b.Count;
29 | JtLoops sum = new JtLoops( na + nb );
30 | sum.AddRange( a );
31 | sum.AddRange( b );
32 | return sum;
33 | }
34 |
35 | ///
36 | /// Return a bounding box
37 | /// containing these loops.
38 | ///
39 | public JtBoundingBox2dInt BoundingBox
40 | {
41 | get
42 | {
43 | JtBoundingBox2dInt bb = new JtBoundingBox2dInt();
44 |
45 | foreach( JtLoop loop in this )
46 | {
47 | foreach( Point2dInt p in loop )
48 | {
49 | bb.ExpandToContain( p );
50 | }
51 | }
52 | return bb;
53 | }
54 | }
55 |
56 | ///
57 | /// Return suitable input for the .NET
58 | /// GraphicsPath.AddLines method to display the
59 | /// loops in a form. Note that a closing segment
60 | /// to connect the last point back to the first
61 | /// is added.
62 | ///
63 | public List GetGraphicsPathLines()
64 | {
65 | List loops
66 | = new List( Count );
67 |
68 | foreach( JtLoop jloop in this )
69 | {
70 | loops.Add( jloop.GetGraphicsPathLines() );
71 | }
72 | return loops;
73 | }
74 |
75 | ///
76 | /// Return the concatenated SVG path
77 | /// specifications for all the loops.
78 | ///
79 | public string SvgPath
80 | {
81 | get
82 | {
83 | return string.Join( " ",
84 | this.Select(
85 | a => a.SvgPath ) );
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtPlacement2dInt.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using Autodesk.Revit.DB;
4 | using System.Diagnostics;
5 | #endregion
6 |
7 | namespace RoomEditorApp
8 | {
9 | ///
10 | /// A 2D integer-based transformation,
11 | /// i.e. translation and rotation.
12 | ///
13 | class JtPlacement2dInt
14 | {
15 | ///
16 | /// Translation.
17 | ///
18 | public Point2dInt Translation { get; set; }
19 |
20 | ///
21 | /// Rotation in degrees.
22 | ///
23 | public int Rotation { get; set; }
24 |
25 | ///
26 | /// The family symbol UniqueId.
27 | ///
28 | public string SymbolId { get; set; }
29 |
30 | public JtPlacement2dInt( FamilyInstance fi )
31 | {
32 | LocationPoint lp = fi.Location as LocationPoint;
33 |
34 | Debug.Assert( null != lp,
35 | "expected valid family instanace location point" );
36 |
37 | Translation = new Point2dInt( lp.Point );
38 |
39 | Rotation = Util.ConvertRadiansToDegrees( lp.Rotation );
40 |
41 | SymbolId = fi.Symbol.UniqueId;
42 | }
43 |
44 | ///
45 | /// Create a dummy placement for a non-instance
46 | /// part, i.e. a nomral BIM element with a given
47 | /// unique id, just for GeoSnoop graphical
48 | /// debugging purposes.
49 | ///
50 | public JtPlacement2dInt( string uidPart )
51 | {
52 | Translation = new Point2dInt( 0, 0 );
53 | Rotation = 0;
54 | SymbolId = uidPart;
55 | }
56 |
57 | ///
58 | /// Return an SVG transform,
59 | /// either for native SVG or Raphael.
60 | ///
61 | public string SvgTransform
62 | {
63 | get
64 | {
65 | return string.Format(
66 | "R{2}T{0},{1}",
67 | //"translate({0},{1}) rotate({2})",
68 | Translation.X,
69 | Util.SvgFlipY( Translation.Y ),
70 | Util.SvgFlipY( Rotation ) );
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtTimer.cs:
--------------------------------------------------------------------------------
1 | #region Header
2 | //
3 | // JtTimer.cs - performance profiling timer
4 | //
5 | // Copyright (C) 2010-2016 by Jeremy Tammik, Autodesk Inc. All rights reserved.
6 | //
7 | #endregion // Header
8 |
9 | #region Namespaces
10 | using System;
11 | using System.Collections.Generic;
12 | using System.ComponentModel;
13 | using System.Diagnostics;
14 | using System.IO;
15 | using System.Linq;
16 | #endregion // Namespaces
17 |
18 | namespace RoomEditorApp
19 | {
20 | ///
21 | /// Performance timer for profiling purposes.
22 | /// For a full description, please refer to
23 | /// http://thebuildingcoder.typepad.com/blog/2010/03/performance-profiling.html
24 | ///
25 | public class JtTimer : IDisposable
26 | {
27 | #region Internal TimeRegistry class
28 | class TimeRegistry
29 | {
30 | #region Internal data and helper methods
31 | class Entry
32 | {
33 | public double Time { get; set; }
34 | public int Calls { get; set; }
35 | }
36 |
37 | static Dictionary _collection
38 | = new Dictionary();
39 |
40 | ///
41 | /// Return the percentage based on total time.
42 | ///
43 | /// value
44 | /// total time
45 | ///
46 | static double GetPercent(
47 | double value,
48 | double totalTime )
49 | {
50 | return 0 == totalTime
51 | ? 0
52 | : Math.Round( value * 100 / totalTime, 2 );
53 | }
54 | #endregion // Internal data and helper methods
55 |
56 | ///
57 | /// Add new duration for specified key.
58 | ///
59 | public static void AddTime(
60 | string key,
61 | double duration )
62 | {
63 | Entry e;
64 | if( _collection.ContainsKey( key ) )
65 | {
66 | e = _collection[key];
67 | }
68 | else
69 | {
70 | e = new Entry();
71 | _collection.Add( key, e );
72 | }
73 | e.Time += duration;
74 | ++e.Calls;
75 | }
76 |
77 | ///
78 | /// Write the report of the results to a text file.
79 | ///
80 | public static void WriteResults(
81 | string description,
82 | double totalTime )
83 | {
84 | // Set up text file path:
85 |
86 | string strReportPath = Path.Combine(
87 | Path.GetTempPath(), "PerformanceReport.txt" );
88 |
89 | FileStream fs = new FileStream( strReportPath,
90 | FileMode.OpenOrCreate, FileAccess.Write );
91 |
92 | StreamWriter streamWriter = new StreamWriter( fs );
93 | streamWriter.BaseStream.Seek( 0, SeekOrigin.End );
94 |
95 | // Sort output by percentage of total time used:
96 |
97 | List lines = new List(
98 | _collection.Count );
99 |
100 | foreach( KeyValuePair pair
101 | in _collection )
102 | {
103 | Entry e = pair.Value;
104 |
105 | lines.Add( string.Format(
106 | "{0,10:0.00}%{1,10:0.00}{2,8} {3}",
107 | GetPercent( e.Time, totalTime ),
108 | Math.Round( e.Time, 2 ),
109 | e.Calls,
110 | pair.Key ) );
111 | }
112 | lines.Sort();
113 |
114 | string header
115 | = " Percentage Seconds Calls Process";
116 |
117 | int n = Math.Max( header.Length,
118 | lines.Max( x => x.Length ) );
119 |
120 | if( null != description
121 | && 0 < description.Length )
122 | {
123 | n = Math.Max( n, description.Length );
124 | header = description + "\r\n" + header;
125 | }
126 | string separator = "-";
127 | while( 0 < n-- )
128 | {
129 | separator += "-";
130 | }
131 | streamWriter.WriteLine( separator );
132 | streamWriter.WriteLine( header );
133 | streamWriter.WriteLine( separator );
134 |
135 | foreach( string line in lines )
136 | {
137 | streamWriter.WriteLine( line );
138 | }
139 | streamWriter.WriteLine( separator + "\r\n" );
140 | streamWriter.Close();
141 | fs.Close();
142 | Process.Start( strReportPath );
143 | _collection.Clear();
144 | }
145 | }
146 | #endregion // Internal TimeRegistry class
147 |
148 | string _key;
149 | Stopwatch _timer;
150 | double _duration = 0;
151 |
152 | ///
153 | /// Performance timer constructor.
154 | ///
155 | ///
156 | /// Key describing code to be timed
157 | public JtTimer( string what_are_we_testing_here )
158 | {
159 | Restart( what_are_we_testing_here );
160 | }
161 |
162 | ///
163 | /// Automatic disposal when the the using statement
164 | /// block finishes: the timer is stopped and the
165 | /// time is registered.
166 | ///
167 | void IDisposable.Dispose()
168 | {
169 | Stop();
170 | }
171 |
172 | ///
173 | /// Write and display a report of the timing
174 | /// results in a text file.
175 | ///
176 | public void Report( string description )
177 | {
178 | TimeRegistry.WriteResults(
179 | description, _duration );
180 | }
181 |
182 | ///
183 | /// Restart the measurement from scratch.
184 | ///
185 | public void Restart( string what_are_we_testing_here )
186 | {
187 | _key = what_are_we_testing_here;
188 | _timer = Stopwatch.StartNew();
189 | }
190 |
191 | ///
192 | /// Stop the timer.
193 | ///
194 | public void Stop()
195 | {
196 | _timer.Stop();
197 | _duration = _timer.Elapsed.TotalSeconds;
198 | TimeRegistry.AddTime( _key, _duration );
199 | }
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/RoomEditorApp/JtWindowHandle.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Diagnostics;
4 | using System.Windows.Forms;
5 | #endregion
6 |
7 | namespace RoomEditorApp
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 |
--------------------------------------------------------------------------------
/RoomEditorApp/Point2dInt.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using Autodesk.Revit.DB;
4 | #endregion
5 |
6 | namespace RoomEditorApp
7 | {
8 | ///
9 | /// An integer-based 2D point class.
10 | ///
11 | class Point2dInt : IComparable
12 | {
13 | public int X { get; set; }
14 | public int Y { get; set; }
15 |
16 | ///
17 | /// Initialise a 2D millimetre integer
18 | /// point to the given values.
19 | ///
20 | public Point2dInt( int x, int y )
21 | {
22 | X = x;
23 | Y = y;
24 | }
25 |
26 | ///
27 | /// Convert a 2D Revit UV to a 2D millimetre
28 | /// integer point by scaling from feet to mm.
29 | ///
30 | public Point2dInt( UV p )
31 | {
32 | X = Util.ConvertFeetToMillimetres( p.U );
33 | Y = Util.ConvertFeetToMillimetres( p.V );
34 | }
35 |
36 | ///
37 | /// Convert a 3D Revit XYZ to a 2D millimetre
38 | /// integer point by discarding the Z coordinate
39 | /// and scaling from feet to mm.
40 | ///
41 | public Point2dInt( XYZ p )
42 | {
43 | X = Util.ConvertFeetToMillimetres( p.X );
44 | Y = Util.ConvertFeetToMillimetres( p.Y );
45 | }
46 |
47 | ///
48 | /// Convert Revit coordinates XYZ to a 2D
49 | /// millimetre integer point by scaling
50 | /// from feet to mm.
51 | ///
52 | public Point2dInt( double x, double y )
53 | {
54 | X = Util.ConvertFeetToMillimetres( x );
55 | Y = Util.ConvertFeetToMillimetres( y );
56 | }
57 |
58 | ///
59 | /// Comparison with another point, important
60 | /// for dictionary lookup support.
61 | ///
62 | public int CompareTo( Point2dInt a )
63 | {
64 | int d = X - a.X;
65 |
66 | if( 0 == d )
67 | {
68 | d = Y - a.Y;
69 | }
70 | return d;
71 | }
72 |
73 | ///
74 | /// Display as a string.
75 | ///
76 | public override string ToString()
77 | {
78 | return string.Format( "({0},{1})", X, Y );
79 | }
80 |
81 | ///
82 | /// Return a string suitable for use in an SVG
83 | /// path. For index i == 0, prefix with 'M', for
84 | /// i == 1 with 'L', and otherwise with nothing.
85 | ///
86 | public string SvgPath( int i )
87 | {
88 | return string.Format( "{0}{1} {2}",
89 | ( 0 == i ? "M" : ( 1 == i ? "L" : "" ) ),
90 | X, Util.SvgFlipY( Y ) );
91 | }
92 |
93 | ///
94 | /// Add two points, i.e. treat one of
95 | /// them as a translation vector.
96 | ///
97 | public static Point2dInt operator +(
98 | Point2dInt a,
99 | Point2dInt b )
100 | {
101 | return new Point2dInt(
102 | a.X + b.X, a.Y + b.Y );
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/RoomEditorApp/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeremytammik/RoomEditorApp/bc816ece6014605746e3a6409fc5fa9e67a6977b/RoomEditorApp/Properties/AssemblyInfo.cs
--------------------------------------------------------------------------------
/RoomEditorApp/RoomEditorApp.addin:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Room Editor
5 | C:\a\vs\RoomEditorApp\RoomEditorApp\bin\Debug\RoomEditorApp.dll
6 | RoomEditorApp.App
7 | 557db8fb-418a-4193-ab89-5b7c5bf21661
8 | TBC_
9 | The Building Coder, http://thebuildingcoder.typepad.com
10 |
11 |
12 |
--------------------------------------------------------------------------------
/RoomEditorApp/RoomEditorApp.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | None
6 |
7 |
8 |
9 |
10 | Debug
11 | AnyCPU
12 |
13 |
14 |
15 |
16 | {7E293FB5-30E3-47FD-BD7D-A8B33EB5C2EE}
17 | Library
18 | Properties
19 | RoomEditorApp
20 | v4.7
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 2017\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 2017\Revit.exe
44 | false
45 |
46 |
47 |
48 | C:\Program Files\Autodesk\Revit 2020\AdWindows.dll
49 | False
50 |
51 |
52 | ..\packages\DreamSeat.1.2.0.0\lib\net40\Autofac.dll
53 | True
54 |
55 |
56 | ..\packages\DreamSeat.1.2.0.0\lib\net40\DreamSeat.dll
57 | True
58 |
59 |
60 | ..\packages\DreamSeat.1.2.0.0\lib\net40\log4net.dll
61 | True
62 |
63 |
64 | ..\packages\DreamSeat.1.2.0.0\lib\net40\mindtouch.dream.dll
65 | True
66 |
67 |
68 | ..\packages\Newtonsoft.Json.4.5.7\lib\net40\Newtonsoft.Json.dll
69 | True
70 |
71 |
72 |
73 | C:\Program Files\Autodesk\Revit 2020\RevitAPI.dll
74 | False
75 |
76 |
77 | C:\Program Files\Autodesk\Revit 2020\RevitAPIUI.dll
78 | False
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 | Form
107 |
108 |
109 | FrmSelectCategories.cs
110 |
111 |
112 | Form
113 |
114 |
115 | FrmSelectSheets.cs
116 |
117 |
118 |
119 |
120 |
121 | Code
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 | FrmSelectCategories.cs
140 |
141 |
142 | FrmSelectSheets.cs
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 | copy "$(ProjectDir)RoomEditorApp.addin" "$(AppData)\Autodesk\REVIT\Addins\2018"
167 |
168 |
--------------------------------------------------------------------------------
/RoomEditorApp/RoomEditorDb.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using Autodesk.Revit.DB;
7 | using Autodesk.Revit.DB.Architecture;
8 | using DreamSeat;
9 | #endregion
10 |
11 | namespace RoomEditorApp
12 | {
13 | class RoomEditorDb
14 | {
15 | const bool _use_local_db = true;
16 | const string _url_web = "jt.iriscouch.com";
17 | const string _url_local = "localhost";
18 | const string _database_name = "roomedit";
19 |
20 | static CouchClient _client = null;
21 | static CouchDatabase _db = null;
22 |
23 | static string Url
24 | {
25 | get
26 | {
27 | return _use_local_db
28 | ? _url_local
29 | : _url_web;
30 | }
31 | }
32 |
33 | public RoomEditorDb()
34 | {
35 | using( JtTimer pt = new JtTimer( "RoomEditorDb ctor" ) )
36 | {
37 | if( null == _client )
38 | {
39 | _client = new CouchClient( Url, 5984 );
40 | }
41 | if( null == _db )
42 | {
43 | _db = _client.GetDatabase( _database_name, true );
44 | }
45 | }
46 | }
47 |
48 | public CouchDatabase Db
49 | {
50 | get
51 | {
52 | return _db;
53 | }
54 | }
55 |
56 | public TDocument GetOrCreate(
57 | ref bool pre_existing,
58 | string uid ) where TDocument : DbObj
59 | {
60 | //return _db.GetDocument( uid );
61 |
62 | TDocument doc;
63 |
64 | if( _db.DocumentExists( uid ) )
65 | {
66 | pre_existing = true;
67 |
68 | doc = _db.GetDocument( uid );
69 |
70 | Debug.Assert(
71 | doc.Id.Equals( uid ),
72 | "expected equal ids" );
73 | }
74 | else
75 | {
76 | pre_existing = false;
77 | doc = (TDocument) Activator.CreateInstance(
78 | typeof( TDocument ), uid );
79 | }
80 | return doc;
81 | }
82 |
83 | ///
84 | /// Return the last sequence number.
85 | ///
86 | public int LastSequenceNumber
87 | {
88 | get
89 | {
90 | using( JtTimer pt = new JtTimer(
91 | "LastSequenceNumber" ) )
92 | {
93 | ChangeOptions opt = new ChangeOptions();
94 |
95 | //opt.Limit = 1;
96 | //opt.Descending = true;
97 |
98 | //opt.IncludeDocs = true;
99 | //opt.View = "roomedit/_changes?descending=true&limit=1";
100 |
101 | CouchChanges changes
102 | = _db.GetChanges( opt );
103 |
104 | CouchChangeResult r
105 | = changes.Results.Last<
106 | CouchChangeResult>();
107 |
108 | return r.Sequence;
109 | }
110 | }
111 | }
112 |
113 | ///
114 | /// Determine whether the given sequence number
115 | /// matches the most up-to-date status.
116 | ///
117 | public bool LastSequenceNumberChanged( int since )
118 | {
119 | using( JtTimer pt = new JtTimer(
120 | "LastSequenceNumberChanged" ) )
121 | {
122 | ChangeOptions opt = new ChangeOptions();
123 |
124 | opt.Since = since;
125 | opt.IncludeDocs = false;
126 |
127 | CouchChanges changes
128 | = _db.GetChanges( opt );
129 |
130 | CouchChangeResult r
131 | = changes.Results.LastOrDefault<
132 | CouchChangeResult>();
133 |
134 | Debug.Assert( null == r || since < r.Sequence,
135 | "expected monotone growing sequence number" );
136 |
137 | return null != r && since < r.Sequence;
138 | }
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/RoomEditorApp/SheetModelCollections.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using Autodesk.Revit.DB;
5 | #endregion
6 |
7 | namespace RoomEditorApp
8 | {
9 | ///
10 | /// Base class for symbol or BIM element, i.e.
11 | /// part or instance.
12 | ///
13 | class ObjData
14 | {
15 | public ElementId Id { get; set; }
16 | }
17 |
18 | ///
19 | /// Part or symbol geometry.
20 | ///
21 | class ViewData : ObjData
22 | {
23 | public JtBoundingBox2dInt BimBoundingBox { get; set; }
24 | public JtBoundingBox2dInt ViewportBoundingBox { get; set; }
25 |
26 | ///
27 | /// Display as a string.
28 | ///
29 | public override string ToString()
30 | {
31 | return string.Format( "view data {0} ({1},{2})",
32 | Id, BimBoundingBox, ViewportBoundingBox );
33 | }
34 | }
35 |
36 | ///
37 | /// Part or symbol geometry.
38 | ///
39 | class GeomData : ObjData
40 | {
41 | public JtLoop Loop { get; set; }
42 | }
43 |
44 | ///
45 | /// Family instance defining placement and referring
46 | /// to symbol. Can live in several views.
47 | ///
48 | class InstanceData : ObjData
49 | {
50 | public ElementId Symbol { get; set; }
51 | public JtPlacement2dInt Placement { get; set; }
52 | //public List Views { get; set; }
53 | }
54 |
55 | ///
56 | /// Package the collections of objects required
57 | /// to represent the model, sheets, views and BIM
58 | /// elements to export.
59 | ///
60 | class SheetModelCollections
61 | {
62 | public ElementId ProjectInfoId { get; set; }
63 | public List SheetIds { get; set; }
64 | public Dictionary> ViewsInSheet { get; set; }
65 | public Dictionary Symbols;
66 | //public Dictionary> InstancesInViews;
67 | //public Dictionary> PartsInViews { get; set; }
68 | public Dictionary> BimelsInViews;
69 |
70 | public SheetModelCollections( ElementId projectInfoId )
71 | {
72 | ProjectInfoId = projectInfoId;
73 | SheetIds = new List();
74 | ViewsInSheet = new Dictionary>();
75 | Symbols = new Dictionary();
76 | BimelsInViews = new Dictionary>();
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/RoomEditorApp/Util.cs:
--------------------------------------------------------------------------------
1 | #region Namespaces
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Diagnostics;
5 | using System.Windows.Forms;
6 | using Autodesk.Revit.DB;
7 | using Autodesk.Revit.UI;
8 | #endregion // Namespaces
9 |
10 | namespace RoomEditorApp
11 | {
12 | class Util
13 | {
14 | #region Geometrical Comparison
15 | const double _eps = 1.0e-9;
16 |
17 | public static bool IsZero(
18 | double a,
19 | double tolerance )
20 | {
21 | return tolerance > Math.Abs( a );
22 | }
23 |
24 | public static bool IsZero( double a )
25 | {
26 | return IsZero( a, _eps );
27 | }
28 |
29 | public static bool IsEqual( double a, double b )
30 | {
31 | return IsZero( b - a );
32 | }
33 | #endregion // Geometrical Comparison
34 |
35 | #region Unit conversion
36 | const double _feet_to_mm = 25.4 * 12;
37 |
38 | public static int ConvertFeetToMillimetres(
39 | double d )
40 | {
41 | //return (int) ( _feet_to_mm * d + 0.5 );
42 | return (int) Math.Round( _feet_to_mm * d,
43 | MidpointRounding.AwayFromZero );
44 | }
45 |
46 | public static double ConvertMillimetresToFeet( int d )
47 | {
48 | return d / _feet_to_mm;
49 | }
50 |
51 | const double _radians_to_degrees = 180.0 / Math.PI;
52 |
53 | public static double ConvertDegreesToRadians( int d )
54 | {
55 | return d * Math.PI / 180.0;
56 | }
57 |
58 | public static int ConvertRadiansToDegrees(
59 | double d )
60 | {
61 | //return (int) ( _radians_to_degrees * d + 0.5 );
62 | return (int) Math.Round( _radians_to_degrees * d,
63 | MidpointRounding.AwayFromZero );
64 | }
65 |
66 | ///
67 | /// Return true if the type b is either a
68 | /// subclass of OR equal to the base class itself.
69 | /// IsSubclassOf returns false if the two types
70 | /// are the same. It only returns true for true
71 | /// non-equal subclasses.
72 | ///
73 | public static bool IsSameOrSubclassOf(
74 | Type a,
75 | Type b )
76 | {
77 | // http://stackoverflow.com/questions/2742276/in-c-how-do-i-check-if-a-type-is-a-subtype-or-the-type-of-an-object
78 |
79 | return a.IsSubclassOf( b ) || a == b;
80 | }
81 | #endregion // Unit conversion
82 |
83 | #region Formatting
84 | ///
85 | /// Uncapitalise string, i.e.
86 | /// lowercase its first character.
87 | ///
88 | public static string Uncapitalise( string s )
89 | {
90 | return Char.ToLowerInvariant( s[0] )
91 | + s.Substring( 1 );
92 | }
93 |
94 | ///
95 | /// Return an English plural suffix for the given
96 | /// number of items, i.e. 's' for zero or more
97 | /// than one, and nothing for exactly one.
98 | ///
99 | public static string PluralSuffix( int n )
100 | {
101 | return 1 == n ? "" : "s";
102 | }
103 |
104 | ///
105 | /// Return an English plural suffix 'ies' or
106 | /// 'y' for the given number of items.
107 | ///
108 | public static string PluralSuffixY( int n )
109 | {
110 | return 1 == n ? "y" : "ies";
111 | }
112 |
113 | ///
114 | /// Return an English pluralised string for the
115 | /// given thing or things. If the thing ends with
116 | /// 'y', the plural is assumes to end with 'ies',
117 | /// e.g.
118 | /// (2, 'chair') -- '2 chairs'
119 | /// (2, 'property') -- '2 properties'
120 | /// (2, 'furniture item') -- '2 furniture items'
121 | /// If in doubt, appending 'item' or 'entry' to
122 | /// the thing description is normally a pretty
123 | /// safe bet. Replaces calls to PluralSuffix
124 | /// and PluralSuffixY.
125 | ///
126 | public static string PluralString(
127 | int n,
128 | string thing )
129 | {
130 | if( 1 == n )
131 | {
132 | return "1 " + thing;
133 | }
134 |
135 | int i = thing.Length - 1;
136 | char cy = thing[i];
137 |
138 | return n.ToString() + " " + ( ( 'y' == cy )
139 | ? thing.Substring( 0, i ) + "ies"
140 | : thing + "s" );
141 | }
142 |
143 | ///
144 | /// Return a dot (full stop) for zero
145 | /// or a colon for more than zero.
146 | ///
147 | public static string DotOrColon( int n )
148 | {
149 | return 0 < n ? ":" : ".";
150 | }
151 |
152 | ///
153 | /// Return a string for a real number
154 | /// formatted to two decimal places.
155 | ///
156 | public static string RealString( double a )
157 | {
158 | return a.ToString( "0.##" );
159 | }
160 |
161 | ///
162 | /// Return a string representation in degrees
163 | /// for an angle given in radians.
164 | ///
165 | public static string AngleString( double angle )
166 | {
167 | return RealString( angle * 180 / Math.PI ) + " degrees";
168 | }
169 |
170 | ///
171 | /// Return a string for a UV point
172 | /// or vector with its coordinates
173 | /// formatted to two decimal places.
174 | ///
175 | public static string PointString( UV p )
176 | {
177 | return string.Format( "({0},{1})",
178 | RealString( p.U ),
179 | RealString( p.V ) );
180 | }
181 |
182 | ///
183 | /// Return a string for an XYZ
184 | /// point or vector with its coordinates
185 | /// formatted to two decimal places.
186 | ///
187 | public static string PointString( XYZ p )
188 | {
189 | return string.Format( "({0},{1},{2})",
190 | RealString( p.X ),
191 | RealString( p.Y ),
192 | RealString( p.Z ) );
193 | }
194 |
195 | ///
196 | /// Return a string for the XY values of an XYZ
197 | /// point or vector with its coordinates
198 | /// formatted to two decimal places.
199 | ///
200 | public static string PointString2d( XYZ p )
201 | {
202 | return string.Format( "({0},{1})",
203 | RealString( p.X ),
204 | RealString( p.Y ) );
205 | }
206 |
207 | ///
208 | /// Return a string displaying the two XYZ
209 | /// endpoints of a geometry curve element.
210 | ///
211 | public static string CurveEndpointString( Curve c )
212 | {
213 | return string.Format( "({0},{1})",
214 | PointString2d( c.GetEndPoint( 0 ) ),
215 | PointString2d( c.GetEndPoint( 1 ) ) );
216 | }
217 |
218 | ///
219 | /// Return a string displaying only the XY values
220 | /// of the two XYZ endpoints of a geometry curve
221 | /// element.
222 | ///
223 | public static string CurveEndpointString2d( Curve c )
224 | {
225 | return string.Format( "({0},{1})",
226 | PointString( c.GetEndPoint( 0 ) ),
227 | PointString( c.GetEndPoint( 1 ) ) );
228 | }
229 |
230 | ///
231 | /// Return a string for a 2D bounding box
232 | /// formatted to two decimal places.
233 | ///
234 | public static string BoundingBoxString(
235 | BoundingBoxUV b )
236 | {
237 | //UV d = b.Max - b.Min;
238 |
239 | return string.Format( "({0},{1})",
240 | PointString( b.Min ),
241 | PointString( b.Max ) );
242 | }
243 |
244 | ///
245 | /// Return a string for a 3D bounding box
246 | /// formatted to two decimal places.
247 | ///
248 | public static string BoundingBoxString(
249 | BoundingBoxXYZ b )
250 | {
251 | //XYZ d = b.Max - b.Min;
252 |
253 | return string.Format( "({0},{1})",
254 | PointString( b.Min ),
255 | PointString( b.Max ) );
256 | }
257 |
258 | ///
259 | /// Return a string for an Outline
260 | /// formatted to two decimal places.
261 | ///
262 | public static string OutlineString( Outline o )
263 | {
264 | //XYZ d = o.MaximumPoint - o.MinimumPoint;
265 |
266 | return string.Format( "({0},{1})",
267 | PointString( o.MinimumPoint ),
268 | PointString( o.MaximumPoint ) );
269 | }
270 | #endregion // Formatting
271 |
272 | #region Element properties
273 | ///
274 | /// Return a string describing the given element:
275 | /// .NET type name,
276 | /// category name,
277 | /// family and symbol name for a family instance,
278 | /// element id and element name.
279 | ///
280 | public static string ElementDescription(
281 | Element e )
282 | {
283 | if( null == e )
284 | {
285 | return "";
286 | }
287 |
288 | // For a wall, the element name equals the
289 | // wall type name, which is equivalent to the
290 | // family name ...
291 |
292 | FamilyInstance fi = e as FamilyInstance;
293 |
294 | string typeName = e.GetType().Name;
295 |
296 | string categoryName = ( null == e.Category )
297 | ? string.Empty
298 | : e.Category.Name + " ";
299 |
300 | string familyName = ( null == fi )
301 | ? string.Empty
302 | : fi.Symbol.Family.Name + " ";
303 |
304 | string symbolName = ( null == fi
305 | || e.Name.Equals( fi.Symbol.Name ) )
306 | ? string.Empty
307 | : fi.Symbol.Name + " ";
308 |
309 | return string.Format( "{0} {1}{2}{3}<{4} {5}>",
310 | typeName, categoryName, familyName,
311 | symbolName, e.Id.IntegerValue, e.Name );
312 | }
313 |
314 | ///
315 | /// Return a string describing the given sheet:
316 | /// sheet number and name.
317 | ///
318 | public static string SheetDescription(
319 | Element e )
320 | {
321 | string sheet_number = e.get_Parameter(
322 | BuiltInParameter.SHEET_NUMBER )
323 | .AsString();
324 |
325 | return string.Format( "{0} - {1}",
326 | sheet_number, e.Name );
327 | }
328 |
329 | ///
330 | /// Return a dictionary of all the given
331 | /// element parameter names and values.
332 | ///
333 | public static bool IsModifiable( Parameter p )
334 | {
335 | StorageType st = p.StorageType;
336 |
337 | return !( p.IsReadOnly )
338 | // && p.UserModifiable // ignore this
339 | && ( ( StorageType.Integer == st )
340 | || ( StorageType.String == st ) );
341 | }
342 |
343 | ///
344 | /// Return a dictionary of all the given
345 | /// element parameter names and values.
346 | ///
347 | public static Dictionary
348 | GetElementProperties(
349 | Element e )
350 | {
351 | IList parameters
352 | = e.GetOrderedParameters();
353 |
354 | Dictionary a
355 | = new Dictionary(
356 | parameters.Count );
357 |
358 | StorageType st;
359 | string s;
360 |
361 | foreach( Parameter p in parameters )
362 | {
363 | st = p.StorageType;
364 |
365 | s = string.Format( "{0} {1}",
366 | ( IsModifiable( p ) ? "w" : "r" ),
367 | ( StorageType.String == st
368 | ? p.AsString()
369 | : p.AsInteger().ToString() ) );
370 |
371 | a.Add( p.Definition.Name, s );
372 | }
373 | return a;
374 | }
375 | #endregion // Element properties
376 |
377 | #region Messages
378 | ///
379 | /// Display a short big message.
380 | ///
381 | public static void InfoMsg( string msg )
382 | {
383 | Debug.Print( msg );
384 | TaskDialog.Show( App.Caption, msg );
385 | }
386 |
387 | ///
388 | /// Display a longer message in smaller font.
389 | ///
390 | public static void InfoMsg2(
391 | string instruction,
392 | string msg,
393 | bool prompt = true )
394 | {
395 | Debug.Print( "{0}: {1}", instruction, msg );
396 | if( prompt )
397 | {
398 | TaskDialog dlg = new TaskDialog( App.Caption );
399 | dlg.MainInstruction = instruction;
400 | dlg.MainContent = msg;
401 | dlg.Show();
402 | }
403 | }
404 |
405 | ///
406 | /// Display an error message.
407 | ///
408 | public static void ErrorMsg( string msg )
409 | {
410 | Debug.Print( msg );
411 | TaskDialog dlg = new TaskDialog( App.Caption );
412 | dlg.MainIcon = TaskDialogIcon.TaskDialogIconWarning;
413 | dlg.MainInstruction = msg;
414 | dlg.Show();
415 | }
416 |
417 | ///
418 | /// Print a debug log message with a time stamp
419 | /// to the Visual Studio debug output window.
420 | ///
421 | public static void Log( string msg )
422 | {
423 | string timestamp = DateTime.Now.ToString(
424 | "HH:mm:ss.fff" );
425 |
426 | Debug.Print( timestamp + " " + msg );
427 | }
428 | #endregion // Messages
429 |
430 | #region Browse for directory
431 | public static bool BrowseDirectory(
432 | ref string path,
433 | bool allowCreate )
434 | {
435 | FolderBrowserDialog browseDlg
436 | = new FolderBrowserDialog();
437 |
438 | browseDlg.SelectedPath = path;
439 | browseDlg.ShowNewFolderButton = allowCreate;
440 |
441 | bool rc = ( DialogResult.OK
442 | == browseDlg.ShowDialog() );
443 |
444 | if( rc )
445 | {
446 | path = browseDlg.SelectedPath;
447 | }
448 | return rc;
449 | }
450 | #endregion // Browse for directory
451 |
452 | #region Flip SVG Y coordinates
453 | public static bool SvgFlip = true;
454 |
455 | ///
456 | /// Flip Y coordinate for SVG export.
457 | ///
458 | public static int SvgFlipY( int y )
459 | {
460 | return SvgFlip ? -y : y;
461 | }
462 | #endregion // Flip SVG Y coordinates
463 | }
464 | }
465 |
--------------------------------------------------------------------------------
/RoomEditorApp/packages.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------