From 803cc3ef00d85c8442425109ab4bf9d689c6de4e Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Wed, 3 Apr 2024 22:18:51 +0200 Subject: [PATCH 01/25] Target net481 separately in IPA.Bcfier project --- src/IPA.Bcfier/IPA.Bcfier.csproj | 2 +- src/IPA.Bcfier/Services/SettingsService.cs | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/IPA.Bcfier/IPA.Bcfier.csproj b/src/IPA.Bcfier/IPA.Bcfier.csproj index ce685977..c5383e96 100644 --- a/src/IPA.Bcfier/IPA.Bcfier.csproj +++ b/src/IPA.Bcfier/IPA.Bcfier.csproj @@ -1,7 +1,7 @@  - netstandard2.1 + netstandard2.1;net481 enable latest diff --git a/src/IPA.Bcfier/Services/SettingsService.cs b/src/IPA.Bcfier/Services/SettingsService.cs index a549b796..7bf39dc7 100644 --- a/src/IPA.Bcfier/Services/SettingsService.cs +++ b/src/IPA.Bcfier/Services/SettingsService.cs @@ -19,10 +19,11 @@ public async Task LoadSettingsAsync() }; } - var serializedSettings = await File.ReadAllTextAsync(settingsPath); + using var settingsFileStream = File.OpenRead(settingsPath); + using var streamReader = new StreamReader(settingsFileStream); + var serializedSettings = await streamReader.ReadToEndAsync(); var deserializedSettings = JsonConvert.DeserializeObject(serializedSettings); - if (deserializedSettings == null) { return new Settings @@ -37,7 +38,9 @@ public async Task LoadSettingsAsync() public async Task SaveSettingsAsync(Settings settings) { var serializedSettings = JsonConvert.SerializeObject(settings); - await File.WriteAllTextAsync(GetPathToSettingsFile(), serializedSettings); + using var settingsFileStream = File.OpenWrite(GetPathToSettingsFile()); + using var streamWriter = new StreamWriter(settingsFileStream); + await streamWriter.WriteAsync(serializedSettings); } private string GetPathToSettingsFile() From ff770a6e2e0e1f6004a6d089ee02100956f06766 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 15:09:52 +0200 Subject: [PATCH 02/25] Increase target size limits for build outputs --- src/ipa-bcfier-ui/angular.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ipa-bcfier-ui/angular.json b/src/ipa-bcfier-ui/angular.json index aaeba5df..05f4a94c 100644 --- a/src/ipa-bcfier-ui/angular.json +++ b/src/ipa-bcfier-ui/angular.json @@ -32,8 +32,8 @@ "budgets": [ { "type": "initial", - "maximumWarning": "500kb", - "maximumError": "1mb" + "maximumWarning": "5mb", + "maximumError": "10mb" }, { "type": "anyComponentStyle", From 18c79c9203962d0f54dc6ccc22f9bd04104ec502 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 15:11:42 +0200 Subject: [PATCH 03/25] Add browser safe way for generating Guids --- src/ipa-bcfier-ui/package-lock.json | 28 ++++++++++++++++--- src/ipa-bcfier-ui/package.json | 2 ++ .../add-snapshot-viewpoint.component.ts | 3 +- .../components/bcf-file/bcf-file.component.ts | 3 +- .../comments-detail.component.ts | 3 +- src/ipa-bcfier-ui/src/app/functions/uuid.ts | 9 ++++++ .../services/bcf-files-messenger.service.ts | 3 +- 7 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 src/ipa-bcfier-ui/src/app/functions/uuid.ts diff --git a/src/ipa-bcfier-ui/package-lock.json b/src/ipa-bcfier-ui/package-lock.json index 62342e07..80b664e0 100644 --- a/src/ipa-bcfier-ui/package-lock.json +++ b/src/ipa-bcfier-ui/package-lock.json @@ -23,6 +23,7 @@ "ngx-toastr": "18.0.0", "rxjs": "~7.8.0", "tslib": "^2.3.0", + "uuid": "9.0.1", "zone.js": "~0.14.3" }, "devDependencies": { @@ -30,6 +31,7 @@ "@angular/cli": "^17.3.2", "@angular/compiler-cli": "^17.3.0", "@types/jasmine": "~5.1.0", + "@types/uuid": "9.0.8", "google-fonts-helper": "^3.5.0", "jasmine-core": "~5.1.0", "karma": "~6.4.0", @@ -4557,6 +4559,12 @@ "@types/node": "*" } }, + "node_modules/@types/uuid": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", + "integrity": "sha512-jg+97EGIcY9AGHJJRaaPVgetKDsrTgbRjQ5Msgjh/DQKEFl0DtyRr/VCOyD1T2R1MNeWPK/u7JoGhlDZnKBAfA==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.10", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", @@ -11492,6 +11500,15 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.1", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz", @@ -12290,10 +12307,13 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "bin": { "uuid": "dist/bin/uuid" } diff --git a/src/ipa-bcfier-ui/package.json b/src/ipa-bcfier-ui/package.json index 574fcb6b..bc69cd13 100644 --- a/src/ipa-bcfier-ui/package.json +++ b/src/ipa-bcfier-ui/package.json @@ -24,6 +24,7 @@ "@ngx-dropzone/cdk": "^17.2.0", "@ngx-dropzone/material": "^17.2.0", "ngx-toastr": "18.0.0", + "uuid": "9.0.1", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.14.3" @@ -33,6 +34,7 @@ "@angular/cli": "^17.3.2", "@angular/compiler-cli": "^17.3.0", "@types/jasmine": "~5.1.0", + "@types/uuid": "9.0.8", "google-fonts-helper": "^3.5.0", "jasmine-core": "~5.1.0", "karma": "~6.4.0", diff --git a/src/ipa-bcfier-ui/src/app/components/add-snapshot-viewpoint/add-snapshot-viewpoint.component.ts b/src/ipa-bcfier-ui/src/app/components/add-snapshot-viewpoint/add-snapshot-viewpoint.component.ts index e8f35eeb..c29dc892 100644 --- a/src/ipa-bcfier-ui/src/app/components/add-snapshot-viewpoint/add-snapshot-viewpoint.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/add-snapshot-viewpoint/add-snapshot-viewpoint.component.ts @@ -13,6 +13,7 @@ import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; +import { getNewRandomGuid } from '../../functions/uuid'; @Component({ selector: 'bcfier-add-snapshot-viewpoint', @@ -48,7 +49,7 @@ export class AddSnapshotViewpointComponent { const base64 = await this.readFileAsBase64(file); const viewpoint: BcfViewpoint = { - id: crypto.randomUUID(), + id: getNewRandomGuid(), clippingPlanes: [], lines: [], viewpointComponents: { diff --git a/src/ipa-bcfier-ui/src/app/components/bcf-file/bcf-file.component.ts b/src/ipa-bcfier-ui/src/app/components/bcf-file/bcf-file.component.ts index 8d3215a7..81ace222 100644 --- a/src/ipa-bcfier-ui/src/app/components/bcf-file/bcf-file.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/bcf-file/bcf-file.component.ts @@ -11,6 +11,7 @@ import { MatProgressBarModule } from '@angular/material/progress-bar'; import { TopicDetailComponent } from '../topic-detail/topic-detail.component'; import { TopicFilterPipe } from '../../pipes/topic-filter.pipe'; import { TopicPreviewImageDirective } from '../../directives/topic-preview-image.directive'; +import { getNewRandomGuid } from '../../functions/uuid'; @Component({ selector: 'bcfier-bcf-file', @@ -54,7 +55,7 @@ export class BcfFileComponent { addIssue(): void { const newIssue: BcfTopic = { comments: [], - id: crypto.randomUUID(), + id: getNewRandomGuid(), files: [], labels: [], referenceLinks: [], diff --git a/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.ts b/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.ts index 42fb51fd..8038bd7f 100644 --- a/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.ts +++ b/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.ts @@ -12,6 +12,7 @@ import { MatInputModule } from '@angular/material/input'; import { NotificationsService } from '../../services/notifications.service'; import { SettingsMessengerService } from '../../services/settings-messenger.service'; import { ViewpointImageDirective } from '../../directives/viewpoint-image.directive'; +import { getNewRandomGuid } from '../../functions/uuid'; import { take } from 'rxjs'; @Component({ @@ -53,7 +54,7 @@ export class CommentsDetailComponent implements OnInit { .pipe(take(1)) .subscribe((settings) => { const newComment = { - id: crypto.randomUUID(), + id: getNewRandomGuid(), author: settings.username, creationDate: new Date(), viewpointId: this.viewpoint?.id, diff --git a/src/ipa-bcfier-ui/src/app/functions/uuid.ts b/src/ipa-bcfier-ui/src/app/functions/uuid.ts new file mode 100644 index 00000000..4b4f0277 --- /dev/null +++ b/src/ipa-bcfier-ui/src/app/functions/uuid.ts @@ -0,0 +1,9 @@ +import { v4 as uuidv4 } from 'uuid'; + +export function getNewRandomGuid(): string { + if (crypto && crypto.randomUUID) { + return crypto.randomUUID(); + } + + return uuidv4(); +} diff --git a/src/ipa-bcfier-ui/src/app/services/bcf-files-messenger.service.ts b/src/ipa-bcfier-ui/src/app/services/bcf-files-messenger.service.ts index 371d7eaf..2b0dbfac 100644 --- a/src/ipa-bcfier-ui/src/app/services/bcf-files-messenger.service.ts +++ b/src/ipa-bcfier-ui/src/app/services/bcf-files-messenger.service.ts @@ -2,6 +2,7 @@ import { ReplaySubject, Subject } from 'rxjs'; import { BcfFile } from '../../generated/models'; import { Injectable } from '@angular/core'; +import { getNewRandomGuid } from '../functions/uuid'; @Injectable({ providedIn: 'root', @@ -24,7 +25,7 @@ export class BcfFilesMessengerService { topics: [], fileAttachments: [], project: { - id: crypto.randomUUID(), + id: getNewRandomGuid(), }, projectExtensions: { priorities: [], From 24434e44793ccfebfc33ee635bdd74bb4a1e0e06 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 15:16:11 +0200 Subject: [PATCH 04/25] Add initial Revit plugin --- .gitignore | 2 + .nuke/build.schema.json | 4 ++ IPA.BCFier.sln | 39 ++++++++++++ build/Build.cs | 28 ++++++++- .../BcfierJavascriptBridge.cs | 41 ++++++++++++ src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.addin | 11 ++++ src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj | 39 ++++++++++++ src/IPA.Bcfier.Revit/IdlingHandler.cs | 12 ++++ src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs | 31 +++++++++ .../OpenIpaBcfierWindowCommand.cs | 53 ++++++++++++++++ .../PluginDebugRequestHandler.cs | 23 +++++++ src/IPA.Bcfier.Revit/PluginRequestHandler.cs | 21 +++++++ .../PluginResourceRequestHandler.cs | 59 ++++++++++++++++++ src/IPA.Bcfier.Revit/Resources/ButtonLogo.png | Bin 0 -> 26157 bytes .../src/app/services/RevitBackendService.ts | 43 ++++++++++++- 15 files changed, 401 insertions(+), 5 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs create mode 100644 src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.addin create mode 100644 src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj create mode 100644 src/IPA.Bcfier.Revit/IdlingHandler.cs create mode 100644 src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs create mode 100644 src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs create mode 100644 src/IPA.Bcfier.Revit/PluginDebugRequestHandler.cs create mode 100644 src/IPA.Bcfier.Revit/PluginRequestHandler.cs create mode 100644 src/IPA.Bcfier.Revit/PluginResourceRequestHandler.cs create mode 100644 src/IPA.Bcfier.Revit/Resources/ButtonLogo.png diff --git a/.gitignore b/.gitignore index 434e291c..ec6be201 100644 --- a/.gitignore +++ b/.gitignore @@ -199,3 +199,5 @@ output/ nuke2/ src/IPA.Bcfier.App/wwwroot/dist/ + +src/IPA.Bcfier.Revit/Resources/Browser/ diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index fb525438..21a00ce9 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -102,6 +102,8 @@ "BuildDocFxMetadata", "BuildDocumentation", "BuildElectronApp", + "BuildFrontend", + "BuildRevitPlugin", "Clean", "Compile", "FrontEndRestore", @@ -129,6 +131,8 @@ "BuildDocFxMetadata", "BuildDocumentation", "BuildElectronApp", + "BuildFrontend", + "BuildRevitPlugin", "Clean", "Compile", "FrontEndRestore", diff --git a/IPA.BCFier.sln b/IPA.BCFier.sln index 3ea5ce69..947c5cfa 100644 --- a/IPA.BCFier.sln +++ b/IPA.BCFier.sln @@ -38,6 +38,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IPA.Bcfier.App", "src\IPA.Bcfier.App\IPA.Bcfier.App.csproj", "{F9C9F3AA-C668-4DFC-A540-F9F56B2438A4}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IPA.Bcfier.Revit", "src\IPA.Bcfier.Revit\IPA.Bcfier.Revit.csproj", "{75D281D7-5E29-46E5-A2A5-95569A7512DC}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -350,6 +352,42 @@ Global {F9C9F3AA-C668-4DFC-A540-F9F56B2438A4}.Release-2021|Any CPU.Build.0 = Release|Any CPU {F9C9F3AA-C668-4DFC-A540-F9F56B2438A4}.Release-2022|Any CPU.ActiveCfg = Release|Any CPU {F9C9F3AA-C668-4DFC-A540-F9F56B2438A4}.Release-2022|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2015|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2015|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2016|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2016|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2017|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2017|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2018|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2018|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2019|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2019|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2020|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2020|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2021|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2021|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2022|Any CPU.ActiveCfg = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Debug-2022|Any CPU.Build.0 = Debug|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2015|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2015|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2016|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2016|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2017|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2017|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2018|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2018|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2019|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2019|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2020|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2020|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2021|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2021|Any CPU.Build.0 = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2022|Any CPU.ActiveCfg = Release|Any CPU + {75D281D7-5E29-46E5-A2A5-95569A7512DC}.Release-2022|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -358,6 +396,7 @@ Global {222E3FA9-3D2D-4698-9C23-1E72A7B737C6} = {F4027890-2807-4012-9BB8-B447DBB5D8B7} {7A585522-0DDE-4F44-8D77-8D84BAA3AAC0} = {97D50745-F340-4DC1-86A8-58C2D7B7D8E3} {F9C9F3AA-C668-4DFC-A540-F9F56B2438A4} = {F4027890-2807-4012-9BB8-B447DBB5D8B7} + {75D281D7-5E29-46E5-A2A5-95569A7512DC} = {F4027890-2807-4012-9BB8-B447DBB5D8B7} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {440C743A-A1DD-46EA-A56F-5A92471914C6} diff --git a/build/Build.cs b/build/Build.cs index c8b46a6d..1156b525 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -226,14 +226,12 @@ public static class FileVersionProvider Npm("ci", SourceDirectory / "ipa-bcfier-ui"); }); - Target BuildElectronApp => _ => _ + Target BuildFrontend => _ => _ .DependsOn(Clean) - .DependsOn(Compile) .DependsOn(FrontEndRestore) .DependsOn(GenerateFrontendVersion) .Executes(() => { - // This builds the frontend NpmRun(c => { c = c @@ -242,7 +240,31 @@ public static class FileVersionProvider return c; }); + }); + + Target BuildRevitPlugin => _ => _ + .DependsOn(BuildFrontend) + .DependsOn(Compile) + .Executes(() => + { + CopyDirectoryRecursively(SourceDirectory / "ipa-bcfier-ui" / "dist" / "ipa-bcfier-ui" / "browser", SourceDirectory / "IPA.Bcfier.Revit" / "Resources" / "Browser", DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite); + + // Now there's a problem with files that have dashes in them, which we don' + + DotNetBuild(c => c.SetProjectFile(SourceDirectory / "IPA.Bcfier.Revit" / "IPA.Bcfier.Revit.csproj") + .SetConfiguration("Release") + .SetAssemblyVersion(GitVersion.AssemblySemVer) + .SetFileVersion(GitVersion.AssemblySemVer) + .SetInformationalVersion(GitVersion.InformationalVersion) + .EnableNoRestore()); + }); + + Target BuildElectronApp => _ => _ + .DependsOn(BuildFrontend) + .DependsOn(Compile) + .Executes(() => + { CopyDirectoryRecursively(SourceDirectory / "ipa-bcfier-ui" / "dist" / "ipa-bcfier-ui" / "browser", SourceDirectory / "IPA.Bcfier.App" / "wwwroot" / "dist" / "en", DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite); // To ensure the tool is always up to date diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs new file mode 100644 index 00000000..27a6fc15 --- /dev/null +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -0,0 +1,41 @@ +using CefSharp; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace IPA.Bcfier.Revit +{ + public class BcfierJavascriptBridge + { + private class DataClass + { + public string Command { get; set; } + + public string Data { get; set; } + } + + public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCallback) + { + var classData = JsonConvert.DeserializeObject(data)!; + + DefaultContractResolver contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + var serializerSettings = new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }; + + if (classData.Command == "getSettings") + { + var userSettings = await new Services.SettingsService().LoadSettingsAsync(); + await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); + } + else + { + // TODO return error for unrecognized commands + } + } + } +} diff --git a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.addin b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.addin new file mode 100644 index 00000000..f7ee13db --- /dev/null +++ b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.addin @@ -0,0 +1,11 @@ + + + + IPA.BCFier + IPA.Bcfier.Revit.IpaBcfierRevitPlugin + IPA.Bcfier.Revit.dll + cfd740a5-8089-4323-a6e5-c86cfee9cca2 + Dangl IT GmbH + www.dangl-it.com + + \ No newline at end of file diff --git a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj new file mode 100644 index 00000000..efffa32a --- /dev/null +++ b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj @@ -0,0 +1,39 @@ + + + + net481 + x64 + enable + enable + latest + DEBUG_BUILD + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + diff --git a/src/IPA.Bcfier.Revit/IdlingHandler.cs b/src/IPA.Bcfier.Revit/IdlingHandler.cs new file mode 100644 index 00000000..72705d3b --- /dev/null +++ b/src/IPA.Bcfier.Revit/IdlingHandler.cs @@ -0,0 +1,12 @@ +using Autodesk.Revit.UI.Events; + +namespace IPA.Bcfier.Revit +{ + public static class IdlingHandler + { + public static void OnIdling(object sender, IdlingEventArgs args) + { + // Not doing anything right now + } + } +} diff --git a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs new file mode 100644 index 00000000..ce0dd06d --- /dev/null +++ b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs @@ -0,0 +1,31 @@ +using Autodesk.Revit.UI; +using System.Reflection; +using System.Windows.Media.Imaging; + +namespace IPA.Bcfier.Revit +{ + public class IpaBcfierRevitPlugin : IExternalApplication + { + public Result OnStartup(UIControlledApplication application) + { + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream("IPA.Bcfier.Revit.Resources.ButtonLogo.png"); + bitmapImage.EndInit(); + + var buttonData = new PushButtonData("openPluginButton", "Open Plugin", Assembly.GetExecutingAssembly().Location, "IPA.Bcfier.Revit.OpenIpaBcfierWindowCommand"); + var pushButton = application.CreateRibbonPanel("IPA").AddItem(buttonData) as PushButton; + pushButton.ToolTip = "Launch IPA.Bcfier Revit Plugin"; + pushButton.LargeImage = bitmapImage; + + application.Idling += IdlingHandler.OnIdling; + + return Result.Succeeded; + } + + public Result OnShutdown(UIControlledApplication application) + { + return Result.Succeeded; + } + } +} diff --git a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs new file mode 100644 index 00000000..a8ddb4f2 --- /dev/null +++ b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs @@ -0,0 +1,53 @@ +using Autodesk.Revit.Attributes; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using CefSharp; +using CefSharp.Wpf; +using System.Reflection; +using System.Windows; +using System.Windows.Media.Imaging; + +namespace IPA.Bcfier.Revit +{ + [Transaction(TransactionMode.Manual)] + public class OpenIpaBcfierWindowCommand : IExternalCommand + { + public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) + { + var browser = new ChromiumWebBrowser(); +#if DEBUG_BUILD + var proxyToLocalhost = true; +#else + var proxyToLocalhost = false; +#endif + browser.JavascriptObjectRepository.Register("bcfierJavascriptBridge", new BcfierJavascriptBridge(), true); + browser.RequestHandler = new PluginRequestHandler(proxyToLocalhost); + browser.IsBrowserInitializedChanged += (s, e) => + { + if (browser.IsBrowserInitialized) + { + browser.ShowDevTools(); + } + }; + + +#if DEBUG_BUILD + browser.Load("http://localhost:4200"); +#else + browser.Load("index.html"); +#endif + + var window = new Window(); + window.Content = browser; + using var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("IPA.Bcfier.Revit.Resources.Browser.favicon.ico"); + if (iconStream != null) + { + window.Icon = BitmapFrame.Create(iconStream); + } + + window.Show(); + + return Result.Succeeded; + } + } +} diff --git a/src/IPA.Bcfier.Revit/PluginDebugRequestHandler.cs b/src/IPA.Bcfier.Revit/PluginDebugRequestHandler.cs new file mode 100644 index 00000000..4dd4a963 --- /dev/null +++ b/src/IPA.Bcfier.Revit/PluginDebugRequestHandler.cs @@ -0,0 +1,23 @@ +using CefSharp.Handler; +using CefSharp; +using System.Net.Http; + +namespace IPA.Bcfier.Revit +{ + public class PluginDebugRequestHandler : ResourceRequestHandler + { + protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request) + { + if (request.Url.EndsWith("api/frontend-config/config.js")) + { + var frontendConfig = @"(async function() { +window.ipaBcfierFrontendConfig = {""isInElectronMode"": false}; +await window[""cefSharp""].bindObjectAsync(""bcfierJavascriptBridge""); +})();"; + return ResourceHandler.FromString(frontendConfig, mimeType: "text/javascript"); + } + + return null; + } + } +} diff --git a/src/IPA.Bcfier.Revit/PluginRequestHandler.cs b/src/IPA.Bcfier.Revit/PluginRequestHandler.cs new file mode 100644 index 00000000..241a9c87 --- /dev/null +++ b/src/IPA.Bcfier.Revit/PluginRequestHandler.cs @@ -0,0 +1,21 @@ +using CefSharp.Handler; +using CefSharp; + +namespace IPA.Bcfier.Revit +{ + public class PluginRequestHandler : RequestHandler + { + private readonly bool _proxyToLocalhost; + + public PluginRequestHandler(bool proxyToLocalhost) + { + _proxyToLocalhost = proxyToLocalhost; + } + + protected override IResourceRequestHandler GetResourceRequestHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request, bool isNavigation, bool isDownload, string requestInitiator, ref bool disableDefaultHandling) + { + return _proxyToLocalhost ? new PluginDebugRequestHandler() + : new PluginResourceRequestHandler(); + } + } +} diff --git a/src/IPA.Bcfier.Revit/PluginResourceRequestHandler.cs b/src/IPA.Bcfier.Revit/PluginResourceRequestHandler.cs new file mode 100644 index 00000000..4aabe108 --- /dev/null +++ b/src/IPA.Bcfier.Revit/PluginResourceRequestHandler.cs @@ -0,0 +1,59 @@ +using CefSharp.Handler; +using CefSharp; +using System.Reflection; + +namespace IPA.Bcfier.Revit +{ + public class PluginResourceRequestHandler : ResourceRequestHandler + { + protected override IResourceHandler GetResourceHandler(IWebBrowser chromiumWebBrowser, IBrowser browser, IFrame frame, IRequest request) + { + if (request.Url.EndsWith("api/frontend-config/config.js")) + { + var frontendConfig = @"(async function() { +window.ipaBcfierFrontendConfig = {""isInElectronMode"": false}; +await window[""cefSharp""].bindObjectAsync(""bcfierJavascriptBridge""); +})();"; + return ResourceHandler.FromString(frontendConfig, mimeType: "text/javascript"); + } + + var fileName = Path.GetFileName(request.Url.TrimEnd('/')); + var stream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"IPA.Bcfier.Revit.Resources.Browser.{fileName}"); + + if (stream == null) + { + var resourceNames = Assembly.GetExecutingAssembly().GetManifestResourceNames() + .Where(r => r.EndsWith(fileName)) + .ToList(); + if (resourceNames.Count == 1) + { + stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(resourceNames[0]); + } + } + + if (stream != null) + { + if (fileName.EndsWith(".js")) + { + return ResourceHandler.FromStream(stream, "text/javascript"); + } + else if (fileName.EndsWith(".css")) + { + return ResourceHandler.FromStream(stream, "text/css"); + } + else if (fileName.EndsWith(".woff2")) + { + return ResourceHandler.FromStream(stream, "font/woff2"); + } + else + { + return ResourceHandler.FromStream(stream); + } + } + else + { + return null; + } + } + } +} diff --git a/src/IPA.Bcfier.Revit/Resources/ButtonLogo.png b/src/IPA.Bcfier.Revit/Resources/ButtonLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..6edaf627556004aa61dda8531b992025fc5fcf8c GIT binary patch literal 26157 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 literal 0 HcmV?d00001 diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index 0e057f03..75cee190 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -1,8 +1,25 @@ import { BcfFile, BcfViewpoint, Settings } from '../../generated/models'; - -import { Observable } from 'rxjs'; +import { Observable, Subject, of } from 'rxjs'; export class RevitBackendService { + private javascriptBridge: { + sendDataToRevit: ( + data: string, + callback: (jsonValue: string) => void + ) => void; + } | null = null; + + constructor() { + const setupBridge = () => { + this.javascriptBridge = (window as any).bcfierJavascriptBridge; + if (!this.javascriptBridge) { + setTimeout(setupBridge, 100); + } + }; + + setupBridge(); + } + importBcfFile(bcfFile: File): Observable { throw new Error('Method not implemented.'); } @@ -12,10 +29,32 @@ export class RevitBackendService { openDocumentation(): void { throw new Error('Method not implemented.'); } + + getSettings(): Observable { + return this.sendCommand('getSettings', null); + } saveSettings(settings: Settings): Observable { throw new Error('Method not implemented.'); } addViewpoint(): Observable { throw new Error('Method not implemented.'); } + + private sendCommand(command: string, data: any): Observable { + const subject = new Subject(); + this.javascriptBridge!.sendDataToRevit( + JSON.stringify({ + command, + data: JSON.stringify(data), + }), + (value) => { + subject.next(JSON.parse(value)); + setTimeout(() => { + subject.complete(); + }, 0); + } + ); + + return subject; + } } From 0c4ace2a6a394d56803af506dcc7f1429fd898ac Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 19:19:43 +0200 Subject: [PATCH 05/25] Cleanup --- build/Build.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/build/Build.cs b/build/Build.cs index 1156b525..7844a6ea 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -249,9 +249,6 @@ public static class FileVersionProvider { CopyDirectoryRecursively(SourceDirectory / "ipa-bcfier-ui" / "dist" / "ipa-bcfier-ui" / "browser", SourceDirectory / "IPA.Bcfier.Revit" / "Resources" / "Browser", DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite); - // Now there's a problem with files that have dashes in them, which we don' - - DotNetBuild(c => c.SetProjectFile(SourceDirectory / "IPA.Bcfier.Revit" / "IPA.Bcfier.Revit.csproj") .SetConfiguration("Release") .SetAssemblyVersion(GitVersion.AssemblySemVer) From 575488029e978db580780075f632de9ec45af2b0 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 20:45:17 +0200 Subject: [PATCH 06/25] Configure Revit Installer generation --- .github/workflows/continuous.yml | 60 ++++++++++++++---- .nuke/build.schema.json | 6 +- build/Build.cs | 51 +++++++++++++-- build/_build.csproj | 1 + .../BcfierJavascriptBridge.cs | 7 ++ src/IPA.Bcfier.Revit/Installer.iss | 39 ++++++++++++ src/IPA.Bcfier.Revit/InstallerAssets/BCF.ico | Bin 0 -> 99678 bytes .../InstallerAssets/banner.bmp | Bin 0 -> 150774 bytes .../OpenIpaBcfierWindowCommand.cs | 3 +- src/ipa-bcfier-ui/package.json | 2 +- .../src/app/services/RevitBackendService.ts | 9 ++- 11 files changed, 153 insertions(+), 25 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/Installer.iss create mode 100644 src/IPA.Bcfier.Revit/InstallerAssets/BCF.ico create mode 100644 src/IPA.Bcfier.Revit/InstallerAssets/banner.bmp diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index 91c3961c..df9f1e2c 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -3,17 +3,9 @@ name: continuous on: [push] jobs: - windows-latest: - name: windows-latest + tests: + name: tests runs-on: windows-latest - env: - GitHubAuthenticationToken: ${{ secrets.GITHUB_TOKEN }} - DocuApiKey: ${{ secrets.DOCUAPIKEY }} - CodeSigningCertificateKeyVaultBaseUrl: ${{ secrets.CODESIGNINGKEYVAULTBASEURL }} - KeyVaultClientId: ${{ secrets.CODESIGNINGKEYVAULTCLIENTID }} - KeyVaultClientSecret: ${{ secrets.CODESIGNINGKEYVAULTCLIENTSECRET }} - CodeSigningKeyVaultTenantId: ${{ secrets.CODESIGNINGKEYVAULTTENANTID }} - CodeSigningCertificateName: ${{ secrets.CODESIGNINGKEYVAULTCERTIFICATENAME }} steps: - name: Checkout repository uses: actions/checkout@v1 @@ -25,5 +17,49 @@ jobs: with: name: Test Results (${{ runner.os }}) path: output/**/*_testresults.xml - - name: UploadDocumentation+PublishGitHubRelease+UploadElectronApp - run: ./build.ps1 UploadDocumentation+PublishGitHubRelease+UploadElectronApp + upload-docs: + needs: tests + name: upload-docs + runs-on: windows-latest + env: + GitHubAuthenticationToken: ${{ secrets.GITHUB_TOKEN }} + DocuApiKey: ${{ secrets.DOCUAPIKEY }} + steps: + - name: Checkout repository + uses: actions/checkout@v1 + - name: UploadDocumentation+PublishGitHubRelease + run: ./build.ps1 UploadDocumentation+PublishGitHubRelease + upload-electron-app: + needs: upload-docs + name: upload-electron-app + runs-on: windows-latest + env: + GitHubAuthenticationToken: ${{ secrets.GITHUB_TOKEN }} + DocuApiKey: ${{ secrets.DOCUAPIKEY }} + CodeSigningCertificateKeyVaultBaseUrl: ${{ secrets.CODESIGNINGKEYVAULTBASEURL }} + KeyVaultClientId: ${{ secrets.CODESIGNINGKEYVAULTCLIENTID }} + KeyVaultClientSecret: ${{ secrets.CODESIGNINGKEYVAULTCLIENTSECRET }} + CodeSigningKeyVaultTenantId: ${{ secrets.CODESIGNINGKEYVAULTTENANTID }} + CodeSigningCertificateName: ${{ secrets.CODESIGNINGKEYVAULTCERTIFICATENAME }} + steps: + - name: Checkout repository + uses: actions/checkout@v1 + - name: UploadElectronApp + run: ./build.ps1 UploadElectronApp + upload-revit-plugin: + needs: upload-docs + name: upload-revit-plugin + runs-on: windows-latest + env: + GitHubAuthenticationToken: ${{ secrets.GITHUB_TOKEN }} + DocuApiKey: ${{ secrets.DOCUAPIKEY }} + CodeSigningCertificateKeyVaultBaseUrl: ${{ secrets.CODESIGNINGKEYVAULTBASEURL }} + KeyVaultClientId: ${{ secrets.CODESIGNINGKEYVAULTCLIENTID }} + KeyVaultClientSecret: ${{ secrets.CODESIGNINGKEYVAULTCLIENTSECRET }} + CodeSigningKeyVaultTenantId: ${{ secrets.CODESIGNINGKEYVAULTTENANTID }} + CodeSigningCertificateName: ${{ secrets.CODESIGNINGKEYVAULTCERTIFICATENAME }} + steps: + - name: Checkout repository + uses: actions/checkout@v1 + - name: UploadRevitPlugin + run: ./build.ps1 UploadRevitPlugin diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json index 21a00ce9..8b71c58a 100644 --- a/.nuke/build.schema.json +++ b/.nuke/build.schema.json @@ -114,7 +114,8 @@ "SignExecutables", "Tests", "UploadDocumentation", - "UploadElectronApp" + "UploadElectronApp", + "UploadRevitPlugin" ] } }, @@ -143,7 +144,8 @@ "SignExecutables", "Tests", "UploadDocumentation", - "UploadElectronApp" + "UploadElectronApp", + "UploadRevitPlugin" ] } }, diff --git a/build/Build.cs b/build/Build.cs index 7844a6ea..03ee54ea 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -66,6 +66,7 @@ class Build : NukeBuild [Parameter] readonly string CodeSigningCertificateName; [Parameter] AbsolutePath ExecutablesToSignFolder; + [NuGetPackage("Tools.InnoSetup", "tools/ISCC.exe")] readonly Tool InnoSetup; [NuGetPackage("AzureSignTool", "tools/net8.0/any/AzureSignTool.dll")] readonly Tool AzureSign; @@ -178,7 +179,7 @@ public static class FileVersionProvider Directory.Delete(RootDirectory / "obj", true); }); - private Target UploadDocumentation => _ => _ + Target UploadDocumentation => _ => _ .DependsOn(BuildDocumentation) .Requires(() => DocuApiKey) .Requires(() => DocuBaseUrl) @@ -249,14 +250,50 @@ public static class FileVersionProvider { CopyDirectoryRecursively(SourceDirectory / "ipa-bcfier-ui" / "dist" / "ipa-bcfier-ui" / "browser", SourceDirectory / "IPA.Bcfier.Revit" / "Resources" / "Browser", DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite); + var pluginOutputDirectory = OutputDirectory / "RevitPlugin"; DotNetBuild(c => c.SetProjectFile(SourceDirectory / "IPA.Bcfier.Revit" / "IPA.Bcfier.Revit.csproj") .SetConfiguration("Release") + .SetOutputDirectory(pluginOutputDirectory) .SetAssemblyVersion(GitVersion.AssemblySemVer) .SetFileVersion(GitVersion.AssemblySemVer) .SetInformationalVersion(GitVersion.InformationalVersion) .EnableNoRestore()); + SignExecutablesInFolder(pluginOutputDirectory, false); + + File.Copy(SourceDirectory / "IPA.Bcfier.Revit" / "Installer.iss", pluginOutputDirectory / "Installer.iss", overwrite: true); + + var installerDirectory = pluginOutputDirectory / "Installer"; + installerDirectory.CreateOrCleanDirectory(); + CopyDirectoryRecursively(SourceDirectory / "IPA.Bcfier.Revit" / "InstallerAssets", installerDirectory / "InstallerAssets", DirectoryExistsPolicy.Merge, FileExistsPolicy.Overwrite); + File.Copy(pluginOutputDirectory / "Dangl.BCF.dll", installerDirectory / "Dangl.BCF.dll"); + File.Copy(pluginOutputDirectory / "IPA.Bcfier.dll", installerDirectory/ "IPA.Bcfier.dll"); + File.Copy(pluginOutputDirectory / "IPA.Bcfier.Revit.dll", installerDirectory/ "IPA.Bcfier.Revit.dll"); + File.Copy(pluginOutputDirectory / "IPA.Bcfier.Revit.addin", installerDirectory/ "IPA.Bcfier.Revit.addin"); + + InnoSetup($"/dAppVersion=\"{GitVersion.AssemblySemVer}\" {pluginOutputDirectory / "Installer.iss"}"); + + SignExecutablesInFolder(OutputDirectory, false); + }); + + Target UploadRevitPlugin => _ => _ + .DependsOn(BuildRevitPlugin) + .Executes(() => + { + var changeLog = GetCompleteChangeLog(ChangeLogFile); + var assets = (OutputDirectory / "RevitPlugin" / "Installer" / "output").GlobFiles("*.exe") + .Select(f => f.ToString()) + .ToArray(); + Assert.NotEmpty(assets); + + AssetFileUpload(s => s + .SetDocuBaseUrl(DocuBaseUrl) + .SetDocuApiKey(DocuApiKey) + .SetVersion(GitVersion.NuGetVersion) + .SetAssetFilePaths(assets) + .SetSkipForVersionConflicts(true)); }); + Target BuildElectronApp => _ => _ .DependsOn(BuildFrontend) .DependsOn(Compile) @@ -271,17 +308,17 @@ public static class FileVersionProvider new[] { "Windows_X64", "/target win" } ); - SignExecutablesInFolder(OutputDirectory / "electron"); + SignExecutablesInFolder(OutputDirectory / "electron", false); }); Target SignExecutables => _ => _ .Requires(() => ExecutablesToSignFolder) .Executes(() => { - SignExecutablesInFolder(ExecutablesToSignFolder); + SignExecutablesInFolder(ExecutablesToSignFolder, false); }); - private void SignExecutablesInFolder(AbsolutePath folderPath) + private void SignExecutablesInFolder(AbsolutePath folderPath, bool includeDll) { Assert.True(!string.IsNullOrWhiteSpace(CodeSigningCertificateKeyVaultBaseUrl), "!string.IsNullOrWhitespace(CodeSigningCertificateKeyVaultBaseUrl)"); Assert.True(!string.IsNullOrWhiteSpace(KeyVaultClientId), "!string.IsNullOrWhitespace(KeyVaultClientId)"); @@ -289,7 +326,8 @@ private void SignExecutablesInFolder(AbsolutePath folderPath) Assert.True(!string.IsNullOrWhiteSpace(CodeSigningKeyVaultTenantId), "!string.IsNullOrWhitespace(CodeSigningKeyVaultTenantId)"); Assert.True(!string.IsNullOrWhiteSpace(CodeSigningCertificateName), "!string.IsNullOrWhitespace(CodeSigningCertificateName)"); - var inputFiles = folderPath.GlobFiles("*.exe"); + var globPattern = includeDll ? "*.dll,*.exe" : "*.exe"; + var inputFiles = folderPath.GlobFiles(globPattern); var filesListPath = OutputDirectory / $"{Guid.NewGuid()}.txt"; filesListPath.WriteAllText(inputFiles.Select(f => f.ToString()).Join(Environment.NewLine) + Environment.NewLine); @@ -354,7 +392,6 @@ private void SetVersionInElectronManifest() .DependsOn(UploadDocumentation) .Requires(() => DocuApiKey) .Requires(() => DocuBaseUrl) - .OnlyWhenDynamic(() => IsOnBranch("master") || IsOnBranch("develop")) .Executes(() => { var changeLog = GetCompleteChangeLog(ChangeLogFile); @@ -373,7 +410,7 @@ private void SetVersionInElectronManifest() Target PublishGitHubRelease => _ => _ .Requires(() => GitHubAuthenticationToken) - .OnlyWhenDynamic(() => GitVersion.BranchName.Equals("master") || GitVersion.BranchName.Equals("origin/master")) + .OnlyWhenDynamic(() => IsOnBranch("master")) .Executes(async () => { var releaseTag = $"v{GitVersion.MajorMinorPatch}"; diff --git a/build/_build.csproj b/build/_build.csproj index fe709541..98e546eb 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -23,6 +23,7 @@ runtime; native; contentfiles; analyzers + diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index 27a6fc15..7b71deb0 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -1,6 +1,7 @@ using CefSharp; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; +using IPA.Bcfier.Models.Settings; namespace IPA.Bcfier.Revit { @@ -32,6 +33,12 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal var userSettings = await new Services.SettingsService().LoadSettingsAsync(); await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); } + else if (classData.Command == "setSettings") + { + var userSettings = JsonConvert.DeserializeObject(classData.Data); + await new Services.SettingsService().SaveSettingsAsync(userSettings!); + await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); + } else { // TODO return error for unrecognized commands diff --git a/src/IPA.Bcfier.Revit/Installer.iss b/src/IPA.Bcfier.Revit/Installer.iss new file mode 100644 index 00000000..1ea56390 --- /dev/null +++ b/src/IPA.Bcfier.Revit/Installer.iss @@ -0,0 +1,39 @@ +#define Repository "./Installer" +#define AppName "IPA BCFier Revit Plugin" +#define AppPublisher "Dangl IT GmbH" +#define AppURL "https://www.dangl-it.com" + +#define RevitAddinFolder "{sd}\ProgramData\Autodesk\Revit\Addins" +#define RevitAddin24 RevitAddinFolder+"\2024\" + +[Setup] +AppId="cfd740a5-8089-4323-a6e5-c86cfee9cca2" +AppName={#AppName} +AppVersion={#AppVersion} +AppVerName={#AppName} {#AppVersion} +AppPublisher={#AppPublisher} +AppPublisherURL={#AppURL} +AppSupportURL={#AppURL} +DefaultDirName={#RevitAddin24} +DisableDirPage=yes +DefaultGroupName={#AppName} +DisableProgramGroupPage=yes +DisableWelcomePage=no +OutputDir={#Repository}\output +OutputBaseFilename=IpaBcfierRevitPlugin +SetupIconFile={#Repository}\InstallerAssets\BCF.ico +Compression=lzma +SolidCompression=yes +WizardImageFile={#Repository}\InstallerAssets\banner.bmp +ChangesAssociations=yes + +[Components] +Name: revit24; Description: Addin for Autodesk Revit 2024; Types: full + +[Files] + +;REVIT 2024 +Source: "{#Repository}\Dangl.BCF.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 +Source: "{#Repository}\IPA.Bcfier.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 +Source: "{#Repository}\IPA.Bcfier.Revit.dll"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 +Source: "{#Repository}\IPA.Bcfier.Revit.addin"; DestDir: "{#RevitAddin24}"; Flags: ignoreversion; Components: revit24 diff --git a/src/IPA.Bcfier.Revit/InstallerAssets/BCF.ico b/src/IPA.Bcfier.Revit/InstallerAssets/BCF.ico new file mode 100644 index 0000000000000000000000000000000000000000..5ab6170a97ae498b5c6545ccaba07c559306abe6 GIT binary patch literal 99678 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 { if (browser.IsBrowserInitialized) @@ -29,7 +30,7 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme browser.ShowDevTools(); } }; - +#endif #if DEBUG_BUILD browser.Load("http://localhost:4200"); diff --git a/src/ipa-bcfier-ui/package.json b/src/ipa-bcfier-ui/package.json index bc69cd13..a8c50e32 100644 --- a/src/ipa-bcfier-ui/package.json +++ b/src/ipa-bcfier-ui/package.json @@ -4,7 +4,7 @@ "scripts": { "ng": "ng", "start": "ng serve", - "build": "npm run install-fonts && ng build", + "build": "npm run install-fonts && ng build --configuration production", "watch": "ng build --watch --configuration development", "test": "ng test", "install-fonts": "node ./copyFonts.js" diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index 75cee190..b6a312db 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -33,8 +33,9 @@ export class RevitBackendService { getSettings(): Observable { return this.sendCommand('getSettings', null); } + saveSettings(settings: Settings): Observable { - throw new Error('Method not implemented.'); + return this.sendCommand('setSettings', settings); } addViewpoint(): Observable { throw new Error('Method not implemented.'); @@ -48,7 +49,11 @@ export class RevitBackendService { data: JSON.stringify(data), }), (value) => { - subject.next(JSON.parse(value)); + if (value) { + subject.next(JSON.parse(value)); + } else { + subject.next(null as T); + } setTimeout(() => { subject.complete(); }, 0); From 041c7aac43987aa087187575b10c7adb6a9cd250 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 20:45:28 +0200 Subject: [PATCH 07/25] Ensure settings file is completely overwritten --- src/IPA.Bcfier/Services/SettingsService.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/IPA.Bcfier/Services/SettingsService.cs b/src/IPA.Bcfier/Services/SettingsService.cs index 7bf39dc7..2d13a4e4 100644 --- a/src/IPA.Bcfier/Services/SettingsService.cs +++ b/src/IPA.Bcfier/Services/SettingsService.cs @@ -38,7 +38,13 @@ public async Task LoadSettingsAsync() public async Task SaveSettingsAsync(Settings settings) { var serializedSettings = JsonConvert.SerializeObject(settings); - using var settingsFileStream = File.OpenWrite(GetPathToSettingsFile()); + var settingsFilePath = GetPathToSettingsFile(); + if (File.Exists(settingsFilePath)) + { + File.Delete(settingsFilePath); + } + + using var settingsFileStream = File.Create(settingsFilePath); using var streamWriter = new StreamWriter(settingsFileStream); await streamWriter.WriteAsync(serializedSettings); } From 42b6798d46a75b032968538dc71812c23a347def Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 21:06:42 +0200 Subject: [PATCH 08/25] Update path to Installer for signing --- build/Build.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/Build.cs b/build/Build.cs index 03ee54ea..66234358 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -272,7 +272,7 @@ public static class FileVersionProvider InnoSetup($"/dAppVersion=\"{GitVersion.AssemblySemVer}\" {pluginOutputDirectory / "Installer.iss"}"); - SignExecutablesInFolder(OutputDirectory, false); + SignExecutablesInFolder(installerDirectory / "output", false); }); Target UploadRevitPlugin => _ => _ From e4b8069bb1837266a8594b1d5268929e35f8f7ac Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 21:09:02 +0200 Subject: [PATCH 09/25] Update GH Actions to not try to upload docs twice --- .github/workflows/continuous.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index df9f1e2c..ad6870a4 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -45,7 +45,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v1 - name: UploadElectronApp - run: ./build.ps1 UploadElectronApp + run: ./build.ps1 UploadElectronApp --skip UploadDocumentation upload-revit-plugin: needs: upload-docs name: upload-revit-plugin From 0774fefcb6dc665c6bb31867a968356d0989b153 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 21:10:02 +0200 Subject: [PATCH 10/25] Skipe more unnecessary targets --- .github/workflows/continuous.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous.yml b/.github/workflows/continuous.yml index ad6870a4..bee22d90 100644 --- a/.github/workflows/continuous.yml +++ b/.github/workflows/continuous.yml @@ -45,7 +45,7 @@ jobs: - name: Checkout repository uses: actions/checkout@v1 - name: UploadElectronApp - run: ./build.ps1 UploadElectronApp --skip UploadDocumentation + run: ./build.ps1 UploadElectronApp --skip UploadDocumentation BuildDocumentation BuildDocFxMetadata upload-revit-plugin: needs: upload-docs name: upload-revit-plugin From d97ae53ae2b7ab5d83f2cfb468c014e607ad2047 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Thu, 4 Apr 2024 21:14:36 +0200 Subject: [PATCH 11/25] Implement help in Revit plugin --- src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs | 5 +++++ src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts | 5 ++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index 7b71deb0..d3db4154 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -39,6 +39,11 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal await new Services.SettingsService().SaveSettingsAsync(userSettings!); await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); } + else if (classData.Command == "openDocumentation") + { + System.Diagnostics.Process.Start("https://docs.dangl-it.com/Projects/IPA.BCFier"); + await javascriptCallback.ExecuteAsync(); + } else { // TODO return error for unrecognized commands diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index b6a312db..57b3b760 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -23,11 +23,13 @@ export class RevitBackendService { importBcfFile(bcfFile: File): Observable { throw new Error('Method not implemented.'); } + exportBcfFile(bcfFile: BcfFile): Observable { throw new Error('Method not implemented.'); } + openDocumentation(): void { - throw new Error('Method not implemented.'); + this.sendCommand('openDocumentation', null); } getSettings(): Observable { @@ -37,6 +39,7 @@ export class RevitBackendService { saveSettings(settings: Settings): Observable { return this.sendCommand('setSettings', settings); } + addViewpoint(): Observable { throw new Error('Method not implemented.'); } From 34c332324c674f24bc710c4b3e8147fdae1c053a Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 21:00:29 +0200 Subject: [PATCH 12/25] Ensure dependent assemblies are loaded when the command is started --- .../OpenIpaBcfierWindowCommand.cs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs index e40d19b2..4e7c40b6 100644 --- a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs +++ b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs @@ -1,4 +1,4 @@ -using Autodesk.Revit.Attributes; +using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using CefSharp; @@ -14,6 +14,8 @@ public class OpenIpaBcfierWindowCommand : IExternalCommand { public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { + EnsureDependentAssembliesAreLoaded(); + var browser = new ChromiumWebBrowser(); #if DEBUG_BUILD var proxyToLocalhost = true; @@ -50,5 +52,16 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme return Result.Succeeded; } + + private void EnsureDependentAssembliesAreLoaded() + { + // I didn't find out why this is required, but apparently Revit + // does something to resolve assemblies, and this seems to fail + // when the assemblies are not directly loaded due to execution in the + // initial command but only later, like in our case when an event from + // the browser is triggering some action + typeof(IPA.Bcfier.Models.Bcf.BcfComment).ToString(); + typeof(Dangl.BCF.APIObjects.V21.Auth_GET).ToString(); + } } } From bbd32361a4ce205a6e35e761337587ddd084aa34 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 21:04:15 +0200 Subject: [PATCH 13/25] Configure dependencies to reduce artefacts that are output to the build folder --- src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj index efffa32a..1da7d6df 100644 --- a/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj +++ b/src/IPA.Bcfier.Revit/IPA.Bcfier.Revit.csproj @@ -10,8 +10,15 @@ - - + + + + + + + + + From 2bf3051244c8df892c9c202ae99e475bcf6c48eb Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:23:19 +0200 Subject: [PATCH 14/25] Add backend queue handling for opening BCF files in Revit --- src/IPA.Bcfier.Revit/IdlingHandler.cs | 12 ----- src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs | 49 +++++++++++++++++++ 2 files changed, 49 insertions(+), 12 deletions(-) delete mode 100644 src/IPA.Bcfier.Revit/IdlingHandler.cs create mode 100644 src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs diff --git a/src/IPA.Bcfier.Revit/IdlingHandler.cs b/src/IPA.Bcfier.Revit/IdlingHandler.cs deleted file mode 100644 index 72705d3b..00000000 --- a/src/IPA.Bcfier.Revit/IdlingHandler.cs +++ /dev/null @@ -1,12 +0,0 @@ -using Autodesk.Revit.UI.Events; - -namespace IPA.Bcfier.Revit -{ - public static class IdlingHandler - { - public static void OnIdling(object sender, IdlingEventArgs args) - { - // Not doing anything right now - } - } -} diff --git a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs new file mode 100644 index 00000000..8599060e --- /dev/null +++ b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs @@ -0,0 +1,49 @@ +using Autodesk.Revit.UI.Events; +using CefSharp; +using IPA.Bcfier.Services; +using Microsoft.Win32; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace IPA.Bcfier.Revit +{ + public class RevitTaskQueueHandler + { + public Queue OpenBcfFileCallbacks = new Queue(); + + public void OnIdling(object sender, IdlingEventArgs args) + { + if (OpenBcfFileCallbacks.Count > 0) + { + var callback = OpenBcfFileCallbacks.Dequeue(); + var openFileDialog = new OpenFileDialog + { + Filter = "BCF Files (*.bcf, *.bcfzip)|*.bcf;*.bcfzip" + }; + + if (!openFileDialog.ShowDialog() ?? false || openFileDialog.FileName == null) + { + return; + } + + var bcfFilePath = openFileDialog.FileName; + Task.Run(async () => + { + var bcfFileName = Path.GetFileName(bcfFilePath); + using var bcfFileStream = File.OpenRead(bcfFilePath); + var bcfResult = await new BcfImportService().ImportBcfFileAsync(bcfFileStream, bcfFileName ?? "issue.bcf"); + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + var serializerSettings = new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }; + await callback.ExecuteAsync(JsonConvert.SerializeObject(bcfResult, serializerSettings)); + }); + } + } + } +} From 098dead88e61d73efaca974016563ff0dbe1d0bf Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:23:52 +0200 Subject: [PATCH 15/25] Update frontend to allow opening BCF files in Revit --- .../src/app/services/RevitBackendService.ts | 26 ++++++++++++------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index 57b3b760..431c5ed1 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -1,4 +1,5 @@ import { BcfFile, BcfViewpoint, Settings } from '../../generated/models'; +import { NgZone, inject } from '@angular/core'; import { Observable, Subject, of } from 'rxjs'; export class RevitBackendService { @@ -9,6 +10,8 @@ export class RevitBackendService { ) => void; } | null = null; + private ngZone: NgZone = inject(NgZone); + constructor() { const setupBridge = () => { this.javascriptBridge = (window as any).bcfierJavascriptBridge; @@ -20,8 +23,8 @@ export class RevitBackendService { setupBridge(); } - importBcfFile(bcfFile: File): Observable { - throw new Error('Method not implemented.'); + importBcfFile(): Observable { + return this.sendCommand('importBcfFile', null); } exportBcfFile(bcfFile: BcfFile): Observable { @@ -52,14 +55,17 @@ export class RevitBackendService { data: JSON.stringify(data), }), (value) => { - if (value) { - subject.next(JSON.parse(value)); - } else { - subject.next(null as T); - } - setTimeout(() => { - subject.complete(); - }, 0); + this.ngZone.run(() => { + if (value) { + subject.next(JSON.parse(value)); + } else { + subject.next(null as T); + } + + setTimeout(() => { + subject.complete(); + }, 0); + }); } ); From e64950ed06251c45e9a42c483c079c047c873347 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:26:34 +0200 Subject: [PATCH 16/25] Hook up new queue handler in Revit command --- src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs | 18 +++++++++++++++++- src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs | 2 -- .../OpenIpaBcfierWindowCommand.cs | 14 ++++++++++++-- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index d3db4154..a3ee8a01 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -2,11 +2,21 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; using IPA.Bcfier.Models.Settings; +using IPA.Bcfier.Services; +using Autodesk.Revit.UI; +using Microsoft.Win32; namespace IPA.Bcfier.Revit { public class BcfierJavascriptBridge { + private readonly RevitTaskQueueHandler _revitTaskQueueHandler; + + public BcfierJavascriptBridge(RevitTaskQueueHandler revitTaskQueueHandler) + { + _revitTaskQueueHandler = revitTaskQueueHandler; + } + private class DataClass { public string Command { get; set; } @@ -18,7 +28,7 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal { var classData = JsonConvert.DeserializeObject(data)!; - DefaultContractResolver contractResolver = new DefaultContractResolver + var contractResolver = new DefaultContractResolver { NamingStrategy = new CamelCaseNamingStrategy() }; @@ -44,6 +54,12 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal System.Diagnostics.Process.Start("https://docs.dangl-it.com/Projects/IPA.BCFier"); await javascriptCallback.ExecuteAsync(); } + else if (classData.Command == "importBcfFile") + { + // Since we need a Revit context (more specifically access to the UI thread), + // we're enqueuing that task to be executed in the Revit context + _revitTaskQueueHandler.OpenBcfFileCallbacks.Enqueue(javascriptCallback); + } else { // TODO return error for unrecognized commands diff --git a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs index ce0dd06d..bf2e1964 100644 --- a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs +++ b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs @@ -18,8 +18,6 @@ public Result OnStartup(UIControlledApplication application) pushButton.ToolTip = "Launch IPA.Bcfier Revit Plugin"; pushButton.LargeImage = bitmapImage; - application.Idling += IdlingHandler.OnIdling; - return Result.Succeeded; } diff --git a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs index 4e7c40b6..92fda815 100644 --- a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs +++ b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs @@ -1,4 +1,4 @@ -using Autodesk.Revit.Attributes; +using Autodesk.Revit.Attributes; using Autodesk.Revit.DB; using Autodesk.Revit.UI; using CefSharp; @@ -22,7 +22,9 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme #else var proxyToLocalhost = false; #endif - browser.JavascriptObjectRepository.Register("bcfierJavascriptBridge", new BcfierJavascriptBridge(), true); + + var taskQueueHandler = new RevitTaskQueueHandler(); + browser.JavascriptObjectRepository.Register("bcfierJavascriptBridge", new BcfierJavascriptBridge(taskQueueHandler), true); browser.RequestHandler = new PluginRequestHandler(proxyToLocalhost); #if DEBUG_BUILD browser.IsBrowserInitializedChanged += (s, e) => @@ -40,6 +42,8 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme browser.Load("index.html"); #endif + commandData.Application.Idling += taskQueueHandler.OnIdling; + var window = new Window(); window.Content = browser; using var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("IPA.Bcfier.Revit.Resources.Browser.favicon.ico"); @@ -48,6 +52,12 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme window.Icon = BitmapFrame.Create(iconStream); } + window.Closed += (s, e) => + { + commandData.Application.Idling -= taskQueueHandler.OnIdling; + browser.Dispose(); + }; + window.Show(); return Result.Succeeded; From b3d80b96561c41b9a56dcad0ced9b76d839a4312 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:29:44 +0200 Subject: [PATCH 17/25] Update images used in Revit plugin --- src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs | 17 ++++++++++++----- src/IPA.Bcfier.Revit/Resources/button16.png | Bin 0 -> 831 bytes src/IPA.Bcfier.Revit/Resources/button32.png | Bin 0 -> 1874 bytes 3 files changed, 12 insertions(+), 5 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/Resources/button16.png create mode 100644 src/IPA.Bcfier.Revit/Resources/button32.png diff --git a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs index bf2e1964..1dbe55ea 100644 --- a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs +++ b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs @@ -8,19 +8,26 @@ public class IpaBcfierRevitPlugin : IExternalApplication { public Result OnStartup(UIControlledApplication application) { - var bitmapImage = new BitmapImage(); - bitmapImage.BeginInit(); - bitmapImage.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream("IPA.Bcfier.Revit.Resources.ButtonLogo.png"); - bitmapImage.EndInit(); + var buttonData = new PushButtonData("openPluginButton", "Open Plugin", Assembly.GetExecutingAssembly().Location, "IPA.Bcfier.Revit.OpenIpaBcfierWindowCommand"); var pushButton = application.CreateRibbonPanel("IPA").AddItem(buttonData) as PushButton; pushButton.ToolTip = "Launch IPA.Bcfier Revit Plugin"; - pushButton.LargeImage = bitmapImage; + pushButton.Image = GetBitmapImage("button16.png"); + pushButton.LargeImage = GetBitmapImage("button32.png"); return Result.Succeeded; } + private BitmapImage GetBitmapImage(string resourceName) + { + var bitmapImage = new BitmapImage(); + bitmapImage.BeginInit(); + bitmapImage.StreamSource = Assembly.GetExecutingAssembly().GetManifestResourceStream($"IPA.Bcfier.Revit.Resources.{resourceName}"); + bitmapImage.EndInit(); + return bitmapImage; + } + public Result OnShutdown(UIControlledApplication application) { return Result.Succeeded; diff --git a/src/IPA.Bcfier.Revit/Resources/button16.png b/src/IPA.Bcfier.Revit/Resources/button16.png new file mode 100644 index 0000000000000000000000000000000000000000..39e117ef2b16ccc486fe053455fcf324ee069574 GIT binary patch literal 831 zcmV-F1Hk-=P)6TLcmica6t_mkh#|&px{y&HZ_f9Pc|8rMDqCiJB%!Qbc59iL}vUs7Yc09eG4?- z76gT;S7JVqd-s$W*i?hLju^a->)>Nb4j{mTNMg!Dw#Y2NVZVY3Y7_nJQv%P2M21y8 zQCQOx$xB-l7GohODFiAqF=`ZY#_&`h6*VIWgaYLN7_$^amj18LxM&T};Eml~}<h?1c2e?xh^t@AT!*S)s<{gr*}N%m{E3h8loDg;gjK zT5Id@T?@lcRm%)4&auOGxkQ;2=APQ~QYc@5Q8oI+37JV)%do44KxDtN@FIVd-!4^5 zk17ZxJ*4432BWvjZ>DUI~KKz|IDI z3&*Cv#M+EsGNiG~y+WvmT1$#l->V2N%fxgEWLBTtWRf7bA3M?IQkp^wA#PWDPnU}G z-R0tFjgl4Jw~dHjwQ)v_xU&lp&zgAi{NB}F^X2B>x3JTB9_L(MUe}|>h8_mA5wobG zGzfbxv^q9)*k$OjDrWc6+rERnsh5BQ!?rsw494wpPUgPR?A1W1!5Z;x;K;dXvttz^ zUM+LvYusO`B5002ov JPDHLkV1g~vd#eBd literal 0 HcmV?d00001 diff --git a/src/IPA.Bcfier.Revit/Resources/button32.png b/src/IPA.Bcfier.Revit/Resources/button32.png new file mode 100644 index 0000000000000000000000000000000000000000..4b45eddbffc4f9a6b8a5bd50e09c6486dadc585e GIT binary patch literal 1874 zcmV-Y2d(&tP)H(o;1F+jdIawY~S?3w0$xfleTC~tR|hBn2?xA2~Q>@tO^N6 z2~(;fiO4Q$2oUHjz3<%o{&Qxw+ghN-reDu?c4xlt{Fm=P%h_FiKHc_721^du2V}Xp z8dZNJ4;+sex8FGXrpq4ihc7^EVrd6DIS6?aF_i^kF2w<T~$88O`OQD0D!CU42odKQFF%;TE9tW!wiJ@YWqMK}v;L?vj!tO`(kQnW z$vCH}sMF8w%}k#=al9fRuJ&V;V?CNB*Q-Q8SoiLc-u=BPHB;x0QH^BN;mqcS@^z~= znCLAT4{wtlBAK2X^9$!zq*rMXO9O}#7a~IWwgnL!BB+b-o@})^vpup1v)3JE4!wUMZG}Aa`m5kOQ>=a)8yC z711L$PT@o;RVqCZ0rKuD@^%Vv$VC~tY838oxOygEtp;__fqQd+=%VGGzYk>1z!gnl zrjisy+(l)DSri3D+)O<#qELYgs$L2^s=a+iQQ5d|useFnnb5Qi^U3K4X+o7p9mzA$ zE!Jy)j+&`3>Zrl)YvKr`uo@xEO}0pj!sm2##qI3I(z%?cCjCVTx!Ad@r(iDn z{>Dq+-q-VKHdGvw^CntAIB3NA*Rgvd*Wo8h#4^f!U#bZvhfMEg=N%B2u=^53fV{2g zmlK7>cso)y81J8Ch)M@xKG`+3B|(j32LL=@6?|vQm}5?8x;d^<&J7~B_Y8kCwAkUY z*ZIJqLXDqk_CMd7)gqa5wX#y(&Ehx)2@MmrdHlBmLZ5{IA{LldweF=(IM}lj#m_`Z zT2;H9lWHDcv$1m*gJh^-=2{knYzmkJ@Y;Uq=c9$C3ZS7BNOBUfwR-ARuW)Zip~D2! z_B}p+$wk$h)N%w8aSyyzxvhw*2PCaOLp&$m`cG@c*X;huW*(KK?Lk;UmPfCRri^d_TiPo7m3I~F~A2N zZR_u>@!3#1j0Vqrb;!AwfRds1jx1NuQM`2qy?SE<>YL(NHiHTM#Y&8_9`J{hc92S-g|m`2YEJBTV>p=3J2rK$tWwLg>t5x zit93+I73m$<&I}!5i<&R@tlr?e6D~fa>yPvaWDyCn*puXyTAYBPAgh-&l8O%ZqAie z*2E>T83vNrg9uDKVJbYX_8=}aPlR8jz=5E)%l1KM!9yt~pNYyJrNMJs$M7*yVhCXl zkRul03*GOZ{J3(Sd#tTLs|U=2=q8ms9idlbZ_Cq9MFh`d9lZGELHoYPa@4^9EVvbu zPY9qnP|_1iHprj~K6yPS`zCJ>G2udtS$|(`;G5H`4g)jHDb*NWJ6YPg{np>^>Z!55 zRmIcJqP`a(3YoH^o(HKZ$y3QZU=w&aIV5>RAp2X?i2(sMfsf+!Se*}eWgJFk0I@o2 z3g0&_>6_7wbvs+9R5^-?e2mC7Nsd|&Mn!%h1w}n#rO`Ko|>9sswcS%OG6@iUKN|r}L?IASKt14O9CoiP zo?i3><%{{yjrBXT_+r+A+-E_asTKh9c2Em>v|``N;txw6-Tw+O0CKM23r4&o-2eap M07*qoM6N<$g5#oU%>V!Z literal 0 HcmV?d00001 From 534c159372a998db8f15f32cee5023ad35419fb1 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:31:03 +0200 Subject: [PATCH 18/25] Remove superfluous newlines --- src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs index 1dbe55ea..e30faba5 100644 --- a/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs +++ b/src/IPA.Bcfier.Revit/IpaBcfierRevitPlugin.cs @@ -8,8 +8,6 @@ public class IpaBcfierRevitPlugin : IExternalApplication { public Result OnStartup(UIControlledApplication application) { - - var buttonData = new PushButtonData("openPluginButton", "Open Plugin", Assembly.GetExecutingAssembly().Location, "IPA.Bcfier.Revit.OpenIpaBcfierWindowCommand"); var pushButton = application.CreateRibbonPanel("IPA").AddItem(buttonData) as PushButton; pushButton.ToolTip = "Launch IPA.Bcfier Revit Plugin"; From 5fc960a76a16e7c90ac96a39ef51e6d5f5416428 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:39:05 +0200 Subject: [PATCH 19/25] Add Revit integration to save BCF files --- .../BcfierJavascriptBridge.cs | 10 +++ .../Models/SaveBcfFileQueueItem.cs | 12 +++ src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs | 87 ++++++++++++++----- .../src/app/services/RevitBackendService.ts | 2 +- 4 files changed, 87 insertions(+), 24 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/Models/SaveBcfFileQueueItem.cs diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index a3ee8a01..e5633d27 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -5,6 +5,8 @@ using IPA.Bcfier.Services; using Autodesk.Revit.UI; using Microsoft.Win32; +using IPA.Bcfier.Revit.Models; +using IPA.Bcfier.Models.Bcf; namespace IPA.Bcfier.Revit { @@ -60,6 +62,14 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal // we're enqueuing that task to be executed in the Revit context _revitTaskQueueHandler.OpenBcfFileCallbacks.Enqueue(javascriptCallback); } + else if (classData.Command == "exportBcfFile") + { + _revitTaskQueueHandler.SaveBcfFileCallbacks.Enqueue(new SaveBcfFileQueueItem + { + Callback = javascriptCallback, + BcfFile = JsonConvert.DeserializeObject(classData.Data) + }); + } else { // TODO return error for unrecognized commands diff --git a/src/IPA.Bcfier.Revit/Models/SaveBcfFileQueueItem.cs b/src/IPA.Bcfier.Revit/Models/SaveBcfFileQueueItem.cs new file mode 100644 index 00000000..ea427fda --- /dev/null +++ b/src/IPA.Bcfier.Revit/Models/SaveBcfFileQueueItem.cs @@ -0,0 +1,12 @@ +using CefSharp; +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.Models +{ + public class SaveBcfFileQueueItem + { + public IJavascriptCallback? Callback { get; set; } + + public BcfFile? BcfFile { get; set; } + } +} diff --git a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs index 8599060e..bdc62a9c 100644 --- a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs +++ b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs @@ -1,5 +1,6 @@ using Autodesk.Revit.UI.Events; using CefSharp; +using IPA.Bcfier.Revit.Models; using IPA.Bcfier.Services; using Microsoft.Win32; using Newtonsoft.Json; @@ -10,40 +11,80 @@ namespace IPA.Bcfier.Revit public class RevitTaskQueueHandler { public Queue OpenBcfFileCallbacks = new Queue(); + public Queue SaveBcfFileCallbacks = new Queue(); public void OnIdling(object sender, IdlingEventArgs args) { if (OpenBcfFileCallbacks.Count > 0) { var callback = OpenBcfFileCallbacks.Dequeue(); - var openFileDialog = new OpenFileDialog + HandleOpenBcfFileCallback(callback); + } + + if (SaveBcfFileCallbacks.Count > 0) + { + var saveBcfFileQueueItem = SaveBcfFileCallbacks.Dequeue(); + HandleSaveBcfFileCallback(saveBcfFileQueueItem); + } + } + + private void HandleOpenBcfFileCallback(IJavascriptCallback callback) + { + var openFileDialog = new OpenFileDialog + { + Filter = "BCF Files (*.bcf, *.bcfzip)|*.bcf;*.bcfzip" + }; + + if (!openFileDialog.ShowDialog() ?? false || openFileDialog.FileName == null) + { + return; + } + + var bcfFilePath = openFileDialog.FileName; + Task.Run(async () => + { + var bcfFileName = Path.GetFileName(bcfFilePath); + using var bcfFileStream = File.OpenRead(bcfFilePath); + var bcfResult = await new BcfImportService().ImportBcfFileAsync(bcfFileStream, bcfFileName ?? "issue.bcf"); + var contractResolver = new DefaultContractResolver { - Filter = "BCF Files (*.bcf, *.bcfzip)|*.bcf;*.bcfzip" + NamingStrategy = new CamelCaseNamingStrategy() }; - - if (!openFileDialog.ShowDialog() ?? false || openFileDialog.FileName == null) + var serializerSettings = new JsonSerializerSettings { - return; - } + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }; + await callback.ExecuteAsync(JsonConvert.SerializeObject(bcfResult, serializerSettings)); + }); + } - var bcfFilePath = openFileDialog.FileName; - Task.Run(async () => - { - var bcfFileName = Path.GetFileName(bcfFilePath); - using var bcfFileStream = File.OpenRead(bcfFilePath); - var bcfResult = await new BcfImportService().ImportBcfFileAsync(bcfFileStream, bcfFileName ?? "issue.bcf"); - var contractResolver = new DefaultContractResolver - { - NamingStrategy = new CamelCaseNamingStrategy() - }; - var serializerSettings = new JsonSerializerSettings - { - ContractResolver = contractResolver, - Formatting = Formatting.Indented - }; - await callback.ExecuteAsync(JsonConvert.SerializeObject(bcfResult, serializerSettings)); - }); + private void HandleSaveBcfFileCallback(SaveBcfFileQueueItem saveBcfFileQueueItem) + { + if (saveBcfFileQueueItem.BcfFile == null || saveBcfFileQueueItem.Callback == null) + { + return; } + + var saveFileDialog = new SaveFileDialog + { + Filter = "BCF Files (*.bcf)|*.bcf" + }; + + if (!saveFileDialog.ShowDialog() ?? false || saveFileDialog.FileName == null) + { + return; + } + + var bcfFilePath = saveFileDialog.FileName; + Task.Run(async () => + { + var bcfFileResult = new BcfExportService().ExportBcfFile(saveBcfFileQueueItem.BcfFile); + using var fs = File.Create(bcfFilePath); + await bcfFileResult.CopyToAsync(fs); + + await saveBcfFileQueueItem.Callback.ExecuteAsync(); + }); } } } diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index 431c5ed1..a0fb5684 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -28,7 +28,7 @@ export class RevitBackendService { } exportBcfFile(bcfFile: BcfFile): Observable { - throw new Error('Method not implemented.'); + return this.sendCommand('exportBcfFile', bcfFile); } openDocumentation(): void { From e1e1cce9ae82275ee4af0802620d05888d4bebfe Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Fri, 5 Apr 2024 22:39:21 +0200 Subject: [PATCH 20/25] Update UI to not render BCF files that are not active --- src/ipa-bcfier-ui/src/app/app.component.html | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/ipa-bcfier-ui/src/app/app.component.html b/src/ipa-bcfier-ui/src/app/app.component.html index 9b854022..fe9634f0 100644 --- a/src/ipa-bcfier-ui/src/app/app.component.html +++ b/src/ipa-bcfier-ui/src/app/app.component.html @@ -4,7 +4,7 @@

IPA.BCFier

- + {{ bcfFile.fileName }} - + From 057d75b37db414f60819c965ae014dc8b7a72c87 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sat, 6 Apr 2024 12:38:19 +0200 Subject: [PATCH 21/25] Implement idling unregister feature --- .../OpenIpaBcfierWindowCommand.cs | 5 +++-- src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs | 20 ++++++++++++++++++- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs index 92fda815..4a44193d 100644 --- a/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs +++ b/src/IPA.Bcfier.Revit/OpenIpaBcfierWindowCommand.cs @@ -24,7 +24,8 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme #endif var taskQueueHandler = new RevitTaskQueueHandler(); - browser.JavascriptObjectRepository.Register("bcfierJavascriptBridge", new BcfierJavascriptBridge(taskQueueHandler), true); + var bcfierJavascriptBridge = new BcfierJavascriptBridge(taskQueueHandler); + browser.JavascriptObjectRepository.Register("bcfierJavascriptBridge", bcfierJavascriptBridge, true); browser.RequestHandler = new PluginRequestHandler(proxyToLocalhost); #if DEBUG_BUILD browser.IsBrowserInitializedChanged += (s, e) => @@ -54,7 +55,7 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme window.Closed += (s, e) => { - commandData.Application.Idling -= taskQueueHandler.OnIdling; + taskQueueHandler.UnregisterEventHandler(); browser.Dispose(); }; diff --git a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs index bdc62a9c..f46d8df9 100644 --- a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs +++ b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs @@ -1,4 +1,5 @@ -using Autodesk.Revit.UI.Events; +using Autodesk.Revit.UI; +using Autodesk.Revit.UI.Events; using CefSharp; using IPA.Bcfier.Revit.Models; using IPA.Bcfier.Services; @@ -12,9 +13,21 @@ public class RevitTaskQueueHandler { public Queue OpenBcfFileCallbacks = new Queue(); public Queue SaveBcfFileCallbacks = new Queue(); + private bool shouldUnregister = false; public void OnIdling(object sender, IdlingEventArgs args) { + var uiApplication = sender as UIApplication; + if (uiApplication == null) + { + return; + } + + if (shouldUnregister) + { + uiApplication.Idling -= OnIdling; + } + if (OpenBcfFileCallbacks.Count > 0) { var callback = OpenBcfFileCallbacks.Dequeue(); @@ -27,6 +40,11 @@ public void OnIdling(object sender, IdlingEventArgs args) HandleSaveBcfFileCallback(saveBcfFileQueueItem); } } + public void UnregisterEventHandler() + { + shouldUnregister = true; + } + private void HandleOpenBcfFileCallback(IJavascriptCallback callback) { From b871c8731add19f2e3adcd537b302a673a1da4ad Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sat, 6 Apr 2024 13:11:03 +0200 Subject: [PATCH 22/25] Update WPF BCFier to work with Revit v2024 --- Bcfier.Revit/App.config | 2 +- Bcfier.Revit/Bcfier.Revit.csproj | 145 +++++++++--------- Bcfier.Revit/Data/RevitUtils.cs | 12 -- Bcfier.Revit/Entry/CmdMain.cs | 3 +- Bcfier.Revit/Properties/Resources.Designer.cs | 2 +- Bcfier.Revit/Properties/Settings.Designer.cs | 2 +- Bcfier.Revit/packages.config | 1 + 7 files changed, 80 insertions(+), 87 deletions(-) diff --git a/Bcfier.Revit/App.config b/Bcfier.Revit/App.config index 0c7ae12f..a33cfa87 100644 --- a/Bcfier.Revit/App.config +++ b/Bcfier.Revit/App.config @@ -1,7 +1,7 @@ - + diff --git a/Bcfier.Revit/Bcfier.Revit.csproj b/Bcfier.Revit/Bcfier.Revit.csproj index 4b768c49..c2ab9f4d 100644 --- a/Bcfier.Revit/Bcfier.Revit.csproj +++ b/Bcfier.Revit/Bcfier.Revit.csproj @@ -9,7 +9,7 @@ Properties Bcfier.Revit Bcfier.Revit - v4.7 + v4.8.1 512 {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 4 @@ -274,40 +274,39 @@ false v4.8 - - - true - bin\Debug-2022\ - TRACE;DEBUG;Version2022 - full - x64 - prompt - MinimumRecommendedRules.ruleset - 2022 - $(AssemblyName) - false - Program - C:\Program Files\Autodesk\Revit 2022\Revit.exe - v4.8 - - - bin\Release-2022\ - TRACE;RELEASE;Version2022 - true - pdbonly - AnyCPU - prompt - MinimumRecommendedRules.ruleset - 2022 - $(AssemblyName) - 0 - + + + true + bin\Debug-2022\ + TRACE;DEBUG;Version2022 + full + x64 + prompt + MinimumRecommendedRules.ruleset + 2022 + $(AssemblyName) + false + Program + C:\Program Files\Autodesk\Revit 2022\Revit.exe + v4.8 + + + bin\Release-2022\ + TRACE;RELEASE;Version2022 + true + pdbonly + AnyCPU + prompt + MinimumRecommendedRules.ruleset + 2022 + $(AssemblyName) + 0 + None - false - v4.8 - - + false + v4.8 + @@ -315,23 +314,17 @@ - - C:\Program Files\Autodesk\Revit 2017\RevitAPI.dll + + ..\packages\Revit_All_Main_Versions_API_x64.2024.0.0\lib\net48\AdWindows.dll + False False - - C:\Program Files\Autodesk\Revit 2017\RevitAPIUI.dll - False - - - C:\Program Files\Autodesk\Revit 2017\RevitAPIIFC.dll + + ..\packages\Revit_All_Main_Versions_API_x64.2024.0.0\lib\net48\RevitAPI.dll + False False - - C:\Program Files\Autodesk\Revit 2018\RevitAPI.dll - False - C:\Program Files\Autodesk\Revit 2018\RevitAPIUI.dll False @@ -379,19 +372,19 @@ C:\Program Files\Autodesk\Revit 2021\RevitAPIIFC.dll False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPI.dll - False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPIUI.dll - False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPIIFC.dll - False - + + + C:\Program Files\Autodesk\Revit 2022\RevitAPI.dll + False + + + C:\Program Files\Autodesk\Revit 2022\RevitAPIUI.dll + False + + + C:\Program Files\Autodesk\Revit 2022\RevitAPIIFC.dll + False + @@ -484,23 +477,28 @@ C:\Program Files\Autodesk\Revit 2021\RevitAPIIFC.dll False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPI.dll - False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPIUI.dll - False - - - C:\Program Files\Autodesk\Revit 2022\RevitAPIIFC.dll - False - + + + C:\Program Files\Autodesk\Revit 2022\RevitAPI.dll + False + + + C:\Program Files\Autodesk\Revit 2022\RevitAPIUI.dll + False + + + C:\Program Files\Autodesk\Revit 2022\RevitAPIIFC.dll + False + C:\Program Files\Autodesk\Revit 2016\RevitAPIUI.dll + + ..\packages\Revit_All_Main_Versions_API_x64.2024.0.0\lib\net48\RevitAPIUI.dll + False + False + @@ -508,6 +506,11 @@ 4.0 + + ..\packages\Revit_All_Main_Versions_API_x64.2024.0.0\lib\net48\UIFramework.dll + False + False + diff --git a/Bcfier.Revit/Data/RevitUtils.cs b/Bcfier.Revit/Data/RevitUtils.cs index 01ff2dea..9446bca8 100644 --- a/Bcfier.Revit/Data/RevitUtils.cs +++ b/Bcfier.Revit/Data/RevitUtils.cs @@ -30,11 +30,7 @@ public static ViewOrientation3D ConvertBasePoint(Document doc, XYZ c, XYZ view, //if BPL is set to 0,0,0 not always it corresponds to Revit's origin XYZ origin = new XYZ(0, 0, 0); -#if Version2019 || Version2020 || Version2021 || Version2022 ProjectPosition position = doc.ActiveProjectLocation.GetProjectPosition(origin); -#else - ProjectPosition position = doc.ActiveProjectLocation.get_ProjectPosition(origin); -#endif int i = (negative) ? -1 : 1; @@ -90,11 +86,7 @@ public static XYZ GetRevitXYZ(Point d) /// public static double ToMeters(this double feet) { -#if Version2021 || Version2022 return UnitUtils.ConvertFromInternalUnits(feet, UnitTypeId.Meters); -#else - return UnitUtils.ConvertFromInternalUnits(feet, DisplayUnitType.DUT_METERS); -#endif } /// /// Converts meters units to feet @@ -103,11 +95,7 @@ public static double ToMeters(this double feet) /// public static double ToFeet(this double meters) { -#if Version2021 || Version2022 return UnitUtils.ConvertToInternalUnits(meters, UnitTypeId.Meters); -#else - return UnitUtils.ConvertToInternalUnits(meters, DisplayUnitType.DUT_METERS); -#endif } } diff --git a/Bcfier.Revit/Entry/CmdMain.cs b/Bcfier.Revit/Entry/CmdMain.cs index 8c5e7145..6951561e 100644 --- a/Bcfier.Revit/Entry/CmdMain.cs +++ b/Bcfier.Revit/Entry/CmdMain.cs @@ -45,9 +45,10 @@ public class CmdMain : IExternalCommand #endif + public const string RevitVersion = "2024"; - internal static CmdMain ThisCmd = null; + internal static CmdMain ThisCmd = null; private static bool _isRunning; private static ExtAppBcfier _extAppBcfier; diff --git a/Bcfier.Revit/Properties/Resources.Designer.cs b/Bcfier.Revit/Properties/Resources.Designer.cs index 809ce116..1f10e4c2 100644 --- a/Bcfier.Revit/Properties/Resources.Designer.cs +++ b/Bcfier.Revit/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace Bcfier.Revit.Properties { // class via a tool like ResGen or Visual Studio. // To add or remove a member, edit your .ResX file then rerun ResGen // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { diff --git a/Bcfier.Revit/Properties/Settings.Designer.cs b/Bcfier.Revit/Properties/Settings.Designer.cs index c35c5dff..78a44116 100644 --- a/Bcfier.Revit/Properties/Settings.Designer.cs +++ b/Bcfier.Revit/Properties/Settings.Designer.cs @@ -12,7 +12,7 @@ namespace Bcfier.Revit.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.5.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.9.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); diff --git a/Bcfier.Revit/packages.config b/Bcfier.Revit/packages.config index a6ed660e..28f14170 100644 --- a/Bcfier.Revit/packages.config +++ b/Bcfier.Revit/packages.config @@ -1,4 +1,5 @@  + \ No newline at end of file From cd7ca3c542efbfb3731bc0b59ba8670c84fcdf9e Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sat, 6 Apr 2024 13:14:58 +0200 Subject: [PATCH 23/25] Add viewpoint creation for Revit --- .../BcfierJavascriptBridge.cs | 9 +- src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs | 32 ++- .../Services/IfcGuidExtensions.cs | 167 ++++++++++++++++ .../Services/RevitUtilities.cs | 126 ++++++++++++ .../Services/RevitViewpointCreationService.cs | 189 ++++++++++++++++++ .../src/app/services/RevitBackendService.ts | 2 +- 6 files changed, 519 insertions(+), 6 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs create mode 100644 src/IPA.Bcfier.Revit/Services/RevitUtilities.cs create mode 100644 src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index e5633d27..19f877d6 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -25,7 +25,6 @@ private class DataClass public string Data { get; set; } } - public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCallback) { var classData = JsonConvert.DeserializeObject(data)!; @@ -42,13 +41,13 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal if (classData.Command == "getSettings") { - var userSettings = await new Services.SettingsService().LoadSettingsAsync(); + var userSettings = await new IPA.Bcfier.Services.SettingsService().LoadSettingsAsync(); await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); } else if (classData.Command == "setSettings") { var userSettings = JsonConvert.DeserializeObject(classData.Data); - await new Services.SettingsService().SaveSettingsAsync(userSettings!); + await new IPA.Bcfier.Services.SettingsService().SaveSettingsAsync(userSettings!); await javascriptCallback.ExecuteAsync(JsonConvert.SerializeObject(userSettings, serializerSettings)); } else if (classData.Command == "openDocumentation") @@ -70,6 +69,10 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal BcfFile = JsonConvert.DeserializeObject(classData.Data) }); } + else if (classData.Command == "createViewpoint") + { + _revitTaskQueueHandler.CreateRevitViewpointCallbacks.Enqueue(javascriptCallback); + } else { // TODO return error for unrecognized commands diff --git a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs index f46d8df9..9ae75b77 100644 --- a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs +++ b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs @@ -1,7 +1,8 @@ -using Autodesk.Revit.UI; +using Autodesk.Revit.UI; using Autodesk.Revit.UI.Events; using CefSharp; using IPA.Bcfier.Revit.Models; +using IPA.Bcfier.Revit.Services; using IPA.Bcfier.Services; using Microsoft.Win32; using Newtonsoft.Json; @@ -13,6 +14,7 @@ public class RevitTaskQueueHandler { public Queue OpenBcfFileCallbacks = new Queue(); public Queue SaveBcfFileCallbacks = new Queue(); + public Queue CreateRevitViewpointCallbacks = new Queue(); private bool shouldUnregister = false; public void OnIdling(object sender, IdlingEventArgs args) @@ -39,13 +41,20 @@ public void OnIdling(object sender, IdlingEventArgs args) var saveBcfFileQueueItem = SaveBcfFileCallbacks.Dequeue(); HandleSaveBcfFileCallback(saveBcfFileQueueItem); } + + if (CreateRevitViewpointCallbacks.Count > 0) + { + var uiDocument = uiApplication.ActiveUIDocument; + var callback = CreateRevitViewpointCallbacks.Dequeue(); + HandleCreateRevitViewpointCallback(callback, uiDocument); + } } + public void UnregisterEventHandler() { shouldUnregister = true; } - private void HandleOpenBcfFileCallback(IJavascriptCallback callback) { var openFileDialog = new OpenFileDialog @@ -104,5 +113,24 @@ private void HandleSaveBcfFileCallback(SaveBcfFileQueueItem saveBcfFileQueueItem await saveBcfFileQueueItem.Callback.ExecuteAsync(); }); } + + private void HandleCreateRevitViewpointCallback(IJavascriptCallback callback, UIDocument uIDocument) + { + var viewpointService = new RevitViewpointCreationService(uIDocument); + var viewpoint = viewpointService.GenerateViewpoint(); + var contractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + var serializerSettings = new JsonSerializerSettings + { + ContractResolver = contractResolver, + Formatting = Formatting.Indented + }; + Task.Run(async () => + { + await callback.ExecuteAsync(JsonConvert.SerializeObject(viewpoint, serializerSettings)); + }); + } } } diff --git a/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs b/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs new file mode 100644 index 00000000..f976867c --- /dev/null +++ b/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs @@ -0,0 +1,167 @@ +using System.Diagnostics; +using System.Globalization; + +namespace IPA.Bcfier.Revit.Services +{ + /// + /// Conversion methods between an IFC + /// encoded GUID string and a .NET GUID. + /// This is a translation of the C code + /// found here: + /// http://www.iai-tech.org/ifc/IFC2x3/TC1/html/index.htm + /// + public static class IfcGuidExtensions + { + /// + /// The replacement table + /// + private static readonly char[] base64Chars = new char[] + { '0','1','2','3','4','5','6','7','8','9' + , 'A','B','C','D','E','F','G','H','I','J' + , 'K','L','M','N','O','P','Q','R','S','T' + , 'U','V','W','X','Y','Z','a','b','c','d' + , 'e','f','g','h','i','j','k','l','m','n' + , 'o','p','q','r','s','t','u','v','w','x' + , 'y','z','_','$' }; + + /// + /// Conversion of an integer into characters + /// with base 64 using the table base64Chars + /// + /// The number to convert + /// The result char array to write to + /// The position in the char array to start writing + /// The length to write + /// + static void cv_to_64(uint number, ref char[] result, int start, int len) + { + uint act; + int iDigit, nDigits; + + Debug.Assert(len <= 4); + act = number; + nDigits = len; + + for (iDigit = 0; iDigit < nDigits; iDigit++) + { + result[start + len - iDigit - 1] = base64Chars[(int)(act % 64)]; + act /= 64; + } + Debug.Assert(act == 0, "Logic failed, act was not null: " + act.ToString()); + return; + } + + /// + /// The reverse function to calculate + /// the number from the characters + /// + /// The char array to convert from + /// Position in array to start read + /// The length to read + /// The calculated nuber + static uint cv_from_64(char[] str, int start, int len) + { + int i, j, index; + var res = 0; + Debug.Assert(len <= 4); + + for (i = 0; i < len; i++) + { + index = -1; + for (j = 0; j < 64; j++) + { + if (base64Chars[j] == str[start + i]) + { + index = j; + break; + } + } + Debug.Assert(index >= 0); + res = res * 64 + ((uint)index); + } + return res; + } + + /// + /// Reconstruction of the GUID + /// from an IFC GUID string (base64) + /// + /// The GUID string to convert. Must be 22 characters long + /// GUID correspondig to the string + public static Guid FromIfcGUID(string guid) + { + Debug.Assert(guid.Length == 22, "Input string must not be longer that 22 chars"); + var num = new uint[6]; + var str = guid.ToCharArray(); + int n = 2, pos = 0, i; + for (i = 0; i < 6; i++) + { + num[i] = cv_from_64(str, pos, n); + pos += n; n = 4; + } + + var a = (int)((num[0] * 16777216 + num[1])); + var b = (short)(num[2] / 256); + var c = (short)((num[2] % 256) * 256 + num[3] / 65536); + var d = new byte[8]; + d[0] = Convert.ToByte((num[3] / 256) % 256); + d[1] = Convert.ToByte(num[3] % 256); + d[2] = Convert.ToByte(num[4] / 65536); + d[3] = Convert.ToByte((num[4] / 256) % 256); + d[4] = Convert.ToByte(num[4] % 256); + d[5] = Convert.ToByte(num[5] / 65536); + d[6] = Convert.ToByte((num[5] / 256) % 256); + d[7] = Convert.ToByte(num[5] % 256); + + return new Guid(a, b, c, d); + } + + /// + /// Conversion of a GUID to a string + /// representing the GUID + /// + /// The GUID to convert + /// IFC (base64) encoded GUID string + public static string ToIfcGuid(this Guid guid) + { + var num = new uint[6]; + var str = new char[22]; + int i, n; + var b = guid.ToByteArray(); + + // Creation of six 32 Bit integers from the components of the GUID structure + num[0] = (uint)(BitConverter.ToUInt32(b, 0) / 16777216); + num[1] = (uint)(BitConverter.ToUInt32(b, 0) % 16777216); + num[2] = (uint)(BitConverter.ToUInt16(b, 4) * 256 + BitConverter.ToUInt16(b, 6) / 256); + num[3] = (uint)((BitConverter.ToUInt16(b, 6) % 256) * 65536 + b[8] * 256 + b[9]); + num[4] = (uint)(b[10] * 65536 + b[11] * 256 + b[12]); + num[5] = (uint)(b[13] * 65536 + b[14] * 256 + b[15]); + + // Conversion of the numbers into a system using a base of 64 + n = 2; + var pos = 0; + for (i = 0; i < 6; i++) + { + cv_to_64(num[i], ref str, pos, n); + pos += n; n = 4; + } + return new string(str); + } + + // + //Get the Unique ID in encoded on IFC Format (base 64) + // + // + // + public static string IfcGUID(string UniqueId) + { + var episodeId = new Guid(UniqueId.Substring(0, 36)); + var elementId = int.Parse(UniqueId.Substring(37), NumberStyles.AllowHexSpecifier); + var last_32_bits = int.Parse(UniqueId.Substring(28, 8), NumberStyles.AllowHexSpecifier); + var xor = last_32_bits ^ elementId; + UniqueId = UniqueId.Substring(0, 28) + xor.ToString("x8"); + var guid = new Guid(UniqueId); + return ToIfcGuid(guid); + } + } +} diff --git a/src/IPA.Bcfier.Revit/Services/RevitUtilities.cs b/src/IPA.Bcfier.Revit/Services/RevitUtilities.cs new file mode 100644 index 00000000..64771924 --- /dev/null +++ b/src/IPA.Bcfier.Revit/Services/RevitUtilities.cs @@ -0,0 +1,126 @@ +using Autodesk.Revit.DB; +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.Services +{ + public static class RevitUtilities + { + public static string GetRevitSnapshotBase64(Document doc) + { + string tempImg = Path.Combine(Path.GetTempPath(), "BCFier", Path.GetTempFileName() + ".png"); + var options = new ImageExportOptions + { + FilePath = tempImg, + HLRandWFViewsFileType = ImageFileType.PNG, + ShadowViewsFileType = ImageFileType.PNG, + ExportRange = ExportRange.VisibleRegionOfCurrentView, + ZoomType = ZoomFitType.FitToPage, + ImageResolution = ImageResolution.DPI_72, + PixelSize = 1000 + }; + doc.ExportImage(options); + + string base64String; + using var memStream = new MemoryStream(); + using (var fileStream = new FileStream(tempImg, FileMode.Open, FileAccess.Read)) + { + fileStream.CopyTo(memStream); + base64String = Convert.ToBase64String(memStream.ToArray()); + } + + File.Delete(tempImg); + return base64String; + } + + /// + /// MOVES THE CAMERA ACCORDING TO THE PROJECT BASE LOCATION + /// function that changes the coordinates accordingly to the project base location to an absolute location (for BCF export) + /// if the value negative is set to true, does the opposite (for opening BCF views) + /// + /// center + /// view direction + /// up direction + /// convert to/from + /// + public static ViewOrientation3D ConvertBasePoint(Document doc, XYZ c, XYZ view, XYZ up, bool negative) + { + double angle = 0; + double x = 0; + double y = 0; + double z = 0; + + //VERY IMPORTANT + //BuiltInParameter.BASEPOINT_EASTWEST_PARAM is the value of the BASE POINT LOCATION + //position is the location of the BPL related to Revit's absolute origin + //if BPL is set to 0,0,0 not always it corresponds to Revit's origin + + XYZ origin = new XYZ(0, 0, 0); + ProjectPosition position = doc.ActiveProjectLocation.GetProjectPosition(origin); + + int i = (negative) ? -1 : 1; + + x = i * position.EastWest; + y = i * position.NorthSouth; + z = i * position.Elevation; + angle = i * position.Angle; + + if (negative) // I do the addition BEFORE + c = new XYZ(c.X + x, c.Y + y, c.Z + z); + + //rotation + double centX = (c.X * Math.Cos(angle)) - (c.Y * Math.Sin(angle)); + double centY = (c.X * Math.Sin(angle)) + (c.Y * Math.Cos(angle)); + + XYZ newC = new XYZ(); + if (negative) + newC = new XYZ(centX, centY, c.Z); + else // I do the addition AFTERWARDS + newC = new XYZ(centX + x, centY + y, c.Z + z); + + + double viewX = (view.X * Math.Cos(angle)) - (view.Y * Math.Sin(angle)); + double viewY = (view.X * Math.Sin(angle)) + (view.Y * Math.Cos(angle)); + XYZ newView = new XYZ(viewX, viewY, view.Z); + + double upX = (up.X * Math.Cos(angle)) - (up.Y * Math.Sin(angle)); + double upY = (up.X * Math.Sin(angle)) + (up.Y * Math.Cos(angle)); + + XYZ newUp = new XYZ(upX, upY, up.Z); + return new ViewOrientation3D(newC, newUp, newView); + } + + public static XYZ GetRevitXYZ(double X, double Y, double Z) + { + return new XYZ(X.ToFeet(), Y.ToFeet(), Z.ToFeet()); + } + + public static XYZ GetRevitXYZ(BcfViewpointVector bcfVector) + { + return new XYZ(bcfVector.X.ToFeet(), bcfVector.Y.ToFeet(), bcfVector.Z.ToFeet()); + } + + public static XYZ GetRevitXYZ(BcfViewpointPoint bcfPoint) + { + return new XYZ(bcfPoint.X.ToFeet(), bcfPoint.Y.ToFeet(), bcfPoint.Z.ToFeet()); + } + + /// + /// Converts feet units to meters + /// + /// Value in feet to be converted to meters + /// + public static double ToMeters(this double feet) + { + return UnitUtils.ConvertFromInternalUnits(feet, UnitTypeId.Meters); + } + /// + /// Converts meters units to feet + /// + /// Value in feet to be converted to feet + /// + public static double ToFeet(this double meters) + { + return UnitUtils.ConvertToInternalUnits(meters, UnitTypeId.Meters); + } + } +} diff --git a/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs b/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs new file mode 100644 index 00000000..c43f1e4f --- /dev/null +++ b/src/IPA.Bcfier.Revit/Services/RevitViewpointCreationService.cs @@ -0,0 +1,189 @@ +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.Services +{ + public class RevitViewpointCreationService + { + private readonly UIDocument _uiDocument; + + public RevitViewpointCreationService(UIDocument uiDocument) + { + _uiDocument = uiDocument; + } + + // + //Generate a VisualizationInfo of the current view + // + // + public BcfViewpoint GenerateViewpoint() + { + try + { + var doc = _uiDocument.Document; + + var bcfViewpoint = new BcfViewpoint(); + + //Corners of the active UI view + var topLeft = _uiDocument.GetOpenUIViews()[0].GetZoomCorners()[0]; + var bottomRight = _uiDocument.GetOpenUIViews()[0].GetZoomCorners()[1]; + + if (_uiDocument.ActiveView.ViewType == ViewType.ThreeD) + { + //It's a 3d view + var viewCenter = new XYZ(); + var view3D = (View3D)_uiDocument.ActiveView; + double zoomValue = 1; + // it is a orthogonal view + if (!view3D.IsPerspective) + { + double x = (topLeft.X + bottomRight.X) / 2; + double y = (topLeft.Y + bottomRight.Y) / 2; + double z = (topLeft.Z + bottomRight.Z) / 2; + //center of the UI view + viewCenter = new XYZ(x, y, z); + + //vector going from BR to TL + XYZ diagVector = topLeft.Subtract(bottomRight); + //length of the vector + double dist = topLeft.DistanceTo(bottomRight) / 2; + + //ViewToWorldScale value + zoomValue = dist * Math.Sin(diagVector.AngleTo(view3D.RightDirection)).ToMeters(); + + ViewOrientation3D t = RevitUtilities.ConvertBasePoint(doc, viewCenter, _uiDocument.ActiveView.ViewDirection, + _uiDocument.ActiveView.UpDirection, false); + + XYZ c = t.EyePosition; + XYZ vi = t.ForwardDirection; + XYZ up = t.UpDirection; + + bcfViewpoint.OrthogonalCamera = new BcfViewpointOrthogonalCamera + { + ViewPoint = + { + X = c.X.ToMeters(), + Y = c.Y.ToMeters(), + Z = c.Z.ToMeters() + }, + UpVector = + { + X = up.X.ToMeters(), + Y = up.Y.ToMeters(), + Z = up.Z.ToMeters() + }, + Direction = + { + X = vi.X.ToMeters() * -1, + Y = vi.Y.ToMeters() * -1, + Z = vi.Z.ToMeters() * -1 + }, + ViewToWorldScale = zoomValue + }; + } + // it is a perspective view + else + { + viewCenter = _uiDocument.ActiveView.Origin; + //revit default value + zoomValue = 45; + + ViewOrientation3D t = RevitUtilities.ConvertBasePoint(doc, viewCenter, _uiDocument.ActiveView.ViewDirection, + _uiDocument.ActiveView.UpDirection, false); + + XYZ c = t.EyePosition; + XYZ vi = t.ForwardDirection; + XYZ up = t.UpDirection; + + bcfViewpoint.PerspectiveCamera = new BcfViewpointPerspectiveCamera + { + ViewPoint = + { + X = c.X.ToMeters(), + Y = c.Y.ToMeters(), + Z = c.Z.ToMeters() + }, + UpVector = + { + X = up.X.ToMeters(), + Y = up.Y.ToMeters(), + Z = up.Z.ToMeters() + }, + Direction = + { + X = vi.X.ToMeters() * -1, + Y = vi.Y.ToMeters() * -1, + Z = vi.Z.ToMeters() * -1 + }, + FieldOfView = zoomValue + }; + } + } + //COMPONENTS PART + string versionName = doc.Application.VersionName; + + var visibleElems = new FilteredElementCollector(doc, doc.ActiveView.Id) + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .ToElementIds(); + var hiddenElems = new FilteredElementCollector(doc) + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .Where(x => x.IsHidden(doc.ActiveView) + || !doc.ActiveView.IsElementVisibleInTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate, x.Id)).Select(x => x.Id) + ;//would need to check how much this is affecting performance + + var selectedElems = _uiDocument.Selection.GetElementIds(); + + var viewpointComponents = new BcfViewpointComponents(); + bcfViewpoint.ViewpointComponents = viewpointComponents; + viewpointComponents.Visibility = new BcfViewpointComponentVisibility(); + + //TODO: set ViewSetupHints + //TODO: create clipping planes + //list of hidden components is smaller than the list of visible components + if (visibleElems.Count() > hiddenElems.Count()) + { + viewpointComponents.Visibility.DefaultVisibility = true; + viewpointComponents.Visibility.Exceptions = hiddenElems.Select(x => new BcfViewpointComponent + { + OriginatingSystem = versionName, + IfcGuid = ExportUtils.GetExportId(doc, x).ToIfcGuid(), + AuthoringToolId = x.Value.ToString() + }).ToList(); + } + //list of visible components is smaller or equals the list of hidden components + else + { + viewpointComponents.Visibility.DefaultVisibility = false; + viewpointComponents.Visibility.Exceptions = visibleElems.Select(x => new BcfViewpointComponent + { + OriginatingSystem = versionName, + IfcGuid = ExportUtils.GetExportId(doc, x).ToIfcGuid(), + AuthoringToolId = x.Value.ToString() + }).ToList(); + } + + //selected elements + viewpointComponents.SelectedComponents = selectedElems.Select(x => new BcfViewpointComponent + { + OriginatingSystem = versionName, + IfcGuid = IfcGuidExtensions.ToIfcGuid(ExportUtils.GetExportId(doc, x)), + AuthoringToolId = x.Value.ToString() + }).ToList(); + + var snapshotBase64 = RevitUtilities.GetRevitSnapshotBase64(_uiDocument.Document); + bcfViewpoint.SnapshotBase64 = snapshotBase64; + + return bcfViewpoint; + } + catch (System.Exception ex1) + { + TaskDialog.Show("Error generating viewpoint", "exception: " + ex1); + } + + return null; + } + } +} diff --git a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts index a0fb5684..17932b07 100644 --- a/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts +++ b/src/ipa-bcfier-ui/src/app/services/RevitBackendService.ts @@ -44,7 +44,7 @@ export class RevitBackendService { } addViewpoint(): Observable { - throw new Error('Method not implemented.'); + return this.sendCommand('createViewpoint', null); } private sendCommand(command: string, data: any): Observable { From fd24d34babbbbcc893b092d02e24883ee4f6f5b9 Mon Sep 17 00:00:00 2001 From: Georg Dangl Date: Sat, 6 Apr 2024 14:54:00 +0200 Subject: [PATCH 24/25] Add loading view to Revit UI --- .../BcfierJavascriptBridge.cs | 9 + .../Models/ShowViewpointQueueItem.cs | 12 + src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs | 80 +++++- .../Services/IfcGuidExtensions.cs | 2 +- .../Services/RevitViewpointDisplayService.cs | 258 ++++++++++++++++++ .../Services/ViewContinuationInstructions.cs | 14 + .../comments-detail.component.html | 6 +- .../comments-detail.component.scss | 1 + .../comments-detail.component.ts | 10 +- .../src/app/services/BackendService.ts | 4 + .../src/app/services/RevitBackendService.ts | 4 + 11 files changed, 392 insertions(+), 8 deletions(-) create mode 100644 src/IPA.Bcfier.Revit/Models/ShowViewpointQueueItem.cs create mode 100644 src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs create mode 100644 src/IPA.Bcfier.Revit/Services/ViewContinuationInstructions.cs diff --git a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs index 19f877d6..755043a9 100644 --- a/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs +++ b/src/IPA.Bcfier.Revit/BcfierJavascriptBridge.cs @@ -73,6 +73,15 @@ public async Task SendDataToRevit(string data, IJavascriptCallback javascriptCal { _revitTaskQueueHandler.CreateRevitViewpointCallbacks.Enqueue(javascriptCallback); } + else if (classData.Command == "showViewpoint") + { + var viewpoint = JsonConvert.DeserializeObject(classData.Data); + _revitTaskQueueHandler.ShowViewpointQueueItems.Enqueue(new ShowViewpointQueueItem + { + Callback = javascriptCallback, + Viewpoint = viewpoint + }); + } else { // TODO return error for unrecognized commands diff --git a/src/IPA.Bcfier.Revit/Models/ShowViewpointQueueItem.cs b/src/IPA.Bcfier.Revit/Models/ShowViewpointQueueItem.cs new file mode 100644 index 00000000..c0920fd7 --- /dev/null +++ b/src/IPA.Bcfier.Revit/Models/ShowViewpointQueueItem.cs @@ -0,0 +1,12 @@ +using CefSharp; +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.Models +{ + public class ShowViewpointQueueItem + { + public IJavascriptCallback? Callback { get; set; } + + public BcfViewpoint? Viewpoint { get; set; } + } +} diff --git a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs index 9ae75b77..5b592930 100644 --- a/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs +++ b/src/IPA.Bcfier.Revit/RevitTaskQueueHandler.cs @@ -1,6 +1,7 @@ using Autodesk.Revit.UI; using Autodesk.Revit.UI.Events; using CefSharp; +using IPA.Bcfier.Models.Bcf; using IPA.Bcfier.Revit.Models; using IPA.Bcfier.Revit.Services; using IPA.Bcfier.Services; @@ -12,11 +13,14 @@ namespace IPA.Bcfier.Revit { public class RevitTaskQueueHandler { - public Queue OpenBcfFileCallbacks = new Queue(); - public Queue SaveBcfFileCallbacks = new Queue(); - public Queue CreateRevitViewpointCallbacks = new Queue(); + public Queue OpenBcfFileCallbacks { get; } = new Queue(); + public Queue SaveBcfFileCallbacks { get; } = new Queue(); + public Queue CreateRevitViewpointCallbacks { get; } = new Queue(); + public Queue ShowViewpointQueueItems { get; } = new Queue(); private bool shouldUnregister = false; + private Queue AfterViewCreationCallbackQueue { get; } = new Queue(); + public void OnIdling(object sender, IdlingEventArgs args) { var uiApplication = sender as UIApplication; @@ -48,6 +52,50 @@ public void OnIdling(object sender, IdlingEventArgs args) var callback = CreateRevitViewpointCallbacks.Dequeue(); HandleCreateRevitViewpointCallback(callback, uiDocument); } + + if (ShowViewpointQueueItems.Count > 0) + { + var uiDocument = uiApplication.ActiveUIDocument; + var showViewpointQueueItem = ShowViewpointQueueItems.Dequeue(); + HandleShowRevitViewpointCallback(showViewpointQueueItem.Callback, showViewpointQueueItem.Viewpoint, uiDocument); + } + + if (AfterViewCreationCallbackQueue.Count > 0) + { + var uiDocument = uiApplication.ActiveUIDocument; + HandlAfterViewCreationCallbackQueueItems(uiDocument); + } + } + + private void HandlAfterViewCreationCallbackQueueItems(UIDocument uiDocument) + { + // This is pretty complicated. The signal flow is like this: + // 1. User clicks on a button in the web view + // 2. We send that data to the Revit API, which puts the request on a queue + // 3. During the Revit Application.Idling event, we process the queue + // 4. A viewpoint display request is processed, and a view is created and set as active view + // The active view can only be set in an asynchronous way from the Application.Idling event in + // the Revit API, so we need to wait until the new view is loaded + // 5. Once the view is loaded, we check this other queue here and apply the callback, which sets + // e.g. the selected components + // 6. After that, we can inform the frontend + var queueLength = AfterViewCreationCallbackQueue.Count; + for (var i = 0; i < queueLength; i++) + { + var item = AfterViewCreationCallbackQueue.Dequeue(); + if (item?.ViewId == uiDocument.ActiveView.Id) + { + item.ViewContinuation?.Invoke(); + Task.Run(async () => + { + await item.JavascriptCallback.ExecuteAsync(); + }); + } + else if (item != null) + { + AfterViewCreationCallbackQueue.Enqueue(item); + } + } } public void UnregisterEventHandler() @@ -114,9 +162,9 @@ private void HandleSaveBcfFileCallback(SaveBcfFileQueueItem saveBcfFileQueueItem }); } - private void HandleCreateRevitViewpointCallback(IJavascriptCallback callback, UIDocument uIDocument) + private void HandleCreateRevitViewpointCallback(IJavascriptCallback callback, UIDocument uiDocument) { - var viewpointService = new RevitViewpointCreationService(uIDocument); + var viewpointService = new RevitViewpointCreationService(uiDocument); var viewpoint = viewpointService.GenerateViewpoint(); var contractResolver = new DefaultContractResolver { @@ -132,5 +180,27 @@ private void HandleCreateRevitViewpointCallback(IJavascriptCallback callback, UI await callback.ExecuteAsync(JsonConvert.SerializeObject(viewpoint, serializerSettings)); }); } + + private void HandleShowRevitViewpointCallback(IJavascriptCallback? callback, BcfViewpoint? viewpoint, UIDocument uiDocument) + { + if (callback == null || viewpoint == null) + { + return; + } + + var viewpointService = new RevitViewpointDisplayService(uiDocument); + var afterViewInitCallback = viewpointService.DisplayViewpoint(viewpoint); + if (afterViewInitCallback?.ViewId == null) + { + Task.Run(async () => + { + await callback.ExecuteAsync(); + }); + return; + } + + afterViewInitCallback.JavascriptCallback = callback; + AfterViewCreationCallbackQueue.Enqueue(afterViewInitCallback); + } } } diff --git a/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs b/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs index f976867c..71926b4e 100644 --- a/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs +++ b/src/IPA.Bcfier.Revit/Services/IfcGuidExtensions.cs @@ -62,7 +62,7 @@ static void cv_to_64(uint number, ref char[] result, int start, int len) static uint cv_from_64(char[] str, int start, int len) { int i, j, index; - var res = 0; + var res = 0U; Debug.Assert(len <= 4); for (i = 0; i < len; i++) diff --git a/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs b/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs new file mode 100644 index 00000000..6dbb3328 --- /dev/null +++ b/src/IPA.Bcfier.Revit/Services/RevitViewpointDisplayService.cs @@ -0,0 +1,258 @@ +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using IPA.Bcfier.Models.Bcf; + +namespace IPA.Bcfier.Revit.Services +{ + public class RevitViewpointDisplayService + { + private readonly UIDocument _uiDocument; + + public RevitViewpointDisplayService(UIDocument uiDocument) + { + _uiDocument = uiDocument; + } + + public ViewContinuationInstructions? DisplayViewpoint(BcfViewpoint bcfViewpoint) + { + try + { + Document doc = _uiDocument.Document; + // We might later want to change this so it can also optionally show the viewpoint + // in the current view + var uniqueView = true; + + ElementId viewId = null; + // IS ORTHOGONAL + if (bcfViewpoint.OrthogonalCamera != null) + { + if (bcfViewpoint.OrthogonalCamera.ViewPoint == null || bcfViewpoint.OrthogonalCamera.UpVector == null || bcfViewpoint.OrthogonalCamera.Direction == null) + { + return null; + } + + //type = "OrthogonalCamera"; + var zoom = bcfViewpoint.OrthogonalCamera.ViewToWorldScale.ToFeet(); + var cameraDirection = RevitUtilities.GetRevitXYZ(bcfViewpoint.OrthogonalCamera.Direction); + var cameraUpVector = RevitUtilities.GetRevitXYZ(bcfViewpoint.OrthogonalCamera.UpVector); + var cameraViewPoint = RevitUtilities.GetRevitXYZ(bcfViewpoint.OrthogonalCamera.ViewPoint); + var orient3D = RevitUtilities.ConvertBasePoint(doc, cameraViewPoint, cameraDirection, cameraUpVector, true); + + View3D orthoView = null; + //if active view is 3d ortho use it + if (doc.ActiveView.ViewType == ViewType.ThreeD) + { + var activeView3D = doc.ActiveView as View3D; + if (!activeView3D.IsPerspective) + orthoView = activeView3D; + } + if (orthoView == null) + { + //try to use an existing 3D view + IEnumerable viewcollector3D = Get3DViews(doc); + if (viewcollector3D.Any(o => o.Name == "{3D}" || o.Name == "BCFortho")) + orthoView = viewcollector3D.First(o => o.Name == "{3D}" || o.Name == "BCFortho"); + } + using (var trans = new Transaction(_uiDocument.Document)) + { + if (trans.Start("Open orthogonal view") == TransactionStatus.Started) + { + //create a new 3d ortho view + + if (orthoView == null || uniqueView) + { + orthoView = View3D.CreateIsometric(doc, GetFamilyViews(doc).First().Id); + orthoView.Name = (uniqueView) ? "BCFortho" + DateTime.Now.ToString("yyyyMMddTHHmmss") : "BCFortho"; + } + else + { + //reusing an existing view, I net to reset the visibility + //placed this here because if set afterwards it doesn't work + orthoView.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate); + } + orthoView.SetOrientation(orient3D); + trans.Commit(); + } + } + + viewId = orthoView.Id; + _uiDocument.RequestViewChange(orthoView); + //adjust view rectangle + + + double x = zoom; + //set UI view position and zoom + XYZ m_xyzTl = _uiDocument.ActiveView.Origin.Add(_uiDocument.ActiveView.UpDirection.Multiply(x)).Subtract(_uiDocument.ActiveView.RightDirection.Multiply(x)); + XYZ m_xyzBr = _uiDocument.ActiveView.Origin.Subtract(_uiDocument.ActiveView.UpDirection.Multiply(x)).Add(_uiDocument.ActiveView.RightDirection.Multiply(x)); + _uiDocument.GetOpenUIViews().First().ZoomAndCenterRectangle(m_xyzTl, m_xyzBr); + } + //perspective + else if (bcfViewpoint.PerspectiveCamera != null) + { + if (bcfViewpoint.PerspectiveCamera.ViewPoint == null || bcfViewpoint.PerspectiveCamera.UpVector == null || bcfViewpoint.PerspectiveCamera.Direction == null) + { + return null; + } + + //not used since the fov cannot be changed in Revit + var zoom = bcfViewpoint.PerspectiveCamera.FieldOfView; + //FOV - not used + + var cameraDirection = RevitUtilities.GetRevitXYZ(bcfViewpoint.PerspectiveCamera.Direction); + var cameraUpVector = RevitUtilities.GetRevitXYZ(bcfViewpoint.PerspectiveCamera.UpVector); + var cameraViewPoint = RevitUtilities.GetRevitXYZ(bcfViewpoint.PerspectiveCamera.ViewPoint); + var orient3D = RevitUtilities.ConvertBasePoint(doc, cameraViewPoint, cameraDirection, cameraUpVector, true); + + View3D perspView = null; + //try to use an existing 3D view + IEnumerable viewcollector3D = Get3DViews(doc); + if (viewcollector3D.Any(o => o.Name == "BCFpersp")) + perspView = viewcollector3D.First(o => o.Name == "BCFpersp"); + + using (var trans = new Transaction(_uiDocument.Document)) + { + if (trans.Start("Open perspective view") == TransactionStatus.Started) + { + if (null == perspView || uniqueView) + { + perspView = View3D.CreatePerspective(doc, GetFamilyViews(doc).First().Id); + perspView.Name = (uniqueView) ? "BCFpersp" + DateTime.Now.ToString("yyyyMMddTHHmmss") : "BCFpersp"; + } + else + { + //reusing an existing view, I net to reset the visibility + //placed this here because if set afterwards it doesn't work + perspView.DisableTemporaryViewMode(TemporaryViewMode.TemporaryHideIsolate); + } + + perspView.SetOrientation(orient3D); + + // turn off the far clip plane + if (perspView.get_Parameter(BuiltInParameter.VIEWER_BOUND_ACTIVE_FAR).HasValue) + { + Parameter m_farClip = perspView.get_Parameter(BuiltInParameter.VIEWER_BOUND_ACTIVE_FAR); + m_farClip.Set(0); + } + perspView.CropBoxActive = true; + perspView.CropBoxVisible = true; + + trans.Commit(); + } + } + + _uiDocument.RequestViewChange(perspView); + viewId = perspView.Id; + } + //no view included + else + { + return null; + } + + Action viewContinuation = () => + { + if (bcfViewpoint.ViewpointComponents == null) + { + return; + } + + var elementsToSelect = new List(); + var elementsToHide = new List(); + var elementsToShow = new List(); + + var visibleElems = new FilteredElementCollector(doc, doc.ActiveView.Id) + .WhereElementIsNotElementType() + .WhereElementIsViewIndependent() + .ToElementIds() + .Where(e => doc.GetElement(e).CanBeHidden(doc.ActiveView)); //might affect performance, but it's necessary + + bool canSetVisibility = (bcfViewpoint.ViewpointComponents.Visibility != null && + bcfViewpoint.ViewpointComponents.Visibility.DefaultVisibility && + bcfViewpoint.ViewpointComponents.Visibility.Exceptions.Any()); + bool canSetSelection = (bcfViewpoint.ViewpointComponents.SelectedComponents != null && bcfViewpoint.ViewpointComponents.SelectedComponents.Any()); + + //loop elements + foreach (var e in visibleElems) + { + var guid = ExportUtils.GetExportId(doc, e).ToIfcGuid(); + + if (canSetVisibility) + { + if (bcfViewpoint.ViewpointComponents.Visibility.DefaultVisibility) + { + if (bcfViewpoint.ViewpointComponents.Visibility.Exceptions.Any(x => x.IfcGuid == guid)) + { + elementsToHide.Add(e); + } + } + else + { + if (bcfViewpoint.ViewpointComponents.Visibility.Exceptions.Any(x => x.IfcGuid == guid)) + { + elementsToShow.Add(e); + } + } + } + + if (canSetSelection) + { + if (bcfViewpoint.ViewpointComponents.SelectedComponents.Any(x => x.IfcGuid == guid)) + { + elementsToSelect.Add(e); + } + } + } + + using (var trans = new Transaction(_uiDocument.Document)) + { + if (trans.Start("Apply BCF visibility and selection") == TransactionStatus.Started) + { + if (elementsToHide.Any()) + doc.ActiveView.HideElementsTemporary(elementsToHide); + //there are no items to hide, therefore hide everything and just show the visible ones + else if (elementsToShow.Any()) + doc.ActiveView.IsolateElementsTemporary(elementsToShow); + + if (elementsToSelect.Any()) + _uiDocument.Selection.SetElementIds(elementsToSelect); + } + trans.Commit(); + } + + _uiDocument.RefreshActiveView(); + }; + + return new ViewContinuationInstructions + { + ViewContinuation = viewContinuation, + ViewId = viewId + }; + } + catch (Exception ex) + { + TaskDialog.Show("Error!", "exception: " + ex); + return null; + } + } + + private IEnumerable GetFamilyViews(Document doc) + { + return from elem in new FilteredElementCollector(doc).OfClass(typeof(ViewFamilyType)) + let type = elem as ViewFamilyType + where type.ViewFamily == ViewFamily.ThreeDimensional + select type; + } + + private IEnumerable Get3DViews(Document doc) + { + return from elem in new FilteredElementCollector(doc).OfClass(typeof(View3D)) + let view = elem as View3D + select view; + } + + public string GetName() + { + return "3D View"; + } + } +} diff --git a/src/IPA.Bcfier.Revit/Services/ViewContinuationInstructions.cs b/src/IPA.Bcfier.Revit/Services/ViewContinuationInstructions.cs new file mode 100644 index 00000000..7693a982 --- /dev/null +++ b/src/IPA.Bcfier.Revit/Services/ViewContinuationInstructions.cs @@ -0,0 +1,14 @@ +using Autodesk.Revit.DB; +using CefSharp; + +namespace IPA.Bcfier.Revit.Services +{ + public class ViewContinuationInstructions + { + public Action? ViewContinuation { get; set; } + + public ElementId? ViewId { get; set; } + + public IJavascriptCallback JavascriptCallback { get; set; } + } +} diff --git a/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.html b/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.html index 9d04c041..5692c056 100644 --- a/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.html +++ b/src/ipa-bcfier-ui/src/app/components/comments-detail/comments-detail.component.html @@ -1,6 +1,10 @@ - +