├── .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 | --------------------------------------------------------------------------------