├── .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 |
2 |
3 | 1.4.0
4 | enable
5 | 12
6 | Crash Cloud
7 | Crash Cloud
8 | $(Version)
9 | latest
10 | 8
11 | enable
12 | $(Version)
13 | True
14 | en
15 | true
16 | https://github.com/crashcloud/crash
17 | false
18 | true
19 |
20 |
--------------------------------------------------------------------------------
/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 | ///
9 | /// A global Application class to handle Application specific logic
10 | ///
11 | public static class CrashApp
12 | {
13 |
14 | /// The current Log Level, anything lower won't be logged
15 | private static LogLevel Level { get; } = LogLevel.Information;
16 |
17 | /// Logs a Message
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 | /// Fired every time a Log is called
56 | public static event EventHandler LogMessage;
57 |
58 | /// Fired every time a Log is called
59 | public static event EventHandler UserMessage;
60 |
61 | /// A Log structure
62 | ///
63 | ///
64 | ///
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 | ///
9 | /// Stashes Instances against a Crash Doc
10 | /// This avoids statics
11 | ///
12 | public static class CrashInstances
13 | {
14 |
15 | public class CrashInstanceSet
16 | {
17 | private Dictionary Instances { get; } = new();
18 |
19 | public bool TryGetInstance(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 Instances { get; } = new();
51 |
52 | public static bool TryGetInstance(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(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 | /// Captures a Change of a Camera
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 | /// Creates a new Camera Change from an IChange
30 | public static CameraChange CreateFrom(IChange change)
31 | {
32 | return new CameraChange
33 | {
34 | Camera = JsonSerializer.Deserialize(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 | /// Creates a new Camera Change from the required parts
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 | /// Creates a new transmittable Change
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 | /// Represents a Done Change inside of Crash
4 | public static class DoneChange
5 | {
6 | public const string ChangeType = "Crash.DoneChange";
7 |
8 | /// Creates a Done Change Template
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 | /// Captures a Transformation Change
9 | public struct TransformChange : IChange
10 | {
11 | public const string ChangeType = "Crash.GeometryChange";
12 |
13 | /// The CTransform
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 | /// IChange wrapping Constructor
44 | public static TransformChange CreateFrom(IChange change)
45 | {
46 | return new TransformChange
47 | {
48 | Transform = JsonSerializer.Deserialize(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 | /// Creates a Transform Change
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 | /// A Queue of a predetermined size
6 | public sealed class FixedSizedQueue : IReadOnlyCollection
7 | {
8 | private readonly Queue _idleQueue;
9 |
10 | /// The Size of the Queue
11 | public readonly int Size;
12 |
13 | ///
14 | public FixedSizedQueue(int size)
15 | {
16 | Size = size;
17 | _idleQueue = new Queue();
18 | }
19 |
20 | /// Current count of the Queue
21 | public int Count => _idleQueue.Count;
22 |
23 | /// GetEnumerator
24 | public IEnumerator GetEnumerator()
25 | {
26 | return _idleQueue.GetEnumerator();
27 | }
28 |
29 | /// GetEnumerator
30 | IEnumerator IEnumerable.GetEnumerator()
31 | {
32 | return _idleQueue.GetEnumerator();
33 | }
34 |
35 | /// Adds an item to the Queue, removing the first item if adding would put it oversize.
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 | ///
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 | ///
10 | public class IdleAction : IDisposable
11 | {
12 | private readonly Action _action;
13 | private readonly IdleArgs _args;
14 | public readonly string Name;
15 |
16 | /// Constructs an Idle Action
17 | /// The Action to be called on Idle
18 | /// The Args to be passed into the Action
19 | /// If any inputs are null
20 | public IdleAction(Action 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 | /// True if successfully invoked
28 | internal bool Invoked { get; set; }
29 |
30 |
31 | public void Dispose()
32 | {
33 | }
34 |
35 | /// Invokes the Action (If it hasn't already been invoked)
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 | /// A Queue for running during the Rhino Idle Event.
9 | public sealed class IdleQueue : IEnumerable
10 | {
11 | private readonly CrashDoc _hostDoc;
12 | private readonly ConcurrentQueue _idleQueue;
13 |
14 | /// Constructs an Idle Queue
15 | public IdleQueue(CrashDoc hostDoc)
16 | {
17 | _hostDoc = hostDoc;
18 | _idleQueue = new ConcurrentQueue();
19 | }
20 |
21 | /// The number of items in the Queue
22 | public int Count => _idleQueue.Count;
23 |
24 | /// GetEnumerator
25 | public IEnumerator GetEnumerator()
26 | {
27 | return _idleQueue.GetEnumerator();
28 | }
29 |
30 | /// GetEnumerator
31 | IEnumerator IEnumerable.GetEnumerator()
32 | {
33 | return _idleQueue.GetEnumerator();
34 | }
35 |
36 | /// Adds an Action to the Queue
37 | public void AddAction(IdleAction action)
38 | {
39 | _idleQueue.Enqueue(action);
40 | }
41 |
42 | /// Attempts to run the next Action
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 | /// Forces the Queue to run until empty
65 | public void ForceCycleQueue()
66 | {
67 | while (!_idleQueue.IsEmpty)
68 | {
69 | RunNextAction();
70 | }
71 | }
72 |
73 | /// Fires when the queue has finished parsing more than 1 item.
74 | public event EventHandler OnCompletedQueue;
75 |
76 | public event EventHandler 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 | ///
12 | /// Crash client class
13 | ///
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 | ///
84 | /// Fires when the connection to the Server closes and cannot be recovered
85 | ///
86 | public event EventHandler 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 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 | ///
13 | /// Crash client class
14 | ///
15 | public sealed partial class CrashClient
16 | {
17 |
18 | internal Func> GetHubConnection { get; set; }
19 |
20 | internal static IRetryPolicy RetryPolicy => new CrashRetryPolicy();
21 |
22 | public async Task 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 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 | ///
74 | /// Fires when a Change fails to be sent to the Server
75 | ///
76 | public event EventHandler 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 | ///
8 | /// Crash client class
9 | ///
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 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 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 changes)
45 | {
46 | await this.CrashDoc.Dispatcher.NotifyClientAsync(changes.ToEnumerable());
47 | }
48 |
49 | /// Registers Local Events responding to Server calls
50 | private void RegisterConnections()
51 | {
52 | if (Options.DryRun) return;
53 |
54 | _connection.Closed += ConnectionClosedAsync;
55 | _connection.Reconnecting += ConnectionReconnectingAsync;
56 |
57 | _connection.On>(INITIALIZE_CHANGES, InitializeChangesAsyncOnce);
58 | _connection.On>(INITIALIZE_USERS, InitializeUsersAsyncOnce);
59 | _connection.On>(SEND_RECIEVE_STREAM, RecieveChangesFromServerToClientAsync);
60 | }
61 |
62 | public event EventHandler 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 | /// Tests for an Active Connection
8 | public bool IsConnected { get; }
9 |
10 | /// The connection address
11 | public string Url { get; }
12 |
13 | /// Registers the client and its connection url
14 | /// The User of the Client
15 | /// url of the server the client will talk to
16 | Task RegisterConnection(string userName, Uri url);
17 |
18 | /// Starts the Client
19 | /// If CrashDoc is null
20 | /// If UserName is empty
21 | public Task StartLocalClientAsync();
22 |
23 | /// Stops the Connection
24 | public Task StopAsync();
25 |
26 | ///
27 | /// Pushes many unique changes at once
28 | /// An example of this may be copying 10 unique items
29 | ///
30 | public Task SendChangesToServerAsync(IAsyncEnumerable changeStream);
31 |
32 | public event EventHandler 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 | /// Abstracted Server Notification Contract
6 | public interface IEventDispatcher
7 | {
8 | ///
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 | ///
13 | /// The ChangeAction
14 | /// The sender of the Event
15 | /// The EventArgs
16 | Task NotifyServerAsync(IEnumerable changes);
17 |
18 | Task NotifyClientAsync(IEnumerable changes);
19 |
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/Crash.Common/Crash.Common.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | netstandard2.0;net7.0
5 | Crash.Common
6 | 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 | }
--------------------------------------------------------------------------------