├── .editorconfig ├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── lib └── RetroUI.dll ├── nuget.config ├── readme.md ├── samples ├── 00 Fundamentals - Establish Connection.ipynb ├── 01 Fundamentals - Variable Sharing.ipynb ├── 02 Revit UI.ipynb ├── GIS │ ├── GIS Visualization Building Footprint.ipynb │ └── requirements.txt ├── InProgress │ ├── ChatGPT-conversations.ipynb │ ├── Export-Ifc-Apply-Color-Using-IFCOpenShell.ipynb │ └── sketchplane.dib ├── example.gif ├── images │ └── variables-view.png └── system-prompts │ ├── revit-api-tutor.md │ └── revit-assistant.md └── src ├── .ipynb_checkpoints └── Untitled-checkpoint.ipynb ├── GetStarted.ipynb ├── Jowsy.CSharp.Tests ├── CodeRewriterTests.cs ├── GlobalUsings.cs ├── Jowsy.CSharp.Tests.csproj └── TestBase.cs ├── Jowsy.CSharp ├── CompilationResults.cs ├── Jowsy.CSharp.csproj ├── ReferenceList.cs ├── RoslynCompilerService.cs └── SyntaxUtils.cs ├── Jowsy.DotNet.Interactive.Extensions ├── ConnectRevitKernelCommand.cs ├── Jowsy.DotNet.Interactive.Extensions.csproj ├── NamedPipeKernelConnector.cs ├── RevitKernelExtension.cs └── docs │ └── README.md ├── Jowsy.Revit.KernelAddin ├── App.config ├── App.cs ├── Core │ ├── DisplayEventArgs.cs │ ├── DynamicCodeHandler.cs │ ├── ICodeCommand.cs │ ├── KernelInitEventHandler.cs │ ├── RevitKernel.cs │ ├── RevitKernelExternalEventHandler.cs │ ├── ScriptSample.cs │ └── Variables.cs ├── Jowsy.Revit.KernelAddin.csproj ├── NETInteractiveKernel.addin ├── Properties │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ ├── Settings.settings │ └── launchSettings.json ├── ShowCommand.cs ├── UI │ ├── CommandViewItem.cs │ ├── EnumToStringConverter.cs │ ├── InverseBooleanConverter.cs │ ├── KernelDockablePaneProvider.cs │ ├── KernelObserver.cs │ ├── KernelStatus.cs │ ├── KernelViewer.xaml │ ├── KernelViewer.xaml.cs │ ├── RelayCommand.cs │ ├── SettingsWindow.xaml │ ├── SettingsWindow.xaml.cs │ ├── VariableViewItem.cs │ ├── ViewModel.cs │ └── ViewModelBase.cs ├── dnMerge.config └── packages.config ├── bim-net-interactive.sln ├── install ├── Installer.Generator.cs ├── Installer.cs ├── Installer.csproj └── Resources │ └── Icons │ ├── BackgroundImage.png │ ├── BannerImage.png │ └── ShellIcon.ico └── screenshot-variables.png /.editorconfig: -------------------------------------------------------------------------------- 1 | 2 | [*.{c,c++,cc,cp,cpp,cu,cuh,cxx,h,hh,hpp,hxx,inc,inl,ino,ipp,mpp,proto,tpp}] 3 | indent_style=tab 4 | indent_size=tab 5 | tab_width=4 6 | 7 | [*.{asax,ascx,aspx,cs,cshtml,css,htm,html,js,jsx,master,razor,skin,ts,tsx,vb,xaml,xamlx,xoml}] 8 | indent_style=space 9 | indent_size=4 10 | tab_width=4 11 | 12 | [*.{appxmanifest,build,config,csproj,dbml,discomap,dtd,json,jsproj,lsproj,njsproj,nuspec,proj,props,resjson,resw,resx,StyleCop,targets,tasks,vbproj,xml,xsd}] 13 | indent_style=space 14 | indent_size=2 15 | tab_width=2 16 | 17 | [*] 18 | 19 | # Microsoft .NET properties 20 | csharp_new_line_before_members_in_object_initializers=false 21 | csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion 22 | csharp_style_var_elsewhere=true:hint 23 | csharp_style_var_for_built_in_types=true:hint 24 | csharp_style_var_when_type_is_apparent=true:hint 25 | dotnet_style_predefined_type_for_locals_parameters_members=true:hint 26 | dotnet_style_predefined_type_for_member_access=true:hint 27 | dotnet_style_qualification_for_event=false:warning 28 | dotnet_style_qualification_for_field=false:warning 29 | dotnet_style_qualification_for_method=false:warning 30 | dotnet_style_qualification_for_property=false:warning 31 | dotnet_style_require_accessibility_modifiers=for_non_interface_members:hint -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. Win11] 28 | - Revit version 29 | - VS Code Polyglot version 30 | 31 | **Additional context** 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | *.idea 400 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Joel Waldheim Saury 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/RetroUI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/lib/RetroUI.dll -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # BIM Interactive Notebooks 2 | 3 | This project explores the possibility run live Revit API C# code bundled with visualizations and explanatory text. 4 | 5 | Amazing for rapid prototyping :rocket:- preferably powered by LLM. It is perfect for knowledge sharing, teaching BIM concepts and API development. The variable-sharing feature in NET Interactive lets us code C# against Revit and then use Python in another kernel. It is the missing data-analysis tool for Autodesk Revit! 6 | 7 | ## Installation 8 | > A preview release of the addin is available under releases. Revit 2023 and 2024 is supported. 9 | 10 | * Download file msi or zip of the last release 11 | * Install the Revit addin and open Revit 12 | * "Interactive Revit Kernel" will show up in the Add-Ins tab 13 | * Click on the button to show the Kernel UI. 14 | 15 | You will also need a notebook environment. 16 | I recommend Polyglot Notebooks, an extension to Visual Studio Code but it is also possible to use Jupyter Notebooks. 17 | 18 | * Install Visual Studio Code 19 | * Goto extensions -> Install Polyglot Notebooks 20 | * Polyglot Notebooks requires .NET 7 SDK 21 | 22 | ## My first notebook 23 | 24 | * Create a new notebook in VS Code. 25 | * In Autodesk Revit, click start in the kernel UI 26 | 27 | Add a new C#-code cell and install the revit kernel extension. 28 | ```csharp 29 | #r "nuget:RevitInteractive" 30 | ``` 31 | 32 | In another cell, use the #connect-directive to establish live connection to revit. Make sure to start the kernel first and specify the revit version. 33 | ```csharp 34 | #!connect revit --kernel-name revit --revit-version 2024 35 | ``` 36 | 37 | From now you'll be able to send code to Revit by starting the c#-cell with #!revit: 38 | ```csharp 39 | #!revit 40 | var collector = new FilteredElementCollector( doc, uidoc.ActiveView.Id); 41 | 42 | var query = collector 43 | .WhereElementIsNotElementType() 44 | .WhereElementIsViewIndependent() 45 | .ToElements(); 46 | 47 | var result = query.GroupBy(x => x.Category.Name).Select(y => new { 48 | Id = y.Key, 49 | Count = y.Count() 50 | }).ToList(); 51 | 52 | display(result); 53 | result 54 | ``` 55 | If you open a new model, you need to restart kernels in both notebook and in revit. 56 | 57 | ## System Prompting LLM 58 | When using LLMs such as ChatGPT to write revit API Code for use in the notebook you would probably want to steer it's behaviour to minimize editing the code for use in an interactive context. 59 | See [System Promts](https://github.com/jowsy/bim-net-interactive/tree/main/samples/system-prompts/) for some initial drafts. 60 | 61 | Begin conversation by: 62 | > [SYSTEM] {System prompt text} 63 | 64 | ## Examples 65 | I will try to collect som fun examples. There is also a collection of tutorials in the folder. 66 | Extracting profile geometry from floor and export to shapefile for GIS-visualization using C# and Python (with Shapely and Geopandas). 67 | 68 | See [samples/GIS/GIS Visualization Building Footprint.ipynb](https://github.com/jowsy/bim-net-interactive/blob/main/samples/GIS/GIS%20Visualization%20Building%20Footprint.ipynb) 69 | 70 | ![](./samples/example.gif) 71 | 72 | ## Limitations 73 | 74 | The Revit API is tightly coupled with the Revit UI and the Revit document data structures and operates on the assumption that it's being called within the same process where the UI and the document are loaded. 75 | This prevents you from calling the API from for example a polyglot notebook without some kind of middle-man or dispatcher. 76 | 77 | ## Current solution 78 | NET interactive operates with kernels. A *kernel* is simply a process that receives execution instructions from clients and communicates the results back to them. The decopuled two-process model where you separate execution from evaluation allows for an approach where an evaluator can live inside Autodesk Revit as an addin and receives code from frontend clients such as Polyglot Notebook, Azure Data Studio or Jupyter. 79 | 80 | In .NET Interactive a *proxy kernel* is a concept that describes a subkernel that proxies a remote kernel. We can add a proxy kernel to the composite kernel that routes commands to the actual implementation written as a Revit Addin. The revit addin implements a NET Interactive kernel process and executes code in the Revit API thread using external events(check Jeremy Tammik's arcticle [External Access to the Revit API](https://thebuildingcoder.typepad.com/blog/2017/05/external-access-to-the-revit-api.html) for more info on this topic). 81 | 82 | However, due to the issues with third-party conflicts regarding the Roslyn API:s it was a hurdle to compile the code in the Revit addin so I tested to move the compilation before the code is sent to the embedded kernel in Revit. Technically, it is done using a registered middleware on the proxykernel that compiles the code and then send the path to the compiled assembly to the revit addin which loads it into memory and executes a method defined in a common interface with a list of common variables. 83 | 84 | ## Resources 85 | 86 | * [IPython](https://ipython.org/ipython-doc/stable/overview.html#ipythonzmq) 87 | * [NET Interactive](https://github.com/dotnet/interactive) 88 | * [Literate Programming with LLMs](https://matt-rickard.com/literate-programming-with-llms) 89 | * [Jupyter BIM](https://github.com/chuongmep/JupyterBIM) 90 | * [ChatGPT System Prompts](https://github.com/mustvlad/ChatGPT-System-Prompts/tree/main) 91 | * [dnMerge](https://github.com/CCob/dnMerge) 92 | 93 | 94 | ## Aknowledgements and third-party dependencies 95 | 96 | * Built on top of [NET Interactive](https://github.com/dotnet/interactive) 97 | * Got a lot inspiration from [RevitAddinManager](https://github.com/chuongmep/RevitAddInManager) for how to configure csproj. 98 | * Credit to Alexander Sharykin for his [https://github.com/jowsy/RetroUI](RetroUI) WPF theme (forked by me and adapted for use in a Revit addin). 99 | 100 | ## License 101 | 102 | MIT 103 | -------------------------------------------------------------------------------- /samples/01 Fundamentals - Variable Sharing.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Fundamentals - Variable Sharing\n", 8 | "If we want to use multiple languages in a single notebook we'll need a way to share variables or data between them. NET Interactive provides magic commands for this, for example #!set.\n", 9 | "\n", 10 | "Again, we need to install the Revit kernel extension and connect to a running kernel." 11 | ] 12 | }, 13 | { 14 | "cell_type": "code", 15 | "execution_count": 1, 16 | "metadata": { 17 | "dotnet_interactive": { 18 | "language": "csharp" 19 | }, 20 | "polyglot_notebook": { 21 | "kernelName": "csharp" 22 | } 23 | }, 24 | "outputs": [ 25 | { 26 | "data": { 27 | "text/html": [ 28 | "
Installed Packages
" 29 | ] 30 | }, 31 | "metadata": {}, 32 | "output_type": "display_data" 33 | }, 34 | { 35 | "data": { 36 | "text/plain": [ 37 | "Loading extensions from `C:\\Users\\sejsau\\.nuget\\packages\\revitinteractive\\0.1.0\\interactive-extensions\\dotnet\\Jowsy.DotNet.Interactive.Extensions.dll`" 38 | ] 39 | }, 40 | "metadata": {}, 41 | "output_type": "display_data" 42 | }, 43 | { 44 | "data": { 45 | "text/html": [ 46 | "
Revit C# Kernel Added.
" 47 | ] 48 | }, 49 | "metadata": {}, 50 | "output_type": "display_data" 51 | } 52 | ], 53 | "source": [ 54 | "#r \"nuget:RevitInteractive, 0.1.0\"" 55 | ] 56 | }, 57 | { 58 | "cell_type": "code", 59 | "execution_count": 2, 60 | "metadata": { 61 | "dotnet_interactive": { 62 | "language": "csharp" 63 | }, 64 | "polyglot_notebook": { 65 | "kernelName": "csharp" 66 | } 67 | }, 68 | "outputs": [ 69 | { 70 | "data": { 71 | "text/plain": [ 72 | "Kernel added: #!revit24" 73 | ] 74 | }, 75 | "metadata": {}, 76 | "output_type": "display_data" 77 | } 78 | ], 79 | "source": [ 80 | "#!connect revit --kernel-name revit24 --revit-version 2024" 81 | ] 82 | }, 83 | { 84 | "cell_type": "markdown", 85 | "metadata": {}, 86 | "source": [ 87 | "We will query the revit database for elements and group them by category." 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 4, 93 | "metadata": { 94 | "dotnet_interactive": { 95 | "language": "csharp" 96 | }, 97 | "polyglot_notebook": { 98 | "kernelName": "csharp" 99 | } 100 | }, 101 | "outputs": [ 102 | { 103 | "data": { 104 | "text/html": [ 105 | "
indexvalue
0
{ Id = Cameras, Count = 35 }
Id
Cameras
Count
35
1
{ Id = Walls, Count = 1120 }
Id
Walls
Count
1120
2
{ Id = Stairs, Count = 27 }
Id
Stairs
Count
27
3
{ Id = Runs, Count = 43 }
Id
Runs
Count
43
4
{ Id = Supports, Count = 1151 }
Id
Supports
Count
1151
5
{ Id = Landings, Count = 17 }
Id
Landings
Count
17
6
{ Id = Railings, Count = 131 }
Id
Railings
Count
131
7
{ Id = Handrails, Count = 121 }
Id
Handrails
Count
121
8
{ Id = Multistory Stairs, Count = 3 }
Id
Multistory Stairs
Count
3
9
{ Id = Top Rails, Count = 91 }
Id
Top Rails
Count
91
10
{ Id = Floors, Count = 176 }
Id
Floors
Count
176
11
{ Id = Slab Edges, Count = 59 }
Id
Slab Edges
Count
59
12
{ Id = Doors, Count = 141 }
Id
Doors
Count
141
13
{ Id = Divisions, Count = 10 }
Id
Divisions
Count
10
14
{ Id = Vertical Circulation, Count = 2 }
Id
Vertical Circulation
Count
2
15
{ Id = Specialty Equipment, Count = 288 }
Id
Specialty Equipment
Count
288
16
{ Id = Furniture, Count = 168 }
Id
Furniture
Count
168
17
{ Id = Rectangular Straight Wall Opening, Count = 9 }
Id
Rectangular Straight Wall Opening
Count
9
18
{ Id = Roofs, Count = 20 }
Id
Roofs
Count
20
19
{ Id = Parking, Count = 20 }
Id
Parking
Count
20
(21 more)
" 137 | ] 138 | }, 139 | "metadata": {}, 140 | "output_type": "display_data" 141 | } 142 | ], 143 | "source": [ 144 | "#!revit24 \n", 145 | "var collector = new FilteredElementCollector( doc, uidoc.ActiveView.Id);\n", 146 | "\n", 147 | "var query = collector\n", 148 | " .WhereElementIsNotElementType()\n", 149 | " .WhereElementIsViewIndependent()\n", 150 | " .ToElements();\n", 151 | "\n", 152 | "var result = query.GroupBy(x => x.Category.Name).Select(y => new {\n", 153 | " Id = y.Key,\n", 154 | " Count = y.Count()\n", 155 | "}).ToList();\n", 156 | "\n", 157 | "display(result);\n", 158 | "result" 159 | ] 160 | }, 161 | { 162 | "cell_type": "markdown", 163 | "metadata": {}, 164 | "source": [ 165 | "The result variable does only exist in memory in the revit kernel.\n", 166 | "If we want to use it in a F#-kernel we need to use the #!set directive, as seen below:" 167 | ] 168 | }, 169 | { 170 | "cell_type": "code", 171 | "execution_count": 5, 172 | "metadata": { 173 | "dotnet_interactive": { 174 | "language": "fsharp" 175 | }, 176 | "polyglot_notebook": { 177 | "kernelName": "fsharp" 178 | } 179 | }, 180 | "outputs": [], 181 | "source": [ 182 | "#!set --value @revit24:result --name result" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "If you look in the notebook's variable view you will notice that a result-variables of type System.Text.Json. NET Interactive will serialize all complex object to json when exchanging between kernels that is not running in the same process.\n", 190 | "\n", 191 | "We can now use Plotly to print a chart." 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "execution_count": 6, 197 | "metadata": { 198 | "dotnet_interactive": { 199 | "language": "fsharp" 200 | }, 201 | "polyglot_notebook": { 202 | "kernelName": "fsharp" 203 | } 204 | }, 205 | "outputs": [ 206 | { 207 | "data": { 208 | "text/html": [ 209 | "
Installed Packages
" 210 | ] 211 | }, 212 | "metadata": {}, 213 | "output_type": "display_data" 214 | }, 215 | { 216 | "data": { 217 | "text/plain": [ 218 | "Loading extensions from `C:\\Users\\sejsau\\.nuget\\packages\\plotly.net.interactive\\4.2.0\\lib\\netstandard2.1\\Plotly.NET.Interactive.dll`" 219 | ] 220 | }, 221 | "metadata": {}, 222 | "output_type": "display_data" 223 | } 224 | ], 225 | "source": [ 226 | "#r \"nuget: FSharp.Data, 6.3.0\"\n", 227 | "#r \"nuget: Plotly.NET, 4.2.0\"\n", 228 | "#r \"nuget: Plotly.NET.Interactive, 4.2.0\"" 229 | ] 230 | }, 231 | { 232 | "cell_type": "code", 233 | "execution_count": 7, 234 | "metadata": { 235 | "dotnet_interactive": { 236 | "language": "fsharp" 237 | }, 238 | "polyglot_notebook": { 239 | "kernelName": "fsharp" 240 | } 241 | }, 242 | "outputs": [ 243 | { 244 | "data": { 245 | "text/html": [ 246 | "
" 269 | ] 270 | }, 271 | "metadata": {}, 272 | "output_type": "display_data" 273 | } 274 | ], 275 | "source": [ 276 | "open Plotly.NET\n", 277 | "open FSharp.Data\n", 278 | "open FSharp.Data.JsonExtensions\n", 279 | "\n", 280 | "let data = JsonValue.Parse(result.RootElement.GetRawText());\n", 281 | "let xData = data.AsArray() |> Seq.map (fun item -> item.GetProperty(\"Id\").AsString())\n", 282 | "let yData = data.AsArray() |> Seq.map (fun item -> item.GetProperty(\"Count\").AsInteger())\n", 283 | "let chart =\n", 284 | " Chart.Column(yData, xData)\n", 285 | " |> Chart.withTitle \"Number of Instances by Category\"\n", 286 | "\n", 287 | "chart" 288 | ] 289 | } 290 | ], 291 | "metadata": { 292 | "kernelspec": { 293 | "display_name": ".NET (C#)", 294 | "language": "C#", 295 | "name": ".net-csharp" 296 | }, 297 | "language_info": { 298 | "name": "polyglot-notebook" 299 | }, 300 | "polyglot_notebook": { 301 | "kernelInfo": { 302 | "defaultKernelName": "csharp", 303 | "items": [ 304 | { 305 | "aliases": [], 306 | "name": "csharp" 307 | } 308 | ] 309 | } 310 | } 311 | }, 312 | "nbformat": 4, 313 | "nbformat_minor": 2 314 | } 315 | -------------------------------------------------------------------------------- /samples/GIS/GIS Visualization Building Footprint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# GIS Visualization of Building Footprint\n", 8 | "This notebook shows how to use C# and Python to visualize a building on a map.\n", 9 | "Make sure the model has a survey coordinate system defined with real world coordinates.\n", 10 | "\n", 11 | "Installation of Jupyter is required. See [here](https://github.com/dotnet/interactive/blob/main/docs/jupyter-in-polyglot-notebooks.md) for options.\n", 12 | "\n", 13 | "All python-dependencies are stored in requirements.txt.\n", 14 | "Create a python cell and run below to install:\n", 15 | "> pip install -r requirements.txt\n", 16 | "\n", 17 | "Run below command to connect to a Python-kernel:" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 1, 23 | "metadata": { 24 | "dotnet_interactive": { 25 | "language": "csharp" 26 | }, 27 | "polyglot_notebook": { 28 | "kernelName": "csharp" 29 | } 30 | }, 31 | "outputs": [ 32 | { 33 | "data": { 34 | "text/markdown": [ 35 | "The `#!connect jupyter` feature is in preview. Please report any feedback or issues at https://github.com/dotnet/interactive/issues/new/choose." 36 | ] 37 | }, 38 | "metadata": {}, 39 | "output_type": "display_data" 40 | }, 41 | { 42 | "data": { 43 | "text/plain": [ 44 | "Kernel added: #!pythonkernel" 45 | ] 46 | }, 47 | "metadata": {}, 48 | "output_type": "display_data" 49 | } 50 | ], 51 | "source": [ 52 | "#!connect jupyter --kernel-name pythonkernel --kernel-spec python3" 53 | ] 54 | }, 55 | { 56 | "cell_type": "markdown", 57 | "metadata": {}, 58 | "source": [ 59 | "Once connected, we setup connection to a revit interactive kernel. Run below:" 60 | ] 61 | }, 62 | { 63 | "cell_type": "code", 64 | "execution_count": 2, 65 | "metadata": { 66 | "dotnet_interactive": { 67 | "language": "pythonkernel" 68 | }, 69 | "polyglot_notebook": { 70 | "kernelName": "pythonkernel" 71 | } 72 | }, 73 | "outputs": [ 74 | { 75 | "name": "stdout", 76 | "output_type": "stream", 77 | "text": [ 78 | "Requirement already satisfied: geopandas in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from -r requirements.txt (line 1)) (0.14.3)\n", 79 | "Requirement already satisfied: shapely in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from -r requirements.txt (line 2)) (2.0.2)\n", 80 | "Requirement already satisfied: fiona in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from -r requirements.txt (line 3)) (1.9.5)\n", 81 | "Requirement already satisfied: numpy in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from -r requirements.txt (line 4)) (1.26.3)\n", 82 | "Requirement already satisfied: packaging in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from geopandas->-r requirements.txt (line 1)) (23.2)\n", 83 | "Requirement already satisfied: pandas>=1.4.0 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from geopandas->-r requirements.txt (line 1)) (2.2.0)\n", 84 | "Requirement already satisfied: pyproj>=3.3.0 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from geopandas->-r requirements.txt (line 1)) (3.6.1)\n", 85 | "Requirement already satisfied: attrs>=19.2.0 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (23.2.0)\n", 86 | "Requirement already satisfied: certifi in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (2023.11.17)\n", 87 | "Requirement already satisfied: click~=8.0 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (8.1.7)\n", 88 | "Requirement already satisfied: click-plugins>=1.0 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (1.1.1)\n", 89 | "Requirement already satisfied: cligj>=0.5 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (0.7.2)\n", 90 | "Requirement already satisfied: six in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (1.16.0)\n", 91 | "Requirement already satisfied: setuptools in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from fiona->-r requirements.txt (line 3)) (65.5.0)\n", 92 | "Requirement already satisfied: colorama in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from click~=8.0->fiona->-r requirements.txt (line 3)) (0.4.6)\n", 93 | "Requirement already satisfied: python-dateutil>=2.8.2 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from pandas>=1.4.0->geopandas->-r requirements.txt (line 1)) (2.8.2)\n", 94 | "Requirement already satisfied: pytz>=2020.1 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from pandas>=1.4.0->geopandas->-r requirements.txt (line 1)) (2023.4)\n", 95 | "Requirement already satisfied: tzdata>=2022.7 in c:\\users\\sejsau\\appdata\\local\\programs\\python\\python311\\lib\\site-packages (from pandas>=1.4.0->geopandas->-r requirements.txt (line 1)) (2023.4)\n", 96 | "Note: you may need to restart the kernel to use updated packages.\n" 97 | ] 98 | }, 99 | { 100 | "name": "stderr", 101 | "output_type": "stream", 102 | "text": [ 103 | "\n", 104 | "[notice] A new release of pip is available: 23.2.1 -> 24.0\n", 105 | "[notice] To update, run: python.exe -m pip install --upgrade pip\n" 106 | ] 107 | } 108 | ], 109 | "source": [ 110 | "pip install -r requirements.txt" 111 | ] 112 | }, 113 | { 114 | "cell_type": "code", 115 | "execution_count": null, 116 | "metadata": { 117 | "dotnet_interactive": { 118 | "language": "csharp" 119 | }, 120 | "polyglot_notebook": { 121 | "kernelName": "csharp" 122 | } 123 | }, 124 | "outputs": [], 125 | "source": [ 126 | "#r \"nuget:RevitInteractive\"" 127 | ] 128 | }, 129 | { 130 | "cell_type": "code", 131 | "execution_count": null, 132 | "metadata": { 133 | "dotnet_interactive": { 134 | "language": "csharp" 135 | }, 136 | "polyglot_notebook": { 137 | "kernelName": "csharp" 138 | } 139 | }, 140 | "outputs": [], 141 | "source": [ 142 | "#!connect revit --kernel-name revit --revit-version 2024" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": null, 148 | "metadata": { 149 | "dotnet_interactive": { 150 | "language": "csharp" 151 | }, 152 | "polyglot_notebook": { 153 | "kernelName": "csharp" 154 | } 155 | }, 156 | "outputs": [], 157 | "source": [ 158 | "#r \"nuget:Revit.RevitApi.x64, 2023.0.0\"\n", 159 | "#r \"nuget:Revit.RevitApiUi.x64, 2023.0.0\"\n", 160 | "using Autodesk.Revit.DB;\n", 161 | "using Autodesk.Revit.UI;" 162 | ] 163 | }, 164 | { 165 | "cell_type": "markdown", 166 | "metadata": {}, 167 | "source": [ 168 | " 2 standard systems of units exists: the international one (SI), also known as the metric system, and the imperial system.\n", 169 | " Internally, Revit stores in the imperial system." 170 | ] 171 | }, 172 | { 173 | "cell_type": "code", 174 | "execution_count": null, 175 | "metadata": { 176 | "dotnet_interactive": { 177 | "language": "csharp" 178 | }, 179 | "polyglot_notebook": { 180 | "kernelName": "csharp" 181 | } 182 | }, 183 | "outputs": [], 184 | "source": [ 185 | "#!revit\n", 186 | "// Assume 'doc' is the current Revit Document you are working with\n", 187 | "Units units = doc.GetUnits();\n", 188 | "// Get the FormatOptions for a specific UnitType (e.g., Length)\n", 189 | "FormatOptions lengthFormatOptions = units.GetFormatOptions(SpecTypeId.Length);\n", 190 | "\n", 191 | "// Get the ForgeTypeId for the Length unit\n", 192 | "ForgeTypeId lengthUnitTypeId = lengthFormatOptions.GetUnitTypeId();\n", 193 | "\n", 194 | "// Output the ForgeTypeId to the console or use it as needed\n", 195 | "lengthUnitTypeId" 196 | ] 197 | }, 198 | { 199 | "cell_type": "markdown", 200 | "metadata": {}, 201 | "source": [ 202 | "## Shared Coordinates\n", 203 | "\n", 204 | "The project location defines the offset from the site location and mesasures in decimal feet.\n", 205 | "\n", 206 | "For each location there are two basepoints: project base boint and survey point.\n", 207 | "\n", 208 | "We will use the currently active project location. " 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": { 215 | "dotnet_interactive": { 216 | "language": "csharp" 217 | }, 218 | "polyglot_notebook": { 219 | "kernelName": "csharp" 220 | } 221 | }, 222 | "outputs": [], 223 | "source": [ 224 | "#!revit\n", 225 | "var projectBasePoint = new FilteredElementCollector(doc).OfClass(typeof(BasePoint))\n", 226 | " .Cast()\n", 227 | " .Where(x => x.IsShared!)\n", 228 | " .First();\n", 229 | "\n", 230 | "var surveyPoint = new FilteredElementCollector(doc).OfClass(typeof(BasePoint))\n", 231 | " .Cast()\n", 232 | " .Where(x => x.IsShared)\n", 233 | " .First();\n", 234 | "\n", 235 | "\n", 236 | "string prompt1 = \"Displays survey and base point in the active ProjectLocation:\\n\";\n", 237 | "prompt1 += \"\\n\\t\" + $\"Survey point:{surveyPoint.SharedPosition}\";\n", 238 | "prompt1 += \"\\n\\t\" + $\"Project base point:{projectBasePoint.SharedPosition}\";\n", 239 | "display(prompt1);\n", 240 | "\n", 241 | "\n", 242 | "string prompt2 = \"Displays survey and base point in internal coordinate system:\\n\";\n", 243 | "prompt2 += \"\\n\\t\" + $\"Survey point:{surveyPoint.Position}\";\n", 244 | "prompt2 += \"\\n\\t\" + $\"Project base point:{projectBasePoint.Position}\";\n", 245 | "display(prompt2);\n", 246 | "\n", 247 | "ProjectLocation projectLocation = doc.ActiveProjectLocation;\n", 248 | "\n", 249 | "ProjectPosition projectPosition = projectLocation.GetProjectPosition(new XYZ(0,0,0));\n", 250 | "\n", 251 | "projectPosition" 252 | ] 253 | }, 254 | { 255 | "cell_type": "code", 256 | "execution_count": null, 257 | "metadata": { 258 | "dotnet_interactive": { 259 | "language": "csharp" 260 | }, 261 | "polyglot_notebook": { 262 | "kernelName": "csharp" 263 | } 264 | }, 265 | "outputs": [], 266 | "source": [ 267 | "\n", 268 | "#!revit\n", 269 | "\n", 270 | "var convertToDisplayUnits = (double valueInFeet) => UnitUtils.ConvertFromInternalUnits(valueInFeet, lengthUnitTypeId);\n", 271 | "\n", 272 | "// Format the prompt string to show the message.\n", 273 | "String prompt = \"Current project location information converted to display units:\\n\";\n", 274 | "prompt += \"\\n\\t\" + \"Origin point position:\";\n", 275 | "prompt += \"\\n\\t\\t\" + \"Angle: \" + projectPosition.Angle;\n", 276 | "prompt += \"\\n\\t\\t\" + \"East to West offset: \" + convertToDisplayUnits(projectPosition.EastWest); //The offset of the project location from the site location in the east/west direction. Measured in decimal feet.\n", 277 | "prompt += \"\\n\\t\\t\" + \"Elevation: \" + convertToDisplayUnits(projectPosition.Elevation);\n", 278 | "prompt += \"\\n\\t\\t\" + \"North to South offset: \" + convertToDisplayUnits(projectPosition.NorthSouth);\n", 279 | "display(prompt);\n", 280 | "\n", 281 | "\n", 282 | "XYZ translationVector = new XYZ(\n", 283 | " projectPosition.EastWest,\n", 284 | " projectPosition.NorthSouth,\n", 285 | " projectPosition.Elevation);\n", 286 | "\n", 287 | "Transform translationTransform\n", 288 | " = Transform.CreateTranslation(\n", 289 | " translationVector);\n", 290 | "\n", 291 | "// Create a rotation for the angle about true north\n", 292 | "\n", 293 | "Transform rotationTransform\n", 294 | " = Transform.CreateRotationAtPoint(XYZ.BasisZ, projectPosition.Angle, XYZ.Zero);\n", 295 | "// Combine the transforms \n", 296 | "\n", 297 | "Transform finalTransform\n", 298 | " = translationTransform.Multiply(\n", 299 | " rotationTransform);\n", 300 | "\n", 301 | "var internalToSurveyTransform = finalTransform;\n", 302 | "\n", 303 | "\n", 304 | "display(internalToSurveyTransform.OfPoint(new XYZ(0,0,0)));\n", 305 | "// Use the inverse of the survey point transform to convert the point from internal to survey coordinates\n", 306 | "internalToSurveyTransform" 307 | ] 308 | }, 309 | { 310 | "cell_type": "markdown", 311 | "metadata": {}, 312 | "source": [ 313 | "Filter all floors in active view. If you leave active view out in the collector, it will search the entire database.\n", 314 | "Also collect solids of the floors and return the list. " 315 | ] 316 | }, 317 | { 318 | "cell_type": "code", 319 | "execution_count": null, 320 | "metadata": { 321 | "dotnet_interactive": { 322 | "language": "csharp" 323 | }, 324 | "polyglot_notebook": { 325 | "kernelName": "csharp" 326 | } 327 | }, 328 | "outputs": [], 329 | "source": [ 330 | "#!revit\n", 331 | "\n", 332 | "\n", 333 | "var floorsInActiveView = new FilteredElementCollector(doc, doc.ActiveView.Id)\n", 334 | " .WhereElementIsNotElementType()\n", 335 | " .WhereElementIsViewIndependent()\n", 336 | " .OfClass(typeof(Floor))\n", 337 | " .OfType();\n", 338 | "\n", 339 | "\n", 340 | "/*\n", 341 | "var allFloors = new FilteredElementCollector(doc)\n", 342 | " .WhereElementIsNotElementType()\n", 343 | " .WhereElementIsViewIndependent()\n", 344 | " .OfClass(typeof(Floor))\n", 345 | " .OfType();\n", 346 | "\n", 347 | "\n", 348 | "var floorsNotInActiveView = allFloors.Where(f => !floorsInActiveView.Any(x => x.Id.IntegerValue == f.Id.IntegerValue));\n", 349 | "*/\n", 350 | "\n", 351 | "\n", 352 | "//display($\"Floors not in active view: {floorsNotInActiveView.Count()}\");\n", 353 | "//display(floorsNotInActiveView.First().Id);\n", 354 | "\n", 355 | "List solids = new List();\n", 356 | "foreach (var floor in floorsInActiveView){\n", 357 | "\n", 358 | " GeometryElement geometry = floor.get_Geometry(new Options());\n", 359 | "\n", 360 | " foreach(var g in geometry){\n", 361 | "\n", 362 | " Solid solid = g as Solid;\n", 363 | "\n", 364 | " if (solid != null){\n", 365 | " solids.Add(solid);\n", 366 | " }\n", 367 | "\n", 368 | " }\n", 369 | "\n", 370 | "}\n", 371 | "display(solids.Count());\n", 372 | "solids\n", 373 | " " 374 | ] 375 | }, 376 | { 377 | "cell_type": "markdown", 378 | "metadata": {}, 379 | "source": [ 380 | "In each solid, we pick the lowest face." 381 | ] 382 | }, 383 | { 384 | "cell_type": "code", 385 | "execution_count": null, 386 | "metadata": { 387 | "dotnet_interactive": { 388 | "language": "csharp" 389 | }, 390 | "polyglot_notebook": { 391 | "kernelName": "csharp" 392 | } 393 | }, 394 | "outputs": [], 395 | "source": [ 396 | "#!revit\n", 397 | "\n", 398 | "List planarFaces = new List();\n", 399 | "foreach (var solid in solids){\n", 400 | "\n", 401 | "PlanarFace lowest = null;\n", 402 | " FaceArray faceArray = solid.Faces;\n", 403 | " foreach( Face f in faceArray )\n", 404 | " {\n", 405 | " PlanarFace pf = f as PlanarFace;\n", 406 | " if( null != pf && (pf.FaceNormal.Z==1.0 || pf.FaceNormal.Z==-1.0))\n", 407 | " {\n", 408 | " if( ( null == lowest )\n", 409 | " || ( pf.Origin.Z < lowest.Origin.Z ) )\n", 410 | " {\n", 411 | " lowest = pf;\n", 412 | " }\n", 413 | " }\n", 414 | " }\n", 415 | "planarFaces.Add(lowest);\n", 416 | "\n", 417 | "}\n", 418 | "planarFaces" 419 | ] 420 | }, 421 | { 422 | "cell_type": "markdown", 423 | "metadata": {}, 424 | "source": [ 425 | "Because the transform will give us coordinates in imperial units we need to convert to display settings if metric system is used." 426 | ] 427 | }, 428 | { 429 | "cell_type": "code", 430 | "execution_count": null, 431 | "metadata": { 432 | "dotnet_interactive": { 433 | "language": "csharp" 434 | }, 435 | "polyglot_notebook": { 436 | "kernelName": "csharp" 437 | } 438 | }, 439 | "outputs": [], 440 | "source": [ 441 | "#!revit\n", 442 | "\n", 443 | "var convertToDisplayUnits = (XYZ pt) => new XYZ(UnitUtils.ConvertFromInternalUnits(pt.X, lengthUnitTypeId),\n", 444 | " UnitUtils.ConvertFromInternalUnits(pt.Y, lengthUnitTypeId),\n", 445 | " UnitUtils.ConvertFromInternalUnits(pt.Z, lengthUnitTypeId));\n", 446 | "\n", 447 | "List> polygons = new List>();\n", 448 | "\n", 449 | "display(planarFaces.Count());\n", 450 | "\n", 451 | "foreach (PlanarFace f in planarFaces){\n", 452 | "//display(f.EdgeLoops);\n", 453 | " if (f!=null){\n", 454 | " var loops = f.EdgeLoops;\n", 455 | " foreach (EdgeArray loop in loops)\n", 456 | " {\n", 457 | " // Select many flattens the list of lists of points into a single list of points\n", 458 | " List vertices = loop.Cast()\n", 459 | " .SelectMany(e => e.Tessellate(), (e, point) => convertToDisplayUnits(internalToSurveyTransform.OfPoint(point)) )\n", 460 | " .ToList();\n", 461 | "\n", 462 | " // Remove last point if it's a duplicate of the first (closed loop assumption)\n", 463 | " if (vertices.First().IsAlmostEqualTo(vertices.Last()))\n", 464 | " {\n", 465 | " vertices.RemoveAt(vertices.Count - 1);\n", 466 | " }\n", 467 | "\n", 468 | " polygons.Add(vertices);\n", 469 | " }\n", 470 | " }\n", 471 | "}\n", 472 | "\n", 473 | "polygons\n" 474 | ] 475 | }, 476 | { 477 | "cell_type": "markdown", 478 | "metadata": {}, 479 | "source": [ 480 | "Cell below sends the polygon data to the python-kernel for processing." 481 | ] 482 | }, 483 | { 484 | "cell_type": "code", 485 | "execution_count": null, 486 | "metadata": { 487 | "dotnet_interactive": { 488 | "language": "pythonkernel" 489 | }, 490 | "polyglot_notebook": { 491 | "kernelName": "pythonkernel" 492 | } 493 | }, 494 | "outputs": [], 495 | "source": [ 496 | "#!set --value @revit:polygons --name polygons" 497 | ] 498 | }, 499 | { 500 | "cell_type": "markdown", 501 | "metadata": {}, 502 | "source": [ 503 | "Extract the XY-coordinates and build polygons. Order it by area and pick the largest one. " 504 | ] 505 | }, 506 | { 507 | "cell_type": "code", 508 | "execution_count": null, 509 | "metadata": { 510 | "dotnet_interactive": { 511 | "language": "pythonkernel" 512 | }, 513 | "polyglot_notebook": { 514 | "kernelName": "pythonkernel" 515 | } 516 | }, 517 | "outputs": [], 518 | "source": [ 519 | "import shapely\n", 520 | "from shapely.geometry import Point, Polygon, LineString, GeometryCollection\n", 521 | "from shapely.ops import unary_union\n", 522 | "import numpy as np\n", 523 | "\n", 524 | "coords = list(map(lambda p: list(map(lambda pt: (pt[\"X\"],pt[\"Y\"]),p) ),polygons))\n", 525 | "\n", 526 | "_polygons = list(map(lambda coords: Polygon(coords),coords))\n", 527 | "_sorted = sorted(_polygons, key=lambda p: p.area, reverse=True)\n", 528 | "footprint = _sorted[0]" 529 | ] 530 | }, 531 | { 532 | "cell_type": "markdown", 533 | "metadata": {}, 534 | "source": [ 535 | "Execute cell below to produce a shape-file in same directory as this notebook.\n", 536 | "\n", 537 | "You can use QGIS or other GIS-software to visualize the shape on a map." 538 | ] 539 | }, 540 | { 541 | "cell_type": "code", 542 | "execution_count": null, 543 | "metadata": { 544 | "dotnet_interactive": { 545 | "language": "pythonkernel" 546 | }, 547 | "polyglot_notebook": { 548 | "kernelName": "pythonkernel" 549 | } 550 | }, 551 | "outputs": [], 552 | "source": [ 553 | "import geopandas as gpd\n", 554 | "from shapely.geometry import Point, Polygon\n", 555 | "from fiona.crs import from_epsg\n", 556 | "\n", 557 | "# Create an empty geopandas GeoDataFrame\n", 558 | "newdata = gpd.GeoDataFrame()\n", 559 | "\n", 560 | "# Insert the polygon into 'geometry' -column at index 0\n", 561 | "newdata.loc[0, 'geometry'] = footprint\n", 562 | "\n", 563 | "# Set the GeoDataFrame's coordinate system to WGS84 (i.e. epsg code 4326)\n", 564 | "# https://spatialreference.org/ref/epsg/2272/\n", 565 | "#newdata.crs = from_epsg(3007)\n", 566 | "newdata.crs = from_epsg(2272) #Revit 2024 Snowdon Towers CS\n", 567 | "\n", 568 | "# Determine the output path for the Shapefile\n", 569 | "outfp = \"footprint.shp\"\n", 570 | "\n", 571 | "# Write the data into that Shapefile\n", 572 | "newdata.to_file(outfp)" 573 | ] 574 | } 575 | ], 576 | "metadata": { 577 | "kernelspec": { 578 | "display_name": ".NET (C#)", 579 | "language": "C#", 580 | "name": ".net-csharp" 581 | }, 582 | "language_info": { 583 | "name": "polyglot-notebook" 584 | }, 585 | "polyglot_notebook": { 586 | "kernelInfo": { 587 | "defaultKernelName": "csharp", 588 | "items": [ 589 | { 590 | "aliases": [], 591 | "name": "csharp" 592 | }, 593 | { 594 | "aliases": [], 595 | "languageName": "python", 596 | "name": "pythonkernel" 597 | } 598 | ] 599 | } 600 | } 601 | }, 602 | "nbformat": 4, 603 | "nbformat_minor": 2 604 | } 605 | -------------------------------------------------------------------------------- /samples/GIS/requirements.txt: -------------------------------------------------------------------------------- 1 | geopandas 2 | shapely 3 | fiona 4 | numpy -------------------------------------------------------------------------------- /samples/InProgress/ChatGPT-conversations.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Conversations with CHAT-GPT\n", 8 | "This notebook documents a collaboration with Chat-GTP where a question is asked and the AI responds with code which can be run live in revit." 9 | ] 10 | }, 11 | { 12 | "cell_type": "code", 13 | "execution_count": null, 14 | "metadata": { 15 | "dotnet_interactive": { 16 | "language": "csharp" 17 | }, 18 | "polyglot_notebook": { 19 | "kernelName": "csharp" 20 | } 21 | }, 22 | "outputs": [], 23 | "source": [ 24 | "#i \"C:\\git\\bim-net-interactive\\src\\Jowsy.DotNet.Interactive.Extensions\\bin\\Debug\"\n", 25 | "\n", 26 | "#r \"nuget:Jowsy.DotNet.Interactive.Extensions,0.1.0-alpha\"" 27 | ] 28 | }, 29 | { 30 | "cell_type": "code", 31 | "execution_count": null, 32 | "metadata": { 33 | "dotnet_interactive": { 34 | "language": "csharp" 35 | }, 36 | "polyglot_notebook": { 37 | "kernelName": "csharp" 38 | } 39 | }, 40 | "outputs": [], 41 | "source": [ 42 | "#!connect revit --kernel-name revit24 --revit-version 2024" 43 | ] 44 | }, 45 | { 46 | "cell_type": "markdown", 47 | "metadata": {}, 48 | "source": [ 49 | "**QUESTION**\n", 50 | "Let the user pick an element from the model i Autodesk Revit using the API and store the elementId in an variable named 'pickedWallId' and return it. Do not use functions, methods or classes and suppose that a variable named uidoc of type UIDocument already is defined. Return the pickedWallId-variable at the end on the final row." 51 | ] 52 | }, 53 | { 54 | "cell_type": "code", 55 | "execution_count": null, 56 | "metadata": { 57 | "dotnet_interactive": { 58 | "language": "csharp" 59 | }, 60 | "polyglot_notebook": { 61 | "kernelName": "csharp" 62 | } 63 | }, 64 | "outputs": [], 65 | "source": [ 66 | "#!revit24\n", 67 | "Reference pickedRef = null;\n", 68 | "ElementId pickedWallId = null;\n", 69 | "\n", 70 | "// Prompt the user to select a wall\n", 71 | "Autodesk.Revit.UI.Selection.Selection sel = uidoc.Selection;\n", 72 | "\n", 73 | "try\n", 74 | "{\n", 75 | " // Use the PickObject method to allow the user to select a wall\n", 76 | " pickedRef = sel.PickObject(Autodesk.Revit.UI.Selection.ObjectType.Element, \"Please select a wall.\");\n", 77 | " \n", 78 | " // Check if the reference picked is a wall\n", 79 | " if (pickedRef != null)\n", 80 | " {\n", 81 | " // Retrieve the ElementId from the picked reference\n", 82 | " pickedWallId = pickedRef.ElementId;\n", 83 | " \n", 84 | " // Use the pickedWallId for other operations as needed\n", 85 | " // For example, you could display it in a TaskDialog or use it elsewhere in your code\n", 86 | " }\n", 87 | "}\n", 88 | "catch (Autodesk.Revit.Exceptions.OperationCanceledException)\n", 89 | "{\n", 90 | " // The user pressed the ESC key or cancelled the pick operation\n", 91 | " // Handle cancellation or show a message if necessary\n", 92 | "}\n", 93 | "return pickedWallId;" 94 | ] 95 | }, 96 | { 97 | "cell_type": "markdown", 98 | "metadata": {}, 99 | "source": [ 100 | "**QUESTION**\n", 101 | "Please return all levels in the Revit model and store those in a list named 'levelsInModels'. Return the list at the end. Don't use functions or classes." 102 | ] 103 | }, 104 | { 105 | "cell_type": "code", 106 | "execution_count": null, 107 | "metadata": { 108 | "dotnet_interactive": { 109 | "language": "csharp" 110 | }, 111 | "polyglot_notebook": { 112 | "kernelName": "csharp" 113 | } 114 | }, 115 | "outputs": [], 116 | "source": [ 117 | "#!revit24\n", 118 | "// Assuming uidoc is an already defined instance of UIDocument.\n", 119 | "Document doc = uidoc.Document;\n", 120 | "\n", 121 | "// Create a new collector instance and collect all the Level elements.\n", 122 | "FilteredElementCollector collector = new FilteredElementCollector(doc);\n", 123 | "ICollection collectedLevels = collector.OfClass(typeof(Level)).ToElements();\n", 124 | "\n", 125 | "// Create a list to store the levels.\n", 126 | "List levelsInModel = new List();\n", 127 | "\n", 128 | "// Iterate through the collected elements and add them to the list of levels.\n", 129 | "foreach (Element element in collectedLevels)\n", 130 | "{\n", 131 | " Level level = element as Level;\n", 132 | " if (level != null)\n", 133 | " {\n", 134 | " levelsInModel.Add(level);\n", 135 | " }\n", 136 | "}\n", 137 | "\n", 138 | "// At this point, levelsInModel contains all the levels in the Revit document.\n", 139 | "// You can now return this list or use it as needed within your code.\n", 140 | "\n", 141 | "// If this code block is within a method that allows returning values, you might return the list like this:\n", 142 | "return levelsInModel;" 143 | ] 144 | }, 145 | { 146 | "cell_type": "markdown", 147 | "metadata": {}, 148 | "source": [ 149 | "**QUESTION**\n", 150 | "Check if the pickedWallId elementId points to an element of type wall and if it does, check if the wall stretches over several floors and if then display which floors. Use the display(arg1) method where arg1 is of type string to display the result." 151 | ] 152 | }, 153 | { 154 | "cell_type": "code", 155 | "execution_count": null, 156 | "metadata": { 157 | "dotnet_interactive": { 158 | "language": "csharp" 159 | }, 160 | "polyglot_notebook": { 161 | "kernelName": "csharp" 162 | } 163 | }, 164 | "outputs": [], 165 | "source": [ 166 | "#!revit24\n", 167 | "// Assuming uidocument is an already defined instance of UIDocument and pickedWallId is the ElementId of the picked wall.\n", 168 | "Document doc = uidoc.Document;\n", 169 | "\n", 170 | "// Retrieve the wall element from the ElementId.\n", 171 | "Element element = doc.GetElement(pickedWallId);\n", 172 | "\n", 173 | "// Check if the element is a wall.\n", 174 | "if (element is Wall wall)\n", 175 | "{\n", 176 | " // Get the location curve of the wall.\n", 177 | " LocationCurve locationCurve = wall.Location as LocationCurve;\n", 178 | " if (locationCurve != null)\n", 179 | " {\n", 180 | " // Determine the base elevation and the top elevation of the wall.\n", 181 | " double wallBaseElevation = locationCurve.Curve.GetEndPoint(0).Z;\n", 182 | " double wallHeight = wall.get_Parameter(BuiltInParameter.WALL_USER_HEIGHT_PARAM).AsDouble();\n", 183 | " double wallTopElevation = wallBaseElevation + wallHeight;\n", 184 | "\n", 185 | " // Create a new collector instance and collect all the Level elements.\n", 186 | " FilteredElementCollector collector = new FilteredElementCollector(doc);\n", 187 | " ICollection collectedLevels = collector.OfClass(typeof(Level)).ToElements();\n", 188 | "\n", 189 | " // Prepare a string to display which floors the wall intersects.\n", 190 | " System.Text.StringBuilder floorsIntersected = new System.Text.StringBuilder();\n", 191 | "\n", 192 | " // Iterate through the levels to check if the wall intersects them.\n", 193 | " foreach (Element lvl in collectedLevels)\n", 194 | " {\n", 195 | " Level level = lvl as Level;\n", 196 | " if (level != null)\n", 197 | " {\n", 198 | " double levelElevation = level.Elevation;\n", 199 | " if (levelElevation >= wallBaseElevation && levelElevation <= wallTopElevation)\n", 200 | " {\n", 201 | " // If the level is within the wall's stretch, add it to the string.\n", 202 | " if (floorsIntersected.Length > 0)\n", 203 | " {\n", 204 | " floorsIntersected.Append(\", \");\n", 205 | " }\n", 206 | " floorsIntersected.Append(level.Name);\n", 207 | " }\n", 208 | " }\n", 209 | " }\n", 210 | "\n", 211 | " // Display the results.\n", 212 | " if (floorsIntersected.Length > 0)\n", 213 | " {\n", 214 | " display($\"The wall intersects the following floors: {floorsIntersected.ToString()}\");\n", 215 | " }\n", 216 | " else\n", 217 | " {\n", 218 | " display(\"The wall does not stretch over several floors.\");\n", 219 | " }\n", 220 | " }\n", 221 | "}\n", 222 | "else\n", 223 | "{\n", 224 | " display(\"The selected element is not a wall.\");\n", 225 | "}" 226 | ] 227 | }, 228 | { 229 | "cell_type": "markdown", 230 | "metadata": {}, 231 | "source": [ 232 | "CORRECTIONS:\n", 233 | "For the moment there are a fixed number of namespaces and StringBuilder belongs to System.Text. Manually modified code to System.Text.StringBuilder." 234 | ] 235 | } 236 | ], 237 | "metadata": { 238 | "kernelspec": { 239 | "display_name": ".NET (C#)", 240 | "language": "C#", 241 | "name": ".net-csharp" 242 | }, 243 | "language_info": { 244 | "name": "polyglot-notebook" 245 | }, 246 | "polyglot_notebook": { 247 | "kernelInfo": { 248 | "defaultKernelName": "csharp", 249 | "items": [ 250 | { 251 | "aliases": [], 252 | "name": "csharp" 253 | } 254 | ] 255 | } 256 | } 257 | }, 258 | "nbformat": 4, 259 | "nbformat_minor": 2 260 | } 261 | -------------------------------------------------------------------------------- /samples/InProgress/Export-Ifc-Apply-Color-Using-IFCOpenShell.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# IFC Export" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": null, 13 | "metadata": { 14 | "dotnet_interactive": { 15 | "language": "csharp" 16 | }, 17 | "polyglot_notebook": { 18 | "kernelName": "csharp" 19 | } 20 | }, 21 | "outputs": [], 22 | "source": [ 23 | "#r \"nuget:Jowsy.DotNet.Interactive.Extensions\"\n", 24 | "\n", 25 | "#i \"C:\\git\\bim-net-interactive\\src\\Jowsy.DotNet.Interactive.Extensions\\bin\\Debug\"" 26 | ] 27 | }, 28 | { 29 | "cell_type": "code", 30 | "execution_count": null, 31 | "metadata": { 32 | "dotnet_interactive": { 33 | "language": "csharp" 34 | }, 35 | "polyglot_notebook": { 36 | "kernelName": "csharp" 37 | } 38 | }, 39 | "outputs": [], 40 | "source": [ 41 | "#!connect revit --kernel-name revit24 --revit-version 2024" 42 | ] 43 | }, 44 | { 45 | "cell_type": "code", 46 | "execution_count": null, 47 | "metadata": { 48 | "dotnet_interactive": { 49 | "language": "csharp" 50 | }, 51 | "polyglot_notebook": { 52 | "kernelName": "csharp" 53 | } 54 | }, 55 | "outputs": [], 56 | "source": [ 57 | "#r \"nuget:Revit.RevitApi.x64, 2023.0.0\"\n", 58 | "#r \"nuget:Revit.RevitApiUi.x64, 2023.0.0\"\n", 59 | "using Autodesk.Revit.DB;\n", 60 | "using Autodesk.Revit.UI;" 61 | ] 62 | }, 63 | { 64 | "cell_type": "code", 65 | "execution_count": null, 66 | "metadata": { 67 | "dotnet_interactive": { 68 | "language": "csharp" 69 | }, 70 | "polyglot_notebook": { 71 | "kernelName": "csharp" 72 | } 73 | }, 74 | "outputs": [], 75 | "source": [ 76 | "#!revit24\n", 77 | "\n", 78 | "BuiltInCategory builtInCategory = BuiltInCategory.OST_Walls;\n", 79 | "Category category = Category.GetCategory(doc, builtInCategory);\n", 80 | "ElementId categoryId = category.Id;\n", 81 | "\n", 82 | "var categoryOverrides = uidoc.ActiveView.GetCategoryOverrides(categoryId);\n", 83 | "var patternColor = categoryOverrides.SurfaceForegroundPatternColor;\n", 84 | "\n", 85 | "var color = new { red = patternColor.Red, \n", 86 | " blue = patternColor.Blue,\n", 87 | " green = patternColor.Green};\n", 88 | "\n", 89 | "display(categoryOverrides);\n", 90 | "color" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": { 97 | "dotnet_interactive": { 98 | "language": "csharp" 99 | }, 100 | "polyglot_notebook": { 101 | "kernelName": "csharp" 102 | } 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "#!set --value @revit24:color --name color" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": null, 112 | "metadata": { 113 | "dotnet_interactive": { 114 | "language": "csharp" 115 | }, 116 | "polyglot_notebook": { 117 | "kernelName": "csharp" 118 | } 119 | }, 120 | "outputs": [], 121 | "source": [ 122 | "#!revit24\n", 123 | "\n", 124 | "//Because the revit kernel is run out of process embedded in the revit application we need to use the set magic command\n", 125 | "// UIDocument uidoc = default;\n", 126 | "\n", 127 | " Document doc = uidoc.Document;\n", 128 | " \n", 129 | " // Set export options\n", 130 | " IFCExportOptions ifcExportOptions = new IFCExportOptions();\n", 131 | " \n", 132 | " // Optionally, you can customize export options further, e.g., set specific configurations.\n", 133 | " // ifcExportOptions.SpaceBoundaries = IFCExportSpaceBoundaries.Boundaries;\n", 134 | " // ifcExportOptions.SplitWallsAndColumns = true;\n", 135 | " // ...\n", 136 | "\n", 137 | " // Perform the export\n", 138 | " Transaction transaction = new Transaction(doc, \"Export IFC\");\n", 139 | " transaction.Start(\"start\");\n", 140 | "\n", 141 | " // Use the Export method of the Document class\n", 142 | "\n", 143 | " string fileName = \"c:\\\\playground\\\\house.ifc\";\n", 144 | "\n", 145 | " doc.Export(System.IO.Path.GetDirectoryName(fileName),System.IO.Path.GetFileName(fileName), ifcExportOptions);\n", 146 | "\n", 147 | " transaction.Commit();\n", 148 | "\n", 149 | " fileName" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": null, 155 | "metadata": { 156 | "dotnet_interactive": { 157 | "language": "csharp" 158 | }, 159 | "polyglot_notebook": { 160 | "kernelName": "csharp" 161 | } 162 | }, 163 | "outputs": [], 164 | "source": [ 165 | "#!connect jupyter --kernel-name pythonkernel --kernel-spec python3" 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "execution_count": null, 171 | "metadata": { 172 | "dotnet_interactive": { 173 | "language": "pythonkernel" 174 | }, 175 | "polyglot_notebook": { 176 | "kernelName": "pythonkernel" 177 | } 178 | }, 179 | "outputs": [], 180 | "source": [ 181 | "#!set --value @revit24:color --name color2" 182 | ] 183 | }, 184 | { 185 | "cell_type": "code", 186 | "execution_count": null, 187 | "metadata": { 188 | "dotnet_interactive": { 189 | "language": "pythonkernel" 190 | }, 191 | "polyglot_notebook": { 192 | "kernelName": "pythonkernel" 193 | } 194 | }, 195 | "outputs": [], 196 | "source": [ 197 | "pip install ifcopenshell" 198 | ] 199 | }, 200 | { 201 | "cell_type": "code", 202 | "execution_count": null, 203 | "metadata": { 204 | "dotnet_interactive": { 205 | "language": "pythonkernel" 206 | }, 207 | "polyglot_notebook": { 208 | "kernelName": "pythonkernel" 209 | } 210 | }, 211 | "outputs": [], 212 | "source": [ 213 | "pip install lark" 214 | ] 215 | }, 216 | { 217 | "cell_type": "code", 218 | "execution_count": null, 219 | "metadata": { 220 | "dotnet_interactive": { 221 | "language": "pythonkernel" 222 | }, 223 | "polyglot_notebook": { 224 | "kernelName": "pythonkernel" 225 | } 226 | }, 227 | "outputs": [], 228 | "source": [ 229 | "pip install numpy" 230 | ] 231 | }, 232 | { 233 | "cell_type": "code", 234 | "execution_count": null, 235 | "metadata": { 236 | "dotnet_interactive": { 237 | "language": "pythonkernel" 238 | }, 239 | "polyglot_notebook": { 240 | "kernelName": "pythonkernel" 241 | } 242 | }, 243 | "outputs": [], 244 | "source": [ 245 | "import ifcopenshell\n", 246 | "\n" 247 | ] 248 | }, 249 | { 250 | "cell_type": "markdown", 251 | "metadata": {}, 252 | "source": [ 253 | "Note that we try to find IfcSolidModel representations. A wall for example might contain a PolyLine and a IfcSweptSolid. IfcSweptSolid is a subtype of IfcSolidModel. To understand the relationship between IfcSweptSolid and IfcSolidModel, it's important to note that IFC uses an inheritance model where subtypes inherit properties and relationships from their supertypes.\n", 254 | "\n", 255 | "\n", 256 | "https://forums.buildingsmart.org/t/need-help-with-revit-2x3-ifc-export-trying-to-figure-out-how-the-colors-are-assigned/4461/4" 257 | ] 258 | }, 259 | { 260 | "cell_type": "code", 261 | "execution_count": null, 262 | "metadata": { 263 | "dotnet_interactive": { 264 | "language": "pythonkernel" 265 | }, 266 | "polyglot_notebook": { 267 | "kernelName": "pythonkernel" 268 | } 269 | }, 270 | "outputs": [], 271 | "source": [ 272 | "\n", 273 | "# Load an IFC file\n", 274 | "ifc_file = ifcopenshell.open('c:\\playground\\house.ifc')\n", 275 | "\n", 276 | "\n", 277 | "for element in ifc_file.by_type('IfcProduct'):\n", 278 | " # Check the category of the element, for example, by using the element's 'IfcType'\n", 279 | " element_type = element.is_a()\n", 280 | "\n", 281 | " if (element_type == \"IfcWallStandardCase\"):\n", 282 | "\n", 283 | " display(element_type)\n", 284 | " # Iterate over the wall's representations to apply the style\n", 285 | " for representation in element.Representation.Representations:\n", 286 | " \n", 287 | " for representation_item in representation.Items:\n", 288 | "\n", 289 | " # Check if the representation item is a subtype of IfcSolidModel\n", 290 | " if representation_item.is_a('IfcSolidModel'):\n", 291 | " \n", 292 | " color = ifc_file.createIfcColourRgb(None, color2['red'], color2['green'], color2['blue']) # This is a grey color\n", 293 | "\n", 294 | " #Create an IfcSurfaceStyleRendering that references the color\n", 295 | " surface_style_rendering = ifc_file.createIfcSurfaceStyleShading()\n", 296 | " surface_style_rendering.SurfaceColour = color \n", 297 | "\n", 298 | " # Create an IfcSurfaceStyle that holds the rendering\n", 299 | " surface_style = ifc_file.createIfcSurfaceStyle(\n", 300 | " color.Name,\n", 301 | " 'BOTH',\n", 302 | " (surface_style_rendering,)\n", 303 | " )\n", 304 | " \n", 305 | " presStyleAssign = ifc_file.createIfcPresentationStyleAssignment((surface_style,))\n", 306 | " \n", 307 | " #ifcopenshell..run(\"style.assign_representation_styles\", ifc, shape_representation=representation_item, styles=[presStyleAssign])\n", 308 | " # Create a new IfcStyledItem that associates the surface style to the wall's geometry\n", 309 | "\n", 310 | " #Important!! we need to remove existing styles if already defined by Revit\n", 311 | " for styledItem in representation_item.StyledByItem:\n", 312 | " ifc_file.remove(styledItem)\n", 313 | "\n", 314 | " \n", 315 | " ifc_file.createIfcStyledItem(representation_item,(presStyleAssign,),color.Name)\n", 316 | " \n", 317 | " display(representation_item.StyledByItem)\n", 318 | "\n", 319 | "\n", 320 | "ifc_file.write('c:\\playground\\path_to_modified_ifc_file5.ifc')" 321 | ] 322 | } 323 | ], 324 | "metadata": { 325 | "kernelspec": { 326 | "display_name": ".NET (C#)", 327 | "language": "C#", 328 | "name": ".net-csharp" 329 | }, 330 | "language_info": { 331 | "name": "polyglot-notebook" 332 | }, 333 | "polyglot_notebook": { 334 | "kernelInfo": { 335 | "defaultKernelName": "csharp", 336 | "items": [ 337 | { 338 | "aliases": [], 339 | "languageName": "csharp", 340 | "name": "csharp" 341 | }, 342 | { 343 | "aliases": [], 344 | "languageName": "python", 345 | "name": "pythonkernel" 346 | } 347 | ] 348 | } 349 | } 350 | }, 351 | "nbformat": 4, 352 | "nbformat_minor": 2 353 | } 354 | -------------------------------------------------------------------------------- /samples/InProgress/sketchplane.dib: -------------------------------------------------------------------------------- 1 | #!meta 2 | 3 | {"kernelInfo":{"defaultKernelName":"csharp","items":[{"aliases":[],"languageName":"csharp","name":"csharp"}]}} 4 | 5 | #!csharp 6 | 7 | #r "nuget:Jowsy.DotNet.Interactive.Extensions" 8 | 9 | #i "C:\git\bim-net-interactive\src\Jowsy.DotNet.Interactive.Extensions\bin\Debug" 10 | 11 | #!csharp 12 | 13 | #!connect revit --kernel-name revit24 --revit-version 2024 14 | 15 | #!csharp 16 | 17 | #r "nuget:Revit.RevitApi.x64, 2023.0.0" 18 | #r "nuget:Revit.RevitApiUi.x64, 2023.0.0" 19 | using Autodesk.Revit.DB; 20 | using Autodesk.Revit.UI; 21 | 22 | #!csharp 23 | 24 | #!revit24 25 | 26 | Document doc = uidoc.Document; 27 | 28 | SketchPlane sketchPlane = null; 29 | using (Transaction tx = new Transaction(doc)) 30 | { 31 | 32 | tx.Start("Create SketchPlane"); 33 | 34 | // Define a plane in Revit. For example, this will be the XY plane (normal is Z-axis). 35 | Plane geomPlane = Plane.CreateByNormalAndOrigin(XYZ.BasisZ, XYZ.Zero); 36 | 37 | // Create a SketchPlane using the Plane you've defined 38 | sketchPlane = SketchPlane.Create(doc, geomPlane); 39 | // Optionally, you may set the active sketch plane in the UI 40 | uidoc.ActiveView.SketchPlane = sketchPlane; 41 | uidoc.ActiveView.ShowActiveWorkPlane(); 42 | // Commit the transaction 43 | tx.Commit(); 44 | 45 | } 46 | display(sketchPlane); 47 | sketchPlane 48 | 49 | 50 | #!csharp 51 | 52 | #!revit24 53 | Document doc = uidoc.Document; 54 | ModelCurve curve = null; 55 | using (Transaction tx = new Transaction(doc)) 56 | { 57 | 58 | tx.Start("Create Model Line"); 59 | 60 | // Define start and end points for the line on the sketch plane 61 | XYZ startPoint = new XYZ(0, 0, 0); // Start point at the origin 62 | XYZ endPoint = new XYZ(10, 10, 0); // End point 10 feet in the X and Y direction 63 | 64 | // Create a line bound by the start and end points 65 | Line geomLine = Line.CreateBound(startPoint, endPoint); 66 | 67 | // Create a detail line using the created line and the sketch plane 68 | curve = doc.Create.NewModelCurve(geomLine, sketchPlane); 69 | 70 | // Commit the transaction 71 | tx.Commit(); 72 | } 73 | display(curve); 74 | curve 75 | 76 | #!csharp 77 | 78 | #!revit24 79 | 80 | Document doc = uidoc.Document; 81 | 82 | // Retrieve the wall type to use for the new wall 83 | WallType wallType = new FilteredElementCollector(doc) 84 | .OfClass(typeof(WallType)) 85 | .OfType() 86 | .FirstOrDefault(); // Specify the wall type name here 87 | 88 | display(wallType.Name); 89 | wallType 90 | 91 | #!csharp 92 | 93 | #!revit24 94 | 95 | // Define the level at which the wall should be placed 96 | Level level = new FilteredElementCollector(doc) 97 | .OfClass(typeof(Level)) 98 | .OfType() 99 | .FirstOrDefault(); 100 | display(level); 101 | level 102 | 103 | #!csharp 104 | 105 | 106 | #!revit24 107 | 108 | using (Transaction tx = new Transaction(doc)) 109 | { 110 | tx.Start("Create Wall"); 111 | 112 | Wall wall = Wall.Create(doc, curve.GeometryCurve, wallType.Id, level.Id, 10, 0, false, false); 113 | 114 | tx.Commit(); 115 | 116 | display(wall); 117 | } 118 | -------------------------------------------------------------------------------- /samples/example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/samples/example.gif -------------------------------------------------------------------------------- /samples/images/variables-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/samples/images/variables-view.png -------------------------------------------------------------------------------- /samples/system-prompts/revit-api-tutor.md: -------------------------------------------------------------------------------- 1 | # C# Revit API Tutor 2 | 3 | You are a Revit API C# Tutor AI and you are dedicated to helping users learn the Revit API. Provide clear explanations of Revit API concepts, syntax, and best practices. Guide users through the process of creating commands and queries. Don't use classes or functions. Write code for use in an C# interactive context. Assume an existence of variable "doc" of type Document, "uidoc" of type UIDocument and app of type UIApplication. DO NOT use ShowDialog for displaying message, use the display(object o). If the user ask you to print an element place the element object or objects directly in the display method. -------------------------------------------------------------------------------- /samples/system-prompts/revit-assistant.md: -------------------------------------------------------------------------------- 1 | # Revit Assistant 2 | 3 | You are a Revit API C# AI and you are dedicated to helping users control Revit using code. Skip explations and seek to optimize code. Don't use classes or functions. Write code for use in an C# interactive context. Assume an existence of variable "doc" of type Document, "uidoc" of type UIDocument and app of type UIApplication. DO NOT use ShowDialog for displaying message, use the display(string message). If the user ask you to select an element set selection in the revit ui using uidoc. -------------------------------------------------------------------------------- /src/.ipynb_checkpoints/Untitled-checkpoint.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [], 3 | "metadata": {}, 4 | "nbformat": 4, 5 | "nbformat_minor": 5 6 | } 7 | -------------------------------------------------------------------------------- /src/GetStarted.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Get started" 8 | ] 9 | }, 10 | { 11 | "cell_type": "markdown", 12 | "metadata": {}, 13 | "source": [ 14 | "## Install the Addin\n", 15 | "\n", 16 | "If you have Autodesk Revit open, close it. The addin need to register a dockable panel on startup!" 17 | ] 18 | }, 19 | { 20 | "cell_type": "code", 21 | "execution_count": null, 22 | "metadata": { 23 | "dotnet_interactive": { 24 | "language": "pwsh" 25 | }, 26 | "polyglot_notebook": { 27 | "kernelName": "pwsh" 28 | } 29 | }, 30 | "outputs": [], 31 | "source": [ 32 | "dotnet build bim-net-interactive.sln -c \"Debug R24\"" 33 | ] 34 | }, 35 | { 36 | "cell_type": "markdown", 37 | "metadata": {}, 38 | "source": [ 39 | "Open Revit and go to the \"Add-ins\" tab where a button \"Show Dockable Pane\" will let you show the kernel viewer." 40 | ] 41 | }, 42 | { 43 | "cell_type": "markdown", 44 | "metadata": {}, 45 | "source": [ 46 | "## Install VS Code Polyglot Notebook extension" 47 | ] 48 | }, 49 | { 50 | "cell_type": "code", 51 | "execution_count": null, 52 | "metadata": { 53 | "dotnet_interactive": { 54 | "language": "pwsh" 55 | }, 56 | "polyglot_notebook": { 57 | "kernelName": "pwsh" 58 | } 59 | }, 60 | "outputs": [], 61 | "source": [ 62 | "# 1. Build the project\n", 63 | "dotnet build Jowsy.DotNet.Interactive.Extensions\n", 64 | "\n", 65 | "# Clear any older versions of this extension package from your NuGet cache\n", 66 | "rm ~/.nuget/packages/Jowsy.DotNet.Interactive.Extensions -Force -Recurse -ErrorAction Ignore\n", 67 | "\n", 68 | "dotnet pack Jowsy.DotNet.Interactive.Extensions -c Debug /p:PackageVersion=1.0.0\n", 69 | "\n", 70 | "$nugets = Get-ChildItem -Recurse RevitInteractive*.nupkg\n", 71 | "\n", 72 | "$nugetPackage = $nugets[0].FullName" 73 | ] 74 | }, 75 | { 76 | "cell_type": "code", 77 | "execution_count": null, 78 | "metadata": { 79 | "dotnet_interactive": { 80 | "language": "csharp" 81 | }, 82 | "polyglot_notebook": { 83 | "kernelName": "csharp" 84 | } 85 | }, 86 | "outputs": [], 87 | "source": [ 88 | "#!set --value @pwsh:nugetPackage --name nugetPackage\n", 89 | "var nugetSource = System.IO.Path.GetDirectoryName(nugetPackage);\n", 90 | "nugetSource" 91 | ] 92 | }, 93 | { 94 | "cell_type": "code", 95 | "execution_count": null, 96 | "metadata": { 97 | "dotnet_interactive": { 98 | "language": "csharp" 99 | }, 100 | "polyglot_notebook": { 101 | "kernelName": "csharp" 102 | } 103 | }, 104 | "outputs": [], 105 | "source": [ 106 | "#r \"nuget:RevitInteractive,*-*\"\n", 107 | "\n", 108 | "#i @csharp:nugetSource" 109 | ] 110 | }, 111 | { 112 | "cell_type": "markdown", 113 | "metadata": {}, 114 | "source": [ 115 | "## Connecting to the Revit kernel\n", 116 | "We use the *#!connect* magic command to establish connection to the embedded revit kernel. Remember to start the kernel on the Revit side. Goto Addins->Net Interactive->Show Dockable Panel." 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": null, 122 | "metadata": { 123 | "dotnet_interactive": { 124 | "language": "csharp" 125 | }, 126 | "polyglot_notebook": { 127 | "kernelName": "csharp" 128 | } 129 | }, 130 | "outputs": [], 131 | "source": [ 132 | "#!connect revit --kernel-name revit24 --revit-version 2024" 133 | ] 134 | }, 135 | { 136 | "cell_type": "markdown", 137 | "metadata": {}, 138 | "source": [ 139 | "If connection is established global variables are created for the current UIApplication, UIDocument and Document. Variables are shown in the kernel viewer as seen below. Those variables can be referenced in code cells chosen to run on the Revit kernel.\n", 140 | "\n", 141 | "![](screenshot-variables.png)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "markdown", 146 | "metadata": {}, 147 | "source": [ 148 | "In order to get IntelliSense you can import Revit API references. For the moment IntelliSense is not supported when using the revit kernel. A workaround is to uncomment the connector directive to activate the csharp-kernel." 149 | ] 150 | }, 151 | { 152 | "cell_type": "code", 153 | "execution_count": null, 154 | "metadata": { 155 | "dotnet_interactive": { 156 | "language": "csharp" 157 | }, 158 | "polyglot_notebook": { 159 | "kernelName": "csharp" 160 | } 161 | }, 162 | "outputs": [], 163 | "source": [ 164 | "#r \"nuget:Revit.RevitApi.x64, 2023.0.0\"\n", 165 | "#r \"nuget:Revit.RevitApiUi.x64, 2023.0.0\"\n", 166 | "using Autodesk.Revit.DB;\n", 167 | "using Autodesk.Revit.UI;" 168 | ] 169 | }, 170 | { 171 | "cell_type": "markdown", 172 | "metadata": {}, 173 | "source": [ 174 | "## Querying the Revit Database\n", 175 | "Use display method to output the result from a query. \n", 176 | "\n", 177 | "NOTE: Objects of type Autodesk.DB.Elements are configured to be rendered as HTML. \n", 178 | "\n", 179 | "The formatter only recurse to a specific depth (default 3)." 180 | ] 181 | }, 182 | { 183 | "cell_type": "code", 184 | "execution_count": null, 185 | "metadata": { 186 | "dotnet_interactive": { 187 | "language": "csharp" 188 | }, 189 | "polyglot_notebook": { 190 | "kernelName": "csharp" 191 | } 192 | }, 193 | "outputs": [], 194 | "source": [ 195 | "#!revit24\n", 196 | " \n", 197 | " var query = new FilteredElementCollector(doc).OfClass(typeof(Wall))\n", 198 | " .Cast();\n", 199 | "\n", 200 | "display(query);" 201 | ] 202 | }, 203 | { 204 | "cell_type": "markdown", 205 | "metadata": {}, 206 | "source": [ 207 | "\n", 208 | "If you just return the variable it will be defined and shown in Variables in the kernel UI in Revit." 209 | ] 210 | }, 211 | { 212 | "cell_type": "code", 213 | "execution_count": null, 214 | "metadata": { 215 | "dotnet_interactive": { 216 | "language": "csharp" 217 | }, 218 | "polyglot_notebook": { 219 | "kernelName": "csharp" 220 | } 221 | }, 222 | "outputs": [], 223 | "source": [ 224 | "#!revit24\n", 225 | " \n", 226 | " var wall = new FilteredElementCollector(doc).OfClass(typeof(Wall))\n", 227 | " .Cast()\n", 228 | " .First();\n", 229 | "\n", 230 | "wall" 231 | ] 232 | }, 233 | { 234 | "cell_type": "markdown", 235 | "metadata": {}, 236 | "source": [ 237 | "Now you can reference wall in other cells." 238 | ] 239 | }, 240 | { 241 | "cell_type": "code", 242 | "execution_count": null, 243 | "metadata": { 244 | "dotnet_interactive": { 245 | "language": "csharp" 246 | }, 247 | "polyglot_notebook": { 248 | "kernelName": "csharp" 249 | } 250 | }, 251 | "outputs": [], 252 | "source": [ 253 | "#!revit24\n", 254 | "display(wall.Name);\n", 255 | "\n", 256 | "Level level = doc.GetElement(wall.LevelId) as Level;\n", 257 | "\n", 258 | "display(level.Name);" 259 | ] 260 | }, 261 | { 262 | "cell_type": "markdown", 263 | "metadata": {}, 264 | "source": [ 265 | "## Variable Sharing\n" 266 | ] 267 | }, 268 | { 269 | "cell_type": "markdown", 270 | "metadata": {}, 271 | "source": [ 272 | "The .NET Interactive kernel enables you to write code in multiple languages within a single notebook. The revit kernel is a C#-kernel but is run on a different process than the composite kernel. This means that we cannot reference a variable from the revit kernel directly but we need to use *#!set* magic command to send the value from the revit kernel to a another kernel. If you try to share a Autodesk Element object it will try to convert it to json but it will probably fail. Some simple types such as XYZ are serializable.\n", 273 | "\n", 274 | "In the example below we extract the wall profile and draws it live in the notebook using Hypar IO.\n", 275 | "\n", 276 | "**Run the cell below and then pick a wall in Revit!**" 277 | ] 278 | }, 279 | { 280 | "cell_type": "code", 281 | "execution_count": null, 282 | "metadata": { 283 | "dotnet_interactive": { 284 | "language": "csharp" 285 | }, 286 | "polyglot_notebook": { 287 | "kernelName": "csharp" 288 | } 289 | }, 290 | "outputs": [], 291 | "source": [ 292 | "//Adapted from https://thebuildingcoder.typepad.com/blog/2015/01/getting-the-wall-elevation-profile.html\n", 293 | "// Credit to Jeremy Tammik\n", 294 | "#!revit24\n", 295 | "\n", 296 | "var uiDoc = uiapp.ActiveUIDocument;\n", 297 | "Selection selection = uiDoc.Selection;\n", 298 | "\n", 299 | "Reference picked = selection.PickObject(ObjectType.Element, \"Pick a wall\");\n", 300 | "\n", 301 | "var selWall = doc.GetElement(picked) as Wall;\n", 302 | "\n", 303 | "\n", 304 | "// Get the external wall face for the profile\n", 305 | "IList sideFaces\n", 306 | " = HostObjectUtils.GetSideFaces(selWall,\n", 307 | " ShellLayerType.Exterior );\n", 308 | " \n", 309 | "Element e2 = doc.GetElement( sideFaces[0] );\n", 310 | "\n", 311 | "Face face = e2.GetGeometryObjectFromReference(\n", 312 | " sideFaces[0] ) as Face;\n", 313 | "\n", 314 | "IList curveLoops\n", 315 | " = face.GetEdgesAsCurveLoops();\n", 316 | "\n", 317 | "// ExporterIFCUtils class can also be used for \n", 318 | "// non-IFC purposes. The SortCurveLoops method \n", 319 | "// sorts curve loops (edge loops) so that the \n", 320 | "// outer loops come first.\n", 321 | "IList> curveLoopLoop\n", 322 | " = ExporterIFCUtils.SortCurveLoops(\n", 323 | " curveLoops );\n", 324 | "\n", 325 | "List> wallLines = new List>();\n", 326 | "\n", 327 | "foreach(var list in curveLoopLoop){\n", 328 | "\n", 329 | " foreach(var lines in list){\n", 330 | " var subList = new List();\n", 331 | " foreach(Curve curve in lines){\n", 332 | " Line line = curve as Line;\n", 333 | " if (line!=null){\n", 334 | " subList.Add(line);\n", 335 | " }\n", 336 | " }\n", 337 | " wallLines.Add(subList);\n", 338 | " }\n", 339 | "\n", 340 | "}\n", 341 | "\n", 342 | "display(curveLoopLoop);\n", 343 | "wallLines" 344 | ] 345 | }, 346 | { 347 | "cell_type": "markdown", 348 | "metadata": {}, 349 | "source": [ 350 | ".NET interactive will have some trouble serializing a Revit Curve to JSON but the XYZ is fine. Let's build a dto." 351 | ] 352 | }, 353 | { 354 | "cell_type": "code", 355 | "execution_count": null, 356 | "metadata": { 357 | "dotnet_interactive": { 358 | "language": "csharp" 359 | }, 360 | "polyglot_notebook": { 361 | "kernelName": "csharp" 362 | } 363 | }, 364 | "outputs": [], 365 | "source": [ 366 | "#!revit24\n", 367 | "var dto = wallLines.Where(l => l.Count > 0).Select(boundary => boundary.Select(ln => new { start = ln.Origin, end = ln.GetEndPoint(1)}).ToList());\n", 368 | "display(dto);\n", 369 | "dto" 370 | ] 371 | }, 372 | { 373 | "cell_type": "markdown", 374 | "metadata": {}, 375 | "source": [ 376 | "Use the #!set magic command to share the dto with the C#-kernel." 377 | ] 378 | }, 379 | { 380 | "cell_type": "code", 381 | "execution_count": null, 382 | "metadata": { 383 | "dotnet_interactive": { 384 | "language": "csharp" 385 | }, 386 | "polyglot_notebook": { 387 | "kernelName": "csharp" 388 | } 389 | }, 390 | "outputs": [], 391 | "source": [ 392 | "#!set --value @revit24:dto --name wallLines\n" 393 | ] 394 | }, 395 | { 396 | "cell_type": "markdown", 397 | "metadata": {}, 398 | "source": [ 399 | "Hypar Elements is a cross-platform library for creating building elements. It brings an interactive extension that enables us to show the geometry live in the notebook. " 400 | ] 401 | }, 402 | { 403 | "cell_type": "code", 404 | "execution_count": null, 405 | "metadata": { 406 | "dotnet_interactive": { 407 | "language": "csharp" 408 | }, 409 | "polyglot_notebook": { 410 | "kernelName": "csharp" 411 | } 412 | }, 413 | "outputs": [], 414 | "source": [ 415 | "#r \"nuget: Hypar.Elements, *-*\"" 416 | ] 417 | }, 418 | { 419 | "cell_type": "code", 420 | "execution_count": null, 421 | "metadata": { 422 | "dotnet_interactive": { 423 | "language": "csharp" 424 | }, 425 | "polyglot_notebook": { 426 | "kernelName": "csharp" 427 | } 428 | }, 429 | "outputs": [], 430 | "source": [ 431 | "var polylines = new List();\n", 432 | "foreach (var boundary in wallLines.RootElement.EnumerateArray())\n", 433 | "{\n", 434 | " \n", 435 | " var points = new List();\n", 436 | "\n", 437 | " foreach (var lineJson in boundary.EnumerateArray())\n", 438 | " {\n", 439 | " \n", 440 | " var start = lineJson.GetProperty(\"start\");\n", 441 | " var end = lineJson.GetProperty(\"end\");\n", 442 | " \n", 443 | "\n", 444 | " var a = new Vector3((double)start.GetProperty(\"X\").GetDouble(),start.GetProperty(\"Y\").GetDouble(),\n", 445 | " start.GetProperty(\"Z\").GetDouble());\n", 446 | " var b = new Vector3((double)end.GetProperty(\"X\").GetDouble(),end.GetProperty(\"Y\").GetDouble(),end.GetProperty(\"Z\").GetDouble());\n", 447 | " \n", 448 | " points.Add(a);\n", 449 | " points.Add(b);\n", 450 | "\n", 451 | " }\n", 452 | "\n", 453 | " Polyline polyline = new Polyline(points);\n", 454 | " polylines.Add(polyline);\n", 455 | "}\n", 456 | "\n", 457 | "return polylines;\n" 458 | ] 459 | } 460 | ], 461 | "metadata": { 462 | "kernelspec": { 463 | "display_name": ".NET (C#)", 464 | "language": "C#", 465 | "name": ".net-csharp" 466 | }, 467 | "language_info": { 468 | "name": "polyglot-notebook" 469 | }, 470 | "polyglot_notebook": { 471 | "kernelInfo": { 472 | "defaultKernelName": "csharp", 473 | "items": [ 474 | { 475 | "aliases": [], 476 | "name": "csharp" 477 | } 478 | ] 479 | } 480 | } 481 | }, 482 | "nbformat": 4, 483 | "nbformat_minor": 2 484 | } 485 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp.Tests/CodeRewriterTests.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.DotNet.Interactive.ValueSharing; 5 | using System.Diagnostics; 6 | 7 | namespace Jowsy.CSharp.Tests 8 | { 9 | public class CodeRewriterTests : TestBase 10 | { 11 | [Fact] 12 | public void ItShouldBePossibleToUseImplicitReturn() 13 | { 14 | var compilationUnit = SyntaxUtils.GenerateCodeCommand(@"string test = ""hello world""; 15 | return test;"); 16 | 17 | var actual = StripUsings(SyntaxUtils.FixReturn(compilationUnit) 18 | .NormalizeWhitespace() 19 | .ToFullString()); 20 | 21 | string expected = @"namespace Jowsy.Revit.KernelAddin.Core 22 | { 23 | public class Command : ICodeCommand 24 | { 25 | public event EventHandler OnDisplay; 26 | public (string, object) Execute(UIApplication uiapp, Variables __variables) 27 | { 28 | string test = ""hello world""; 29 | return (""test"", test); 30 | } 31 | 32 | public void display(object o) 33 | { 34 | OnDisplay?.Invoke(this, new DisplayEventArgs(o)); 35 | } 36 | } 37 | }"; 38 | 39 | actual.Should().Be(expected); 40 | } 41 | 42 | [Fact] 43 | public void UndeclaredVariablesShouldBeResolvedIfTheyExistGloballyInKernel() 44 | { 45 | var valueInfos = new List(){ 46 | new KernelValueInfo("message", 47 | new Microsoft.DotNet.Interactive.FormattedValue("text",""), 48 | typeof(string)), 49 | new KernelValueInfo("number", 50 | new Microsoft.DotNet.Interactive.FormattedValue("text","1"), 51 | typeof(int)) 52 | }; 53 | 54 | 55 | var sourceText = SyntaxUtils.BuildClassCode(@"message = ""hello world""; 56 | int result = number + 12; 57 | return null;"); 58 | 59 | var compilation = Compile(sourceText); 60 | 61 | var node = SyntaxUtils.ResolveUndeclaredVariables(compilation, valueInfos); 62 | var actual = StripUsings(node?.NormalizeWhitespace().ToFullString()); 63 | Debug.Write(actual); 64 | 65 | string expected = @"namespace Jowsy.Revit.KernelAddin.Core 66 | { 67 | public class Command : ICodeCommand 68 | { 69 | public event EventHandler OnDisplay; 70 | public (string, object) Execute(UIApplication uiapp, Variables __variables) 71 | { 72 | System.String message = (System.String)__variables.GetVariables()[""message""]; 73 | System.Int32 number = (System.Int32)__variables.GetVariables()[""number""]; 74 | message = ""hello world""; 75 | int result = number + 12; 76 | return null; 77 | } 78 | 79 | public void display(object o) 80 | { 81 | OnDisplay?.Invoke(this, new DisplayEventArgs(o)); 82 | } 83 | } 84 | }"; 85 | expected.Should().Be(actual); 86 | } 87 | 88 | [Fact] 89 | public void UndeclaredListVariablesShouldBeResolvedIfTheyExistGloballyInKernel() 90 | { 91 | var valueInfos = new List(){ 92 | new KernelValueInfo("numbers", 93 | new Microsoft.DotNet.Interactive.FormattedValue("text","List"), 94 | typeof(List)) 95 | }; 96 | 97 | 98 | var sourceText = SyntaxUtils.BuildClassCode(@"int count = numbers.Count(); 99 | return null;"); 100 | 101 | var compilation = Compile(sourceText); 102 | 103 | var node = SyntaxUtils.ResolveUndeclaredVariables(compilation, valueInfos); 104 | var actual = StripUsings(node?.NormalizeWhitespace().ToFullString()); 105 | Debug.Write(actual); 106 | 107 | string expected = @"namespace Jowsy.Revit.KernelAddin.Core 108 | { 109 | public class Command : ICodeCommand 110 | { 111 | public event EventHandler OnDisplay; 112 | public (string, object) Execute(UIApplication uiapp, Variables __variables) 113 | { 114 | System.Collections.Generic.List numbers = (System.Collections.Generic.List)__variables.GetVariables()[""numbers""]; 115 | int count = numbers.Count(); 116 | return null; 117 | } 118 | 119 | public void display(object o) 120 | { 121 | OnDisplay?.Invoke(this, new DisplayEventArgs(o)); 122 | } 123 | } 124 | }"; 125 | expected.Should().Be(actual); 126 | } 127 | 128 | [Fact] 129 | public async Task TryCompile() 130 | { 131 | RoslynCompilerService service = new RoslynCompilerService("2024"); 132 | 133 | var results = await service.CompileRevitAddin("height = 5;", false, () => 134 | { 135 | return Task.FromResult(new KernelValueInfo[]{ 136 | new KernelValueInfo("height", 137 | new Microsoft.DotNet.Interactive.FormattedValue("text","5"), 138 | typeof(int)), 139 | }); 140 | 141 | }); 142 | 143 | 144 | 145 | } 146 | 147 | public static string StripUsings(string code) 148 | { 149 | var index = code.ToLower().IndexOf("namespace"); 150 | 151 | return code.Substring(index); 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /src/Jowsy.CSharp.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using Xunit; -------------------------------------------------------------------------------- /src/Jowsy.CSharp.Tests/Jowsy.CSharp.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 8 | false 9 | true 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | all 19 | 20 | 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | all 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp.Tests/TestBase.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | 4 | namespace Jowsy.CSharp.Tests 5 | { 6 | public class TestBase 7 | { 8 | 9 | private ReferenceList _references; 10 | private ReferenceList GetMetadataReferences() 11 | { 12 | if (_references == null) 13 | { 14 | InitReferences(); 15 | } 16 | return _references; 17 | } 18 | 19 | internal Compilation? Compile(string sourceText) 20 | { 21 | 22 | var syntaxTree = CSharpSyntaxTree.ParseText(sourceText.Trim()); 23 | var optimizationLevel = OptimizationLevel.Release; 24 | 25 | var compilation = CSharpCompilation.Create($"default-test-library") 26 | .WithOptions(new CSharpCompilationOptions( 27 | OutputKind.DynamicallyLinkedLibrary, optimizationLevel: optimizationLevel) 28 | ) 29 | .AddReferences(GetMetadataReferences()) 30 | .AddSyntaxTrees(syntaxTree); 31 | 32 | return compilation; 33 | 34 | } 35 | 36 | public void InitReferences() 37 | { 38 | _references = new ReferenceList(); 39 | AddAssembly("mscorlib.dll"); 40 | AddAssembly("System.dll"); 41 | AddAssembly("System.Core.dll"); 42 | //AddAssembly("System.Private.CoreLib.dll"); 43 | AddAssembly("Microsoft.CSharp.dll"); 44 | AddAssembly("System.Net.Http.dll"); 45 | //AddAssembly(typeof(object).GetTypeInfo().Assembly.FullName); 46 | AddAssembly("C:\\git\\bim-net-interactive\\src\\RevitKernelUI\\bin\\Debug R24\\Jowsy.Revit.KernelAddin.dll"); 47 | AddAssembly("C:\\Program Files\\Autodesk\\Revit 2024\\RevitAPI.dll"); 48 | AddAssembly("C:\\Program Files\\Autodesk\\Revit 2024\\RevitAPIUI.dll"); 49 | 50 | // this library and CodeAnalysis libs 51 | //AddAssembly(typeof(ReferenceList)); // Scripting Library 52 | } 53 | internal bool AddAssembly(string assemblyDll) 54 | { 55 | if (string.IsNullOrEmpty(assemblyDll)) return false; 56 | 57 | var file = Path.GetFullPath(assemblyDll); 58 | 59 | if (!File.Exists(file)) 60 | { 61 | // check framework or dedicated runtime app folder 62 | var path = "C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\";//Path.GetDirectoryName(typeof(object).Assembly.Location); 63 | file = Path.Combine(path, assemblyDll); 64 | if (!File.Exists(file)) 65 | return false; 66 | } 67 | 68 | if (_references.Any(r => r.FilePath == file)) return true; 69 | 70 | try 71 | { 72 | var reference = MetadataReference.CreateFromFile(file); 73 | _references.Add(reference); 74 | } 75 | catch 76 | { 77 | return false; 78 | } 79 | 80 | return true; 81 | } 82 | public class ReferenceList : HashSet 83 | { 84 | 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp/CompilationResults.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | namespace Jowsy.CSharp 4 | { 5 | public class CompilationResults 6 | { 7 | public bool Success { get; set; } 8 | 9 | public string? DiagnosticText { get; set; } 10 | 11 | public string? AssemblyPath { get; set; } 12 | 13 | public Assembly? Assembly { get; set; } 14 | 15 | } 16 | } -------------------------------------------------------------------------------- /src/Jowsy.CSharp/Jowsy.CSharp.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 0.1.0 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp/ReferenceList.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | //Influenced by https://github.com/RickStrahl/Westwind.Scripting/blob/master/Westwind.Scripting/CSharpScriptExecution.cs#L1254 3 | namespace Jowsy.CSharp 4 | { 5 | public class ReferenceList : HashSet 6 | { 7 | 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp/RoslynCompilerService.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.CodeAnalysis.Emit; 5 | using Microsoft.DotNet.Interactive.ValueSharing; 6 | using System.Reflection; 7 | using System.Text; 8 | 9 | //See https://github.com/RickStrahl/Westwind.Scripting/blob/master/Westwind.Scripting/CSharpScriptExecution.cs#L1254 10 | namespace Jowsy.CSharp 11 | { 12 | public class RoslynCompilerService 13 | { 14 | 15 | public ReferenceList References { get; private set; } 16 | public string GeneratedClassCode { get; private set; } 17 | 18 | public RoslynCompilerService(string? revitVersion) 19 | { 20 | if (revitVersion == null) 21 | { 22 | throw new ArgumentNullException(nameof(revitVersion)); 23 | } 24 | 25 | References = new ReferenceList(); 26 | AddNetFrameworkDefaultReferences(revitVersion); 27 | 28 | string appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 29 | 30 | string addinPath = Path.Combine(appDataFolder, "Autodesk", "Revit", "Addins", revitVersion, "Jowsy.Revit.KernelAddin"); 31 | AddAssembly(Path.Combine(addinPath, "Jowsy.Revit.KernelAddin.dll")); 32 | AddAssembly(Path.Combine(addinPath, "System.Text.Json.dll")); 33 | } 34 | public void AddNetFrameworkDefaultReferences(string revitVersion) 35 | { 36 | AddAssembly("mscorlib.dll"); 37 | AddAssembly("System.dll"); 38 | AddAssembly("System.Core.dll"); 39 | AddAssembly("Microsoft.CSharp.dll"); 40 | AddAssembly("System.Net.Http.dll"); 41 | AddAssembly($"C:\\Program Files\\Autodesk\\Revit {revitVersion}\\RevitAPI.dll"); 42 | AddAssembly($"C:\\Program Files\\Autodesk\\Revit {revitVersion}\\RevitAPIUI.dll"); 43 | AddAssembly($"C:\\Program Files\\Autodesk\\Revit {revitVersion}\\RevitAPIIFC.dll"); 44 | AddAssembly($"C:\\Program Files\\Autodesk\\Revit {revitVersion}\\NewtonSoft.Json.dll"); 45 | 46 | AddAssembly(typeof(ReferenceList)); 47 | } 48 | public bool AddAssembly(string assemblyDll) 49 | { 50 | if (string.IsNullOrEmpty(assemblyDll)) return false; 51 | 52 | var file = Path.GetFullPath(assemblyDll); 53 | 54 | if (!File.Exists(file)) 55 | { 56 | // check framework or dedicated runtime app folder 57 | var path = "C:\\Program Files (x86)\\Reference Assemblies\\Microsoft\\Framework\\.NETFramework\\v4.8\\"; 58 | file = Path.Combine(path, assemblyDll); 59 | if (!File.Exists(file)) 60 | return false; 61 | } 62 | 63 | if (References.Any(r => r.FilePath == file)) return true; 64 | 65 | try 66 | { 67 | var reference = MetadataReference.CreateFromFile(file); 68 | References.Add(reference); 69 | } 70 | catch 71 | { 72 | return false; 73 | } 74 | 75 | return true; 76 | } 77 | 78 | 79 | /// 80 | /// Compiles a revit addin dll from a C#-script 81 | /// 82 | /// C# code 83 | /// a resolver for list of kernelvalueinfos 84 | /// 85 | public async Task CompileRevitAddin(string script, bool 86 | toAssemblyFile, 87 | Func>? KernelValueInfosResolver) 88 | { 89 | var source = SyntaxUtils.BuildClassCode(script); 90 | 91 | var tree = CSharpSyntaxTree.ParseText(source.Trim()); 92 | 93 | var root = (CompilationUnitSyntax)tree.GetRoot(); 94 | 95 | var tRoot = SyntaxUtils.FixReturn(root); 96 | var finalTree = CSharpSyntaxTree.Create(tRoot); 97 | 98 | var optimizationLevel = OptimizationLevel.Release; 99 | 100 | var compilation = CSharpCompilation.Create($"revitkernelgenerated-{DateTime.Today.Ticks}") 101 | .WithOptions(new CSharpCompilationOptions( 102 | OutputKind.DynamicallyLinkedLibrary, optimizationLevel: optimizationLevel) 103 | ) 104 | .AddReferences(References) 105 | .AddSyntaxTrees(finalTree); 106 | 107 | var diagnostics = compilation.GetDiagnostics().Where(d => d.Id == "CS0103"); //Look for undeclared variables 108 | 109 | GeneratedClassCode = finalTree.ToString(); 110 | 111 | if (diagnostics.Any()) 112 | { 113 | 114 | if (KernelValueInfosResolver == null) 115 | { 116 | throw new ArgumentNullException(nameof(KernelValueInfosResolver)); 117 | } 118 | 119 | var valueInfos = await KernelValueInfosResolver(); 120 | 121 | var syntax = SyntaxUtils.ResolveUndeclaredVariables(compilation, valueInfos) as CompilationUnitSyntax; 122 | 123 | //New try 124 | var newTree = CSharpSyntaxTree.Create(syntax); 125 | compilation = CSharpCompilation.Create($"revitkernelgenerated-{DateTime.Today.Ticks}") 126 | .WithOptions(new CSharpCompilationOptions( 127 | OutputKind.DynamicallyLinkedLibrary, optimizationLevel: optimizationLevel) 128 | ) 129 | .AddReferences(References) 130 | .AddSyntaxTrees(newTree); 131 | 132 | GeneratedClassCode = newTree.ToString(); 133 | 134 | } 135 | //if (SaveGeneratedCode) 136 | 137 | 138 | 139 | Stream codeStream = null; 140 | Assembly assembly = null; 141 | string outputAssembly = null; 142 | 143 | if (toAssemblyFile) 144 | { 145 | outputAssembly = Path.Combine(Path.GetTempPath(), 146 | "revitkernel", $"{Path.GetRandomFileName()}.dll"); 147 | if (!Directory.Exists(Path.GetDirectoryName(outputAssembly))) 148 | { 149 | Directory.CreateDirectory(Path.GetDirectoryName(outputAssembly)); 150 | } 151 | } 152 | 153 | codeStream = toAssemblyFile ? new FileStream(outputAssembly, FileMode.Create) : 154 | new MemoryStream(); 155 | 156 | 157 | 158 | using (codeStream) 159 | { 160 | EmitResult compilationResult = null; 161 | 162 | compilationResult = compilation.Emit(codeStream); 163 | 164 | 165 | // Compilation Error handling 166 | if (!compilationResult.Success) 167 | { 168 | var sb = new StringBuilder(); 169 | foreach (var diag in compilationResult.Diagnostics) 170 | { 171 | 172 | sb.AppendLine(diag.ToString()); 173 | } 174 | if (sb != null && sb.Length > 0) 175 | { 176 | return new CompilationResults() 177 | { 178 | DiagnosticText = sb.ToString(), 179 | Success = false 180 | }; 181 | 182 | } 183 | 184 | if (!toAssemblyFile) 185 | { 186 | var memStream = (MemoryStream)codeStream; 187 | memStream.Seek(0, SeekOrigin.Begin); 188 | assembly = Assembly.Load(memStream.ToArray()); 189 | } 190 | 191 | return new CompilationResults() 192 | { 193 | Success = false, 194 | DiagnosticText = sb.ToString(), 195 | }; 196 | } 197 | } 198 | 199 | if (toAssemblyFile) 200 | { 201 | return new CompilationResults() 202 | { 203 | Success = true, 204 | AssemblyPath = outputAssembly 205 | }; 206 | } 207 | else 208 | { 209 | return new CompilationResults() 210 | { 211 | Success = true, 212 | Assembly = assembly 213 | }; 214 | } 215 | 216 | 217 | } 218 | private Assembly LoadAssemblyFrom(string assemblyFile) 219 | { 220 | #if NETCORE 221 | if (AlternateAssemblyLoadContext != null) 222 | { 223 | return AlternateAssemblyLoadContext.LoadFromAssemblyPath(assemblyFile); 224 | } 225 | #endif 226 | return Assembly.LoadFrom(assemblyFile); 227 | } 228 | private Assembly LoadAssembly(byte[] rawAssembly) 229 | { 230 | /*#if NETCORE 231 | if (AlternateAssemblyLoadContext != null) 232 | { 233 | return AlternateAssemblyLoadContext.LoadFromStream(new MemoryStream(rawAssembly)); 234 | } 235 | #endif*/ 236 | return Assembly.Load(rawAssembly); 237 | } 238 | public bool AddAssembly(Type type) 239 | { 240 | try 241 | { 242 | if (References.Any(r => r.FilePath == type.Assembly.Location)) 243 | return true; 244 | 245 | var systemReference = MetadataReference.CreateFromFile(type.Assembly.Location); 246 | References.Add(systemReference); 247 | } 248 | catch 249 | { 250 | return false; 251 | } 252 | 253 | return true; 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /src/Jowsy.CSharp/SyntaxUtils.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.CodeAnalysis.CSharp.Syntax; 4 | using Microsoft.DotNet.Interactive.ValueSharing; 5 | 6 | namespace Jowsy.CSharp 7 | { 8 | public class SyntaxUtils 9 | { 10 | public static string BuildClassCode(string script) => 11 | @"using System; 12 | using Autodesk.Revit.Attributes; 13 | using Autodesk.Revit.DB; 14 | using Autodesk.Revit.UI; 15 | using Autodesk.Revit.DB.IFC; 16 | using System.Collections; 17 | using Autodesk.Revit.UI.Selection; 18 | using System.Linq; 19 | using System.Collections.Generic; 20 | using Jowsy.Revit.KernelAddin.Core; 21 | using Newtonsoft.Json; 22 | using Newtonsoft.Json.Linq; 23 | using System.Text.Json; 24 | 25 | namespace Jowsy.Revit.KernelAddin.Core 26 | { 27 | public class Command : ICodeCommand 28 | { 29 | public event EventHandler OnDisplay; 30 | 31 | public (string, object) Execute(UIApplication uiapp, Variables __variables) 32 | {" 33 | + script + @" 34 | } 35 | 36 | public void display(object o) 37 | { 38 | OnDisplay?.Invoke(this, new DisplayEventArgs(o)); 39 | } 40 | } 41 | }"; 42 | 43 | public static CompilationUnitSyntax GenerateCodeCommand(string script) 44 | { 45 | 46 | var source = BuildClassCode(script); 47 | 48 | var tree = CSharpSyntaxTree.ParseText(source); 49 | 50 | var root = (CompilationUnitSyntax)tree.GetRoot().NormalizeWhitespace(); 51 | 52 | return root; 53 | } 54 | public static CompilationUnitSyntax FixReturn(CompilationUnitSyntax root) 55 | { 56 | MethodDeclarationSyntax methodDecl = root 57 | .DescendantNodes() 58 | .OfType() 59 | .First().ChildNodes().OfType().First(); 60 | 61 | var block = methodDecl.DescendantNodes().OfType().FirstOrDefault(); 62 | 63 | //look for return statement in the method block 64 | var returnStatement = block.DescendantNodes() 65 | .OfType().FirstOrDefault(); 66 | 67 | if (returnStatement != null) 68 | { 69 | var returnStatementSyntax = returnStatement as ReturnStatementSyntax; 70 | var varName = (returnStatementSyntax.Expression as IdentifierNameSyntax).Identifier.Text; 71 | 72 | var newNode = SyntaxFactory.ParseStatement($"return (\"{varName}\", {varName});"); 73 | var newTree2 = root.ReplaceNode(returnStatement, newNode); 74 | return newTree2; 75 | } 76 | 77 | var nodes = block.ChildNodes(); 78 | var lastNode = nodes.Last(); 79 | 80 | 81 | var testCSharpScriptReturnStatement = lastNode as ExpressionStatementSyntax; 82 | if (testCSharpScriptReturnStatement != null) 83 | { 84 | if (string.IsNullOrEmpty(testCSharpScriptReturnStatement.SemicolonToken.Text)) 85 | { 86 | var returnVariableName = (testCSharpScriptReturnStatement.Expression as IdentifierNameSyntax).Identifier.Text; 87 | if (returnVariableName != null) 88 | { 89 | 90 | var newNode = SyntaxFactory.ParseStatement($"return (\"{returnVariableName}\",{returnVariableName});"); 91 | var newTree2 = root.ReplaceNode(testCSharpScriptReturnStatement, newNode); 92 | 93 | return newTree2; 94 | 95 | } 96 | 97 | } 98 | } 99 | 100 | //When we just want to execute statements we return null 101 | var newReturnStatement = SyntaxFactory.ParseStatement("return (null,null);"); 102 | 103 | var newTree = root.InsertNodesAfter(lastNode, new List() { newReturnStatement }); 104 | 105 | return newTree; 106 | 107 | } 108 | 109 | public static SyntaxNode? ResolveUndeclaredVariables(Compilation? compilation, IEnumerable? valueInfos) 110 | { 111 | var diagnostics = compilation.GetDiagnostics().Where(d => d.Id == "CS0103"); 112 | 113 | List missingVariableDeclarations = new List(); 114 | foreach (var item in diagnostics) 115 | { 116 | var sourceSpan = item.Location?.SourceSpan; 117 | if (sourceSpan == null) 118 | { 119 | continue; 120 | } 121 | 122 | var variable = item.Location? 123 | .SourceTree? 124 | .ToString() 125 | .Substring(sourceSpan.Value.Start, sourceSpan.Value.Length); 126 | if (variable != null) 127 | { 128 | missingVariableDeclarations.Add(variable); 129 | } 130 | } 131 | 132 | var tree = compilation.SyntaxTrees.FirstOrDefault(); 133 | 134 | //var semanticModel = compilation.GetSemanticModel(tree); 135 | 136 | MethodDeclarationSyntax methodDecl = tree.GetRoot() 137 | .DescendantNodes() 138 | .OfType() 139 | .First().ChildNodes() 140 | .OfType() 141 | .First(); 142 | 143 | var block = methodDecl.DescendantNodes() 144 | .OfType() 145 | .FirstOrDefault(); 146 | 147 | SyntaxNode node = block.ChildNodes().First(); 148 | 149 | List nodesToAppend = new List(); 150 | foreach (var variable in missingVariableDeclarations.Distinct()) 151 | { 152 | var valueInfo = valueInfos.Where(vi => vi.Name == variable).FirstOrDefault(); 153 | 154 | if (valueInfo != null) 155 | { 156 | 157 | string typeName = valueInfo.TypeName; 158 | //TypeSyntaxFactory.GetTypeSyntax() 159 | 160 | var valueInfoNode = SyntaxFactory.ParseStatement($"{valueInfo.TypeName} {valueInfo.Name} = ({valueInfo.TypeName})__variables.GetVariables()[\"{valueInfo.Name}\"];"); 161 | nodesToAppend.Add(valueInfoNode); 162 | } 163 | 164 | } 165 | var result = tree.GetRoot() 166 | .InsertNodesBefore(node, nodesToAppend); 167 | 168 | return result; 169 | } 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/Jowsy.DotNet.Interactive.Extensions/ConnectRevitKernelCommand.cs: -------------------------------------------------------------------------------- 1 | using Jowsy.CSharp; 2 | using Microsoft.DotNet.Interactive; 3 | using Microsoft.DotNet.Interactive.Commands; 4 | using Microsoft.DotNet.Interactive.Connection; 5 | using Microsoft.DotNet.Interactive.Events; 6 | using Microsoft.DotNet.Interactive.Utility; 7 | using System.CommandLine; 8 | using System.CommandLine.Invocation; 9 | 10 | namespace Jowsy.DotNet.Interactive.Extensions 11 | { 12 | public class ConnectRevitKernelCommand : ConnectKernelCommand 13 | { 14 | public static string? RevitVersion; 15 | public Option RevitVersionOption { get; } = 16 | new("--revit-version", "version of revit to connect to") 17 | { 18 | IsRequired = true 19 | }; 20 | 21 | public ConnectRevitKernelCommand() : base("revit", "connect to a revit kernel") 22 | { 23 | Add(RevitVersionOption); 24 | } 25 | 26 | public override async Task> ConnectKernelsAsync(KernelInvocationContext context, InvocationContext commandLineContext) 27 | { 28 | var revitversion = commandLineContext.ParseResult.GetValueForOption(RevitVersionOption); 29 | 30 | var pipeName = $"revit-kernel-{revitversion}-pipe"; 31 | 32 | var connector = new NamedPipeKernelConnector(pipeName!); 33 | 34 | var localName = commandLineContext.ParseResult.GetValueForOption(KernelNameOption); 35 | 36 | var proxyKernel = await connector.CreateKernelAsync(localName!).Timeout(new TimeSpan(0, 0, 10)); 37 | 38 | proxyKernel.AddMiddleware(async (command, context, next) => 39 | { 40 | var submitCommand = command as SubmitCode; 41 | if (submitCommand != null) 42 | { 43 | RoslynCompilerService compilerService = new RoslynCompilerService(revitversion); 44 | 45 | var results = await compilerService.CompileRevitAddin(submitCommand.Code, 46 | true, 47 | async () => 48 | { 49 | var result = await proxyKernel.SendAsync(new RequestValueInfos()); 50 | 51 | var valueInfosProduced = result.Events.Where(e => e is ValueInfosProduced) 52 | .FirstOrDefault() as ValueInfosProduced; 53 | if (valueInfosProduced == null) 54 | { 55 | return null; 56 | } 57 | return valueInfosProduced.ValueInfos.ToArray(); 58 | 59 | }); 60 | 61 | if (results.AssemblyPath != null) 62 | { 63 | await proxyKernel.SendAsync(new SendValue("assemblyPath", results.AssemblyPath, FormattedValue.CreateSingleFromObject(results.AssemblyPath))); 64 | 65 | } 66 | else 67 | { 68 | context.DisplayStandardError(results.DiagnosticText); 69 | await proxyKernel.SendAsync(new SendValue("assemblyPath", "")); 70 | } 71 | 72 | 73 | await next(command, context); 74 | } 75 | else 76 | { 77 | await next(command, context); 78 | } 79 | 80 | 81 | 82 | 83 | }); 84 | proxyKernel.UseValueSharing(); 85 | 86 | //Add support for sendvalue 87 | proxyKernel.KernelInfo.SupportedKernelCommands.Add(new KernelCommandInfo("SendValue")); 88 | proxyKernel.KernelInfo.SupportedKernelCommands.Add(new KernelCommandInfo("RequestValueInfos")); 89 | 90 | var reqHover = proxyKernel.KernelInfo.SupportedKernelCommands.Where(c => c.Name == "RequestHoverText").FirstOrDefault(); 91 | if (reqHover != null) 92 | { 93 | proxyKernel.KernelInfo.SupportedKernelCommands.Remove(reqHover); 94 | } 95 | 96 | return new ProxyKernel[] { proxyKernel }; 97 | 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Jowsy.DotNet.Interactive.Extensions/Jowsy.DotNet.Interactive.Extensions.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0 5 | enable 6 | enable 7 | 0.1.0 8 | $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage 9 | 10 | 11 | 12 | true 13 | $(Description) 14 | RevitInteractive 15 | autodesk revit;dotnet-interactive 16 | true 17 | True 18 | $(version) 19 | README.md 20 | Joel Waldheim Saury 21 | https://github.com/jowsy/bim-net-interactive/ 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | MIT 37 | 38 | 39 | 40 | true 41 | Jowsy.CSharp.dll 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/Jowsy.DotNet.Interactive.Extensions/NamedPipeKernelConnector.cs: -------------------------------------------------------------------------------- 1 | namespace Jowsy.DotNet.Interactive.Extensions 2 | { 3 | using Microsoft.DotNet.Interactive.Commands; 4 | using Microsoft.DotNet.Interactive.Connection; 5 | using System; 6 | using System.IO.Pipes; 7 | using System.Reactive.Disposables; 8 | using System.Security.Principal; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | public class NamedPipeKernelConnector : IDisposable 13 | { 14 | private KernelCommandAndEventReceiver? _receiver; 15 | private KernelCommandAndEventSender? _sender; 16 | private NamedPipeClientStream? _clientStream; 17 | private RefCountDisposable? _refCountDisposable; 18 | 19 | public NamedPipeKernelConnector(string pipeName) 20 | { 21 | PipeName = pipeName; 22 | RemoteHostUri = new Uri($"kernel://{PipeName}"); 23 | } 24 | 25 | public string PipeName { get; } 26 | 27 | public Uri RemoteHostUri { get; } 28 | 29 | 30 | public async Task CreateKernelAsync(string localName) 31 | { 32 | ProxyKernel? proxyKernel; 33 | 34 | if (_receiver is null) 35 | { 36 | _clientStream = new NamedPipeClientStream( 37 | ".", 38 | PipeName, 39 | PipeDirection.InOut, 40 | PipeOptions.Asynchronous, 41 | TokenImpersonationLevel.Impersonation); 42 | 43 | await _clientStream.ConnectAsync(); 44 | 45 | _clientStream.ReadMode = PipeTransmissionMode.Message; 46 | 47 | _receiver = KernelCommandAndEventReceiver.FromNamedPipe(_clientStream); 48 | _sender = KernelCommandAndEventSender.FromNamedPipe( 49 | _clientStream, 50 | RemoteHostUri); 51 | 52 | _refCountDisposable = new RefCountDisposable(new CompositeDisposable 53 | { 54 | Disposable.Create( () => _clientStream.Dispose()), 55 | Disposable.Create( () => _receiver.Dispose()) 56 | }); 57 | 58 | proxyKernel = new ProxyKernel( 59 | localName, 60 | _sender, 61 | _receiver, 62 | new Uri(RemoteHostUri, localName)); 63 | proxyKernel.RegisterForDisposal(_refCountDisposable); 64 | } 65 | else 66 | { 67 | proxyKernel = new ProxyKernel( 68 | localName, 69 | _sender, 70 | _receiver, 71 | new Uri(RemoteHostUri, localName)); 72 | 73 | proxyKernel.RegisterForDisposal(_refCountDisposable!.GetDisposable()); 74 | } 75 | 76 | var destinationUri = new Uri(RemoteHostUri, localName); 77 | 78 | await _sender!.SendAsync( 79 | new RequestKernelInfo(destinationUri), 80 | CancellationToken.None); 81 | 82 | return proxyKernel; 83 | } 84 | 85 | public void Dispose() => _refCountDisposable?.Dispose(); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Jowsy.DotNet.Interactive.Extensions/RevitKernelExtension.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Html; 2 | using Microsoft.DotNet.Interactive; 3 | 4 | namespace Jowsy.DotNet.Interactive.Extensions 5 | { 6 | public sealed class CSharpKernelExtension : IKernelExtension 7 | { 8 | public Task OnLoadAsync(Kernel kernel) 9 | { 10 | if (kernel is not CompositeKernel compositeKernel) return Task.CompletedTask; 11 | 12 | compositeKernel 13 | .AddKernelConnector(new ConnectRevitKernelCommand()); 14 | 15 | KernelInvocationContext.Current?.Display( 16 | new HtmlString(@"
Autodesk Revit Interactive Extension 17 |

