diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 0000000..dd84ea7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 0000000..bbcbbe7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..820aa67 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,30 @@ +### Purpose + +(FILL ME IN) This section describes why this PR is here. Usually it would include a reference +to the tracking task that it is part or all of the solution for. + +### Description + +(FILL ME IN) Brief description of the fix / enhancement. **Mandatory section** + +### Declarations + +Check these if you believe they are true + +- [ ] This PR fix bug +- [ ] This PR for new feature +- [ ] The codebase is in a better state after this PR +- [ ] The level of testing this PR includes is appropriate +- [ ] User facing strings, if any, are extracted into `*.resx` files +- [ ] Snapshot of UI changes, if any. +- [ ] This PR modifies some build requirements and the readme is updated + +### Reviewers + +(FILL ME IN) Reviewer 1 (If possible, assign the Reviewer for the PR) + +(FILL ME IN, optional) Any additional notes to reviewers or testers. + +### FYIs + +(FILL ME IN, Optional) Names of anyone else you wish to be notified of diff --git a/.github/workflows/Workflow.yml b/.github/workflows/Workflow.yml new file mode 100644 index 0000000..d95c820 --- /dev/null +++ b/.github/workflows/Workflow.yml @@ -0,0 +1,19 @@ +name: Workflow + +on: + push: + branches: + - '**' + pull_request: + branches: + - '!master' + +jobs: + windows: + name: windows-2022 + runs-on: windows-2022 + steps: + - name: Checkout + uses: actions/checkout@v1 + - name: Run Nuke Build + run: ./.nuke/build.cmd --GitHubToken ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index afef6a5..2fa0e9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,61 @@ +#Ignore thumbnails created by Windows +Thumbs.db + +#Ignore files built by Visual Studio +*.obj +*.exe +*.pdb +*.user +*.aps +*.pch +*.vspscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.cache +*.ilk +*.log +*.iref +*.db +*.ide +*.opendb +*.lock +*.ide-shm +*.ide-wal +*.dll +*.suo +[Bb]in +[Dd]ebug*/ +*.lib +*.sbr +obj/ +[Rr]elease*/ +_ReSharper*/ +[Tt]est[Rr]esult* +.vs/ + +#Rider +.idea + +#Installer +output + +#Nuget packages folder +packages/ +*.html +*.htm + # Project specific files and folders to ignore PythonConsoleControl/obj/ RevitPythonShell/obj/ RpsRuntime/obj/ + # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] @@ -15,7 +67,6 @@ __pycache__/ .Python env/ bin/ -build/ develop-eggs/ dist/ eggs/ @@ -60,12 +111,5 @@ docs/_build/ *.swp -# project specific stuff -.DS_Store -RevitPythonShell/Examples/Output -RevitPythonShell/Examples/Output_HelloWorld -*.suo - - # Visual Studio 2015 cache/options directory -.vs/ \ No newline at end of file +.vs/ diff --git a/.idea/.idea.RevitPythonShell/.idea/.gitignore b/.idea/.idea.RevitPythonShell/.idea/.gitignore new file mode 100644 index 0000000..1c7f2c6 --- /dev/null +++ b/.idea/.idea.RevitPythonShell/.idea/.gitignore @@ -0,0 +1,13 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/modules.xml +/.idea.RevitPythonShell.iml +/contentModel.xml +/projectSettingsUpdater.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.RevitPythonShell/.idea/.name b/.idea/.idea.RevitPythonShell/.idea/.name new file mode 100644 index 0000000..8229d03 --- /dev/null +++ b/.idea/.idea.RevitPythonShell/.idea/.name @@ -0,0 +1 @@ +RevitPythonShell \ No newline at end of file diff --git a/.idea/.idea.RevitPythonShell/.idea/encodings.xml b/.idea/.idea.RevitPythonShell/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.RevitPythonShell/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.RevitPythonShell/.idea/indexLayout.xml b/.idea/.idea.RevitPythonShell/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.RevitPythonShell/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.RevitPythonShell/.idea/vcs.xml b/.idea/.idea.RevitPythonShell/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/.idea.RevitPythonShell/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuke/build.cmd b/.nuke/build.cmd new file mode 100644 index 0000000..0dbf838 --- /dev/null +++ b/.nuke/build.cmd @@ -0,0 +1 @@ +powershell -ExecutionPolicy ByPass -NoProfile -File "%~dp0Build.ps1" %* \ No newline at end of file diff --git a/.nuke/build.ps1 b/.nuke/build.ps1 new file mode 100644 index 0000000..927e2a3 --- /dev/null +++ b/.nuke/build.ps1 @@ -0,0 +1,69 @@ +[CmdletBinding()] +Param( + [Parameter(Position=0,Mandatory=$false,ValueFromRemainingArguments=$true)] + [string[]]$BuildArguments +) + +Write-Output "PowerShell $($PSVersionTable.PSEdition) version $($PSVersionTable.PSVersion)" + +Set-StrictMode -Version 2.0; $ErrorActionPreference = "Stop"; $ConfirmPreference = "None"; trap { Write-Error $_ -ErrorAction Continue; exit 1 } + +########################################################################### +# CONFIGURATION +########################################################################### + +$SolutionDirectory = Split-Path $PSScriptRoot -Parent +$BuildProjectFile = "$SolutionDirectory\Build\Build.csproj" +$TempDirectory = "$SolutionDirectory\.nuke\temp" + +$DotNetGlobalFile = "$SolutionDirectory\global.json" +$DotNetInstallUrl = "https://dot.net/v1/dotnet-install.ps1" +$DotNetChannel = "Current" + +$env:DOTNET_SKIP_FIRST_TIME_EXPERIENCE = 1 +$env:DOTNET_CLI_TELEMETRY_OPTOUT = 1 +$env:DOTNET_MULTILEVEL_LOOKUP = 0 + +########################################################################### +# EXECUTION +########################################################################### + +function ExecSafe([scriptblock] $cmd) { + & $cmd + if ($LASTEXITCODE) { exit $LASTEXITCODE } +} + +# If dotnet CLI is installed globally and it matches requested version, use for execution +if ($null -ne (Get-Command "dotnet" -ErrorAction SilentlyContinue) -and ` + $(dotnet --version) -and $LASTEXITCODE -eq 0) { + $env:DOTNET_EXE = (Get-Command "dotnet").Path +} +else { + # Download install script + $DotNetInstallFile = "$TempDirectory\dotnet-install.ps1" + New-Item -ItemType Directory -Path $TempDirectory -Force | Out-Null + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + (New-Object System.Net.WebClient).DownloadFile($DotNetInstallUrl, $DotNetInstallFile) + + # If global.json exists, load expected version + if (Test-Path $DotNetGlobalFile) { + $DotNetGlobal = $(Get-Content $DotNetGlobalFile | Out-String | ConvertFrom-Json) + if ($DotNetGlobal.PSObject.Properties["sdk"] -and $DotNetGlobal.sdk.PSObject.Properties["version"]) { + $DotNetVersion = $DotNetGlobal.sdk.version + } + } + + # Install by channel or version + $DotNetDirectory = "$TempDirectory\dotnet-win" + if (!(Test-Path variable:DotNetVersion)) { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Channel $DotNetChannel -NoPath } + } else { + ExecSafe { & powershell $DotNetInstallFile -InstallDir $DotNetDirectory -Version $DotNetVersion -NoPath } + } + $env:DOTNET_EXE = "$DotNetDirectory\dotnet.exe" +} + +Write-Output "Microsoft (R) .NET Core SDK version $(& $env:DOTNET_EXE --version)" + +ExecSafe { & $env:DOTNET_EXE build $BuildProjectFile /nodeReuse:false /p:UseSharedCompilation=false -nologo -clp:NoSummary --verbosity quiet } +ExecSafe { & $env:DOTNET_EXE run --project $BuildProjectFile --no-build -- $BuildArguments } \ No newline at end of file diff --git a/.nuke/build.schema.json b/.nuke/build.schema.json new file mode 100644 index 0000000..d471241 --- /dev/null +++ b/.nuke/build.schema.json @@ -0,0 +1,106 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "Build Schema", + "$ref": "#/definitions/build", + "definitions": { + "build": { + "type": "object", + "properties": { + "Continue": { + "type": "boolean", + "description": "Indicates to continue a previously failed build attempt" + }, + "GitHubToken": { + "type": "string" + }, + "Help": { + "type": "boolean", + "description": "Shows the help text for this build assembly" + }, + "Host": { + "type": "string", + "description": "Host for execution. Default is 'automatic'", + "enum": [ + "AppVeyor", + "AzurePipelines", + "Bamboo", + "Bitrise", + "GitHubActions", + "GitLab", + "Jenkins", + "Rider", + "SpaceAutomation", + "TeamCity", + "Terminal", + "TravisCI", + "VisualStudio", + "VSCode" + ] + }, + "NoLogo": { + "type": "boolean", + "description": "Disables displaying the NUKE logo" + }, + "Partition": { + "type": "string", + "description": "Partition to use on CI" + }, + "Plan": { + "type": "boolean", + "description": "Shows the execution plan (HTML)" + }, + "Profile": { + "type": "array", + "description": "Defines the profiles to load", + "items": { + "type": "string" + } + }, + "Root": { + "type": "string", + "description": "Root directory during build execution" + }, + "Skip": { + "type": "array", + "description": "List of targets to be skipped. Empty list skips all dependencies", + "items": { + "type": "string", + "enum": [ + "Cleaning", + "Compile", + "CreateInstaller", + "PublishGitHubRelease" + ] + } + }, + "Solution": { + "type": "string", + "description": "Path to a solution file that is automatically loaded" + }, + "Target": { + "type": "array", + "description": "List of targets to be invoked. Default is '{default_target}'", + "items": { + "type": "string", + "enum": [ + "Cleaning", + "Compile", + "CreateInstaller", + "PublishGitHubRelease" + ] + } + }, + "Verbosity": { + "type": "string", + "description": "Logging verbosity during build execution. Default is 'Normal'", + "enum": [ + "Minimal", + "Normal", + "Quiet", + "Verbose" + ] + } + } + } + } +} \ No newline at end of file diff --git a/.nuke/parameters.json b/.nuke/parameters.json new file mode 100644 index 0000000..9af8c82 --- /dev/null +++ b/.nuke/parameters.json @@ -0,0 +1,5 @@ +{ + "$schema": "./build.schema.json", + "Solution": "RevitPythonShell.sln", + "Verbosity": "Normal" +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6d304ef --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,10 @@ +# Changelog +- 2022-06-27 **1.0.1** + - Add Process CI/CD Automatic. + - Fix Problem show owner window. + - Upgrade process use SDK Style .NET6. + - Improve Codebase build button. + - Fix minimize form window. + - Support installation in one file msi from Revit 2018 to Revit 2023. + - Version numbers changed because we use one file msi installer for all versions.(e.g 2023.0.0 to 1.0.0) + diff --git a/Installer/Installer.cs b/Installer/Installer.cs new file mode 100644 index 0000000..035a794 --- /dev/null +++ b/Installer/Installer.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using WixSharp; +using WixSharp.CommonTasks; +using WixSharp.Controls; + +const string installationDir = @"%AppDataFolder%\Autodesk\Revit\Addins\"; +const string projectName = "RevitPythonShell"; +const string outputName = "RevitPythonShell"; +const string outputDir = "output"; +const string version = "1.0.1"; + +var fileName = new StringBuilder().Append(outputName).Append("-").Append(version); +var project = new Project +{ + Name = projectName, + OutDir = outputDir, + Platform = Platform.x64, + Description = "The RevitPythonShell adds an IronPython interpreter to Autodesk Revit and Vasari.", + UI = WUI.WixUI_InstallDir, + Version = new Version(version), + OutFileName = fileName.ToString(), + InstallScope = InstallScope.perUser, + MajorUpgrade = MajorUpgrade.Default, + GUID = new Guid("8A43E94C-B89C-4135-8D7C-B8E51DCE70D5"), + BackgroundImage = @"Installer\Resources\Icons\BackgroundImage.png", + BannerImage = @"Installer\Resources\Icons\BannerImage.png", + ControlPanelInfo = + { + Manufacturer = "architecture-building-systems", + HelpLink = "https://github.com/architecture-building-systems/revitpythonshell", + Comments = "The RevitPythonShell adds an IronPython interpreter to Autodesk Revit and Vasari.", + ProductIcon = @"Installer\Resources\Icons\ShellIcon.ico", + }, + Dirs = new Dir[] + { + new InstallDir(installationDir, GenerateWixEntities()) + } +}; + +MajorUpgrade.Default.AllowSameVersionUpgrades = true; +project.RemoveDialogsBetween(NativeDialogs.WelcomeDlg, NativeDialogs.InstallDirDlg); +project.BuildMsi(); + +WixEntity[] GenerateWixEntities() +{ + var versionRegex = new Regex(@"\d+"); + var versionStorages = new Dictionary>(); + + foreach (var directory in args) + { + var directoryInfo = new DirectoryInfo(directory); + var fileVersion = versionRegex.Match(directoryInfo.Name).Value; + var files = new Files($@"{directory}\*.*"); + if (versionStorages.ContainsKey(fileVersion)) + versionStorages[fileVersion].Add(files); + else + versionStorages.Add(fileVersion, new List { files }); + + var assemblies = Directory.GetFiles(directory, "*", SearchOption.AllDirectories); + Console.WriteLine($"Added '{fileVersion}' version files: "); + foreach (var assembly in assemblies) Console.WriteLine($"'{assembly}'"); + } + + return versionStorages.Select(storage => new Dir(storage.Key, storage.Value.ToArray())).Cast().ToArray(); +} \ No newline at end of file diff --git a/Installer/Installer.csproj b/Installer/Installer.csproj new file mode 100644 index 0000000..73fc313 --- /dev/null +++ b/Installer/Installer.csproj @@ -0,0 +1,21 @@ + + + Exe + latest + x64 + net48 + false + + + true + none + + + + 1.19.* + + + 3.11.* + + + diff --git a/Installer/Resources/Icons/BackgroundImage.png b/Installer/Resources/Icons/BackgroundImage.png new file mode 100644 index 0000000..549faa1 Binary files /dev/null and b/Installer/Resources/Icons/BackgroundImage.png differ diff --git a/Installer/Resources/Icons/BannerImage.png b/Installer/Resources/Icons/BannerImage.png new file mode 100644 index 0000000..366db7c Binary files /dev/null and b/Installer/Resources/Icons/BannerImage.png differ diff --git a/Installer/Resources/Icons/ShellIcon.ico b/Installer/Resources/Icons/ShellIcon.ico new file mode 100644 index 0000000..940e075 Binary files /dev/null and b/Installer/Resources/Icons/ShellIcon.ico differ diff --git a/Output/2015.03.20_Setup_RevitPythonShell_2014.exe b/Output/2015.03.20_Setup_RevitPythonShell_2014.exe deleted file mode 100644 index cea3203..0000000 Binary files a/Output/2015.03.20_Setup_RevitPythonShell_2014.exe and /dev/null differ diff --git a/Output/2015.03.20_Setup_RevitPythonShell_2015.exe b/Output/2015.03.20_Setup_RevitPythonShell_2015.exe deleted file mode 100644 index b0014c3..0000000 Binary files a/Output/2015.03.20_Setup_RevitPythonShell_2015.exe and /dev/null differ diff --git a/Output/2015.03.20_Setup_RevitPythonShell_Vasari_Beta3.exe b/Output/2015.03.20_Setup_RevitPythonShell_Vasari_Beta3.exe deleted file mode 100644 index 051b299..0000000 Binary files a/Output/2015.03.20_Setup_RevitPythonShell_Vasari_Beta3.exe and /dev/null differ diff --git a/Output/2015.04.18_Setup_RevitPythonShell_2014.exe b/Output/2015.04.18_Setup_RevitPythonShell_2014.exe deleted file mode 100644 index 305087a..0000000 Binary files a/Output/2015.04.18_Setup_RevitPythonShell_2014.exe and /dev/null differ diff --git a/Output/2015.04.29_Setup_RevitPythonShell_2015.exe b/Output/2015.04.29_Setup_RevitPythonShell_2015.exe deleted file mode 100644 index c121e33..0000000 Binary files a/Output/2015.04.29_Setup_RevitPythonShell_2015.exe and /dev/null differ diff --git a/Output/2015.04.29_Setup_RevitPythonShell_2016.exe b/Output/2015.04.29_Setup_RevitPythonShell_2016.exe deleted file mode 100644 index 156ae83..0000000 Binary files a/Output/2015.04.29_Setup_RevitPythonShell_2016.exe and /dev/null differ diff --git a/Output/2015.05.18_Setup_RevitPythonShell_2015.exe b/Output/2015.05.18_Setup_RevitPythonShell_2015.exe deleted file mode 100644 index ee3dae8..0000000 Binary files a/Output/2015.05.18_Setup_RevitPythonShell_2015.exe and /dev/null differ diff --git a/Output/2015.05.18_Setup_RevitPythonShell_2016.exe b/Output/2015.05.18_Setup_RevitPythonShell_2016.exe deleted file mode 100644 index 8e5745f..0000000 Binary files a/Output/2015.05.18_Setup_RevitPythonShell_2016.exe and /dev/null differ diff --git a/Output/2015.05.21_Setup_RevitPythonShell_2014.exe b/Output/2015.05.21_Setup_RevitPythonShell_2014.exe deleted file mode 100644 index 1c15fbd..0000000 Binary files a/Output/2015.05.21_Setup_RevitPythonShell_2014.exe and /dev/null differ diff --git a/Output/2015.05.21_Setup_RevitPythonShell_2015.exe b/Output/2015.05.21_Setup_RevitPythonShell_2015.exe deleted file mode 100644 index 439a1a8..0000000 Binary files a/Output/2015.05.21_Setup_RevitPythonShell_2015.exe and /dev/null differ diff --git a/Output/2015.05.21_Setup_RevitPythonShell_2016.exe b/Output/2015.05.21_Setup_RevitPythonShell_2016.exe deleted file mode 100644 index eced67d..0000000 Binary files a/Output/2015.05.21_Setup_RevitPythonShell_2016.exe and /dev/null differ diff --git a/Output/2015.06.17_Setup_RevitPythonShell_2014.exe b/Output/2015.06.17_Setup_RevitPythonShell_2014.exe deleted file mode 100644 index 1c15fbd..0000000 Binary files a/Output/2015.06.17_Setup_RevitPythonShell_2014.exe and /dev/null differ diff --git a/Output/2016.06.22_Setup_RevitPythonShell_2017.exe b/Output/2016.06.22_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index 2eaad9e..0000000 Binary files a/Output/2016.06.22_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2016.06.23_Setup_RevitPythonShell_2017.exe b/Output/2016.06.23_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index 1d4b9c4..0000000 Binary files a/Output/2016.06.23_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2016.06.29_Setup_RevitPythonShell_2017.exe b/Output/2016.06.29_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index 64d3ddc..0000000 Binary files a/Output/2016.06.29_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2016.10.24_Setup_RevitPythonShell_2017.exe b/Output/2016.10.24_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index da0345e..0000000 Binary files a/Output/2016.10.24_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2017.03.07_Setup_RevitPythonShell_2015.exe b/Output/2017.03.07_Setup_RevitPythonShell_2015.exe deleted file mode 100644 index 0ac985f..0000000 Binary files a/Output/2017.03.07_Setup_RevitPythonShell_2015.exe and /dev/null differ diff --git a/Output/2017.03.07_Setup_RevitPythonShell_2016.exe b/Output/2017.03.07_Setup_RevitPythonShell_2016.exe deleted file mode 100644 index c1d93b2..0000000 Binary files a/Output/2017.03.07_Setup_RevitPythonShell_2016.exe and /dev/null differ diff --git a/Output/2017.03.07_Setup_RevitPythonShell_2017.exe b/Output/2017.03.07_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index a732ecb..0000000 Binary files a/Output/2017.03.07_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2017.04.05_Setup_RevitPythonShell_2017.exe b/Output/2017.04.05_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index 5a367e7..0000000 Binary files a/Output/2017.04.05_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2017.04.06_Setup_RevitPythonShell_2017.exe b/Output/2017.04.06_Setup_RevitPythonShell_2017.exe deleted file mode 100644 index b8029ba..0000000 Binary files a/Output/2017.04.06_Setup_RevitPythonShell_2017.exe and /dev/null differ diff --git a/Output/2017.07.24_Setup_RevitPythonShell_2018.exe b/Output/2017.07.24_Setup_RevitPythonShell_2018.exe deleted file mode 100644 index e6573cb..0000000 Binary files a/Output/2017.07.24_Setup_RevitPythonShell_2018.exe and /dev/null differ diff --git a/Output/2017.09.12_Setup_RevitPythonShell_2018.exe b/Output/2017.09.12_Setup_RevitPythonShell_2018.exe deleted file mode 100644 index 3af970e..0000000 Binary files a/Output/2017.09.12_Setup_RevitPythonShell_2018.exe and /dev/null differ diff --git a/Output/2018.01.04_Setup_RevitPythonShell_2018.exe b/Output/2018.01.04_Setup_RevitPythonShell_2018.exe deleted file mode 100644 index a5f41f3..0000000 Binary files a/Output/2018.01.04_Setup_RevitPythonShell_2018.exe and /dev/null differ diff --git a/Output/2018.03.19_Setup_RevitPythonShell_2018.exe b/Output/2018.03.19_Setup_RevitPythonShell_2018.exe deleted file mode 100644 index 636b6c4..0000000 Binary files a/Output/2018.03.19_Setup_RevitPythonShell_2018.exe and /dev/null differ diff --git a/Output/2018.09.19_Setup_RevitPythonShell_2019.exe b/Output/2018.09.19_Setup_RevitPythonShell_2019.exe deleted file mode 100644 index 8ff2243..0000000 Binary files a/Output/2018.09.19_Setup_RevitPythonShell_2019.exe and /dev/null differ diff --git a/Output/2020.01.19_Setup_RevitPythonShell_2020.exe b/Output/2020.01.19_Setup_RevitPythonShell_2020.exe deleted file mode 100644 index 7de2384..0000000 Binary files a/Output/2020.01.19_Setup_RevitPythonShell_2020.exe and /dev/null differ diff --git a/Output/2021.03.22_Setup_RevitPythonShell_2021.exe b/Output/2021.03.22_Setup_RevitPythonShell_2021.exe deleted file mode 100755 index 58020e1..0000000 Binary files a/Output/2021.03.22_Setup_RevitPythonShell_2021.exe and /dev/null differ diff --git a/Output/2021.06.20_Setup_RevitPythonShell_2022.exe b/Output/2021.06.20_Setup_RevitPythonShell_2022.exe deleted file mode 100755 index 016db18..0000000 Binary files a/Output/2021.06.20_Setup_RevitPythonShell_2022.exe and /dev/null differ diff --git a/PythonConsoleControl/CommandLineHistory.cs b/PythonConsoleControl/CommandLineHistory.cs index d2d0f93..5c689dd 100644 --- a/PythonConsoleControl/CommandLineHistory.cs +++ b/PythonConsoleControl/CommandLineHistory.cs @@ -2,8 +2,6 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; namespace PythonConsoleControl { diff --git a/PythonConsoleControl/PythonConsole.cs b/PythonConsoleControl/PythonConsole.cs index 8028850..339ca70 100644 --- a/PythonConsoleControl/PythonConsole.cs +++ b/PythonConsoleControl/PythonConsole.cs @@ -2,27 +2,17 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.IO; using System.Threading; using Microsoft.Scripting.Hosting.Shell; using System.Windows.Input; using Microsoft.Scripting; -using IronPython.Hosting; -using IronPython.Runtime; using Microsoft.Scripting.Hosting; -using Microsoft.Scripting.Hosting.Providers; using System.Diagnostics; -using System.Globalization; -using System.Runtime.InteropServices; using System.Windows; using System.Windows.Threading; -using System.Windows.Documents; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Utils; using Style = Microsoft.Scripting.Hosting.Shell.Style; using System.Runtime.Remoting; diff --git a/PythonConsoleControl/PythonConsoleCompletionDataProvider.cs b/PythonConsoleControl/PythonConsoleCompletionDataProvider.cs index 5ab1553..e6c6262 100644 --- a/PythonConsoleControl/PythonConsoleCompletionDataProvider.cs +++ b/PythonConsoleControl/PythonConsoleCompletionDataProvider.cs @@ -5,11 +5,7 @@ using System.Linq; using System.Text; using ICSharpCode.AvalonEdit.CodeCompletion; -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.Editing; -using ICSharpCode.AvalonEdit.Document; using Microsoft.Scripting.Hosting.Shell; -using Microsoft.Scripting.Hosting; using Microsoft.Scripting; using System.Threading; using System.Reflection; diff --git a/PythonConsoleControl/PythonConsoleCompletionWindow.cs b/PythonConsoleControl/PythonConsoleCompletionWindow.cs index 596f3fb..523f385 100644 --- a/PythonConsoleControl/PythonConsoleCompletionWindow.cs +++ b/PythonConsoleControl/PythonConsoleCompletionWindow.cs @@ -1,14 +1,9 @@ // Copyright (c) 2010 Joe Moorhouse using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Diagnostics; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; -using System.Windows.Data; using System.Windows.Input; using System.Windows.Threading; using ICSharpCode.AvalonEdit.Document; @@ -16,7 +11,6 @@ using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.CodeCompletion; using System.Reflection; -using System.ComponentModel; namespace PythonConsoleControl { diff --git a/PythonConsoleControl/PythonConsoleControl.csproj b/PythonConsoleControl/PythonConsoleControl.csproj index 1dd65a3..6fefb45 100644 --- a/PythonConsoleControl/PythonConsoleControl.csproj +++ b/PythonConsoleControl/PythonConsoleControl.csproj @@ -1,125 +1,20 @@  - - Debug;Debug One;Release - - - - - net40;net45;net451;net452;net46;net47;net471;net48 - win + Debug;Release true - - - - - net48 - - - - 2014 - - - 2015 - - - 2016 - - - 2017 - - - 2018 - - - 2019 - - - 2020 - - - 2021 - - - 2022 - - - - + net48 x64 - x64 - None - - - {351668CC-8477-4fbf-BFE3-5F1006E4DB1F} - - - false - false - - - REVIT$(RevitVersion);WINFORMS - $(DefineConstants) - - - false - - + latest false - ..\bin\$(Configuration)\$(RevitVersion) + false - $(DefineConstants);DEBUG full - - - - - - - - - - - - - - - - - - - - - - - - - ..\RequiredLibraries\IronPython.dll @@ -137,8 +32,6 @@ ..\RequiredLibraries\Microsoft.Scripting.Metadata.dll - - @@ -179,18 +72,4 @@ - - - \ No newline at end of file diff --git a/PythonConsoleControl/PythonConsoleControl.xaml.cs b/PythonConsoleControl/PythonConsoleControl.xaml.cs index 6e3453e..6615483 100644 --- a/PythonConsoleControl/PythonConsoleControl.xaml.cs +++ b/PythonConsoleControl/PythonConsoleControl.xaml.cs @@ -1,24 +1,10 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Windows; using System.Windows.Controls; -using System.Windows.Data; -using System.Windows.Documents; -using System.Windows.Input; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using System.Windows.Navigation; -using System.Windows.Shapes; using System.IO; -using System.Windows.Threading; using System.Xml; -using ICSharpCode.AvalonEdit.CodeCompletion; -using ICSharpCode.AvalonEdit.Folding; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.AvalonEdit.Rendering; -using Microsoft.Win32; namespace PythonConsoleControl diff --git a/PythonConsoleControl/PythonConsoleHighlightingColorizer.cs b/PythonConsoleControl/PythonConsoleHighlightingColorizer.cs index b4ef7c5..576979a 100644 --- a/PythonConsoleControl/PythonConsoleHighlightingColorizer.cs +++ b/PythonConsoleControl/PythonConsoleHighlightingColorizer.cs @@ -1,17 +1,7 @@ // Copyright (c) 2010 Joe Moorhouse using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Diagnostics; -using System.Windows; -using System.Windows.Media; -using System.Windows.Threading; - -using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; -using ICSharpCode.AvalonEdit.Rendering; using ICSharpCode.AvalonEdit.Highlighting; namespace PythonConsoleControl diff --git a/PythonConsoleControl/PythonConsoleHost.cs b/PythonConsoleControl/PythonConsoleHost.cs index 49ba31c..a0eba92 100644 --- a/PythonConsoleControl/PythonConsoleHost.cs +++ b/PythonConsoleControl/PythonConsoleHost.cs @@ -1,10 +1,7 @@ // Copyright (c) 2010 Joe Moorhouse using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Windows; using System.Threading; using IronPython.Hosting; using IronPython.Runtime; diff --git a/PythonConsoleControl/PythonConsolePad.cs b/PythonConsoleControl/PythonConsolePad.cs index 16f51a3..d0e2ace 100644 --- a/PythonConsoleControl/PythonConsolePad.cs +++ b/PythonConsoleControl/PythonConsolePad.cs @@ -1,9 +1,5 @@ // Copyright (c) 2010 Joe Moorhouse -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using ICSharpCode.AvalonEdit; using System.Windows.Media; diff --git a/PythonConsoleControl/PythonEditingCommandHandler.cs b/PythonConsoleControl/PythonEditingCommandHandler.cs index 2f425b9..b68d67f 100644 --- a/PythonConsoleControl/PythonEditingCommandHandler.cs +++ b/PythonConsoleControl/PythonEditingCommandHandler.cs @@ -1,22 +1,15 @@ // Copyright (c) 2010 Joe Moorhouse using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Diagnostics; -using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Windows; -using System.Windows.Documents; using System.Windows.Input; using System.Reflection; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.Highlighting; -using ICSharpCode.AvalonEdit.Utils; using ICSharpCode.AvalonEdit.Editing; namespace PythonConsoleControl diff --git a/PythonConsoleControl/PythonOutputStream.cs b/PythonConsoleControl/PythonOutputStream.cs index dba34f3..62bf20c 100644 --- a/PythonConsoleControl/PythonOutputStream.cs +++ b/PythonConsoleControl/PythonOutputStream.cs @@ -1,6 +1,5 @@ // Copyright (c) 2010 Joe Moorhouse -using System; using System.IO; using System.Text; diff --git a/PythonConsoleControl/PythonTextEditor.cs b/PythonConsoleControl/PythonTextEditor.cs index a405e6a..4f5e95e 100644 --- a/PythonConsoleControl/PythonTextEditor.cs +++ b/PythonConsoleControl/PythonTextEditor.cs @@ -2,14 +2,12 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using ICSharpCode.AvalonEdit; using ICSharpCode.AvalonEdit.Editing; using ICSharpCode.AvalonEdit.Document; using ICSharpCode.AvalonEdit.CodeCompletion; using System.Windows; -using System.Windows.Media; using System.Windows.Threading; using System.Windows.Input; using System.Threading; diff --git a/README.md b/README.md index 83e8381..d2c1c53 100644 --- a/README.md +++ b/README.md @@ -1,35 +1,47 @@ # RevitPythonShell +![Revit API](https://img.shields.io/badge/Revit%20API%202023-blue.svg) ![Platform](https://img.shields.io/badge/platform-Windows-lightgray.svg) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -The RevitPythonShell adds an IronPython interpreter to Autodesk Revit and Vasari. +![ReSharper](https://img.shields.io/badge/ReSharper-2021.3.3-yellow) ![Rider](https://img.shields.io/badge/Rider-2021.3.3-yellow) ![Visual Studio 2022](https://img.shields.io/badge/Visual_Studio_2022_Preview_2.0-17.1.0-yellow) ![.NET Framework](https://img.shields.io/badge/.NET_6.0-yellow) -The RevitPythonShell (RPS) lets you to write plugins for Revit in Python, my favourite scripting language! But even better, it provides you with an -interactive shell that lets you see the results of your code *as you type it*. This is great for exploring the Revit API while -writing your Revit Addins - use this in combination with the [RevitLookup](https://github.com/jeremytammik/RevitLookup) database exploration tool to become a Revit API Ninja :) +[![Publish](../../actions/workflows/Workflow.yml/badge.svg)](../../actions) +[![Github All Releases](https://img.shields.io/github/downloads/architecture-building-systems/revitpythonshell/total?color=blue&label=Download)]() +[![HitCount](https://hits.dwyl.com/architecture-building-systems/revitpythonshell.svg?style=flat-square)](http://hits.dwyl.com/architecture-building-systems/revitpythonshell) + +The RevitPythonShell adds an IronPython interpreter to Autodesk Revit and Vasari. + +The RevitPythonShell (RPS) lets you to write plugins for Revit in Python, my favourite scripting language! But even +better, it provides you with an +interactive shell that lets you see the results of your code *as you type it*. This is great for exploring the Revit API +while +writing your Revit Addins - use this in combination with the [RevitLookup](https://github.com/jeremytammik/RevitLookup) +database exploration tool to become a Revit API Ninja :) ## Features - interactive IronPython interpreter for exploring the API - - with syntax highlighting - - autocompletion (press CTRL+SPACE after a period) - - based on the [IronLab](http://code.google.com/p/ironlab/) project + - with syntax highlighting + - autocompletion (press CTRL+SPACE after a period) + - based on the [IronLab](http://code.google.com/p/ironlab/) project - batteries included! (Python standard library is bundled as a resource in the `RpsRuntime.dll`) - full access to the .NET framework and the Revit API - configurable "environment" variables that can be used in your scripts - save "external scripts" for reuse and start collecting your awesome hacks! - run scripts at Revit startup - deploy scripts as standalone Revit Addins -- `lookup()` function for snooping `Element`, `ElementSet` and `ElementId` objects in [RevitLookup](https://github.com/jeremytammik/RevitLookup) +- `lookup()` function for snooping `Element`, `ElementSet` and `ElementId` objects + in [RevitLookup](https://github.com/jeremytammik/RevitLookup) ## Installation + +Please follow last release at section [Release](https://github.com/architecture-building-systems/revitpythonshell/releases/latest) support version Support From Revit 2018-2023. + +Older versions: - [Installer for Autodesk Revit 2022](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2021.06.20/2021.06.20_Setup_RevitPythonShell_2022.exe) - [Installer for Autodesk Revit 2021](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2021.03.22/2021.03.22_Setup_RevitPythonShell_2021.exe) - [Installer for Autodesk Revit 2020](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2019.01.27/2020.01.19_Setup_RevitPythonShell_2020.exe) - [Installer for Autodesk Revit 2019](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2018.09.19/2018.09.19_Setup_RevitPythonShell_2019.exe) - [Installer for Autodesk Revit 2018 (and 2018.1)](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2017.07.24/2017.07.24_Setup_RevitPythonShell_2018.exe) - [Installer for Autodesk Revit 2017](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2017.04.06/2017.04.06_Setup_RevitPythonShell_2017.exe) - - -Older versions: - [Installer for Autodesk Revit 2016](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2017.03.07/2017.03.07_Setup_RevitPythonShell_2016.exe) - [Installer for Autodesk Revit 2015](https://github.com/architecture-building-systems/revitpythonshell/releases/download/2017.03.07/2017.03.07_Setup_RevitPythonShell_2015.exe) - [Installer for Autodesk Revit 2015](http://sustain.arch.ethz.ch/DPV/Setup_RevitPythonShell_2015.exe) @@ -52,28 +64,30 @@ Older versions: Learn some python: - * [The Python Tutorial](https://docs.python.org/2/tutorial/) - * [Dive Into Python](http://www.diveintopython.net/) +* [The Python Tutorial](https://docs.python.org/2/tutorial/) +* [Dive Into Python](http://www.diveintopython.net/) Learn about the Revit API: - * [Autodesk Developer Network](T) - * [Jeremy Tammiks blog "The Building Coder"](http://thebuildingcoder.typepad.com/) - +* [Autodesk Developer Network](T) +* [Jeremy Tammiks blog "The Building Coder"](http://thebuildingcoder.typepad.com/) + Tutorials recommended by the community: - * [Mono IronPython Winforms Tutorial](http://zetcode.com/tutorials/ironpythontutorial/) - recommended by Callum +* [Mono IronPython Winforms Tutorial](http://zetcode.com/tutorials/ironpythontutorial/) - recommended by Callum You can find sample scripts here: - * [Sample RPS Scripts on GitHub](https://github.com/daren-thomas/rps-sample-scripts) +* [Sample RPS Scripts on GitHub](https://github.com/daren-thomas/rps-sample-scripts) * feel free to send me your own scripts for inclusion! - * [Nathan's Revit API Notebook using the RevitPythonShell](http://wiki.theprovingground.org/revit-api) - * Nathan Miller even has a [Mobius Surface for Vasari](http://wiki.theprovingground.org/revit-api-py-parametric) sample - * [dp stuff (Python Scripts Archives)](http://dp-stuff.org/category/python-scripts) +* [Nathan's Revit API Notebook using the RevitPythonShell](http://wiki.theprovingground.org/revit-api) + * Nathan Miller even has a [Mobius Surface for Vasari](http://wiki.theprovingground.org/revit-api-py-parametric) + sample +* [dp stuff (Python Scripts Archives)](http://dp-stuff.org/category/python-scripts) * lots of scripts - * [my own blog](http://darenatwork.blogspot.com/) contains the odd sample script - * [Check out pyRevit](http://eirannejad.github.io/pyRevit/whatspyrevit/) by Ehsan Iran-Nejad - it includes a library of interesting scripts and some additions to make writing your own easier! +* [my own blog](http://darenatwork.blogspot.com/) contains the odd sample script +* [Check out pyRevit](http://eirannejad.github.io/pyRevit/whatspyrevit/) by Ehsan Iran-Nejad - it includes a library of + interesting scripts and some additions to make writing your own easier! ## License @@ -81,17 +95,22 @@ This project is licensed under the terms of the [MIT License](http://opensource. ## Credits - * Daren Thomas (original version, maintainer) - * Zachary Kron (original port to Vasari) - * Akimitsu Hogge (original port to Vasari) - * Joe Moorhouse (interactive shell was taken from his project [IronLab](http://ironlab.net/)) - * Jason Schaeffer (port to Revit 2011) - * [Ehsan Iran-Nejad (@eirannejad)](https://github.com/eirannejad) countless improvements, the awesome [pyRevit](https://github.com/eirannejad/pyRevit) tool and a special thanks for helping maintain RPS! - * [@DanRumery](https://github.com/danrumery) improved autocompletion with PR #59 - * [Petr Mitev (@mitevpi)](https://github.com/mitevpi) ported to Revit 2019 with RP #86 - * [Alvaro Ortega Pickmans (@alvpickmans)](https://github.com/alvpickmans) refactor to sdk csproject and release for Revit 2020 PR #101 - * [@hdm-dt-fb](https://github.com/hdm-dt-fb) added `set_font_sizes` function for presenting RPS ([PR #77](https://github.com/architecture-building-systems/revitpythonshell/pull/77)) - * many, many users with questions, bug reports etc! +* Daren Thomas (original version, maintainer) +* Zachary Kron (original port to Vasari) +* Akimitsu Hogge (original port to Vasari) +* Joe Moorhouse (interactive shell was taken from his project [IronLab](http://ironlab.net/)) +* Jason Schaeffer (port to Revit 2011) +* [Ehsan Iran-Nejad (@eirannejad)](https://github.com/eirannejad) countless improvements, the + awesome [pyRevit](https://github.com/eirannejad/pyRevit) tool and a special thanks for helping maintain RPS! +* [@DanRumery](https://github.com/danrumery) improved autocompletion with PR #59 +* [Petr Mitev (@mitevpi)](https://github.com/mitevpi) ported to Revit 2019 with RP #86 +* [Alvaro Ortega Pickmans (@alvpickmans)](https://github.com/alvpickmans) refactor to sdk csproject and release for + Revit 2020 PR #101 +* [@hdm-dt-fb](https://github.com/hdm-dt-fb) added `set_font_sizes` function for presenting + RPS ([PR #77](https://github.com/architecture-building-systems/revitpythonshell/pull/77)) +* [Nice3Point](https://github.com/Nice3point) for process CI/CD +* [Chuong Mep](https://github.com/chuongmep/) a people like maintain for project open source. +* many, many users with questions, bug reports etc! Also, many thanks to the [Chair for Architecture & Building Systems](http://systems.arch.ethz.ch) for making this project possible. diff --git a/RevitPythonShell.sln b/RevitPythonShell.sln index 7335ffe..c6f2876 100644 --- a/RevitPythonShell.sln +++ b/RevitPythonShell.sln @@ -13,52 +13,113 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PythonConsoleControl", "Pyt EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RpsRuntime", "RpsRuntime\RpsRuntime.csproj", "{C8446636-C663-409F-82D0-72C0D55BBA1C}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Installer", "Installer\Installer.csproj", "{B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Build", "build\Build.csproj", "{0DF5F952-C36B-4C97-B0CD-678C61093311}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SolutionItems", "SolutionItems", "{7D7FB600-5CB5-46B8-9CBB-2F42E3EF19EF}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + CHANGELOG.md = CHANGELOG.md + README.md = README.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug One|Any CPU = Debug One|Any CPU - Debug One|x64 = Debug One|x64 Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 + Installer|Any CPU = Installer|Any CPU + Debug R22|Any CPU = Debug R22|Any CPU + Debug R23|Any CPU = Debug R23|Any CPU + Release R18|Any CPU = Release R18|Any CPU + Release R19|Any CPU = Release R19|Any CPU + Release R20|Any CPU = Release R20|Any CPU + Release R21|Any CPU = Release R21|Any CPU + Release R22|Any CPU = Release R22|Any CPU + Release R23|Any CPU = Release R23|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug One|Any CPU.ActiveCfg = Debug One|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug One|Any CPU.Build.0 = Debug One|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug One|x64.ActiveCfg = Debug One|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug One|x64.Build.0 = Debug One|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|Any CPU.ActiveCfg = Debug|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|Any CPU.Build.0 = Debug|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|x64.ActiveCfg = Debug|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|x64.Build.0 = Debug|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release|Any CPU.ActiveCfg = Release|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release|Any CPU.Build.0 = Release|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release|x64.ActiveCfg = Release|x64 - {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release|x64.Build.0 = Release|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug One|Any CPU.ActiveCfg = Debug One|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug One|Any CPU.Build.0 = Debug One|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug One|x64.ActiveCfg = Debug One|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug One|x64.Build.0 = Debug One|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|Any CPU.ActiveCfg = Debug|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|Any CPU.Build.0 = Debug|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|x64.ActiveCfg = Debug|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|x64.Build.0 = Debug|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release|Any CPU.ActiveCfg = Release|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release|Any CPU.Build.0 = Release|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release|x64.ActiveCfg = Release|x64 - {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release|x64.Build.0 = Release|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug One|Any CPU.ActiveCfg = Debug One|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug One|Any CPU.Build.0 = Debug One|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug One|x64.ActiveCfg = Debug One|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug One|x64.Build.0 = Debug One|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|Any CPU.ActiveCfg = Debug|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|Any CPU.Build.0 = Debug|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|x64.ActiveCfg = Debug|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|x64.Build.0 = Debug|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release|Any CPU.ActiveCfg = Release|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release|Any CPU.Build.0 = Release|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release|x64.ActiveCfg = Release|x64 - {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release|x64.Build.0 = Release|x64 + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Debug R22|Any CPU.Build.0 = Debug|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R18|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R19|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R20|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R21|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R22|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Release R23|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Installer|Any CPU.ActiveCfg = Release|Any CPU + {B0FECFED-4451-4C7D-ACB7-59CC6F12FBAF}.Installer|Any CPU.Build.0 = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Debug R22|Any CPU.Build.0 = Debug|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R18|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R19|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R20|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R21|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R22|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Release R23|Any CPU.ActiveCfg = Release|Any CPU + {0DF5F952-C36B-4C97-B0CD-678C61093311}.Installer|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug R22|Any CPU.ActiveCfg = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug R22|Any CPU.Build.0 = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug R23|Any CPU.ActiveCfg = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R18|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R18|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R19|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R19|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R20|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R20|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R21|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R21|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R22|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R22|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R23|Any CPU.ActiveCfg = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Release R23|Any CPU.Build.0 = Release|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Debug R23|Any CPU.Build.0 = Debug|Any CPU + {F1152D68-346B-4F48-8DB7-BE67B5CB1F49}.Installer|Any CPU.ActiveCfg = Release|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|Any CPU.ActiveCfg = Debug R22|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug|Any CPU.Build.0 = Debug R22|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug R22|Any CPU.ActiveCfg = Debug R22|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug R22|Any CPU.Build.0 = Debug R22|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug R23|Any CPU.ActiveCfg = Debug R23|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R18|Any CPU.ActiveCfg = Release R18|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R18|Any CPU.Build.0 = Release R18|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R19|Any CPU.ActiveCfg = Release R19|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R19|Any CPU.Build.0 = Release R19|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R20|Any CPU.ActiveCfg = Release R20|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R20|Any CPU.Build.0 = Release R20|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R21|Any CPU.ActiveCfg = Release R21|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R21|Any CPU.Build.0 = Release R21|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R23|Any CPU.ActiveCfg = Release R23|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R23|Any CPU.Build.0 = Release R23|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Debug R23|Any CPU.Build.0 = Debug R23|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Installer|Any CPU.ActiveCfg = Release R23|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R22|Any CPU.ActiveCfg = Release R22|Any CPU + {7E37F14E-D840-42F8-8CA6-90FFC5497972}.Release R22|Any CPU.Build.0 = Release R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|Any CPU.ActiveCfg = Debug R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug|Any CPU.Build.0 = Debug R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug R22|Any CPU.ActiveCfg = Debug R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug R22|Any CPU.Build.0 = Debug R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug R23|Any CPU.ActiveCfg = Debug R23|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R18|Any CPU.ActiveCfg = Release R18|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R18|Any CPU.Build.0 = Release R18|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R19|Any CPU.ActiveCfg = Release R19|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R19|Any CPU.Build.0 = Release R19|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R20|Any CPU.ActiveCfg = Release R20|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R20|Any CPU.Build.0 = Release R20|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R21|Any CPU.ActiveCfg = Release R21|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R21|Any CPU.Build.0 = Release R21|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R22|Any CPU.ActiveCfg = Release R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R22|Any CPU.Build.0 = Release R22|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R23|Any CPU.ActiveCfg = Release R23|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Release R23|Any CPU.Build.0 = Release R23|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Debug R23|Any CPU.Build.0 = Debug R23|Any CPU + {C8446636-C663-409F-82D0-72C0D55BBA1C}.Installer|Any CPU.ActiveCfg = Release R23|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/RevitPythonShell/RevitPythonShellApplication.cs b/RevitPythonShell/App.cs similarity index 93% rename from RevitPythonShell/RevitPythonShellApplication.cs rename to RevitPythonShell/App.cs index 9fd17bc..83b79a3 100644 --- a/RevitPythonShell/RevitPythonShellApplication.cs +++ b/RevitPythonShell/App.cs @@ -1,618 +1,614 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Xml.Linq; -using Autodesk.Revit; -using Autodesk.Revit.UI; -using Autodesk.Revit.ApplicationServices; -using Autodesk.Revit.Attributes; -using System.Windows.Media; -using System.Windows.Media.Imaging; -using RevitPythonShell.RpsRuntime; - -namespace RevitPythonShell -{ - [Regeneration(RegenerationOption.Manual)] - [Transaction(TransactionMode.Manual)] - class RevitPythonShellApplication : IExternalApplication - { - private const string APP_NAME = "RevitPythonShell"; - private static string versionNumber; - private static string dllfolder; - - /// - /// Hook into Revit to allow starting a command. - /// - Result IExternalApplication.OnStartup(UIControlledApplication application) - { - - try - { - versionNumber = application.ControlledApplication.VersionNumber; - if (application.ControlledApplication.VersionName.ToLower().Contains("vasari")) - { - versionNumber = "_Vasari"; - } - -#if DEBUG - dllfolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); -#else - dllfolder = Path.Combine( - Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), - $"{APP_NAME}/{versionNumber}"); -#endif - var assemblyName = "CommandLoaderAssembly"; - var dllfullpath = Path.Combine(dllfolder, assemblyName + ".dll"); - - var settings = GetSettings(); - - CreateCommandLoaderAssembly(settings, dllfolder, assemblyName); - BuildRibbonPanel(application, dllfullpath); - - ExecuteStartupScript(application); - - return Result.Succeeded; - } - catch (Exception ex) - { - var td = new TaskDialog("Error setting up RevitPythonShell"); - td.MainInstruction = ex.Message; - td.ExpandedContent = ex.ToString(); - td.Show(); - return Result.Failed; - } - } - - private static void ExecuteStartupScript(UIControlledApplication uiControlledApplication) - { - // we need a UIApplication object to assign as `__revit__` in python... - var versionNumber = uiControlledApplication.ControlledApplication.VersionNumber; - var fieldName = int.Parse(versionNumber) >= 2017 ? "m_uiapplication": "m_application"; - var fi = uiControlledApplication.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - var uiApplication = (UIApplication)fi.GetValue(uiControlledApplication); - // execute StartupScript - var startupScript = GetStartupScript(); - if (startupScript != null) - { - var executor = new ScriptExecutor(GetConfig(), uiApplication, uiControlledApplication); - var result = executor.ExecuteScript(startupScript, GetStartupScriptPath()); - if (result == (int)Result.Failed) - { - TaskDialog.Show("RevitPythonShell - StartupScript", executor.Message); - } - } - } - - private static void BuildRibbonPanel(UIControlledApplication application, string dllfullpath) - { - var assembly = typeof(RevitPythonShellApplication).Assembly; - var smallImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Python-16.png"); - var largeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Python-32.png"); - - - RibbonPanel ribbonPanel = application.CreateRibbonPanel(APP_NAME); - var splitButton = ribbonPanel.AddItem(new SplitButtonData("splitButtonRevitPythonShell", APP_NAME)) as SplitButton; - - PushButtonData pbdOpenPythonShell = new PushButtonData( - APP_NAME, - "Interactive\nPython Shell", - assembly.Location, - "RevitPythonShell.IronPythonConsoleCommand"); - pbdOpenPythonShell.Image = smallImage; - pbdOpenPythonShell.LargeImage = largeImage; - pbdOpenPythonShell.AvailabilityClassName = "RevitPythonShell.IronPythonConsoleCommandAvail"; - splitButton.AddPushButton(pbdOpenPythonShell); - - PushButtonData pbdOpenNonModalShell = new PushButtonData( - "NonModalRevitPythonShell", - "Non-modal\nShell", - assembly.Location, - "RevitPythonShell.NonModalConsoleCommand"); - pbdOpenNonModalShell.Image = smallImage; - pbdOpenNonModalShell.LargeImage = largeImage; - pbdOpenNonModalShell.AvailabilityClassName = "RevitPythonShell.IronPythonConsoleCommandAvail"; - splitButton.AddPushButton(pbdOpenNonModalShell); - - PushButtonData pbdConfigure = new PushButtonData( - "Configure", - "Configure...", - assembly.Location, - "RevitPythonShell.ConfigureCommand"); - pbdConfigure.Image = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Settings-16.png"); - pbdConfigure.LargeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Settings-32.png"); - pbdConfigure.AvailabilityClassName = "RevitPythonShell.IronPythonConsoleCommandAvail"; - splitButton.AddPushButton(pbdConfigure); - - PushButtonData pbdDeployRpsAddin = new PushButtonData( - "DeployRpsAddin", - "Deploy RpsAddin", - assembly.Location, - "RevitPythonShell.DeployRpsAddinCommand"); - pbdDeployRpsAddin.Image = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Deployment-16.png"); - pbdDeployRpsAddin.LargeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Deployment-32.png"); - pbdDeployRpsAddin.AvailabilityClassName = "RevitPythonShell.IronPythonConsoleCommandAvail"; - splitButton.AddPushButton(pbdDeployRpsAddin); - - var commands = GetCommands(GetSettings()).ToList(); - AddGroupedCommands(dllfullpath, ribbonPanel, commands.Where(c => !string.IsNullOrEmpty(c.Group)).GroupBy(c => c.Group)); - AddUngroupedCommands(dllfullpath, ribbonPanel, commands.Where(c => string.IsNullOrEmpty(c.Group)).ToList()); - } - - - - private static ImageSource GetEmbeddedBmp(System.Reflection.Assembly app, string imageName) - { - var file = app.GetManifestResourceStream(imageName); - var source = BmpBitmapDecoder.Create(file, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); - return source.Frames[0]; - } - - private static ImageSource GetEmbeddedPng(System.Reflection.Assembly app, string imageName) - { - var file = app.GetManifestResourceStream(imageName); - var source = PngBitmapDecoder.Create(file, BitmapCreateOptions.None, BitmapCacheOption.None); - return source.Frames[0]; - } - - private static void AddGroupedCommands(string dllfullpath, RibbonPanel ribbonPanel, IEnumerable> groupedCommands) - { - foreach (var group in groupedCommands) - { - SplitButtonData splitButtonData = new SplitButtonData(group.Key, group.Key); - var splitButton = ribbonPanel.AddItem(splitButtonData) as SplitButton; - foreach (var command in group) - { - var pbd = new PushButtonData(command.Name, command.Name, dllfullpath, "Command" + command.Index); - pbd.Image = command.SmallImage; - pbd.LargeImage = command.LargeImage; - splitButton.AddPushButton(pbd); - } - } - } - - - private static void AddUngroupedCommands(string dllfullpath, RibbonPanel ribbonPanel, List commands) - { - // add canned commands as stacked pushbuttons (try to pack 3 commands per pushbutton, then 2) - while (commands.Count > 4 || commands.Count == 3) - { - // remove first three commands from the list - var command0 = commands[0]; - var command1 = commands[1]; - var command2 = commands[2]; - commands.RemoveAt(0); - commands.RemoveAt(0); - commands.RemoveAt(0); - - PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); - pbdA.Image = command0.SmallImage; - pbdA.LargeImage = command0.LargeImage; - - PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); - pbdB.Image = command1.SmallImage; - pbdB.LargeImage = command1.LargeImage; - - PushButtonData pbdC = new PushButtonData(command2.Name, command2.Name, dllfullpath, "Command" + command2.Index); - pbdC.Image = command2.SmallImage; - pbdC.LargeImage = command2.LargeImage; - - ribbonPanel.AddStackedItems(pbdA, pbdB, pbdC); - } - if (commands.Count == 4) - { - // remove first two commands from the list - var command0 = commands[0]; - var command1 = commands[1]; - commands.RemoveAt(0); - commands.RemoveAt(0); - - PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); - pbdA.Image = command0.SmallImage; - pbdA.LargeImage = command0.LargeImage; - - PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); - pbdB.Image = command0.SmallImage; - pbdB.LargeImage = command0.LargeImage; - - ribbonPanel.AddStackedItems(pbdA, pbdB); - } - if (commands.Count == 2) - { - // remove first two commands from the list - var command0 = commands[0]; - var command1 = commands[1]; - commands.RemoveAt(0); - commands.RemoveAt(0); - PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); - pbdA.Image = command0.SmallImage; - pbdA.LargeImage = command0.LargeImage; - - PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); - pbdB.Image = command1.SmallImage; - pbdB.LargeImage = command1.LargeImage; - - ribbonPanel.AddStackedItems(pbdA, pbdB); - } - if (commands.Count == 1) - { - // only one command defined, show as a big button... - var command = commands[0]; - PushButtonData pbd = new PushButtonData(command.Name, command.Name, dllfullpath, "Command" + command.Index); - pbd.Image = command.SmallImage; - pbd.LargeImage = command.LargeImage; - ribbonPanel.AddItem(pbd); - } - } - - /// - /// Creates a dynamic assembly that contains types for starting the canned commands. - /// - private static void CreateCommandLoaderAssembly(XDocument repository, string dllfolder, string dllname) - { - var assemblyName = new AssemblyName { Name = dllname + ".dll", Version = new Version(1, 0, 0, 0) }; - var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, dllfolder); - var moduleBuilder = assemblyBuilder.DefineDynamicModule("CommandLoaderModule", dllname + ".dll"); - - foreach (var command in GetCommands(repository)) - { - var typebuilder = moduleBuilder.DefineType("Command" + command.Index, - TypeAttributes.Class | TypeAttributes.Public, - typeof(CommandLoaderBase)); - - // add RegenerationAttribute to type - var regenerationConstrutorInfo = typeof(RegenerationAttribute).GetConstructor(new Type[] { typeof(RegenerationOption) }); - var regenerationAttributeBuilder = new CustomAttributeBuilder(regenerationConstrutorInfo, new object[] {RegenerationOption.Manual}); - typebuilder.SetCustomAttribute(regenerationAttributeBuilder); - - // add TransactionAttribute to type - var transactionConstructorInfo = typeof(TransactionAttribute).GetConstructor(new Type[] { typeof(TransactionMode) }); - var transactionAttributeBuilder = new CustomAttributeBuilder(transactionConstructorInfo, new object[] { TransactionMode.Manual }); - typebuilder.SetCustomAttribute(transactionAttributeBuilder); - - // call base constructor with script path - var ci = typeof(CommandLoaderBase).GetConstructor(new[] { typeof(string) }); - - var constructorBuilder = typebuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]); - var gen = constructorBuilder.GetILGenerator(); - gen.Emit(OpCodes.Ldarg_0); // Load "this" onto eval stack - gen.Emit(OpCodes.Ldstr, command.Source); // Load the path to the command as a string onto stack - gen.Emit(OpCodes.Call, ci); // call base constructor (consumes "this" and the string) - gen.Emit(OpCodes.Nop); // Fill some space - this is how it is generated for equivalent C# code - gen.Emit(OpCodes.Nop); - gen.Emit(OpCodes.Nop); - gen.Emit(OpCodes.Ret); // return from constructor - typebuilder.CreateType(); - } - assemblyBuilder.Save(dllname + ".dll"); - } - - Result IExternalApplication.OnShutdown(UIControlledApplication application) - { - // FIXME: deallocate the python shell... - return Result.Succeeded; - } - - public static IRpsConfig GetConfig() - { - return new RpsConfig(GetSettingsFile()); - } - - /// - /// Returns a handle to the settings file. - /// - /// - public static XDocument GetSettings() - { - string settingsFile = GetSettingsFile(); - return XDocument.Load(settingsFile); - } - - private static string GetSettingsFile() - { - string folder = GetSettingsFolder(); - return Path.Combine(folder, "RevitPythonShell.xml"); - } - - /// - /// Returns the name of the folder with the settings file. This folder - /// is also the default folder for relative paths in StartupScript and InitScript tags. - /// - private static string GetSettingsFolder() - { -#if DEBUG - return Path.Combine(dllfolder, "DefaultConfig"); -#else - return dllfolder; -#endif - } - - /// - /// Returns a list of commands as defined in the repository file. - /// - /// - public static IEnumerable GetCommands(XDocument repository) - { - int i = 0; - foreach (var commandNode in repository.Root.Descendants("Command") ?? new List()) - { - var addinAssembly = typeof(RpsExternalApplicationBase).Assembly; - var commandName = commandNode.Attribute("name").Value; - var commandSrc = commandNode.Attribute("src").Value; - var group = commandNode.Attribute("group") == null ? "" : commandNode.Attribute("group").Value; - - ImageSource largeImage = null; - if (IsValidPath(commandNode.Attribute("largeImage"))) - { - var largeImagePath = GetAbsolutePath(commandNode.Attribute("largeImage").Value); - largeImage = BitmapDecoder.Create(File.OpenRead(largeImagePath), BitmapCreateOptions.None, BitmapCacheOption.None).Frames[0]; - } - else - { - largeImage = GetEmbeddedPng(addinAssembly, "RpsRuntime.Resources.PythonScript32x32.png"); - } - - ImageSource smallImage = null; - if (IsValidPath(commandNode.Attribute("smallImage"))) - { - var smallImagePath = GetAbsolutePath(commandNode.Attribute("smallImage").Value); - smallImage = BitmapDecoder.Create(File.OpenRead(smallImagePath), BitmapCreateOptions.None, BitmapCacheOption.None).Frames[0]; - } - else - { - smallImage = GetEmbeddedPng(addinAssembly, "RpsRuntime.Resources.PythonScript16x16.png"); - } - - yield return new Command { - Name = commandName, - Source = commandSrc, - Group = group, - LargeImage = largeImage, - SmallImage = smallImage, - Index = i++ - }; - } - } - - /// - /// True, if the contents of the attribute is a valid absolute path (or relative path to the assembly) is - /// an existing path. - /// - private static bool IsValidPath(XAttribute pathAttribute) - { - if (pathAttribute != null && !string.IsNullOrEmpty(pathAttribute.Value)) - { - return File.Exists(GetAbsolutePath(pathAttribute.Value)); - } - return false; - } - - /// - /// Return an absolute path for input path, with relative paths seen as - /// relative to the assembly location. No guarantees are made as to - /// wether the path exists or not. - /// - private static string GetAbsolutePath(string path) - { - if (Path.IsPathRooted(path)) - { - return path; - } - else - { - var assembly = typeof(RevitPythonShellApplication).Assembly; - return Path.Combine(Path.GetDirectoryName(assembly.Location), path); - } - } - - /// - /// Returns a string to be executed, whenever the interactive shell is started. - /// If this is not specified in the XML file (under /RevitPythonShell/InitScript), - /// then null is returned. - /// - public static string GetInitScript() - { - var path = GetInitScriptPath(); - if (File.Exists(path)) - { - using (var reader = File.OpenText(path)) - { - var source = reader.ReadToEnd(); - return source; - } - } - - // backwards compatibility: InitScript used to have a CDATA section directly - // embedded in the settings xml file - var initScriptTags = GetSettings().Root.Descendants("InitScript") ?? new List(); - if (initScriptTags.Count() == 0) - { - return null; - } - var firstScript = initScriptTags.First(); - // backwards compatibility: InitScript used to be included as CDATA in the config file - return firstScript.Value.Trim(); - } - - /// - /// Returns the path to the InitScript as configured in the settings file or "" if not - /// configured. This is used in the ConfigureCommandsForm. - /// - public static string GetInitScriptPath() - { - return GetScriptPath("InitScript"); - } - - - /// - /// Returns the path to the StartupScript as configured in the settings file or "" if not - /// configured. This is used in the ConfigureCommandsForm. - /// - public static string GetStartupScriptPath() - { - return GetScriptPath("StartupScript"); - } - - /// - /// Returns the value of the "src" attribute for the tag "tagName" in the settings file - /// or "" if not configured. - /// - private static string GetScriptPath(string tagName) - { - var tags = GetSettings().Root.Descendants(tagName) ?? new List(); - if (tags.Count() == 0) - { - return ""; - } - var firstScript = tags.First(); - if (firstScript.Attribute("src") != null) - { - var path = firstScript.Attribute("src").Value; - if (Path.IsPathRooted(path)) - { - return path; - } - else - { - return Path.Combine(GetSettingsFolder(), path); - } - } - else - { - return ""; - } - } - - /// - /// Returns a string to be executed, whenever the revit is started. - /// If this is not specified as a path to an existing file in the XML file (under /RevitPythonShell/StartupScript/@src), - /// then null is returned. - /// - public static string GetStartupScript() - { - var path = GetStartupScriptPath(); - if (File.Exists(path)) - { - using (var reader = File.OpenText(path)) - { - var source = reader.ReadToEnd(); - return source; - } - } - // no startup script found - return null; - } - - /// - /// Writes settings to the settings file, replacing the old commands. - /// - public static void WriteSettings( - IEnumerable commands, - IEnumerable searchPaths, - IEnumerable> variables, - string initScript, - string startupScript) - { - var doc = GetSettings(); - var settingsFolder = GetSettingsFolder(); - - // clean out current stuff - foreach (var xmlExistingCommands in (doc.Root.Descendants("Commands") ?? new List()).ToList()) - { - xmlExistingCommands.Remove(); - } - foreach (var xmlExistingSearchPaths in doc.Root.Descendants("SearchPaths").ToList()) - { - xmlExistingSearchPaths.Remove(); - } - foreach (var xmlExistingVariables in doc.Root.Descendants("Variables").ToList()) - { - xmlExistingVariables.Remove(); - } - foreach (var xmlExistingInitScript in doc.Root.Descendants("InitScript").ToList()) - { - xmlExistingInitScript.Remove(); - } - foreach (var xmlExistingStartupScript in doc.Root.Descendants("StartupScript").ToList()) - { - xmlExistingStartupScript.Remove(); - } - - // add commnads - var xmlCommands = new XElement("Commands"); - foreach (var command in commands) - { - xmlCommands.Add(new XElement( - "Command", - new XAttribute("name", command.Name), - new XAttribute("src", command.Source), - new XAttribute("group", command.Group))); - - } - doc.Root.Add(xmlCommands); - - // add search paths - var xmlSearchPaths = new XElement("SearchPaths"); - foreach (var path in searchPaths) - { - xmlSearchPaths.Add(new XElement( - "SearchPath", - new XAttribute("name", path))); - - } - // ensure settings directory is added to the search paths - if (!searchPaths.Contains(settingsFolder)) { - xmlSearchPaths.Add(new XElement( - "SearchPath", - new XAttribute("name", settingsFolder))); - - } - doc.Root.Add(xmlSearchPaths); - - // add variables - var xmlVariables = new XElement("Variables"); - foreach (var variable in variables) - { - xmlVariables.Add(new XElement( - "StringVariable", - new XAttribute("name", variable.Key), - new XAttribute("value", variable.Value))); - - } - doc.Root.Add(xmlVariables); - - // add init script - var xmlInitScript = new XElement("InitScript"); - xmlInitScript.Add(new XAttribute("src", initScript)); - doc.Root.Add(xmlInitScript); - - // add startup script - var xmlStartupScript = new XElement("StartupScript"); - xmlStartupScript.Add(new XAttribute("src", startupScript)); - doc.Root.Add(xmlStartupScript); - - doc.Save(GetSettingsFile()); - } - } - - /// - /// A simple structure to hold information about canned commands. - /// - internal class Command - { - public string Name; - public string Group; - public string Source; - public int Index; - public ImageSource LargeImage; - public ImageSource SmallImage; - - public override string ToString() - { - return Name; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using System.Xml.Linq; +using Autodesk.Revit; +using Autodesk.Revit.UI; +using Autodesk.Revit.ApplicationServices; +using Autodesk.Revit.Attributes; +using System.Windows.Media; +using System.Windows.Media.Imaging; +using RevitPythonShell.RevitCommands; +using RpsRuntime; + +namespace RevitPythonShell +{ + [Regeneration(RegenerationOption.Manual)] + [Transaction(TransactionMode.Manual)] + class App : IExternalApplication + { + private const string APP_NAME = "RevitPythonShell"; + private static string versionNumber; + private static string dllfolder; + + /// + /// Hook into Revit to allow starting a command. + /// + Result IExternalApplication.OnStartup(UIControlledApplication application) + { + + try + { + versionNumber = application.ControlledApplication.VersionNumber; + if (application.ControlledApplication.VersionName.ToLower().Contains("vasari")) + { + versionNumber = "_Vasari"; + } + + dllfolder = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); + + // dllfolder = Path.Combine( + // Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), + // $"{APP_NAME}/{versionNumber}"); + var assemblyName = "CommandLoaderAssembly"; + var dllfullpath = Path.Combine(dllfolder, assemblyName + ".dll"); + + var settings = GetSettings(); + + CreateCommandLoaderAssembly(settings, dllfolder, assemblyName); + BuildRibbonPanel(application, dllfullpath); + + ExecuteStartupScript(application); + + return Result.Succeeded; + } + catch (Exception ex) + { + var td = new TaskDialog("Error setting up RevitPythonShell"); + td.MainInstruction = ex.Message; + td.ExpandedContent = ex.ToString(); + td.Show(); + return Result.Failed; + } + } + + private static void ExecuteStartupScript(UIControlledApplication uiControlledApplication) + { + // we need a UIApplication object to assign as `__revit__` in python... + var versionNumber = uiControlledApplication.ControlledApplication.VersionNumber; + var fieldName = int.Parse(versionNumber) >= 2017 ? "m_uiapplication": "m_application"; + var fi = uiControlledApplication.GetType().GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); + var uiApplication = (UIApplication)fi.GetValue(uiControlledApplication); + // execute StartupScript + var startupScript = GetStartupScript(); + if (startupScript != null) + { + var executor = new ScriptExecutor(GetConfig(), uiApplication, uiControlledApplication); + var result = executor.ExecuteScript(startupScript, GetStartupScriptPath()); + if (result == (int)Result.Failed) + { + TaskDialog.Show("RevitPythonShell - StartupScript", executor.Message); + } + } + } + + private static void BuildRibbonPanel(UIControlledApplication application, string dllfullpath) + { + var assembly = typeof(App).Assembly; + var smallImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Python-16.png"); + var largeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Python-32.png"); + + + RibbonPanel ribbonPanel = application.CreateRibbonPanel(APP_NAME); + var splitButton = ribbonPanel.AddItem(new SplitButtonData("splitButtonRevitPythonShell", APP_NAME)) as SplitButton; + + PushButtonData pbdOpenPythonShell = new PushButtonData( + APP_NAME, + "Interactive\nPython Shell", + assembly.Location, + typeof(IronPythonConsoleCommand).FullName); + pbdOpenPythonShell.Image = smallImage; + pbdOpenPythonShell.LargeImage = largeImage; + pbdOpenPythonShell.AvailabilityClassName = typeof(IronPythonConsoleCommandAvail).FullName; + splitButton.AddPushButton(pbdOpenPythonShell); + + PushButtonData pbdOpenNonModalShell = new PushButtonData( + "NonModalRevitPythonShell", + "Non-modal\nShell", + assembly.Location, + typeof(NonModalConsoleCommand).FullName); + pbdOpenNonModalShell.Image = smallImage; + pbdOpenNonModalShell.LargeImage = largeImage; + pbdOpenNonModalShell.AvailabilityClassName = typeof(IronPythonConsoleCommandAvail).FullName; + splitButton.AddPushButton(pbdOpenNonModalShell); + + PushButtonData pbdConfigure = new PushButtonData( + "Configure", + "Configure...", + assembly.Location, + typeof(ConfigureCommand).FullName); + pbdConfigure.Image = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Settings-16.png"); + pbdConfigure.LargeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Settings-32.png"); + pbdConfigure.AvailabilityClassName = typeof(IronPythonConsoleCommandAvail).FullName; + splitButton.AddPushButton(pbdConfigure); + + PushButtonData pbdDeployRpsAddin = new PushButtonData( + "DeployRpsAddin", + "Deploy RpsAddin", + assembly.Location, + typeof(DeployRpsAddinCommand).FullName); + pbdDeployRpsAddin.Image = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Deployment-16.png"); + pbdDeployRpsAddin.LargeImage = GetEmbeddedPng(assembly, "RevitPythonShell.Resources.Deployment-32.png"); + pbdDeployRpsAddin.AvailabilityClassName = typeof(IronPythonConsoleCommandAvail).FullName; + splitButton.AddPushButton(pbdDeployRpsAddin); + + var commands = GetCommands(GetSettings()).ToList(); + AddGroupedCommands(dllfullpath, ribbonPanel, commands.Where(c => !string.IsNullOrEmpty(c.Group)).GroupBy(c => c.Group)); + AddUngroupedCommands(dllfullpath, ribbonPanel, commands.Where(c => string.IsNullOrEmpty(c.Group)).ToList()); + } + + + + private static ImageSource GetEmbeddedBmp(System.Reflection.Assembly app, string imageName) + { + var file = app.GetManifestResourceStream(imageName); + var source = BmpBitmapDecoder.Create(file, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.Default); + return source.Frames[0]; + } + + private static ImageSource GetEmbeddedPng(System.Reflection.Assembly app, string imageName) + { + var file = app.GetManifestResourceStream(imageName); + var source = PngBitmapDecoder.Create(file, BitmapCreateOptions.None, BitmapCacheOption.None); + return source.Frames[0]; + } + + private static void AddGroupedCommands(string dllfullpath, RibbonPanel ribbonPanel, IEnumerable> groupedCommands) + { + foreach (var group in groupedCommands) + { + SplitButtonData splitButtonData = new SplitButtonData(group.Key, group.Key); + var splitButton = ribbonPanel.AddItem(splitButtonData) as SplitButton; + foreach (var command in group) + { + var pbd = new PushButtonData(command.Name, command.Name, dllfullpath, "Command" + command.Index); + pbd.Image = command.SmallImage; + pbd.LargeImage = command.LargeImage; + splitButton.AddPushButton(pbd); + } + } + } + + + private static void AddUngroupedCommands(string dllfullpath, RibbonPanel ribbonPanel, List commands) + { + // add canned commands as stacked pushbuttons (try to pack 3 commands per pushbutton, then 2) + while (commands.Count > 4 || commands.Count == 3) + { + // remove first three commands from the list + var command0 = commands[0]; + var command1 = commands[1]; + var command2 = commands[2]; + commands.RemoveAt(0); + commands.RemoveAt(0); + commands.RemoveAt(0); + + PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); + pbdA.Image = command0.SmallImage; + pbdA.LargeImage = command0.LargeImage; + + PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); + pbdB.Image = command1.SmallImage; + pbdB.LargeImage = command1.LargeImage; + + PushButtonData pbdC = new PushButtonData(command2.Name, command2.Name, dllfullpath, "Command" + command2.Index); + pbdC.Image = command2.SmallImage; + pbdC.LargeImage = command2.LargeImage; + + ribbonPanel.AddStackedItems(pbdA, pbdB, pbdC); + } + if (commands.Count == 4) + { + // remove first two commands from the list + var command0 = commands[0]; + var command1 = commands[1]; + commands.RemoveAt(0); + commands.RemoveAt(0); + + PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); + pbdA.Image = command0.SmallImage; + pbdA.LargeImage = command0.LargeImage; + + PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); + pbdB.Image = command0.SmallImage; + pbdB.LargeImage = command0.LargeImage; + + ribbonPanel.AddStackedItems(pbdA, pbdB); + } + if (commands.Count == 2) + { + // remove first two commands from the list + var command0 = commands[0]; + var command1 = commands[1]; + commands.RemoveAt(0); + commands.RemoveAt(0); + PushButtonData pbdA = new PushButtonData(command0.Name, command0.Name, dllfullpath, "Command" + command0.Index); + pbdA.Image = command0.SmallImage; + pbdA.LargeImage = command0.LargeImage; + + PushButtonData pbdB = new PushButtonData(command1.Name, command1.Name, dllfullpath, "Command" + command1.Index); + pbdB.Image = command1.SmallImage; + pbdB.LargeImage = command1.LargeImage; + + ribbonPanel.AddStackedItems(pbdA, pbdB); + } + if (commands.Count == 1) + { + // only one command defined, show as a big button... + var command = commands[0]; + PushButtonData pbd = new PushButtonData(command.Name, command.Name, dllfullpath, "Command" + command.Index); + pbd.Image = command.SmallImage; + pbd.LargeImage = command.LargeImage; + ribbonPanel.AddItem(pbd); + } + } + + /// + /// Creates a dynamic assembly that contains types for starting the canned commands. + /// + private static void CreateCommandLoaderAssembly(XDocument repository, string dllfolder, string dllname) + { + var assemblyName = new AssemblyName { Name = dllname + ".dll", Version = new Version(1, 0, 0, 0) }; + var assemblyBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndSave, dllfolder); + var moduleBuilder = assemblyBuilder.DefineDynamicModule("CommandLoaderModule", dllname + ".dll"); + + foreach (var command in GetCommands(repository)) + { + var typebuilder = moduleBuilder.DefineType("Command" + command.Index, + TypeAttributes.Class | TypeAttributes.Public, + typeof(CommandLoaderBase)); + + // add RegenerationAttribute to type + var regenerationConstrutorInfo = typeof(RegenerationAttribute).GetConstructor(new Type[] { typeof(RegenerationOption) }); + var regenerationAttributeBuilder = new CustomAttributeBuilder(regenerationConstrutorInfo, new object[] {RegenerationOption.Manual}); + typebuilder.SetCustomAttribute(regenerationAttributeBuilder); + + // add TransactionAttribute to type + var transactionConstructorInfo = typeof(TransactionAttribute).GetConstructor(new Type[] { typeof(TransactionMode) }); + var transactionAttributeBuilder = new CustomAttributeBuilder(transactionConstructorInfo, new object[] { TransactionMode.Manual }); + typebuilder.SetCustomAttribute(transactionAttributeBuilder); + + // call base constructor with script path + var ci = typeof(CommandLoaderBase).GetConstructor(new[] { typeof(string) }); + + var constructorBuilder = typebuilder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard, new Type[0]); + var gen = constructorBuilder.GetILGenerator(); + gen.Emit(OpCodes.Ldarg_0); // Load "this" onto eval stack + gen.Emit(OpCodes.Ldstr, command.Source); // Load the path to the command as a string onto stack + gen.Emit(OpCodes.Call, ci); // call base constructor (consumes "this" and the string) + gen.Emit(OpCodes.Nop); // Fill some space - this is how it is generated for equivalent C# code + gen.Emit(OpCodes.Nop); + gen.Emit(OpCodes.Nop); + gen.Emit(OpCodes.Ret); // return from constructor + typebuilder.CreateType(); + } + assemblyBuilder.Save(dllname + ".dll"); + } + + Result IExternalApplication.OnShutdown(UIControlledApplication application) + { + // FIXME: deallocate the python shell... + return Result.Succeeded; + } + + public static IRpsConfig GetConfig() + { + return new RpsConfig(GetSettingsFile()); + } + + /// + /// Returns a handle to the settings file. + /// + /// + public static XDocument GetSettings() + { + string settingsFile = GetSettingsFile(); + return XDocument.Load(settingsFile); + } + + private static string GetSettingsFile() + { + string folder = GetSettingsFolder(); + return Path.Combine(folder, "RevitPythonShell.xml"); + } + + /// + /// Returns the name of the folder with the settings file. This folder + /// is also the default folder for relative paths in StartupScript and InitScript tags. + /// + private static string GetSettingsFolder() + { + + return dllfolder; + } + + /// + /// Returns a list of commands as defined in the repository file. + /// + /// + public static IEnumerable GetCommands(XDocument repository) + { + int i = 0; + foreach (var commandNode in repository.Root.Descendants("Command") ?? new List()) + { + var addinAssembly = typeof(RpsExternalApplicationBase).Assembly; + var commandName = commandNode.Attribute("name").Value; + var commandSrc = commandNode.Attribute("src").Value; + var group = commandNode.Attribute("group") == null ? "" : commandNode.Attribute("group").Value; + + ImageSource largeImage = null; + if (IsValidPath(commandNode.Attribute("largeImage"))) + { + var largeImagePath = GetAbsolutePath(commandNode.Attribute("largeImage").Value); + largeImage = BitmapDecoder.Create(File.OpenRead(largeImagePath), BitmapCreateOptions.None, BitmapCacheOption.None).Frames[0]; + } + else + { + largeImage = GetEmbeddedPng(addinAssembly, "RpsRuntime.Resources.PythonScript32x32.png"); + } + + ImageSource smallImage = null; + if (IsValidPath(commandNode.Attribute("smallImage"))) + { + var smallImagePath = GetAbsolutePath(commandNode.Attribute("smallImage").Value); + smallImage = BitmapDecoder.Create(File.OpenRead(smallImagePath), BitmapCreateOptions.None, BitmapCacheOption.None).Frames[0]; + } + else + { + smallImage = GetEmbeddedPng(addinAssembly, "RpsRuntime.Resources.PythonScript16x16.png"); + } + + yield return new Command { + Name = commandName, + Source = commandSrc, + Group = group, + LargeImage = largeImage, + SmallImage = smallImage, + Index = i++ + }; + } + } + + /// + /// True, if the contents of the attribute is a valid absolute path (or relative path to the assembly) is + /// an existing path. + /// + private static bool IsValidPath(XAttribute pathAttribute) + { + if (pathAttribute != null && !string.IsNullOrEmpty(pathAttribute.Value)) + { + return File.Exists(GetAbsolutePath(pathAttribute.Value)); + } + return false; + } + + /// + /// Return an absolute path for input path, with relative paths seen as + /// relative to the assembly location. No guarantees are made as to + /// wether the path exists or not. + /// + private static string GetAbsolutePath(string path) + { + if (Path.IsPathRooted(path)) + { + return path; + } + else + { + var assembly = typeof(App).Assembly; + return Path.Combine(Path.GetDirectoryName(assembly.Location), path); + } + } + + /// + /// Returns a string to be executed, whenever the interactive shell is started. + /// If this is not specified in the XML file (under /RevitPythonShell/InitScript), + /// then null is returned. + /// + public static string GetInitScript() + { + var path = GetInitScriptPath(); + if (File.Exists(path)) + { + using (var reader = File.OpenText(path)) + { + var source = reader.ReadToEnd(); + return source; + } + } + + // backwards compatibility: InitScript used to have a CDATA section directly + // embedded in the settings xml file + var initScriptTags = GetSettings().Root.Descendants("InitScript") ?? new List(); + if (initScriptTags.Count() == 0) + { + return null; + } + var firstScript = initScriptTags.First(); + // backwards compatibility: InitScript used to be included as CDATA in the config file + return firstScript.Value.Trim(); + } + + /// + /// Returns the path to the InitScript as configured in the settings file or "" if not + /// configured. This is used in the ConfigureCommandsForm. + /// + public static string GetInitScriptPath() + { + return GetScriptPath("InitScript"); + } + + + /// + /// Returns the path to the StartupScript as configured in the settings file or "" if not + /// configured. This is used in the ConfigureCommandsForm. + /// + public static string GetStartupScriptPath() + { + return GetScriptPath("StartupScript"); + } + + /// + /// Returns the value of the "src" attribute for the tag "tagName" in the settings file + /// or "" if not configured. + /// + private static string GetScriptPath(string tagName) + { + var tags = GetSettings().Root.Descendants(tagName) ?? new List(); + if (tags.Count() == 0) + { + return ""; + } + var firstScript = tags.First(); + if (firstScript.Attribute("src") != null) + { + var path = firstScript.Attribute("src").Value; + if (Path.IsPathRooted(path)) + { + return path; + } + else + { + return Path.Combine(GetSettingsFolder(), path); + } + } + else + { + return ""; + } + } + + /// + /// Returns a string to be executed, whenever the revit is started. + /// If this is not specified as a path to an existing file in the XML file (under /RevitPythonShell/StartupScript/@src), + /// then null is returned. + /// + public static string GetStartupScript() + { + var path = GetStartupScriptPath(); + if (File.Exists(path)) + { + using (var reader = File.OpenText(path)) + { + var source = reader.ReadToEnd(); + return source; + } + } + // no startup script found + return null; + } + + /// + /// Writes settings to the settings file, replacing the old commands. + /// + public static void WriteSettings( + IEnumerable commands, + IEnumerable searchPaths, + IEnumerable> variables, + string initScript, + string startupScript) + { + var doc = GetSettings(); + var settingsFolder = GetSettingsFolder(); + + // clean out current stuff + foreach (var xmlExistingCommands in (doc.Root.Descendants("Commands") ?? new List()).ToList()) + { + xmlExistingCommands.Remove(); + } + foreach (var xmlExistingSearchPaths in doc.Root.Descendants("SearchPaths").ToList()) + { + xmlExistingSearchPaths.Remove(); + } + foreach (var xmlExistingVariables in doc.Root.Descendants("Variables").ToList()) + { + xmlExistingVariables.Remove(); + } + foreach (var xmlExistingInitScript in doc.Root.Descendants("InitScript").ToList()) + { + xmlExistingInitScript.Remove(); + } + foreach (var xmlExistingStartupScript in doc.Root.Descendants("StartupScript").ToList()) + { + xmlExistingStartupScript.Remove(); + } + + // add commnads + var xmlCommands = new XElement("Commands"); + foreach (var command in commands) + { + xmlCommands.Add(new XElement( + "Command", + new XAttribute("name", command.Name), + new XAttribute("src", command.Source), + new XAttribute("group", command.Group))); + + } + doc.Root.Add(xmlCommands); + + // add search paths + var xmlSearchPaths = new XElement("SearchPaths"); + foreach (var path in searchPaths) + { + xmlSearchPaths.Add(new XElement( + "SearchPath", + new XAttribute("name", path))); + + } + // ensure settings directory is added to the search paths + if (!searchPaths.Contains(settingsFolder)) { + xmlSearchPaths.Add(new XElement( + "SearchPath", + new XAttribute("name", settingsFolder))); + + } + doc.Root.Add(xmlSearchPaths); + + // add variables + var xmlVariables = new XElement("Variables"); + foreach (var variable in variables) + { + xmlVariables.Add(new XElement( + "StringVariable", + new XAttribute("name", variable.Key), + new XAttribute("value", variable.Value))); + + } + doc.Root.Add(xmlVariables); + + // add init script + var xmlInitScript = new XElement("InitScript"); + xmlInitScript.Add(new XAttribute("src", initScript)); + doc.Root.Add(xmlInitScript); + + // add startup script + var xmlStartupScript = new XElement("StartupScript"); + xmlStartupScript.Add(new XAttribute("src", startupScript)); + doc.Root.Add(xmlStartupScript); + + doc.Save(GetSettingsFile()); + } + } + + /// + /// A simple structure to hold information about canned commands. + /// + internal class Command + { + public string Name; + public string Group; + public string Source; + public int Index; + public ImageSource LargeImage; + public ImageSource SmallImage; + + public override string ToString() + { + return Name; + } + } +} diff --git a/RevitPythonShell/Helpers/ProcessManager.cs b/RevitPythonShell/Helpers/ProcessManager.cs new file mode 100644 index 0000000..350db19 --- /dev/null +++ b/RevitPythonShell/Helpers/ProcessManager.cs @@ -0,0 +1,53 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Windows; +using System.Windows.Interop; + +namespace RevitPythonShell.Helpers +{ + public static class ProcessManager + { + + [DllImport("user32.dll")] + [return: MarshalAs(UnmanagedType.Bool)] + private static extern bool SetForegroundWindow(IntPtr hWnd); + + public static void SetRevitAsWindowOwner(this Window window) + { + if (null == window) { return; } + window.WindowStartupLocation = WindowStartupLocation.CenterScreen; + WindowInteropHelper helper = new WindowInteropHelper(window); + helper.Owner = GetActivateWindow(); + window.Closing += SetActivateWindow; + } + + private static void SetActivateWindow(object sender, CancelEventArgs e) + { + SetActivateWindow(); + } + + /// + /// Set process revert use revit + /// + /// + public static void SetActivateWindow() + { + IntPtr ptr = GetActivateWindow(); + if (ptr != IntPtr.Zero) + { + SetForegroundWindow(ptr); + } + } + + /// + /// return active windows is active + /// + /// + public static IntPtr GetActivateWindow() + { + return Process.GetCurrentProcess().MainWindowHandle; + } + } +} \ No newline at end of file diff --git a/RevitPythonShell/Properties/launchSettings.json b/RevitPythonShell/Properties/launchSettings.json index 977df04..1325e5c 100644 --- a/RevitPythonShell/Properties/launchSettings.json +++ b/RevitPythonShell/Properties/launchSettings.json @@ -43,6 +43,14 @@ "Revit 2021": { "commandName": "Executable", "executablePath": "%ProgramW6432%\\Autodesk\\Revit 2021\\Revit.exe" + } , + "Revit 2022": { + "commandName": "Executable", + "executablePath": "%ProgramW6432%\\Autodesk\\Revit 2022\\Revit.exe" + } , + "Revit 2023": { + "commandName": "Executable", + "executablePath": "%ProgramW6432%\\Autodesk\\Revit 2023\\Revit.exe" } } } \ No newline at end of file diff --git a/RevitPythonShell/CommandLoaderBase.cs b/RevitPythonShell/RevitCommands/CommandLoaderBase.cs similarity index 89% rename from RevitPythonShell/CommandLoaderBase.cs rename to RevitPythonShell/RevitCommands/CommandLoaderBase.cs index fd993ab..1efc199 100644 --- a/RevitPythonShell/CommandLoaderBase.cs +++ b/RevitPythonShell/RevitCommands/CommandLoaderBase.cs @@ -1,65 +1,63 @@ -using System; -using System.IO; -using Autodesk.Revit; -using Autodesk.Revit.UI; -using Autodesk.Revit.DB; -using Autodesk.Revit.Attributes; -using RevitPythonShell.RpsRuntime; - -namespace RevitPythonShell -{ - /// - /// Starts up a ScriptOutput window for a given canned command. - /// - /// It is expected that this will be inherited by dynamic types that have the field - /// _scriptSource set to point to a python file that will be executed in the constructor. - /// - [Regeneration(RegenerationOption.Manual)] - [Transaction(TransactionMode.Manual)] - public abstract class CommandLoaderBase : IExternalCommand - { - protected string _scriptSource = ""; - - public CommandLoaderBase(string scriptSource) - { - _scriptSource = scriptSource; - } - - /// - /// Overload this method to implement an external command within Revit. - /// - /// - /// The result indicates if the execution fails, succeeds, or was canceled by user. If it does not - /// succeed, Revit will undo any changes made by the external command. - /// - /// An ExternalCommandData object which contains reference to Application and View - /// needed by external command.Error message can be returned by external command. This will be displayed only if the command status - /// was "Failed". There is a limit of 1023 characters for this message; strings longer than this will be truncated.Element set indicating problem elements to display in the failure dialog. This will be used - /// only if the command status was "Failed". - public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) - { - // FIXME: somehow fetch back message after script execution... - var executor = new ScriptExecutor(RevitPythonShellApplication.GetConfig(), commandData, message, elements); - - string source; - using (var reader = File.OpenText(_scriptSource)) - { - source = reader.ReadToEnd(); - } - - var result = executor.ExecuteScript(source, _scriptSource); - message = executor.Message; - switch (result) - { - case (int)Result.Succeeded: - return Result.Succeeded; - case (int)Result.Cancelled: - return Result.Cancelled; - case (int)Result.Failed: - return Result.Failed; - default: - return Result.Succeeded; - } - } - } -} +using System.IO; +using Autodesk.Revit.Attributes; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using RpsRuntime; + +namespace RevitPythonShell.RevitCommands +{ + /// + /// Starts up a ScriptOutput window for a given canned command. + /// + /// It is expected that this will be inherited by dynamic types that have the field + /// _scriptSource set to point to a python file that will be executed in the constructor. + /// + [Regeneration(RegenerationOption.Manual)] + [Transaction(TransactionMode.Manual)] + public abstract class CommandLoaderBase : IExternalCommand + { + protected string _scriptSource = ""; + + public CommandLoaderBase(string scriptSource) + { + _scriptSource = scriptSource; + } + + /// + /// Overload this method to implement an external command within Revit. + /// + /// + /// The result indicates if the execution fails, succeeds, or was canceled by user. If it does not + /// succeed, Revit will undo any changes made by the external command. + /// + /// An ExternalCommandData object which contains reference to Application and View + /// needed by external command.Error message can be returned by external command. This will be displayed only if the command status + /// was "Failed". There is a limit of 1023 characters for this message; strings longer than this will be truncated.Element set indicating problem elements to display in the failure dialog. This will be used + /// only if the command status was "Failed". + public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) + { + // FIXME: somehow fetch back message after script execution... + var executor = new ScriptExecutor(App.GetConfig(), commandData, message, elements); + + string source; + using (var reader = File.OpenText(_scriptSource)) + { + source = reader.ReadToEnd(); + } + + var result = executor.ExecuteScript(source, _scriptSource); + message = executor.Message; + switch (result) + { + case (int)Result.Succeeded: + return Result.Succeeded; + case (int)Result.Cancelled: + return Result.Cancelled; + case (int)Result.Failed: + return Result.Failed; + default: + return Result.Succeeded; + } + } + } +} diff --git a/RevitPythonShell/RevitCommands/ConfigureCommand.cs b/RevitPythonShell/RevitCommands/ConfigureCommand.cs index 15eb015..5ebb07b 100644 --- a/RevitPythonShell/RevitCommands/ConfigureCommand.cs +++ b/RevitPythonShell/RevitCommands/ConfigureCommand.cs @@ -1,14 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autodesk.Revit; -using Autodesk.Revit.UI; -using Autodesk.Revit.DB; +using System.Windows.Forms; using Autodesk.Revit.Attributes; -using System.Windows.Forms; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; +using RevitPythonShell.Views; -namespace RevitPythonShell +namespace RevitPythonShell.RevitCommands { /// /// Open the configuration dialog. @@ -20,6 +16,7 @@ class ConfigureCommand: IExternalCommand public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements) { var dialog = new ConfigureCommandsForm(); + dialog.StartPosition = FormStartPosition.CenterScreen; dialog.ShowDialog(); MessageBox.Show("Restart Revit to see changes to the commands in the Ribbon", "Configure RevitPythonShell", MessageBoxButtons.OK, MessageBoxIcon.Information); diff --git a/RevitPythonShell/RevitCommands/DeployRpsAddinCommand.cs b/RevitPythonShell/RevitCommands/DeployRpsAddinCommand.cs index 35afa71..77d7c27 100644 --- a/RevitPythonShell/RevitCommands/DeployRpsAddinCommand.cs +++ b/RevitPythonShell/RevitCommands/DeployRpsAddinCommand.cs @@ -1,18 +1,16 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autodesk.Revit.UI; using System.IO; +using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Windows.Forms; using System.Xml.Linq; using Autodesk.Revit.Attributes; -using System.Windows.Forms; -using RevitPythonShell.RpsRuntime; -using System.Security.AccessControl; +using Autodesk.Revit.UI; +using RpsRuntime; -namespace RevitPythonShell +namespace RevitPythonShell.RevitCommands { /// /// Ask the user for an RpsAddin xml file. Create a subfolder diff --git a/RevitPythonShell/RevitCommands/IronPythonConsoleCommand.cs b/RevitPythonShell/RevitCommands/IronPythonConsoleCommand.cs index 6995f9a..4a2fff0 100644 --- a/RevitPythonShell/RevitCommands/IronPythonConsoleCommand.cs +++ b/RevitPythonShell/RevitCommands/IronPythonConsoleCommand.cs @@ -1,17 +1,16 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Threading; +using System.Windows; +using System.Windows.Threading; using Autodesk.Revit.Attributes; -using Autodesk.Revit.UI; using Autodesk.Revit.DB; -using System.Diagnostics; +using Autodesk.Revit.UI; using Microsoft.Scripting; -using System.Threading; -using System.Windows.Threading; -using RevitPythonShell.RpsRuntime; +using RevitPythonShell.Helpers; +using RevitPythonShell.Views; +using RpsRuntime; -namespace RevitPythonShell +namespace RevitPythonShell.RevitCommands { /// /// Start an interactive shell in a modal window. @@ -32,13 +31,13 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme { // now that the console is created and initialized, the script scope should // be accessible... - new ScriptExecutor(RevitPythonShellApplication.GetConfig(), commandData, messageCopy, elements) + new ScriptExecutor(App.GetConfig(), commandData, messageCopy, elements) .SetupEnvironment(host.Engine, host.Console.ScriptScope); host.Console.ScriptScope.SetVariable("__window__", gui); // run the initscript - var initScript = RevitPythonShellApplication.GetInitScript(); + var initScript = App.GetInitScript(); if (initScript != null) { var scriptSource = host.Engine.CreateScriptSourceFromString(initScript, SourceCodeKind.Statements); @@ -78,6 +77,8 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme } }); }); + gui.WindowStartupLocation = WindowStartupLocation.CenterScreen; + gui.SetRevitAsWindowOwner(); gui.ShowDialog(); return Result.Succeeded; } diff --git a/RevitPythonShell/RevitCommands/NonModalConsoleCommand.cs b/RevitPythonShell/RevitCommands/NonModalConsoleCommand.cs index 6f8b3ea..26e682c 100644 --- a/RevitPythonShell/RevitCommands/NonModalConsoleCommand.cs +++ b/RevitPythonShell/RevitCommands/NonModalConsoleCommand.cs @@ -1,20 +1,18 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Diagnostics; +using System.Threading; +using System.Windows; using Autodesk.Revit.Attributes; -using Autodesk.Revit.UI; using Autodesk.Revit.DB; -using System.Diagnostics; +using Autodesk.Revit.UI; using Microsoft.Scripting; -using System.Threading; -using System.Windows.Threading; -using RevitPythonShell.RpsRuntime; -using System.Threading.Tasks; -using IronPython.Runtime; using Microsoft.Scripting.Hosting; +using RevitPythonShell.Helpers; +using RevitPythonShell.Views; +using RpsRuntime; -namespace RevitPythonShell +namespace RevitPythonShell.RevitCommands { /// /// An object of this class is instantiated every time the user clicks on the @@ -37,13 +35,13 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme { // now that the console is created and initialized, the script scope should // be accessible... - new ScriptExecutor(RevitPythonShellApplication.GetConfig(), commandData, messageCopy, elements) + new ScriptExecutor(App.GetConfig(), commandData, messageCopy, elements) .SetupEnvironment(host.Engine, host.Console.ScriptScope); host.Console.ScriptScope.SetVariable("__window__", gui); // run the initscript - var initScript = RevitPythonShellApplication.GetInitScript(); + var initScript = App.GetInitScript(); if (initScript != null) { var scriptSource = host.Engine.CreateScriptSourceFromString(initScript, SourceCodeKind.Statements); @@ -71,8 +69,9 @@ public Result Execute(ExternalCommandData commandData, ref string message, Eleme commandCompletedEvent.WaitOne(); }); }); - gui.Topmost = true; gui.Title = gui.Title.Replace("RevitPythonShell", "RevitPythonShell (non-modal)"); + gui.WindowStartupLocation = WindowStartupLocation.CenterScreen; + gui.SetRevitAsWindowOwner(); gui.Show(); return Result.Succeeded; } diff --git a/RevitPythonShell/RevitPythonShell.addin b/RevitPythonShell/RevitPythonShell.addin new file mode 100644 index 0000000..30e196d --- /dev/null +++ b/RevitPythonShell/RevitPythonShell.addin @@ -0,0 +1,11 @@ + + + + RevitPythonShell.App + 3a7a1d24-51ed-462b-949f-1ddcca12008d + RevitPythonShell + RevitPythonShell/RevitPythonShell.dll + 3a7a1d24-51ed-462b-949f-1ddcca12008d + RIPS, RIPS, + + diff --git a/RevitPythonShell/RevitPythonShell.csproj b/RevitPythonShell/RevitPythonShell.csproj index 9e23b1d..9773421 100644 --- a/RevitPythonShell/RevitPythonShell.csproj +++ b/RevitPythonShell/RevitPythonShell.csproj @@ -1,132 +1,65 @@  - - Debug;Debug One;Release - - - - - net40;net45;net451;net452;net46;net47;net471;net48 + Debug;Release win true true + net48 + latest + x64 + 3a7a1d24-51ed-462b-949f-1ddcca12008d + RevitPythonShell + RIPS + Revit Python Shell addin for Revit + false + Debug R22;Debug R23 + $(Configurations);Release R18;Release R19;Release R20;Release R21;Release R22;Release R23 - - - - net48 - - - - 2014 - - - 2015 - - - 2016 + + true + full + $(DefineConstants);DEBUG - - 2017 + + true + none + $(DefineConstants);RELEASE - + 2018 + $(DefineConstants);R18 - + 2019 + $(DefineConstants);R19 - + 2020 + $(DefineConstants);R20 - + 2021 + $(DefineConstants);R21 - + 2022 + $(DefineConstants);R22 + + + 2023 + $(DefineConstants);R23 - - x64 - x64 - None - - - {351668CC-8477-4fbf-BFE3-5F1006E4DB1F} - - - false - false - - - REVIT$(RevitVersion);WINFORMS - $(DefineConstants) - - - false - - + $(RevitVersion) + true false - .\bin\$(Configuration)\$(RevitVersion) - 3a7a1d24-51ed-462b-949f-1ddcca12008d - RevitPythonShell - RIPS - Revit Python Shell addin for Revit - - - - TRACE;REVIT2020;WINFORMS;DEBUG - full - .\bin\$(Configuration)\$(RevitVersion) - - - - false + The RevitPythonShell adds an IronPython interpreter to Autodesk Revit and Vasari. + true + true - - - - - - - - - - - - - - - - - - - - - - - ..\RequiredLibraries\IronPython.dll @@ -144,7 +77,6 @@ ..\RequiredLibraries\Microsoft.Scripting.Metadata.dll - @@ -163,7 +95,8 @@ - + + @@ -184,7 +117,7 @@ True Resources.resx - + @@ -196,6 +129,7 @@ + MSDataSetGenerator AddinTemplate.Designer.cs @@ -215,14 +149,10 @@ - - PreserveNewest - + - - PreserveNewest - + @@ -235,10 +165,9 @@ - + Designer - PreserveNewest - + @@ -259,32 +188,6 @@ all - - - - - - - - - - - - - - - - $(SolutionName)\bin\$(Configuration)\$(RevitVersion)\ - - - - - - - - - - @@ -297,14 +200,11 @@ - - - + ]]> - @@ -316,28 +216,29 @@ - - - + ]]> - - - - + + - + + + - - + + bin\AddIn $(RevitVersion) $(Configuration)\ + $(RootDir)$(AssemblyName)\ + - - - - - - + + + + + + + \ No newline at end of file diff --git a/RevitPythonShell/Views/CompletionToolTip.cs b/RevitPythonShell/Views/CompletionToolTip.cs index d62ddee..acdd31c 100644 --- a/RevitPythonShell/Views/CompletionToolTip.cs +++ b/RevitPythonShell/Views/CompletionToolTip.cs @@ -1,11 +1,10 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; -using System.Text; using System.Windows.Forms; -using System.Drawing; -namespace RevitPythonShell +namespace RevitPythonShell.Views { /// /// Display a listbox with a list of completions for a given string. diff --git a/RevitPythonShell/Views/ConfigureCommandsForm.Designer.cs b/RevitPythonShell/Views/ConfigureCommandsForm.Designer.cs index ba70253..01865e2 100644 --- a/RevitPythonShell/Views/ConfigureCommandsForm.Designer.cs +++ b/RevitPythonShell/Views/ConfigureCommandsForm.Designer.cs @@ -1,4 +1,4 @@ -namespace RevitPythonShell +namespace RevitPythonShell.Views { partial class ConfigureCommandsForm { diff --git a/RevitPythonShell/Views/ConfigureCommandsForm.cs b/RevitPythonShell/Views/ConfigureCommandsForm.cs index 65f0319..97188ec 100644 --- a/RevitPythonShell/Views/ConfigureCommandsForm.cs +++ b/RevitPythonShell/Views/ConfigureCommandsForm.cs @@ -1,14 +1,10 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Data; -using System.Drawing; +using System.IO; using System.Linq; -using System.Text; using System.Windows.Forms; -using System.IO; -namespace RevitPythonShell +namespace RevitPythonShell.Views { public partial class ConfigureCommandsForm : Form { @@ -35,21 +31,21 @@ private void btnCancel_Click(object sender, EventArgs e) /// private void ConfigureCommandsForm_Load(object sender, EventArgs e) { - _commands = RevitPythonShellApplication.GetCommands( - RevitPythonShellApplication.GetSettings()).ToList(); + _commands = App.GetCommands( + App.GetSettings()).ToList(); lstCommands.DataSource = _commands; - _searchPaths = RevitPythonShellApplication.GetConfig().GetSearchPaths().ToList(); + _searchPaths = App.GetConfig().GetSearchPaths().ToList(); lstSearchPaths.DataSource = _searchPaths; - _variables = RevitPythonShellApplication.GetConfig().GetVariables().AsEnumerable().ToList(); + _variables = App.GetConfig().GetVariables().AsEnumerable().ToList(); lstVariables.DataSource = _variables; lstVariables.DisplayMember = "Key"; - string initScriptPath = RevitPythonShellApplication.GetInitScriptPath(); + string initScriptPath = App.GetInitScriptPath(); txtInitScript.Text = initScriptPath; - string startupScriptPath = RevitPythonShellApplication.GetStartupScriptPath(); + string startupScriptPath = App.GetStartupScriptPath(); txtStartupScript.Text = startupScriptPath; } @@ -219,7 +215,7 @@ private void btnCommandMoveDown_Click(object sender, EventArgs e) /// private void btnCommandSave_Click(object sender, EventArgs e) { - RevitPythonShellApplication.WriteSettings(_commands, _searchPaths, _variables, txtInitScript.Text, txtStartupScript.Text); + App.WriteSettings(_commands, _searchPaths, _variables, txtInitScript.Text, txtStartupScript.Text); Close(); } diff --git a/RevitPythonShell/Views/ConsoleOptions.cs b/RevitPythonShell/Views/ConsoleOptions.cs index 52f7741..425fdeb 100644 --- a/RevitPythonShell/Views/ConsoleOptions.cs +++ b/RevitPythonShell/Views/ConsoleOptions.cs @@ -1,15 +1,11 @@ // Copyright (c) 2010 Joe Moorhouse -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.ComponentModel; -using ICSharpCode.AvalonEdit; using System.Windows.Media; +using ICSharpCode.AvalonEdit; using PythonConsoleControl; -namespace RevitPythonShell +namespace RevitPythonShell.Views { public class ConsoleOptions { diff --git a/RevitPythonShell/Views/IronPythonConsole.xaml b/RevitPythonShell/Views/IronPythonConsole.xaml index fdbecfc..4cbf120 100644 --- a/RevitPythonShell/Views/IronPythonConsole.xaml +++ b/RevitPythonShell/Views/IronPythonConsole.xaml @@ -1,29 +1,36 @@ - - - - - + + + - - + + - - + + - - + + - + - + - - - + + + - - + + - - + + - + # IronPython Pad. Write code snippets here and F5 to run. + FontFamily="Consolas" + FontSize="10pt" + GotFocus="textEditor_GotFocus" + Name="textEditor"> + # IronPython Pad. Write code snippets here and F5 to run. - + diff --git a/RevitPythonShell/Views/IronPythonConsole.xaml.cs b/RevitPythonShell/Views/IronPythonConsole.xaml.cs index f9023b7..c20b276 100644 --- a/RevitPythonShell/Views/IronPythonConsole.xaml.cs +++ b/RevitPythonShell/Views/IronPythonConsole.xaml.cs @@ -1,14 +1,14 @@ -using ICSharpCode.AvalonEdit; -using ICSharpCode.AvalonEdit.Highlighting; -using Microsoft.Win32; -using System; +using System; using System.IO; using System.Reflection; using System.Windows; using System.Windows.Input; using System.Xml; +using ICSharpCode.AvalonEdit; +using ICSharpCode.AvalonEdit.Highlighting; +using Microsoft.Win32; -namespace RevitPythonShell +namespace RevitPythonShell.Views { /// /// Interaction logic for IronPythonConsole.xaml diff --git a/RpsRuntime/ExternalCommandAssemblyBuilder.cs b/RpsRuntime/ExternalCommandAssemblyBuilder.cs index 8a0e852..45689b2 100644 --- a/RpsRuntime/ExternalCommandAssemblyBuilder.cs +++ b/RpsRuntime/ExternalCommandAssemblyBuilder.cs @@ -1,14 +1,11 @@ -using Autodesk.Revit.Attributes; -using System; +using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Text; -using System.Xml.Linq; +using Autodesk.Revit.Attributes; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// The ExternalCommandAssemblyBuilder creates an assembly (.net dll) for diff --git a/RpsRuntime/IRpsConfig.cs b/RpsRuntime/IRpsConfig.cs index 4d5fb4c..9a2f385 100644 --- a/RpsRuntime/IRpsConfig.cs +++ b/RpsRuntime/IRpsConfig.cs @@ -1,9 +1,6 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { public interface IRpsConfig { diff --git a/RpsRuntime/RpsConfig.cs b/RpsRuntime/RpsConfig.cs index fa58f82..eb4076a 100644 --- a/RpsRuntime/RpsConfig.cs +++ b/RpsRuntime/RpsConfig.cs @@ -1,10 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +using System.Collections.Generic; using System.Xml.Linq; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// Provides access functions to those parts of the RevitPythonShell.xml file diff --git a/RpsRuntime/RpsExternalApplicationBase.cs b/RpsRuntime/RpsExternalApplicationBase.cs index 54aaf9c..25aa72b 100644 --- a/RpsRuntime/RpsExternalApplicationBase.cs +++ b/RpsRuntime/RpsExternalApplicationBase.cs @@ -1,15 +1,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; -using System.Text; -using Autodesk.Revit.UI; using System.Reflection; -using System.IO; -using System.Xml.Linq; -using System.Windows.Media.Imaging; using System.Windows.Media; +using System.Windows.Media.Imaging; +using System.Xml.Linq; +using Autodesk.Revit.UI; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// A base class for RpsAddins to inherit from. diff --git a/RpsRuntime/RpsExternalCommandBase.cs b/RpsRuntime/RpsExternalCommandBase.cs index 40d7c8d..5578797 100644 --- a/RpsRuntime/RpsExternalCommandBase.cs +++ b/RpsRuntime/RpsExternalCommandBase.cs @@ -1,12 +1,9 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autodesk.Revit.UI; using System.IO; using System.Xml.Linq; +using Autodesk.Revit.UI; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// An abstract base class for ExternalCommand instances created by the DeployRpsAddin projects. diff --git a/RpsRuntime/RpsExternalCommandScriptBase.cs b/RpsRuntime/RpsExternalCommandScriptBase.cs index 90507eb..88c9ea5 100644 --- a/RpsRuntime/RpsExternalCommandScriptBase.cs +++ b/RpsRuntime/RpsExternalCommandScriptBase.cs @@ -1,14 +1,11 @@ -using Autodesk.Revit.Attributes; -using Autodesk.Revit.DB; -using Autodesk.Revit.UI; -using System; -using System.Collections.Generic; +using System; using System.IO; -using System.Linq; using System.Xml.Linq; -using System.Text; +using Autodesk.Revit.Attributes; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// This class is very much like the RpsExternalCommandBase, but instead of looking for the diff --git a/RpsRuntime/RpsRuntime.csproj b/RpsRuntime/RpsRuntime.csproj index 119b850..40ea59f 100644 --- a/RpsRuntime/RpsRuntime.csproj +++ b/RpsRuntime/RpsRuntime.csproj @@ -1,203 +1,132 @@  - - - Debug;Debug One;Release - - - - - net40;net45;net451;net452;net46;net47;net471;net48 - win - - - - - net48 - - - - 2014 - - - 2015 - - - 2016 - - - 2017 - - - 2018 - - - 2019 - - - 2020 - - - 2021 - - - 2022 - - - - - x64 - x64 - None - - - {351668CC-8477-4fbf-BFE3-5F1006E4DB1F} - - - false - false - - - REVIT$(RevitVersion);WINFORMS - $(DefineConstants) - - - false - - - false - ..\bin\$(Configuration)\$(RevitVersion) - - - - $(DefineConstants);DEBUG - full - - - - - - - - - - - - - - - - - - - - - - - - - - - + + Debug;Release + net48 + latest + false + false + x64 + Debug R22;Debug R23 + $(Configurations);Release R18;Release R19;Release R20;Release R21;Release R22;Release R23 + + + true + full + $(DefineConstants);DEBUG + + + true + none + $(DefineConstants);RELEASE + + + 2018 + $(DefineConstants);R18 + + + 2019 + $(DefineConstants);R19 + + + 2020 + $(DefineConstants);R20 + + + 2021 + $(DefineConstants);R21 + + + 2022 + $(DefineConstants);R22 + + + 2023 + $(DefineConstants);R23 + + + $(RevitVersion) + true + false + A Project Support for developer in revit + true + true + - - + + + + + + + + + + + + ..\RequiredLibraries\IronPython.dll + + + ..\RequiredLibraries\IronPython.Modules.dll + + + ..\RequiredLibraries\Microsoft.Dynamic.dll + + + ..\RequiredLibraries\Microsoft.Scripting.dll + + + ..\RequiredLibraries\Microsoft.Scripting.Metadata.dll + + + ..\RequiredLibraries\Microsoft.CSharp.dll + + + + + + + + + + + + + + + + + + + + + + + + + Form + + + ScriptOutput.cs + + + + + + + ScriptOutput.cs + Designer + + + + + + + + + + + + + - - - - - ..\RequiredLibraries\IronPython.dll - - - ..\RequiredLibraries\IronPython.Modules.dll - - - ..\RequiredLibraries\Microsoft.Dynamic.dll - - - ..\RequiredLibraries\Microsoft.Scripting.dll - - - ..\RequiredLibraries\Microsoft.Scripting.Metadata.dll - - - ..\RequiredLibraries\Microsoft.CSharp.dll - - - - - - - - - - - - - - - - - - - - - - - - - - Form - - - ScriptOutput.cs - - - - - - - ScriptOutput.cs - Designer - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/RpsRuntime/ScriptExecutor.cs b/RpsRuntime/ScriptExecutor.cs index ca6b766..d39a76c 100644 --- a/RpsRuntime/ScriptExecutor.cs +++ b/RpsRuntime/ScriptExecutor.cs @@ -1,15 +1,14 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; -using Autodesk.Revit; +using Autodesk.Revit.DB; +using Autodesk.Revit.UI; using IronPython.Runtime.Exceptions; using Microsoft.Scripting; using Microsoft.Scripting.Hosting; -using Autodesk.Revit.UI; -using Autodesk.Revit.DB; -using System.Collections.Generic; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// Executes a script scripts @@ -181,7 +180,7 @@ public void SetupEnvironment(ScriptEngine engine, ScriptScope scope) engine.Runtime.LoadAssembly(typeof(Autodesk.Revit.UI.TaskDialog).Assembly); // also, allow access to the RPS internals - engine.Runtime.LoadAssembly(typeof(RevitPythonShell.RpsRuntime.ScriptExecutor).Assembly); + engine.Runtime.LoadAssembly(typeof(ScriptExecutor).Assembly); } /// diff --git a/RpsRuntime/ScriptOutput.Designer.cs b/RpsRuntime/ScriptOutput.Designer.cs index 7dab383..01195cf 100644 --- a/RpsRuntime/ScriptOutput.Designer.cs +++ b/RpsRuntime/ScriptOutput.Designer.cs @@ -1,4 +1,4 @@ -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { partial class ScriptOutput { diff --git a/RpsRuntime/ScriptOutput.cs b/RpsRuntime/ScriptOutput.cs index 777362e..08f1a7a 100644 --- a/RpsRuntime/ScriptOutput.cs +++ b/RpsRuntime/ScriptOutput.cs @@ -1,7 +1,6 @@ -using System; -using System.Windows.Forms; +using System.Windows.Forms; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { public partial class ScriptOutput : Form { diff --git a/RpsRuntime/ScriptOutputStream.cs b/RpsRuntime/ScriptOutputStream.cs index 6cb5d83..7705723 100644 --- a/RpsRuntime/ScriptOutputStream.cs +++ b/RpsRuntime/ScriptOutputStream.cs @@ -5,10 +5,9 @@ using System.Text; using System.Threading; using System.Windows.Forms; -using IronPython.Runtime.Exceptions; using Microsoft.Scripting.Hosting; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// A stream to write output to... diff --git a/RpsRuntime/SettingsDictionary.cs b/RpsRuntime/SettingsDictionary.cs index 6d5b6ac..40deca8 100644 --- a/RpsRuntime/SettingsDictionary.cs +++ b/RpsRuntime/SettingsDictionary.cs @@ -1,10 +1,8 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; -using System.Text; using System.Xml.Linq; -namespace RevitPythonShell.RpsRuntime +namespace RpsRuntime { /// /// A subclass of Dictionary, that writes changes back to a settings xml file. diff --git a/build/.editorconfig b/build/.editorconfig new file mode 100644 index 0000000..133dbc8 --- /dev/null +++ b/build/.editorconfig @@ -0,0 +1,15 @@ +# noinspection EditorConfigKeyCorrectness +[*.cs] +dotnet_style_qualification_for_field = false:warning +dotnet_style_qualification_for_property = false:warning +dotnet_style_qualification_for_method = false:warning +dotnet_style_qualification_for_event = false:warning +dotnet_style_require_accessibility_modifiers = never:warning + +csharp_style_expression_bodied_methods = true:silent +csharp_style_expression_bodied_properties = true:warning +csharp_style_expression_bodied_indexers = true:warning +csharp_style_expression_bodied_accessors = true:warning + +resharper_check_namespace_highlighting = none +resharper_class_never_instantiated_global_highlighting = none diff --git a/build/Build.Clean.cs b/build/Build.Clean.cs new file mode 100644 index 0000000..cc881ae --- /dev/null +++ b/build/Build.Clean.cs @@ -0,0 +1,21 @@ +using Nuke.Common; +using Nuke.Common.IO; +using Nuke.Common.Utilities.Collections; +using static Nuke.Common.IO.FileSystemTasks; + +internal partial class Build +{ + private Target Cleaning => _ => _ + .Executes(() => + { + EnsureCleanDirectory(ArtifactsDirectory); + + if (IsServerBuild) return; + foreach (var projectName in Projects) + { + var project = BuilderExtensions.GetProject(Solution, projectName); + var binDirectory = (AbsolutePath)new DirectoryInfo(project.GetBinDirectory()).FullName; + binDirectory.GlobDirectories($"{AddInBinPrefix}*", "Release*").ForEach(DeleteDirectory); + } + }); +} \ No newline at end of file diff --git a/build/Build.Compile.cs b/build/Build.Compile.cs new file mode 100644 index 0000000..38b297e --- /dev/null +++ b/build/Build.Compile.cs @@ -0,0 +1,24 @@ +using Nuke.Common; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.MSBuild; +using static Nuke.Common.Tools.MSBuild.MSBuildTasks; + +internal partial class Build +{ + private Target Compile => _ => _ + .TriggeredBy(Cleaning) + .Executes(() => + { + var configurations = GetConfigurations(BuildConfiguration, InstallerConfiguration); + configurations.ForEach(configuration => + { + MSBuild(s => s + .SetTargets("Rebuild") + .SetProcessToolPath(MsBuildPath.Value) + .SetConfiguration(configuration) + .SetVerbosity(MSBuildVerbosity.Minimal) + .DisableNodeReuse() + .EnableRestore()); + }); + }); +} \ No newline at end of file diff --git a/build/Build.GitHubRelease.cs b/build/Build.GitHubRelease.cs new file mode 100644 index 0000000..34a13c0 --- /dev/null +++ b/build/Build.GitHubRelease.cs @@ -0,0 +1,141 @@ +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.Tools.GitHub; +using Nuke.Common.Tools.GitVersion; +using Octokit; +using Serilog; +using System.Text; +using System.Text.RegularExpressions; + +internal partial class Build +{ + [GitVersion(NoFetch = true)] private readonly GitVersion GitVersion; + [Parameter] private string GitHubToken { get; set; } + private readonly Regex VersionRegex = new(@"(\d+\.)+\d+", RegexOptions.Compiled); + + private Target PublishGitHubRelease => _ => _ + .TriggeredBy(CreateInstaller) + .Requires(() => GitHubToken) + .Requires(() => GitRepository) + .Requires(() => GitVersion) + .OnlyWhenStatic(() => GitRepository.IsOnMainOrMasterBranch()) + .OnlyWhenStatic(() => IsServerBuild) + .Executes(() => + { + GitHubTasks.GitHubClient = new GitHubClient(new ProductHeaderValue(Solution.Name)) + { + Credentials = new Credentials(GitHubToken) + }; + + var gitHubName = GitRepository.GetGitHubName(); + var gitHubOwner = GitRepository.GetGitHubOwner(); + var artifacts = Directory.GetFiles(ArtifactsDirectory, "*"); + var version = GetProductVersion(artifacts); + + CheckTags(gitHubOwner, gitHubName, version); + Log.Information("Detected Tag: {Version}", version); + + var newRelease = new NewRelease(version) + { + Name = version, + Body = CreateChangelog(version), + Draft = true, + TargetCommitish = GitVersion.Sha + }; + + var draft = CreatedDraft(gitHubOwner, gitHubName, newRelease); + UploadArtifacts(draft, artifacts); + ReleaseDraft(gitHubOwner, gitHubName, draft); + }); + + private string CreateChangelog(string version) + { + if (!File.Exists(ChangeLogPath)) + { + Log.Warning("Can't find changelog file: {Log}", ChangeLogPath); + return string.Empty; + } + + Log.Information("Detected Changelog: {Path}", ChangeLogPath); + + var logBuilder = new StringBuilder(); + var changelogLineRegex = new Regex($@"^.*({version})\S*\s?"); + const string nextRecordSymbol = "- "; + + foreach (var line in File.ReadLines(ChangeLogPath)) + { + if (logBuilder.Length > 0) + { + if (line.StartsWith(nextRecordSymbol)) break; + logBuilder.AppendLine(line); + continue; + } + + if (!changelogLineRegex.Match(line).Success) continue; + var truncatedLine = changelogLineRegex.Replace(line, string.Empty); + logBuilder.AppendLine(truncatedLine); + } + + if (logBuilder.Length == 0) Log.Warning("There is no version entry in the changelog: {Version}", version); + return logBuilder.ToString(); + } + + private static void CheckTags(string gitHubOwner, string gitHubName, string version) + { + var gitHubTags = GitHubTasks.GitHubClient.Repository + .GetAllTags(gitHubOwner, gitHubName) + .Result; + + if (gitHubTags.Select(tag => tag.Name).Contains(version)) throw new ArgumentException($"The repository already contains a Release with the tag: {version}"); + } + + private string GetProductVersion(IEnumerable artifacts) + { + var stringVersion = string.Empty; + var doubleVersion = 0d; + foreach (var file in artifacts) + { + var fileInfo = new FileInfo(file); + var match = VersionRegex.Match(fileInfo.Name); + if (!match.Success) continue; + var version = match.Value; + var parsedValue = double.Parse(version.Replace(".", "")); + if (parsedValue > doubleVersion) + { + doubleVersion = parsedValue; + stringVersion = version; + } + } + + if (stringVersion.Equals(string.Empty)) throw new ArgumentException("Could not determine product version from artifacts."); + + return stringVersion; + } + + private static void UploadArtifacts(Release createdRelease, IEnumerable artifacts) + { + foreach (var file in artifacts) + { + var releaseAssetUpload = new ReleaseAssetUpload + { + ContentType = "application/x-binary", + FileName = Path.GetFileName(file), + RawData = File.OpenRead(file) + }; + var _ = GitHubTasks.GitHubClient.Repository.Release.UploadAsset(createdRelease, releaseAssetUpload).Result; + Log.Information("Added artifact: {Path}", file); + } + } + + private static Release CreatedDraft(string gitHubOwner, string gitHubName, NewRelease newRelease) => + GitHubTasks.GitHubClient.Repository.Release + .Create(gitHubOwner, gitHubName, newRelease) + .Result; + + private static void ReleaseDraft(string gitHubOwner, string gitHubName, Release draft) + { + var _ = GitHubTasks.GitHubClient.Repository.Release + .Edit(gitHubOwner, gitHubName, draft.Id, new ReleaseUpdate { Draft = false }) + .Result; + } +} \ No newline at end of file diff --git a/build/Build.Installer.cs b/build/Build.Installer.cs new file mode 100644 index 0000000..6afadb5 --- /dev/null +++ b/build/Build.Installer.cs @@ -0,0 +1,75 @@ +using JetBrains.Annotations; +using Nuke.Common; +using Nuke.Common.Git; +using Serilog; +using System.Diagnostics; +using System.Text; +using System.Text.RegularExpressions; + +internal partial class Build +{ + private readonly Regex StreamRegex = new("'(.+?)'", RegexOptions.Compiled); + + private Target CreateInstaller => _ => _ + .TriggeredBy(Compile) + .OnlyWhenStatic(() => IsLocalBuild || GitRepository.IsOnMainOrMasterBranch()) + .Executes(() => + { + var installerProject = BuilderExtensions.GetProject(Solution, InstallerProject); + var buildDirectories = GetBuildDirectories(); + var configurations = GetConfigurations(InstallerConfiguration); + foreach (var directoryGroup in buildDirectories) + { + var directories = directoryGroup.ToList(); + var exeArguments = BuildExeArguments(directories.Select(info => info.FullName).ToList()); + var exeFile = installerProject.GetExecutableFile(configurations, directories); + if (string.IsNullOrEmpty(exeFile)) + { + Log.Warning("No installer executable was found for these packages:\n {Directories}", string.Join("\n", directories)); + continue; + } + + var proc = new Process(); + proc.StartInfo.FileName = exeFile; + proc.StartInfo.Arguments = exeArguments; + proc.StartInfo.RedirectStandardOutput = true; + proc.Start(); + while (!proc.StandardOutput.EndOfStream) ParseProcessOutput(proc.StandardOutput.ReadLine()); + proc.WaitForExit(); + if (proc.ExitCode != 0) throw new Exception("The installer creation failed."); + } + }); + + private void ParseProcessOutput([CanBeNull] string value) + { + if (value is null) return; + var matches = StreamRegex.Matches(value); + if (matches.Count > 0) + { + var parameters = matches.Select(match => match.Value + .Substring(1, match.Value.Length - 2)) + .Cast() + .ToArray(); + var line = StreamRegex.Replace(value, match => $"{{Parameter{match.Index}}}"); + Log.Information(line, parameters); + } + else + { + Log.Debug(value); + } + } + + private static string BuildExeArguments(IReadOnlyList args) + { + var argumentBuilder = new StringBuilder(); + for (var i = 0; i < args.Count; i++) + { + if (i > 0) argumentBuilder.Append(' '); + var value = args[i]; + if (value.Contains(' ')) value = $"\"{value}\""; + argumentBuilder.Append(value); + } + + return argumentBuilder.ToString(); + } +} \ No newline at end of file diff --git a/build/Build.Properties.cs b/build/Build.Properties.cs new file mode 100644 index 0000000..d1624e7 --- /dev/null +++ b/build/Build.Properties.cs @@ -0,0 +1,18 @@ +internal partial class Build +{ + private readonly string[] Projects = + { + "RevitPythonShell" + }; + + public const string InstallerProject = "Installer"; + + public const string BuildConfiguration = "Release"; + public const string InstallerConfiguration = "Installer"; + + private const string AddInBinPrefix = "AddIn"; + private const string ArtifactsFolder = "output"; + + //Specify the path to the MSBuild.exe file here if you are not using VisualStudio + private const string CustomMsBuildPath = @"C:\Program Files\JetBrains\JetBrains Rider\tools\MSBuild\Current\Bin\MSBuild.exe"; +} \ No newline at end of file diff --git a/build/Build.cs b/build/Build.cs new file mode 100644 index 0000000..43e0065 --- /dev/null +++ b/build/Build.cs @@ -0,0 +1,67 @@ +using Nuke.Common; +using Nuke.Common.Git; +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using Nuke.Common.Tools.VSWhere; +using System.Text.RegularExpressions; + +internal partial class Build : NukeBuild +{ + private readonly AbsolutePath ArtifactsDirectory = RootDirectory / ArtifactsFolder; + private readonly AbsolutePath ChangeLogPath = RootDirectory / "CHANGELOG.md"; + [GitRepository] private readonly GitRepository GitRepository; + [Solution] private readonly Solution Solution; + + private static readonly Lazy MsBuildPath = new(() => + { + if (IsServerBuild) return null; + var (_, output) = VSWhereTasks.VSWhere(settings => settings + .EnableLatest() + .AddRequires("Microsoft.Component.MSBuild") + .SetProperty("installationPath") + ); + + if (output.Count > 0) return null; + if (!File.Exists(CustomMsBuildPath)) throw new Exception($"Missing file: {CustomMsBuildPath}. Change the path to the build platform or install Visual Studio."); + return CustomMsBuildPath; + }); + + public static int Main() => Execute(x => x.Cleaning); + + private List GetConfigurations(params string[] startPatterns) + { + var configurations = Solution.Configurations + .Select(pair => pair.Key) + .Where(s => startPatterns.Any(s.StartsWith)) + .Select(s => + { + var platformIndex = s.LastIndexOf('|'); + return s.Remove(platformIndex); + }) + .ToList(); + if (configurations.Count == 0) throw new Exception($"Can't find configurations in the solution by patterns: {string.Join(" | ", startPatterns)}."); + return configurations; + } + + private IEnumerable> GetBuildDirectories() + { + var directories = new List(); + foreach (var projectName in Projects) + { + var project = BuilderExtensions.GetProject(Solution, projectName); + var directoryInfo = new DirectoryInfo(project.GetBinDirectory()).GetDirectories(); + + directories.AddRange(directoryInfo); + } + + if (directories.Count == 0) throw new Exception("There are no packaged assemblies in the project. Try to build the project again."); + + var versionRegex = new Regex(@"^.*R\d+ ?"); + var addInsDirectory = directories + .Where(dir => dir.Name.StartsWith(AddInBinPrefix)) + .Where(dir => dir.Name.Contains(BuildConfiguration)) + .GroupBy(dir => versionRegex.Replace(dir.Name, string.Empty)); + + return addInsDirectory; + } +} \ No newline at end of file diff --git a/build/Build.csproj b/build/Build.csproj new file mode 100644 index 0000000..3a0399f --- /dev/null +++ b/build/Build.csproj @@ -0,0 +1,21 @@ + + + Exe + AnyCPU + CS0649;CS0169 + latest + true + net6.0 + .. + .. + 1 + Release;Debug + + + + + + + + + diff --git a/build/Build.csproj.DotSettings b/build/Build.csproj.DotSettings new file mode 100644 index 0000000..95fbce8 --- /dev/null +++ b/build/Build.csproj.DotSettings @@ -0,0 +1,28 @@ + + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + DO_NOT_SHOW + Implicit + Implicit + ExpressionBody + 0 + NEXT_LINE + True + False + 120 + IF_OWNER_IS_SINGLE_LINE + WRAP_IF_LONG + False + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /> + True + True + True + True + True + True + True + True + True diff --git a/build/BuilderExtensions.cs b/build/BuilderExtensions.cs new file mode 100644 index 0000000..018f51b --- /dev/null +++ b/build/BuilderExtensions.cs @@ -0,0 +1,32 @@ +using Nuke.Common.IO; +using Nuke.Common.ProjectModel; +using System.Text.RegularExpressions; + +internal static class BuilderExtensions +{ + public static Project GetProject(this Solution solution, string projectName) => + solution.GetProject(projectName) ?? throw new NullReferenceException($"Cannon find project \"{projectName}\""); + + public static AbsolutePath GetBinDirectory(this Project project) => project.Directory / "bin"; + + private static AbsolutePath GetExePath(this Project project, string configuration) => project.GetBinDirectory() / configuration / $"{project.Name}.exe"; + + public static AbsolutePath GetExecutableFile(this Project project, IEnumerable configurations, List directories) + { + var directory = directories[0].Name; + var subConfigRegex = new Regex(@"R\d+$"); + foreach (var subCategory in configurations.Select(configuration => configuration.Replace(Build.InstallerConfiguration, ""))) + if (string.IsNullOrEmpty(subCategory)) + { + if (!string.IsNullOrEmpty(subConfigRegex.Match(directory).Value)) + return project.GetExePath(Build.BuildConfiguration); + } + else + { + if (directory.EndsWith(subCategory)) + return project.GetExePath($"{Build.BuildConfiguration}{subCategory}"); + } + + return null; + } +} \ No newline at end of file