├── .gitignore ├── .nuke ├── build.cmd ├── build.ps1 └── parameters.json ├── .run ├── Nuke Clean.run.xml ├── Nuke Plan.run.xml └── Nuke.run.xml ├── Build ├── .editorconfig ├── Build.CI.GitHub.cs ├── Build.Clean.cs ├── Build.Compile.cs ├── Build.Configuration.cs ├── Build.Installer.cs ├── Build.Regex.cs ├── Build.cs ├── Build.csproj └── Build.csproj.DotSettings ├── Changelog.md ├── Installer ├── Installer.Generator.cs ├── Installer.cs ├── Installer.csproj └── Resources │ └── Icons │ ├── BackgroundImage.png │ ├── BannerImage.png │ └── ShellIcon.ico ├── OptionsBar.sln ├── OptionsBar ├── Application.cs ├── Commands │ ├── ShowBarCommand.cs │ ├── ShowRunningBarCommand.cs │ └── ShowUtilsBarCommand.cs ├── OptionsBar.addin ├── OptionsBar.csproj ├── Resources │ └── Icons │ │ ├── RibbonIcon16.png │ │ └── RibbonIcon32.png ├── ViewModels │ ├── OptionsViewModel.cs │ └── UtilsViewModel.cs └── Views │ ├── Controls │ ├── RunningLine.cs │ └── RunningLine.xaml │ ├── OptionsView.xaml │ ├── OptionsView.xaml.cs │ ├── RunningLineView.xaml │ ├── RunningLineView.xaml.cs │ ├── Utils │ ├── RibbonController.cs │ └── VisualUtils.cs │ ├── UtilsView.xaml │ └── UtilsView.xaml.cs ├── Readme.md └── global.json /.gitignore: -------------------------------------------------------------------------------- 1 | #IDE folders 2 | *.idea/ 3 | *.vs/ 4 | *.vscode/ 5 | 6 | #Project-specific folders 7 | *obj/ 8 | *bin/ 9 | *temp/ 10 | 11 | #Deprecated Nuget folder 12 | /packages/ 13 | 14 | #Nuke output folder 15 | /output/ 16 | 17 | #Project-specific files 18 | */build.schema.json 19 | 20 | #User-specific files 21 | *.user -------------------------------------------------------------------------------- /.nuke/build.cmd: -------------------------------------------------------------------------------- 1 | powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0Build.ps1" %* 2 | -------------------------------------------------------------------------------- /.nuke/build.ps1: -------------------------------------------------------------------------------- 1 | [CmdletBinding()] 2 | Param( 3 | [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] 4 | [string[]]$BuildArguments 5 | ) 6 | 7 | Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" 8 | 9 | Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } 10 | 11 | ########################################################################### 12 | # CONFIGURATION 13 | ########################################################################### 14 | 15 | $SolutionDirectory = Split-Path $PSScriptRoot -Parent 16 | $BuildProjectFile = "$SolutionDirectory\Build\Build.csproj" 17 | $TempDirectory = "$SolutionDirectory\.nuke\temp" 18 | 19 | $DotNetGlobalFile = "$SolutionDirectory\global.json" 20 | $DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" 21 | $DotNetChannel = "Current" 22 | 23 | $env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 24 | $env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 25 | $env:DOTNET_MULTILEVEL_LOOKUP = 0 26 | 27 | ########################################################################### 28 | # EXECUTION 29 | ########################################################################### 30 | 31 | function ExecSafe([scriptblock] $cmd) { 32 | & $cmd 33 | if ($LASTEXITCODE) { exit $LASTEXITCODE } 34 | } 35 | 36 | # If dotnet CLI is installed globally and it matches requested version, use for execution 37 | if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` 38 | $(dotnet --version) -and $LASTEXITCODE -eq 0) { 39 | $env:DOTNET_EXE = (Get-Command "dotnet").Path 40 | } 41 | else { 42 | # Download install script 43 | $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" 44 | New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null 45 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 46 | (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) 47 | 48 | # If global.json exists, load expected version 49 | if (Test-Path $DotNetGlobalFile) { 50 | $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) 51 | if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { 52 | $DotNetVersion = $DotNetGlobal.sdk.version 53 | } 54 | } 55 | 56 | # Install by channel or version 57 | $DotNetDirectory = "$TempDirectory\dotnet-win" 58 | if (!(Test-Path variable:DotNetVersion)) { 59 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } 60 | } else { 61 | ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } 62 | } 63 | $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" 64 | } 65 | 66 | Write-Output "Microsoft (R) .NET SDK version $(& $env:DOTNET_EXE --version)" 67 | 68 | ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } 69 | ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } -------------------------------------------------------------------------------- /.nuke/parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./build.schema.json", 3 | "Solution": "OptionsBar.sln", 4 | "Verbosity": "Normal" 5 | } -------------------------------------------------------------------------------- /.run/Nuke Clean.run.xml: -------------------------------------------------------------------------------- 1 |  2 | 6 | 25 | -------------------------------------------------------------------------------- /.run/Nuke Plan.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 22 | -------------------------------------------------------------------------------- /.run/Nuke.run.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 22 | -------------------------------------------------------------------------------- /Build/.editorconfig: -------------------------------------------------------------------------------- 1 | # noinspection EditorConfigKeyCorrectness 2 | [*.cs] 3 | dotnet_style_qualification_for_field = false:warning 4 | dotnet_style_qualification_for_property = false:warning 5 | dotnet_style_qualification_for_method = false:warning 6 | dotnet_style_qualification_for_event = false:warning 7 | dotnet_style_require_accessibility_modifiers = never:warning 8 | 9 | csharp_style_expression_bodied_methods = true:silent 10 | csharp_style_expression_bodied_properties = true:warning 11 | csharp_style_expression_bodied_indexers = true:warning 12 | csharp_style_expression_bodied_accessors = true:warning 13 | 14 | resharper_check_namespace_highlighting = none 15 | resharper_class_never_instantiated_global_highlighting = none -------------------------------------------------------------------------------- /Build/Build.CI.GitHub.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.RegularExpressions; 3 | using Nuke.Common.Git; 4 | using Nuke.Common.Tools.GitHub; 5 | using Octokit; 6 | 7 | sealed partial class Build 8 | { 9 | Target PublishGitHub => _ => _ 10 | .TriggeredBy(CreateInstaller) 11 | .Requires(() => GitHubToken) 12 | .Requires(() => GitRepository) 13 | .Requires(() => GitVersion) 14 | .OnlyWhenStatic(() => GitRepository.IsOnMainOrMasterBranch() && IsServerBuild) 15 | .Executes(async () => 16 | { 17 | GitHubTasks.GitHubClient = new GitHubClient(new ProductHeaderValue(Solution.Name)) 18 | { 19 | Credentials = new Credentials(GitHubToken) 20 | }; 21 | 22 | var gitHubName = GitRepository.GetGitHubName(); 23 | var gitHubOwner = GitRepository.GetGitHubOwner(); 24 | var artifacts = Directory.GetFiles(ArtifactsDirectory, "*"); 25 | 26 | await CheckTagsAsync(gitHubOwner, gitHubName, Version); 27 | Log.Information("Tag: {Version}", Version); 28 | 29 | var newRelease = new NewRelease(Version) 30 | { 31 | Name = Version, 32 | Body = CreateChangelog(Version), 33 | Draft = true, 34 | TargetCommitish = GitVersion.Sha 35 | }; 36 | 37 | var draft = await CreatedDraftAsync(gitHubOwner, gitHubName, newRelease); 38 | await UploadArtifactsAsync(draft, artifacts); 39 | await ReleaseDraftAsync(gitHubOwner, gitHubName, draft); 40 | }); 41 | 42 | static async Task CheckTagsAsync(string gitHubOwner, string gitHubName, string version) 43 | { 44 | var gitHubTags = await GitHubTasks.GitHubClient.Repository.GetAllTags(gitHubOwner, gitHubName); 45 | if (gitHubTags.Select(tag => tag.Name).Contains(version)) 46 | throw new ArgumentException($"A Release with the specified tag already exists in the repository: {version}"); 47 | } 48 | 49 | static async Task CreatedDraftAsync(string gitHubOwner, string gitHubName, NewRelease newRelease) => 50 | await GitHubTasks.GitHubClient.Repository.Release.Create(gitHubOwner, gitHubName, newRelease); 51 | 52 | static async Task ReleaseDraftAsync(string gitHubOwner, string gitHubName, Release draft) => 53 | await GitHubTasks.GitHubClient.Repository.Release.Edit(gitHubOwner, gitHubName, draft.Id, new ReleaseUpdate {Draft = false}); 54 | 55 | static async Task UploadArtifactsAsync(Release createdRelease, string[] artifacts) 56 | { 57 | foreach (var file in artifacts) 58 | { 59 | var releaseAssetUpload = new ReleaseAssetUpload 60 | { 61 | ContentType = "application/x-binary", 62 | FileName = Path.GetFileName(file), 63 | RawData = File.OpenRead(file) 64 | }; 65 | 66 | await GitHubTasks.GitHubClient.Repository.Release.UploadAsset(createdRelease, releaseAssetUpload); 67 | Log.Information("Artifact: {Path}", file); 68 | } 69 | } 70 | 71 | string CreateChangelog(string version) 72 | { 73 | if (!File.Exists(ChangeLogPath)) 74 | { 75 | Log.Warning("Unable to locate the changelog file: {Log}", ChangeLogPath); 76 | return string.Empty; 77 | } 78 | 79 | Log.Information("Changelog: {Path}", ChangeLogPath); 80 | 81 | var logBuilder = new StringBuilder(); 82 | var changelogLineRegex = new Regex($@"^.*({version})\S*\s?"); 83 | const string nextRecordSymbol = "# "; 84 | 85 | foreach (var line in File.ReadLines(ChangeLogPath)) 86 | { 87 | if (logBuilder.Length > 0) 88 | { 89 | if (line.StartsWith(nextRecordSymbol)) break; 90 | logBuilder.AppendLine(line); 91 | continue; 92 | } 93 | 94 | if (!changelogLineRegex.Match(line).Success) continue; 95 | var truncatedLine = changelogLineRegex.Replace(line, string.Empty); 96 | logBuilder.AppendLine(truncatedLine); 97 | } 98 | 99 | if (logBuilder.Length == 0) Log.Warning("No version entry exists in the changelog: {Version}", version); 100 | return logBuilder.ToString(); 101 | } 102 | } -------------------------------------------------------------------------------- /Build/Build.Clean.cs: -------------------------------------------------------------------------------- 1 | using Nuke.Common.Tools.DotNet; 2 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 3 | 4 | sealed partial class Build 5 | { 6 | Target Clean => _ => _ 7 | .OnlyWhenStatic(() => IsLocalBuild) 8 | .Executes(() => 9 | { 10 | foreach (var configuration in GlobBuildConfigurations()) 11 | DotNetClean(settings => settings 12 | .SetConfiguration(configuration) 13 | .SetVerbosity(DotNetVerbosity.Minimal)); 14 | 15 | foreach (var project in Solution.AllProjects.Where(project => project != Solution.Build)) 16 | CleanDirectory(project.Directory / "bin"); 17 | 18 | CleanDirectory(ArtifactsDirectory); 19 | }); 20 | 21 | static void CleanDirectory(AbsolutePath path) 22 | { 23 | Log.Information("Cleaning directory: {Directory}", path); 24 | path.CreateOrCleanDirectory(); 25 | } 26 | } -------------------------------------------------------------------------------- /Build/Build.Compile.cs: -------------------------------------------------------------------------------- 1 | using System.IO.Enumeration; 2 | using Nuke.Common.Tools.DotNet; 3 | using static Nuke.Common.Tools.DotNet.DotNetTasks; 4 | 5 | sealed partial class Build 6 | { 7 | Target Compile => _ => _ 8 | .TriggeredBy(Clean) 9 | .Executes(() => 10 | { 11 | foreach (var configuration in GlobBuildConfigurations()) 12 | DotNetBuild(settings => settings 13 | .SetConfiguration(configuration) 14 | .SetVersion(Version) 15 | .SetVerbosity(DotNetVerbosity.Minimal)); 16 | }); 17 | 18 | List GlobBuildConfigurations() 19 | { 20 | var configurations = Solution.Configurations 21 | .Select(pair => pair.Key) 22 | .Select(config => config.Remove(config.LastIndexOf('|'))) 23 | .Where(config => Configurations.Any(wildcard => FileSystemName.MatchesSimpleExpression(wildcard, config))) 24 | .ToList(); 25 | 26 | if (configurations.Count == 0) 27 | throw new Exception($"No solution configurations have been found. Pattern: {string.Join(" | ", Configurations)}"); 28 | 29 | return configurations; 30 | } 31 | } -------------------------------------------------------------------------------- /Build/Build.Configuration.cs: -------------------------------------------------------------------------------- 1 | sealed partial class Build 2 | { 3 | const string Version = "1.0.0"; 4 | readonly AbsolutePath ArtifactsDirectory = RootDirectory / "output"; 5 | readonly AbsolutePath ChangeLogPath = RootDirectory / "Changelog.md"; 6 | 7 | protected override void OnBuildInitialized() 8 | { 9 | Configurations = new[] 10 | { 11 | "Release*", 12 | "Installer*" 13 | }; 14 | 15 | InstallersMap = new() 16 | { 17 | {Solution.Installer, Solution.OptionsBar} 18 | }; 19 | } 20 | } -------------------------------------------------------------------------------- /Build/Build.Installer.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using System.Diagnostics.CodeAnalysis; 3 | using Nuke.Common.Git; 4 | using Nuke.Common.Utilities; 5 | using Serilog.Events; 6 | 7 | sealed partial class Build 8 | { 9 | Target CreateInstaller => _ => _ 10 | .TriggeredBy(Compile) 11 | .OnlyWhenStatic(() => IsLocalBuild || GitRepository.IsOnMainOrMasterBranch()) 12 | .Executes(() => 13 | { 14 | foreach (var (installer, project) in InstallersMap) 15 | { 16 | Log.Information("Project: {Name}", project.Name); 17 | 18 | var exePattern = $"*{installer.Name}.exe"; 19 | var exeFile = Directory.EnumerateFiles(installer.Directory, exePattern, SearchOption.AllDirectories).FirstOrDefault(); 20 | if (exeFile is null) throw new Exception($"No installer file was found for the project: {installer.Name}"); 21 | 22 | var directories = Directory.GetDirectories(project.Directory, "Publish*", SearchOption.AllDirectories); 23 | if (directories.Length == 0) throw new Exception("No files were found to create an installer"); 24 | 25 | var proc = new Process(); 26 | proc.StartInfo.FileName = exeFile; 27 | proc.StartInfo.Arguments = directories.Select(path => path.DoubleQuoteIfNeeded()).JoinSpace(); 28 | proc.StartInfo.RedirectStandardOutput = true; 29 | proc.StartInfo.RedirectStandardError = true; 30 | proc.Start(); 31 | 32 | RedirectStream(proc.StandardOutput, LogEventLevel.Information); 33 | RedirectStream(proc.StandardError, LogEventLevel.Error); 34 | 35 | proc.WaitForExit(); 36 | if (proc.ExitCode != 0) throw new Exception($"The installer creation failed with ExitCode {proc.ExitCode}"); 37 | } 38 | }); 39 | 40 | [SuppressMessage("ReSharper", "TemplateIsNotCompileTimeConstantProblem")] 41 | void RedirectStream(StreamReader reader, LogEventLevel eventLevel) 42 | { 43 | while (!reader.EndOfStream) 44 | { 45 | var value = reader.ReadLine(); 46 | if (value is null) continue; 47 | 48 | var matches = ArgumentsRegex.Matches(value); 49 | if (matches.Count > 0) 50 | { 51 | var parameters = matches 52 | .Select(match => match.Value.Substring(1, match.Value.Length - 2)) 53 | .Cast() 54 | .ToArray(); 55 | 56 | var line = ArgumentsRegex.Replace(value, match => $"{{Parameter{match.Index}}}"); 57 | Log.Write(eventLevel, line, parameters); 58 | } 59 | else 60 | { 61 | Log.Debug(value); 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Build/Build.Regex.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | 3 | sealed partial class Build 4 | { 5 | readonly Regex ArgumentsRegex = ArgumentsRegexGenerator(); 6 | 7 | [GeneratedRegex("'(.+?)'", RegexOptions.Compiled)] 8 | private static partial Regex ArgumentsRegexGenerator(); 9 | } -------------------------------------------------------------------------------- /Build/Build.cs: -------------------------------------------------------------------------------- 1 | using Nuke.Common.Git; 2 | using Nuke.Common.ProjectModel; 3 | using Nuke.Common.Tools.GitVersion; 4 | 5 | sealed partial class Build : NukeBuild 6 | { 7 | string[] Configurations; 8 | Dictionary InstallersMap; 9 | 10 | [Parameter] string GitHubToken; 11 | [GitRepository] readonly GitRepository GitRepository; 12 | [GitVersion(NoFetch = true)] readonly GitVersion GitVersion; 13 | [Solution(GenerateProjects = true)] Solution Solution; 14 | 15 | public static int Main() => Execute(x => x.Clean); 16 | } -------------------------------------------------------------------------------- /Build/Build.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | Exe 4 | CS0649;CS0169 5 | latest 6 | true 7 | net7.0 8 | .. 9 | .. 10 | 1 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Build/Build.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | DO_NOT_SHOW 3 | DO_NOT_SHOW 4 | DO_NOT_SHOW 5 | DO_NOT_SHOW 6 | DO_NOT_SHOW 7 | Implicit 8 | Implicit 9 | ExpressionBody 10 | 0 11 | NEXT_LINE 12 | True 13 | False 14 | 120 15 | IF_OWNER_IS_SINGLE_LINE 16 | WRAP_IF_LONG 17 | False 18 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 19 | <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> 20 | True 21 | True 22 | True 23 | True 24 | True 25 | True 26 | True 27 | True 28 | True 29 | -------------------------------------------------------------------------------- /Changelog.md: -------------------------------------------------------------------------------- 1 | # **1.0.0** 2 | 3 | Initial release. Enjoy! -------------------------------------------------------------------------------- /Installer/Installer.Generator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Text.RegularExpressions; 6 | using WixSharp; 7 | 8 | namespace Installer; 9 | 10 | public static class Generator 11 | { 12 | public static WixEntity[] GenerateWixEntities(IEnumerable args) 13 | { 14 | var versionRegex = new Regex(@"\d+"); 15 | var versionStorages = new Dictionary>(); 16 | 17 | foreach (var directory in args) 18 | { 19 | var directoryInfo = new DirectoryInfo(directory); 20 | var fileVersion = versionRegex.Match(directoryInfo.Name).Value; 21 | var feature = new Feature 22 | { 23 | Name = $"Revit {fileVersion}", 24 | Description = $"Install add-in for Revit {fileVersion}", 25 | ConfigurableDir = $"INSTALL{fileVersion}" 26 | }; 27 | 28 | var files = new Files(feature, $@"{directory}\*.*"); 29 | if (versionStorages.TryGetValue(fileVersion, out var storage)) 30 | storage.Add(files); 31 | else 32 | versionStorages.Add(fileVersion, new List {files}); 33 | 34 | var assemblies = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); 35 | Console.WriteLine($"Installer files for version '{fileVersion}':"); 36 | foreach (var assembly in assemblies) Console.WriteLine($"'{assembly}'"); 37 | } 38 | 39 | return versionStorages 40 | .Select(storage => new Dir(new Id($"INSTALL{storage.Key}"), storage.Key, storage.Value.ToArray())) 41 | .Cast() 42 | .ToArray(); 43 | } 44 | } -------------------------------------------------------------------------------- /Installer/Installer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Installer; 3 | using WixSharp; 4 | using WixSharp.CommonTasks; 5 | using WixSharp.Controls; 6 | using Assembly = System.Reflection.Assembly; 7 | 8 | const string outputName = "OptionsBar"; 9 | const string projectName = "OptionsBar"; 10 | 11 | var project = new Project 12 | { 13 | OutDir = "output", 14 | Name = projectName, 15 | Platform = Platform.x64, 16 | UI = WUI.WixUI_FeatureTree, 17 | MajorUpgrade = MajorUpgrade.Default, 18 | GUID = new Guid("E9DCD184-9BCD-4F2C-A8FA-9AD26120DCFF"), 19 | BannerImage = @"Installer\Resources\Icons\BannerImage.png", 20 | BackgroundImage = @"Installer\Resources\Icons\BackgroundImage.png", 21 | Version = Assembly.GetExecutingAssembly().GetName().Version.ClearRevision(), 22 | ControlPanelInfo = 23 | { 24 | Manufacturer = Environment.UserName, 25 | ProductIcon = @"Installer\Resources\Icons\ShellIcon.ico" 26 | } 27 | }; 28 | 29 | var wixEntities = Generator.GenerateWixEntities(args); 30 | project.RemoveDialogsBetween(NativeDialogs.WelcomeDlg, NativeDialogs.CustomizeDlg); 31 | 32 | BuildSingleUserMsi(); 33 | BuildMultiUserUserMsi(); 34 | 35 | void BuildSingleUserMsi() 36 | { 37 | project.InstallScope = InstallScope.perUser; 38 | project.OutFileName = $"{outputName}-{project.Version}-SingleUser"; 39 | project.Dirs = new Dir[] 40 | { 41 | new InstallDir(@"%AppDataFolder%\Autodesk\Revit\Addins\", wixEntities) 42 | }; 43 | project.BuildMsi(); 44 | } 45 | 46 | void BuildMultiUserUserMsi() 47 | { 48 | project.InstallScope = InstallScope.perMachine; 49 | project.OutFileName = $"{outputName}-{project.Version}-MultiUser"; 50 | project.Dirs = new Dir[] 51 | { 52 | new InstallDir(@"%CommonAppDataFolder%\Autodesk\Revit\Addins\", wixEntities) 53 | }; 54 | project.BuildMsi(); 55 | } -------------------------------------------------------------------------------- /Installer/Installer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | Exe 4 | latest 5 | x64 6 | net48 7 | false 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Installer/Resources/Icons/BackgroundImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomatiq/OptionsBar/d1681691ef66763a3b36fca297794e1b8d633a89/Installer/Resources/Icons/BackgroundImage.png -------------------------------------------------------------------------------- /Installer/Resources/Icons/BannerImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomatiq/OptionsBar/d1681691ef66763a3b36fca297794e1b8d633a89/Installer/Resources/Icons/BannerImage.png -------------------------------------------------------------------------------- /Installer/Resources/Icons/ShellIcon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomatiq/OptionsBar/d1681691ef66763a3b36fca297794e1b8d633a89/Installer/Resources/Icons/ShellIcon.ico -------------------------------------------------------------------------------- /OptionsBar.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31919.166 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{90FCE70F-7835-401B-B8DC-23DE1E1045F9}") = "Build", "Build\Build.csproj", "{5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}" 7 | EndProject 8 | Project("{90FCE70F-7835-401B-B8DC-23DE1E1045F9}") = "Installer", "Installer\Installer.csproj", "{C3A8AF74-957B-441B-A69C-A8422C845980}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D44011CC-8AB6-4223-95BF-74629946F83C}" 11 | ProjectSection(SolutionItems) = preProject 12 | .gitignore = .gitignore 13 | Changelog.md = Changelog.md 14 | Readme.md = Readme.md 15 | Build\Build.Configuration.cs = Build\Build.Configuration.cs 16 | EndProjectSection 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OptionsBar", "OptionsBar\OptionsBar.csproj", "{11A906EC-ECD6-4E6F-A489-0E03B108DE05}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug R20|Any CPU = Debug R20|Any CPU 23 | Debug R21|Any CPU = Debug R21|Any CPU 24 | Debug R22|Any CPU = Debug R22|Any CPU 25 | Debug R23|Any CPU = Debug R23|Any CPU 26 | Debug R24|Any CPU = Debug R24|Any CPU 27 | Release R20|Any CPU = Release R20|Any CPU 28 | Release R21|Any CPU = Release R21|Any CPU 29 | Release R22|Any CPU = Release R22|Any CPU 30 | Release R23|Any CPU = Release R23|Any CPU 31 | Release R24|Any CPU = Release R24|Any CPU 32 | Installer|Any CPU = Installer|Any CPU 33 | EndGlobalSection 34 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 35 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Debug R20|Any CPU.ActiveCfg = Debug|Any CPU 36 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU 37 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU 38 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU 39 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU 40 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Release R20|Any CPU.ActiveCfg = Release|Any CPU 41 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Release R21|Any CPU.ActiveCfg = Release|Any CPU 42 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Release R22|Any CPU.ActiveCfg = Release|Any CPU 43 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Release R23|Any CPU.ActiveCfg = Release|Any CPU 44 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Release R24|Any CPU.ActiveCfg = Release|Any CPU 45 | {5E5B0C2C-FDCD-46D9-A99B-3C03722C77DB}.Installer|Any CPU.ActiveCfg = Release|Any CPU 46 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Debug R20|Any CPU.ActiveCfg = Debug|Any CPU 47 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Debug R21|Any CPU.ActiveCfg = Debug|Any CPU 48 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU 49 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU 50 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Debug R24|Any CPU.ActiveCfg = Debug|Any CPU 51 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Release R20|Any CPU.ActiveCfg = Release|Any CPU 52 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Release R21|Any CPU.ActiveCfg = Release|Any CPU 53 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Release R22|Any CPU.ActiveCfg = Release|Any CPU 54 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Release R23|Any CPU.ActiveCfg = Release|Any CPU 55 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Release R24|Any CPU.ActiveCfg = Release|Any CPU 56 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Installer|Any CPU.ActiveCfg = Release|Any CPU 57 | {C3A8AF74-957B-441B-A69C-A8422C845980}.Installer|Any CPU.Build.0 = Release|Any CPU 58 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R20|Any CPU.ActiveCfg = Debug R20|Any CPU 59 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R20|Any CPU.Build.0 = Debug R20|Any CPU 60 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R21|Any CPU.ActiveCfg = Debug R21|Any CPU 61 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R21|Any CPU.Build.0 = Debug R21|Any CPU 62 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R22|Any CPU.ActiveCfg = Debug R22|Any CPU 63 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R22|Any CPU.Build.0 = Debug R22|Any CPU 64 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R23|Any CPU.ActiveCfg = Debug R23|Any CPU 65 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R23|Any CPU.Build.0 = Debug R23|Any CPU 66 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R24|Any CPU.ActiveCfg = Debug R24|Any CPU 67 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Debug R24|Any CPU.Build.0 = Debug R24|Any CPU 68 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R20|Any CPU.ActiveCfg = Release R20|Any CPU 69 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R20|Any CPU.Build.0 = Release R20|Any CPU 70 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R21|Any CPU.ActiveCfg = Release R21|Any CPU 71 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R21|Any CPU.Build.0 = Release R21|Any CPU 72 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R22|Any CPU.ActiveCfg = Release R22|Any CPU 73 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R22|Any CPU.Build.0 = Release R22|Any CPU 74 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R23|Any CPU.ActiveCfg = Release R23|Any CPU 75 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R23|Any CPU.Build.0 = Release R23|Any CPU 76 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R24|Any CPU.ActiveCfg = Release R24|Any CPU 77 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Release R24|Any CPU.Build.0 = Release R24|Any CPU 78 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Installer|Any CPU.ActiveCfg = Debug R20|Any CPU 79 | {11A906EC-ECD6-4E6F-A489-0E03B108DE05}.Installer|Any CPU.Build.0 = Debug R20|Any CPU 80 | EndGlobalSection 81 | EndGlobal 82 | -------------------------------------------------------------------------------- /OptionsBar/Application.cs: -------------------------------------------------------------------------------- 1 | using Nice3point.Revit.Toolkit.External; 2 | using OptionsBar.Commands; 3 | 4 | namespace OptionsBar; 5 | 6 | [UsedImplicitly] 7 | public class Application : ExternalApplication 8 | { 9 | public override void OnStartup() 10 | { 11 | CreateRibbon(); 12 | } 13 | 14 | private void CreateRibbon() 15 | { 16 | var panel = Application.CreatePanel("Commands", "OptionsBar"); 17 | 18 | var showSelectionButton = panel.AddPushButton("Select\nWall"); 19 | showSelectionButton.SetImage("/OptionsBar;component/Resources/Icons/RibbonIcon16.png"); 20 | showSelectionButton.SetLargeImage("/OptionsBar;component/Resources/Icons/RibbonIcon32.png"); 21 | 22 | var showUtilsButton = panel.AddPushButton("Show\nUtils"); 23 | showUtilsButton.SetImage("/OptionsBar;component/Resources/Icons/RibbonIcon16.png"); 24 | showUtilsButton.SetLargeImage("/OptionsBar;component/Resources/Icons/RibbonIcon32.png"); 25 | 26 | var showRunningButton = panel.AddPushButton("Show\nrunning line"); 27 | showRunningButton.SetImage("/OptionsBar;component/Resources/Icons/RibbonIcon16.png"); 28 | showRunningButton.SetLargeImage("/OptionsBar;component/Resources/Icons/RibbonIcon32.png"); 29 | } 30 | } -------------------------------------------------------------------------------- /OptionsBar/Commands/ShowBarCommand.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.Attributes; 2 | using Autodesk.Revit.DB; 3 | using Autodesk.Revit.UI.Selection; 4 | using Nice3point.Revit.Toolkit.External; 5 | using Nice3point.Revit.Toolkit.Options; 6 | using OptionsBar.ViewModels; 7 | using OptionsBar.Views; 8 | using OptionsBar.Views.Utils; 9 | using OperationCanceledException = Autodesk.Revit.Exceptions.OperationCanceledException; 10 | 11 | namespace OptionsBar.Commands; 12 | 13 | [UsedImplicitly] 14 | [Transaction(TransactionMode.Manual)] 15 | public class ShowBarCommand : ExternalCommand 16 | { 17 | public override void Execute() 18 | { 19 | try 20 | { 21 | var options = SetupOptionsBar(); 22 | var wall = PickWall(); 23 | ModifyWall(wall, options); 24 | } 25 | catch (OperationCanceledException) 26 | { 27 | // ignored 28 | } 29 | finally 30 | { 31 | RibbonController.HideOptionsBar(); 32 | } 33 | } 34 | 35 | private OptionsViewModel SetupOptionsBar() 36 | { 37 | var options = new OptionsViewModel 38 | { 39 | Offset = 0, 40 | Constraints = Document.EnumerateInstances(BuiltInCategory.OST_Levels).Select(level => level.Name).ToArray() 41 | }; 42 | 43 | var view = new OptionsView(options); 44 | RibbonController.ShowOptionsBar(view); 45 | return options; 46 | } 47 | 48 | private Wall PickWall() 49 | { 50 | var selectionConfiguration = new SelectionConfiguration().Allow.Element(selection => selection is Wall); 51 | var reference = UiDocument.Selection.PickObject(ObjectType.Element, selectionConfiguration.Filter, "Select wall"); 52 | return reference.ElementId.ToElement(Document); 53 | } 54 | 55 | private void ModifyWall(Wall wall, OptionsViewModel options) 56 | { 57 | // Example of using user-defined values from the Options Bar 58 | using var transaction = new Transaction(Document); 59 | transaction.Start("Set offset"); 60 | wall.GetParameter(BuiltInParameter.WALL_TOP_OFFSET)!.Set(options.Offset); 61 | transaction.Commit(); 62 | } 63 | } -------------------------------------------------------------------------------- /OptionsBar/Commands/ShowRunningBarCommand.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.Attributes; 2 | using Nice3point.Revit.Toolkit.External; 3 | using OptionsBar.Views; 4 | using OptionsBar.Views.Utils; 5 | 6 | namespace OptionsBar.Commands; 7 | 8 | [UsedImplicitly] 9 | [Transaction(TransactionMode.Manual)] 10 | public class ShowRunningBarCommand : ExternalCommand 11 | { 12 | public override void Execute() 13 | { 14 | RibbonController.ShowOptionsBar(new RunningLineView()); 15 | RibbonController.HideOptionsBar(TimeSpan.FromSeconds(30)); 16 | } 17 | } -------------------------------------------------------------------------------- /OptionsBar/Commands/ShowUtilsBarCommand.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.Attributes; 2 | using Nice3point.Revit.Toolkit.External; 3 | using OptionsBar.ViewModels; 4 | using OptionsBar.Views; 5 | using OptionsBar.Views.Utils; 6 | 7 | namespace OptionsBar.Commands; 8 | 9 | [UsedImplicitly] 10 | [Transaction(TransactionMode.Manual)] 11 | public class ShowUtilsBarCommand : ExternalCommand 12 | { 13 | public override void Execute() 14 | { 15 | var viewModel = new UtilsViewModel(); 16 | var view = new UtilsView(viewModel); 17 | RibbonController.ShowOptionsBar(view); 18 | } 19 | } -------------------------------------------------------------------------------- /OptionsBar/OptionsBar.addin: -------------------------------------------------------------------------------- 1 |  2 | 3 | OptionsBar 4 | OptionsBar/OptionsBar.dll 5 | A11B0727-5CC9-4E06-9CCA-0C1D51576001 6 | OptionsBar.Application 7 | Atomatiq 8 | Manufacturing Software Development 9 | 10 | -------------------------------------------------------------------------------- /OptionsBar/OptionsBar.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | true 4 | latest 5 | x64 6 | true 7 | net48 8 | Debug R20;Debug R21;Debug R22;Debug R23;Debug R24 9 | $(Configurations);Release R20;Release R21;Release R22;Release R23;Release R24 10 | 11 | 12 | full 13 | true 14 | Local 15 | $(DefineConstants);DEBUG 16 | 17 | 18 | true 19 | none 20 | Publish 21 | $(DefineConstants);RELEASE 22 | 23 | 24 | 2020 25 | $(DefineConstants);R20 26 | $(DefineConstants);R20_OR_GREATER 27 | 28 | 29 | 2021 30 | $(DefineConstants);R21 31 | $(DefineConstants);R20_OR_GREATER;R21_OR_GREATER 32 | 33 | 34 | 2022 35 | $(DefineConstants);R22 36 | $(DefineConstants);R20_OR_GREATER;R21_OR_GREATER;R22_OR_GREATER 37 | 38 | 39 | 2023 40 | $(DefineConstants);R23 41 | $(DefineConstants);R20_OR_GREATER;R21_OR_GREATER;R22_OR_GREATER;R23_OR_GREATER 42 | 43 | 44 | 2024 45 | $(DefineConstants);R24 46 | $(DefineConstants);R20_OR_GREATER;R21_OR_GREATER;R22_OR_GREATER;R23_OR_GREATER;R24_OR_GREATER 47 | 48 | 49 | $(RevitVersion) 50 | true 51 | false 52 | 53 | 54 | Program 55 | C:\Program Files\Autodesk\Revit $(RevitVersion)\Revit.exe 56 | /language ENG 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | MSBuild:Compile 83 | Wpf 84 | Designer 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | bin\$(SharingType) $(RevitVersion) $(Configuration)\ 96 | $(RootDir)$(AssemblyName)\ 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /OptionsBar/Resources/Icons/RibbonIcon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomatiq/OptionsBar/d1681691ef66763a3b36fca297794e1b8d633a89/OptionsBar/Resources/Icons/RibbonIcon16.png -------------------------------------------------------------------------------- /OptionsBar/Resources/Icons/RibbonIcon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/atomatiq/OptionsBar/d1681691ef66763a3b36fca297794e1b8d633a89/OptionsBar/Resources/Icons/RibbonIcon32.png -------------------------------------------------------------------------------- /OptionsBar/ViewModels/OptionsViewModel.cs: -------------------------------------------------------------------------------- 1 | using CommunityToolkit.Mvvm.ComponentModel; 2 | 3 | namespace OptionsBar.ViewModels; 4 | 5 | public partial class OptionsViewModel : ObservableObject 6 | { 7 | [ObservableProperty] private double _offset; 8 | [ObservableProperty] private string[] _constraints; 9 | } -------------------------------------------------------------------------------- /OptionsBar/ViewModels/UtilsViewModel.cs: -------------------------------------------------------------------------------- 1 | using Autodesk.Revit.UI; 2 | using CommunityToolkit.Mvvm.ComponentModel; 3 | using CommunityToolkit.Mvvm.Input; 4 | using OptionsBar.Views.Utils; 5 | 6 | namespace OptionsBar.ViewModels; 7 | 8 | public partial class UtilsViewModel : ObservableObject 9 | { 10 | [ObservableProperty] private string _message; 11 | 12 | [RelayCommand] 13 | private void ShowDialog() 14 | { 15 | TaskDialog.Show("Message", Message); 16 | RibbonController.HideOptionsBar(); 17 | } 18 | } -------------------------------------------------------------------------------- /OptionsBar/Views/Controls/RunningLine.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | using System.Windows.Controls; 3 | using System.Windows.Media.Animation; 4 | 5 | namespace OptionsBar.Views.Controls; 6 | 7 | public class RunningLine : ContentControl 8 | { 9 | public static readonly DependencyProperty LineTemplateProperty = DependencyProperty.Register( 10 | nameof(LineTemplate), typeof(DataTemplate), typeof(RunningLine), new PropertyMetadata(default(DataTemplate))); 11 | 12 | public static readonly DependencyProperty AnimationSpeedProperty = DependencyProperty.Register( 13 | nameof(AnimationSpeed), typeof(double), typeof(RunningLine), new PropertyMetadata(0.2d)); 14 | 15 | public RunningLine() 16 | { 17 | Loaded += OnLoaded; 18 | } 19 | 20 | public DataTemplate LineTemplate 21 | { 22 | get => (DataTemplate) GetValue(LineTemplateProperty); 23 | set => SetValue(LineTemplateProperty, value); 24 | } 25 | 26 | public double AnimationSpeed 27 | { 28 | get => (double) GetValue(AnimationSpeedProperty); 29 | set => SetValue(AnimationSpeedProperty, value); 30 | } 31 | 32 | private void OnLoaded(object sender, RoutedEventArgs e) 33 | { 34 | var rootPanel = (ItemsControl) Template.FindName("RootPanel", this); 35 | var rootPanelNext = (ItemsControl) Template.FindName("RootPanelNext", this); 36 | 37 | EvaluatePlaceholders(rootPanel); 38 | EvaluatePlaceholders(rootPanelNext); 39 | 40 | RunAnimation(rootPanel, rootPanelNext); 41 | } 42 | 43 | private void EvaluatePlaceholders(ItemsControl panel) 44 | { 45 | panel.Items.Add(null); 46 | panel.UpdateLayout(); 47 | 48 | var placeholdersCount = (int) (ActualWidth / panel.ActualWidth); 49 | var placeholders = Enumerable.Repeat((string) null, placeholdersCount); 50 | foreach (var item in placeholders) panel.Items.Add(item); 51 | } 52 | 53 | private async void RunAnimation(ItemsControl panel, ItemsControl panelNext) 54 | { 55 | panel.UpdateLayout(); 56 | panelNext.UpdateLayout(); 57 | 58 | var animation = new DoubleAnimation 59 | { 60 | From = 0, 61 | To = -panel.ActualWidth, 62 | RepeatBehavior = RepeatBehavior.Forever 63 | }; 64 | 65 | var animationNext = new DoubleAnimation 66 | { 67 | From = ActualWidth, 68 | To = ActualWidth - panel.ActualWidth, 69 | RepeatBehavior = RepeatBehavior.Forever 70 | }; 71 | 72 | animation.Duration = TimeSpan.FromMilliseconds((animation.From.Value - animation.To.Value) / AnimationSpeed); 73 | animationNext.Duration = TimeSpan.FromMilliseconds((animationNext.From.Value - animationNext.To.Value) / AnimationSpeed); 74 | animationNext.BeginTime = TimeSpan.FromMilliseconds(-animationNext.To.Value / AnimationSpeed); 75 | 76 | panel.BeginAnimation(Canvas.LeftProperty, animation); 77 | panelNext.BeginAnimation(Canvas.LeftProperty, animationNext); 78 | 79 | await Task.Delay(animationNext.BeginTime.Value); 80 | panelNext.Visibility = Visibility.Visible; 81 | } 82 | } -------------------------------------------------------------------------------- /OptionsBar/Views/Controls/RunningLine.xaml: -------------------------------------------------------------------------------- 1 |  5 | 6 | 53 | 54 |