This extension adds support for connecting to Autodesk Revit using the #!connect revit magic command. As example, to connect to revit 2024, run a cell using #!connect revit --kernel-name revit24 --revit-version 2024.

18 |

Then begin with #!revit24 in a C#-cell to execute code in the revit context.

"), "text/html"); 19 | 20 | return Task.CompletedTask; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Jowsy.DotNet.Interactive.Extensions/docs/README.md: -------------------------------------------------------------------------------- 1 | This project explores the possibility to run live Revit C#-scripts inside notebooks bundled with visualizations and narrative text. -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/App.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.DB; 2 | using Autodesk.Revit.UI; 3 | using Jowsy.Revit.KernelAddin.Core; 4 | using Jowsy.Revit.KernelAddin.UI; 5 | using Microsoft.DotNet.Interactive.Formatting; 6 | using System.IO; 7 | using System.Reflection; 8 | 9 | namespace Jowsy.Revit.KernelAddin 10 | { 11 | public class App : IExternalApplication 12 | { 13 | public static ExternalEvent KernelEvent; 14 | public static ExternalEvent InitKernelEvent; 15 | 16 | public static DockablePaneId DockablePaneId = new DockablePaneId(new Guid("3776E883-839D-4D43-8498-8C7D2345C1CB")); 17 | internal static RevitKernelExternalEventHandler KernelEventHandler; 18 | internal static KernelInitEventHandler KernelInitEventHandler; 19 | 20 | public Result OnShutdown(UIControlledApplication application) 21 | { 22 | return Result.Cancelled; 23 | } 24 | 25 | public Result OnStartup(UIControlledApplication application) 26 | { 27 | AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve; 28 | 29 | var ribbonPanel = application.CreateRibbonPanel("Interactive Revit Kernel"); 30 | 31 | var showCommand = typeof(ShowCommand); 32 | var showButtonData = new PushButtonData(showCommand.FullName, "Show\nDockable Pane", Assembly.GetAssembly(showCommand).Location, showCommand.FullName); 33 | ribbonPanel.AddItem(showButtonData); 34 | 35 | KernelEventHandler = new RevitKernelExternalEventHandler(); 36 | KernelEvent = ExternalEvent.Create(KernelEventHandler); 37 | 38 | KernelInitEventHandler = new KernelInitEventHandler(); 39 | InitKernelEvent = ExternalEvent.Create(KernelInitEventHandler); 40 | 41 | var kernelPaneProvider = new KernelDockablePaneProvider(new ViewModel(application.ControlledApplication.VersionNumber)); 42 | 43 | application.RegisterDockablePane(DockablePaneId, "Interactive Revit Kernel", kernelPaneProvider); 44 | 45 | //Force loading of assembly 46 | var activityType = typeof(System.Diagnostics.Activity); 47 | 48 | //TODO: Implement a better formatter! Maybe based on RevitLookup? 49 | Formatter.SetPreferredMimeTypesFor(typeof(Element), "text/html"); 50 | 51 | //It's common for object graphs to contain reference cycles. 52 | //The .NET Interactive formatter will traverse object graphs but in order to avoid both oversized outputs and possible 53 | //infinite recursion when there is a reference cycle, the formatter will only recurse to a specific depth. 54 | Formatter.RecursionLimit = 3; 55 | return Result.Succeeded; 56 | } 57 | Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args) 58 | { 59 | // load the assembly from the embedded resources 60 | string folder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 61 | string assemblyPath = Path.Combine(folder, new AssemblyName(args.Name).Name + ".dll"); 62 | if (File.Exists(assemblyPath)) 63 | { 64 | return Assembly.LoadFrom(assemblyPath); 65 | } 66 | return null; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/DisplayEventArgs.cs: -------------------------------------------------------------------------------- 1 | /* 2 | * Give credit to https://github.com/ricaun/RevitAddin.CodeCompileTest/blob/master/RevitAddin.CodeCompileTest/Services/CodeDomService.cs 3 | * */ 4 | namespace Jowsy.Revit.KernelAddin.Core 5 | { 6 | public class DisplayEventArgs : EventArgs 7 | { 8 | private readonly object _displayObject; 9 | 10 | public DisplayEventArgs(object displayObject) 11 | { 12 | _displayObject = displayObject; 13 | } 14 | 15 | public object DisplayObject 16 | { 17 | get { return _displayObject; } 18 | } 19 | } 20 | 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/DynamicCodeHandler.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.UI; 2 | 3 | namespace Jowsy.Revit.KernelAddin.Core 4 | { 5 | internal class DynamicCodeHandler : IDisposable 6 | { 7 | private bool disposed = false; 8 | internal static ExternalEvent Event; 9 | 10 | internal DynamicCodeHandler() 11 | { 12 | var eventHandler = new RevitKernelExternalEventHandler(); 13 | Event = ExternalEvent.Create(eventHandler); 14 | 15 | } 16 | 17 | protected virtual void Dispose(bool disposing) 18 | { 19 | if (disposed) 20 | { 21 | return; 22 | } 23 | if (disposing) 24 | { 25 | Event?.Dispose(); 26 | Event = null; 27 | } 28 | 29 | disposed = true; 30 | } 31 | 32 | public void Dispose() 33 | { 34 | Dispose(false); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/ICodeCommand.cs: -------------------------------------------------------------------------------- 1 |  2 | /* 3 | * Give credit to https://github.com/ricaun/RevitAddin.CodeCompileTest/blob/master/RevitAddin.CodeCompileTest/Services/CodeDomService.cs 4 | * */ 5 | using Autodesk.Revit.UI; 6 | 7 | namespace Jowsy.Revit.KernelAddin.Core 8 | { 9 | public interface ICodeCommand 10 | { 11 | public event EventHandler OnDisplay; 12 | public (string, object) Execute(UIApplication uiapp, Variables variables); 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/KernelInitEventHandler.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.UI; 2 | 3 | namespace Jowsy.Revit.KernelAddin.Core 4 | { 5 | internal class KernelInitEventHandler : IExternalEventHandler 6 | { 7 | internal UIApplication UIApplication { get; set; } 8 | internal TaskCompletionSource Tcs = null; 9 | public void Execute(UIApplication app) 10 | { 11 | UIApplication = app; 12 | Tcs.SetResult(true); 13 | } 14 | 15 | public string GetName() 16 | { 17 | return nameof(KernelInitEventHandler); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/RevitKernel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.DotNet.Interactive; 4 | using Microsoft.DotNet.Interactive.Commands; 5 | using Microsoft.DotNet.Interactive.Events; 6 | using Microsoft.DotNet.Interactive.Formatting; 7 | using Microsoft.DotNet.Interactive.ValueSharing; 8 | 9 | namespace Jowsy.Revit.KernelAddin.Core 10 | { 11 | 12 | public class RevitKernel : Kernel, 13 | IKernelCommandHandler, 14 | IKernelCommandHandler, 15 | IKernelCommandHandler, 16 | IKernelCommandHandler 17 | { 18 | private readonly Variables _variablesStore; 19 | //private ScriptOptions _scriptOptions; 20 | 21 | 22 | public RevitKernel(string name, Variables variablesStore) : base(name) 23 | { 24 | 25 | 26 | KernelInfo.LanguageName = "C#"; 27 | KernelInfo.LanguageVersion = "7.3"; //All .NET framework 28 | KernelInfo.DisplayName = $"RevitKernel - C# Script"; 29 | _variablesStore = variablesStore ?? throw new ArgumentNullException(nameof(variablesStore)); 30 | 31 | } 32 | 33 | public async Task HandleAsync(SubmitCode command, KernelInvocationContext context) 34 | { 35 | App.KernelEventHandler.tcs = new TaskCompletionSource<(string, object)>(); 36 | App.KernelEventHandler.KernelContext = context; 37 | App.KernelEventHandler.Variables = _variablesStore; 38 | 39 | _variablesStore.TryGetValue("assemblyPath", out string dllPath); 40 | 41 | if (string.IsNullOrEmpty(dllPath)) 42 | { 43 | context.Fail(command); 44 | } 45 | 46 | App.KernelEventHandler.CompiledDllPath = dllPath; 47 | App.KernelEvent.Raise(); 48 | var result = await App.KernelEventHandler.tcs.Task; 49 | if (result != (null, null)) 50 | { 51 | _variablesStore.Add(result.Item1, result.Item2); 52 | } 53 | 54 | context.Complete(command); 55 | } 56 | 57 | public Task HandleAsync(RequestValue command, KernelInvocationContext context) 58 | { 59 | if (_variablesStore.TryGetValue(command.Name, out var value)) 60 | { 61 | context.PublishValueProduced(command, value); 62 | } 63 | else 64 | { 65 | context.Fail(command, message: $"Value '{command.Name}' not found in kernel {Name}"); 66 | } 67 | 68 | return Task.CompletedTask; 69 | } 70 | 71 | public Task HandleAsync(RequestValueInfos command, KernelInvocationContext context) 72 | { 73 | var valueInfos = _variablesStore.GetVariables() 74 | .Select(v => 75 | new KernelValueInfo(v.Key, 76 | CreateSingleFromObject(v.Value, PlainTextSummaryFormatter.MimeType), 77 | v.Value.GetType())) 78 | .ToArray(); 79 | 80 | context.Publish(new ValueInfosProduced(valueInfos, command)); 81 | 82 | return Task.CompletedTask; 83 | } 84 | 85 | public static FormattedValue CreateSingleFromObject(object value, string mimeType = null) 86 | { 87 | if (mimeType is null) 88 | { 89 | mimeType = Formatter.GetPreferredMimeTypesFor(value?.GetType()).First(); 90 | } 91 | return new FormattedValue(mimeType, value.ToDisplayString(mimeType)); 92 | } 93 | 94 | public async Task HandleAsync(SendValue command, KernelInvocationContext context) 95 | { 96 | await SetValueAsync(command, context, SetValueAsync); 97 | } 98 | 99 | private Task SetValueAsync(string valueName, object value, Type declaredType = null) 100 | { 101 | _variablesStore.Add(valueName, value); 102 | return Task.CompletedTask; 103 | } 104 | 105 | /*public async Task HandleAsync(RequestHoverText command, KernelInvocationContext context) 106 | { 107 | //var text = await document.GetTextAsync(context.CancellationToken); 108 | var code = command.Code; 109 | 110 | context.Publish( 111 | new HoverTextProduced( 112 | command, 113 | new[] 114 | { 115 | new FormattedValue("text/markdown", "hej") 116 | })); 117 | } 118 | */ 119 | 120 | } 121 | } -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/RevitKernelExternalEventHandler.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.UI; 2 | using Jowsy.Revit.KernelAddin.UI; 3 | using Microsoft.DotNet.Interactive; 4 | using System.IO; 5 | using System.Reflection; 6 | 7 | namespace Jowsy.Revit.KernelAddin.Core 8 | { 9 | internal class RevitKernelExternalEventHandler : IExternalEventHandler 10 | { 11 | internal bool IsRunning { get; set; } 12 | 13 | internal Variables Variables { get; set; } 14 | 15 | internal TaskCompletionSource<(string, object)> tcs = null; 16 | 17 | public KernelInvocationContext KernelContext { get; set; } 18 | public string CompiledDllPath { get; set; } 19 | public void Execute(UIApplication uiapp) 20 | { 21 | (string, object) result = (null, null); 22 | try 23 | { 24 | //AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveDlls); 25 | IsRunning = true; 26 | UIDocument uidoc = uiapp.ActiveUIDocument; 27 | if (null == uidoc) 28 | { 29 | return; // no document, nothing to do 30 | } 31 | try 32 | { 33 | var newCommand = Assembly.LoadFile(CompiledDllPath); 34 | if (newCommand == null) 35 | { 36 | return; 37 | } 38 | var type = newCommand.GetType("Jowsy.Revit.KernelAddin.Core.Command"); 39 | var runnable = Activator.CreateInstance(type) as ICodeCommand; 40 | if (runnable == null) throw new Exception(""); 41 | runnable.OnDisplay += Runnable_OnDisplay1; 42 | result = runnable.Execute(uiapp, Variables); 43 | //context.DisplayStandardOut("Code were successfully compiled and executed in the Revit thread."); 44 | } 45 | catch (Exception ex) 46 | { 47 | KernelContext.DisplayStandardError("Compilation failed: \n" + ex.ToString()); 48 | } 49 | } 50 | finally 51 | { 52 | tcs.SetResult(result); 53 | } 54 | IsRunning = false; 55 | } 56 | 57 | private Assembly ResolveDlls(object sender, ResolveEventArgs args) 58 | { 59 | 60 | try 61 | { 62 | if (args.Name.Contains("System.Reflection.Metadata") || args.Name.Contains("Microsoft.CodeAnalysis")) 63 | { 64 | string filename = Path.GetDirectoryName(typeof(ViewModel).Assembly.Location); 65 | 66 | filename = Path.Combine(filename, 67 | args.Name.Split(',').FirstOrDefault() + ".dll"); 68 | 69 | if (File.Exists(filename)) 70 | { 71 | return Assembly 72 | .LoadFrom(filename); 73 | } 74 | } 75 | } 76 | catch (Exception ex) 77 | { 78 | 79 | } 80 | return null; 81 | } 82 | 83 | private void Runnable_OnDisplay1(object sender, DisplayEventArgs e) 84 | { 85 | var mimetype = Microsoft.DotNet.Interactive.Formatting.Formatter.GetPreferredMimeTypesFor(e.DisplayObject?.GetType()).First(); 86 | KernelContext.Display(e.DisplayObject, mimetype); 87 | } 88 | 89 | public string GetName() 90 | { 91 | return "my event"; 92 | } 93 | 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/ScriptSample.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.DB; 2 | using Autodesk.Revit.UI; 3 | 4 | namespace Jowsy.Revit.KernelAddin.Core 5 | { 6 | internal class ScriptSample : ICodeCommand 7 | { 8 | public event EventHandler OnDisplay; 9 | 10 | public (string, object) Execute(UIApplication uiapp) 11 | { 12 | //Because the revit kernel is run out of process embedded in the revit application we need to use the set magic command 13 | // UIDocument uidoc = default; 14 | var uidoc = uiapp.ActiveUIDocument; 15 | 16 | Document doc = uidoc.Document; 17 | 18 | // Set export options 19 | IFCExportOptions ifcExportOptions = new IFCExportOptions(); 20 | 21 | 22 | // Optionally, you can customize export options further, e.g., set specific configurations. 23 | // ifcExportOptions.SpaceBoundaries = IFCExportSpaceBoundaries.Boundaries; 24 | // ifcExportOptions.SplitWallsAndColumns = true; 25 | // ... 26 | 27 | // Perform the export 28 | Transaction transaction = new Transaction(doc, "Export IFC"); 29 | transaction.Start("start"); 30 | 31 | // Use the Export method of the Document class 32 | doc.Export("c:\\Temp\\", "export2.ifc", ifcExportOptions); 33 | 34 | transaction.Commit(); 35 | 36 | 37 | return (null, null); 38 | // transaction 39 | 40 | } 41 | 42 | public (string, object) Execute(UIApplication uiapp, Variables variables) 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Core/Variables.cs: -------------------------------------------------------------------------------- 1 | namespace Jowsy.Revit.KernelAddin.Core 2 | { 3 | public sealed class Variables 4 | { 5 | 6 | public event EventHandler VariablesChanged; 7 | 8 | private readonly Dictionary _variables = new Dictionary(); 9 | 10 | public Dictionary GetVariables() => _variables; 11 | 12 | public void Add(string name, object value) 13 | { 14 | 15 | if (!_variables.ContainsKey(name)) 16 | { 17 | _variables.Add(name, value); 18 | VariablesChanged?.Invoke(this, EventArgs.Empty); 19 | return; 20 | } 21 | 22 | _variables[name] = value; 23 | VariablesChanged?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public void Remove(string name, object value) 27 | { 28 | 29 | _variables.Remove(name); 30 | VariablesChanged?.Invoke(this, EventArgs.Empty); 31 | } 32 | 33 | public void Clear() 34 | { 35 | _variables.Clear(); 36 | VariablesChanged?.Invoke(this, EventArgs.Empty); 37 | } 38 | 39 | internal bool TryGetValue(string name, out object value) 40 | { 41 | return _variables.TryGetValue(name, out value); 42 | } 43 | 44 | internal bool TryGetValue(string name, out T value) 45 | { 46 | _variables.TryGetValue(name, out object objectValue); 47 | try 48 | { 49 | value = (T)objectValue; 50 | } 51 | catch (Exception) 52 | { 53 | value = default; 54 | return false; 55 | } 56 | return false; 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Jowsy.Revit.KernelAddin.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | true 4 | 10.0 5 | x64 6 | true 7 | false 8 | net48 9 | Debug R22;Debug R23;Debug R24 10 | $(Configurations);Release R22;Release R23;Release R24 11 | 12 | 13 | 2024 14 | $(DefineConstants);R24 15 | 16 | 17 | 2023 18 | $(DefineConstants);R23 19 | 20 | 21 | $(RevitVersion) 22 | true 23 | false 24 | 25 | true 26 | 27 | 28 | 29 | 30 | all 31 | runtime; build; native; contentfiles; analyzers; buildtransitive 32 | 33 | 34 | 35 | 36 | 37 | 38 | ..\..\lib\RetroUI.dll 39 | True 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | bin\AddIn $(RevitVersion) $(Configuration)\ 51 | $(RootDir)$(AssemblyName)\ 52 | bin\AddIn $(RevitVersion) $(Configuration) Install\ 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/NETInteractiveKernel.addin: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | NET Interactive Kernel for Revit 5 | Jowsy.Revit.KernelAddin\Jowsy.Revit.KernelAddin.dll 6 | 3E9B3B59-7931-4FDD-B4C1-204E5726D6A6 7 | Jowsy.Revit.KernelAddin.App 8 | https://github.com/jowsy 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RevitKernelUI.Properties 12 | { 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources 26 | { 27 | 28 | private static global::System.Resources.ResourceManager resourceMan; 29 | 30 | private static global::System.Globalization.CultureInfo resourceCulture; 31 | 32 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 33 | internal Resources() 34 | { 35 | } 36 | 37 | /// 38 | /// Returns the cached ResourceManager instance used by this class. 39 | /// 40 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 41 | internal static global::System.Resources.ResourceManager ResourceManager 42 | { 43 | get 44 | { 45 | if ((resourceMan == null)) 46 | { 47 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RevitKernelUI.Properties.Resources", typeof(Resources).Assembly); 48 | resourceMan = temp; 49 | } 50 | return resourceMan; 51 | } 52 | } 53 | 54 | /// 55 | /// Overrides the current thread's CurrentUICulture property for all 56 | /// resource lookups using this strongly typed resource class. 57 | /// 58 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 59 | internal static global::System.Globalization.CultureInfo Culture 60 | { 61 | get 62 | { 63 | return resourceCulture; 64 | } 65 | set 66 | { 67 | resourceCulture = value; 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Properties/Resources.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 | text/microsoft-resx 107 | 108 | 109 | 2.0 110 | 111 | 112 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 113 | 114 | 115 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace RevitKernelUI.Properties 12 | { 13 | 14 | 15 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 16 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] 17 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase 18 | { 19 | 20 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 21 | 22 | public static Settings Default 23 | { 24 | get 25 | { 26 | return defaultInstance; 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "RevitKernel": { 4 | "commandName": "Project" 5 | }, 6 | "Profile 1": { 7 | "commandName": "Executable", 8 | "executablePath": "C:\\Program Files\\Autodesk\\Revit 2024\\Revit.exe" 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/ShowCommand.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.Attributes; 2 | using Autodesk.Revit.DB; 3 | using Autodesk.Revit.UI; 4 | 5 | namespace Jowsy.Revit.KernelAddin 6 | { 7 | 8 | [Transaction(TransactionMode.Manual)] 9 | public class ShowCommand : IExternalCommand 10 | { 11 | public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) 12 | { 13 | DockablePane dockableWindow = commandData.Application.GetDockablePane(App.DockablePaneId); 14 | 15 | dockableWindow.Show(); 16 | 17 | return Result.Succeeded; 18 | } 19 | } 20 | 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/CommandViewItem.cs: -------------------------------------------------------------------------------- 1 | namespace Jowsy.Revit.KernelAddin.UI 2 | { 3 | public class CommandViewItem 4 | { 5 | public string KernelEvent { get; set; } 6 | 7 | public string Command { get; set; } 8 | public string TargetKernel { get; internal set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/EnumToStringConverter.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Reflection; 3 | using System.Windows; 4 | using System.Windows.Data; 5 | 6 | namespace Jowsy.Revit.KernelAddin.UI 7 | { 8 | //Converts ENum to strings, uses Description for the parameter passed in, or parameter as string 9 | [ValueConversion(typeof(Enum), typeof(String))] 10 | public class EnumToStringConverter : IValueConverter 11 | { 12 | public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 13 | { 14 | if (value != null) 15 | { 16 | FieldInfo fi = value.GetType().GetField(value.ToString()); 17 | if (fi != null) 18 | { 19 | var attributes = (DescriptionAttribute[])fi.GetCustomAttributes(typeof(DescriptionAttribute), false); 20 | return ((attributes.Length > 0) && (!String.IsNullOrEmpty(attributes[0].Description))) ? attributes[0].Description : value.ToString(); 21 | } 22 | } 23 | 24 | return DependencyProperty.UnsetValue; 25 | } 26 | 27 | public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/InverseBooleanConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Data; 2 | 3 | namespace Jowsy.Revit.KernelAddin.UI 4 | { 5 | [ValueConversion(typeof(bool), typeof(bool))] 6 | public class InverseBooleanConverter : IValueConverter 7 | { 8 | #region IValueConverter Members 9 | 10 | public object Convert(object value, Type targetType, object parameter, 11 | System.Globalization.CultureInfo culture) 12 | { 13 | if (targetType != typeof(bool)) 14 | throw new InvalidOperationException("The target must be a boolean"); 15 | 16 | return !(bool)value; 17 | } 18 | 19 | public object ConvertBack(object value, Type targetType, object parameter, 20 | System.Globalization.CultureInfo culture) 21 | { 22 | throw new NotSupportedException(); 23 | } 24 | 25 | #endregion 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/KernelDockablePaneProvider.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.UI; 2 | 3 | namespace Jowsy.Revit.KernelAddin.UI 4 | { 5 | internal class KernelDockablePaneProvider : IDockablePaneProvider 6 | { 7 | private KernelViewer _dockableWindow; 8 | internal static ViewModel _viewModel; 9 | 10 | public KernelDockablePaneProvider(ViewModel viewModel) 11 | { 12 | _viewModel = viewModel ?? throw new ArgumentNullException(nameof(viewModel)); 13 | } 14 | 15 | /// 16 | /// Builds the dockable pane 17 | /// 18 | /// 19 | public void SetupDockablePane(DockablePaneProviderData data) 20 | { 21 | 22 | // Create a new instance of the dockable window 23 | _dockableWindow = new KernelViewer(); 24 | _dockableWindow.DataContext = _viewModel; 25 | _dockableWindow.Title = ".NET Interactive Revit Kernel"; 26 | 27 | // Connect to the providerData 28 | data.FrameworkElement = _dockableWindow; 29 | data.InitialState = new DockablePaneState(); 30 | data.InitialState.DockPosition = DockPosition.Tabbed; 31 | data.VisibleByDefault = false; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/KernelObserver.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.DotNet.Interactive.Events; 2 | 3 | namespace Jowsy.Revit.KernelAddin.UI 4 | { 5 | public class KernelObserver : IObserver 6 | { 7 | public KernelObserver(ViewModel viewModel) 8 | { 9 | ViewModel = viewModel; 10 | } 11 | 12 | public ViewModel ViewModel { get; } 13 | 14 | public void OnCompleted() 15 | { 16 | // throw new NotImplementedException(); 17 | } 18 | 19 | public void OnError(Exception error) 20 | { 21 | // throw new NotImplementedException(); 22 | } 23 | 24 | public void OnNext() 25 | { 26 | 27 | } 28 | 29 | public void OnNext(KernelEvent value) 30 | { 31 | ViewModel.KernelCommands.Add(new CommandViewItem() 32 | { 33 | KernelEvent = value.GetType().Name, 34 | Command = value.Command.GetType().Name, 35 | TargetKernel = value.Command.TargetKernelName 36 | 37 | 38 | }); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/KernelStatus.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | 3 | namespace Jowsy.Revit.KernelAddin.UI 4 | { 5 | public enum KernelStatus 6 | { 7 | [Description(nameof(Stopped))] 8 | Stopped = 0, 9 | [Description("Awaiting connection...")] 10 | AwaitingConnection = 1, 11 | [Description(nameof(Connected))] 12 | Connected = 2 13 | } 14 | } -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/KernelViewer.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 32 | 33 | 34 | 35 | 36 | 41 | 42 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 67 | 68 | 69 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/KernelViewer.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Controls; 2 | 3 | namespace Jowsy.Revit.KernelAddin.UI 4 | { 5 | /// 6 | /// Interaction logic for KernelViewer.xaml 7 | /// 8 | public partial class KernelViewer : Page 9 | { 10 | public KernelViewer() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/RelayCommand.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Windows.Input; 3 | 4 | namespace Jowsy.Revit.KernelAddin.UI 5 | { 6 | public class RelayCommand : ICommand 7 | { 8 | #region Fields 9 | readonly Action _execute; 10 | readonly Predicate _canExecute; 11 | #endregion // Fields 12 | #region Constructors 13 | public RelayCommand(Action execute) : this(execute, null) { } 14 | public RelayCommand(Action execute, Predicate canExecute) 15 | { 16 | if (execute == null) 17 | throw new ArgumentNullException("execute"); 18 | _execute = execute; _canExecute = canExecute; 19 | } 20 | #endregion // Constructors 21 | #region ICommand Members 22 | [DebuggerStepThrough] 23 | public bool CanExecute(object parameter) 24 | { 25 | return _canExecute == null ? true : _canExecute(parameter); 26 | } 27 | public event EventHandler CanExecuteChanged 28 | { 29 | add { CommandManager.RequerySuggested += value; } 30 | remove { CommandManager.RequerySuggested -= value; } 31 | } 32 | public void Execute(object parameter) { _execute(parameter); } 33 | #endregion // ICommand Members 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/SettingsWindow.xaml: -------------------------------------------------------------------------------- 1 |  11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/SettingsWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.DotNet.Interactive.Formatting; 2 | using System.Windows; 3 | 4 | namespace Jowsy.Revit.KernelAddin.UI 5 | { 6 | /// 7 | /// Interaction logic for SettingsWindow.xaml 8 | /// 9 | public partial class SettingsWindow : Window 10 | { 11 | public SettingsWindow() 12 | { 13 | InitializeComponent(); 14 | 15 | //TODO: Use MVVM-pattern 16 | 17 | //This is not how WPF should be used but it is quick :) 18 | txtRecursionLimit.Text = Formatter.RecursionLimit.ToString(); 19 | } 20 | 21 | private void Button_Click(object sender, RoutedEventArgs e) 22 | { 23 | if (!int.TryParse(txtRecursionLimit.Text, out int recursionLimit)) 24 | { 25 | MessageBox.Show("Recursion limit must be an integer"); 26 | return; 27 | } 28 | 29 | Formatter.RecursionLimit = recursionLimit; 30 | 31 | this.Close(); 32 | } 33 | 34 | private void Button_Click_1(object sender, RoutedEventArgs e) 35 | { 36 | this.Close(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/VariableViewItem.cs: -------------------------------------------------------------------------------- 1 | namespace Jowsy.Revit.KernelAddin.UI 2 | { 3 | public class VariableViewItem 4 | { 5 | public string Name { get; set; } 6 | public string Value { get; set; } 7 | public string Type { get; set; } 8 | } 9 | } -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/ViewModel.cs: -------------------------------------------------------------------------------- 1 | using Jowsy.Revit.KernelAddin.Core; 2 | using Microsoft.DotNet.Interactive; 3 | using Microsoft.DotNet.Interactive.Connection; 4 | using System.Collections.ObjectModel; 5 | using System.IO.Pipes; 6 | using System.Reactive.Linq; 7 | 8 | namespace Jowsy.Revit.KernelAddin.UI 9 | { 10 | public class ViewModel : ViewModelBase, IDisposable 11 | { 12 | public RelayCommand StartCommand { get; } 13 | public RelayCommand RestartCommand { get; } 14 | public RelayCommand SettingsCommand { get; } 15 | 16 | private readonly string _revitVersion; 17 | private ObservableCollection _kernelCommands = new ObservableCollection(); 18 | private Variables _variablesStore; 19 | private CompositeKernel _kernel; 20 | private KernelStatus _kernelStatus; 21 | 22 | public KernelStatus KernelStatus 23 | { 24 | get { return _kernelStatus; } 25 | set 26 | { 27 | _kernelStatus = value; 28 | OnPropertyChanged(nameof(KernelStatus)); 29 | OnPropertyChanged(nameof(StartButtonEnabled)); 30 | } 31 | } 32 | 33 | public bool StartButtonEnabled => KernelStatus == KernelStatus.Stopped; 34 | 35 | public ObservableCollection KernelCommands 36 | { 37 | get { return _kernelCommands; } 38 | set 39 | { 40 | _kernelCommands = value; 41 | OnPropertyChanged(nameof(KernelCommands)); 42 | } 43 | } 44 | 45 | private ObservableCollection _variables = new ObservableCollection(); 46 | public ObservableCollection Variables 47 | { 48 | get { return _variables; } 49 | set 50 | { 51 | _variables = value; 52 | OnPropertyChanged(nameof(Variables)); 53 | } 54 | } 55 | 56 | public string NamedPipeName { get => $"revit-kernel-{_revitVersion}-pipe"; } 57 | 58 | public ViewModel(string revitVersion) 59 | { 60 | this._revitVersion = revitVersion ?? throw new ArgumentNullException(nameof(revitVersion)); 61 | 62 | _variablesStore = new Variables(); 63 | _variablesStore.VariablesChanged += _variablesStore_VariablesChanged; 64 | 65 | StartCommand = new RelayCommand(async (c) => 66 | { 67 | await InitKernel(); 68 | }); 69 | 70 | 71 | RestartCommand = new RelayCommand(async (c) => 72 | { 73 | DisposeKernel(); 74 | await InitKernel(); 75 | }); 76 | 77 | SettingsCommand = new RelayCommand((c) => 78 | { 79 | var settingsWindow = new SettingsWindow(); 80 | settingsWindow.ShowDialog(); 81 | }); 82 | 83 | } 84 | public void DisposeKernel() 85 | { 86 | _kernel.Dispose(); 87 | 88 | _variablesStore.Clear(); 89 | 90 | Variables?.Clear(); 91 | 92 | KernelCommands?.Clear(); 93 | } 94 | 95 | public async Task InitKernel() 96 | { 97 | _kernel = new CompositeKernel(); 98 | _kernel.KernelEvents.ObserveOn(SynchronizationContext.Current).Subscribe(new KernelObserver(this), new CancellationToken()); 99 | 100 | var revitKernel = new RevitKernel("RevitKernel", _variablesStore); 101 | _kernel.Add(revitKernel); 102 | revitKernel.UseValueSharing(); 103 | 104 | SetUpNamedPipeKernelConnection(); 105 | 106 | KernelStatus = KernelStatus.AwaitingConnection; 107 | } 108 | 109 | private async Task SetupInitialVariables() 110 | { 111 | App.KernelInitEventHandler.Tcs = new TaskCompletionSource(); 112 | 113 | App.InitKernelEvent.Raise(); 114 | 115 | await App.KernelInitEventHandler.Tcs.Task; 116 | 117 | var uiApp = App.KernelInitEventHandler.UIApplication; 118 | _variablesStore.Add("uiapp", uiApp); 119 | _variablesStore.Add("uidoc", uiApp.ActiveUIDocument); 120 | _variablesStore.Add("doc", uiApp.ActiveUIDocument.Document); 121 | 122 | } 123 | 124 | private void _variablesStore_VariablesChanged(object sender, EventArgs e) 125 | { 126 | Variables = new ObservableCollection(_variablesStore.GetVariables() 127 | .Select(v => new VariableViewItem() 128 | { 129 | Name = v.Key, 130 | Type = v.Value?.GetType().Name, 131 | Value = v.Value?.ToString() 132 | })); 133 | } 134 | public bool KernelIsRunning() 135 | { 136 | return _kernel != null; 137 | } 138 | private void SetUpNamedPipeKernelConnection() 139 | { 140 | var serverStream = new NamedPipeServerStream( 141 | NamedPipeName, 142 | PipeDirection.InOut, 143 | 1, 144 | PipeTransmissionMode.Message, 145 | PipeOptions.Asynchronous); 146 | 147 | var sender = KernelCommandAndEventSender.FromNamedPipe( 148 | serverStream, 149 | new Uri("kernel://remote-control")); 150 | var receiver = KernelCommandAndEventReceiver.FromNamedPipe(serverStream); 151 | 152 | var host = _kernel.UseHost(sender, receiver, new Uri($"kernel://revit-kernel-{_revitVersion}")); 153 | 154 | _kernel.RegisterForDisposal(host); 155 | _kernel.RegisterForDisposal(receiver); 156 | _kernel.RegisterForDisposal(serverStream); 157 | 158 | var _ = Task.Run(async () => 159 | { 160 | // required as waiting connection on named pipe server will block 161 | serverStream.WaitForConnection(); 162 | var _host = host.ConnectAsync(); 163 | await SetupInitialVariables(); 164 | KernelStatus = KernelStatus.Connected; 165 | 166 | }); 167 | } 168 | 169 | public void Dispose() 170 | { 171 | _kernel.Dispose(); 172 | } 173 | } 174 | 175 | } 176 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/UI/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Jowsy.Revit.KernelAddin.UI 5 | { 6 | public abstract class ViewModelBase : INotifyPropertyChanged 7 | { 8 | public event PropertyChangedEventHandler PropertyChanged; 9 | 10 | protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/dnMerge.config: -------------------------------------------------------------------------------- 1 | 2 | false 3 | true 4 | 5 | bofnet.dll 6 | 7 | -------------------------------------------------------------------------------- /src/Jowsy.Revit.KernelAddin/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/install/Installer.Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using WixSharp; 7 | 8 | namespace Installer; 9 | 10 | public static class Generator 11 | { 12 | public static WixEntity[] GenerateWixEntities(IEnumerable args) 13 | { 14 | var versionRegex = new Regex(@"\d+"); 15 | var versionStorages = new Dictionary>(); 16 | 17 | foreach (var directory in args) 18 | { 19 | var directoryInfo = new DirectoryInfo(directory); 20 | var fileVersion = versionRegex.Match(directoryInfo.Name).Value; 21 | var feature = new Feature 22 | { 23 | Name = $"Revit {fileVersion}", 24 | Description = $"Install add-in for Revit {fileVersion}", 25 | ConfigurableDir = $"INSTALL{fileVersion}" 26 | }; 27 | 28 | var files = new Files(feature, $@"{directory}\*.*"); 29 | if (versionStorages.TryGetValue(fileVersion, out var storage)) 30 | storage.Add(files); 31 | else 32 | versionStorages.Add(fileVersion, [files]); 33 | 34 | var assemblies = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); 35 | Console.WriteLine($"Installer files for version '{fileVersion}':"); 36 | foreach (var assembly in assemblies) Console.WriteLine($"'{assembly}'"); 37 | } 38 | 39 | return versionStorages 40 | .Select(storage => new Dir(new Id($"INSTALL{storage.Key}"), storage.Key, storage.Value.ToArray())) 41 | .Cast() 42 | .ToArray(); 43 | } 44 | } -------------------------------------------------------------------------------- /src/install/Installer.cs: -------------------------------------------------------------------------------- 1 | using Installer; 2 | using System; 3 | using WixSharp; 4 | using WixSharp.CommonTasks; 5 | using WixSharp.Controls; 6 | using Assembly = System.Reflection.Assembly; 7 | 8 | const string outputName = "RevitInteractiveKernel"; 9 | const string projectName = "Revit Interactive Kernel Addin"; 10 | 11 | var project = new Project 12 | { 13 | OutDir = "output", 14 | Name = projectName, 15 | Platform = Platform.x64, 16 | UI = WUI.WixUI_FeatureTree, 17 | MajorUpgrade = MajorUpgrade.Default, 18 | Description = "Enables support for running Revit as an interactive kernel and executing code from a notebook environment such as VS Code Polyglot Notebook.", 19 | GUID = new Guid("3A1CED73-19D0-47B2-B127-0F28098FBF2E"), 20 | //BannerImage = @"install\Resources\Icons\BannerImage.png", 21 | // BackgroundImage = @"install\Resources\Icons\BackgroundImage.png", 22 | Version = Assembly.GetExecutingAssembly().GetName().Version.ClearRevision(), 23 | ControlPanelInfo = 24 | { 25 | Manufacturer = "Joel Waldheim Saury" 26 | //ProductIcon = @"install\Resources\Icons\ShellIcon.ico" 27 | } 28 | }; 29 | 30 | var wixEntities = Generator.GenerateWixEntities(args); 31 | project.RemoveDialogsBetween(NativeDialogs.WelcomeDlg, NativeDialogs.CustomizeDlg); 32 | 33 | BuildSingleUserMsi(); 34 | BuildMultiUserUserMsi(); 35 | 36 | void BuildSingleUserMsi() 37 | { 38 | project.InstallScope = InstallScope.perUser; 39 | project.OutFileName = $"{outputName}-{project.Version}-SingleUser"; 40 | project.Dirs = 41 | [ 42 | new InstallDir(@"%AppDataFolder%\Autodesk\Revit\Addins\", wixEntities) 43 | ]; 44 | project.BuildMsi(); 45 | } 46 | 47 | void BuildMultiUserUserMsi() 48 | { 49 | project.InstallScope = InstallScope.perMachine; 50 | project.OutFileName = $"{outputName}-{project.Version}-MultiUser"; 51 | project.Dirs = 52 | [ 53 | new InstallDir(@"%CommonAppDataFolder%\Autodesk\Revit\Addins\", wixEntities) 54 | ]; 55 | project.BuildMsi(); 56 | } -------------------------------------------------------------------------------- /src/install/Installer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | latest 6 | x64 7 | net48 8 | 0.1.0 9 | false 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | Always 20 | 21 | 22 | Always 23 | 24 | 25 | Always 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /src/install/Resources/Icons/BackgroundImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/src/install/Resources/Icons/BackgroundImage.png -------------------------------------------------------------------------------- /src/install/Resources/Icons/BannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/src/install/Resources/Icons/BannerImage.png -------------------------------------------------------------------------------- /src/install/Resources/Icons/ShellIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/src/install/Resources/Icons/ShellIcon.ico -------------------------------------------------------------------------------- /src/screenshot-variables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jowsy/bim-net-interactive/0fe23c5b279e2374c1444c6d19fe426f5ae1865c/src/screenshot-variables.png --------------------------------------------------------------------------------