├── .circleci └── config.yml ├── .editorconfig ├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── FEATURE-REQUEST.yml │ └── bug_report.md ├── pull_request_template.md └── workflows │ └── deploy-nuget.yml ├── .gitignore ├── .vscode ├── launch.json ├── settings.json └── tasks.json ├── BUILDSOURCE.md ├── CONTRIBUTING.md ├── Crash.sln ├── Crash.sln.DotSettings ├── Directory.Build.Props ├── LICENSE.md ├── README.md ├── art ├── Icons.afdesign └── crash-logo.jpg ├── dist └── Examples │ └── CollaborativeBake.ghx ├── global.json ├── scripts ├── YakCompiler.ps1 └── YakUploader.ps1 ├── src ├── Crash.Common │ ├── App │ │ ├── CrashApp.cs │ │ └── CrashInstances.cs │ ├── Changes │ │ ├── CameraChange.cs │ │ ├── DoneChange.cs │ │ └── TransformChange.cs │ ├── Collections │ │ ├── FixedSizedQueue.cs │ │ ├── IdleAction.cs │ │ └── IdleQueue.cs │ ├── Communications │ │ ├── Client │ │ │ ├── ClientErrors.cs │ │ │ ├── CrashClient.cs │ │ │ ├── CrashRetryPolicy.cs │ │ │ ├── FromClientToServer.cs │ │ │ ├── FromServerToClient.cs │ │ │ └── ICrashClient.cs │ │ └── IEventDispatcher.cs │ ├── Crash.Common.csproj │ ├── Document │ │ ├── CrashDoc.cs │ │ └── User.cs │ ├── Events │ │ ├── CrashChangeArgs.cs │ │ ├── CrashEventArgs.cs │ │ ├── CrashInitArgs.cs │ │ └── IdleArgs.cs │ ├── Exceptions │ │ └── OversizedChangeException.cs │ ├── Logging │ │ ├── CrashLogger.cs │ │ └── CrashLoggerProvider.cs │ ├── Properties │ │ └── GlobalUsings.cs │ ├── Serialization │ │ ├── CameraConverter.cs │ │ └── Options.cs │ ├── Tables │ │ ├── CacheTable.cs │ │ ├── CameraTable.cs │ │ ├── ICacheTable.cs │ │ ├── RealisedChangeTable.cs │ │ ├── TemporaryChangeTable.cs │ │ └── UserTable.cs │ └── View │ │ └── Camera.cs ├── Crash.Handlers │ ├── Changes │ │ └── ChangeHelpers.cs │ ├── Crash.Handlers.csproj │ ├── CrashDocRegistry.cs │ ├── Data │ │ ├── BitmapConverter.cs │ │ ├── CrashData.cs │ │ ├── ModelRenderState.cs │ │ ├── SharedModel.cs │ │ └── SharedModelCache.cs │ ├── InternalEvents │ │ ├── CrashLayerArgs.cs │ │ ├── CrashObject.cs │ │ ├── CrashObjectEventArgs.cs │ │ ├── CrashSelectionEventArgs.cs │ │ ├── CrashTransformEventArgs.cs │ │ ├── CrashUpdateArgs.cs │ │ ├── CrashViewArgs.cs │ │ └── Wrapping │ │ │ ├── AddRecord.cs │ │ │ ├── DeleteRecord.cs │ │ │ ├── EventWrapper.cs │ │ │ ├── IUndoRedoCache.cs │ │ │ ├── ModifyGeometryRecord.cs │ │ │ ├── TransformRecord.cs │ │ │ └── UpdateRecord.cs │ ├── LoadingUtils.cs │ ├── Plugins │ │ ├── Camera │ │ │ ├── CameraChangeDefinition.cs │ │ │ ├── Create │ │ │ │ └── CameraCreateAction.cs │ │ │ └── Recieve │ │ │ │ └── CameraRecieveAction.cs │ │ ├── CrashPlugin.cs │ │ ├── CreateRecieveArgs.cs │ │ ├── EventDispatcher.cs │ │ ├── Geometry │ │ │ ├── Create │ │ │ │ ├── GeometryCreateAction.cs │ │ │ │ ├── GeometryLockAction.cs │ │ │ │ ├── GeometryRemoveAction.cs │ │ │ │ ├── GeometryTransformAction.cs │ │ │ │ ├── GeometryUnlockAction.cs │ │ │ │ └── GeometryUpdateAction.cs │ │ │ ├── GeometryChange.cs │ │ │ ├── GeometryChangeDefinition.cs │ │ │ └── Recieve │ │ │ │ ├── GeometryAddRecieveAction.cs │ │ │ │ ├── GeometryLockRecieveAction.cs │ │ │ │ ├── GeometryRemoveRecieveAction.cs │ │ │ │ ├── GeometryTransformRecieveAction.cs │ │ │ │ ├── GeometryUnlockRecieveAction.cs │ │ │ │ └── GeometryUpdateRecieveAction.cs │ │ ├── IChangeCreateAction.cs │ │ ├── IChangeDefinition.cs │ │ ├── IChangeRecieveAction.cs │ │ ├── Initializers │ │ │ ├── DoneDefinition.cs │ │ │ └── Recieve │ │ │ │ └── DoneRecieve.cs │ │ └── Layers │ │ │ ├── CrashLayer.cs │ │ │ ├── Create │ │ │ ├── LayerCreateAction.cs │ │ │ ├── LayerDeleteAction.cs │ │ │ └── LayerModifyAction.cs │ │ │ ├── LayerChange.cs │ │ │ ├── LayerChangeDefinition.cs │ │ │ ├── LayerTable.cs │ │ │ └── Recieve │ │ │ ├── LayerCreateRecieveAction.cs │ │ │ ├── LayerDeleteRecieveAction.cs │ │ │ └── LayerModifyRecieveAction.cs │ ├── Properties │ │ └── GlobalUsings.cs │ ├── Refs │ │ ├── net48 │ │ │ └── crash.auth.dll │ │ └── net7.0 │ │ │ └── crash.auth.dll │ └── Utils │ │ ├── Dictionaries │ │ ├── DictionaryUtils.cs │ │ ├── LayerComparisonUtils.cs │ │ └── ObjectAttributeComparisonUtils.cs │ │ ├── Geometry.cs │ │ ├── RhinoDocUtils.cs │ │ └── RhinoLayerUtils.cs ├── Crash │ ├── Commands │ │ ├── AsyncCommand.cs │ │ ├── CommandUtils.cs │ │ ├── JoinSharedModel.cs │ │ ├── LeaveSharedModel.cs │ │ ├── Release.cs │ │ ├── ReleaseSelected.cs │ │ ├── SelectionUtils.cs │ │ └── ShowCrashUsers.cs │ ├── Crash.csproj │ ├── CrashRhinoPlugIn.cs │ ├── Plugins │ │ ├── CrashPluginLoader.cs │ │ └── PluginLoadContext.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── GlobalUsings.cs │ │ └── launchSettings.json │ ├── Resources │ │ ├── CrashIcons.cs │ │ ├── camera-follow.png │ │ ├── camera-off.png │ │ ├── camera.png │ │ ├── close.png │ │ ├── join.png │ │ ├── plus.png │ │ ├── reload-all.png │ │ ├── reload.png │ │ ├── signal-low.png │ │ ├── signal-off.png │ │ ├── signal.png │ │ └── user.png │ ├── UI │ │ ├── BaseViewModel.cs │ │ ├── CrashCommands.cs │ │ ├── ExceptionsAndErrors │ │ │ └── BadChangePipeline.cs │ │ ├── InteractivePipe.cs │ │ ├── Palette.cs │ │ ├── RecentView │ │ │ ├── AddressInputDialog.cs │ │ │ ├── ModelControl.cs │ │ │ ├── OverflowLayout.cs │ │ │ ├── RecentModelDialog.cs │ │ │ ├── RecentViewModel.cs │ │ │ └── RightClickMenu.cs │ │ └── UsersView │ │ │ ├── UserObject.cs │ │ │ ├── UsersView.cs │ │ │ └── UsersViewModel.cs │ ├── icon.png │ └── manifest.yml ├── Package.nuspec └── README.md └── tests ├── Crash.Common.Tests ├── Changes │ └── CameraChangeTests.cs ├── Collections │ ├── FixedSizeQueue.cs │ └── IdleQueue.cs ├── Crash.Common.Tests.csproj ├── Document │ ├── DocumentTests.cs │ └── UserTests.cs ├── Events │ ├── CrashEventArgsTests.cs │ └── IdleActionTests.cs ├── Serialization │ ├── CTransformSerialization.cs │ └── CameraSerialization.cs ├── Tables.cs │ ├── CacheTableTests.cs │ ├── CameraTableTests.cs │ └── UserTableTests.cs ├── Usings.cs └── Validity │ ├── CTransforms.cs │ └── Camera.cs ├── Crash.Handlers.Tests ├── Changes │ └── GeometryChangeTests.cs ├── Crash.Handlers.Tests.csproj ├── Geometry │ └── ConversionTests.cs ├── GlobalUsings.cs ├── Plugins │ ├── Camera │ │ ├── CameraCreateActionTests.cs │ │ └── CameraRecieveActionTests.cs │ ├── CreateRecieveArgTests.cs │ ├── EventDispatcherTests.cs │ └── Geometry │ │ ├── GeometryAddRecieveActionTests.cs │ │ ├── GeometryCreateRemoveActionTests.cs │ │ ├── GeometrySelectUnselectActionTests.cs │ │ ├── GeometryTransformtActionTests.cs │ │ └── SharedUtils.cs └── Properties │ ├── GlobalUsings.cs │ └── launchSettings.json └── Crash.Tests ├── Crash.Tests.csproj ├── Crash.Tests.sln ├── GlobalUsings.cs ├── Plugins └── PluginLoaderTests.cs └── Properties ├── GlobalUsings.cs └── launchSettings.json /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Use the latest 2.1 version of CircleCI pipeline process engine. 2 | # See: https://circleci.com/docs/configuration-reference 3 | version: 2.1 4 | 5 | # The Windows orb gives you everything you 6 | # need to start using the Windows executor. 7 | orbs: 8 | rhino: crashcloud/rhino@0.4.0 9 | 10 | 11 | # Define a job to be invoked later in a workflow. 12 | # See: https://circleci.com/docs/configuration-reference/#jobs 13 | jobs: 14 | setup-env: 15 | machine: 16 | image: 'windows-server-2022-gui:current' 17 | resource_class: windows.medium 18 | shell: powershell.exe -ExecutionPolicy Bypass 19 | 20 | steps: 21 | 22 | - checkout 23 | 24 | - rhino/setup_rhino: 25 | version: '7' 26 | 27 | - run: 28 | name: Restore NuGet Packages 29 | command: nuget restore Crash.sln 30 | 31 | - run: 32 | name: Build 33 | command: dotnet build Crash.sln /p:Configuration=Release --no-restore 34 | 35 | - run: 36 | name: Run Tests 37 | command: dotnet test --no-restore --results-directory test-results --logger trx 38 | 39 | - run: 40 | name: Install trx2junit 41 | command: dotnet tool install trx2junit -g 42 | when: always 43 | 44 | - run: 45 | name: Convert Unit Test Results 46 | command: ~\.dotnet\tools\trx2junit.exe .\test-results\*.trx 47 | when: always 48 | 49 | - store_test_results: 50 | path: .\test-results\ 51 | 52 | # Orchestrate jobs using workflows 53 | # See: https://circleci.com/docs/configuration-reference/#workflows 54 | workflows: 55 | unit-test: 56 | jobs: 57 | - setup-env 58 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # for LFS 2 | *.3dm filter=lfs diff=lfs merge=lfs -text 3 | *.zip filter=lfs diff=lfs merge=lfs -text 4 | 5 | # from https://github.com/github/VisualStudio/blob/master/.gitattributes 6 | # Auto detect text files and perform LF normalization 7 | * text=auto 8 | 9 | # Custom for Visual Studio 10 | *.cs diff=csharp 11 | *.sln merge=union 12 | *.csproj merge=union 13 | *.vbproj merge=union 14 | *.fsproj merge=union 15 | *.dbproj merge=union -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/FEATURE-REQUEST.yml: -------------------------------------------------------------------------------- 1 | name: "💡 Feature Request" 2 | description: Create a new ticket for a new feature request 3 | title: "💡 [REQUEST] - " 4 | labels: [ 5 | "question" 6 | ] 7 | body: 8 | - type: input 9 | id: start_date 10 | attributes: 11 | label: "Start Date" 12 | description: Start of development 13 | placeholder: "month/day/year" 14 | validations: 15 | required: false 16 | - type: textarea 17 | id: implementation_pr 18 | attributes: 19 | label: "Implementation PR" 20 | description: Pull request used 21 | placeholder: "#Pull Request ID" 22 | validations: 23 | required: false 24 | - type: textarea 25 | id: reference_issues 26 | attributes: 27 | label: "Reference Issues" 28 | description: Common issues 29 | placeholder: "#Issues IDs" 30 | validations: 31 | required: false 32 | - type: textarea 33 | id: summary 34 | attributes: 35 | label: "Summary" 36 | description: Provide a brief explanation of the feature 37 | placeholder: Describe in a few lines your feature request 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: basic_example 42 | attributes: 43 | label: "Basic Example" 44 | description: Indicate here some basic examples of your feature. 45 | placeholder: A few specific words about your feature request. 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: drawbacks 50 | attributes: 51 | label: "Drawbacks" 52 | description: What are the drawbacks/impacts of your feature request ? 53 | placeholder: Identify the drawbacks and impacts while being neutral on your feature request 54 | validations: 55 | required: true 56 | - type: textarea 57 | id: unresolved_question 58 | attributes: 59 | label: "Unresolved questions" 60 | description: What questions still remain unresolved ? 61 | placeholder: Identify any unresolved issues. 62 | validations: 63 | required: false 64 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Version Info (please complete the following information):** 27 | - Rhino Version [x.x.x] 28 | - Crash Version [x.x.x] 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes # (issue) 6 | 7 | ## Type of change 8 | 9 | Please delete options that are not relevant. 10 | 11 | - [ ] Bug fix (non-breaking change which fixes an issue) 12 | - [ ] New feature (non-breaking change which adds functionality) 13 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 14 | - [ ] This change requires a documentation update 15 | 16 | # How Has This Been Tested? 17 | 18 | Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration 19 | 20 | - [ ] Test A 21 | - [ ] Test B 22 | 23 | # Checklist: 24 | 25 | - [ ] My code follows the style of this project 26 | - [ ] I have performed a self-review of my own code 27 | - [ ] I have commented my code, particularly in hard-to-understand areas 28 | - [ ] I have made corresponding changes to the docs 29 | - [ ] My changes generate no new warnings 30 | -------------------------------------------------------------------------------- /.github/workflows/deploy-nuget.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: DeployNuget 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths: 8 | - '.github/workflows/deploy-nuget.yml' 9 | - '*.Build.Props' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v3 16 | 17 | - name: Setup NuGet 18 | uses: NuGet/setup-nuget@v1 19 | 20 | - name: Restore Packages 21 | run: nuget restore 22 | 23 | - uses: actions/setup-dotnet@v3 24 | with: 25 | dotnet-version: | 26 | 7.0 27 | 28 | - name: Build and Setup Nuget 29 | id: setup-nuget 30 | run: | 31 | dotnet nuget add source --username USERNAME --password ${{ secrets.GITHUB_TOKEN }} --store-password-in-clear-text --name github "https://nuget.pkg.github.com/crashcloud/index.json" 32 | 33 | nuget pack Package.nuspec 34 | 35 | - name: Push Nuget to GitHub 36 | id: publish-nuget-nuget 37 | run: | 38 | dotnet nuget push src/*.nupkg --api-key ${{ secrets.GITHUB_TOKEN }} --source "github" --skip-duplicate 39 | 40 | - name: Push Nuget to Nuget 41 | id: publish-nuget-github 42 | run: | 43 | dotnet nuget push src/*.nupkg --api-key ${{ secrets.NUGET_TOKEN }} --source "https://api.nuget.org/v3/index.json" --skip-duplicate 44 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Rhino 8", 6 | "type": "coreclr", 7 | "request": "launch", 8 | "preLaunchTask": "build-plugin-netcore", 9 | "program": "", 10 | "osx": { 11 | "program": "/Applications/Rhino 8.app/Contents/MacOS/Rhinoceros" 12 | }, 13 | "windows": { 14 | "program": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe", 15 | "targetArchitecture": "x86_64" 16 | }, 17 | "args": [ 18 | "-nosplash", 19 | "-notemplate", 20 | "-runscript=JoinSharedModel" 21 | ], 22 | "cwd": "${workspaceFolder}", 23 | "stopAtEntry": false, 24 | "console": "internalConsole", 25 | "env": { 26 | "RHINO_PACKAGE_DIRS": "${workspaceFolder}/src/Crash/bin/Debug/" 27 | } 28 | }, 29 | { 30 | "name": "Run Rhino 9 WIP", 31 | "type": "coreclr", 32 | "request": "launch", 33 | "preLaunchTask": "build-plugin-netcore", 34 | "program": "", 35 | "osx": { 36 | "program": "/Applications/RhinoWIP.app/Contents/MacOS/Rhinoceros" 37 | }, 38 | "windows": { 39 | "program": "C:\\Program Files\\Rhino WIP\\System\\Rhino.exe", 40 | "targetArchitecture": "x86_64" 41 | }, 42 | "args": [ 43 | "-nosplash", 44 | "-notemplate" 45 | ], 46 | "cwd": "${workspaceFolder}", 47 | "stopAtEntry": false, 48 | "console": "internalConsole", 49 | "env": { 50 | "RHINO_PACKAGE_DIRS": "${workspaceFolder}/src/Crash/bin/Debug/" 51 | } 52 | }, 53 | { 54 | "name": "Run Rhino 7 (Windows)", 55 | "type": "clr", 56 | "request": "launch", 57 | "preLaunchTask": "build-plugin-netframework", 58 | "program": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 59 | "args": [ 60 | "${workspaceFolder}\\src\\Crash\\bin\\Debug\\net48\\Crash.rhp" 61 | ], 62 | "cwd": "${workspaceFolder}", 63 | "console": "internalConsole", 64 | "env": {} 65 | } 66 | ], 67 | "compounds": [] 68 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "dotnet.defaultSolution": "Crash.sln" 3 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build-plugin-netcore", 6 | "command": "dotnet", 7 | "type": "shell", 8 | "args": [ 9 | "build", 10 | "-clp:NoSummary", 11 | "${workspaceFolder}/src/Crash/Crash.csproj", 12 | "-f", "net7.0"], 13 | "problemMatcher": [ 14 | "$msCompile" 15 | ], 16 | "presentation": { 17 | "reveal": "always", 18 | "clear": true 19 | }, 20 | "group": "build" 21 | }, 22 | { 23 | "label": "build-plugin-netframework", 24 | "command": "dotnet", 25 | "type": "shell", 26 | "args": [ 27 | "build", 28 | "-clp:NoSummary", 29 | "${workspaceFolder}/src/Crash/Crash.csproj", 30 | "-f", "net48"], 31 | "problemMatcher": [ 32 | "$msCompile" 33 | ], 34 | "presentation": { 35 | "reveal": "always", 36 | "clear": true 37 | }, 38 | "group": "build" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /BUILDSOURCE.md: -------------------------------------------------------------------------------- 1 | # Build Crash! from source 2 | 3 | These instructions will get you a copy of the project up and running on your 4 | local machine for development and testing purposes. 5 | 6 | ## Prerequisites 7 | 8 | * Git 9 | ([download](https://git-scm.com/downloads)) 10 | * Visual Studio 2022 (For Windows) 11 | ([download](https://visualstudio.microsoft.com/downloads/)) 12 | * Visual Studio Code (For Mac) 13 | ([download](https://code.visualstudio.com/Download)) 14 | * .NET Framework (4.8) & .NET Core (7.0) Developer Packs 15 | ([download](https://www.microsoft.com/net/download/visual-studio-sdks)) 16 | * Rhino 17 | ([download v7](https://www.rhino3d.com/download/rhino/7.0)) 18 | ([download v8 (WIP)](https://discourse.mcneel.com/t/welcome-to-serengeti/9612)) 19 | 20 | ## Getting Source & Build 21 | 22 | 1. Clone the repository. At the command prompt, enter the following command: 23 | 24 | ```console 25 | git clone --recursive https://github.com/crashcloud/crash.git 26 | ``` 27 | 28 | 2. In Visual Studio, open `Crash.sln`. 29 | 3. Press F5 or Debug 30 | 31 | ## Installing & Uninstalling 32 | 33 | Inside your build directory will be Crash.rhp, you will want to drag this into the Rhino window. 34 | To uninstall navigate to `%appdata%\McNeel\Rhinoceros\packages\7.0` and delete the directory titled Crash. 35 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Hello! 🦏🦏🦏 2 | 3 | > First off, thank you for considering contributing to **CRASH!**. It's people like you that make the world a better 4 | > place! 5 | 6 | ### Guidelines 7 | 8 | > Following these guidelines helps to communicate that you respect the time of the developers managing and developing 9 | > this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, 10 | > and helping you finalize your pull requests. 11 | 12 | ## Ground Rules 13 | 14 | > Responsibilities 15 | > * Ensure that all code is made lovingly 16 | > * Create a discussion for any major changes and enhancements that you wish to make. Discuss things transparently and 17 | get community feedback. 18 | > * Keep feature versions as small as possible, preferably one new feature per version. 19 | > * Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. I think Python's Code of 20 | Conduct sums it up nicely. 21 | > 22 | > 👉 [Python Code of Conduct](https://www.python.org/psf/codeofconduct/). 23 | 24 | # Your First Contribution 25 | 26 | > Unsure where to begin contributing to Crash? There's a lot of ways you can help. 27 | > 28 | > 1. Read through the bugs 29 | in [issues](https://github.com/clicketyclackety/Crash/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc) and help 30 | someone out 31 | > 2. Read through the discussions and join in the discourse 32 | > 3. Create new ideas and topics in the discourse 33 | > 4. Ask Questions in the Q&A! Questions are very helpful 34 | > 5. Create a Pull Request to fix a bug 🐛 or implement a new idea 💡 35 | > 36 | > You may feel like the only productive contribution is `code`. But that's not true! Any and all engagement helps the 37 | > project, so give yourself a little pat on the back 😊! 38 | > 39 | > You can start by looking through [these](https://github.com/clicketyclackety/Crash/labels/Beginner%20issues) beginner 40 | > and help-wanted issues: 41 | > Beginner issues - issues which should only require a few lines of code, and a test or two. 42 | > Help wanted issues - issues which should be a bit more involved than beginner issues. 43 | > Both issue lists are sorted by total number of comments. While not perfect, number of comments is a reasonable proxy 44 | > for impact a given change will have. 45 | 46 | ## New to open-source? 47 | 48 | So are we! Here are a couple of friendly tutorials you can peruse 49 | 50 | 👉 [Make a Pull Request](http://makeapullrequest.com) 51 | 52 | 👉 [First Timers Only](http://www.firsttimersonly.com/) 53 | 54 | 👉 [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github) 55 | -------------------------------------------------------------------------------- /Directory.Build.Props: -------------------------------------------------------------------------------- 1 | <Project> 2 | <PropertyGroup> 3 | <Version>1.4.0</Version> 4 | <Nullable>enable</Nullable> 5 | <LangVersion>12</LangVersion> 6 | <Company>Crash Cloud</Company> 7 | <Copyright>Crash Cloud</Copyright> 8 | <FileVersion>$(Version)</FileVersion> 9 | <AnalysisLevel>latest</AnalysisLevel> 10 | <RhinoMacLauncher>8</RhinoMacLauncher> 11 | <ImplicitUsings>enable</ImplicitUsings> 12 | <AssemblyVersion>$(Version)</AssemblyVersion> 13 | <EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild> 14 | <SatelliteResourceLanguages>en</SatelliteResourceLanguages> 15 | <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies> 16 | <RepositoryUrl>https://github.com/crashcloud/crash</RepositoryUrl> 17 | <GenerateNeutralResourcesLanguageAttribute>false</GenerateNeutralResourcesLanguageAttribute> 18 | <GenerateResourceUsePreserializedResources>true</GenerateResourceUsePreserializedResources> 19 | </PropertyGroup> 20 | </Project> -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /art/Icons.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/art/Icons.afdesign -------------------------------------------------------------------------------- /art/crash-logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/art/crash-logo.jpg -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "7.0.0", 4 | "rollForward": "latestMajor", 5 | "allowPrerelease": true 6 | } 7 | } -------------------------------------------------------------------------------- /scripts/YakCompiler.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$true, HelpMessage="The input directory")] 3 | [Alias("Path")] 4 | [string]$inputDir, 5 | 6 | [Parameter(Mandatory=$false, HelpMessage="The output location")] 7 | [Alias("DestinationPath")] 8 | [string]$outputDir = $inputDir, 9 | 10 | [Parameter(Mandatory=$false, HelpMessage="What Config was Project built for?")] 11 | [Alias("Config")] 12 | [string]$configuration="Debug", 13 | 14 | [Parameter(Mandatory=$false, HelpMessage="What OS is current running")] 15 | [string]$os="win", 16 | 17 | [Parameter(Mandatory=$false, HelpMessage="Minimum required version of Rhino")] 18 | [Alias("Yak")] 19 | [string]$yakExe="C:\Program Files\Rhino 7\System\Yak.exe", 20 | 21 | [Parameter(Mandatory=$false, HelpMessage="Push to yak server?")] 22 | [bool]$publish=$false 23 | ) 24 | 25 | $crashVersion = "1.0.0" 26 | 27 | foreach($yakFile in Get-ChildItem "$outputDir\*.yak") 28 | { 29 | Remove-Item $yakFile 30 | } 31 | 32 | $originalLocation = Get-Location 33 | Set-Location $inputDir 34 | 35 | & $yakExe build --platform $os 36 | 37 | if ($inputDir -ne $outputDir) 38 | { 39 | Copy-Item -Path "$inputDir\*.yak" -DestinationPath $outputDir 40 | } 41 | 42 | <# 43 | Compress-Archive -Path "$inputDir\*.*" -DestinationPath $zipFile -CompressionLevel Optimal 44 | Compress-Archive -Path "$inputDir\Server" -DestinationPath $zipFile -CompressionLevel Optimal -Update 45 | Rename-Item $zipFile $yakFile 46 | #> 47 | 48 | Set-Location $originalLocation -------------------------------------------------------------------------------- /scripts/YakUploader.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [Parameter(Mandatory=$false, HelpMessage="What Config was Project built for?")] 3 | [string]$configuration="Debug", 4 | 5 | [Parameter(Mandatory=$false, HelpMessage="Push to yak server?")] 6 | [bool]$publish=$False 7 | ) 8 | 9 | $script_dir = $PSScriptRoot 10 | $base_dir = (get-item $script_dir).parent.FullName 11 | $buildDir = "$base_dir\src\Crash\bin\$configuration\net48\" 12 | 13 | $loc = Get-Location 14 | Set-Location $buildDir 15 | 16 | # Build plugin 17 | & dotnet build -c $configuration "$base_dir/src/Crash/Crash.csproj" 18 | 19 | # Build servers 20 | & dotnet publish -c $configuration -r win-x64 "$base_dir/Crash.Server/Crash.Server.csproj" /p:Publish=True 21 | # & dotnet publish -c $configuration -r osx-x64 "$base_dir/Crash.Server/Crash.Server.csproj" /p:Publish=True 22 | # & dotnet publish -c $configuration -r osx-arm64 "$base_dir/Crash.Server/Crash.Server.csproj" /p:Publish=True 23 | 24 | $yakexe = "C:\Program Files\Rhino 7\System\Yak.exe" 25 | & $yakexe build --platform win 26 | 27 | if($publish -eq $True) { 28 | & $yakexe push "$buildDir\crash-1.0.0-rh7_21-win.yak" 29 | } 30 | 31 | Set-Location $loc -------------------------------------------------------------------------------- /src/Crash.Common/App/CrashApp.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Crash.Common.App 6 | { 7 | 8 | /// <summary> 9 | /// A global Application class to handle Application specific logic 10 | /// </summary> 11 | public static class CrashApp 12 | { 13 | 14 | /// <summary>The current Log Level, anything lower won't be logged</summary> 15 | private static LogLevel Level { get; } = LogLevel.Information; 16 | 17 | /// <summary>Logs a Message</summary> 18 | public static void Log(string message, LogLevel level = LogLevel.Information) 19 | { 20 | LogMessage?.Invoke(null, new CrashLog(message, level, DateTime.UtcNow)); 21 | 22 | if (level < Level || string.IsNullOrEmpty(message)) 23 | { 24 | } 25 | 26 | // Add Log logic here 27 | } 28 | 29 | public static void LogError(string message) 30 | { 31 | Log(message, LogLevel.Error); 32 | } 33 | 34 | public static void LogInformation(string message) 35 | { 36 | Log(message); 37 | } 38 | 39 | public static void LogCritical(string message) 40 | { 41 | Log(message, LogLevel.Critical); 42 | } 43 | 44 | public static void LogWarning(string message) 45 | { 46 | Log(message, LogLevel.Warning); 47 | } 48 | 49 | public static void InformUser(string message) 50 | { 51 | Log(message, LogLevel.Debug); 52 | UserMessage?.Invoke(null, message); 53 | } 54 | 55 | /// <summary>Fired every time a Log is called</summary> 56 | public static event EventHandler<CrashLog> LogMessage; 57 | 58 | /// <summary>Fired every time a Log is called</summary> 59 | public static event EventHandler<string> UserMessage; 60 | 61 | /// <summary>A Log structure</summary> 62 | /// <param name="Message"></param> 63 | /// <param name="Level"></param> 64 | /// <param name="Stamp"></param> 65 | public record struct CrashLog(string Message, LogLevel Level, DateTime Stamp); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Crash.Common/App/CrashInstances.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.App 4 | { 5 | 6 | public interface ICrashInstance { } 7 | 8 | /// <summary> 9 | /// Stashes Instances against a Crash Doc 10 | /// This avoids statics 11 | /// </summary> 12 | public static class CrashInstances 13 | { 14 | 15 | public class CrashInstanceSet 16 | { 17 | private Dictionary<string, ICrashInstance> Instances { get; } = new(); 18 | 19 | public bool TryGetInstance<TInstance>(out TInstance instance) where TInstance : ICrashInstance 20 | { 21 | instance = default; 22 | if (Instances.TryGetValue(typeof(TInstance).Name, out var crashInstance)) 23 | { 24 | if (crashInstance is TInstance typedInstance) 25 | { 26 | instance = typedInstance; 27 | return true; 28 | } 29 | } 30 | 31 | return false; 32 | } 33 | 34 | public bool SetInstance(ICrashInstance instance) 35 | { 36 | if (instance is null) return false; 37 | if (Instances.ContainsKey(instance.GetType().Name)) return false; 38 | Instances.Add(instance.GetType().Name, instance); 39 | return true; 40 | } 41 | 42 | public bool Remove(Type type) 43 | { 44 | if (type is null) return false; 45 | return Instances.Remove(type.Name); 46 | } 47 | 48 | } 49 | 50 | private static Dictionary<CrashDoc, CrashInstanceSet> Instances { get; } = new(); 51 | 52 | public static bool TryGetInstance<TInstance>(CrashDoc crashDoc, out TInstance instance) where TInstance : ICrashInstance 53 | { 54 | instance = default; 55 | if (crashDoc is null) return false; 56 | if (!Instances.TryGetValue(crashDoc, out CrashInstanceSet? instanceSet)) return false; 57 | return instanceSet.TryGetInstance(out instance); 58 | } 59 | 60 | public static bool TrySetInstance<TInstance>(CrashDoc crashDoc, TInstance instance) where TInstance : ICrashInstance 61 | { 62 | if (crashDoc is null) return false; 63 | if (!Instances.TryGetValue(crashDoc, out var instanceSet)) 64 | { 65 | instanceSet = new CrashInstanceSet(); 66 | instanceSet.SetInstance(instance); 67 | Instances.Add(crashDoc, instanceSet); 68 | } 69 | else 70 | { 71 | instanceSet.SetInstance(instance); 72 | } 73 | 74 | return true; 75 | } 76 | 77 | public static bool RemoveInstance(CrashDoc crashDoc, Type type) 78 | { 79 | if (crashDoc is null) return false; 80 | if (!Instances.TryGetValue(crashDoc, out var instanceSet)) return false; 81 | return instanceSet.Remove(type); 82 | } 83 | 84 | public static void DestroyInstance(CrashDoc crashDoc) 85 | { 86 | if (crashDoc is null) return; 87 | Instances.Remove(crashDoc); 88 | } 89 | 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /src/Crash.Common/Changes/CameraChange.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using Crash.Common.Serialization; 4 | using Crash.Common.View; 5 | 6 | namespace Crash.Common.Changes 7 | { 8 | /// <summary>Captures a Change of a Camera</summary> 9 | public struct CameraChange : IChange 10 | { 11 | public const string ChangeType = "Crash.CameraChange"; 12 | 13 | public Camera Camera { get; private set; } 14 | 15 | public DateTime Stamp { get; private set; } 16 | 17 | public Guid Id { get; private set; } 18 | 19 | public string? Owner { get; private set; } 20 | 21 | public string? Payload { get; private set; } 22 | 23 | public ChangeAction Action { get; set; } = ChangeAction.Add; 24 | 25 | public readonly string Type => ChangeType; 26 | 27 | public CameraChange() { } 28 | 29 | /// <summary>Creates a new Camera Change from an IChange</summary> 30 | public static CameraChange CreateFrom(IChange change) 31 | { 32 | return new CameraChange 33 | { 34 | Camera = JsonSerializer.Deserialize<Camera>(change.Payload), 35 | Stamp = change.Stamp, 36 | Id = change.Id, 37 | Owner = change.Owner, 38 | Payload = change.Payload, 39 | Action = ChangeAction.Add 40 | }; 41 | } 42 | 43 | /// <summary>Creates a new Camera Change from the required parts</summary> 44 | public static CameraChange CreateNew(Camera camera, string userName) 45 | { 46 | return new CameraChange 47 | { 48 | Camera = camera, 49 | Stamp = DateTime.UtcNow, 50 | Id = Guid.NewGuid(), 51 | Owner = userName, 52 | Payload = JsonSerializer.Serialize(camera, Options.Default), 53 | Action = ChangeAction.Add 54 | }; 55 | } 56 | 57 | /// <summary>Creates a new transmittable Change</summary> 58 | public static Change CreateChange(Camera camera, string userName) 59 | { 60 | return new Change 61 | { 62 | Stamp = DateTime.UtcNow, 63 | Id = Guid.NewGuid(), 64 | Owner = userName, 65 | Payload = JsonSerializer.Serialize(camera, Options.Default), 66 | Action = ChangeAction.Add, 67 | Type = ChangeType 68 | }; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Crash.Common/Changes/DoneChange.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Common.Changes 2 | { 3 | /// <summary>Represents a Done Change inside of Crash</summary> 4 | public static class DoneChange 5 | { 6 | public const string ChangeType = "Crash.DoneChange"; 7 | 8 | /// <summary>Creates a Done Change Template</summary> 9 | public static Change GetDoneChange(string owner) 10 | { 11 | return new Change 12 | { 13 | Stamp = DateTime.UtcNow, 14 | Id = Guid.NewGuid(), 15 | Owner = owner, 16 | Action = ChangeAction.Release, 17 | Type = ChangeType 18 | }; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crash.Common/Changes/TransformChange.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using Crash.Common.Serialization; 4 | using Crash.Geometry; 5 | 6 | namespace Crash.Common.Changes 7 | { 8 | /// <summary>Captures a Transformation Change</summary> 9 | public struct TransformChange : IChange 10 | { 11 | public const string ChangeType = "Crash.GeometryChange"; 12 | 13 | /// <summary>The CTransform</summary> 14 | public CTransform Transform { get; private set; } 15 | 16 | public DateTime Stamp { get; private set; } 17 | 18 | public Guid Id { get; private set; } 19 | 20 | public string? Owner { get; private set; } 21 | 22 | public string? Payload { get; private set; } 23 | 24 | public string Type => ChangeType; 25 | 26 | public ChangeAction Action { get; set; } = ChangeAction.Transform; 27 | 28 | public TransformChange() { } 29 | 30 | public static Change CreateChange(Guid id, string userName, CTransform transform) 31 | { 32 | return new Change 33 | { 34 | Id = id, 35 | Owner = userName, 36 | Payload = JsonSerializer.Serialize(transform, Options.Default), 37 | Stamp = DateTime.UtcNow, 38 | Action = ChangeAction.Transform, 39 | Type = ChangeType 40 | }; 41 | } 42 | 43 | /// <summary>IChange wrapping Constructor</summary> 44 | public static TransformChange CreateFrom(IChange change) 45 | { 46 | return new TransformChange 47 | { 48 | Transform = JsonSerializer.Deserialize<CTransform>(change.Payload), 49 | Payload = change.Payload, 50 | Owner = change.Owner, 51 | Stamp = change.Stamp, 52 | Id = change.Id, 53 | Action = ChangeAction.Transform 54 | }; 55 | } 56 | 57 | /// <summary>Creates a Transform Change</summary> 58 | public static TransformChange CreateNew(CTransform transform, string userName, Guid id) 59 | { 60 | return new TransformChange 61 | { 62 | Id = id, 63 | Owner = userName, 64 | Payload = JsonSerializer.Serialize(transform, Options.Default), 65 | Stamp = DateTime.UtcNow, 66 | Action = ChangeAction.Transform 67 | }; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Crash.Common/Collections/FixedSizedQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | namespace Crash.Common.Collections 4 | { 5 | /// <summary>A Queue of a predetermined size</summary> 6 | public sealed class FixedSizedQueue<T> : IReadOnlyCollection<T> 7 | { 8 | private readonly Queue<T> _idleQueue; 9 | 10 | /// <summary>The Size of the Queue</summary> 11 | public readonly int Size; 12 | 13 | /// <summary></summary> 14 | public FixedSizedQueue(int size) 15 | { 16 | Size = size; 17 | _idleQueue = new Queue<T>(); 18 | } 19 | 20 | /// <summary>Current count of the Queue</summary> 21 | public int Count => _idleQueue.Count; 22 | 23 | /// <summary>GetEnumerator</summary> 24 | public IEnumerator<T> GetEnumerator() 25 | { 26 | return _idleQueue.GetEnumerator(); 27 | } 28 | 29 | /// <summary>GetEnumerator</summary> 30 | IEnumerator IEnumerable.GetEnumerator() 31 | { 32 | return _idleQueue.GetEnumerator(); 33 | } 34 | 35 | /// <summary>Adds an item to the Queue, removing the first item if adding would put it oversize.</summary> 36 | public void Enqueue(T item) 37 | { 38 | if (_idleQueue.Count >= Size) 39 | { 40 | _idleQueue.Dequeue(); 41 | } 42 | 43 | _idleQueue.Enqueue(item); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Crash.Common/Collections/IdleAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Events; 2 | 3 | namespace Crash.Events 4 | { 5 | /// <summary> 6 | /// Idle Actions are called in a FIFO order by the IdleQueue. 7 | /// Inheriting from this is fine. 8 | /// Do not override Invoke however. 9 | /// </summary> 10 | public class IdleAction : IDisposable 11 | { 12 | private readonly Action<IdleArgs> _action; 13 | private readonly IdleArgs _args; 14 | public readonly string Name; 15 | 16 | /// <summary>Constructs an Idle Action</summary> 17 | /// <param name="action">The Action to be called on Idle</param> 18 | /// <param name="args">The Args to be passed into the Action</param> 19 | /// <exception cref="ArgumentNullException">If any inputs are null</exception> 20 | public IdleAction(Action<IdleArgs> action, IdleArgs args, string name = "") 21 | { 22 | _action = action ?? throw new ArgumentNullException($"{nameof(action)} is null"); 23 | _args = args ?? throw new ArgumentNullException($"{nameof(args)} is null"); 24 | Name = name; 25 | } 26 | 27 | /// <summary>True if successfully invoked</summary> 28 | internal bool Invoked { get; set; } 29 | 30 | 31 | public void Dispose() 32 | { 33 | } 34 | 35 | /// <summary>Invokes the Action (If it hasn't already been invoked)</summary> 36 | public void Invoke() 37 | { 38 | if (Invoked) 39 | { 40 | return; 41 | } 42 | 43 | _action(_args); 44 | Invoked = true; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Crash.Common/Collections/IdleQueue.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Common.Document; 4 | using Crash.Common.Events; 5 | 6 | namespace Crash.Events 7 | { 8 | /// <summary>A Queue for running during the Rhino Idle Event.</summary> 9 | public sealed class IdleQueue : IEnumerable<IdleAction> 10 | { 11 | private readonly CrashDoc _hostDoc; 12 | private readonly ConcurrentQueue<IdleAction> _idleQueue; 13 | 14 | /// <summary>Constructs an Idle Queue</summary> 15 | public IdleQueue(CrashDoc hostDoc) 16 | { 17 | _hostDoc = hostDoc; 18 | _idleQueue = new ConcurrentQueue<IdleAction>(); 19 | } 20 | 21 | /// <summary>The number of items in the Queue</summary> 22 | public int Count => _idleQueue.Count; 23 | 24 | /// <summary>GetEnumerator</summary> 25 | public IEnumerator<IdleAction> GetEnumerator() 26 | { 27 | return _idleQueue.GetEnumerator(); 28 | } 29 | 30 | /// <summary>GetEnumerator</summary> 31 | IEnumerator IEnumerable.GetEnumerator() 32 | { 33 | return _idleQueue.GetEnumerator(); 34 | } 35 | 36 | /// <summary>Adds an Action to the Queue</summary> 37 | public void AddAction(IdleAction action) 38 | { 39 | _idleQueue.Enqueue(action); 40 | } 41 | 42 | /// <summary>Attempts to run the next Action</summary> 43 | public void RunNextAction() 44 | { 45 | if (_idleQueue.IsEmpty) 46 | { 47 | return; 48 | } 49 | 50 | if (!_idleQueue.TryDequeue(out var action)) 51 | { 52 | return; 53 | } 54 | 55 | action?.Invoke(); 56 | OnItemProcessed?.Invoke(this, new CrashEventArgs(_hostDoc)); 57 | 58 | if (_idleQueue.IsEmpty) 59 | { 60 | OnCompletedQueue?.Invoke(this, new CrashEventArgs(_hostDoc)); 61 | } 62 | } 63 | 64 | /// <summary>Forces the Queue to run until empty</summary> 65 | public void ForceCycleQueue() 66 | { 67 | while (!_idleQueue.IsEmpty) 68 | { 69 | RunNextAction(); 70 | } 71 | } 72 | 73 | /// <summary>Fires when the queue has finished parsing more than 1 item.</summary> 74 | public event EventHandler<CrashEventArgs> OnCompletedQueue; 75 | 76 | public event EventHandler<CrashEventArgs> OnItemProcessed; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/Client/ClientErrors.cs: -------------------------------------------------------------------------------- 1 | using System.Net.WebSockets; 2 | 3 | using Crash.Common.App; 4 | using Crash.Common.Events; 5 | 6 | using Microsoft.AspNetCore.SignalR; 7 | using Microsoft.AspNetCore.SignalR.Client; 8 | 9 | namespace Crash.Common.Communications; 10 | 11 | /// <summary> 12 | /// Crash client class 13 | /// </summary> 14 | public sealed partial class CrashClient 15 | { 16 | 17 | private async Task ConnectionReconnectingAsync(Exception? arg) 18 | { 19 | var closedTask = arg switch 20 | { 21 | HubException => ChangesCouldNotBeSent(), 22 | _ => Task.CompletedTask 23 | }; 24 | 25 | await closedTask; 26 | } 27 | 28 | private async Task ConnectionClosedAsync(Exception? arg) 29 | { 30 | var closedTask = arg switch 31 | { 32 | HubException => ChangesCouldNotBeSent(), 33 | WebSocketException => ServerIndicatedPossibleClosure(), 34 | OperationCanceledException => ServerClosedUnexpectidly(), 35 | _ => Task.CompletedTask 36 | }; 37 | 38 | await closedTask; 39 | } 40 | 41 | private async Task ServerClosedUnexpectidly() 42 | { 43 | if (ClosedByUser) return; 44 | OnServerClosed?.Invoke(this, new CrashEventArgs(CrashDoc)); 45 | } 46 | 47 | private async Task ServerIndicatedPossibleClosure() 48 | { 49 | 50 | } 51 | 52 | private async Task ChangesCouldNotBeSent() 53 | { 54 | // TODO : Remove 55 | } 56 | 57 | private Task ConnectionReconnectedAsync(string? arg) 58 | { 59 | return Task.CompletedTask; 60 | } 61 | 62 | private async Task HandleReconnectAttempt(Exception? exception) 63 | { 64 | var timeoutTime = TimeSpan.FromSeconds(8).TotalSeconds / 2.0; 65 | var timeoutChunk = timeoutTime / 10.0; 66 | 67 | for(double i = 0; i < timeoutTime; i+=timeoutChunk) 68 | { 69 | await Task.Delay((int)timeoutChunk); 70 | if (_connection.State == HubConnectionState.Connected) return; 71 | } 72 | 73 | CrashApp.InformUser("Attempting to reconnect to Server ..."); 74 | _connection.Reconnected += InformUserOfReconnection; 75 | } 76 | 77 | private async Task InformUserOfReconnection(string? arg) 78 | { 79 | _connection.Reconnected -= InformUserOfReconnection; 80 | CrashApp.InformUser("Reconnected successfully"); 81 | } 82 | 83 | /// <summary> 84 | /// Fires when the connection to the Server closes and cannot be recovered 85 | /// </summary> 86 | public event EventHandler<CrashEventArgs> OnServerClosed; 87 | 88 | } 89 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/Client/CrashRetryPolicy.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.SignalR.Client; 2 | 3 | namespace Crash.Common.Communications; 4 | 5 | internal class CrashRetryPolicy : IRetryPolicy 6 | { 7 | 8 | private Dictionary<int, TimeSpan> retryDelays { get; } = new() 9 | { 10 | { 0, TimeSpan.FromMilliseconds(10)}, 11 | { 1, TimeSpan.FromMilliseconds(50)}, 12 | { 2, TimeSpan.FromMilliseconds(100)}, 13 | { 3, TimeSpan.FromSeconds(1)}, 14 | { 4, TimeSpan.FromSeconds(2.5)}, 15 | { 5, TimeSpan.FromSeconds(5)}, 16 | }; 17 | 18 | public TimeSpan? NextRetryDelay(RetryContext retryContext) 19 | { 20 | int index = (int)retryContext.PreviousRetryCount; 21 | if (retryDelays.TryGetValue(index, out TimeSpan delay)) 22 | return delay; 23 | 24 | return null; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/Client/FromClientToServer.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Events; 2 | 3 | using Microsoft.AspNetCore.SignalR.Client; 4 | 5 | namespace Crash.Common.Communications; 6 | 7 | public class MissingHubConnection : Exception 8 | { 9 | public MissingHubConnection() : base("Hub Connection could not be found or created!") {} 10 | } 11 | 12 | /// <summary> 13 | /// Crash client class 14 | /// </summary> 15 | public sealed partial class CrashClient 16 | { 17 | 18 | internal Func<Uri, IRetryPolicy, Task<HubConnection>> GetHubConnection { get; set; } 19 | 20 | internal static IRetryPolicy RetryPolicy => new CrashRetryPolicy(); 21 | 22 | public async Task<Exception> RegisterConnection(string userName, Uri url) 23 | { 24 | if (string.IsNullOrEmpty(userName?.Replace(" ", ""))) 25 | { 26 | return new ArgumentException("Username cannot be empty or null"); 27 | } 28 | 29 | if (url is null) 30 | { 31 | return new UriFormatException("URL Cannot be null"); 32 | } 33 | 34 | if (!url.AbsoluteUri.Contains("/Crash")) 35 | { 36 | return new UriFormatException("URL must end in /Crash to connect!"); 37 | } 38 | 39 | try 40 | { 41 | _user = userName; 42 | if (GetHubConnection is null) 43 | return new MissingHubConnection(); 44 | 45 | _connection = await GetHubConnection(url, RetryPolicy); 46 | if (_connection is null) 47 | return new MissingHubConnection(); 48 | 49 | _connection.Reconnecting += HandleReconnectAttempt; 50 | Url = url.AbsoluteUri; 51 | RegisterConnections(); 52 | } 53 | catch (Exception ex) 54 | { 55 | return ex; 56 | } 57 | 58 | return null!; 59 | } 60 | 61 | public async Task SendChangesToServerAsync(IAsyncEnumerable<Change> changeStream) 62 | { 63 | try 64 | { 65 | await _connection.InvokeAsync(SEND_RECIEVE_STREAM, changeStream); 66 | } 67 | catch 68 | { 69 | OnPushChangeFailed?.Invoke(this, new CrashChangeArgs(CrashDoc, changeStream.ToEnumerable())); 70 | } 71 | } 72 | 73 | /// <summary> 74 | /// Fires when a Change fails to be sent to the Server 75 | /// </summary> 76 | public event EventHandler<CrashChangeArgs> OnPushChangeFailed; 77 | 78 | } 79 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/Client/FromServerToClient.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Events; 2 | 3 | using Microsoft.AspNetCore.SignalR.Client; 4 | 5 | namespace Crash.Common.Communications; 6 | 7 | /// <summary> 8 | /// Crash client class 9 | /// </summary> 10 | public sealed partial class CrashClient 11 | { 12 | 13 | private bool ChangesInitialized { get; set; } 14 | private bool UsersInitialized { get; set; } 15 | 16 | private async Task InitializeChangesAsyncOnce(IAsyncEnumerable<Change> changes) 17 | { 18 | var enumerableChanges = changes.ToEnumerable().ToList(); 19 | OnStartInitialization?.Invoke(this, new CrashInitArgs(CrashDoc, enumerableChanges.Count)); 20 | 21 | _connection.Remove(INITIALIZE_CHANGES); 22 | await this.CrashDoc.Dispatcher.NotifyClientAsync(enumerableChanges); 23 | 24 | ChangesInitialized = true; 25 | 26 | if (ChangesInitialized && UsersInitialized) 27 | OnFinishInitialization?.Invoke(this, EventArgs.Empty); 28 | } 29 | 30 | private async Task InitializeUsersAsyncOnce(IAsyncEnumerable<string> users) 31 | { 32 | _connection.Remove(INITIALIZE_USERS); 33 | await foreach (var user in users) 34 | { 35 | this.CrashDoc.Users.Add(user); 36 | } 37 | 38 | UsersInitialized = true; 39 | 40 | if (ChangesInitialized && UsersInitialized) 41 | OnFinishInitialization?.Invoke(this, EventArgs.Empty); 42 | } 43 | 44 | private async Task RecieveChangesFromServerToClientAsync(IAsyncEnumerable<Change> changes) 45 | { 46 | await this.CrashDoc.Dispatcher.NotifyClientAsync(changes.ToEnumerable()); 47 | } 48 | 49 | /// <summary>Registers Local Events responding to Server calls</summary> 50 | private void RegisterConnections() 51 | { 52 | if (Options.DryRun) return; 53 | 54 | _connection.Closed += ConnectionClosedAsync; 55 | _connection.Reconnecting += ConnectionReconnectingAsync; 56 | 57 | _connection.On<IAsyncEnumerable<Change>>(INITIALIZE_CHANGES, InitializeChangesAsyncOnce); 58 | _connection.On<IAsyncEnumerable<string>>(INITIALIZE_USERS, InitializeUsersAsyncOnce); 59 | _connection.On<IAsyncEnumerable<Change>>(SEND_RECIEVE_STREAM, RecieveChangesFromServerToClientAsync); 60 | } 61 | 62 | public event EventHandler<CrashInitArgs> OnStartInitialization; 63 | 64 | public event EventHandler OnFinishInitialization; 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/Client/ICrashClient.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Events; 2 | 3 | namespace Crash.Common.Communications 4 | { 5 | public interface ICrashClient 6 | { 7 | /// <summary>Tests for an Active Connection</summary> 8 | public bool IsConnected { get; } 9 | 10 | /// <summary>The connection address</summary> 11 | public string Url { get; } 12 | 13 | /// <summary>Registers the client and its connection url</summary> 14 | /// <param name="userName">The User of the Client</param> 15 | /// <param name="url">url of the server the client will talk to</param> 16 | Task<Exception> RegisterConnection(string userName, Uri url); 17 | 18 | /// <summary>Starts the Client</summary> 19 | /// <exception cref="NullReferenceException">If CrashDoc is null</exception> 20 | /// <exception cref="Exception">If UserName is empty</exception> 21 | public Task<Exception?> StartLocalClientAsync(); 22 | 23 | /// <summary>Stops the Connection</summary> 24 | public Task StopAsync(); 25 | 26 | /// <summary> 27 | /// Pushes many unique changes at once 28 | /// An example of this may be copying 10 unique items 29 | /// </summary> 30 | public Task SendChangesToServerAsync(IAsyncEnumerable<Change> changeStream); 31 | 32 | public event EventHandler<CrashInitArgs> OnStartInitialization; 33 | 34 | public event EventHandler OnFinishInitialization; 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Common/Communications/IEventDispatcher.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Communications 4 | { 5 | /// <summary>Abstracted Server Notification Contract</summary> 6 | public interface IEventDispatcher 7 | { 8 | /// <summary> 9 | /// Notifies the Dispatcher of any Events that should notify the server 10 | /// Avoid Subscribing to events and pinging the server yourself 11 | /// Wrap any related events with this method. 12 | /// </summary> 13 | /// <param name="changeAction">The ChangeAction</param> 14 | /// <param name="sender">The sender of the Event</param> 15 | /// <param name="args">The EventArgs</param> 16 | Task NotifyServerAsync(IEnumerable<Change> changes); 17 | 18 | Task NotifyClientAsync(IEnumerable<Change> changes); 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crash.Common/Crash.Common.csproj: -------------------------------------------------------------------------------- 1 | <Project Sdk="Microsoft.NET.Sdk"> 2 | 3 | <PropertyGroup> 4 | <TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks> 5 | <RootNamespace>Crash.Common</RootNamespace> 6 | <Title>Crash.Common 7 | Callum Sykes 8 | Crash.Common is the foundation of Crash inside Rhino 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | NU5104,NU1903 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/Crash.Common/Document/CrashDoc.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | using Crash.Common.App; 4 | using Crash.Common.Communications; 5 | using Crash.Common.Tables; 6 | using Crash.Events; 7 | 8 | [assembly: InternalsVisibleTo("Crash.Common.Tests")] 9 | 10 | namespace Crash.Common.Document 11 | { 12 | /// The Crash Document 13 | public sealed class CrashDoc : IEquatable, IDisposable 14 | { 15 | private Guid Id { get; } 16 | 17 | #region constructors 18 | 19 | /// Constructs a Crash Doc 20 | public CrashDoc() 21 | { 22 | Id = Guid.NewGuid(); 23 | 24 | Users = new UserTable(this); 25 | Tables = new CacheTable(this); 26 | Tables.AddTable(new TemporaryChangeTable(this)); 27 | Tables.AddTable(new RealisedChangeTable(this)); 28 | Cameras = new CameraTable(this); 29 | 30 | LocalClient = new CrashClient(this); 31 | 32 | Queue = new IdleQueue(this); 33 | } 34 | 35 | #endregion 36 | 37 | private bool _documentIsBusy { get; set; } 38 | 39 | // TODO : What if someone DOES something when we're adding stuff? 40 | /// 41 | /// Marks the Document as in a "Busy State" state which means 42 | /// Nothing can be sent to the server 43 | /// 44 | public bool DocumentIsBusy 45 | { 46 | get => _documentIsBusy; 47 | set 48 | { 49 | _documentIsBusy = value; 50 | CrashApp.Log($"{nameof(DocumentIsBusy)} was set to {value}"); 51 | } 52 | } 53 | 54 | #region Queue 55 | 56 | /// The Idle Queue for the Crash Document 57 | public IdleQueue Queue { get; private set; } 58 | 59 | #endregion 60 | 61 | public void Dispose() 62 | { 63 | LocalClient?.StopAsync(); 64 | } 65 | 66 | #region Connectivity 67 | 68 | /// The Local Client for the Crash Doc 69 | public ICrashClient LocalClient { get; set; } 70 | 71 | /// The current Documents Dispatcher 72 | public IEventDispatcher Dispatcher { get; set; } 73 | 74 | #endregion 75 | 76 | #region Tables 77 | 78 | /// The Users Table for the Crash Doc 79 | public UserTable Users { get; } 80 | 81 | /// Stores all of the Tables in the Crash Doc 82 | public CacheTable Tables { get; } 83 | 84 | /// The Camera Table for the crash Doc 85 | public CameraTable Cameras { get; } 86 | 87 | #endregion 88 | 89 | #region Methods 90 | 91 | public bool Equals(CrashDoc? other) 92 | { 93 | return other?.Id == Id; 94 | } 95 | 96 | public override bool Equals(object? obj) 97 | { 98 | return obj is CrashDoc other && Equals(other); 99 | } 100 | 101 | public override int GetHashCode() 102 | { 103 | return Id.GetHashCode(); 104 | } 105 | 106 | #endregion 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/Crash.Common/Document/User.cs: -------------------------------------------------------------------------------- 1 | using System.Drawing; 2 | using System.Security.Cryptography; 3 | using System.Text; 4 | 5 | namespace Crash.Common.Document 6 | { 7 | /// The state of the Camera for this user 8 | public enum CameraState 9 | { 10 | None = 0, 11 | Visible = 1, 12 | Follow = 2 13 | } 14 | 15 | /// An external collaborator 16 | public struct User : IEquatable 17 | { 18 | public static readonly Color DefaultColour = Color.Gray; 19 | 20 | /// Is this user Visible? 21 | public bool Visible { get; set; } = true; 22 | 23 | /// Name of the user 24 | public string Name { get; } 25 | 26 | /// Color of the user 27 | public Color Color { get; set; } 28 | 29 | /// The current state of the Users Camera 30 | public CameraState Camera { get; set; } = CameraState.Visible; 31 | 32 | /// User Constructor 33 | /// the name of the user 34 | public User(string inputName) 35 | { 36 | Name = CleanedUserName(inputName); 37 | 38 | if (string.IsNullOrEmpty(Name)) 39 | { 40 | Color = DefaultColour; 41 | } 42 | else 43 | { 44 | var md5 = MD5.Create(); 45 | var hash = md5.ComputeHash(Encoding.UTF8.GetBytes(Name)); 46 | Color = Color.FromArgb(hash[0], hash[1], hash[2]); 47 | } 48 | 49 | Camera = CameraState.Visible; 50 | } 51 | 52 | /// Checks user for being valid 53 | public bool IsValid() 54 | { 55 | return !string.IsNullOrEmpty(Name); 56 | } 57 | 58 | /// Ensures a username is lowercase and not null 59 | /// Any string 60 | /// A non-null lowercase name 61 | public static string CleanedUserName(string username) 62 | { 63 | return username?.ToLowerInvariant() ?? string.Empty; 64 | } 65 | 66 | public override int GetHashCode() 67 | { 68 | return CleanedUserName(Name).GetHashCode(); 69 | } 70 | 71 | public override bool Equals(object? obj) 72 | { 73 | return obj is User user && Equals(user); 74 | } 75 | 76 | 77 | public bool Equals(User other) 78 | { 79 | if (string.IsNullOrEmpty(Name)) 80 | return false; 81 | 82 | return Name.Equals(other.Name, StringComparison.InvariantCultureIgnoreCase); 83 | } 84 | 85 | public static bool operator ==(User left, User right) 86 | { 87 | return left.Equals(right); 88 | } 89 | 90 | public static bool operator !=(User left, User right) 91 | { 92 | return !(left == right); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Crash.Common/Events/CrashChangeArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Events 4 | { 5 | /// 6 | /// Captures changes in a set of s 7 | /// 8 | public sealed class CrashChangeArgs : CrashEventArgs 9 | { 10 | /// 11 | /// The updated s 12 | /// 13 | public readonly IEnumerable Changes; 14 | 15 | public CrashChangeArgs(CrashDoc crashDoc, IEnumerable changes) 16 | : base(crashDoc) 17 | { 18 | Changes = changes; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crash.Common/Events/CrashEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Events 4 | { 5 | /// The Crash Event Args 6 | public class CrashEventArgs : EventArgs 7 | { 8 | /// The Crash Doc of these Args 9 | public readonly CrashDoc CrashDoc; 10 | 11 | private Dictionary _args { get; } = new Dictionary(); 12 | 13 | /// Default Constructor 14 | public CrashEventArgs(CrashDoc crashDoc) 15 | { 16 | CrashDoc = crashDoc; 17 | } 18 | 19 | public CrashEventArgs(CrashDoc crashDoc, Dictionary args) 20 | { 21 | CrashDoc = crashDoc; 22 | _args = args ?? new Dictionary(); 23 | } 24 | 25 | /// Get the Value of a Key 26 | public T TryGet(string name) 27 | { 28 | try 29 | { 30 | if (_args.TryGetValue(name, out var value)) 31 | { 32 | return (T)value; 33 | } 34 | } 35 | catch 36 | { 37 | // ignored 38 | } 39 | 40 | return default; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Crash.Common/Events/CrashInitArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Events 4 | { 5 | 6 | public sealed class CrashInitArgs : CrashEventArgs 7 | { 8 | 9 | public int ChangeCount { get; } 10 | 11 | public CrashInitArgs(CrashDoc crashDoc, int changeCount) 12 | : base(crashDoc) 13 | { 14 | ChangeCount = changeCount; 15 | } 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /src/Crash.Common/Events/IdleArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Events 4 | { 5 | /// Arguments for an Idle Action 6 | public class IdleArgs : EventArgs 7 | { 8 | /// The affected Change 9 | public readonly IChange Change; 10 | 11 | /// The CrashDoc this event was called in 12 | public readonly CrashDoc Doc; 13 | 14 | /// Constructs Args for an Idle Event 15 | public IdleArgs(CrashDoc crashDoc, IChange change) 16 | { 17 | Doc = crashDoc; 18 | Change = change; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crash.Common/Exceptions/OversizedChangeException.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Common.Exceptions 2 | { 3 | /// OversizedChangeException 4 | public sealed class OversizedChangeException : Exception 5 | { 6 | /// OversizedChangeException 7 | public OversizedChangeException(string? message) : base(message) 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/Crash.Common/Logging/CrashLogger.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | 3 | using Microsoft.Extensions.Logging; 4 | 5 | namespace Crash.Common.Logging 6 | { 7 | // TODO : Merge with CrashApp 8 | /// Enables logging for Crash 9 | public sealed class CrashLogger : ILogger, IDisposable 10 | { 11 | private readonly LogLevel _currentLevel; 12 | 13 | static CrashLogger() 14 | { 15 | Logger = new CrashLogger(); 16 | } 17 | 18 | internal CrashLogger() 19 | { 20 | _currentLevel = Debugger.IsAttached ? LogLevel.Trace : LogLevel.Information; 21 | } 22 | 23 | public static CrashLogger Logger { get; private set; } 24 | 25 | public void Dispose() 26 | { 27 | } 28 | 29 | public IDisposable BeginScope(TState state) 30 | { 31 | return this; 32 | } 33 | 34 | public bool IsEnabled(LogLevel logLevel) 35 | { 36 | return logLevel >= _currentLevel; 37 | } 38 | 39 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, 40 | Func formatter) 41 | { 42 | var _eventId = eventId.Name; 43 | var formattedMessage = formatter.Invoke(state, exception); 44 | var message = $"{logLevel} : {formattedMessage} : {_eventId}"; 45 | 46 | LogFiles.writeLogMessage(message); 47 | } 48 | 49 | internal sealed class LogFiles 50 | { 51 | private static readonly string _logDirectory; 52 | private static readonly string _logFileName; 53 | private static readonly string _logFilePath; 54 | 55 | private static CrashLogger _logger; 56 | 57 | static LogFiles() 58 | { 59 | var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 60 | _logDirectory = Path.Combine(appData, "Crash", "Logs"); 61 | _logFileName = $"{DateTime.UtcNow:yyyy_MM_dd_HH_mm_ss}.log"; 62 | _logFilePath = Path.Combine(_logDirectory, _logFileName); 63 | 64 | createLogFile(); 65 | } 66 | 67 | internal LogFiles(CrashLogger logger) 68 | { 69 | _logger = logger; 70 | } 71 | 72 | private static void createLogFile() 73 | { 74 | if (!Directory.Exists(_logDirectory)) 75 | { 76 | Directory.CreateDirectory(_logDirectory); 77 | } 78 | 79 | if (!File.Exists(_logFilePath)) 80 | { 81 | File.Create(_logFilePath); 82 | } 83 | } 84 | 85 | internal static void writeLogMessage(string message) 86 | { 87 | try 88 | { 89 | File.AppendAllLines(_logFilePath, new[] { message }); 90 | } 91 | catch (Exception) 92 | { 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Crash.Common/Logging/CrashLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Logging; 2 | 3 | namespace Crash.Common.Logging 4 | { 5 | internal sealed class CrashLoggerProvider : ILoggerProvider 6 | { 7 | public ILogger CreateLogger(string categoryName) 8 | { 9 | return CrashLogger.Logger; 10 | } 11 | 12 | public void Dispose() 13 | { 14 | CrashLogger.Logger.Dispose(); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Crash.Common/Properties/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Concurrent; 3 | global using System.Collections.Generic; 4 | global using System.Linq; 5 | global using System.Threading.Tasks; 6 | 7 | global using Crash.Changes; 8 | 9 | using System.Runtime.CompilerServices; 10 | 11 | [assembly: InternalsVisibleTo("Crash.Common.Tests")] 12 | [assembly: InternalsVisibleTo("Crash.Integration.Tests")] 13 | -------------------------------------------------------------------------------- /src/Crash.Common/Serialization/CameraConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Crash.Common.View; 5 | using Crash.Geometry; 6 | 7 | namespace Crash.Common.Serialization 8 | { 9 | /// 10 | /// Converts the Camera class to and from JSON efficiently 11 | /// 12 | public sealed class CameraConverter : JsonConverter 13 | { 14 | public override Camera Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 15 | { 16 | if (reader.TokenType != JsonTokenType.StartArray) 17 | { 18 | throw new JsonException(); 19 | } 20 | 21 | if (reader.Read() && reader.TryGetDouble(out var targetX) && 22 | reader.Read() && reader.TryGetDouble(out var targetY) && 23 | reader.Read() && reader.TryGetDouble(out var targetZ) && 24 | reader.Read() && reader.TryGetDouble(out var locationX) && 25 | reader.Read() && reader.TryGetDouble(out var locationY) && 26 | reader.Read() && reader.TryGetDouble(out var locationZ) && 27 | reader.Read() && reader.TryGetInt64(out var ticks) && 28 | reader.Read() && reader.TokenType == JsonTokenType.EndArray) 29 | { 30 | var location = new CPoint(locationX, locationY, locationZ); 31 | var target = new CPoint(targetX, targetY, targetZ); 32 | 33 | return new Camera(location, target) { Stamp = new DateTime(ticks) }; 34 | } 35 | 36 | throw new JsonException(); 37 | } 38 | 39 | public override void Write(Utf8JsonWriter writer, Camera value, JsonSerializerOptions options) 40 | { 41 | var target = value.Target; 42 | var location = value.Location; 43 | 44 | writer.WriteStartArray(); 45 | 46 | writer.WriteNumberValue(target.X); 47 | writer.WriteNumberValue(target.Y); 48 | writer.WriteNumberValue(target.Z); 49 | 50 | writer.WriteNumberValue(location.X); 51 | writer.WriteNumberValue(location.Y); 52 | writer.WriteNumberValue(location.Z); 53 | 54 | writer.WriteNumberValue(value.Stamp.Ticks); 55 | 56 | writer.WriteEndArray(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Crash.Common/Serialization/Options.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace Crash.Common.Serialization 5 | { 6 | /// 7 | /// Default Internal Json Serialization. 8 | /// This has nothing to do with the SignalR Hub 9 | /// 10 | internal static class Options 11 | { 12 | internal static readonly JsonSerializerOptions Default; 13 | 14 | static Options() 15 | { 16 | Default = new JsonSerializerOptions 17 | { 18 | IgnoreReadOnlyFields = true, 19 | IgnoreReadOnlyProperties = true, 20 | IncludeFields = true, 21 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, 22 | ReadCommentHandling = JsonCommentHandling.Skip, 23 | WriteIndented = true 24 | }; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Crash.Common/Tables/CacheTable.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Tables 4 | { 5 | 6 | /// 7 | /// Stores all of the tables related to the Crash Doc 8 | /// 9 | public sealed class CacheTable 10 | { 11 | private CrashDoc CrashDoc { get; } 12 | 13 | internal CacheTable(CrashDoc hostDoc) 14 | { 15 | CrashDoc = hostDoc; 16 | CachedTables = new Dictionary(); 17 | } 18 | 19 | private Dictionary CachedTables { get; } 20 | 21 | /// 22 | /// Adds a table into the cache 23 | /// 24 | /// The table to add 25 | public void AddTable(ICacheTable table) 26 | { 27 | if (table is null) return; 28 | 29 | var key = table.GetType().Name.ToLowerInvariant(); 30 | if (!CachedTables.ContainsKey(key)) 31 | { 32 | CachedTables.Add(key, table); 33 | } 34 | } 35 | 36 | /// 37 | /// Returns the table, or null if it doesn't exist 38 | /// 39 | /// The type related to the table 40 | /// Returns the table if found, null otherwise 41 | public TTable? Get() where TTable : class 42 | { 43 | var key = typeof(TTable).Name.ToLowerInvariant(); 44 | CachedTables.TryGetValue(key, out var table); 45 | return table as TTable; 46 | } 47 | 48 | /// 49 | /// Attempts to return a table 50 | /// 51 | /// The type related to the table 52 | /// True if the table exists 53 | public bool TryGet(out TTable table) where TTable : class 54 | { 55 | var key = typeof(TTable).Name.ToLowerInvariant(); 56 | CachedTables.TryGetValue(key, out var cachedTable); 57 | table = cachedTable as TTable; 58 | return table is not null; 59 | } 60 | 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Crash.Common/Tables/CameraTable.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Common.Changes; 4 | using Crash.Common.Collections; 5 | using Crash.Common.Document; 6 | using Crash.Common.View; 7 | 8 | namespace Crash.Common.Tables 9 | { 10 | public sealed class CameraTable : IEnumerable 11 | { 12 | internal const int MAX_CAMERAS_IN_QUEUE = 3; 13 | 14 | private readonly Dictionary> _cameraLocations; 15 | 16 | private readonly CrashDoc _crashDoc; 17 | 18 | 19 | internal CameraTable(CrashDoc hostDoc) 20 | { 21 | _cameraLocations = new Dictionary>(); 22 | _crashDoc = hostDoc; 23 | } 24 | 25 | public IEnumerator GetEnumerator() 26 | { 27 | return _cameraLocations.Values.SelectMany(c => c).GetEnumerator(); 28 | } 29 | 30 | IEnumerator IEnumerable.GetEnumerator() 31 | { 32 | return GetEnumerator(); 33 | } 34 | 35 | /// 36 | /// Returns all of the Active s paired to s 37 | /// 38 | public Dictionary GetActiveCameras() 39 | { 40 | var cameras = new Dictionary(_cameraLocations.Count); 41 | foreach (var cameraLocation in _cameraLocations) 42 | { 43 | var user = _crashDoc.Users.Get(cameraLocation.Key); 44 | if (string.IsNullOrEmpty(user.Name)) 45 | { 46 | continue; 47 | } 48 | 49 | cameras.Add(user, cameraLocation.Value.FirstOrDefault()); 50 | } 51 | 52 | return cameras; 53 | } 54 | 55 | /// 56 | /// Adds a new Camera to the table. 57 | /// If there are 3 in the table it will supersede one 58 | /// 59 | /// True on success, false otherwise 60 | public bool TryAddCamera(CameraChange cameraChange) 61 | { 62 | var user = new User(cameraChange.Owner); 63 | FixedSizedQueue? queue; 64 | 65 | if (!_cameraLocations.ContainsKey(user.Name)) 66 | { 67 | queue = new FixedSizedQueue(MAX_CAMERAS_IN_QUEUE); 68 | queue.Enqueue(cameraChange.Camera); 69 | _cameraLocations.Add(user.Name, queue); 70 | } 71 | else 72 | { 73 | if (!_cameraLocations.TryGetValue(user.Name, out queue)) 74 | { 75 | return false; 76 | } 77 | 78 | queue.Enqueue(cameraChange.Camera); 79 | } 80 | 81 | return true; 82 | } 83 | 84 | /// 85 | /// Attempts to retrieve the current queue of s based on the given User 86 | /// 87 | /// True if any found 88 | public bool TryGetCamera(User user, out FixedSizedQueue cameras) 89 | { 90 | return _cameraLocations.TryGetValue(user.Name, out cameras); 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/Crash.Common/Tables/ICacheTable.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Common.Tables 2 | { 3 | public interface ICacheTable 4 | { 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/Crash.Common/View/Camera.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | using Crash.Common.Serialization; 4 | using Crash.Geometry; 5 | 6 | namespace Crash.Common.View 7 | { 8 | /// The Camera represents a user view with two points. 9 | [JsonConverter(typeof(CameraConverter))] 10 | public struct Camera : IEquatable 11 | { 12 | /// The location of the viewpont of the camera 13 | public CPoint Location { get; set; } 14 | 15 | /// The target viewpoint of the camera 16 | public CPoint Target { get; set; } 17 | 18 | /// A datetime stamp for the Camera 19 | public DateTime Stamp { get; internal set; } 20 | 21 | /// Creates a Camera 22 | public Camera(CPoint location, CPoint target) 23 | { 24 | Location = location; 25 | Target = target; 26 | Stamp = DateTime.UtcNow; 27 | } 28 | 29 | /// A non-existant Camera 30 | public static Camera None => new(CPoint.None, CPoint.None); 31 | 32 | /// Checks for Validity 33 | public bool IsValid() 34 | { 35 | return Location != Target && 36 | Location != CPoint.None && 37 | Target != CPoint.None && 38 | Stamp > DateTime.MinValue && 39 | Stamp < DateTime.MaxValue; 40 | } 41 | 42 | 43 | public override int GetHashCode() 44 | { 45 | return HashCode.Combine(Location.GetHashCode(), Target.GetHashCode()); 46 | } 47 | 48 | /// Equality Comparison 49 | public override bool Equals(object? obj) 50 | { 51 | return obj is Camera camera && camera == this; 52 | } 53 | 54 | /// Equality Comparison 55 | public bool Equals(Camera other) 56 | { 57 | return this == other; 58 | } 59 | 60 | /// Equality Comparison 61 | public static bool operator ==(Camera c1, Camera c2) 62 | { 63 | return c1.Location.Round(3).Equals(c2.Location.Round(3)) && 64 | c1.Target.Round(3).Equals(c2.Target.Round(3)); 65 | } 66 | 67 | /// Inqquality Comparison 68 | public static bool operator !=(Camera c1, Camera c2) 69 | { 70 | return !(c1 == c2); 71 | } 72 | 73 | 74 | public override string ToString() 75 | { 76 | return $"Camera {Location}/{Target}"; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Changes/ChangeHelpers.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | using Crash.Handlers; 4 | 5 | using Rhino.DocObjects; 6 | 7 | using ChangeGuid = System.Guid; 8 | using RhinoGuid = System.Guid; 9 | 10 | 11 | namespace Crash.Utils 12 | { 13 | /// Utilities for Change Objects. 14 | public static class ChangeHelpers 15 | { 16 | /// Acquires the Rhino Object given the RhinoId from an IRhinoChange 17 | public static bool TryGetRhinoObject(this IChange change, CrashDoc crashDoc, out RhinoObject rhinoObject) 18 | { 19 | rhinoObject = default; 20 | if (change is null || crashDoc is null) 21 | { 22 | return false; 23 | } 24 | 25 | if (crashDoc.Tables.TryGet(out var realTable)) return false; 26 | if (!realTable.TryGetRhinoId(change, out var rhinoId)) 27 | { 28 | return false; 29 | } 30 | 31 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(crashDoc); 32 | if (rhinoDoc is null) 33 | return false; 34 | 35 | rhinoObject = rhinoDoc.Objects.FindId(rhinoId); 36 | 37 | return rhinoObject is not null; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Crash.Handlers.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net48;net7.0 5 | Crash.Handlers 6 | Crash.Handlers 7 | Callum Sykes 8 | Crash.Handlers manipluates Crash.Common, Crash.Changes and handles Crash.Server requests. It is the foundation package for making Crash work, and allowing 3rd parties to consume crash. 9 | 10 | 11 | 12 | 13 | all 14 | 15 | 16 | 17 | NU5104,NU1903 18 | 19 | 20 | NU5104,NU1903,NU1701 21 | 22 | 23 | NU5104,NU1903,NU1701 24 | 25 | 26 | NU5104,NU1903 27 | 28 | 29 | NU5104,NU1903 30 | 31 | 32 | 33 | 34 | 35 | 36 | Refs\net48\crash.auth.dll 37 | Refs\net7.0\crash.auth.dll 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Data/BitmapConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Eto.Drawing; 5 | 6 | namespace Crash.Handlers.Data 7 | { 8 | public class BitmapConverter : JsonConverter 9 | { 10 | 11 | public override Eto.Drawing.Bitmap? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 12 | { 13 | var base64String = reader.GetString(); 14 | var base64Data = Convert.FromBase64String(base64String); 15 | 16 | return new Bitmap(base64Data); ; 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, Eto.Drawing.Bitmap bitmap, JsonSerializerOptions options) 20 | { 21 | var byteImage = bitmap.ToByteArray(ImageFormat.Png); 22 | var base64String = Convert.ToBase64String(byteImage); 23 | writer.WriteStringValue(base64String); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Data/CrashData.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using System.Text.Json; 3 | 4 | using Crash.Common.App; 5 | using Crash.Common.Document; 6 | 7 | namespace Crash.Handlers.Data 8 | { 9 | public class CrashData : ICrashInstance 10 | { 11 | 12 | public static string CrashDataDirectory 13 | { 14 | get 15 | { 16 | var appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); 17 | return System.IO.Path.Combine(appData, "crash", "desktop"); 18 | } 19 | } 20 | 21 | public static bool WriteFile(string data, string filename) 22 | { 23 | try 24 | { 25 | var crashDataDir = CrashData.CrashDataDirectory; 26 | var path = System.IO.Path.Combine(crashDataDir, filename); 27 | 28 | if (!Directory.Exists(crashDataDir)) 29 | { 30 | Directory.CreateDirectory(crashDataDir); 31 | } 32 | 33 | System.IO.File.WriteAllText(path, data); 34 | return true; 35 | } 36 | catch 37 | { 38 | return false; 39 | } 40 | } 41 | 42 | public static bool TryReadFileData(string filename, out TData data) 43 | { 44 | try 45 | { 46 | var crashDataDir = CrashData.CrashDataDirectory; 47 | var path = System.IO.Path.Combine(crashDataDir, filename); 48 | 49 | if (System.IO.File.Exists(path)) 50 | { 51 | var json = System.IO.File.ReadAllText(path); 52 | var options = new JsonDocumentOptions 53 | { 54 | AllowTrailingCommas = true, 55 | CommentHandling = JsonCommentHandling.Skip, 56 | }; 57 | 58 | data = JsonSerializer.Deserialize(json); 59 | return data is not null; 60 | } 61 | } 62 | catch { } 63 | 64 | data = default; 65 | return false; 66 | } 67 | 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Data/ModelRenderState.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.Data; 2 | 3 | [Flags] 4 | public enum ModelRenderState 5 | { 6 | None = 0, 7 | // Add = 1 << 0, 8 | // Sandbox = 1 << 1, 9 | // Debug = 1 << 2, 10 | 11 | Loading = 1 << 3, 12 | Loaded = 1 << 4, 13 | FailedToLoad = 1 << 5, 14 | 15 | RightClick = 1 << 6, 16 | Selected = 1 << 7, 17 | MouseOver = 1 << 8, 18 | } 19 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Data/SharedModelCache.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using Crash.Common.App; 4 | using Crash.Common.Document; 5 | 6 | namespace Crash.Handlers.Data 7 | { 8 | public class SharedModelCache 9 | { 10 | 11 | public record SharedModelData 12 | { 13 | public List Models { get; set; } 14 | 15 | public SharedModelData() 16 | { 17 | 18 | } 19 | 20 | public SharedModelData(List models) 21 | { 22 | Models = models; 23 | } 24 | } 25 | 26 | private const string SharedModelCacheFileName = "SharedModelCache.json"; 27 | 28 | private static JsonSerializerOptions GetJsonOptions() 29 | { 30 | var defaultOptions = new JsonSerializerOptions(JsonSerializerOptions.Default); 31 | defaultOptions.IgnoreReadOnlyFields = true; 32 | defaultOptions.IgnoreReadOnlyProperties = true; 33 | defaultOptions.IncludeFields = false; 34 | defaultOptions.WriteIndented = true; 35 | defaultOptions.PropertyNameCaseInsensitive = true; 36 | defaultOptions.Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping; 37 | 38 | return defaultOptions; 39 | } 40 | 41 | public static bool TryLoadSharedModels(out List sharedModels) 42 | { 43 | sharedModels = new(); 44 | try 45 | { 46 | if (CrashData.TryReadFileData(SharedModelCacheFileName, out var data)) 47 | { 48 | foreach (var sharedModel in data.Models) 49 | { 50 | if (string.IsNullOrEmpty(sharedModel?.ModelAddress)) continue; 51 | if (sharedModels.Any(sm => SharedModel.Equals(sm, sharedModel))) continue; 52 | sharedModels.Add(sharedModel); 53 | } 54 | } 55 | } 56 | catch 57 | { 58 | return false; 59 | } 60 | 61 | return true; 62 | } 63 | 64 | 65 | public static bool TrySaveSharedModels(List sharedModels) 66 | { 67 | try 68 | { 69 | if (sharedModels is null) return false; 70 | var crashDataDir = CrashData.CrashDataDirectory; 71 | var options = GetJsonOptions(); 72 | var json = JsonSerializer.Serialize(new SharedModelData(sharedModels), options); 73 | if (string.IsNullOrEmpty(json)) return false; 74 | 75 | CrashData.WriteFile(json, SharedModelCacheFileName); 76 | return true; 77 | } 78 | catch { } 79 | return false; 80 | } 81 | 82 | public static bool TryGetSharedModelsData(CrashDoc crashDoc, out List activeModels) 83 | { 84 | activeModels = new(); 85 | if (!TryLoadSharedModels(out var sharedModels)) return false; 86 | activeModels = sharedModels; 87 | return activeModels?.Count > 0; 88 | } 89 | 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashLayerArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Handlers.Plugins.Layers; 3 | 4 | namespace Crash.Handlers.InternalEvents 5 | { 6 | /// Wraps Rhino Object change Event Args 7 | public sealed class CrashLayerArgs : EventArgs 8 | { 9 | public readonly ChangeAction Action; 10 | 11 | /// The modified Crash Object 12 | public readonly CrashLayer CrashLayer; 13 | 14 | /// The Crash Doc of these Args 15 | public readonly CrashDoc Doc; 16 | 17 | /// Object Updates 18 | public readonly Dictionary Updates; 19 | 20 | /// Default constructor 21 | /// The modified Crash Object 22 | /// The given updates 23 | public CrashLayerArgs(CrashDoc crashDoc, CrashLayer layer, ChangeAction action, 24 | Dictionary updates) 25 | { 26 | Doc = crashDoc; 27 | Action = action; 28 | CrashLayer = layer; 29 | Updates = updates; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashObject.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | 4 | using Rhino.DocObjects; 5 | 6 | namespace Crash.Handlers.InternalEvents 7 | { 8 | /// Wraps an and Id 9 | public readonly struct CrashObject 10 | { 11 | /// The Change Id 12 | public readonly Guid ChangeId; 13 | 14 | /// The Rhino Id 15 | public readonly Guid RhinoId; 16 | 17 | /// 18 | /// Creates a new CrashObject 19 | /// 20 | internal CrashObject(CrashDoc crashDoc, RhinoObject rhinoObject) 21 | { 22 | RhinoId = rhinoObject.Id; 23 | if (crashDoc.Tables.TryGet(out var realTable)) 24 | { 25 | realTable.TryGetChangeId(rhinoObject.Id, out ChangeId); 26 | } 27 | } 28 | 29 | internal CrashObject(Guid changeId, Guid rhinoId) 30 | { 31 | ChangeId = changeId; 32 | RhinoId = rhinoId; 33 | } 34 | 35 | public override int GetHashCode() 36 | { 37 | return HashCode.Combine(ChangeId, RhinoId); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashObjectEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | 4 | using Rhino.DocObjects; 5 | using Rhino.Geometry; 6 | 7 | namespace Crash.Handlers.InternalEvents 8 | { 9 | /// Wraps RhinoObjectEventArgs 10 | public sealed class CrashObjectEventArgs : EventArgs 11 | { 12 | /// Change Id 13 | public readonly Guid ChangeId; 14 | 15 | /// The Crash Doc this Object comes from 16 | public readonly CrashDoc Doc; 17 | 18 | /// The Event Geometry 19 | public readonly GeometryBase Geometry; 20 | 21 | /// The Event Rhino Id 22 | public readonly Guid RhinoId; 23 | 24 | /// UnDelete flag 25 | public readonly bool UnDelete; 26 | 27 | /// Constructor mainly for tests 28 | public CrashObjectEventArgs(CrashDoc crashDoc, GeometryBase geometry, Guid rhinoId, Guid changeId = default, 29 | bool unDelete = false) 30 | { 31 | Doc = crashDoc; 32 | RhinoId = rhinoId; 33 | Geometry = geometry; 34 | if (changeId == default) 35 | { 36 | if (crashDoc.Tables.TryGet(out var realisedTable) && 37 | !realisedTable.TryGetChangeId(rhinoId, out ChangeId)) 38 | { 39 | ChangeId = Guid.NewGuid(); 40 | } 41 | } 42 | else 43 | { 44 | ChangeId = changeId; 45 | } 46 | 47 | UnDelete = unDelete; 48 | } 49 | 50 | /// Default Constructor 51 | public CrashObjectEventArgs(CrashDoc crashDoc, RhinoObject rhinoObject, Guid changeId = default, 52 | bool unDelete = false) 53 | : this(crashDoc, rhinoObject.Geometry, rhinoObject.Id, changeId, unDelete) 54 | { 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashSelectionEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Handlers.InternalEvents 4 | { 5 | /// Wraps the RhinoSelection and Deselection Events 6 | public sealed class CrashSelectionEventArgs : EventArgs 7 | { 8 | /// Related Event Objets 9 | public readonly IEnumerable CrashObjects; 10 | 11 | /// The Crash Doc of these Args 12 | public readonly CrashDoc Doc; 13 | 14 | /// Was this a Selection Event or Deselection Event 15 | public readonly bool Selected; 16 | 17 | /// Singular Selection/Deselection Event Constructor 18 | private CrashSelectionEventArgs(CrashDoc crashDoc, bool selected, 19 | IEnumerable crashObjects) 20 | { 21 | Doc = crashDoc; 22 | CrashObjects = crashObjects; 23 | Selected = selected; 24 | } 25 | 26 | /// Creates a new DeSelection Event for One item 27 | public static CrashSelectionEventArgs CreateSelectionEvent(CrashDoc crashDoc, 28 | IEnumerable crashObjects) 29 | { 30 | return new CrashSelectionEventArgs(crashDoc, true, crashObjects); 31 | } 32 | 33 | /// Creates a new Selection Event for One item 34 | public static CrashSelectionEventArgs CreateDeSelectionEvent(CrashDoc crashDoc, 35 | IEnumerable crashObjects) 36 | { 37 | return new CrashSelectionEventArgs(crashDoc, false, crashObjects); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashTransformEventArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Geometry; 3 | 4 | namespace Crash.Handlers.InternalEvents 5 | { 6 | /// Wraps Rhino Transform Event Args 7 | public sealed class CrashTransformEventArgs : EventArgs 8 | { 9 | /// The Crash Doc of these Args 10 | public readonly CrashDoc Doc; 11 | 12 | /// The affected Objects 13 | public readonly IEnumerable Objects; 14 | 15 | /// Will objects be copied? 16 | public readonly bool ObjectsWillBeCopied; 17 | 18 | /// The CTransform of the Event 19 | public readonly CTransform Transform; 20 | 21 | /// Default Constructor 22 | internal CrashTransformEventArgs(CrashDoc crashDoc, CTransform transform, 23 | IEnumerable objects, 24 | bool objectsWillBeCopied) 25 | { 26 | Doc = crashDoc; 27 | Transform = transform; 28 | Objects = objects; 29 | ObjectsWillBeCopied = objectsWillBeCopied; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashUpdateArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Handlers.InternalEvents 4 | { 5 | /// Wraps Rhino Object change Event Args 6 | public sealed class CrashUpdateArgs : EventArgs 7 | { 8 | /// The modified Crash Object 9 | public readonly CrashObject CrashObject; 10 | 11 | /// The Crash Doc of these Args 12 | public readonly CrashDoc Doc; 13 | 14 | /// Object Updates 15 | public readonly Dictionary Updates; 16 | 17 | /// Default constructor 18 | /// The modified Crash Object 19 | /// The given updates 20 | public CrashUpdateArgs(CrashDoc crashDoc, CrashObject crashObject, Dictionary updates) 21 | { 22 | Doc = crashDoc; 23 | CrashObject = crashObject; 24 | Updates = updates; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/CrashViewArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Geometry; 3 | 4 | using Rhino.Display; 5 | 6 | namespace Crash.Handlers.InternalEvents 7 | { 8 | /// Wraps Rhino View Event Args 9 | public sealed class CrashViewArgs : EventArgs 10 | { 11 | /// The Crash Doc of these Args 12 | public readonly CrashDoc Doc; 13 | 14 | /// The Camera Location of the Event 15 | public readonly CPoint Location; 16 | 17 | /// The Camera Target of the Event 18 | public readonly CPoint Target; 19 | 20 | /// Lazy Constructor 21 | internal CrashViewArgs(CrashDoc crashDoc, RhinoView view) 22 | : this(crashDoc, view.ActiveViewport.CameraLocation.ToCrash(), 23 | view.ActiveViewport.CameraTarget.ToCrash()) 24 | { 25 | } 26 | 27 | 28 | /// Constructor mainly for Tests 29 | internal CrashViewArgs(CrashDoc crashDoc, CPoint location, CPoint target) 30 | { 31 | Doc = crashDoc; 32 | Location = location; 33 | Target = target; 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/AddRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.InternalEvents.Wrapping 2 | { 3 | /// 4 | /// Records a single added item 5 | /// 6 | internal record AddRecord : IUndoRedoCache 7 | { 8 | internal readonly CrashObjectEventArgs AddArgs; 9 | 10 | internal AddRecord(CrashObjectEventArgs addArgs) 11 | { 12 | AddArgs = addArgs; 13 | } 14 | 15 | public bool TryGetInverse(out IUndoRedoCache cache) 16 | { 17 | cache = 18 | new DeleteRecord(new CrashObjectEventArgs(AddArgs.Doc, AddArgs.Geometry, AddArgs.RhinoId, 19 | AddArgs.ChangeId)); 20 | return true; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/DeleteRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.InternalEvents.Wrapping 2 | { 3 | /// 4 | /// Records the delete of a single item 5 | /// 6 | internal record DeleteRecord : IUndoRedoCache 7 | { 8 | internal readonly CrashObjectEventArgs DeleteArgs; 9 | 10 | internal DeleteRecord(CrashObjectEventArgs args) 11 | { 12 | DeleteArgs = args; 13 | } 14 | 15 | public bool TryGetInverse(out IUndoRedoCache cache) 16 | { 17 | cache = 18 | new AddRecord(new CrashObjectEventArgs(DeleteArgs.Doc, DeleteArgs.Geometry, DeleteArgs.RhinoId, 19 | DeleteArgs.ChangeId, true)); 20 | return true; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/IUndoRedoCache.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.InternalEvents.Wrapping 2 | { 3 | /// 4 | /// Stores UndoRedo events 5 | /// 6 | internal interface IUndoRedoCache 7 | { 8 | /// 9 | /// Calculates the inverse of the current Cache 10 | /// 11 | bool TryGetInverse(out IUndoRedoCache cache); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/ModifyGeometryRecord.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | using Rhino.DocObjects; 4 | 5 | namespace Crash.Handlers.InternalEvents.Wrapping 6 | { 7 | /// 8 | /// Records an Event that converts one or more change(s) into one or more change(s) 9 | /// A good example is Explode or BooleanUnion 10 | /// 11 | internal record ModifyGeometryRecord : IUndoRedoCache 12 | { 13 | internal readonly CrashObjectEventArgs[] AddArgs; 14 | internal readonly CrashObjectEventArgs[] RemoveArgs; 15 | 16 | internal ModifyGeometryRecord(CrashDoc doc, 17 | IEnumerable addedObjects, 18 | IEnumerable removedObjects) 19 | { 20 | AddArgs = addedObjects.Select(ao => new CrashObjectEventArgs(doc, ao)).ToArray(); 21 | RemoveArgs = removedObjects.Select(ro => new CrashObjectEventArgs(doc, ro)).ToArray(); 22 | } 23 | 24 | private ModifyGeometryRecord(CrashObjectEventArgs[] addedArgs, 25 | CrashObjectEventArgs[] removedArgs) 26 | { 27 | AddArgs = addedArgs; 28 | RemoveArgs = removedArgs; 29 | } 30 | 31 | public bool TryGetInverse(out IUndoRedoCache cache) 32 | { 33 | cache = new ModifyGeometryRecord(RemoveArgs, AddArgs); 34 | return true; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/TransformRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.InternalEvents.Wrapping 2 | { 3 | // TODO : Include Plane Cache or otherwise 4 | /// 5 | /// Records a single Object Transform 6 | /// 7 | internal record TransformRecord : IUndoRedoCache 8 | { 9 | public readonly CrashTransformEventArgs TransformArgs; 10 | 11 | internal TransformRecord(CrashTransformEventArgs args) 12 | { 13 | TransformArgs = args; 14 | } 15 | 16 | public bool TryGetInverse(out IUndoRedoCache cache) 17 | { 18 | var transformCache = TransformArgs.Transform.ToRhino(); 19 | transformCache.TryGetInverse(out var inverseTransform); 20 | 21 | var newArgs = new CrashTransformEventArgs(TransformArgs.Doc, 22 | inverseTransform.ToCrash(), 23 | TransformArgs.Objects, 24 | TransformArgs.ObjectsWillBeCopied); 25 | cache = new TransformRecord(newArgs); 26 | return true; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/Crash.Handlers/InternalEvents/Wrapping/UpdateRecord.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.InternalEvents.Wrapping 2 | { 3 | /// 4 | /// Records an Object Update Record 5 | /// 6 | internal record UpdateRecord : IUndoRedoCache 7 | { 8 | internal readonly CrashUpdateArgs UpdateArgs; 9 | 10 | internal UpdateRecord(CrashUpdateArgs args) 11 | { 12 | UpdateArgs = args; 13 | } 14 | 15 | public bool TryGetInverse(out IUndoRedoCache cache) 16 | { 17 | cache = null; 18 | return false; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/Crash.Handlers/LoadingUtils.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.App; 2 | using Crash.Common.Document; 3 | 4 | using Rhino.UI; 5 | 6 | namespace Crash.Commands 7 | { 8 | 9 | public static class LoadingUtils 10 | { 11 | 12 | internal class CrashStatusBar : ICrashInstance 13 | { 14 | public bool Enabled { get; set; } 15 | 16 | public int _progress { get; set; } = 0; 17 | public int Progress 18 | { 19 | get => _progress; 20 | set 21 | { 22 | _progress = value; 23 | if (Enabled) 24 | StatusBar.UpdateProgressMeter(_progress, true); 25 | } 26 | } 27 | 28 | 29 | } 30 | 31 | public enum LoadingState 32 | { 33 | None = -1, 34 | CheckingServer = 0, 35 | ConnectingToServer = 25, 36 | LoadingChanges = 50, 37 | Done = 100, 38 | } 39 | 40 | 41 | public static void Start(CrashDoc crashDoc) 42 | { 43 | Close(crashDoc); 44 | 45 | var statusBar = new CrashStatusBar(); 46 | 47 | statusBar.Progress = 0; 48 | statusBar.Enabled = true; 49 | CrashInstances.TrySetInstance(crashDoc, statusBar); 50 | 51 | StatusBar.ShowProgressMeter(0, 100, "Loading Crash", true, true); 52 | SetState(crashDoc, LoadingState.CheckingServer); 53 | } 54 | 55 | public static void SetState(CrashDoc crashDoc, LoadingState state, bool slowlyUpdate = true) 56 | { 57 | if (state == LoadingState.None) 58 | { 59 | Close(crashDoc); 60 | return; 61 | } 62 | 63 | if (!CrashInstances.TryGetInstance(crashDoc, out CrashStatusBar statusBar)) return; 64 | 65 | statusBar.Progress = (int)state; 66 | LoadingState nextState = LoadingState.None; 67 | 68 | if (state == LoadingState.CheckingServer) 69 | { 70 | nextState = LoadingState.ConnectingToServer; 71 | } 72 | else if (state == LoadingState.ConnectingToServer) 73 | { 74 | nextState = LoadingState.LoadingChanges; 75 | } 76 | else if (state == LoadingState.LoadingChanges) 77 | { 78 | nextState = LoadingState.Done; 79 | } 80 | 81 | #pragma warning disable CS4014, VSTHRD110 // Because this call is not awaited, execution of the current method continues before the call is completed 82 | if (slowlyUpdate) 83 | SlowlyUpdate(statusBar, (int)nextState); 84 | #pragma warning restore CS4014, VSTHRD110 // Because this call is not awaited, execution of the current method continues before the call is completed 85 | 86 | } 87 | 88 | private static async Task SlowlyUpdate(CrashStatusBar statusBar, int end) 89 | { 90 | await Task.Run(async () => 91 | { 92 | while (statusBar.Enabled && statusBar.Progress < end) 93 | { 94 | await Task.Delay(600); 95 | statusBar.Progress++; 96 | } 97 | }); 98 | } 99 | 100 | public static void Close(CrashDoc crashDoc) 101 | { 102 | if (!CrashInstances.TryGetInstance(crashDoc, out CrashStatusBar statusBar)) return; 103 | statusBar.Enabled = false; 104 | StatusBar.HideProgressMeter(); 105 | StatusBar.ClearMessagePane(); 106 | } 107 | 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Camera/Create/CameraCreateAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Geometry; 3 | using Crash.Handlers.InternalEvents; 4 | 5 | namespace Crash.Handlers.Plugins.Camera.Create 6 | { 7 | /// Creates a Camera from a View Event 8 | internal sealed class CameraCreateAction : IChangeCreateAction 9 | { 10 | private static readonly TimeSpan maxPerSecond = TimeSpan.FromMilliseconds(250); 11 | private CPoint lastLocation; 12 | 13 | private DateTime lastSentTime; 14 | private CPoint lastTarget; 15 | 16 | /// Default Constructor 17 | internal CameraCreateAction() 18 | { 19 | lastSentTime = DateTime.MinValue; 20 | lastLocation = CPoint.None; 21 | lastTarget = CPoint.None; 22 | } 23 | 24 | 25 | public ChangeAction Action => ChangeAction.Add; 26 | 27 | 28 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 29 | { 30 | if (crashArgs.Args is not CrashViewArgs viewArgs) 31 | { 32 | return false; 33 | } 34 | 35 | var now = DateTime.UtcNow; 36 | var timeSinceLastSent = now - lastSentTime; 37 | if (timeSinceLastSent < maxPerSecond) 38 | { 39 | return false; 40 | } 41 | 42 | if (DistanceBetween(viewArgs.Location, lastLocation) < 10 && 43 | DistanceBetween(viewArgs.Target, lastTarget) < 10) 44 | { 45 | return false; 46 | } 47 | 48 | lastLocation = viewArgs.Location; 49 | lastTarget = viewArgs.Target; 50 | lastSentTime = DateTime.UtcNow; 51 | 52 | return true; 53 | } 54 | 55 | 56 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 57 | { 58 | changes = Array.Empty(); 59 | if (crashArgs.Args is not CrashViewArgs viewArgs) 60 | { 61 | changes = null; 62 | return false; 63 | } 64 | 65 | var userName = crashArgs.Doc.Users.CurrentUser.Name; 66 | var camera = new Common.View.Camera(viewArgs.Location, viewArgs.Target); 67 | var change = CameraChange.CreateChange(camera, userName); 68 | changes = new List { change }; 69 | 70 | return true; 71 | } 72 | 73 | // TODO : Move to Crash.Changes? 74 | private double DistanceBetween(CPoint p1, CPoint p2) 75 | { 76 | // https://www.mathsisfun.com/algebra/distance-2-points.html 77 | var dist = Math.Sqrt( 78 | Math.Pow(p1.X - p2.X, 2) + 79 | Math.Pow(p1.Y - p2.Y, 2) + 80 | Math.Pow(p1.Z - p2.Z, 2) 81 | ); 82 | 83 | return dist; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Camera/Recieve/CameraRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Events; 5 | 6 | using Rhino; 7 | 8 | namespace Crash.Handlers.Plugins.Camera.Recieve 9 | { 10 | /// Handles receiving a camera from the Server 11 | internal sealed class CameraRecieveAction : IChangeRecieveAction 12 | { 13 | 14 | public bool CanRecieve(IChange change) => change.Action.HasFlag(ChangeAction.Add); 15 | 16 | 17 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 18 | { 19 | var cameraArgs = new IdleArgs(crashDoc, recievedChange); 20 | var cameraAction = new IdleAction(AddToDocument, cameraArgs); 21 | crashDoc.Queue.AddAction(cameraAction); 22 | } 23 | 24 | private void AddToDocument(IdleArgs args) 25 | { 26 | var convertedChange = CameraChange.CreateFrom(args.Change); 27 | args.Doc.Cameras.TryAddCamera(convertedChange); 28 | args.Doc.Users.Add(args.Change.Owner); 29 | 30 | if (args.Doc.Users.Get(args.Change.Owner).Camera == CameraState.Follow) 31 | { 32 | FollowCamera(convertedChange, args.Doc); 33 | } 34 | } 35 | 36 | private void FollowCamera(CameraChange change, CrashDoc doc) 37 | { 38 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(doc); 39 | var activeView = rhinoDoc?.Views?.ActiveView; 40 | if (activeView is null) return; 41 | 42 | var cameraTarget = change.Camera.Target.ToRhino(); 43 | var cameraLocation = change.Camera.Location.ToRhino(); 44 | 45 | activeView.ActiveViewport.SetCameraLocations(cameraTarget, cameraLocation); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/CrashPlugin.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.Plugins 2 | { 3 | /// 4 | /// Defines a Crash PlugIn. 5 | /// You will need to inherit this for your Plugin to be loaded by Crash 6 | /// 7 | public abstract class CrashPlugIn 8 | { 9 | /// Required constructor 10 | /// 11 | /// 12 | protected CrashPlugIn(string name, Guid id) 13 | { 14 | Name = name; 15 | Id = id; 16 | Changes = new List(); 17 | } 18 | 19 | /// The Id of the 20 | public Guid Id { get; } 21 | 22 | /// The Name of the 23 | public string Name { get; } 24 | 25 | /// Contains all of the Change Definitions of this 26 | protected IEnumerable Changes { get; } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/CreateRecieveArgs.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Handlers.Plugins 4 | { 5 | /// A wrapper for Crash Args 6 | public class CreateRecieveArgs : EventArgs 7 | { 8 | /// The Action 9 | public readonly ChangeAction Action; 10 | 11 | /// The EventArgs, often wrapped Rhino Args 12 | public readonly EventArgs Args; 13 | 14 | /// The current Crash Document 15 | public readonly CrashDoc Doc; 16 | 17 | /// Internal Constructor 18 | public CreateRecieveArgs(ChangeAction action, EventArgs args, CrashDoc doc) 19 | { 20 | Action = action; 21 | Args = args ?? throw new ArgumentNullException(nameof(args)); 22 | Doc = doc ?? throw new ArgumentNullException(nameof(doc)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Create/GeometryCreateAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | using Crash.Handlers.Changes; 4 | using Crash.Handlers.InternalEvents; 5 | using Crash.Handlers.Utils; 6 | 7 | using Rhino.Geometry; 8 | 9 | namespace Crash.Handlers.Plugins.Geometry.Create 10 | { 11 | /// Captures Creation of default Rhino Geometry 12 | internal sealed class GeometryCreateAction : IChangeCreateAction 13 | { 14 | public ChangeAction Action => ChangeAction.Add | ChangeAction.Temporary; 15 | 16 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 17 | { 18 | return crashArgs.Args is CrashObjectEventArgs rargs && 19 | rargs.Geometry is not null; 20 | } 21 | 22 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 23 | { 24 | if (crashArgs.Args is not CrashObjectEventArgs cargs) 25 | { 26 | changes = Array.Empty(); 27 | return false; 28 | } 29 | 30 | changes = CreateChangesFromArgs(crashArgs.Doc, cargs.RhinoId, cargs.Geometry, cargs.UnDelete, 31 | cargs.ChangeId); 32 | return changes.Any(); 33 | } 34 | 35 | private Change[] CreateChangesFromArgs(CrashDoc crashDoc, Guid rhinoId, GeometryBase geometry, 36 | bool unDelete, Guid changeId = default) 37 | { 38 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(crashDoc); 39 | if (rhinoDoc is null) 40 | { 41 | throw new NullReferenceException("Rhino Document cannot be found!"); 42 | } 43 | 44 | var rhinoObject = rhinoDoc.Objects.FindId(rhinoId); 45 | if (rhinoObject is null) 46 | { 47 | return Array.Empty(); 48 | } 49 | 50 | var userName = crashDoc.Users.CurrentUser.Name; 51 | 52 | if (!crashDoc.Tables.TryGet(out var realTable)) 53 | { 54 | return Array.Empty(); 55 | } 56 | 57 | // For unDelete 58 | if (changeId == Guid.Empty && !realTable.TryGetChangeId(rhinoId, out changeId)) 59 | { 60 | changeId = Guid.NewGuid(); 61 | } 62 | 63 | realTable.AddPair(changeId, rhinoId); 64 | 65 | Change change; 66 | if (unDelete) 67 | { 68 | change = GeometryChange.CreateChange(changeId, userName, ChangeAction.Add | ChangeAction.Temporary); 69 | } 70 | else 71 | { 72 | var updates = ObjectAttributeComparisonUtils.GetDefaults(rhinoObject.Attributes, userName); 73 | change = GeometryChange.CreateChange(changeId, userName, Action, geometry, updates); 74 | } 75 | 76 | return new[] { change }; 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Create/GeometryLockAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | using Crash.Handlers.Changes; 4 | using Crash.Handlers.InternalEvents; 5 | 6 | namespace Crash.Handlers.Plugins.Geometry.Create 7 | { 8 | /// Handles Selection 9 | internal sealed class GeometryLockAction : IChangeCreateAction 10 | { 11 | public ChangeAction Action => ChangeAction.Locked; 12 | 13 | 14 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 15 | { 16 | return crashArgs.Args is CrashSelectionEventArgs cargs && cargs.Selected; 17 | } 18 | 19 | 20 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 21 | { 22 | changes = Array.Empty(); 23 | if (crashArgs.Args is not CrashSelectionEventArgs cargs) 24 | { 25 | return false; 26 | } 27 | 28 | var userName = crashArgs.Doc.Users.CurrentUser.Name; 29 | changes = GetChanges(crashArgs.Doc, cargs.CrashObjects, userName); 30 | 31 | if (!crashArgs.Doc.Tables.TryGet(out var realisedTable)) 32 | { 33 | return false; 34 | } 35 | 36 | foreach (var change in changes) 37 | { 38 | realisedTable.AddSelected(change.Id); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | private IEnumerable GetChanges(CrashDoc crashDoc, IEnumerable crashObjects, 45 | string userName) 46 | { 47 | var changes = new List(crashObjects.Count()); 48 | foreach (var crashObject in crashObjects) 49 | { 50 | if (crashObject.ChangeId == Guid.Empty) 51 | { 52 | continue; 53 | } 54 | 55 | var change = GeometryChange.CreateChange(crashObject.ChangeId, userName, ChangeAction.Locked); 56 | changes.Add(change); 57 | } 58 | 59 | return changes; 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Create/GeometryRemoveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.Changes; 2 | using Crash.Handlers.InternalEvents; 3 | 4 | namespace Crash.Handlers.Plugins.Geometry.Create 5 | { 6 | /// Handles Removed Objects 7 | internal sealed class GeometryRemoveAction : IChangeCreateAction 8 | { 9 | public ChangeAction Action => ChangeAction.Remove; 10 | 11 | 12 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 13 | { 14 | return crashArgs.Args is CrashObjectEventArgs rargs && 15 | rargs.ChangeId != Guid.Empty; 16 | } 17 | 18 | 19 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 20 | { 21 | changes = Array.Empty(); 22 | if (crashArgs.Args is not CrashObjectEventArgs rargs) 23 | { 24 | return false; 25 | } 26 | 27 | if (rargs.ChangeId == Guid.Empty) 28 | { 29 | return false; 30 | } 31 | 32 | var _user = crashArgs.Doc.Users.CurrentUser.Name; 33 | var removeChange = 34 | GeometryChange.CreateChange(rargs.ChangeId, _user, ChangeAction.Remove); 35 | 36 | changes = new List { removeChange }; 37 | 38 | return true; 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Create/GeometryUnlockAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Tables; 2 | using Crash.Handlers.Changes; 3 | using Crash.Handlers.InternalEvents; 4 | 5 | namespace Crash.Handlers.Plugins.Geometry.Create 6 | { 7 | /// Handles unselction 8 | internal sealed class GeometryUnlockAction : IChangeCreateAction 9 | { 10 | public ChangeAction Action => ChangeAction.Unlocked; 11 | 12 | 13 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 14 | { 15 | return crashArgs.Args is CrashSelectionEventArgs cargs && 16 | !cargs.Selected; 17 | } 18 | 19 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 20 | { 21 | changes = Array.Empty(); 22 | if (crashArgs.Args is not CrashSelectionEventArgs cargs) 23 | { 24 | return false; 25 | } 26 | 27 | if (!crashArgs.Doc.Tables.TryGet(out var realisedTable)) 28 | { 29 | return false; 30 | } 31 | 32 | var userName = crashArgs.Doc.Users.CurrentUser.Name; 33 | 34 | changes = getChanges(cargs.CrashObjects, userName); 35 | 36 | foreach (var change in changes) 37 | { 38 | realisedTable.RemoveSelected(change.Id); 39 | } 40 | 41 | return true; 42 | } 43 | 44 | private IEnumerable getChanges(IEnumerable crashObjects, string userName) 45 | { 46 | var changes = new List(); 47 | foreach (var crashObject in crashObjects) 48 | { 49 | changes.Add(CreateChange(crashObject.ChangeId, userName)); 50 | } 51 | 52 | return changes; 53 | } 54 | 55 | private Change CreateChange(Guid changeId, string userName) 56 | { 57 | return GeometryChange.CreateChange(changeId, userName, ChangeAction.Unlocked); 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Create/GeometryUpdateAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Handlers.Changes; 3 | using Crash.Handlers.InternalEvents; 4 | 5 | namespace Crash.Handlers.Plugins.Geometry.Create 6 | { 7 | /// Captures Creation of default Rhino Geometry 8 | internal sealed class GeometryUpdateAction : IChangeCreateAction 9 | { 10 | public ChangeAction Action => ChangeAction.Update; 11 | 12 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 13 | { 14 | return crashArgs.Args is CrashUpdateArgs args && args.Updates.Count > 0; 15 | } 16 | 17 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 18 | { 19 | if (crashArgs.Args is not CrashUpdateArgs args) 20 | { 21 | changes = Array.Empty(); 22 | return false; 23 | } 24 | 25 | changes = CreateUpdatesChange(crashArgs.Doc, args.CrashObject.ChangeId, args.Updates); 26 | return changes.Any(); 27 | } 28 | 29 | private Change[] CreateUpdatesChange(CrashDoc crashDoc, Guid changeId, Dictionary updates) 30 | { 31 | var userName = crashDoc.Users.CurrentUser.Name; 32 | var change = GeometryChange.CreateChange(changeId, userName, Action, null, updates); 33 | 34 | return new[] { change }; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Recieve/GeometryLockRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Events; 3 | using Crash.Events; 4 | using Crash.Utils; 5 | 6 | namespace Crash.Handlers.Plugins.Geometry.Recieve 7 | { 8 | /// Handles Selected objects from the server 9 | internal sealed class GeometryLockRecieveAction : IChangeRecieveAction 10 | { 11 | public bool CanRecieve(IChange change) 12 | { 13 | return change.Action.HasFlag(ChangeAction.Locked); 14 | } 15 | 16 | 17 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 18 | { 19 | var changeArgs = new IdleArgs(crashDoc, recievedChange); 20 | var lockAction = new IdleAction(LockChange, changeArgs); 21 | crashDoc.Queue.AddAction(lockAction); 22 | } 23 | 24 | private void LockChange(IdleArgs args) 25 | { 26 | if (!args.Change.TryGetRhinoObject(args.Doc, out var rhinoObject)) 27 | { 28 | return; 29 | } 30 | 31 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 32 | rhinoDoc.Objects.Lock(rhinoObject, true); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Recieve/GeometryRemoveRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Events; 3 | using Crash.Common.Tables; 4 | using Crash.Events; 5 | using Crash.Handlers.Changes; 6 | using Crash.Utils; 7 | 8 | namespace Crash.Handlers.Plugins.Geometry.Recieve 9 | { 10 | /// Handles Deleted objects from the Server 11 | internal sealed class GeometryRemoveRecieveAction : IChangeRecieveAction 12 | { 13 | public bool CanRecieve(IChange change) 14 | { 15 | return change.Action.HasFlag(ChangeAction.Remove); 16 | } 17 | 18 | 19 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 20 | { 21 | IdleArgs idleArgs; 22 | IdleAction idleAction; 23 | 24 | var changeArgs = new IdleArgs(crashDoc, recievedChange); 25 | IdleAction resultingAction = null; 26 | 27 | if (crashDoc.Tables.TryGet(out var tempTable) && tempTable.TryGetChangeOfType(recievedChange.Id, out IChange temporaryChange)) 28 | { 29 | resultingAction = new IdleAction(RemoveFromCache, changeArgs); 30 | } 31 | else if (crashDoc.Tables.TryGet(out var realisedTable) && realisedTable.ContainsChangeId(recievedChange.Id)) 32 | { 33 | resultingAction = new IdleAction(RemoveFromDocument, changeArgs); 34 | } 35 | else 36 | { 37 | return; 38 | } 39 | 40 | crashDoc.Queue.AddAction(resultingAction); 41 | } 42 | 43 | private void RemoveFromDocument(IdleArgs args) 44 | { 45 | args.Doc.DocumentIsBusy = true; 46 | try 47 | { 48 | if (!args.Change.TryGetRhinoObject(args.Doc, out var rhinoObject)) 49 | { 50 | args.Doc.DocumentIsBusy = false; 51 | return; 52 | } 53 | 54 | var geomChange = GeometryChange.CreateFrom(args.Change); 55 | geomChange.SetGeometry(rhinoObject.Geometry.Duplicate()); 56 | 57 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 58 | rhinoDoc.Objects.Delete(rhinoObject, true, true); 59 | 60 | if (!args.Doc.Tables.TryGet(out var tempTable)) return; 61 | if (!args.Doc.Tables.TryGet(out var realTable)) return; 62 | 63 | tempTable.UpdateChange(geomChange); 64 | tempTable.DeleteChange(geomChange.Id); 65 | realTable.PurgeChange(args.Change.Id); 66 | } 67 | finally 68 | { 69 | args.Doc.DocumentIsBusy = false; 70 | } 71 | } 72 | 73 | private void RemoveFromCache(IdleArgs args) 74 | { 75 | if (!args.Doc.Tables.TryGet(out var tempTable)) return; 76 | tempTable.DeleteChange(args.Change.Id); 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Recieve/GeometryTransformRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Common.Tables; 5 | using Crash.Events; 6 | using Crash.Handlers.Changes; 7 | using Crash.Utils; 8 | 9 | namespace Crash.Handlers.Plugins.Geometry.Recieve 10 | { 11 | /// Handles transforms recieved from the server 12 | internal sealed class GeometryTransformRecieveAction : IChangeRecieveAction 13 | { 14 | public bool CanRecieve(IChange change) 15 | { 16 | return change.Action == ChangeAction.Transform; 17 | } 18 | 19 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 20 | { 21 | var transformChange = TransformChange.CreateFrom(recievedChange); 22 | if (!transformChange.Transform.IsValid()) 23 | { 24 | return; 25 | } 26 | 27 | var xform = transformChange.Transform.ToRhino(); 28 | if (!xform.IsValid) 29 | { 30 | return; 31 | } 32 | 33 | if (!crashDoc.Tables.TryGet(out var tempTable)) 34 | return; 35 | 36 | if (tempTable.TryGetChangeOfType(recievedChange.Id, out GeometryChange geomChange)) 37 | { 38 | geomChange.Geometry.Transform(xform); 39 | } 40 | else 41 | { 42 | crashDoc.Queue.AddAction(new IdleAction(TransformGeometry, new IdleArgs(crashDoc, transformChange))); 43 | } 44 | } 45 | 46 | private void TransformGeometry(IdleArgs args) 47 | { 48 | if (args.Change is not TransformChange transformChange) 49 | { 50 | return; 51 | } 52 | 53 | var xform = transformChange.Transform.ToRhino(); 54 | if (!xform.IsValid) 55 | { 56 | return; 57 | } 58 | 59 | args.Doc.DocumentIsBusy = true; 60 | try 61 | { 62 | if (!args.Change.TryGetRhinoObject(args.Doc, out var rhinoObject)) 63 | { 64 | return; 65 | } 66 | 67 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 68 | 69 | var isLocked = rhinoObject.IsLocked; 70 | if (isLocked) 71 | { 72 | rhinoDoc.Objects.Unlock(rhinoObject, true); 73 | rhinoObject.CommitChanges(); 74 | } 75 | 76 | rhinoObject.Geometry.Transform(xform); 77 | rhinoObject.CommitChanges(); 78 | 79 | if (isLocked) 80 | { 81 | rhinoDoc.Objects.Lock(rhinoObject, true); 82 | rhinoObject.CommitChanges(); 83 | } 84 | } 85 | finally 86 | { 87 | args.Doc.DocumentIsBusy = false; 88 | } 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Recieve/GeometryUnlockRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Events; 3 | using Crash.Events; 4 | using Crash.Utils; 5 | 6 | namespace Crash.Handlers.Plugins.Geometry.Recieve 7 | { 8 | /// Handles unselections from the server 9 | internal sealed class GeometryUnlockRecieveAction : IChangeRecieveAction 10 | { 11 | public bool CanRecieve(IChange change) 12 | { 13 | return change.Action.HasFlag(ChangeAction.Unlocked); 14 | } 15 | 16 | 17 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 18 | { 19 | var changeArgs = new IdleArgs(crashDoc, recievedChange); 20 | var lockAction = new IdleAction(LockChange, changeArgs); 21 | crashDoc.Queue.AddAction(lockAction); 22 | } 23 | 24 | private void LockChange(IdleArgs args) 25 | { 26 | if (!args.Change.TryGetRhinoObject(args.Doc, out var rhinoObject)) 27 | { 28 | return; 29 | } 30 | 31 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 32 | rhinoDoc.Objects.Unlock(rhinoObject, true); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Geometry/Recieve/GeometryUpdateRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | using Crash.Common.Document; 4 | using Crash.Common.Tables; 5 | using Crash.Handlers.Utils; 6 | 7 | namespace Crash.Handlers.Plugins.Geometry.Recieve 8 | { 9 | /// Handles updates from the server 10 | internal sealed class GeometryUpdateRecieveAction : IChangeRecieveAction 11 | { 12 | public bool CanRecieve(IChange change) 13 | { 14 | return change.Action.HasFlag(ChangeAction.Update); 15 | } 16 | 17 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 18 | { 19 | var payload = JsonSerializer.Deserialize(recievedChange.Payload); 20 | 21 | if (!crashDoc.Tables.TryGet(out var realisedTable)) return; 22 | 23 | realisedTable.TryGetRhinoId(recievedChange.Id, out var rhinoId); 24 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(crashDoc); 25 | 26 | var rhinoObject = rhinoDoc.Objects.FindId(rhinoId); 27 | 28 | var updates = payload.Updates; 29 | if (updates?.Count > 0) 30 | { 31 | var userName = crashDoc.Users.CurrentUser.Name; 32 | ObjectAttributeComparisonUtils.UpdateAttributes(rhinoObject.Attributes, updates, userName); 33 | rhinoObject.CommitChanges(); 34 | } 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/IChangeCreateAction.cs: -------------------------------------------------------------------------------- 1 | namespace Crash.Handlers.Plugins 2 | { 3 | /// Describes how to Convert an Event into a Change 4 | public interface IChangeCreateAction 5 | { 6 | /// The Action this responds to 7 | ChangeAction Action { get; } 8 | 9 | /// Attempts to convert an Event into an 10 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes); 11 | 12 | /// Tests Event Args for Conversion 13 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/IChangeDefinition.cs: -------------------------------------------------------------------------------- 1 | using Rhino.Display; 2 | using Rhino.Geometry; 3 | 4 | namespace Crash.Handlers.Plugins 5 | { 6 | /// Describes a Change 7 | public interface IChangeDefinition 8 | { 9 | /// The Name to recognise these Changes by 10 | string ChangeName { get; } 11 | 12 | /// 13 | /// These will be registered somewhere and Crash will perform a fall through to find the first conversion candidate 14 | /// They will be index by Action too. 15 | /// 16 | IEnumerable CreateActions { get; } 17 | 18 | /// 19 | /// These will be registered somewhere, and when Crash receives a Change, and then perform the conversion 20 | /// It will then be indexed by name 21 | /// 22 | IEnumerable RecieveActions { get; } 23 | 24 | /// Draws the Change in the Pipeline 25 | void Draw(DrawEventArgs drawArgs, DisplayMaterial material, IChange change); 26 | 27 | /// Returns a BoundingBox of the Change for Drawing 28 | BoundingBox GetBoundingBox(IChange change); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/IChangeRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Handlers.Plugins 4 | { 5 | /// Handles recieved changes from the Server 6 | public interface IChangeRecieveAction 7 | { 8 | /// The Action this ICreateAction responds to 9 | bool CanRecieve(IChange change); 10 | 11 | /// Deserializes a Server Sent Change 12 | public Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Initializers/DoneDefinition.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Handlers.Plugins.Initializers.Recieve; 3 | 4 | using Rhino.Display; 5 | using Rhino.Geometry; 6 | 7 | namespace Crash.Handlers.Plugins.Initializers 8 | { 9 | /// Handles Done calls inside of Crash 10 | public sealed class DoneDefinition : IChangeDefinition 11 | { 12 | /// Default Constructor 13 | public DoneDefinition() 14 | { 15 | CreateActions = Array.Empty(); 16 | RecieveActions = new List { new DoneRecieve() }; 17 | } 18 | 19 | public string ChangeName => DoneChange.ChangeType; 20 | 21 | public IEnumerable CreateActions { get; } 22 | 23 | 24 | public IEnumerable RecieveActions { get; } 25 | 26 | 27 | public void Draw(DrawEventArgs drawArgs, DisplayMaterial material, IChange change) 28 | { 29 | throw new NotImplementedException(); 30 | } 31 | 32 | 33 | public BoundingBox GetBoundingBox(IChange change) 34 | { 35 | throw new NotImplementedException(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Initializers/Recieve/DoneRecieve.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes.Extensions; 2 | using Crash.Common.Document; 3 | using Crash.Common.Tables; 4 | using Crash.Handlers.Changes; 5 | using Crash.Handlers.Plugins.Geometry.Recieve; 6 | 7 | using Rhino; 8 | 9 | namespace Crash.Handlers.Plugins.Initializers.Recieve 10 | { 11 | /// Handles 'Done' calls from the Server 12 | internal class DoneRecieve : IChangeRecieveAction 13 | { 14 | public bool CanRecieve(IChange change) 15 | { 16 | return change.Action.HasFlag(ChangeAction.Release); 17 | } 18 | 19 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 20 | { 21 | crashDoc.DocumentIsBusy = true; 22 | try 23 | { 24 | if (!crashDoc.Tables.TryGet(out var tempTable)) return; 25 | // Done Range 26 | if (string.IsNullOrEmpty(recievedChange.Owner)) 27 | { 28 | if (!tempTable.TryGetChangeOfType(recievedChange.Id, out IChange doneChange)) 29 | { 30 | return; 31 | } 32 | 33 | await ReleaseChange(crashDoc, doneChange); 34 | } 35 | // Done 36 | else 37 | { 38 | foreach (var change in tempTable.GetChanges()) 39 | { 40 | if (string.Equals(change.Owner, recievedChange.Owner, 41 | StringComparison.InvariantCultureIgnoreCase)) 42 | { 43 | await ReleaseChange(crashDoc, change); 44 | } 45 | } 46 | } 47 | } 48 | catch (Exception e) 49 | { 50 | Console.WriteLine(e); 51 | throw; 52 | } 53 | } 54 | 55 | private async Task ReleaseChange(CrashDoc crashDoc, IChange change) 56 | { 57 | if (!crashDoc.Tables.TryGet(out var tempTable)) return; 58 | if (!tempTable.TryGetChangeOfType(change.Id, out GeometryChange geomChange)) return; 59 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(crashDoc); 60 | if (rhinoDoc is null) return; 61 | 62 | tempTable.RemoveChange(change.Id); 63 | 64 | geomChange.RemoveAction(ChangeAction.Temporary); 65 | 66 | var add = new GeometryAddRecieveAction(); 67 | await add.OnRecieveAsync(crashDoc, geomChange); 68 | 69 | rhinoDoc.ClearUndoRecords(true); 70 | (crashDoc.Dispatcher as EventDispatcher).ClearUndoRedoQueue(); 71 | 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Create/LayerCreateAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.InternalEvents; 2 | 3 | namespace Crash.Handlers.Plugins.Layers.Create 4 | { 5 | public class LayerCreateAction : IChangeCreateAction 6 | { 7 | public ChangeAction Action => ChangeAction.Add; 8 | 9 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 10 | { 11 | changes = Array.Empty(); 12 | if (crashArgs.Args is not CrashLayerArgs layerArgs) 13 | { 14 | return false; 15 | } 16 | 17 | var owner = crashArgs.Doc.Users.CurrentUser.Name; 18 | changes = new[] 19 | { 20 | LayerChange.CreateChange(owner, 21 | layerArgs.CrashLayer, 22 | ChangeAction.Add, 23 | layerArgs.Updates) 24 | }; 25 | 26 | var layerTable = crashArgs.Doc.Tables.Get(); 27 | layerTable.AddLayer(layerArgs.CrashLayer); 28 | 29 | return true; 30 | } 31 | 32 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 33 | { 34 | return crashArgs.Args is CrashLayerArgs cargs && cargs.Action == Action; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Create/LayerDeleteAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.InternalEvents; 2 | 3 | namespace Crash.Handlers.Plugins.Layers.Create 4 | { 5 | public class LayerDeleteAction : IChangeCreateAction 6 | { 7 | public ChangeAction Action => ChangeAction.Remove; 8 | 9 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 10 | { 11 | changes = Array.Empty(); 12 | if (crashArgs.Args is not CrashLayerArgs layerArgs) 13 | { 14 | return false; 15 | } 16 | 17 | var owner = crashArgs.Doc.Users.CurrentUser.Name; 18 | changes = new[] 19 | { 20 | LayerChange.CreateChange(owner, 21 | layerArgs.CrashLayer, 22 | ChangeAction.Remove, 23 | layerArgs.Updates) 24 | }; 25 | 26 | var layerTable = crashArgs.Doc.Tables.Get(); 27 | layerTable.MarkAsDeleted(layerArgs.CrashLayer.Index); 28 | 29 | return true; 30 | } 31 | 32 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 33 | { 34 | return crashArgs.Args is CrashLayerArgs cargs && cargs.Action == Action; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Create/LayerModifyAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.InternalEvents; 2 | 3 | namespace Crash.Handlers.Plugins.Layers.Create 4 | { 5 | public class LayerModifyAction : IChangeCreateAction 6 | { 7 | public ChangeAction Action => ChangeAction.Update; 8 | 9 | public bool TryConvert(object? sender, CreateRecieveArgs crashArgs, out IEnumerable changes) 10 | { 11 | changes = Array.Empty(); 12 | if (crashArgs.Args is not CrashLayerArgs layerArgs) 13 | { 14 | return false; 15 | } 16 | 17 | var owner = crashArgs.Doc.Users.CurrentUser.Name; 18 | changes = new[] 19 | { 20 | LayerChange.CreateChange(owner, 21 | layerArgs.CrashLayer, 22 | layerArgs.Action, 23 | layerArgs.Updates) 24 | }; 25 | 26 | var layerTable = crashArgs.Doc.Tables.Get(); 27 | layerTable.UpdateLayer(layerArgs.CrashLayer); 28 | 29 | return true; 30 | } 31 | 32 | public bool CanConvert(object? sender, CreateRecieveArgs crashArgs) 33 | { 34 | return crashArgs.Args is CrashLayerArgs cargs && cargs.Action == Action; 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/LayerChange.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | 3 | namespace Crash.Handlers.Plugins.Layers 4 | { 5 | public sealed class LayerChange : IChange 6 | { 7 | public const string ChangeType = "Crash.LayerChange"; 8 | 9 | 10 | public CrashLayer Layer { get; private set; } 11 | 12 | public DateTime Stamp { get; private set; } 13 | public Guid Id { get; private set; } 14 | public string? Owner { get; private set; } 15 | public string? Payload { get; private set; } 16 | public string Type { get; private set; } 17 | public ChangeAction Action { get; set; } 18 | 19 | 20 | public static Change CreateChange(string owner, CrashLayer layer, ChangeAction action, 21 | Dictionary updates) 22 | { 23 | var packet = new PayloadPacket { Updates = updates }; 24 | return new Change 25 | { 26 | Stamp = DateTime.Now, 27 | Id = layer.Id, 28 | Owner = owner, 29 | Type = ChangeType, 30 | Action = action, 31 | Payload = JsonSerializer.Serialize(packet) 32 | }; 33 | } 34 | 35 | public static bool TryCreateLayerChange(IChange change, out LayerChange layerChange) 36 | { 37 | var payload = JsonSerializer.Deserialize(change.Payload); 38 | if (payload is null) 39 | { 40 | layerChange = default; 41 | return false; 42 | } 43 | 44 | layerChange = new LayerChange 45 | { 46 | Stamp = DateTime.Now, 47 | Id = change.Id, 48 | Owner = change.Owner, 49 | Type = ChangeType, 50 | Action = change.Action, 51 | Payload = change.Payload, 52 | Layer = CrashLayer.CreateFrom(change) 53 | }; 54 | 55 | return true; 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/LayerChangeDefinition.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Events; 2 | using Crash.Handlers.Plugins.Layers.Create; 3 | using Crash.Handlers.Plugins.Layers.Recieve; 4 | 5 | using Rhino.Display; 6 | using Rhino.Geometry; 7 | 8 | namespace Crash.Handlers.Plugins.Layers 9 | { 10 | public class LayerChangeDefinition : IChangeDefinition 11 | { 12 | public LayerChangeDefinition() 13 | { 14 | CreateActions = new List 15 | { 16 | new LayerCreateAction(), new LayerDeleteAction(), new LayerModifyAction() 17 | }; 18 | RecieveActions = new List 19 | { 20 | new LayerCreateRecieveAction(), 21 | new LayerModifyRecieveAction(), 22 | new LayerDeleteRecieveAction() 23 | }; 24 | 25 | CrashDocRegistry.DocumentRegistered += RegisterLayersTable; 26 | } 27 | 28 | public string ChangeName => LayerChange.ChangeType; 29 | public IEnumerable CreateActions { get; } 30 | public IEnumerable RecieveActions { get; } 31 | 32 | public void Draw(DrawEventArgs drawArgs, DisplayMaterial material, IChange change) 33 | { 34 | } 35 | 36 | public BoundingBox GetBoundingBox(IChange change) 37 | { 38 | return BoundingBox.Empty; 39 | } 40 | 41 | private void RegisterLayersTable(object? sender, CrashEventArgs e) 42 | { 43 | e.CrashDoc.Tables.AddTable(new LayerTable(e.CrashDoc)); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/LayerTable.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Tables; 3 | 4 | namespace Crash.Handlers.Plugins.Layers 5 | { 6 | internal class LayerTable : ICacheTable 7 | { 8 | internal LayerTable(CrashDoc crashDoc) 9 | { 10 | CrashDoc = crashDoc; 11 | Layers = new Dictionary(); 12 | } 13 | 14 | private Dictionary Layers { get; } 15 | 16 | private CrashDoc CrashDoc { get; } 17 | 18 | internal void AddLayer(CrashLayer layer) 19 | { 20 | if (Layers.ContainsKey(layer.Index)) 21 | { 22 | return; 23 | } 24 | 25 | Layers.Add(layer.Index, layer); 26 | } 27 | 28 | internal void MarkAsDeleted(int index) 29 | { 30 | if (Layers.TryGetValue(index, out var layer)) 31 | { 32 | layer.IsDeleted = true; 33 | } 34 | } 35 | 36 | internal void RestoreFromDeleted(int index) 37 | { 38 | if (Layers.TryGetValue(index, out var layer)) 39 | { 40 | layer.IsDeleted = false; 41 | } 42 | } 43 | 44 | internal void LockLayer(int index) 45 | { 46 | if (Layers.TryGetValue(index, out var layer)) 47 | { 48 | layer.IsLocked = true; 49 | } 50 | } 51 | 52 | internal void UnlockLayer(int index) 53 | { 54 | if (Layers.TryGetValue(index, out var layer)) 55 | { 56 | layer.IsDeleted = false; 57 | } 58 | } 59 | 60 | internal void HideLayer(int index) 61 | { 62 | if (Layers.TryGetValue(index, out var layer)) 63 | { 64 | layer.IsVisible = true; 65 | } 66 | } 67 | 68 | internal void ShowLayer(int index) 69 | { 70 | if (Layers.TryGetValue(index, out var layer)) 71 | { 72 | layer.IsVisible = true; 73 | } 74 | } 75 | 76 | internal void SetCurrent(int index) 77 | { 78 | foreach (var layer in Layers) 79 | { 80 | if (layer.Value.Index == index) 81 | { 82 | layer.Value.Current = true; 83 | } 84 | else 85 | { 86 | layer.Value.Current = false; 87 | } 88 | } 89 | } 90 | 91 | public void UpdateLayer(CrashLayer layer) 92 | { 93 | Layers.Remove(layer.Index); 94 | AddLayer(layer); 95 | } 96 | 97 | public bool TryGet(int index, out CrashLayer crashLayer) 98 | { 99 | return Layers.TryGetValue(index, out crashLayer); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Recieve/LayerCreateRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes.Extensions; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Events; 5 | using Crash.Handlers.Utils; 6 | 7 | namespace Crash.Handlers.Plugins.Layers.Recieve 8 | { 9 | public class LayerCreateRecieveAction : IChangeRecieveAction 10 | { 11 | public bool CanRecieve(IChange change) 12 | { 13 | // TODO : Add Deleted Layers and Delete them so Undo works? 14 | return change.HasFlag(ChangeAction.Add) && !change.HasFlag(ChangeAction.Remove); 15 | } 16 | 17 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 18 | { 19 | crashDoc.Queue.AddAction(new IdleAction(CreateLayerAction, new IdleArgs(crashDoc, recievedChange))); 20 | } 21 | 22 | private void CreateLayerAction(IdleArgs args) 23 | { 24 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 25 | 26 | args.Doc.DocumentIsBusy = true; 27 | try 28 | { 29 | var crashLayer = CrashLayer.CreateFrom(args.Change); 30 | 31 | var rhinoLayer = crashLayer.GetOrCreateRhinoLayer(rhinoDoc); 32 | rhinoLayer = RhinoLayerUtils.MoveLayerToCorrectLocation(rhinoDoc, rhinoLayer, crashLayer); 33 | CrashLayer.UpdateRegisteredLayer(rhinoDoc, crashLayer); 34 | 35 | var layerTable = args.Doc.Tables.Get(); 36 | layerTable.UpdateLayer(crashLayer); 37 | } 38 | finally 39 | { 40 | args.Doc.DocumentIsBusy = false; 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Recieve/LayerDeleteRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes.Extensions; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Events; 5 | 6 | namespace Crash.Handlers.Plugins.Layers.Recieve 7 | { 8 | public class LayerDeleteRecieveAction : IChangeRecieveAction 9 | { 10 | public bool CanRecieve(IChange change) 11 | { 12 | return change.HasFlag(ChangeAction.Remove); 13 | } 14 | 15 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 16 | { 17 | crashDoc.Queue.AddAction(new IdleAction(DeleteLayerAction, new IdleArgs(crashDoc, recievedChange))); 18 | } 19 | 20 | private void DeleteLayerAction(IdleArgs args) 21 | { 22 | var crashLayer = CrashLayer.CreateFrom(args.Change); 23 | 24 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 25 | 26 | args.Doc.DocumentIsBusy = true; 27 | try 28 | { 29 | var layerTable = args.Doc.Tables.Get(); 30 | layerTable.MarkAsDeleted(crashLayer.Index); 31 | 32 | rhinoDoc.Layers.Delete(crashLayer.Index, false); 33 | } 34 | finally 35 | { 36 | args.Doc.DocumentIsBusy = false; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Plugins/Layers/Recieve/LayerModifyRecieveAction.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes.Extensions; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Events; 5 | using Crash.Handlers.Utils; 6 | 7 | namespace Crash.Handlers.Plugins.Layers.Recieve 8 | { 9 | public class LayerModifyRecieveAction : IChangeRecieveAction 10 | { 11 | public bool CanRecieve(IChange change) 12 | { 13 | return change.HasFlag(ChangeAction.Update) && !change.HasFlag(ChangeAction.Remove); 14 | } 15 | 16 | public async Task OnRecieveAsync(CrashDoc crashDoc, Change recievedChange) 17 | { 18 | crashDoc.Queue.AddAction(new IdleAction(ModifyLayerAction, new IdleArgs(crashDoc, recievedChange))); 19 | } 20 | 21 | private void ModifyLayerAction(IdleArgs args) 22 | { 23 | var rhinoDoc = CrashDocRegistry.GetRelatedDocument(args.Doc); 24 | 25 | args.Doc.DocumentIsBusy = true; 26 | try 27 | { 28 | var crashLayer = CrashLayer.CreateFrom(args.Change); 29 | 30 | var rhinoLayer = crashLayer.GetOrCreateRhinoLayer(rhinoDoc); 31 | rhinoLayer = RhinoLayerUtils.MoveLayerToCorrectLocation(rhinoDoc, rhinoLayer, crashLayer); 32 | 33 | CrashLayer.UpdateRhinoLayer(rhinoDoc, crashLayer, rhinoLayer); 34 | CrashLayer.UpdateRegisteredLayer(rhinoDoc, crashLayer); 35 | 36 | crashLayer = new CrashLayer(rhinoLayer, args.Change.Id); 37 | var layerTable = args.Doc.Tables.Get(); 38 | layerTable.UpdateLayer(crashLayer); 39 | } 40 | finally 41 | { 42 | args.Doc.DocumentIsBusy = false; 43 | } 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Properties/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | global using System.Threading.Tasks; 5 | 6 | global using Crash.Changes; 7 | 8 | using System.Runtime.CompilerServices; 9 | 10 | [assembly: InternalsVisibleTo("Crash.Integration.Tests")] 11 | [assembly: InternalsVisibleTo("Crash.Handlers.Tests")] 12 | [assembly: InternalsVisibleTo("Crash.Handlers.two.Tests")] 13 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Refs/net48/crash.auth.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash.Handlers/Refs/net48/crash.auth.dll -------------------------------------------------------------------------------- /src/Crash.Handlers/Refs/net7.0/crash.auth.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash.Handlers/Refs/net7.0/crash.auth.dll -------------------------------------------------------------------------------- /src/Crash.Handlers/Utils/Geometry.cs: -------------------------------------------------------------------------------- 1 | using Crash.Geometry; 2 | 3 | using Rhino.Geometry; 4 | 5 | namespace Crash.Handlers 6 | { 7 | /// Crash to RhinoGeometry Converter Classes 8 | public static class Geometry 9 | { 10 | /// Converts a to a 11 | public static Point3d ToRhino(this CPoint cPoint) 12 | { 13 | return new Point3d(cPoint.X, cPoint.Y, cPoint.Z); 14 | } 15 | 16 | /// Converts a to a 17 | public static CPoint ToCrash(this Point3d cPoint) 18 | { 19 | return new CPoint(cPoint.X, cPoint.Y, cPoint.Z); 20 | } 21 | 22 | /// Converts a to a 23 | public static Vector3d ToRhino(this CVector cVector) 24 | { 25 | return new Vector3d(cVector.X, cVector.Y, cVector.Z); 26 | } 27 | 28 | /// Converts a to a 29 | public static CVector ToCrash(this Vector3d cVector) 30 | { 31 | return new CVector(cVector.X, cVector.Y, cVector.Z); 32 | } 33 | 34 | /// Converts a to a 35 | public static CTransform ToCrash(this Transform transform) 36 | { 37 | return new CTransform(transform.M00, transform.M01, transform.M02, transform.M03, 38 | transform.M10, transform.M11, transform.M12, transform.M13, 39 | transform.M20, transform.M21, transform.M22, transform.M23, 40 | transform.M30, transform.M31, transform.M32, transform.M33); 41 | } 42 | 43 | /// Converts a to a 44 | public static Transform ToRhino(this CTransform transform) 45 | { 46 | return new Transform 47 | { 48 | M00 = transform[0, 0], 49 | M01 = transform[0, 1], 50 | M02 = transform[0, 2], 51 | M03 = transform[0, 3], 52 | M10 = transform[1, 0], 53 | M11 = transform[1, 1], 54 | M12 = transform[1, 2], 55 | M13 = transform[1, 3], 56 | M20 = transform[2, 0], 57 | M21 = transform[2, 1], 58 | M22 = transform[2, 2], 59 | M23 = transform[2, 3], 60 | M30 = transform[3, 0], 61 | M31 = transform[3, 1], 62 | M32 = transform[3, 2], 63 | M33 = transform[3, 3] 64 | }; 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Utils/RhinoDocUtils.cs: -------------------------------------------------------------------------------- 1 | using Rhino; 2 | using Rhino.DocObjects; 3 | 4 | namespace Crash.Handlers.Utils 5 | { 6 | internal static class RhinoDocUtils 7 | { 8 | internal static RhinoDoc GetRhinoDocFromObjects(IEnumerable rhinoObjects) 9 | { 10 | var rhinoDoc = rhinoObjects 11 | ?.FirstOrDefault(o => o.Document is not null) 12 | ?.Document; 13 | 14 | return rhinoDoc; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Crash.Handlers/Utils/RhinoLayerUtils.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.Plugins.Layers; 2 | 3 | using Rhino; 4 | using Rhino.DocObjects; 5 | 6 | namespace Crash.Handlers.Utils 7 | { 8 | public static class RhinoLayerUtils 9 | { 10 | public static Layer MoveLayerToCorrectLocation(RhinoDoc rhinoDoc, Layer rhinoLayer, CrashLayer crashLayer) 11 | { 12 | var layerResult = rhinoLayer; 13 | if (rhinoDoc.Layers.FindIndex(rhinoLayer.Index) is null) 14 | { 15 | var layerIndex = rhinoDoc.Layers.Add(rhinoLayer); 16 | layerResult = rhinoDoc.Layers.FindIndex(layerIndex); 17 | } 18 | 19 | if (string.Equals(layerResult.FullPath, crashLayer.FullPath, StringComparison.OrdinalIgnoreCase)) 20 | { 21 | return layerResult; 22 | } 23 | 24 | var lineage = CrashLayer.GetLayerLineage(crashLayer.FullPath).ToList(); 25 | Layer previousLayer = null; 26 | for (var i = 1; i < lineage.Count + 1; i++) 27 | { 28 | var range = lineage.GetRange(0, i); 29 | var fullPath = CrashLayer.GetFullPath(range); 30 | 31 | var layerIndex = rhinoDoc.Layers.FindByFullPath(fullPath, -1); 32 | if (layerIndex == -1) 33 | { 34 | layerIndex = AddLayer(rhinoDoc, fullPath); 35 | if (previousLayer is not null) 36 | { 37 | AssignParent(rhinoDoc, previousLayer.Id, layerIndex); 38 | } 39 | } 40 | 41 | previousLayer = rhinoDoc.Layers.FindIndex(layerIndex); 42 | } 43 | 44 | return layerResult; 45 | } 46 | 47 | private static void AssignParent(RhinoDoc rhinoDoc, Guid parentId, int layerIndex) 48 | { 49 | var layer = rhinoDoc.Layers.FindIndex(layerIndex); 50 | layer.ParentLayerId = parentId; 51 | } 52 | 53 | private static int AddLayer(RhinoDoc rhinoDoc, string fullPath) 54 | { 55 | var newLayer = new Layer { Name = CrashLayer.GetLayerNameFromPath(fullPath) }; 56 | return rhinoDoc.Layers.Add(newLayer); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Crash/Commands/AsyncCommand.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Handlers; 3 | 4 | using Rhino.Commands; 5 | 6 | namespace Crash.Commands 7 | { 8 | /// 9 | /// A wrapper for Rhino Commands to make Async awaiting easier and more centralised. 10 | /// https://discourse.mcneel.com/t/making-async-calls-from-runcommand/143160/7 11 | /// 12 | public abstract class AsyncCommand : Command 13 | { 14 | private bool Await { get; } 15 | 16 | internal AsyncCommand(bool await = false) 17 | { 18 | Await = await; 19 | } 20 | 21 | protected override Result RunCommand(RhinoDoc doc, RunMode mode) 22 | { 23 | var crashDoc = CrashDocRegistry.GetRelatedDocument(doc); 24 | 25 | try 26 | { 27 | if (Await) 28 | { 29 | Result result = Result.Failure; 30 | #pragma warning disable VSTHRD101 // Avoid unsupported async delegates 31 | Eto.Forms.Application.Instance.AsyncInvoke(async () => 32 | { 33 | try 34 | { 35 | result = await RunCommandAsync(doc, crashDoc, mode); 36 | } 37 | catch 38 | { 39 | 40 | } 41 | }); 42 | #pragma warning restore VSTHRD101 // Avoid unsupported async delegates 43 | return result; 44 | } 45 | else 46 | { 47 | #pragma warning disable VSTHRD110 // Observe result of async calls 48 | RunCommandAsync(doc, crashDoc, mode); 49 | #pragma warning restore VSTHRD110 // Observe result of async calls 50 | } 51 | 52 | return Result.Success; 53 | } 54 | catch (Exception ex) 55 | { 56 | RhinoApp.WriteLine(ex.Message); 57 | return Result.Failure; 58 | } 59 | } 60 | 61 | protected abstract Task RunCommandAsync(RhinoDoc rhinoDoc, CrashDoc crashDoc, RunMode mode); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Crash/Commands/LeaveSharedModel.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Communications; 2 | using Crash.Common.Changes; 3 | using Crash.Common.Document; 4 | using Crash.Handlers; 5 | using Crash.UI.UsersView; 6 | 7 | using Rhino.Commands; 8 | using Rhino.UI; 9 | 10 | namespace Crash.Commands 11 | { 12 | /// Command to Close a Shared Model 13 | [CommandStyle(Style.ScriptRunner)] 14 | public sealed class LeaveSharedModel : AsyncCommand 15 | { 16 | private bool defaultValue; 17 | 18 | public override string EnglishName => EnglishCommandName; 19 | public const string EnglishCommandName = "LeaveSharedModel"; 20 | 21 | protected override async Task RunCommandAsync(RhinoDoc doc, CrashDoc crashDoc, RunMode mode) 22 | { 23 | if (crashDoc?.LocalClient?.IsConnected != true) 24 | { 25 | RhinoApp.WriteLine("You are not connected to a Shared Model currently."); 26 | return Result.Cancel; 27 | } 28 | 29 | var choice = _GetReleaseChoice(); 30 | switch (choice) 31 | { 32 | case null: 33 | return Result.Cancel; 34 | case true: 35 | var doneChange = DoneChange.GetDoneChange(crashDoc.Users.CurrentUser.Name); 36 | await crashDoc.LocalClient.SendChangesToServerAsync(new List { doneChange }.ToAsyncEnumerable()); 37 | break; 38 | } 39 | 40 | doc.Objects.UnselectAll(); 41 | 42 | (crashDoc.LocalClient as CrashClient).ClosedByUser = true; 43 | await CrashDocRegistry.DisposeOfDocumentAsync(crashDoc); 44 | var pipe = InteractivePipe.GetActive(crashDoc); 45 | pipe.Enabled = false; 46 | 47 | RhinoApp.WriteLine("Model closed and saved successfully"); 48 | 49 | doc.Views.Redraw(); 50 | UsersForm.CloseActiveForm(crashDoc); 51 | LoadingUtils.Close(crashDoc); 52 | 53 | return Result.Success; 54 | } 55 | 56 | private bool? _GetReleaseChoice() 57 | { 58 | return SelectionUtils.GetBoolean(ref defaultValue, 59 | "Would you like to Release your Changes before exiting?", 60 | "JustExit", 61 | "ReleaseThenExit"); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Crash/Commands/Release.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Common.Document; 3 | 4 | using Rhino.Commands; 5 | 6 | namespace Crash.Commands 7 | { 8 | /// Command to Release Changes 9 | [CommandStyle(Style.DoNotRepeat | Style.NotUndoable)] 10 | public sealed class Release : AsyncCommand 11 | { 12 | 13 | public override string EnglishName => "Release"; 14 | 15 | protected override async Task RunCommandAsync(RhinoDoc doc, CrashDoc crashDoc, RunMode mode) 16 | { 17 | if (!CommandUtils.InSharedModel(crashDoc)) 18 | { 19 | RhinoApp.WriteLine("You aren't in a shared model."); 20 | return Result.Failure; 21 | } 22 | 23 | var doneChange = DoneChange.GetDoneChange(crashDoc.Users.CurrentUser.Name); 24 | 25 | await crashDoc.LocalClient.SendChangesToServerAsync(new List { doneChange }.ToAsyncEnumerable()); 26 | 27 | doc.Objects.UnselectAll(); 28 | doc.Views.Redraw(); 29 | 30 | return Result.Success; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Crash/Commands/ReleaseSelected.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Changes; 2 | using Crash.Common.Document; 3 | using Crash.Common.Tables; 4 | using Crash.Handlers; 5 | using Crash.Utils; 6 | 7 | using Rhino.Commands; 8 | 9 | namespace Crash.Commands 10 | { 11 | /// Command to Release Changes 12 | [CommandStyle(Style.DoNotRepeat | Style.NotUndoable)] 13 | public sealed class ReleaseSelected : AsyncCommand 14 | { 15 | public override string EnglishName => "ReleaseSelected"; 16 | 17 | protected override async Task RunCommandAsync(RhinoDoc doc, CrashDoc crashDoc, RunMode mode) 18 | { 19 | if (!CommandUtils.InSharedModel(crashDoc)) 20 | { 21 | RhinoApp.WriteLine("You aren't in a shared model."); 22 | return Result.Failure; 23 | } 24 | 25 | var selectedChanges = GetSelectedChanges(doc); 26 | if (!selectedChanges.Any()) 27 | { 28 | return Result.Cancel; 29 | } 30 | 31 | List changes = new List(); 32 | foreach (var changeId in selectedChanges) 33 | { 34 | var change = DoneChange.GetDoneChange(string.Empty); 35 | change.Id = changeId; 36 | changes.Add(change); 37 | } 38 | 39 | await crashDoc.LocalClient.SendChangesToServerAsync(changes.ToAsyncEnumerable()); 40 | 41 | doc.Objects.UnselectAll(); 42 | doc.Views.Redraw(); 43 | 44 | return Result.Success; 45 | } 46 | 47 | private static IEnumerable GetSelectedChanges(RhinoDoc doc) 48 | { 49 | var crashDoc = CrashDocRegistry.GetRelatedDocument(doc); 50 | if (!crashDoc.Tables.TryGet(out var realTable)) 51 | { 52 | return Enumerable.Empty(); 53 | } 54 | 55 | var selected = new List(); 56 | foreach (var rhinoObj in doc.Objects.GetSelectedObjects(false, false)) 57 | { 58 | if (!realTable.TryGetChangeId(rhinoObj.Id, out var changeId)) 59 | { 60 | continue; 61 | } 62 | 63 | selected.Add(changeId); 64 | } 65 | 66 | return selected; 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Crash/Commands/SelectionUtils.cs: -------------------------------------------------------------------------------- 1 | using Rhino.Commands; 2 | using Rhino.Input; 3 | using Rhino.Input.Custom; 4 | 5 | namespace Crash.Commands 6 | { 7 | /// Reusable Utilities for commands 8 | internal static class SelectionUtils 9 | { 10 | /// 11 | /// Asks a user to toggle between two options 12 | /// 13 | /// The default value iput 14 | /// The Prompt for the User 15 | /// The message for false 16 | /// The message for true 17 | /// null on Cancel. True or False from the user choice 18 | internal static bool? GetBoolean(ref bool defaultValue, string prompt, string offValue, string onValue) 19 | { 20 | var go = new GetOption(); 21 | go.AcceptEnterWhenDone(true); 22 | go.AcceptNothing(true); 23 | go.SetCommandPrompt(prompt); 24 | var releaseValue = new OptionToggle(defaultValue, offValue, onValue); 25 | var releaseIndex = go.AddOptionToggle("Choose", ref releaseValue); 26 | 27 | while (true) 28 | { 29 | var result = go.Get(); 30 | if (result == GetResult.Option && go.OptionIndex() == releaseIndex) 31 | { 32 | defaultValue = !defaultValue; 33 | } 34 | 35 | else if (result == GetResult.Cancel) 36 | { 37 | return null; 38 | } 39 | 40 | else if (result == GetResult.Nothing) 41 | { 42 | return defaultValue; 43 | } 44 | } 45 | } 46 | 47 | /// 48 | /// Asks the user to input a string 49 | /// 50 | /// The Prompt for the User 51 | /// The chosen Value, can set a default 52 | /// bool on success, false on cancel or empty value 53 | internal static bool GetValidString(string prompt, ref string value) 54 | { 55 | var getUrl = RhinoGet.GetString(prompt, true, ref value); 56 | if (string.IsNullOrEmpty(value)) 57 | { 58 | return false; 59 | } 60 | 61 | return getUrl == Result.Success; 62 | } 63 | 64 | /// 65 | /// Asks the user to input an integer 66 | /// 67 | /// The Prompt for the User 68 | /// The chosen Value, can set a default 69 | /// 70 | /// bool on success, false on cancel or integer < 0 71 | internal static bool GetPositiveInteger(string prompt, ref int value) 72 | { 73 | var getPort = RhinoGet.GetInteger(prompt, false, ref value); 74 | if (value <= 0) 75 | { 76 | return false; 77 | } 78 | 79 | return getPort == Result.Success; 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/Crash/Commands/ShowCrashUsers.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers; 2 | using Crash.UI.UsersView; 3 | 4 | using Rhino.Commands; 5 | 6 | namespace Crash.Commands 7 | { 8 | /// Toggles the Crash Users UI 9 | public sealed class ShowCrashUsers : Command 10 | { 11 | public override string EnglishName => nameof(ShowCrashUsers); 12 | 13 | protected override Result RunCommand(RhinoDoc doc, RunMode mode) 14 | { 15 | var crashDoc = CrashDocRegistry.GetRelatedDocument(doc); 16 | if (crashDoc is null) 17 | { 18 | return Result.Failure; 19 | } 20 | 21 | UsersForm.ShowForm(crashDoc); 22 | return Result.Success; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Crash/Plugins/CrashPluginLoader.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | 3 | using Crash.Handlers.Plugins; 4 | 5 | namespace Crash.Plugins 6 | { 7 | public class CrashPluginLoader 8 | { 9 | public const string Extension = ".op"; 10 | 11 | internal CrashPluginLoader(IEnumerable? crashPluginLocations) 12 | { 13 | CrashPluginLocations = crashPluginLocations ?? Array.Empty(); 14 | } 15 | 16 | private IEnumerable CrashPluginLocations { get; } 17 | 18 | 19 | internal List LoadCrashPlugins() 20 | { 21 | List changeDefinitions = new(); 22 | foreach (var pluginDirectory in CrashPluginLocations) 23 | { 24 | if (!Directory.Exists(pluginDirectory)) 25 | { 26 | continue; 27 | } 28 | 29 | var crashPluginExtensions = Directory.EnumerateFiles(pluginDirectory, $"*{Extension}")?.ToArray() ?? 30 | Array.Empty(); 31 | 32 | if (crashPluginExtensions.Length == 0) 33 | { 34 | continue; 35 | } 36 | 37 | foreach (var pluginAssembly in crashPluginExtensions) 38 | { 39 | var loadedChangeDefinitions = LoadCrashPlugin(pluginAssembly); 40 | changeDefinitions.AddRange(loadedChangeDefinitions); 41 | } 42 | } 43 | 44 | return changeDefinitions; 45 | } 46 | 47 | private List LoadCrashPlugin(string crashAssembly) 48 | { 49 | var assembly = LoadPlugin(crashAssembly); 50 | var changeDefinitionTypes = 51 | assembly.ExportedTypes.Where(et => typeof(IChangeDefinition).IsAssignableFrom(et)).ToList(); 52 | 53 | if (changeDefinitionTypes is null || changeDefinitionTypes.Count() == 0) 54 | { 55 | RhinoApp.WriteLine($"Could not find any type in {crashAssembly} that implements type {nameof(IChangeDefinition)}"); 56 | return new List(); 57 | } 58 | 59 | List newChangeDefinitions = new(changeDefinitionTypes.Count); 60 | foreach (var changeDefinitionType in changeDefinitionTypes) 61 | { 62 | var changeDefinition = Activator.CreateInstance(changeDefinitionType) as IChangeDefinition; 63 | if (changeDefinition is null) 64 | { 65 | RhinoApp.WriteLine($"Could not load {changeDefinitionType.Name}"); 66 | continue; 67 | } 68 | 69 | newChangeDefinitions.Add(changeDefinition); 70 | } 71 | 72 | return newChangeDefinitions; 73 | } 74 | 75 | private Assembly LoadPlugin(string crashAssembly) 76 | { 77 | #if NETFRAMEWORK 78 | return Assembly.LoadFrom(crashAssembly); 79 | 80 | #elif NET7_0_OR_GREATER 81 | RhinoApp.WriteLine($"Loading commands from: {crashAssembly}"); 82 | var loadContext = new PluginLoadContext(crashAssembly); 83 | return loadContext.LoadFromAssemblyName(new AssemblyName(Path.GetFileNameWithoutExtension(crashAssembly))); 84 | 85 | #else 86 | RhinoApp.WriteLine("An Unsupported Framework has been loaded"); 87 | #endif 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Crash/Plugins/PluginLoadContext.cs: -------------------------------------------------------------------------------- 1 | #if NET7_0_OR_GREATER 2 | using System.Reflection; 3 | using System.Runtime.Loader; 4 | #endif 5 | 6 | namespace Crash.Plugins 7 | { 8 | #if NET7_0_OR_GREATER 9 | internal class PluginLoadContext : AssemblyLoadContext 10 | { 11 | private readonly AssemblyDependencyResolver _resolver; 12 | 13 | public PluginLoadContext(string pluginPath) 14 | { 15 | _resolver = new AssemblyDependencyResolver(pluginPath); 16 | } 17 | 18 | protected override Assembly? Load(AssemblyName assemblyName) 19 | { 20 | var assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName); 21 | if (assemblyPath is not null) 22 | { 23 | return LoadFromAssemblyPath(assemblyPath); 24 | } 25 | 26 | return null; 27 | } 28 | 29 | protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) 30 | { 31 | var libraryPath = _resolver.ResolveUnmanagedDllToPath(unmanagedDllName); 32 | if (libraryPath is not null) 33 | { 34 | return LoadUnmanagedDllFromPath(libraryPath); 35 | } 36 | 37 | return IntPtr.Zero; 38 | } 39 | } 40 | #endif 41 | } 42 | -------------------------------------------------------------------------------- /src/Crash/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using Rhino.PlugIns; 2 | 3 | [assembly: PlugInDescription(DescriptionType.Organization, "CRASH!")] 4 | [assembly: PlugInDescription(DescriptionType.UpdateUrl, " rhino://package/search?q=Crash")] 5 | [assembly: PlugInDescription(DescriptionType.WebSite, "https://github.com/clicketyclackety/crash")] 6 | [assembly: PlugInDescription(DescriptionType.Icon, "Crash.EmbeddedResources.logo.ico")] 7 | -------------------------------------------------------------------------------- /src/Crash/Properties/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | global using System.Collections.Generic; 3 | global using System.Linq; 4 | 5 | global using Crash.Changes; 6 | global using Crash.UI; 7 | 8 | global using Rhino; 9 | -------------------------------------------------------------------------------- /src/Crash/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Win Rhino 7 net48": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 6 | "commandLineArgs": "/nosplash" 7 | }, 8 | "Win Rhino 8 net48": { 9 | "commandName": "Executable", 10 | "executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe", 11 | "commandLineArgs": "/nosplash /netfx" 12 | }, 13 | "Win Rhino 8 Net7.0": { 14 | "commandName": "Executable", 15 | "executablePath": "C:\\Program Files\\Rhino 8\\System\\Rhino.exe", 16 | "commandLineArgs": "/nosplash /dotnet" 17 | }, 18 | "Mac Rhino 8 Net7.0": { 19 | "commandName": "Executable", 20 | "executablePath": "/Applications/Rhino 8.app/Contents/MacOS/Rhinoceros", 21 | "hotReloadEnabled": true 22 | }, 23 | "Mac Rhino 7 net48": { 24 | "commandName": "Executable", 25 | "executablePath": "/Applications/Rhino 7.app/Contents/MacOS/Rhinoceros", 26 | "hotReloadEnabled": true 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /src/Crash/Resources/camera-follow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/camera-follow.png -------------------------------------------------------------------------------- /src/Crash/Resources/camera-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/camera-off.png -------------------------------------------------------------------------------- /src/Crash/Resources/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/camera.png -------------------------------------------------------------------------------- /src/Crash/Resources/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/close.png -------------------------------------------------------------------------------- /src/Crash/Resources/join.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/join.png -------------------------------------------------------------------------------- /src/Crash/Resources/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/plus.png -------------------------------------------------------------------------------- /src/Crash/Resources/reload-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/reload-all.png -------------------------------------------------------------------------------- /src/Crash/Resources/reload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/reload.png -------------------------------------------------------------------------------- /src/Crash/Resources/signal-low.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/signal-low.png -------------------------------------------------------------------------------- /src/Crash/Resources/signal-off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/signal-off.png -------------------------------------------------------------------------------- /src/Crash/Resources/signal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/signal.png -------------------------------------------------------------------------------- /src/Crash/Resources/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/Resources/user.png -------------------------------------------------------------------------------- /src/Crash/UI/BaseViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel; 2 | using System.Runtime.CompilerServices; 3 | 4 | namespace Crash.UI 5 | { 6 | 7 | public abstract class BaseViewModel : INotifyPropertyChanged 8 | { 9 | 10 | public void NotifyPropertyChanged([CallerMemberName] string propertyName = "") 11 | { 12 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); 13 | 14 | if (_propertyChangedActions.TryGetValue(propertyName, out var actions)) 15 | { 16 | foreach (var action in actions) 17 | { 18 | action?.Invoke(); 19 | } 20 | } 21 | } 22 | 23 | protected bool Set(ref T t, T val, [CallerMemberName] string propertyName = null) 24 | { 25 | if (!Equals(t, val)) 26 | { 27 | t = val; 28 | NotifyPropertyChanged(propertyName); 29 | return true; 30 | } 31 | 32 | return false; 33 | } 34 | 35 | public event PropertyChangedEventHandler? PropertyChanged; 36 | 37 | public BaseViewModel() 38 | { 39 | } 40 | 41 | private Dictionary> _propertyChangedActions { get; } = new Dictionary>(); 42 | 43 | public void ListenToProperty(string propertyName, Action action) 44 | { 45 | if (!_propertyChangedActions.TryGetValue(propertyName, out var actions)) 46 | { 47 | _propertyChangedActions.Add(propertyName, new List { action }); 48 | } 49 | else 50 | { 51 | actions.Add(action); 52 | } 53 | } 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/Crash/UI/CrashCommands.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Crash.Commands; 4 | using Crash.Common.App; 5 | using Crash.Common.Document; 6 | using Crash.Handlers; 7 | using Crash.Handlers.Data; 8 | using Crash.Resources; 9 | 10 | using Eto.Drawing; 11 | using Eto.Forms; 12 | 13 | namespace Crash.UI.RecentView; 14 | 15 | internal class CrashCommands : ICrashInstance 16 | { 17 | 18 | public class CrashCommand : Command 19 | { 20 | private string IconKey { get; } 21 | 22 | public string Name { get; } 23 | 24 | public bool Hover { get; set; } = false; 25 | 26 | public Color ColourOverride { get; set; } = Colors.Transparent; 27 | 28 | public CrashCommand(string name, string iconKey, Action action) : base((s, e) => action?.Invoke()) 29 | { 30 | Name = name; 31 | MenuText = name; 32 | ToolTip = name; 33 | ToolBarText = name; 34 | IconKey = iconKey; 35 | } 36 | 37 | public Bitmap GetIcon(int size) => ColourOverride == Colors.Transparent ? 38 | CrashIcons.Icon(IconKey, size) : 39 | CrashIcons.Icon(IconKey, size, ColourOverride); 40 | 41 | public Bitmap GetIcon(int size, Color colour) => CrashIcons.Icon(IconKey, size, colour); 42 | 43 | public override string ToString() => $"{Name}"; 44 | 45 | } 46 | 47 | private RecentModelDialog Host { get; } 48 | public CrashCommands(RecentModelDialog host) 49 | { 50 | Host = host; 51 | 52 | Add = new("Add", "plus", Host.ShowNewModelDialog); 53 | Join = new("Join", "join", Host.Model.JoinSelected); 54 | ReloadAll = new("Reload All", "reload-all", Host.Model.ReloadAll); 55 | Reload = new("Reload", "reload", Host.Model.ReloadSelected); 56 | Remove = new("Remove", "close", Host.Model.RemoveSelected) { ColourOverride = Palette.Red }; 57 | // TODO : Add Pin command 58 | } 59 | 60 | public CrashCommand Add { get; } 61 | public CrashCommand Join { get; } 62 | public CrashCommand Reload { get; } 63 | public CrashCommand Remove { get; } 64 | public CrashCommand ReloadAll { get; } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/Crash/UI/Palette.cs: -------------------------------------------------------------------------------- 1 | using Eto.Drawing; 2 | 3 | using Rhino.Runtime; 4 | 5 | namespace Crash.UI 6 | { 7 | public static class Palette 8 | { 9 | private static readonly Color s_darkGrey = Color.FromArgb(125, 125, 125); 10 | 11 | internal static bool DarkMode => HostUtils.RunningInDarkMode; 12 | 13 | // Text & Highlights 14 | 15 | public static Color TextColour => DarkMode ? White : Black; 16 | public static Color DisabledTextColour => DarkMode ? Color.FromArgb(222, 226, 230) : Color.FromArgb(73, 80, 87); 17 | 18 | // Crash Colours 19 | 20 | // public static Color SeaGreen => Color.FromArgb(0, 38, 38); 21 | 22 | public static Color White => Color.FromArgb(233, 241, 247); 23 | 24 | public static Color Yellow => Color.FromArgb(246, 174, 45); 25 | 26 | public static Color Black => Color.FromArgb(0, 21, 20); // 19, 27, 35 27 | public static Color DarkGray => Color.FromArgb(30, 30, 32); 28 | public static Color Gray => Color.FromArgb(65, 67, 97); 29 | public static Color LightGray => Color.FromArgb(200, 200, 200); 30 | 31 | public static Color Shadow => new Color(0f, 0f, 0f, 0.2f); 32 | 33 | public static Color Green => Color.FromArgb(64, 192, 87); 34 | 35 | public static Color Lime => Color.FromArgb(209, 214, 70); 36 | 37 | public static Color Purple => Color.FromArgb(109, 89, 122); 38 | 39 | public static Color Red => Color.FromArgb(215, 38, 56); 40 | 41 | public static Color Blue => Color.FromArgb(90, 177, 187); 42 | 43 | public static Color Pink => Color.FromArgb(247, 131, 172); 44 | 45 | public static Pen GetDashedPen(Color color, float width = 4f) 46 | { 47 | var pen = new Pen(color, width); 48 | pen.DashStyle = new DashStyle(4, 4); 49 | return pen; 50 | } 51 | 52 | public static TextureBrush GetHashedTexture(int size = 6, float opaciy = 0.5f) 53 | { 54 | var image = new Bitmap(new Size(size * 4, size * 4), PixelFormat.Format32bppRgba); 55 | using (var g = new Graphics(image)) 56 | { 57 | g.Clear(Palette.Black); 58 | int i = 1; 59 | for (int y = -4; y < size * 8; y++) 60 | { 61 | bool draw = false; 62 | i--; 63 | for (int x = -4; x < size * 8; x += size) 64 | { 65 | draw = !draw; 66 | if (!draw) continue; 67 | 68 | g.FillRectangle(Palette.Gray, new Rectangle(x + i, y, size, 1)); 69 | } 70 | } 71 | } 72 | 73 | TextureBrush brush = new TextureBrush(image, opaciy); 74 | return brush; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/Crash/UI/UsersView/UserObject.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Resources; 3 | 4 | using Eto.Drawing; 5 | 6 | using Rhino.UI; 7 | 8 | namespace Crash.UI.UsersView 9 | { 10 | 11 | // TODO : This is messy 12 | internal sealed class UserObject 13 | { 14 | private CrashDoc _crashDoc { get; set; } 15 | private CameraState _cameraState { get; set; } 16 | private bool _visible { get; set; } 17 | 18 | public bool IsCurrentUser { get; private set; } = false; 19 | 20 | internal EventHandler OnPropertyChanged; 21 | 22 | internal UserObject(CrashDoc crashDoc, User user) 23 | { 24 | _crashDoc = crashDoc; 25 | 26 | Name = user.Name; 27 | Colour = user.Color; 28 | 29 | _cameraState = user.Camera; 30 | _visible = user.Visible; 31 | } 32 | 33 | private UserObject() { } 34 | 35 | internal static UserObject CreateForCurrentUser(CrashDoc crashDoc) 36 | { 37 | var user = crashDoc.Users.CurrentUser; 38 | 39 | return new UserObject() 40 | { 41 | _crashDoc = crashDoc, 42 | Name = $"{user.Name} (You)", 43 | Colour = user.Color, 44 | _cameraState = CameraState.None, 45 | _visible = true, 46 | IsCurrentUser = true, 47 | }; 48 | } 49 | 50 | public string Name { get; private set; } 51 | public System.Drawing.Color Colour { get; private set; } 52 | 53 | public CameraState Camera 54 | { 55 | get => _cameraState; 56 | set 57 | { 58 | if (IsCurrentUser) return; 59 | _cameraState = value; 60 | _crashDoc.Users.Update(Convert(this)); 61 | OnPropertyChanged?.Invoke(null, this); 62 | } 63 | } 64 | 65 | public Bitmap Image => Camera switch 66 | { 67 | CameraState.Follow => CrashIcons.Icon("camera-follow", 24), 68 | CameraState.Visible => CrashIcons.Icon("camera", 24), 69 | 70 | _ => CrashIcons.Icon("camera-off", 24) 71 | }; 72 | 73 | public bool Visible 74 | { 75 | get => _visible; 76 | 77 | set 78 | { 79 | if (IsCurrentUser) return; 80 | _visible = value; 81 | _crashDoc.Users.Update(Convert(this)); 82 | Camera = value ? CameraState.Visible : CameraState.None; 83 | OnPropertyChanged?.Invoke(null, this); 84 | } 85 | } 86 | 87 | private static User Convert(UserObject userObject) 88 | { 89 | return new User(userObject.Name) { Camera = userObject.Camera, Visible = userObject.Visible }; 90 | } 91 | 92 | public override string ToString() 93 | { 94 | return Name; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Crash/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/crashcloud/Crash/968bb0caf2fb35a6c919a785e92b63c9e44fbcc5/src/Crash/icon.png -------------------------------------------------------------------------------- /src/Crash/manifest.yml: -------------------------------------------------------------------------------- 1 | name: Crash 2 | version: _YakVersion_ 3 | authors: 4 | - Callum Sykes 5 | - Lukas Fuhrimann 6 | - Curtis Wensley 7 | - Russell Feathers 8 | - Erika Santos 9 | - Morteza Karimi 10 | - Moustafa El-Sawy 11 | 12 | description: > 13 | Crash allows you to create shared models which can be interacted with by people within your office, or across the globe. 14 | keywords: 15 | - Multiuser 16 | - Crash 17 | icon: icon.png 18 | url: "https://github.com/crashcloud/Crash" 19 | -------------------------------------------------------------------------------- /src/Package.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Crash.Plugins 5 | 1.4.0-beta 6 | Crash SDK for Plugins 7 | CrashCloud 8 | false 9 | MIT 10 | 11 | http://github.com/crashcloud/crash 12 | SDK for creating Crash Plugins 13 | CrashCloud 14 | Crash CrashCloud Plugin Crash.Handlers Crash.Common 15 | docs\README.md 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | # Crash Plugin SDK 2 | 3 | Welcome to Crash Plugin Development. 4 | 5 | Crash Plugins is currently in beta. The API is liable to change based on user feedback and will likely not be finalised and stable until 2025. Please don't rely on any Crash Plugins for now. 6 | 7 | --- 8 | 9 | That being said, if you're here to have fun, you'll be right at home! 10 | 11 | ## Getting Started 12 | 13 | Clone or fork the CrashPluginExample from https://github.com/crashcloud/CrashPluginExample. 14 | 15 | ### Concepts 16 | 17 | **Plugin assembly** 18 | 19 | The Crash Plugin must be built into a `.mup` assembly. As some plugins are created for Rhino using C++ I made Crash require a separate DLL so these Plugins can create a separate C# DLL to consume anything required. Whilst it might make sense for some plugins to include Crash inside their main plugin, I'd like to make Crash separate and available for every plugin. You should include these in your `.yak` package (or at least distribute next to your `.rhp`) so that Crash can find them. 20 | 21 | **Changes** 22 | 23 | Changes are how we capture a change during Rhinos runtime. This can be a box being transformed, a Grasshopper component being added to the canvas, or anything you need to communicate to other users of the Shared Model. These changes can even be completely independent of the Rhino Doc. 24 | 25 | **Change Definition** 26 | 27 | The Change Definition describes how the changes are drawn, if at all, how they are categorised and what actions can be taken with the changes. 28 | 29 | **Change Create Action** 30 | 31 | Change Create Actions define the circumstances in which a Change is created. This could be a Box being moved, deleted, selected, or even a completely unique event in your Plugin! 32 | 33 | **Change Receive Action** 34 | 35 | Change Receive Actions define how changes get created locally when a Change is received from the server that matches the description of the Change Definition. 36 | 37 | **Events** 38 | If you do want to subscribe to custom events and let crash know, you MUST forward the event to `crashDoc.Dispatcher.NotifyServerAsync`. It is recommended, if you can, to create your own custom event args as this will make it easier for you to capture that change in your custom `ChangeCreateAction`. DO NOT subscribe to the default Rhino Events. These are already captured by Crash. If Crash is missing a Rhino Event you would like subscribed to, please open a PR and add it. 39 | 40 | # Contributing 41 | 42 | If you like or dislike the way the plugin API works, have requests, questions or more, please post on the Discourse Forums under the `Multi-user` category https://discourse.mcneel.com/c/plug-ins/multi-user/163. 43 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Changes/CameraChangeTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.View; 2 | using Crash.Geometry; 3 | 4 | namespace Crash.Common.Test.Changes 5 | { 6 | public sealed class CameraChangeTests 7 | { 8 | public static IEnumerable EqualCameras 9 | { 10 | get 11 | { 12 | var cameraOne = new Camera(CPoint.Origin, new CPoint(10, 20, 30)); 13 | yield return new object[] { cameraOne, cameraOne }; 14 | 15 | var cameraTwo = new Camera(new CPoint(-50, 20, 30.216), CPoint.Origin); 16 | yield return new object[] { cameraTwo, cameraTwo }; 17 | } 18 | } 19 | 20 | public static IEnumerable NotEqualCameras 21 | { 22 | get 23 | { 24 | yield return new object[] { Camera.None, new Camera(CPoint.Origin, new CPoint(1, 2, 3)) }; 25 | 26 | var cameraOne = new Camera(CPoint.Origin, new CPoint(10, 20, 30)); 27 | var cameraTwo = new Camera(new CPoint(10, 20, 30), CPoint.Origin); 28 | yield return new object[] { cameraOne, cameraTwo }; 29 | } 30 | } 31 | 32 | [TestCaseSource(nameof(EqualCameras))] 33 | [Parallelizable(ParallelScope.All)] 34 | public void CamerasAreEqual(Camera left, Camera right) 35 | { 36 | Assert.That(left, Is.EqualTo(right)); 37 | Assert.That(left == right, Is.True); 38 | Assert.That(left.Equals(right), Is.True); 39 | Assert.That(left.Equals((object)right), Is.True); 40 | Assert.That(left.GetHashCode() == right.GetHashCode()); 41 | } 42 | 43 | [TestCaseSource(nameof(NotEqualCameras))] 44 | [Parallelizable(ParallelScope.All)] 45 | public void CamerasAreNotEqual(Camera left, Camera right) 46 | { 47 | Assert.That(left, Is.Not.EqualTo(right)); 48 | Assert.That(left != right, Is.True); 49 | Assert.That(left.Equals(right), Is.False); 50 | Assert.That(left.Equals((object)right), Is.False); 51 | Assert.That(left.GetHashCode() != right.GetHashCode()); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Collections/FixedSizeQueue.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Collections; 2 | 3 | namespace Crash.Tests.Collections 4 | { 5 | public sealed class FixedSizedQueueTests 6 | { 7 | [Test] 8 | [Parallelizable] 9 | public void Enqueue_WhenQueueIsNotFull_AddsItemToQueue() 10 | { 11 | // Arrange 12 | var queue = new FixedSizedQueue(3); 13 | 14 | // Act 15 | queue.Enqueue(1); 16 | queue.Enqueue(2); 17 | 18 | // Assert 19 | Assert.That(queue.Count, Is.EqualTo(2)); 20 | Assert.That(new[] { 1, 2 }, Is.EqualTo(queue)); 21 | } 22 | 23 | [Test] 24 | [Parallelizable] 25 | public void Enqueue_WhenQueueIsFull_DiscardsOldestItem() 26 | { 27 | // Arrange 28 | var queue = new FixedSizedQueue(3); 29 | 30 | // Act 31 | queue.Enqueue(1); 32 | queue.Enqueue(2); 33 | queue.Enqueue(3); 34 | queue.Enqueue(4); 35 | 36 | // Assert 37 | Assert.That(queue.Count, Is.EqualTo(3)); 38 | Assert.That(new[] { 2, 3, 4 }, Is.EqualTo(queue)); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Crash.Common.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0 5 | enable 6 | enable 7 | 8 | false 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Document/DocumentTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Tests.Document 4 | { 5 | public sealed class DocumentTests 6 | { 7 | public static IEnumerable EqualCrashDocs 8 | { 9 | get 10 | { 11 | var docOne = new CrashDoc(); 12 | yield return new[] { docOne, docOne }; 13 | 14 | var docTwo = new CrashDoc(); 15 | yield return new[] { docTwo, docTwo }; 16 | } 17 | } 18 | 19 | public static IEnumerable NotEqualCrashDocs 20 | { 21 | get 22 | { 23 | yield return new CrashDoc[] { new(), new() }; 24 | yield return new CrashDoc[] { new(), new() }; 25 | } 26 | } 27 | 28 | [TestCaseSource(nameof(EqualCrashDocs))] 29 | [Parallelizable(ParallelScope.All)] 30 | public void CrashDocsAreEqual(CrashDoc left, CrashDoc right) 31 | { 32 | Assert.That(left, Is.EqualTo(right)); 33 | Assert.That(left == right, Is.True); 34 | Assert.That(left.Equals(right), Is.True); 35 | Assert.That(left.Equals((object)right), Is.True); 36 | Assert.That(left.GetHashCode() == right.GetHashCode()); 37 | } 38 | 39 | [TestCaseSource(nameof(NotEqualCrashDocs))] 40 | [Parallelizable(ParallelScope.All)] 41 | public void CrashDocsAreNotEqual(CrashDoc left, CrashDoc right) 42 | { 43 | Assert.That(left, Is.Not.EqualTo(right)); 44 | Assert.That(left != right, Is.True); 45 | Assert.That(left.Equals(right), Is.False); 46 | Assert.That(left.Equals((object)right), Is.False); 47 | Assert.That(left.GetHashCode() != right.GetHashCode()); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Document/UserTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | 3 | namespace Crash.Common.Tests.Document 4 | { 5 | public sealed class UserTests 6 | { 7 | public static IEnumerable EqualUsers 8 | { 9 | get 10 | { 11 | yield return new User[] { new("Jack"), new("Jack") }; 12 | yield return new User[] { new("James"), new("James") }; 13 | } 14 | } 15 | 16 | public static IEnumerable NotEqualUsers 17 | { 18 | get 19 | { 20 | yield return new User[] { new("James"), new("Jack") }; 21 | yield return new User[] { new("Jack"), new("James") }; 22 | } 23 | } 24 | 25 | [TestCaseSource(nameof(EqualUsers))] 26 | [Parallelizable(ParallelScope.All)] 27 | public void UsersAreEqual(User left, User right) 28 | { 29 | Assert.That(left, Is.EqualTo(right)); 30 | Assert.That(left == right, Is.True); 31 | Assert.That(left.Equals(right), Is.True); 32 | Assert.That(left.Equals((object)right), Is.True); 33 | Assert.That(left.GetHashCode() == right.GetHashCode()); 34 | } 35 | 36 | [TestCaseSource(nameof(NotEqualUsers))] 37 | [Parallelizable(ParallelScope.All)] 38 | public void UsersAreNotEqual(User left, User right) 39 | { 40 | Assert.That(left, Is.Not.EqualTo(right)); 41 | Assert.That(left != right, Is.True); 42 | Assert.That(left.Equals(right), Is.False); 43 | Assert.That(left.Equals((object)right), Is.False); 44 | Assert.That(left.GetHashCode() != right.GetHashCode()); 45 | } 46 | 47 | [Theory] 48 | [TestCase("James")] 49 | [TestCase("Jack")] 50 | [TestCase("A")] 51 | [Parallelizable(ParallelScope.All)] 52 | public void ValidUsers(string? name) 53 | { 54 | var user = new User(name); 55 | Assert.That(user.IsValid(), Is.EqualTo(true)); 56 | Assert.That(user.Color, Is.Not.EqualTo(User.DefaultColour)); 57 | } 58 | 59 | [Theory] 60 | [TestCase(null)] 61 | [TestCase("")] 62 | [Parallelizable(ParallelScope.All)] 63 | public void InValidUsers(string? name) 64 | { 65 | var user = new User(name); 66 | Assert.That(user.IsValid(), Is.EqualTo(false)); 67 | Assert.That(user.Color, Is.EqualTo(User.DefaultColour)); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Events/CrashEventArgsTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.Document; 2 | using Crash.Common.Events; 3 | 4 | namespace Crash.Common.Tests.Events 5 | { 6 | public sealed class CrashEventArgsTests 7 | { 8 | [Test] 9 | public void Constructor() 10 | { 11 | var crashDoc = new CrashDoc(); 12 | var args = new CrashEventArgs(crashDoc); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Events/IdleActionTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Events; 5 | 6 | namespace Crash.Common.Tests.Events 7 | { 8 | public sealed class IdleActionTests 9 | { 10 | [Test] 11 | public void AttemptDoubleInvoke_InvokesOnce() 12 | { 13 | var doc = new CrashDoc(); 14 | var change = new Change(); 15 | var idleArgs = new IdleArgs(doc, change); 16 | 17 | var invokeCount = 0; 18 | 19 | Action action = args => 20 | { 21 | invokeCount += 1; 22 | }; 23 | 24 | var idleAction = new IdleAction(action, idleArgs); 25 | 26 | Assert.That(invokeCount, Is.EqualTo(0)); 27 | 28 | idleAction.Invoke(); 29 | Assert.That(invokeCount, Is.EqualTo(1)); 30 | 31 | idleAction.Invoke(); 32 | Assert.That(invokeCount, Is.EqualTo(1)); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Serialization/CTransformSerialization.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Crash.Geometry; 5 | 6 | namespace Crash.Common.Tests.Serialization 7 | { 8 | [TestFixture] 9 | public sealed class CTransformSerializationTests 10 | { 11 | internal static readonly JsonSerializerOptions TestOptions; 12 | 13 | static CTransformSerializationTests() 14 | { 15 | TestOptions = new JsonSerializerOptions 16 | { 17 | IgnoreReadOnlyFields = true, 18 | IgnoreReadOnlyProperties = true, 19 | IncludeFields = true, 20 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, 21 | ReadCommentHandling = JsonCommentHandling.Skip, 22 | WriteIndented = true // TODO : Should this be avoided? Does it add extra memory? 23 | }; 24 | } 25 | 26 | [TestCaseSource(typeof(InvalidTransformValues), nameof(InvalidTransformValues.TestCases))] 27 | public void TestCTransformSerializationMaximums(CTransform transform) 28 | { 29 | TestCTransformSerialization(transform); 30 | } 31 | 32 | [TestCase(1)] 33 | [TestCase(10)] 34 | [TestCase(100)] 35 | [Parallelizable] 36 | public void TestCTransformSerializationRandom(int count) 37 | { 38 | for (var i = 0; i < count; i++) 39 | { 40 | var doubleValues = GetRandomTransformValues().ToArray(); 41 | TestCTransformSerialization(new CTransform(doubleValues)); 42 | } 43 | } 44 | 45 | private IEnumerable GetRandomTransformValues() 46 | { 47 | for (double i = 0; i < 16; i++) 48 | { 49 | yield return TestContext.CurrentContext.Random.NextDouble(-10_000, 10_000); 50 | } 51 | } 52 | 53 | private void TestCTransformSerialization(CTransform cTransform) 54 | { 55 | var json = JsonSerializer.Serialize(cTransform, TestOptions); 56 | var cTransformOut = JsonSerializer.Deserialize(json, TestOptions); 57 | 58 | var cDoublesEnumer = cTransform.GetEnumerator(); 59 | var cDoublesOutEnumer = cTransformOut.GetEnumerator(); 60 | while (cDoublesEnumer.MoveNext() & 61 | cDoublesOutEnumer.MoveNext()) 62 | { 63 | Assert.That(cDoublesEnumer.Current, Is.EqualTo(cDoublesOutEnumer.Current)); 64 | } 65 | } 66 | 67 | public static class InvalidTransformValues 68 | { 69 | public static IEnumerable TestCases 70 | { 71 | get 72 | { 73 | yield return new CTransform(double.NaN); 74 | yield return new CTransform(double.NegativeInfinity); 75 | yield return new CTransform(double.PositiveInfinity); 76 | yield return new CTransform(double.MaxValue); 77 | yield return new CTransform(double.MinValue); 78 | } 79 | } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Serialization/CameraSerialization.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | using Crash.Common.View; 5 | using Crash.Geometry; 6 | 7 | namespace Crash.Changes.Tests.Serialization 8 | { 9 | [TestFixture] 10 | public sealed class CameraSerialization 11 | { 12 | private const double MIN = -123456789.123456789; 13 | private const double MAX = MIN * -1; 14 | 15 | internal static readonly JsonSerializerOptions TestOptions; 16 | 17 | static CameraSerialization() 18 | { 19 | TestOptions = new JsonSerializerOptions 20 | { 21 | IgnoreReadOnlyFields = true, 22 | IgnoreReadOnlyProperties = true, 23 | IncludeFields = true, 24 | NumberHandling = JsonNumberHandling.AllowNamedFloatingPointLiterals, 25 | ReadCommentHandling = JsonCommentHandling.Skip, 26 | WriteIndented = true // TODO : Should this be avoided? Does it add extra memory? 27 | }; 28 | } 29 | 30 | [TestCase(1)] 31 | [TestCase(10)] 32 | [TestCase(100)] 33 | [Parallelizable] 34 | public void TestCameraSerializationRandom(int count) 35 | { 36 | for (var i = 0; i < count; i++) 37 | { 38 | var xLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 39 | var yLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 40 | var zLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 41 | var location = new CPoint(xLocation, yLocation, zLocation); 42 | 43 | var xTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 44 | var yTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 45 | var zTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 46 | var target = new CPoint(xTarget, yTarget, zTarget); 47 | 48 | var ticks = TestContext.CurrentContext.Random.NextLong(DateTime.MinValue.Ticks, 49 | DateTime.MaxValue.Ticks); 50 | var time = new DateTime(ticks); 51 | 52 | var camera = new Camera(location, target) { Stamp = time }; 53 | 54 | TestCameraSerializtion(camera); 55 | } 56 | } 57 | 58 | private void TestCameraSerializtion(Camera Camera) 59 | { 60 | var json = JsonSerializer.Serialize(Camera, TestOptions); 61 | var CameraOut = JsonSerializer.Deserialize(json, TestOptions); 62 | Assert.That(Camera, Is.EqualTo(CameraOut)); 63 | Assert.That(Camera.Stamp, Is.EqualTo(CameraOut.Stamp)); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using NUnit.Framework; 2 | 3 | global using System.Collections.Generic; 4 | global using System.Collections; 5 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Validity/CTransforms.cs: -------------------------------------------------------------------------------- 1 | using Crash.Geometry; 2 | 3 | namespace Crash.Common.Tests.Validity 4 | { 5 | [TestFixture] 6 | public sealed class CTransformValidity 7 | { 8 | [TestCase(1)] 9 | [TestCase(10)] 10 | [TestCase(100)] 11 | public void IsValidExplicit(int count) 12 | { 13 | for (var i = 0; i < count; i++) 14 | { 15 | var m00 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 16 | var m01 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 17 | var m02 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 18 | var m03 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 19 | 20 | var m10 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 21 | var m11 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 22 | var m12 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 23 | var m13 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 24 | 25 | var m20 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 26 | var m21 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 27 | var m22 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 28 | var m23 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 29 | 30 | var m30 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 31 | var m31 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 32 | var m32 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 33 | var m33 = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 34 | 35 | var transform = new CTransform(m00, m01, m02, m03, 36 | m10, m11, m12, m13, 37 | m20, m21, m22, m23, 38 | m30, m31, m32, m33); 39 | 40 | 41 | Assert.That(transform.IsValid(), Is.True); 42 | } 43 | } 44 | 45 | [TestCaseSource(typeof(InvalidTransformValues), nameof(InvalidValues.TestCases))] 46 | public bool IsNotValid(double[] mValues) 47 | { 48 | var transform = new CTransform(mValues); 49 | return transform.IsValid(); 50 | } 51 | } 52 | 53 | public sealed class InvalidTransformValues 54 | { 55 | public static IEnumerable TestCases 56 | { 57 | get 58 | { 59 | yield return new TestCaseData(new[] { double.MinValue }).Returns(false); 60 | yield return new TestCaseData(new[] { double.MaxValue }).Returns(false); 61 | yield return new TestCaseData(new[] { double.NaN }).Returns(false); 62 | yield return new TestCaseData(new[] { double.NaN, double.MaxValue }).Returns(false); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /tests/Crash.Common.Tests/Validity/Camera.cs: -------------------------------------------------------------------------------- 1 | using Crash.Common.View; 2 | using Crash.Geometry; 3 | 4 | namespace Crash.Common.Tests.Validity 5 | { 6 | public sealed class CameraValidity 7 | { 8 | private const double MIN = -123456789.123456789; 9 | private const double MAX = MIN * -1; 10 | 11 | [TestCase(1)] 12 | [TestCase(10)] 13 | [TestCase(100)] 14 | public void IsValid(int count) 15 | { 16 | for (var i = 0; i < count; i++) 17 | { 18 | var xLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 19 | var yLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 20 | var zLocation = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 21 | var location = new CPoint(xLocation, yLocation, zLocation); 22 | 23 | var xTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 24 | var yTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 25 | var zTarget = TestContext.CurrentContext.Random.NextDouble(MIN, MAX); 26 | var target = new CPoint(xTarget, yTarget, zTarget); 27 | 28 | var ticks = TestContext.CurrentContext.Random.NextLong(DateTime.MinValue.Ticks, 29 | DateTime.MaxValue.Ticks); 30 | var time = new DateTime(ticks); 31 | 32 | var camera = new Camera(location, target) { Stamp = time }; 33 | 34 | Assert.That(camera.IsValid(), Is.True); 35 | } 36 | } 37 | 38 | [TestCaseSource(typeof(InvalidValues), nameof(InvalidValues.TestCases))] 39 | public bool IsNotValid(CPoint point, CPoint target, DateTime time) 40 | { 41 | var camera = new Camera(point, target) { Stamp = time }; 42 | 43 | return camera.IsValid(); 44 | } 45 | } 46 | 47 | public sealed class InvalidValues 48 | { 49 | public static IEnumerable TestCases 50 | { 51 | get 52 | { 53 | yield return new TestCaseData(new CPoint(0), new CPoint(0), DateTime.Now).Returns(false); 54 | yield return new TestCaseData(CPoint.None, CPoint.None, DateTime.MaxValue).Returns(false); 55 | yield return new TestCaseData(CPoint.None, CPoint.None, DateTime.MinValue).Returns(false); 56 | yield return new TestCaseData(CPoint.Origin, new CPoint(1, 2, 3), DateTime.MinValue).Returns(false); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Changes/GeometryChangeTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Handlers.Changes; 4 | 5 | using Rhino.Geometry; 6 | 7 | namespace Crash.Handlers.Tests.Changes 8 | { 9 | 10 | [RhinoTestFixture] 11 | public sealed class GeometryChangeTests 12 | { 13 | public static IEnumerable ValidGeometry 14 | { 15 | get 16 | { 17 | yield return new Point(NRhino.Random.Geometry.NPoint3d.Any()); 18 | yield return NRhino.Random.Geometry.NLineCurve.Any(); 19 | yield return new TextDot("Test", Point3d.Origin); 20 | yield return new LinearDimension(Plane.WorldXY, new Point2d(-100, -100), new Point2d(100, 100), 21 | new Point2d(10, 20)); 22 | 23 | var _int = new Interval(-100, 100); 24 | var box = new Box(Plane.WorldXY, _int, _int, _int); 25 | 26 | yield return NRhino.Random.Geometry.NMesh.Any(); 27 | yield return Brep.CreateFromBox(box); 28 | yield return SubD.CreateFromMesh(Mesh.CreateFromBox(box, 10, 10, 10)); 29 | } 30 | } 31 | 32 | public static IEnumerable InValidGeometry 33 | { 34 | get 35 | { 36 | yield return null; 37 | yield return new LineCurve(Point3d.Unset, Point3d.Unset); 38 | yield return new Point(Point3d.Unset); 39 | yield return new Brep(); 40 | yield return new Mesh(); 41 | } 42 | } 43 | 44 | [TestCaseSource(nameof(ValidGeometry))] 45 | public void CreateGeometryChange_Successful(GeometryBase geom) 46 | { 47 | var change = GeometryChange.CreateNew(geom, "Me"); 48 | Assert.That(change, Is.Not.Null); 49 | Assert.That(change.Geometry, Is.EqualTo(geom)); 50 | Assert.That(change.Payload, Is.Not.Null); 51 | 52 | Assert.That(GeometryChange.GeneratePayload(geom), Is.EqualTo(change.Payload)); 53 | } 54 | 55 | [TestCaseSource(nameof(InValidGeometry))] 56 | public void CreateGeometryChange_Failures(GeometryBase geom) 57 | { 58 | // Test 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Crash.Handlers.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | NU5104,NU1903 13 | 14 | 15 | NU5104,NU1903 16 | 17 | 18 | all 19 | runtime; build; native; contentfiles; analyzers; buildtransitive 20 | 21 | 22 | 23 | 24 | NU5104,NU1903,NU1701,NU1608 25 | 26 | 27 | NU5104,NU1903,NU1701,NU1608 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Collections; 2 | global using System.Linq; 3 | global using System; 4 | 5 | global using Crash.Geometry; 6 | global using Crash.Changes; 7 | global using Crash.Common; 8 | 9 | global using Rhino.Testing.Fixtures; 10 | global using Rhino.Testing; 11 | global using Rhino.Geometry; 12 | global using Rhino; 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/Camera/CameraCreateActionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Text.Json; 3 | 4 | using Crash.Changes; 5 | using Crash.Common.Document; 6 | using Crash.Handlers.InternalEvents; 7 | using Crash.Handlers.Plugins; 8 | using Crash.Handlers.Plugins.Camera.Create; 9 | 10 | namespace Crash.Handlers.Tests.Plugins 11 | { 12 | [RhinoTestFixture] 13 | public sealed class CameraCreateActionTests 14 | { 15 | private readonly CrashDoc _cdoc; 16 | 17 | 18 | public CameraCreateActionTests() 19 | { 20 | _cdoc = new CrashDoc(); 21 | } 22 | 23 | public static IEnumerable ViewArgs 24 | { 25 | get 26 | { 27 | for (var i = 0; i < 10; i++) 28 | { 29 | var location = NRhino.Random.Geometry.NPoint3d.Any().ToCrash(); 30 | var target = NRhino.Random.Geometry.NPoint3d.Any().ToCrash(); 31 | 32 | yield return new CrashViewArgs(null, location, target); 33 | } 34 | } 35 | } 36 | 37 | [TestCaseSource(nameof(ViewArgs))] 38 | public void CameraCreateAction_CanConvert(CrashViewArgs viewArgs) 39 | { 40 | var cameraArgs = new CreateRecieveArgs(ChangeAction.Add, viewArgs, _cdoc); 41 | var createAction = new CameraCreateAction(); 42 | Assert.That(createAction.CanConvert(null, cameraArgs), Is.True); 43 | } 44 | 45 | [TestCaseSource(nameof(ViewArgs))] 46 | public void CameraCreateAction_TryConvert(CrashViewArgs viewargs) 47 | { 48 | var createArgs = new CreateRecieveArgs(ChangeAction.Add, viewargs, _cdoc); 49 | var createAction = new CameraCreateAction(); 50 | Assert.That(createAction.TryConvert(null, createArgs, out var changes), Is.True); 51 | Assert.That(changes, Is.Not.Empty); 52 | foreach (var change in changes) 53 | { 54 | Assert.That(change.Action, Is.EqualTo(ChangeAction.Add)); 55 | 56 | // Check this succeeds 57 | JsonSerializer.Deserialize(change.Payload); 58 | } 59 | } 60 | 61 | [OneTimeTearDown] 62 | public void TearDown() 63 | { 64 | _cdoc.Dispose(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/Camera/CameraRecieveActionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Changes; 4 | using Crash.Common.Changes; 5 | using Crash.Common.Document; 6 | using Crash.Handlers.Plugins.Camera.Recieve; 7 | 8 | namespace Crash.Handlers.Tests.Plugins.Camera 9 | { 10 | [RhinoTestFixture] 11 | public sealed class CameraRecieveActionTests 12 | { 13 | public static IEnumerable CameraChanges 14 | { 15 | get 16 | { 17 | for (var i = 0; i < 10; i++) 18 | { 19 | var location = NRhino.Random.Geometry.NPoint3d.Any().ToCrash(); 20 | var target = NRhino.Random.Geometry.NPoint3d.Any().ToCrash(); 21 | yield return new Common.View.Camera(location, target); 22 | } 23 | } 24 | } 25 | 26 | [TestCaseSource(nameof(CameraChanges))] 27 | public async Task CameraRecieveAction_CanConvert(Common.View.Camera camera) 28 | { 29 | var username = Path.GetRandomFileName().Replace(".", ""); 30 | IChange change = CameraChange.CreateNew(camera, username); 31 | var serverChange = new Change(change); 32 | 33 | var crashDoc = new CrashDoc(); 34 | 35 | var recieveAction = new CameraRecieveAction(); 36 | 37 | Assert.That(crashDoc.Cameras, Is.Empty); 38 | await recieveAction.OnRecieveAsync(crashDoc, serverChange); 39 | while (crashDoc.Queue.Count > 0) 40 | { 41 | crashDoc.Queue.RunNextAction(); 42 | } 43 | 44 | Assert.That(crashDoc.Cameras, Is.Not.Empty); 45 | 46 | Assert.That(crashDoc.Cameras.TryGetCamera(new User(username), out var cameras), Is.True); 47 | Assert.That(cameras, Has.Count.EqualTo(1)); 48 | Assert.That(cameras.FirstOrDefault(), Is.EqualTo(camera)); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/CreateRecieveArgTests.cs: -------------------------------------------------------------------------------- 1 | using Crash.Changes; 2 | using Crash.Common.Document; 3 | using Crash.Common.Events; 4 | using Crash.Handlers.Plugins; 5 | 6 | namespace Crash.Handlers.Tests.Plugins 7 | { 8 | [RhinoTestFixture] 9 | public sealed class CreateRecieveArgTests 10 | { 11 | [Test] 12 | public void ValidityTests() 13 | { 14 | var doc = new CrashDoc(); 15 | var eargs = new CrashEventArgs(doc); 16 | var args = new CreateRecieveArgs(ChangeAction.Add, eargs, doc); 17 | } 18 | 19 | [Test] 20 | public void Creation_BadInputs() 21 | { 22 | Assert.Throws(() => new CreateRecieveArgs((ChangeAction)(-1), null, null)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/Geometry/GeometryAddRecieveActionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Changes; 4 | using Crash.Common.Document; 5 | using Crash.Common.Tables; 6 | using Crash.Handlers.Changes; 7 | using Crash.Handlers.Plugins.Geometry.Recieve; 8 | 9 | using Rhino; 10 | 11 | namespace Crash.Handlers.Tests.Plugins.Geometry 12 | { 13 | [RhinoTestFixture] 14 | public sealed class GeometryAddRecieveActionTests 15 | { 16 | private readonly CrashDoc _cdoc; 17 | private readonly RhinoDoc _rdoc; 18 | 19 | public GeometryAddRecieveActionTests() 20 | { 21 | RhinoDoc.ActiveDoc = _rdoc = RhinoDoc.CreateHeadless(null); 22 | _cdoc = CrashDocRegistry.CreateAndRegisterDocument(_rdoc); 23 | } 24 | 25 | public static IEnumerable AddChanges 26 | { 27 | get 28 | { 29 | for (var i = 0; i < 10; i++) 30 | { 31 | var owner = Path.GetRandomFileName().Replace(".", ""); 32 | var lineCurve = NRhino.Random.Geometry.NLineCurve.Any(); 33 | IChange change = GeometryChange.CreateNew(lineCurve, owner); 34 | 35 | yield return new Change(change); 36 | } 37 | } 38 | } 39 | 40 | [TestCaseSource(nameof(AddChanges))] 41 | public async Task TestGeometryAddRecieveAction(Change change) 42 | { 43 | var addAction = new GeometryAddRecieveAction(); 44 | await addAction.OnRecieveAsync(_cdoc, change); 45 | while (_cdoc.Queue.Count > 0) 46 | { 47 | _cdoc.Queue.RunNextAction(); 48 | } 49 | 50 | // ChangeUtils.TryGetChangeId() ? 51 | 52 | // Assert that RhinoDoc had something added 53 | Assert.That(_rdoc.Objects, Is.Not.Empty); 54 | Assert.That(_cdoc.Tables.TryGet(out var tempTable), Is.True); 55 | Assert.That(tempTable, Is.Empty); 56 | } 57 | 58 | [OneTimeTearDown] 59 | public void TearDown() 60 | { 61 | _cdoc.Dispose(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/Geometry/GeometryTransformtActionTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Changes; 4 | using Crash.Common.Document; 5 | using Crash.Geometry; 6 | using Crash.Handlers.InternalEvents; 7 | using Crash.Handlers.Plugins; 8 | using Crash.Handlers.Plugins.Geometry.Create; 9 | using Crash.Handlers.Tests.Plugins.Geometry; 10 | 11 | namespace Crash.Handlers.Tests.Plugins 12 | { 13 | [RhinoTestFixture] 14 | public sealed class GeometryTransformtActionTests 15 | { 16 | private readonly CrashDoc _cdoc; 17 | 18 | public GeometryTransformtActionTests() 19 | { 20 | _cdoc = new CrashDoc(); 21 | } 22 | 23 | public static IEnumerable TransformArgs 24 | { 25 | get 26 | { 27 | foreach (var crashObject in SharedUtils.SelectObjects) 28 | { 29 | var doubles = new double[16]; 30 | for (var i = 0; i < 16; i++) 31 | { 32 | var value = TestContext.CurrentContext.Random.NextDouble(short.MinValue, short.MaxValue); 33 | doubles[i] = value; 34 | } 35 | 36 | var transform = new CTransform(doubles); 37 | var objects = new List { crashObject }; 38 | yield return new CrashTransformEventArgs(null, transform, objects, false); 39 | } 40 | } 41 | } 42 | 43 | [OneTimeTearDown] 44 | public void TearDown() 45 | { 46 | _cdoc.Dispose(); 47 | } 48 | 49 | [TestCaseSource(nameof(TransformArgs))] 50 | public void GeometrySelectAction_CanConvert(CrashTransformEventArgs transformArgs) 51 | { 52 | var createArgs = new CreateRecieveArgs(ChangeAction.Transform, transformArgs, _cdoc); 53 | var createAction = new GeometryTransformAction(); 54 | Assert.That(createAction.CanConvert(null, createArgs), Is.True); 55 | } 56 | 57 | [TestCaseSource(nameof(TransformArgs))] 58 | public void GeometryTransformAction_TryConvert(CrashTransformEventArgs transformArgs) 59 | { 60 | var createArgs = new CreateRecieveArgs(ChangeAction.Transform, transformArgs, _cdoc); 61 | var createAction = new GeometryTransformAction(); 62 | Assert.That(createAction.TryConvert(null, createArgs, out var changes), Is.True); 63 | Assert.That(changes, Is.Not.Empty); 64 | foreach (var change in changes) 65 | { 66 | Assert.That(change.Action, Is.EqualTo(createArgs.Action)); 67 | } 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Plugins/Geometry/SharedUtils.cs: -------------------------------------------------------------------------------- 1 | using Crash.Handlers.InternalEvents; 2 | 3 | using Rhino.Geometry; 4 | 5 | namespace Crash.Handlers.Tests.Plugins.Geometry 6 | { 7 | internal static class SharedUtils 8 | { 9 | internal static IEnumerable SelectObjects 10 | { 11 | get 12 | { 13 | var geometryGen = new Func[] 14 | { 15 | NRhino.Random.Geometry.NBrep.Any, NRhino.Random.Geometry.NMesh.Any, 16 | NRhino.Random.Geometry.NLineCurve.Any 17 | }; 18 | 19 | // TODO : Fix 20 | throw new NotImplementedException("Fix this!"); 21 | for (var i = 0; i < geometryGen.Length; i++) 22 | { 23 | var geom = geometryGen[i](); 24 | for (var j = 0; j < 5; j++) 25 | { 26 | // yield return new CrashObject(Guid.NewGuid(), Guid.NewGuid(), geom); 27 | } 28 | } 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Properties/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | 3 | global using NUnit.Framework; 4 | -------------------------------------------------------------------------------- /tests/Crash.Handlers.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Run": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 6 | "commandLineArgs": "/nosplash /notemplate /runscript=\"_-NUnitTestRunner $(MSBuildProjectDirectory)\\\\bin\\\\$(Configuration)/net48\\\\Crash.Handlers.tests.dll\"" 7 | }, 8 | "Debug": { 9 | "commandName": "Executable", 10 | "executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 11 | "commandLineArgs": "/nosplash /notemplate /runscript=\"NUnitTestRunner" 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /tests/Crash.Tests/Crash.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | false 5 | net48 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | NU5104,NU1903,NU1701,NU1608 20 | 21 | 22 | NU5104,NU1903,NU1701,NU1608 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /tests/Crash.Tests/Crash.Tests.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.002.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Crash.Tests", "Crash.Tests.csproj", "{58787B74-A356-4713-B355-4CD82E1D1B5F}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {58787B74-A356-4713-B355-4CD82E1D1B5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {58787B74-A356-4713-B355-4CD82E1D1B5F}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {58787B74-A356-4713-B355-4CD82E1D1B5F}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {58787B74-A356-4713-B355-4CD82E1D1B5F}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {DDAB2AE4-C0C7-4FCD-AA39-262292C6F73B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /tests/Crash.Tests/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System.Linq; 2 | global using System; 3 | 4 | global using Crash.Changes; 5 | global using Crash.Common; 6 | 7 | global using Rhino.Testing.Fixtures; 8 | global using Rhino.Testing; 9 | -------------------------------------------------------------------------------- /tests/Crash.Tests/Plugins/PluginLoaderTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | 3 | using Crash.Plugins; 4 | 5 | namespace Crash.Tests.Plugins 6 | { 7 | [RhinoTestFixture] 8 | public sealed class PluginLoaderTests 9 | { 10 | public static IEnumerable PluginSources 11 | { 12 | get 13 | { 14 | yield return "Example Plugin Directory"; 15 | } 16 | } 17 | 18 | [TestCaseSource(nameof(PluginSources))] 19 | public void LoadPlugin(string pluginSource) 20 | { 21 | var loader = new CrashPluginLoader(new[] { pluginSource }); 22 | var changeDefinitions = loader.LoadCrashPlugins(); 23 | Assert.That(changeDefinitions, Is.Not.Null.Or.Empty); 24 | Assert.That(changeDefinitions.Count, Is.GreaterThan(0)); 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /tests/Crash.Tests/Properties/GlobalUsings.cs: -------------------------------------------------------------------------------- 1 | global using System; 2 | 3 | global using NUnit.Framework; 4 | -------------------------------------------------------------------------------- /tests/Crash.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Run": { 4 | "commandName": "Executable", 5 | "executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 6 | "commandLineArgs": "/nosplash /notemplate /runscript=\"_-NUnitTestRunner $(MSBuildProjectDirectory)\\\\bin\\\\$(Configuration)/net48\\\\Crash.tests.dll\"" 7 | }, 8 | "Debug": { 9 | "commandName": "Executable", 10 | "executablePath": "C:\\Program Files\\Rhino 7\\System\\Rhino.exe", 11 | "commandLineArgs": "/nosplash /notemplate /runscript=\"NUnitTestRunner" 12 | } 13 | } 14 | } --------------------------------------------------------------------------------