From 0b375cfdd5bbfea16320ccbe0442715f2249552e Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Thu, 11 Jul 2024 13:26:18 -0500 Subject: [PATCH 01/10] Moved addons and added VSCode setting and app launcher scripts --- .env.example | 5 + .github/workflows/re-use-tests.yml | 2 +- .gitignore | 3 - .vscode/launch.json | 96 +++++++++++ .vscode/python.env | 2 + .vscode/settings.json | 82 +++++++++ .vscode/tasks.json | 90 ++++++++++ docs/contributing/development.md | 67 ++++---- docs/contributing/images/development/2.1.gif | Bin 0 -> 74982 bytes docs/contributing/images/development/2.2.png | Bin 0 -> 21181 bytes docs/contributing/testing.md | 6 +- docs/send2ue/introduction/quickstart.md | 2 +- requirements.txt | 1 + scripts/addon_watcher.py | 11 +- scripts/create_release.py | 2 +- scripts/dev_helpers.py | 24 ++- scripts/launch.py | 162 ++++++++++++++++++ scripts/resources/blender/startup.py | 56 ++++++ scripts/resources/profiles/dev.code-profile | 1 + scripts/resources/unreal/init_unreal.py | 25 +++ {send2ue => src/addons/send2ue}/__init__.py | 0 {send2ue => src/addons/send2ue}/constants.py | 0 .../addons/send2ue}/core/__init__.py | 0 .../addons/send2ue}/core/export.py | 0 .../addons/send2ue}/core/extension.py | 0 .../addons/send2ue}/core/formatting.py | 0 .../addons/send2ue}/core/ingest.py | 0 .../addons/send2ue}/core/io/__init__.py | 0 .../addons/send2ue}/core/io/fbx_b3.py | 0 .../addons/send2ue}/core/io/fbx_b4.py | 0 .../addons/send2ue}/core/settings.py | 0 .../addons/send2ue}/core/utilities.py | 0 .../addons/send2ue}/core/validations.py | 0 .../addons/send2ue}/dependencies/__init__.py | 0 .../send2ue}/dependencies/remote_execution.py | 0 .../send2ue}/dependencies/rpc/__init__.py | 0 .../send2ue}/dependencies/rpc/base_server.py | 12 +- .../dependencies/rpc/blender_server.py | 0 .../send2ue}/dependencies/rpc/client.py | 9 +- .../send2ue}/dependencies/rpc/exceptions.py | 0 .../send2ue}/dependencies/rpc/factory.py | 0 .../send2ue}/dependencies/rpc/server.py | 0 .../dependencies/rpc/unreal_server.py | 0 .../send2ue}/dependencies/rpc/validations.py | 0 .../addons/send2ue}/dependencies/unreal.py | 0 {send2ue => src/addons/send2ue}/operators.py | 0 {send2ue => src/addons/send2ue}/properties.py | 0 .../addons/send2ue}/release_notes.md | 0 .../send2ue}/resources/extensions/affixes.py | 0 .../extensions/apply_groom_modifiers.py | 0 .../resources/extensions/combine_assets.py | 0 .../create_post_import_assets_for_groom.py | 0 .../resources/extensions/instance_assets.py | 0 .../resources/extensions/ue2rigify.py | 0 .../extensions/use_collections_as_folders.py | 0 .../extensions/use_immediate_parent_name.py | 0 .../resources/setting_templates/default.json | 0 .../addons/send2ue}/resources/settings.json | 0 .../addons/send2ue}/ui/__init__.py | 0 .../addons/send2ue}/ui/addon_preferences.py | 0 {send2ue => src/addons/send2ue}/ui/dialog.py | 0 .../addons/send2ue}/ui/file_browser.py | 0 .../addons/send2ue}/ui/header_menu.py | 0 .../addons/ue2rigify}/__init__.py | 0 .../addons/ue2rigify}/constants.py | 0 .../addons/ue2rigify}/core/__init__.py | 0 .../addons/ue2rigify}/core/nodes.py | 0 .../addons/ue2rigify}/core/scene.py | 0 .../addons/ue2rigify}/core/templates.py | 0 .../addons/ue2rigify}/core/utilities.py | 0 .../addons/ue2rigify}/core/validations.py | 0 .../addons/ue2rigify}/operators.py | 0 .../addons/ue2rigify}/properties.py | 0 .../addons/ue2rigify}/release_notes.md | 0 .../control_metadata.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b3_6/female_mannequin_UE4/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b3_6/female_mannequin_UE5/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b3_6/male_mannequin_UE4/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b3_6/male_mannequin_UE5/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b4_0/female_mannequin_UE4/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b4_0/female_mannequin_UE5/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b4_0/male_mannequin_UE4/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../fk_to_source_links.json | 0 .../fk_to_source_nodes.json | 0 .../b4_0/male_mannequin_UE5/metarig.py | 0 .../source_to_deform_links.json | 0 .../source_to_deform_nodes.json | 0 .../addons/ue2rigify}/settings/__init__.py | 0 .../addons/ue2rigify}/settings/tool_tips.py | 0 .../ue2rigify}/settings/viewport_settings.py | 0 .../addons/ue2rigify}/ui/__init__.py | 0 .../addons/ue2rigify}/ui/addon_preferences.py | 0 .../addons/ue2rigify}/ui/exporter.py | 0 .../addons/ue2rigify}/ui/node_editor.py | 0 .../addons/ue2rigify}/ui/view_3d.py | 0 tests/run_tests.py | 30 ++-- .../test01/Config/DefaultEngine.ini | 2 +- tests/utils/base_test_case.py | 6 +- tests/utils/blender.py | 2 +- 127 files changed, 614 insertions(+), 84 deletions(-) create mode 100644 .env.example create mode 100644 .vscode/launch.json create mode 100644 .vscode/python.env create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100644 docs/contributing/images/development/2.1.gif create mode 100644 docs/contributing/images/development/2.2.png create mode 100644 scripts/launch.py create mode 100644 scripts/resources/blender/startup.py create mode 100644 scripts/resources/profiles/dev.code-profile create mode 100644 scripts/resources/unreal/init_unreal.py rename {send2ue => src/addons/send2ue}/__init__.py (100%) rename {send2ue => src/addons/send2ue}/constants.py (100%) rename {send2ue => src/addons/send2ue}/core/__init__.py (100%) rename {send2ue => src/addons/send2ue}/core/export.py (100%) rename {send2ue => src/addons/send2ue}/core/extension.py (100%) rename {send2ue => src/addons/send2ue}/core/formatting.py (100%) rename {send2ue => src/addons/send2ue}/core/ingest.py (100%) rename {send2ue => src/addons/send2ue}/core/io/__init__.py (100%) rename {send2ue => src/addons/send2ue}/core/io/fbx_b3.py (100%) rename {send2ue => src/addons/send2ue}/core/io/fbx_b4.py (100%) rename {send2ue => src/addons/send2ue}/core/settings.py (100%) rename {send2ue => src/addons/send2ue}/core/utilities.py (100%) rename {send2ue => src/addons/send2ue}/core/validations.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/__init__.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/remote_execution.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/__init__.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/base_server.py (96%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/blender_server.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/client.py (94%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/exceptions.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/factory.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/server.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/unreal_server.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/rpc/validations.py (100%) rename {send2ue => src/addons/send2ue}/dependencies/unreal.py (100%) rename {send2ue => src/addons/send2ue}/operators.py (100%) rename {send2ue => src/addons/send2ue}/properties.py (100%) rename {send2ue => src/addons/send2ue}/release_notes.md (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/affixes.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/apply_groom_modifiers.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/combine_assets.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/create_post_import_assets_for_groom.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/instance_assets.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/ue2rigify.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/use_collections_as_folders.py (100%) rename {send2ue => src/addons/send2ue}/resources/extensions/use_immediate_parent_name.py (100%) rename {send2ue => src/addons/send2ue}/resources/setting_templates/default.json (100%) rename {send2ue => src/addons/send2ue}/resources/settings.json (100%) rename {send2ue => src/addons/send2ue}/ui/__init__.py (100%) rename {send2ue => src/addons/send2ue}/ui/addon_preferences.py (100%) rename {send2ue => src/addons/send2ue}/ui/dialog.py (100%) rename {send2ue => src/addons/send2ue}/ui/file_browser.py (100%) rename {send2ue => src/addons/send2ue}/ui/header_menu.py (100%) rename {ue2rigify => src/addons/ue2rigify}/__init__.py (100%) rename {ue2rigify => src/addons/ue2rigify}/constants.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/__init__.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/nodes.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/scene.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/templates.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/utilities.py (100%) rename {ue2rigify => src/addons/ue2rigify}/core/validations.py (100%) rename {ue2rigify => src/addons/ue2rigify}/operators.py (100%) rename {ue2rigify => src/addons/ue2rigify}/properties.py (100%) rename {ue2rigify => src/addons/ue2rigify}/release_notes.md (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/control_metadata.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE5/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE4/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE5/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE4/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE5/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE4/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE5/metarig.py (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_links.json (100%) rename {ue2rigify => src/addons/ue2rigify}/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_nodes.json (100%) rename {ue2rigify => src/addons/ue2rigify}/settings/__init__.py (100%) rename {ue2rigify => src/addons/ue2rigify}/settings/tool_tips.py (100%) rename {ue2rigify => src/addons/ue2rigify}/settings/viewport_settings.py (100%) rename {ue2rigify => src/addons/ue2rigify}/ui/__init__.py (100%) rename {ue2rigify => src/addons/ue2rigify}/ui/addon_preferences.py (100%) rename {ue2rigify => src/addons/ue2rigify}/ui/exporter.py (100%) rename {ue2rigify => src/addons/ue2rigify}/ui/node_editor.py (100%) rename {ue2rigify => src/addons/ue2rigify}/ui/view_3d.py (100%) diff --git a/.env.example b/.env.example new file mode 100644 index 00000000..861bc317 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +# GITHUB_USERNAME= +# GITHUB_TOKEN= +# BLENDER_EXE_PATH= +# UNREAL_EXE_PATH= +# UNREAL_PROJECT_PATH= \ No newline at end of file diff --git a/.github/workflows/re-use-tests.yml b/.github/workflows/re-use-tests.yml index 0a257a7f..6cee1e8e 100644 --- a/.github/workflows/re-use-tests.yml +++ b/.github/workflows/re-use-tests.yml @@ -50,7 +50,7 @@ jobs: source .venv/bin/activate export GITHUB_TOKEN=${{ secrets.GH_PAT }} - export TEST_ENVIRONMENT=1 + export DOCKER_ENVIRONMENT=yes export UNREAL_VERSION=${{ inputs.unreal-version }} export BLENDER_VERSION=${{ inputs.blender-version }} diff --git a/.gitignore b/.gitignore index 4a3d6856..66fbc9f4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ *~ *.swp /venv/* -.vscode release/* venv .env @@ -17,8 +16,6 @@ results release site scratches -scripts/resources -launch.py tests/test_files/data tests/test_files/unreal_projects/**/DefaultInput.ini tests/test_files/blender_files/textures \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..bd592db1 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,96 @@ +{ + "configurations": [ + { + "name": "Run Active Python File", + "type": "debugpy", + "request": "launch", + "program": "${file}", + "console": "integratedTerminal", + "envFile": "${workspaceFolder}/.env", + "justMyCode": false + }, + { + "name": "Python Debugger: Attach Blender", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5678 + } + }, + { + "name": "Python Debugger: Attach Unreal", + "type": "debugpy", + "request": "attach", + "connect": { + "host": "localhost", + "port": 5679 + } + }, + { + "name": "Run All Tests", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/tests/run_tests.py", + "console": "integratedTerminal", + "envFile": "${workspaceFolder}/.env", + "cwd": "${workspaceFolder}/tests", + "justMyCode": false, + "env": { + "DOCKER_ENVIRONMENT": "${input:docker-environment}" + } + }, + { + "name": "Run Specific Tests", + "type": "debugpy", + "request": "launch", + "program": "${workspaceFolder}/tests/run_tests.py", + "console": "integratedTerminal", + "envFile": "${workspaceFolder}/.env", + "cwd": "${workspaceFolder}/tests", + "justMyCode": false, + "env": { + "EXCLUSIVE_TEST_FILES": "${input:test-file}", + "EXCLUSIVE_TESTS": "${input:test-name}", + "DOCKER_ENVIRONMENT": "${input:docker-environment}" + } + }, + ], + "inputs": [ + { + "id": "test-file", + "type": "pickString", + "default": "test_send2ue_core.py", + "options": [ + "test_send2ue_core.py", + "test_send2ue_cubes.py", + "test_send2ue_extension_affixes.py", + "test_send2ue_extension_combine_assets.py", + "test_send2ue_extension_create_post_import_assets_for_groom.py", + "test_send2ue_extension_example.py", + "test_send2ue_extension_instance_assets.py", + "test_send2ue_extension_use_collections_as_folders.py", + "test_send2ue_extension_use_immediate_parent_name.py", + "test_send2ue_mannequins.py", + "test_ue2rigify_core.py", + "test_ue2rigify_mannequins.py" + ], + "description": "Which test file to run" + }, + { + "id": "docker-environment", + "type": "pickString", + "default": "", + "options": [ + "no", + "yes" + ], + "description": "Run in docker environment?" + }, + { + "id": "test-name", + "type": "promptString", + "description": "The name of the specific test to run" + } + ] +} \ No newline at end of file diff --git a/.vscode/python.env b/.vscode/python.env new file mode 100644 index 00000000..4e6951a4 --- /dev/null +++ b/.vscode/python.env @@ -0,0 +1,2 @@ +PYTHONPATH=';./.venv/Lib/site-packages' +PYTHONUNBUFFERED=1 \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..f4fb8548 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,82 @@ +{ + "files.eol": "\n", + "python.pythonPath": "${workspaceFolder}/.venv/Lib/site-packages", + "python.terminal.activateEnvironment": true, + "python.defaultInterpreterPath": "${workspaceFolder}/.venv/Scripts/python.exe", + "python.envFile": "${workspaceFolder}/.vscode/python.env", + "python.analysis.typeCheckingMode": "basic", + "python.testing.unittestEnabled": false, + "python.testing.pytestEnabled": true, + "python.languageServer": "Pylance", + "editor.tabCompletion": "on", +// "python.analysis.disabled": ["unresolved-import"], + "python.autoComplete.extraPaths": [ + "${workspaceFolder}/admin/packages" + ], + "python.analysis.extraPaths": [ + "${workspaceFolder}/admin/packages" + ], + "cSpell.ignorePaths": [ + "${workspaceFolder}/.venv", + "${workspaceFolder}/.linux-venv", + "${workspaceFolder}/.py3.10-venv", + "${workspaceFolder}/.unreal-venv", + "${workspaceFolder}/build", + "${workspaceFolder}/scratches", + "${workspaceFolder}/reports" + ], + "cSpell.words": [ + "autoexec", + "compileshaders", + "debugpy", + "dotenv", + "epicgames", + "everytime", + "eyeshell", + "fcurve", + "fcurves", + "fontawesome", + "forcelogflush", + "idname", + "inlinehilite", + "kwargs", + "levelno", + "linenums", + "LODS", + "mathutils", + "mikepenz", + "Mkdocs", + "mklink", + "noloadstartuppackages", + "nopause", + "nosplash", + "nullrhi", + "nytimes", + "outliner", + "POLYHAMMER", + "pygments", + "Pylance", + "pymdownx", + "pytest", + "PYTHONUNBUFFERED", + "quickstart", + "retarget", + "retargeting", + "Rigify", + "runas", + "skincache", + "staticmethod", + "superfences", + "teleporting", + "termynal", + "TOPBAR", + "Unregisters", + "uproject", + "venv" + ], + "editor.rulers": [ + 80, + 120 + ], + "jupyter.notebookFileRoot": "${workspaceFolder}" +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..35315c40 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,90 @@ +// See https://go.microsoft.com/fwlink/?LinkId=733558 +// for the documentation about the tasks.json format +{ + "version": "2.0.0", + "tasks": [ + { + "label": "launch blender", + "command": "python ${workspaceFolder}/scripts/launch.py blender ${input:blender-version} ${input:blender-debug}", + "type": "shell", + "group": { + "kind": "build" + }, + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true + } + }, + { + "label": "launch unreal", + "command": "python ${workspaceFolder}/scripts/launch.py unreal ${input:unreal-version} ${input:unreal-debug}", + "type": "shell", + "group": { + "kind": "build" + }, + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true + } + }, + { + "label": "Serve Docs", + "command": "mkdocs serve", + "type": "shell", + "group": { + "kind": "build" + }, + "presentation": { + "reveal": "always", + "panel": "shared", + "focus": true + } + } + ], + "inputs": [ + { + "id": "blender-version", + "type": "pickString", + "default": "4.1", + "options": [ + "3.6", + "4.0", + "4.1", + "4.2", + ], + "description": "What blender version to launch" + }, + { + "id": "blender-debug", + "type": "pickString", + "default": "no", + "options": [ + "no", + "yes" + ], + "description": "Should use debugger?" + }, + { + "id": "unreal-version", + "type": "pickString", + "default": "5.4", + "options": [ + "5.3", + "5.4" + ], + "description": "What unreal version to launch" + }, + { + "id": "unreal-debug", + "type": "pickString", + "default": "no", + "options": [ + "no", + "yes" + ], + "description": "Should use debugger?" + } + ] +} \ No newline at end of file diff --git a/docs/contributing/development.md b/docs/contributing/development.md index d654f255..33dee655 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -32,56 +32,49 @@ cd BlenderTools git checkout some-task-branch ``` -## Testing your changes +## VSCode +The repo contains the tasks, launch actions, and settings for for developing with vscode. To get setup do the following: + +### Setup +1. Install the VSCode profile under `scripts/resources/profiles/dev.code-profile`. You can do this by typing `> Profile Import Profile` in the command pallette. This will give you all the vscode extensions you will need. +1. Close and re-open VSCode +1. Type `> Python: Create Environment`, hit enter and chose `Venv` and choose your Python 3.11 installation, then check the box to install the `requirements.txt` +1. Close and re-open your integrated terminal in vscode and ensure that you now have a `(.venv)` prefixing your shell. If you do, you are all setup and ready to go! + +### Build Tasks +The VSCode project has build tasks for launch your apps. It is highly recommended that you launch blender and unreal through these, since they will ensure you have the dev dependencies in your python environment for reloading code and debugging. To do this move your cursor over the integrate terminal and press the hot keys `CTRL+SHIFT+B`. + +![2.1](./images/development/2.1.gif) + +!!! note + If you choose yes to debug, the app will hang until you attach to it via debugpy in VSCode. (See Launch Actions) + +!!! note + The launch scripts look for the unreal and blender executables in their default locations. You can override the exe paths by setting + the `UNREAL_EXE_PATH`and `BLENDER_EXE_PATH` variables in a `.env` file in the repo root. + +### Launch Actions +Several launch actions have been created for debugging. Use the `Python Debugger: Attach Blender` or `Python Debugger: Attach Unreal` to attach to the apps after they have been launched from the build tasks and have logged that they are waiting for the debugger to attach. There are also a few other debug configurations for the test cases as well. + +![2.2](./images/development/2.2.png) + +## Reloading your changes While developing, you will want to be able to rapidly test your new changes. You can do this by running this script in the Blender Script Editor. !!! note - You need to change `` to match the absolute path to the scripts folder in your local project. Running this script installs and reloads the tool. + If your didn't launch from VSCode, You need to change `` to match the absolute path to the scripts folder in your local project. Running this script reloads the addon code. ```python -import bpy import sys sys.path.append(r'C:\\BlenderTools\scripts') import dev_helpers - -addons = ['send2ue', 'ue2rigify'] - -# installs the actual addons zips -#dev_helpers.reload_addon_zips(addons) - -# reloads the code from your repo. A lot faster but doesn't load addon preferences -dev_helpers.reload_addon_source_code(addons) - -# start blender and unreal rpc server for automated testing -#bpy.ops.send2ue.start_rpc_servers() +dev_helpers.reload_addon_source_code(['send2ue', 'ue2rigify']) ``` -In most cases you can get away with running `dev_helpers.reload_addon_source_code`, which reloads very fast and any -stack traces get linked back to the repo code. - -However, the true test is running `dev_helpers.reload_addon_zips` this actually zips up the code and installs the -addons. You will need to do this if you are testing features that rely on properties in the addon preferences. - -`bpy.ops.send2ue.start_rpc_servers()` Ensures that both unreal and blender rpc servers are running. This is needed if -you want to run the unittests on the open app instances. - -## Hot reloading from PyCharm -These steps must be completed in-order for the addons to hot-reload while you type in PyCharm. -1. If you have the addons already installed, uninstall them and shutdown Blender and PyCharm. -1. You must symlink the addon folders into the blender addon installation location. Then enable the addons - -!!! Windows - Run this from a commandline launched as administrator. Swapping out the last path with your own. - ```shell - mklink /D "%APPDATA%\Blender Foundation\Blender\3.6\scripts\addons\send2ue" "D:\repos\BlenderTools\send2ue" - ``` - -1. You must install the `./scripts/addon-watcher.xml` in Pycharm by going to `Settings > Tools > File Watchers > Import` -1. And finally blender has to be running with the send2ue addon enabled and clicking `Pipeline > Utilities > Start RPC Servers` -Now PyCharm should reload your addons on file save events. +In most cases you can get away with running `dev_helpers.reload_addon_source_code`, which reloads your addon code without restarting blender. ## Code Structure diff --git a/docs/contributing/images/development/2.1.gif b/docs/contributing/images/development/2.1.gif new file mode 100644 index 0000000000000000000000000000000000000000..b96c7d24d85f55e9b0003aa998de5e1ddfc4167b GIT binary patch literal 74982 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 literal 0 HcmV?d00001 diff --git a/docs/contributing/images/development/2.2.png b/docs/contributing/images/development/2.2.png new file mode 100644 index 0000000000000000000000000000000000000000..b0f0770709480018ce63cc4a9920bbcefe34d9b5 GIT binary patch literal 21181 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 literal 0 HcmV?d00001 diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md index 0f0f4208..868ee26e 100644 --- a/docs/contributing/testing.md +++ b/docs/contributing/testing.md @@ -26,13 +26,13 @@ Then install [Ubuntu on Windows](https://ubuntu.com/tutorials/install-ubuntu-on- 1. Install [Docker](https://docs.docker.com/get-docker/). 1. Make sure docker is working by running `docker images`. -1. Set the environment variable `TEST_ENVIRONMENT=1`. +1. Set the environment variable `DOCKER_ENVIRONMENT=1`. 1. Set the environment variable `GITHUB_TOKEN=your-personal access-token`. This is needed to pull the [unreal container image](https://docs.unrealengine.com/4.27/en-US/SharingAndReleasing/Containers/ContainersQuickStart/) if you don't already have it. ```shell set GITHUB_TOKEN=your-personal access-token -set TEST_ENVIRONMENT=1 +set DOCKER_ENVIRONMENT=yes cd ./tests python run_tests.py ``` @@ -73,7 +73,7 @@ specific cases with `EXCLUSIVE_TESTS` can be beneficial. | Variables | Description | Default | | -------------- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------| -| `TEST_ENVIRONMENT` | Whether to run the in the container test environment | If no value is set, testing will run on the host | +| `DOCKER_ENVIRONMENT` | Whether to run the in the container test environment | If no value is set, testing will run on the host | | `EXCLUSIVE_TEST_FILES` | Whether to run tests exclusively on this list of test files. Comma seperated list. | Runs all tests files by default | | `EXCLUSIVE_TESTS` | Whether to run tests exclusively on this list of test names. Comma seperated list. | Runs all tests cases by default | | `REMOVE_CONTAINERS` | Whether to shutdown and remove the containers after testing. Testing will be faster if the containers don't have to restart | `True` by default. Leave blank if you want the containers to keep running | diff --git a/docs/send2ue/introduction/quickstart.md b/docs/send2ue/introduction/quickstart.md index 797206b1..93158fbb 100644 --- a/docs/send2ue/introduction/quickstart.md +++ b/docs/send2ue/introduction/quickstart.md @@ -45,7 +45,7 @@ Once you have enabled the plugins and project settings, you'll be prompted to re ![7](./images/7.png) -Search for "python" and then enable `remote execution` and set the Multicast bind address to `0.0.0.0`. Now Send to Unreal will work with your new Unreal project. +Search for "python" and then enable `remote execution` and set the Multicast bind address to `0.0.0.0` if you are not using a Windows machine, otherwise leave this set to `127.0.0.1`. Now Send to Unreal will work with your new Unreal project. ![8](./images/8.png) diff --git a/requirements.txt b/requirements.txt index e3693173..cc0f56b9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,6 +5,7 @@ PyGithub==2.1.1 debugpy==1.8.1 numpy==1.26.4 websocket==0.2.1 +python-dotenv==1.0.1 # documenation mkdocs<=1.5.3 diff --git a/scripts/addon_watcher.py b/scripts/addon_watcher.py index d6856195..2ef4ae5e 100644 --- a/scripts/addon_watcher.py +++ b/scripts/addon_watcher.py @@ -1,10 +1,6 @@ import sys import os -sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'send2ue', 'dependencies')) -import rpc - -@rpc.factory.remote_call(port=9997) def reload_addon(addon, scripts_path): sys.path.append(scripts_path) import dev_helpers @@ -12,6 +8,8 @@ def reload_addon(addon, scripts_path): if __name__ == '__main__': + sys.path.append(os.path.join(os.path.dirname(__file__), os.pardir, 'send2ue', 'dependencies')) + from rpc.rpc_factory import RPCFactory, RPCClient # type: ignore changed_file_path = os.path.normpath(sys.argv[-1]) print(f'Checking path "{changed_file_path}"...') split_path = changed_file_path.split(os.sep) @@ -21,6 +19,7 @@ def reload_addon(addon, scripts_path): if root_folder in ['send2ue', 'ue2rigify']: addon_name = root_folder print(f'reloading "{addon_name}"...') - reload_addon(addon_name, os.path.basename(__file__)) + rpc_factory = RPCFactory(rpc_client=RPCClient(9997)) + rpc_factory.run_function_remotely(reload_addon, [addon_name, os.path.basename(__file__)]) else: - print(f'No addon to reload') + print('No addon to reload') diff --git a/scripts/create_release.py b/scripts/create_release.py index e1bddae3..12e1bfeb 100644 --- a/scripts/create_release.py +++ b/scripts/create_release.py @@ -134,7 +134,7 @@ def package_addons(addon_release_folder): # package the addons for addon_name in BLENDER_ADDONS: - addon_folder_path = os.path.join(PROJECT_FOLDER, addon_name) + addon_folder_path = os.path.join(PROJECT_FOLDER, 'src', 'addons', addon_name) addon_packager = AddonPackager(addon_name, addon_folder_path, addon_release_folder) addon_packager.zip_addon() diff --git a/scripts/dev_helpers.py b/scripts/dev_helpers.py index 83f148da..08e92628 100644 --- a/scripts/dev_helpers.py +++ b/scripts/dev_helpers.py @@ -2,14 +2,22 @@ import sys import bpy import importlib -import threading +from types import ModuleType repo_folder = os.path.join(os.path.dirname(__file__), os.pardir) tests_folder = os.path.join(repo_folder, 'tests') -sys.path.append(repo_folder) -sys.path.append(os.path.join(tests_folder, 'utils')) -from addon_packager import AddonPackager +def deep_reload(m: ModuleType): + name = m.__name__ # get the name that is used in sys.modules + name_ext = name + '.' # support finding sub modules or packages + + def compare(loaded: str): + return (loaded == name) or loaded.startswith(name_ext) + + all_mods = tuple(sys.modules) # prevent changing iterable while iterating over it + sub_mods = filter(compare, all_mods) + for pkg in sorted(sub_mods, key=lambda item: item.count('.'), reverse=True): + importlib.reload(sys.modules[pkg]) # reload packages, beginning with the most deeply nested def reload_addon_source_code(addons, only_unregister=False): @@ -26,9 +34,10 @@ def reload_addon_source_code(addons, only_unregister=False): for addon in addons: os.environ[f'{addon.upper()}_DEV'] = '1' addon = importlib.import_module(addon) - importlib.reload(addon) addon.unregister() + deep_reload(addon) + if not only_unregister: addon.register() @@ -41,13 +50,16 @@ def reload_addon_zips(addons): :param list addons: A list of addon names. """ + sys.path.append(os.path.join(tests_folder, 'utils')) + from addon_packager import AddonPackager # type: ignore + # unregister any registered addons reload_addon_source_code(addons, only_unregister=True) # zip up each addon for addon in addons: os.environ[f'{addon.upper()}_DEV'] = '1' - addon_folder_path = os.path.join(repo_folder, addon) + addon_folder_path = os.path.join(repo_folder, 'src', 'addons', addon) release_folder_path = os.path.join(repo_folder, 'release') addon_packager = AddonPackager(addon, addon_folder_path, release_folder_path) addon_packager.zip_addon() diff --git a/scripts/launch.py b/scripts/launch.py new file mode 100644 index 00000000..2de7e8a9 --- /dev/null +++ b/scripts/launch.py @@ -0,0 +1,162 @@ +import os +import sys +import subprocess +from pathlib import Path +from dotenv import load_dotenv + + +REPO_ROOT = Path(__file__).parent.parent + +ENV_FILE = REPO_ROOT / '.env' +if ENV_FILE.exists(): + load_dotenv(ENV_FILE) + +PYTHON_3_11_VIRTUAL_ENV = REPO_ROOT / '.venv' +PYTHON_3_10_VIRTUAL_ENV = REPO_ROOT / '.py3.10-venv' +PYTHON_3_9_VIRTUAL_ENV = REPO_ROOT / '.py3.9-venv' + +BLENDER_STARTUP_SCRIPT = Path(__file__).parent / 'resources' / 'blender' / 'startup.py' +UNREAL_STARTUP_SCRIPT = Path(__file__).parent / 'resources' / 'unreal' / 'init_unreal.py' + +SCRIPTS_FOLDER = REPO_ROOT / 'scripts' + + +UNREAL_PROJECT = os.environ.get('UNREAL_PROJECT_PATH') or REPO_ROOT / 'tests' / 'test_files' / 'unreal_projects' / 'test01' / 'test01.uproject' + + +def shell(command: str, **kwargs): + process = subprocess.Popen( + command, + shell=True, + universal_newlines=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + **kwargs + ) + + output = [] + for line in iter(process.stdout.readline, ""): # type: ignore + output += [line.rstrip()] + sys.stdout.write(line) + + process.wait() + + if process.returncode != 0: + raise OSError("\n".join(output)) + +def validate_venv(venv_path: Path) -> bool: + if not venv_path.exists(): + print(f'Virtual environment not found here: "{venv_path}"') + python_version = venv_path.name.split('-')[0].strip('.') + if python_version == 'venv': + python_version = '3.11' + + print('This can be fixed by running the following command at the root of the repo to create a virtual environment with the correct python version:') + print(f"C:/some/path/{python_version}/python.exe -m venv {venv_path}") + return False + + return True + + +def validate_exe(exe_path: Path) -> bool: + if not exe_path.exists(): + app_name = exe_path.name.split('.')[0].lower() + if app_name != 'blender': + app_name = 'unreal' + + print(f'The {app_name} executable was not found here: "{exe_path}"') + print('Are you sure you have the correct path to the executable? Is it installed?') + print(f"Try setting the environment variable '{app_name.upper()}_EXE_PATH' to the correct path if you know where {app_name} is.") + return False + return True + + +def launch_blender(version: str, debug: str): + virtual_env = PYTHON_3_11_VIRTUAL_ENV + if version in ['3.6']: + virtual_env = PYTHON_3_10_VIRTUAL_ENV + + if not validate_venv(virtual_env): + return + + site_packages = virtual_env / 'Lib' / 'site-packages' + + exe_path = os.environ.get('BLENDER_EXE_PATH') + if not exe_path: + if sys.platform == 'win32': + exe_path = rf"C:\Program Files\Blender Foundation\Blender {version}\blender.exe" + else: + exe_path = None + + if not validate_exe(Path(exe_path)): + return + + if exe_path: + command = f'"{exe_path}" --python-use-system-env --python "{BLENDER_STARTUP_SCRIPT}"' + shell( + command, + env={ + **os.environ.copy(), + 'PYTHONUNBUFFERED': '1', + 'BLENDER_APP_VERSION': version, + 'BLENDER_DEBUGGING_ON': debug, + 'PYTHONPATH': ';'.join([ + str(site_packages), + str(SCRIPTS_FOLDER) + ]) + } + ) + + +def launch_unreal(version: str, debug: str): + virtual_env = PYTHON_3_11_VIRTUAL_ENV + if version in ['5.3']: + virtual_env = PYTHON_3_9_VIRTUAL_ENV + + if not validate_venv(virtual_env): + return + + exe_path = os.environ.get('UNREAL_EXE_PATH') + if not exe_path: + if sys.platform == 'win32': + exe_path = rf'C:\Program Files\Epic Games\UE_{app_version}\Engine\Binaries\Win64\UnrealEditor.exe' + else: + exe_path = None + + site_packages = virtual_env / 'Lib' / 'site-packages' + + if not validate_exe(Path(exe_path)): + return + + if exe_path: + command = f'"{exe_path}" "{UNREAL_PROJECT}" -stdout -nopause -forcelogflush -verbose' + shell( + command, + env={ + **os.environ.copy(), + 'PYTHONUNBUFFERED': '1', + 'UNREAL_APP_VERSION': version, + 'UNREAL_DEBUGGING_ON': debug, + 'UE_PYTHONPATH': ';'.join([ + str(site_packages.absolute()), + str(UNREAL_STARTUP_SCRIPT.parent.absolute()), + ]) + } + ) + +if __name__ == "__main__": + app_name = sys.argv[1] + app_version = sys.argv[2] + debug_on = sys.argv[3] + + if app_name == 'blender': + launch_blender( + version=app_version, + debug=debug_on + ) + + if app_name == 'unreal': + launch_unreal( + version=app_version, + debug=debug_on + ) \ No newline at end of file diff --git a/scripts/resources/blender/startup.py b/scripts/resources/blender/startup.py new file mode 100644 index 00000000..a15c6a74 --- /dev/null +++ b/scripts/resources/blender/startup.py @@ -0,0 +1,56 @@ +import os +import sys +import bpy +import logging +from pathlib import Path + +logger = logging.getLogger(__name__) + +logging.basicConfig(level=logging.DEBUG) + +debug = os.environ.get('BLENDER_DEBUGGING_ON', 'no').lower() == 'yes' + +ADDONS = { + 'send2ue': Path(__file__).parent.parent.parent.parent / 'src', + 'ue2rigify': Path(__file__).parent.parent.parent.parent / 'src' +} + + +if bpy.app.version[0] > 2: # type: ignore + current_script_directories = [ + Path(i.directory) + for i in bpy.context.preferences.filepaths.script_directories.values() + ] + + + for name, scripts_folder in ADDONS.items(): + if scripts_folder not in current_script_directories: + script_directory = bpy.context.preferences.filepaths.script_directories.new() + script_directory.name = name # type: ignore + script_directory.directory = str(scripts_folder) # type: ignore + + try: + bpy.ops.script.reload() + except ValueError: + pass + + +for addon in ADDONS.keys(): + try: + bpy.ops.preferences.addon_enable(module=addon) + except Exception as e: + logger.warning(f'Failed to enable addon {addon}: {e}') + +if debug: + try: + import debugpy + port = int(os.environ.get('BLENDER_DEBUG_PORT', 5678)) + debugpy.configure(python=os.environ.get('BLENDER_PYTHON_EXE', sys.executable)) + debugpy.listen(port) + logger.info(f'Waiting for debugger to attach on port {port}...') + debugpy.wait_for_client() + except ImportError: + logger.error( + 'Failed to initialize debugger because debugpy is not available ' + 'in the current python environment.' + ) \ No newline at end of file diff --git a/scripts/resources/profiles/dev.code-profile b/scripts/resources/profiles/dev.code-profile new file mode 100644 index 00000000..d9396f44 --- /dev/null +++ b/scripts/resources/profiles/dev.code-profile @@ -0,0 +1 @@ +{"name":"Blender Dev","settings":"{\"settings\":\"{\\r\\n \\\"python.analysis.autoImportCompletions\\\": false,\\r\\n \\\"python.analysis.fixAll\\\": [\\\"source.unusedImports\\\"], \\r\\n \\\"editor.defaultFormatter\\\": \\\"ms-python.black-formatter\\\",\\r\\n \\\"files.exclude\\\": {\\r\\n \\\"**/__pycache__\\\": true,\\r\\n \\\"**/.cache\\\": true,\\r\\n \\\"**/.coverage\\\": true,\\r\\n \\\"**/.coverage.*\\\": true,\\r\\n \\\"**/.hypothesis\\\": true,\\r\\n \\\"**/.mypy_cache\\\": true,\\r\\n \\\"**/.nox\\\": true,\\r\\n \\\"**/.pytest_cache\\\": true,\\r\\n \\\"**/.ruff_cache\\\": true,\\r\\n \\\"**/.tox\\\": true\\r\\n },\\r\\n \\\"files.autoSave\\\": \\\"afterDelay\\\",\\r\\n \\\"git.enableSmartCommit\\\": true,\\r\\n \\\"git.autofetch\\\": true,\\r\\n \\\"terminal.integrated.defaultProfile.windows\\\": \\\"Command Prompt\\\",\\r\\n \\\"[jsonc]\\\": {\\r\\n \\\"editor.defaultFormatter\\\": \\\"vscode.json-language-features\\\"\\r\\n },\\r\\n \\\"window.zoomLevel\\\": -1,\\r\\n \\\"[json]\\\": {\\r\\n \\\"editor.defaultFormatter\\\": \\\"vscode.json-language-features\\\"\\r\\n },\\r\\n \\\"git.confirmSync\\\": false,\\r\\n \\\"redhat.telemetry.enabled\\\": false,\\r\\n \\\"[xml]\\\": {\\r\\n \\\"editor.defaultFormatter\\\": \\\"redhat.vscode-xml\\\"\\r\\n }\\r\\n\\r\\n}\"}","snippets":"{\"snippets\":{\"python.json\":\"\\n{\\n \\\"if\\\": {\\n \\\"prefix\\\": \\\"if\\\",\\n \\\"body\\\": [\\\"if ${1:expression}:\\\", \\\"\\\\t${2:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for an if statement\\\"\\n },\\n \\\"if/else\\\": {\\n \\\"prefix\\\": \\\"if/else\\\",\\n \\\"body\\\": [\\\"if ${1:condition}:\\\", \\\"\\\\t${2:pass}\\\", \\\"else:\\\", \\\"\\\\t${3:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for an if statement with else\\\"\\n },\\n \\\"elif\\\": {\\n \\\"prefix\\\": \\\"elif\\\",\\n \\\"body\\\": [\\\"elif ${1:expression}:\\\", \\\"\\\\t${2:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for an elif\\\"\\n },\\n \\\"else\\\": {\\n \\\"prefix\\\": \\\"else\\\",\\n \\\"body\\\": [\\\"else:\\\", \\\"\\\\t${1:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for an else\\\"\\n },\\n \\\"while\\\": {\\n \\\"prefix\\\": \\\"while\\\",\\n \\\"body\\\": [\\\"while ${1:expression}:\\\", \\\"\\\\t${2:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a while loop\\\"\\n },\\n \\\"while/else\\\": {\\n \\\"prefix\\\": \\\"while/else\\\",\\n \\\"body\\\": [\\\"while ${1:expression}:\\\", \\\"\\\\t${2:pass}\\\", \\\"else:\\\", \\\"\\\\t${3:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a while loop with else\\\"\\n },\\n \\\"for\\\": {\\n \\\"prefix\\\": \\\"for\\\",\\n \\\"body\\\": [\\\"for ${1:target_list} in ${2:expression_list}:\\\", \\\"\\\\t${3:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a for loop\\\"\\n },\\n \\\"for/else\\\": {\\n \\\"prefix\\\": \\\"for/else\\\",\\n \\\"body\\\": [\\\"for ${1:target_list} in ${2:expression_list}:\\\", \\\"\\\\t${3:pass}\\\", \\\"else:\\\", \\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a for loop with else\\\"\\n },\\n \\\"try/except\\\": {\\n \\\"prefix\\\": \\\"try/except\\\",\\n \\\"body\\\": [\\\"try:\\\", \\\"\\\\t${1:pass}\\\", \\\"except ${2:expression} as ${3:identifier}:\\\", \\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a try/except statement\\\"\\n },\\n \\\"try/finally\\\": {\\n \\\"prefix\\\": \\\"try/finally\\\",\\n \\\"body\\\": [\\\"try:\\\", \\\"\\\\t${1:pass}\\\", \\\"finally:\\\", \\\"\\\\t${2:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a try/finally statement\\\"\\n },\\n \\\"try/except/else\\\": {\\n \\\"prefix\\\": \\\"try/except/else\\\",\\n \\\"body\\\": [\\n \\\"try:\\\",\\n \\\"\\\\t${1:pass}\\\",\\n \\\"except ${2:expression} as ${3:identifier}:\\\",\\n \\\"\\\\t${4:pass}\\\",\\n \\\"else:\\\",\\n \\\"\\\\t${5:pass}\\\"\\n ],\\n \\\"description\\\": \\\"Code snippet for a try/except/else statement\\\"\\n },\\n \\\"try/except/finally\\\": {\\n \\\"prefix\\\": \\\"try/except/finally\\\",\\n \\\"body\\\": [\\n \\\"try:\\\",\\n \\\"\\\\t${1:pass}\\\",\\n \\\"except ${2:expression} as ${3:identifier}:\\\",\\n \\\"\\\\t${4:pass}\\\",\\n \\\"finally:\\\",\\n \\\"\\\\t${5:pass}\\\"\\n ],\\n \\\"description\\\": \\\"Code snippet for a try/except/finally statement\\\"\\n },\\n \\\"try/except/else/finally\\\": {\\n \\\"prefix\\\": \\\"try/except/else/finally\\\",\\n \\\"body\\\": [\\n \\\"try:\\\",\\n \\\"\\\\t${1:pass}\\\",\\n \\\"except ${2:expression} as ${3:identifier}:\\\",\\n \\\"\\\\t${4:pass}\\\",\\n \\\"else:\\\",\\n \\\"\\\\t${5:pass}\\\",\\n \\\"finally:\\\",\\n \\\"\\\\t${6:pass}\\\"\\n ],\\n \\\"description\\\": \\\"Code snippet for a try/except/else/finally statement\\\"\\n },\\n \\\"with\\\": {\\n \\\"prefix\\\": \\\"with\\\",\\n \\\"body\\\": [\\\"with ${1:expression} as ${2:target}:\\\", \\\"\\\\t${3:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a with statement\\\"\\n },\\n \\\"def\\\": {\\n \\\"prefix\\\": \\\"def\\\",\\n \\\"body\\\": [\\\"def ${1:funcname}(${2:parameter_list}):\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${3:docstring}\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\",\\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a function definition\\\"\\n },\\n \\\"def(class method)\\\": {\\n \\\"prefix\\\": \\\"def(class method)\\\",\\n \\\"body\\\": [\\\"def ${1:funcname}(self, ${2:parameter_list}):\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${3:docstring}\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a class method\\\"\\n },\\n \\\"def(static class method)\\\": {\\n \\\"prefix\\\": \\\"def(static class method)\\\",\\n \\\"body\\\": [\\\"@staticmethod\\\", \\\"def ${1:funcname}(${2:parameter_list}):\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${3:docstring}\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a static class method\\\"\\n },\\n \\\"def(abstract class method)\\\": {\\n \\\"prefix\\\": \\\"def(abstract class method)\\\",\\n \\\"body\\\": [\\\"def ${1:funcname}(self, ${2:parameter_list}):\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${3:docstring}\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\traise NotImplementedError\\\"],\\n \\\"description\\\": \\\"Code snippet for an abstract class method\\\"\\n },\\n \\\"class\\\": {\\n \\\"prefix\\\": \\\"class\\\",\\n \\\"body\\\": [\\\"class ${1:classname}(${2:object}):\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${3:docstring}\\\", \\\"\\\\t\\\\\\\"\\\\\\\"\\\\\\\"\\\", \\\"\\\\t${4:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a class definition\\\"\\n },\\n \\\"lambda\\\": {\\n \\\"prefix\\\": \\\"lambda\\\",\\n \\\"body\\\": [\\\"lambda ${1:parameter_list}: ${2:expression}\\\"],\\n \\\"description\\\": \\\"Code snippet for a lambda statement\\\"\\n },\\n \\\"if(main)\\\": {\\n \\\"prefix\\\": \\\"__main__\\\",\\n \\\"body\\\": [\\\"if __name__ == \\\\\\\"__main__\\\\\\\":\\\", \\\" ${1:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for a `if __name__ == \\\\\\\"__main__\\\\\\\": ...` block\\\"\\n },\\n \\\"async/def\\\": {\\n \\\"prefix\\\": \\\"async/def\\\",\\n \\\"body\\\": [\\\"async def ${1:funcname}(${2:parameter_list}):\\\", \\\"\\\\t${3:pass}\\\"],\\n \\\"description\\\": \\\"Code snippet for an async statement\\\"\\n },\\n \\\"async/for\\\": {\\n \\\"prefix\\\": \\\"async/for\\\",\\n \\\"body\\\": [\\\"async for ${1:target} in ${2:iter}:\\\", \\\"\\\\t${3:block}\\\"],\\n \\\"description\\\": \\\"Code snippet for an async for statement\\\"\\n },\\n \\\"async/for/else\\\": {\\n \\\"prefix\\\": \\\"async/for/else\\\",\\n \\\"body\\\": [\\\"async for ${1:target} in ${2:iter}:\\\", \\\"\\\\t${3:block}\\\", \\\"else:\\\", \\\"\\\\t${4:block}\\\"],\\n \\\"description\\\": \\\"Code snippet for an async for statement with else\\\"\\n },\\n \\\"async/with\\\": {\\n \\\"prefix\\\": \\\"async/with\\\",\\n \\\"body\\\": [\\\"async with ${1:expr} as ${2:var}:\\\", \\\"\\\\t${3:block}\\\"],\\n \\\"description\\\": \\\"Code snippet for an async with statement\\\"\\n }\\n}\"}}","extensions":"[{\"identifier\":{\"id\":\"charliermarsh.ruff\",\"uuid\":\"c2ca9b43-fa38-44fc-928e-5125970b9c00\"},\"displayName\":\"Ruff\",\"preRelease\":true},{\"identifier\":{\"id\":\"discretegames.f5anything\",\"uuid\":\"370d707b-acca-4ddc-9e39-cfa9705953f0\"},\"displayName\":\"F5 Anything\"},{\"identifier\":{\"id\":\"donjayamanne.python-environment-manager\",\"uuid\":\"0c9f60fd-5588-42f7-9176-e80c3ae111ec\"},\"displayName\":\"Python Environment Manager\"},{\"identifier\":{\"id\":\"github.remotehub\",\"uuid\":\"fc7d7e85-2e58-4c1c-97a3-2172ed9a77cd\"},\"displayName\":\"GitHub Repositories\"},{\"identifier\":{\"id\":\"magicstack.magicpython\",\"uuid\":\"1f5eb737-5496-44a3-a8a1-36a85eee4979\"}},{\"identifier\":{\"id\":\"mhutchie.git-graph\",\"uuid\":\"438221f8-1107-4ccd-a6fe-f3b7fe0856b7\"},\"displayName\":\"Git Graph\"},{\"identifier\":{\"id\":\"ms-azuretools.vscode-docker\",\"uuid\":\"0479fc1c-3d67-49f9-b087-fb9069afe48f\"},\"displayName\":\"Docker\"},{\"identifier\":{\"id\":\"ms-python.black-formatter\",\"uuid\":\"859e640c-c157-47da-8699-9080b81c8371\"},\"displayName\":\"Black Formatter\",\"preRelease\":true},{\"identifier\":{\"id\":\"ms-python.debugpy\",\"uuid\":\"4bd5d2c9-9d65-401a-b0b2-7498d9f17615\"},\"displayName\":\"Python Debugger\"},{\"identifier\":{\"id\":\"ms-python.python\",\"uuid\":\"f1f59ae4-9318-4f3c-a9b5-81b2eaa5f8a5\"},\"displayName\":\"Python\",\"preRelease\":true},{\"identifier\":{\"id\":\"ms-python.vscode-pylance\",\"uuid\":\"364d2426-116a-433a-a5d8-a5098dc3afbd\"},\"displayName\":\"Pylance\",\"preRelease\":true},{\"identifier\":{\"id\":\"ms-toolsai.jupyter\",\"uuid\":\"6c2f1801-1e7f-45b2-9b5c-7782f1e076e8\"},\"displayName\":\"Jupyter\"},{\"identifier\":{\"id\":\"ms-toolsai.jupyter-keymap\",\"uuid\":\"9f6dc8db-620c-4844-b8c5-e74914f1be27\"},\"displayName\":\"Jupyter Keymap\"},{\"identifier\":{\"id\":\"ms-toolsai.jupyter-renderers\",\"uuid\":\"b15c72f8-d5fe-421a-a4f7-27ed9f6addbf\"},\"displayName\":\"Jupyter Notebook Renderers\"},{\"identifier\":{\"id\":\"ms-toolsai.vscode-jupyter-cell-tags\",\"uuid\":\"ab4fb32a-befb-4102-adf9-1652d0cd6a5e\"},\"displayName\":\"Jupyter Cell Tags\"},{\"identifier\":{\"id\":\"ms-toolsai.vscode-jupyter-slideshow\",\"uuid\":\"e153ca70-b543-4865-b4c5-b31d34185948\"},\"displayName\":\"Jupyter Slide Show\"},{\"identifier\":{\"id\":\"ms-vscode-remote.remote-containers\",\"uuid\":\"93ce222b-5f6f-49b7-9ab1-a0463c6238df\"},\"displayName\":\"Dev Containers\",\"preRelease\":true},{\"identifier\":{\"id\":\"ms-vscode-remote.remote-ssh\",\"uuid\":\"607fd052-be03-4363-b657-2bd62b83d28a\"},\"displayName\":\"Remote - SSH\"},{\"identifier\":{\"id\":\"ms-vscode-remote.remote-ssh-edit\",\"uuid\":\"bfeaf631-bcff-4908-93ed-fda4ef9a0c5c\"},\"displayName\":\"Remote - SSH: Editing Configuration Files\"},{\"identifier\":{\"id\":\"ms-vscode-remote.remote-wsl\",\"uuid\":\"f0c5397b-d357-4197-99f0-cb4202f22818\"},\"displayName\":\"WSL\"},{\"identifier\":{\"id\":\"ms-vscode-remote.vscode-remote-extensionpack\",\"uuid\":\"23d72dfc-8dd1-4e30-926e-8783b4378f13\"},\"displayName\":\"Remote Development\"},{\"identifier\":{\"id\":\"ms-vscode.remote-explorer\",\"uuid\":\"11858313-52cc-4e57-b3e4-d7b65281e34b\"},\"displayName\":\"Remote Explorer\"},{\"identifier\":{\"id\":\"ms-vscode.remote-repositories\",\"uuid\":\"cf5142f0-3701-4992-980c-9895a750addf\"},\"displayName\":\"Remote Repositories\"},{\"identifier\":{\"id\":\"ms-vscode.remote-server\",\"uuid\":\"105c0b3c-07a9-4156-a4fc-4141040eb07e\"},\"displayName\":\"Remote - Tunnels\"},{\"identifier\":{\"id\":\"njpwerner.autodocstring\",\"uuid\":\"2d6fea35-f68e-461d-9b7b-5cd05be99451\"},\"displayName\":\"autoDocstring - Python Docstring Generator\"},{\"identifier\":{\"id\":\"redhat.vscode-xml\",\"uuid\":\"6703768d-d42f-474e-9f6e-5f288d53f6e8\"},\"displayName\":\"XML\"},{\"identifier\":{\"id\":\"smartertomato.locate-this-document\",\"uuid\":\"64feedce-76fb-4ead-920e-d7fb7797af6b\"},\"displayName\":\"Locate This Document\"},{\"identifier\":{\"id\":\"streetsidesoftware.code-spell-checker\",\"uuid\":\"f6dbd813-b0a0-42c1-90ea-10dde9d925a7\"},\"displayName\":\"Code Spell Checker\"},{\"identifier\":{\"id\":\"tamasfe.even-better-toml\",\"uuid\":\"b2215d5f-675e-4a2b-b6ac-1ca737518b78\"},\"displayName\":\"Even Better TOML\"}]","globalState":"{\"storage\":{\"workbench.panel.markers.hidden\":\"[{\\\"id\\\":\\\"workbench.panel.markers.view\\\",\\\"isHidden\\\":false}]\",\"workbench.panel.output.hidden\":\"[{\\\"id\\\":\\\"workbench.panel.output\\\",\\\"isHidden\\\":false}]\",\"terminal.hidden\":\"[{\\\"id\\\":\\\"terminal\\\",\\\"isHidden\\\":false}]\",\"workbench.explorer.views.state.hidden\":\"[{\\\"id\\\":\\\"outline\\\",\\\"isHidden\\\":false,\\\"order\\\":2},{\\\"id\\\":\\\"timeline\\\",\\\"isHidden\\\":false,\\\"order\\\":3},{\\\"id\\\":\\\"workbench.explorer.openEditorsView\\\",\\\"isHidden\\\":false,\\\"order\\\":1},{\\\"id\\\":\\\"workbench.explorer.emptyView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"npm\\\",\\\"isHidden\\\":false,\\\"order\\\":4},{\\\"id\\\":\\\"workbench.explorer.fileView\\\",\\\"isHidden\\\":false,\\\"order\\\":0},{\\\"id\\\":\\\"jupyterViewVariables\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerFavorites\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerGit\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerSVN\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerAny\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerMercurial\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectsExplorerVSCode\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"projectManagerHelpAndFeedback\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"cell-tag\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:login\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"pr:github\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"issues:github\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workspaceEnvironments\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"pythonEnvironments\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"commitViewProvider\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"compareCommitViewProvider\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:createPullRequestWebview\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"cmake.projectStatus\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"hexEditor.dataInspectorView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"makefile.outline\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:compareChangesFiles\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"cmake.outline\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:compareChangesCommits\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"cmake.pinnedCommands\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"prStatus:github\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:activePullRequest\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github:activePullRequest:welcome\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.cloud.run.plan\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.providers\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.cloud.run.apply\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.modules\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.cloud.workspaces\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.cloud.runs\\\",\\\"isHidden\\\":false}]\",\"workbench.scm.views.state.hidden\":\"[{\\\"id\\\":\\\"workbench.scm.repositories\\\",\\\"isHidden\\\":true},{\\\"id\\\":\\\"workbench.scm\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.scm.sync\\\",\\\"isHidden\\\":false}]\",\"workbench.view.search.state.hidden\":\"[{\\\"id\\\":\\\"workbench.view.search\\\",\\\"isHidden\\\":false}]\",\"workbench.activity.pinnedViewlets2\":\"[{\\\"id\\\":\\\"workbench.view.explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":0},{\\\"id\\\":\\\"workbench.view.search\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":1},{\\\"id\\\":\\\"workbench.view.scm\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":2},{\\\"id\\\":\\\"workbench.view.debug\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":3},{\\\"id\\\":\\\"workbench.view.extension.npm\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.panel.aiSidebar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":2},{\\\"id\\\":\\\"workbench.view.extension.bookmarks\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extensions\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":4},{\\\"id\\\":\\\"workbench.view.extension.azuresphere\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.mongoDB\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.remote\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":4},{\\\"id\\\":\\\"workbench.view.extension.jupyter\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.todo-tree-container\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.test\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"workbench.view.extension.references-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":7},{\\\"id\\\":\\\"workbench.view.testing\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":5},{\\\"id\\\":\\\"workbench.view.extension.ts-playground\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.teamsfx\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.ObjectScriptView\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.panel.chatSidebar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":100},{\\\"id\\\":\\\"workbench.view.extension.frontmatter-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":25},{\\\"id\\\":\\\"workbench.view.extension.openapi-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.copilot\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.42crunch-platform\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":26},{\\\"id\\\":\\\"workbench.view.extension.thunder-client\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.PowerShell\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.wallaby\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.github-actions\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.jsonEditor-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.copilot-sidebar-webview\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.stripe\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.objectExplorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.browser-preview\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.PowerShellCommandExplorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.view.extension.nuget-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.graalvm-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.sqltoolsActivityBarContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.view.extension.gitlens\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.sln_explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.kubernetesView\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.nx-console\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.node-notebook\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.view.extension.workspaceViewer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.view.extension.angular\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.cspell-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.flutter\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.music-time\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.actions\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.powerShellProTools\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":14},{\\\"id\\\":\\\"workbench.view.extension.githd-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.code-time\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.platformio\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.vscode-edge-devtools-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.panel.interactiveSessionSidebar.copilot\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":100},{\\\"id\\\":\\\"workbench.view.extension.dockerView\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":9},{\\\"id\\\":\\\"workbench.view.extension.cmake__viewContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.panel.markers\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":0},{\\\"id\\\":\\\"workbench.view.extension.data-wrangler-primary\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.package-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.azure\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.panel.terminal\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":22},{\\\"id\\\":\\\"workbench.view.extension.testExplorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":25},{\\\"id\\\":\\\"workbench.panel.repl\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":24},{\\\"id\\\":\\\"workbench.panel.output\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":25},{\\\"id\\\":\\\"workbench.view.extension.blockchain-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":18},{\\\"id\\\":\\\"workbench.view.extension.CppRenameActivityBar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":20},{\\\"id\\\":\\\"workbench.view.extension.VirtualGistsContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.opensshremotesexplorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":21},{\\\"id\\\":\\\"workbench.view.extension.l13Projects\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":20},{\\\"id\\\":\\\"workbench.view.extension.unotes\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":15},{\\\"id\\\":\\\"workbench.view.extension.latex\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.todo\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":16},{\\\"id\\\":\\\"workbench.view.extension.pwa\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":25},{\\\"id\\\":\\\"workbench.view.extension.dataworkspace\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.liveshare\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.codestream-activitybar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.copilot-labs\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.snippet-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":15},{\\\"id\\\":\\\"workbench.view.extension.tree-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.1-kubernetesContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.latex-workshop-activitybar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":11},{\\\"id\\\":\\\"workbench.view.extension.favorites-bar\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":20},{\\\"id\\\":\\\"workbench.view.extension.log-viewer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":25},{\\\"id\\\":\\\"workbench.view.extension.favorites-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":24},{\\\"id\\\":\\\"workbench.view.extension.gitlab-workflow\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.chatActivityViewSlack\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":17},{\\\"id\\\":\\\"workbench.view.extension.chatActivityViewDiscord\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":18},{\\\"id\\\":\\\"workbench.view.extension.appmap\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.azdo-pull-requests\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":32},{\\\"id\\\":\\\"workbench.view.extension.2-cloudRunContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.azure-policy\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":12},{\\\"id\\\":\\\"workbench.view.extension.thunder-client-sidebar-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":27},{\\\"id\\\":\\\"workbench.view.extension.snippView\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":29},{\\\"id\\\":\\\"workbench.view.extension.ionide-fsharp\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":29},{\\\"id\\\":\\\"workbench.view.extension.swaggerhub\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":29},{\\\"id\\\":\\\"workbench.view.extension.taskActivity\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":26},{\\\"id\\\":\\\"workbench.view.extension.gistpad\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":29},{\\\"id\\\":\\\"workbench.view.extension.VirtualRepositories\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.Repositories\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.VirtualRepositoriesContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":13},{\\\"id\\\":\\\"workbench.view.extension.3-cloudApiContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":13},{\\\"id\\\":\\\"workbench.view.extension.workspace-explorer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":28},{\\\"id\\\":\\\"workbench.view.extension.snippetsManager\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.iridium\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":19},{\\\"id\\\":\\\"workbench.view.extension.gitops\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":30},{\\\"id\\\":\\\"workbench.view.extension.4-cloudSecretsContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":14},{\\\"id\\\":\\\"workbench.view.extension.bloop-search-view\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":34},{\\\"id\\\":\\\"workbench.view.extension.microsoft-todo-unoffcial\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":29},{\\\"id\\\":\\\"workbench.view.extension.5-apigeeContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":15},{\\\"id\\\":\\\"workbench.view.extension.spring\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"userDataProfiles\\\",\\\"pinned\\\":true,\\\"visible\\\":true},{\\\"id\\\":\\\"workbench.view.editSessions\\\",\\\"pinned\\\":true,\\\"visible\\\":false},{\\\"id\\\":\\\"workbench.view.sync\\\",\\\"pinned\\\":true,\\\"visible\\\":false}]\",\"workbench.view.debug.state.hidden\":\"[{\\\"id\\\":\\\"workbench.debug.welcome\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.debug.variablesView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.debug.watchExpressionsView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.debug.callStackView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.debug.loadedScriptsView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.debug.breakPointsView\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"jsBrowserBreakpoints\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"jsExcludedCallers\\\",\\\"isHidden\\\":false}]\",\"workbench.panel.repl.hidden\":\"[{\\\"id\\\":\\\"workbench.panel.repl.view\\\",\\\"isHidden\\\":false}]\",\"workbench.panel.pinnedPanels\":\"[{\\\"id\\\":\\\"workbench.panel.markers\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":0},{\\\"id\\\":\\\"workbench.panel.output\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":1},{\\\"id\\\":\\\"workbench.panel.repl\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":2},{\\\"id\\\":\\\"~remote.forwardedPortsContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":5},{\\\"id\\\":\\\"terminal\\\",\\\"pinned\\\":true,\\\"visible\\\":true,\\\"order\\\":3},{\\\"id\\\":\\\"workbench.panel.testResults\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":3},{\\\"id\\\":\\\"workbench.view.extension.jupyter-variables\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"workbench.view.extension.sqltoolsPanelContainer\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"workbench.view.extension.azurePanel\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"workbench.panel.comments\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":10},{\\\"id\\\":\\\"workbench.view.extension.AskJarvisPanel\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":8},{\\\"id\\\":\\\"workbench.view.extension.gitlensPanel\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"workbench.view.extension.data-wrangler-secondary\\\",\\\"pinned\\\":true,\\\"visible\\\":false,\\\"order\\\":6},{\\\"id\\\":\\\"refactorPreview\\\",\\\"pinned\\\":true,\\\"visible\\\":false}]\",\"nps/lastSessionDate\":\"Mon Mar 20 2023\",\"nps/sessionCount\":\"1\",\"cpp.1.lastSessionDate\":\"Mon Mar 20 2023\",\"cpp.1.sessionCount\":\"1\",\"java.2.lastSessionDate\":\"Mon Mar 20 2023\",\"java.2.sessionCount\":\"1\",\"javascript.1.lastSessionDate\":\"Mon Mar 20 2023\",\"javascript.1.sessionCount\":\"1\",\"typescript.1.lastSessionDate\":\"Mon Mar 20 2023\",\"typescript.1.sessionCount\":\"1\",\"csharp.1.lastSessionDate\":\"Mon Mar 20 2023\",\"csharp.1.sessionCount\":\"1\",\"workbench.telemetryOptOutShown\":\"true\",\"workbench.welcomePage.walkthroughMetadata\":\"[[\\\"ms-vscode-remote.remote-wsl#wslWalkthrough\\\",{\\\"firstSeen\\\":1701354403261,\\\"stepIDs\\\":[\\\"explore.commands\\\",\\\"open.wslwindow\\\",\\\"create.project\\\",\\\"open.project\\\",\\\"linux.environment\\\",\\\"install.tools\\\",\\\"run.debug\\\",\\\"come.back\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-azuretools.vscode-docker#dockerStart\\\",{\\\"firstSeen\\\":1701354572424,\\\"stepIDs\\\":[\\\"openFolder\\\",\\\"openFolderMac\\\",\\\"scaffold\\\",\\\"buildImage\\\",\\\"runContainer\\\",\\\"dockerExplorer\\\",\\\"pushImage\\\",\\\"azDeploy\\\",\\\"learn\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-python.python#pythonWelcome\\\",{\\\"firstSeen\\\":1701354572424,\\\"stepIDs\\\":[\\\"python.createPythonFile\\\",\\\"python.installPythonWin8\\\",\\\"python.installPythonMac\\\",\\\"python.installPythonLinux\\\",\\\"python.selectInterpreter\\\",\\\"python.createEnvironment\\\",\\\"python.runAndDebug\\\",\\\"python.learnMoreWithDS\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-python.python#pythonWelcome2\\\",{\\\"firstSeen\\\":1701354572424,\\\"stepIDs\\\":[\\\"python.createPythonFolder\\\",\\\"python.createPythonFile\\\",\\\"python.installPythonWin8\\\",\\\"python.installPythonMac\\\",\\\"python.installPythonLinux\\\",\\\"python.createEnvironment2\\\",\\\"python.runAndDebug\\\",\\\"python.learnMoreWithDS2\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-python.python#pythonDataScienceWelcome\\\",{\\\"firstSeen\\\":1701354572424,\\\"stepIDs\\\":[\\\"python.installJupyterExt\\\",\\\"python.createNewNotebook\\\",\\\"python.openInteractiveWindow\\\",\\\"python.dataScienceLearnMore\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-vscode.remote-repositories#remoteRepositoriesWalkthrough\\\",{\\\"firstSeen\\\":1701355351789,\\\"stepIDs\\\":[\\\"editCommitRepo\\\",\\\"createGitHubPullRequest\\\",\\\"continueOn\\\",\\\"openRepo\\\",\\\"remoteIndicator\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-toolsai.jupyter#jupyterWelcome\\\",{\\\"firstSeen\\\":1701360557587,\\\"stepIDs\\\":[\\\"ipynb.newUntitledIpynb\\\",\\\"jupyter.selectKernel\\\",\\\"jupyter.exploreAndDebug\\\",\\\"jupyter.dataScienceLearnMore\\\"],\\\"manaullyOpened\\\":false}],[\\\"ms-vscode.cpptools#cppWelcome\\\",{\\\"firstSeen\\\":1701360557587,\\\"stepIDs\\\":[\\\"awaiting.activation.mac\\\",\\\"awaiting.activation.linux\\\",\\\"awaiting.activation.windows\\\",\\\"awaiting.activation.windows10\\\",\\\"awaiting.activation.windows11\\\",\\\"no.compilers.found.mac\\\",\\\"no.compilers.found.linux\\\",\\\"no.compilers.found.windows\\\",\\\"no.compilers.found.windows10\\\",\\\"no.compilers.found.windows11\\\",\\\"verify.compiler.mac\\\",\\\"verify.compiler.linux\\\",\\\"verify.compiler.windows\\\",\\\"verify.compiler.windows10\\\",\\\"verify.compiler.windows11\\\",\\\"create.cpp.file\\\",\\\"relaunch.developer.command.prompt.windows\\\",\\\"run.project.mac\\\",\\\"run.project.linux\\\",\\\"run.project.windows\\\",\\\"customize.debugging.linux\\\",\\\"customize.debugging.windows\\\",\\\"customize.debugging.mac\\\"],\\\"manaullyOpened\\\":false}],[\\\"alefragnani.project-manager#projectManagerWelcome\\\",{\\\"firstSeen\\\":1713359925003,\\\"stepIDs\\\":[\\\"saveYourFavoriteProjects\\\",\\\"autoDetectGitRepositories\\\",\\\"findAndOpenProjects\\\",\\\"organizeWithTags\\\",\\\"exclusiveSideBar\\\",\\\"workingWithRemotes\\\"],\\\"manaullyOpened\\\":false}]]\",\"workbench.statusbar.hidden\":\"[\\\"status.workspaceTrust.1679345439283\\\",\\\"status.workspaceTrust.73d5e5c0573f77ee09afaabbb9bfcaf5\\\",\\\"status.workspaceTrust.42864412b711bf21d7aea2d7e66cc6de\\\",\\\"status.workspaceTrust.92806934cad7db7294e53c50943b7fe8\\\",\\\"status.workspaceTrust.abd1be6f9ea531ca0d9d1ab35f49f1ad\\\",\\\"status.workspaceTrust.f97d4cd6c7f6aafa346eb8b327f26194\\\",\\\"status.workspaceTrust.64c49213a05bb8b0a575b6db4cbdf16e\\\",\\\"status.workspaceTrust.1701730257032\\\",\\\"status.workspaceTrust.d58ac7e584d4d3579b0e3c053b0ca6ba\\\",\\\"status.workspaceTrust.1701730854591\\\",\\\"status.workspaceTrust.eee8674571c5c0f4ea865274ed180362\\\",\\\"status.workspaceTrust.335f224169beac13614e3f98eac7b190\\\",\\\"status.workspaceTrust.1701791529243\\\",\\\"status.workspaceTrust.1701799361388\\\",\\\"status.workspaceTrust.acd1730dc09b267cf5ac4a485b4867eb\\\",\\\"status.workspaceTrust.1701800756151\\\",\\\"status.workspaceTrust.1701816872653\\\",\\\"status.workspaceTrust.39dec3c877c9667b0232a6a0f80f5c43\\\",\\\"status.workspaceTrust.1701822484214\\\",\\\"status.workspaceTrust.da603eaf2527eb0721b46c0d7632cb8f\\\",\\\"status.workspaceTrust.1701896902489\\\",\\\"status.workspaceTrust.1701898300503\\\",\\\"status.workspaceTrust.1701900534969\\\",\\\"status.workspaceTrust.1701902493954\\\",\\\"status.workspaceTrust.1701903618503\\\",\\\"status.workspaceTrust.1701903894983\\\",\\\"status.workspaceTrust.1701905408772\\\",\\\"status.workspaceTrust.1701953899959\\\",\\\"status.workspaceTrust.1701958552145\\\",\\\"status.workspaceTrust.1701963428085\\\",\\\"status.workspaceTrust.1702072777066\\\",\\\"status.workspaceTrust.1702336223137\\\",\\\"status.workspaceTrust.1702392992341\\\",\\\"status.workspaceTrust.1702399735733\\\",\\\"status.workspaceTrust.1702418249542\\\",\\\"status.workspaceTrust.1702478928626\\\",\\\"status.workspaceTrust.1702479965295\\\",\\\"status.workspaceTrust.1702485006626\\\",\\\"status.workspaceTrust.1702504885155\\\",\\\"status.workspaceTrust.1702508462022\\\",\\\"status.workspaceTrust.1702508491282\\\",\\\"status.workspaceTrust.1702553710265\\\",\\\"status.workspaceTrust.1702573580391\\\",\\\"status.workspaceTrust.9fc66e67b2e80cd117e26d58aabc2ae1\\\",\\\"status.workspaceTrust.839447ce7ddb4db5114ff16f8a53e666\\\",\\\"status.workspaceTrust.1706742870159\\\",\\\"status.workspaceTrust.2c8bba3e0d96116b21be61557027a6fd\\\",\\\"status.workspaceTrust.1706806581042\\\",\\\"status.workspaceTrust.68a23cd59c8d3cad00f3ab6d6ce12ae9\\\",\\\"status.workspaceTrust.1706818785243\\\",\\\"status.workspaceTrust.1706819098999\\\",\\\"status.workspaceTrust.1706820204694\\\",\\\"status.workspaceTrust.1706888695278\\\",\\\"status.workspaceTrust.b9f5052cbd9c07cf28102f7cc1b2bdb1\\\",\\\"status.workspaceTrust.1706895487690\\\",\\\"status.workspaceTrust.1706911812914\\\",\\\"status.workspaceTrust.1707149652297\\\",\\\"status.workspaceTrust.840c692c7a44a561a89dbf0b1011c377\\\",\\\"status.workspaceTrust.98eae51c9579bdba8299c836f7185b56\\\",\\\"status.workspaceTrust.1707160582050\\\",\\\"status.workspaceTrust.1707177935504\\\",\\\"status.workspaceTrust.1707236239656\\\",\\\"status.workspaceTrust.1707244215989\\\",\\\"status.workspaceTrust.1707250732231\\\",\\\"status.workspaceTrust.1707253682305\\\",\\\"status.workspaceTrust.1707255516448\\\",\\\"status.workspaceTrust.1707258566660\\\",\\\"status.workspaceTrust.1707261344177\\\",\\\"status.workspaceTrust.1707262136113\\\",\\\"status.workspaceTrust.56321a36854721e63a74f927bcff5c87\\\",\\\"status.workspaceTrust.1707332562638\\\",\\\"status.workspaceTrust.1707351940026\\\",\\\"status.workspaceTrust.1707406029929\\\",\\\"status.workspaceTrust.1707426191034\\\",\\\"status.workspaceTrust.1707428185541\\\",\\\"status.workspaceTrust.1707842148097\\\",\\\"status.workspaceTrust.1707926217756\\\",\\\"status.workspaceTrust.1708098881842\\\",\\\"status.workspaceTrust.1708444543998\\\",\\\"status.workspaceTrust.1708481717094\\\",\\\"status.workspaceTrust.1708562309743\\\",\\\"status.workspaceTrust.1708614996082\\\",\\\"status.workspaceTrust.1708629681551\\\",\\\"status.workspaceTrust.ac43c8570feb9aa1c3314b7d74571669\\\",\\\"status.workspaceTrust.1708715331692\\\",\\\"status.workspaceTrust.c3cdca62a1427ef95a438cce8888c1dd\\\",\\\"status.workspaceTrust.1709598359839\\\",\\\"status.workspaceTrust.1ddcc2b61364f24f72d91d51da23e542\\\"]\",\"workbench.view.extensions.state.hidden\":\"[{\\\"id\\\":\\\"workbench.views.extensions.installed\\\",\\\"isHidden\\\":true},{\\\"id\\\":\\\"workbench.views.extensions.searchOutdated\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.workspaceRecommendations\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.popular\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchRecentlyUpdated\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.otherRecommendations\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"extensions.recommendedList\\\",\\\"isHidden\\\":true},{\\\"id\\\":\\\"workbench.views.extensions.enabled\\\",\\\"isHidden\\\":true},{\\\"id\\\":\\\"workbench.views.extensions.disabled\\\",\\\"isHidden\\\":true},{\\\"id\\\":\\\"workbench.views.extensions.marketplace\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchInstalled\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchEnabled\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchDisabled\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchBuiltin\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.searchWorkspaceUnsupported\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.builtinFeatureExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.builtinThemeExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.builtinProgrammingLanguageExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.untrustedUnsupportedExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.untrustedPartiallySupportedExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.virtualUnsupportedExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.virtualPartiallySupportedExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.deprecatedExtensions\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.local.installed\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.extensions.remote.installed\\\",\\\"isHidden\\\":false}]\",\"commandPalette.mru.cache\":\"{\\\"usesLRU\\\":true,\\\"entries\\\":[{\\\"key\\\":\\\"workbench.profiles.actions.importProfile\\\",\\\"value\\\":1},{\\\"key\\\":\\\"editor.action.smartSelect.expand\\\",\\\"value\\\":11},{\\\"key\\\":\\\"git.deleteRemoteTag\\\",\\\"value\\\":25},{\\\"key\\\":\\\"git.fetch\\\",\\\"value\\\":27},{\\\"key\\\":\\\"git.fetchPrune\\\",\\\"value\\\":29},{\\\"key\\\":\\\"python.createTerminal\\\",\\\"value\\\":51},{\\\"key\\\":\\\"python.setInterpreter\\\",\\\"value\\\":74},{\\\"key\\\":\\\"opensshremotes.addNewSshHost\\\",\\\"value\\\":83},{\\\"key\\\":\\\"git.checkout\\\",\\\"value\\\":114},{\\\"key\\\":\\\"git.deleteBranch\\\",\\\"value\\\":118},{\\\"key\\\":\\\"python.createEnvironment\\\",\\\"value\\\":120},{\\\"key\\\":\\\"git.push\\\",\\\"value\\\":121},{\\\"key\\\":\\\"opensshremotes.openEmptyWindow\\\",\\\"value\\\":124},{\\\"key\\\":\\\"git.pullFrom\\\",\\\"value\\\":126},{\\\"key\\\":\\\"git.branchFrom\\\",\\\"value\\\":127}]}\",\"commandPalette.mru.counter\":\"128\",\"workbench.view.extension.test.state.hidden\":\"[{\\\"id\\\":\\\"workbench.view.testing\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.view.testCoverage\\\",\\\"isHidden\\\":false}]\",\"workbench.view.extension.dockerView.state.hidden\":\"[{\\\"id\\\":\\\"dockerContainers\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"dockerImages\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"dockerRegistries\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"dockerNetworks\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"dockerVolumes\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"vscode-docker.views.dockerContexts\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"vscode-docker.views.help\\\",\\\"isHidden\\\":false}]\",\"extensionTips/promptedExecutableTips\":\"{\\\"wsl\\\":[\\\"ms-vscode-remote.remote-wsl\\\"]}\",\"workbench.view.remote.state.hidden\":\"[{\\\"id\\\":\\\"targetsContainers\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"detailsContainers\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"devVolumes\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"targetsWsl\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"remoteTargets\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"remoteHub.views.workspaceRepositories\\\",\\\"isHidden\\\":false}]\",\"colorThemeData\":\"{\\\"id\\\":\\\"vs-dark vscode-theme-defaults-themes-dark_modern-json\\\",\\\"label\\\":\\\"Dark Modern\\\",\\\"settingsId\\\":\\\"Default Dark Modern\\\",\\\"themeTokenColors\\\":[{\\\"settings\\\":{\\\"foreground\\\":\\\"#D4D4D4\\\"},\\\"scope\\\":[\\\"meta.embedded\\\",\\\"source.groovy.embedded\\\",\\\"string meta.image.inline.markdown\\\",\\\"variable.legacy.builtin.python\\\"]},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"italic\\\"},\\\"scope\\\":\\\"emphasis\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"bold\\\"},\\\"scope\\\":\\\"strong\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#000080\\\"},\\\"scope\\\":\\\"header\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#6A9955\\\"},\\\"scope\\\":\\\"comment\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"constant.language\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#b5cea8\\\"},\\\"scope\\\":[\\\"constant.numeric\\\",\\\"variable.other.enummember\\\",\\\"keyword.operator.plus.exponent\\\",\\\"keyword.operator.minus.exponent\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#646695\\\"},\\\"scope\\\":\\\"constant.regexp\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"entity.name.tag\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d7ba7d\\\"},\\\"scope\\\":[\\\"entity.name.tag.css\\\",\\\"entity.name.tag.less\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9cdcfe\\\"},\\\"scope\\\":\\\"entity.other.attribute-name\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d7ba7d\\\"},\\\"scope\\\":[\\\"entity.other.attribute-name.class.css\\\",\\\"source.css entity.other.attribute-name.class\\\",\\\"entity.other.attribute-name.id.css\\\",\\\"entity.other.attribute-name.parent-selector.css\\\",\\\"entity.other.attribute-name.parent.less\\\",\\\"source.css entity.other.attribute-name.pseudo-class\\\",\\\"entity.other.attribute-name.pseudo-element.css\\\",\\\"source.css.less entity.other.attribute-name.id\\\",\\\"entity.other.attribute-name.scss\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#f44747\\\"},\\\"scope\\\":\\\"invalid\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"underline\\\"},\\\"scope\\\":\\\"markup.underline\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"bold\\\",\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"markup.bold\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"bold\\\",\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"markup.heading\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"italic\\\"},\\\"scope\\\":\\\"markup.italic\\\"},{\\\"settings\\\":{\\\"fontStyle\\\":\\\"strikethrough\\\"},\\\"scope\\\":\\\"markup.strikethrough\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#b5cea8\\\"},\\\"scope\\\":\\\"markup.inserted\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":\\\"markup.deleted\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"markup.changed\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#6A9955\\\"},\\\"scope\\\":\\\"punctuation.definition.quote.begin.markdown\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#6796e6\\\"},\\\"scope\\\":\\\"punctuation.definition.list.begin.markdown\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":\\\"markup.inline.raw\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#808080\\\"},\\\"scope\\\":\\\"punctuation.definition.tag\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"meta.preprocessor\\\",\\\"entity.name.function.preprocessor\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":\\\"meta.preprocessor.string\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#b5cea8\\\"},\\\"scope\\\":\\\"meta.preprocessor.numeric\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9cdcfe\\\"},\\\"scope\\\":\\\"meta.structure.dictionary.key.python\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"meta.diff.header\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"storage\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"storage.type\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"storage.modifier\\\",\\\"keyword.operator.noexcept\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":[\\\"string\\\",\\\"meta.embedded.assembly\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":\\\"string.tag\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#ce9178\\\"},\\\"scope\\\":\\\"string.value\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d16969\\\"},\\\"scope\\\":\\\"string.regexp\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"punctuation.definition.template-expression.begin\\\",\\\"punctuation.definition.template-expression.end\\\",\\\"punctuation.section.embedded\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d4d4d4\\\"},\\\"scope\\\":[\\\"meta.template.expression\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9cdcfe\\\"},\\\"scope\\\":[\\\"support.type.vendored.property-name\\\",\\\"support.type.property-name\\\",\\\"source.css variable\\\",\\\"source.coffee.embedded\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"keyword\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"keyword.control\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d4d4d4\\\"},\\\"scope\\\":\\\"keyword.operator\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"keyword.operator.new\\\",\\\"keyword.operator.expression\\\",\\\"keyword.operator.cast\\\",\\\"keyword.operator.sizeof\\\",\\\"keyword.operator.alignof\\\",\\\"keyword.operator.typeid\\\",\\\"keyword.operator.alignas\\\",\\\"keyword.operator.instanceof\\\",\\\"keyword.operator.logical.python\\\",\\\"keyword.operator.wordlike\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#b5cea8\\\"},\\\"scope\\\":\\\"keyword.other.unit\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"punctuation.section.embedded.begin.php\\\",\\\"punctuation.section.embedded.end.php\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9cdcfe\\\"},\\\"scope\\\":\\\"support.function.git-rebase\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#b5cea8\\\"},\\\"scope\\\":\\\"constant.sha.git-rebase\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d4d4d4\\\"},\\\"scope\\\":[\\\"storage.modifier.import.java\\\",\\\"variable.language.wildcard.java\\\",\\\"storage.modifier.package.java\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":\\\"variable.language\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#DCDCAA\\\"},\\\"scope\\\":[\\\"entity.name.function\\\",\\\"support.function\\\",\\\"support.constant.handlebars\\\",\\\"source.powershell variable.other.member\\\",\\\"entity.name.operator.custom-literal\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#4EC9B0\\\"},\\\"scope\\\":[\\\"support.class\\\",\\\"support.type\\\",\\\"entity.name.type\\\",\\\"entity.name.namespace\\\",\\\"entity.other.attribute\\\",\\\"entity.name.scope-resolution\\\",\\\"entity.name.class\\\",\\\"storage.type.numeric.go\\\",\\\"storage.type.byte.go\\\",\\\"storage.type.boolean.go\\\",\\\"storage.type.string.go\\\",\\\"storage.type.uintptr.go\\\",\\\"storage.type.error.go\\\",\\\"storage.type.rune.go\\\",\\\"storage.type.cs\\\",\\\"storage.type.generic.cs\\\",\\\"storage.type.modifier.cs\\\",\\\"storage.type.variable.cs\\\",\\\"storage.type.annotation.java\\\",\\\"storage.type.generic.java\\\",\\\"storage.type.java\\\",\\\"storage.type.object.array.java\\\",\\\"storage.type.primitive.array.java\\\",\\\"storage.type.primitive.java\\\",\\\"storage.type.token.java\\\",\\\"storage.type.groovy\\\",\\\"storage.type.annotation.groovy\\\",\\\"storage.type.parameters.groovy\\\",\\\"storage.type.generic.groovy\\\",\\\"storage.type.object.array.groovy\\\",\\\"storage.type.primitive.array.groovy\\\",\\\"storage.type.primitive.groovy\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#4EC9B0\\\"},\\\"scope\\\":[\\\"meta.type.cast.expr\\\",\\\"meta.type.new.expr\\\",\\\"support.constant.math\\\",\\\"support.constant.dom\\\",\\\"support.constant.json\\\",\\\"entity.other.inherited-class\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#C586C0\\\"},\\\"scope\\\":[\\\"keyword.control\\\",\\\"source.cpp keyword.operator.new\\\",\\\"keyword.operator.delete\\\",\\\"keyword.other.using\\\",\\\"keyword.other.directive.using\\\",\\\"keyword.other.operator\\\",\\\"entity.name.operator\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9CDCFE\\\"},\\\"scope\\\":[\\\"variable\\\",\\\"meta.definition.variable.name\\\",\\\"support.variable\\\",\\\"entity.name.variable\\\",\\\"constant.other.placeholder\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#4FC1FF\\\"},\\\"scope\\\":[\\\"variable.other.constant\\\",\\\"variable.other.enummember\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#9CDCFE\\\"},\\\"scope\\\":[\\\"meta.object-literal.key\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#CE9178\\\"},\\\"scope\\\":[\\\"support.constant.property-value\\\",\\\"support.constant.font-name\\\",\\\"support.constant.media-type\\\",\\\"support.constant.media\\\",\\\"constant.other.color.rgb-value\\\",\\\"constant.other.rgb-value\\\",\\\"support.constant.color\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#CE9178\\\"},\\\"scope\\\":[\\\"punctuation.definition.group.regexp\\\",\\\"punctuation.definition.group.assertion.regexp\\\",\\\"punctuation.definition.character-class.regexp\\\",\\\"punctuation.character.set.begin.regexp\\\",\\\"punctuation.character.set.end.regexp\\\",\\\"keyword.operator.negation.regexp\\\",\\\"support.other.parenthesis.regexp\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d16969\\\"},\\\"scope\\\":[\\\"constant.character.character-class.regexp\\\",\\\"constant.other.character-class.set.regexp\\\",\\\"constant.other.character-class.regexp\\\",\\\"constant.character.set.regexp\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#DCDCAA\\\"},\\\"scope\\\":[\\\"keyword.operator.or.regexp\\\",\\\"keyword.control.anchor.regexp\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d7ba7d\\\"},\\\"scope\\\":\\\"keyword.operator.quantifier.regexp\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#569cd6\\\"},\\\"scope\\\":[\\\"constant.character\\\",\\\"constant.other.option\\\"]},{\\\"settings\\\":{\\\"foreground\\\":\\\"#d7ba7d\\\"},\\\"scope\\\":\\\"constant.character.escape\\\"},{\\\"settings\\\":{\\\"foreground\\\":\\\"#C8C8C8\\\"},\\\"scope\\\":\\\"entity.name.label\\\"}],\\\"semanticTokenRules\\\":[{\\\"_selector\\\":\\\"newOperator\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#d4d4d4\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"stringLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#ce9178\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"customLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#d4d4d4\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"numberLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#b5cea8\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"newOperator\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#c586c0\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"stringLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#ce9178\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"customLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#dcdcaa\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}},{\\\"_selector\\\":\\\"numberLiteral\\\",\\\"_style\\\":{\\\"_foreground\\\":\\\"#b5cea8\\\",\\\"_bold\\\":null,\\\"_underline\\\":null,\\\"_italic\\\":null,\\\"_strikethrough\\\":null}}],\\\"extensionData\\\":{\\\"_extensionId\\\":\\\"vscode.theme-defaults\\\",\\\"_extensionIsBuiltin\\\":true,\\\"_extensionName\\\":\\\"theme-defaults\\\",\\\"_extensionPublisher\\\":\\\"vscode\\\"},\\\"themeSemanticHighlighting\\\":true,\\\"colorMap\\\":{\\\"checkbox.border\\\":\\\"#3c3c3c\\\",\\\"editor.background\\\":\\\"#1f1f1f\\\",\\\"editor.foreground\\\":\\\"#cccccc\\\",\\\"editor.inactiveSelectionBackground\\\":\\\"#3a3d41\\\",\\\"editorIndentGuide.background1\\\":\\\"#404040\\\",\\\"editorIndentGuide.activeBackground1\\\":\\\"#707070\\\",\\\"editor.selectionHighlightBackground\\\":\\\"#add6ff26\\\",\\\"list.dropBackground\\\":\\\"#383b3d\\\",\\\"activityBarBadge.background\\\":\\\"#0078d4\\\",\\\"sideBarTitle.foreground\\\":\\\"#cccccc\\\",\\\"input.placeholderForeground\\\":\\\"#989898\\\",\\\"menu.background\\\":\\\"#1f1f1f\\\",\\\"menu.foreground\\\":\\\"#cccccc\\\",\\\"menu.separatorBackground\\\":\\\"#454545\\\",\\\"menu.border\\\":\\\"#454545\\\",\\\"statusBarItem.remoteForeground\\\":\\\"#ffffff\\\",\\\"statusBarItem.remoteBackground\\\":\\\"#0078d4\\\",\\\"ports.iconRunningProcessForeground\\\":\\\"#369432\\\",\\\"sideBarSectionHeader.background\\\":\\\"#181818\\\",\\\"sideBarSectionHeader.border\\\":\\\"#2b2b2b\\\",\\\"tab.selectedBackground\\\":\\\"#222222\\\",\\\"tab.selectedForeground\\\":\\\"#ffffffa0\\\",\\\"tab.lastPinnedBorder\\\":\\\"#cccccc33\\\",\\\"list.activeSelectionIconForeground\\\":\\\"#ffffff\\\",\\\"terminal.inactiveSelectionBackground\\\":\\\"#3a3d41\\\",\\\"widget.border\\\":\\\"#313131\\\",\\\"actionBar.toggledBackground\\\":\\\"#383a49\\\",\\\"activityBar.activeBorder\\\":\\\"#0078d4\\\",\\\"activityBar.background\\\":\\\"#181818\\\",\\\"activityBar.border\\\":\\\"#2b2b2b\\\",\\\"activityBar.foreground\\\":\\\"#d7d7d7\\\",\\\"activityBar.inactiveForeground\\\":\\\"#868686\\\",\\\"activityBarBadge.foreground\\\":\\\"#ffffff\\\",\\\"badge.background\\\":\\\"#616161\\\",\\\"badge.foreground\\\":\\\"#f8f8f8\\\",\\\"button.background\\\":\\\"#0078d4\\\",\\\"button.border\\\":\\\"#ffffff12\\\",\\\"button.foreground\\\":\\\"#ffffff\\\",\\\"button.hoverBackground\\\":\\\"#026ec1\\\",\\\"button.secondaryBackground\\\":\\\"#313131\\\",\\\"button.secondaryForeground\\\":\\\"#cccccc\\\",\\\"button.secondaryHoverBackground\\\":\\\"#3c3c3c\\\",\\\"chat.slashCommandBackground\\\":\\\"#34414b\\\",\\\"chat.slashCommandForeground\\\":\\\"#40a6ff\\\",\\\"checkbox.background\\\":\\\"#313131\\\",\\\"debugToolBar.background\\\":\\\"#181818\\\",\\\"descriptionForeground\\\":\\\"#9d9d9d\\\",\\\"dropdown.background\\\":\\\"#313131\\\",\\\"dropdown.border\\\":\\\"#3c3c3c\\\",\\\"dropdown.foreground\\\":\\\"#cccccc\\\",\\\"dropdown.listBackground\\\":\\\"#1f1f1f\\\",\\\"editor.findMatchBackground\\\":\\\"#9e6a03\\\",\\\"editorGroup.border\\\":\\\"#ffffff17\\\",\\\"editorGroupHeader.tabsBackground\\\":\\\"#181818\\\",\\\"editorGroupHeader.tabsBorder\\\":\\\"#2b2b2b\\\",\\\"editorGutter.addedBackground\\\":\\\"#2ea043\\\",\\\"editorGutter.deletedBackground\\\":\\\"#f85149\\\",\\\"editorGutter.modifiedBackground\\\":\\\"#0078d4\\\",\\\"editorLineNumber.activeForeground\\\":\\\"#cccccc\\\",\\\"editorLineNumber.foreground\\\":\\\"#6e7681\\\",\\\"editorOverviewRuler.border\\\":\\\"#010409\\\",\\\"editorWidget.background\\\":\\\"#202020\\\",\\\"errorForeground\\\":\\\"#f85149\\\",\\\"focusBorder\\\":\\\"#0078d4\\\",\\\"foreground\\\":\\\"#cccccc\\\",\\\"icon.foreground\\\":\\\"#cccccc\\\",\\\"input.background\\\":\\\"#313131\\\",\\\"input.border\\\":\\\"#3c3c3c\\\",\\\"input.foreground\\\":\\\"#cccccc\\\",\\\"inputOption.activeBackground\\\":\\\"#2489db82\\\",\\\"inputOption.activeBorder\\\":\\\"#2488db\\\",\\\"keybindingLabel.foreground\\\":\\\"#cccccc\\\",\\\"menu.selectionBackground\\\":\\\"#0078d4\\\",\\\"notificationCenterHeader.background\\\":\\\"#1f1f1f\\\",\\\"notificationCenterHeader.foreground\\\":\\\"#cccccc\\\",\\\"notifications.background\\\":\\\"#1f1f1f\\\",\\\"notifications.border\\\":\\\"#2b2b2b\\\",\\\"notifications.foreground\\\":\\\"#cccccc\\\",\\\"panel.background\\\":\\\"#181818\\\",\\\"panel.border\\\":\\\"#2b2b2b\\\",\\\"panelInput.border\\\":\\\"#2b2b2b\\\",\\\"panelTitle.activeBorder\\\":\\\"#0078d4\\\",\\\"panelTitle.activeForeground\\\":\\\"#cccccc\\\",\\\"panelTitle.inactiveForeground\\\":\\\"#9d9d9d\\\",\\\"peekViewEditor.background\\\":\\\"#1f1f1f\\\",\\\"peekViewEditor.matchHighlightBackground\\\":\\\"#bb800966\\\",\\\"peekViewResult.background\\\":\\\"#1f1f1f\\\",\\\"peekViewResult.matchHighlightBackground\\\":\\\"#bb800966\\\",\\\"pickerGroup.border\\\":\\\"#3c3c3c\\\",\\\"progressBar.background\\\":\\\"#0078d4\\\",\\\"quickInput.background\\\":\\\"#222222\\\",\\\"quickInput.foreground\\\":\\\"#cccccc\\\",\\\"settings.dropdownBackground\\\":\\\"#313131\\\",\\\"settings.dropdownBorder\\\":\\\"#3c3c3c\\\",\\\"settings.headerForeground\\\":\\\"#ffffff\\\",\\\"settings.modifiedItemIndicator\\\":\\\"#bb800966\\\",\\\"sideBar.background\\\":\\\"#181818\\\",\\\"sideBar.border\\\":\\\"#2b2b2b\\\",\\\"sideBar.foreground\\\":\\\"#cccccc\\\",\\\"sideBarSectionHeader.foreground\\\":\\\"#cccccc\\\",\\\"statusBar.background\\\":\\\"#181818\\\",\\\"statusBar.border\\\":\\\"#2b2b2b\\\",\\\"statusBar.debuggingBackground\\\":\\\"#0078d4\\\",\\\"statusBar.debuggingForeground\\\":\\\"#ffffff\\\",\\\"statusBar.focusBorder\\\":\\\"#0078d4\\\",\\\"statusBar.foreground\\\":\\\"#cccccc\\\",\\\"statusBar.noFolderBackground\\\":\\\"#1f1f1f\\\",\\\"statusBarItem.focusBorder\\\":\\\"#0078d4\\\",\\\"statusBarItem.prominentBackground\\\":\\\"#6e768166\\\",\\\"tab.activeBackground\\\":\\\"#1f1f1f\\\",\\\"tab.activeBorder\\\":\\\"#1f1f1f\\\",\\\"tab.activeBorderTop\\\":\\\"#0078d4\\\",\\\"tab.activeForeground\\\":\\\"#ffffff\\\",\\\"tab.selectedBorderTop\\\":\\\"#6caddf\\\",\\\"tab.border\\\":\\\"#2b2b2b\\\",\\\"tab.hoverBackground\\\":\\\"#1f1f1f\\\",\\\"tab.inactiveBackground\\\":\\\"#181818\\\",\\\"tab.inactiveForeground\\\":\\\"#9d9d9d\\\",\\\"tab.unfocusedActiveBorder\\\":\\\"#1f1f1f\\\",\\\"tab.unfocusedActiveBorderTop\\\":\\\"#2b2b2b\\\",\\\"tab.unfocusedHoverBackground\\\":\\\"#1f1f1f\\\",\\\"terminal.foreground\\\":\\\"#cccccc\\\",\\\"terminal.tab.activeBorder\\\":\\\"#0078d4\\\",\\\"textBlockQuote.background\\\":\\\"#2b2b2b\\\",\\\"textBlockQuote.border\\\":\\\"#616161\\\",\\\"textCodeBlock.background\\\":\\\"#2b2b2b\\\",\\\"textLink.activeForeground\\\":\\\"#4daafc\\\",\\\"textLink.foreground\\\":\\\"#4daafc\\\",\\\"textPreformat.foreground\\\":\\\"#d0d0d0\\\",\\\"textPreformat.background\\\":\\\"#3c3c3c\\\",\\\"textSeparator.foreground\\\":\\\"#21262d\\\",\\\"titleBar.activeBackground\\\":\\\"#181818\\\",\\\"titleBar.activeForeground\\\":\\\"#cccccc\\\",\\\"titleBar.border\\\":\\\"#2b2b2b\\\",\\\"titleBar.inactiveBackground\\\":\\\"#1f1f1f\\\",\\\"titleBar.inactiveForeground\\\":\\\"#9d9d9d\\\",\\\"welcomePage.tileBackground\\\":\\\"#2b2b2b\\\",\\\"welcomePage.progress.foreground\\\":\\\"#0078d4\\\"},\\\"watch\\\":false}\",\"workbench.view.extension.package-explorer.state.hidden\":\"[{\\\"id\\\":\\\"pythonEnvironments\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workspaceEnvironments\\\",\\\"isHidden\\\":false}]\",\"workbench.panel.alignment\":\"center\",\"workbench.welcomePage.hiddenCategories\":\"[]\",\"userDataProfiles.state.hidden\":\"[{\\\"id\\\":\\\"workbench.views.profiles.export.preview\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"workbench.views.profiles.import.preview\\\",\\\"isHidden\\\":false}]\",\"~remote.forwardedPortsContainer.hidden\":\"[{\\\"id\\\":\\\"~remote.forwardedPorts\\\",\\\"isHidden\\\":false}]\",\"memento/gettingStartedService\":\"{\\\"settingsSync\\\":{\\\"done\\\":true},\\\"pickColorTheme\\\":{\\\"done\\\":true},\\\"commandPaletteTask\\\":{\\\"done\\\":true},\\\"extensionsWeb\\\":{\\\"done\\\":true},\\\"findLanguageExtensions\\\":{\\\"done\\\":true},\\\"pickAFolderTask-Mac\\\":{\\\"done\\\":true},\\\"pickAFolderTask-Other\\\":{\\\"done\\\":true},\\\"quickOpen\\\":{\\\"done\\\":true},\\\"commandPaletteTaskWeb\\\":{\\\"done\\\":true},\\\"installGit\\\":{\\\"done\\\":true},\\\"pickColorThemeWeb\\\":{\\\"done\\\":true},\\\"ms-python.python#pythonWelcome#python.createEnvironment\\\":{\\\"done\\\":true},\\\"ms-python.python#pythonWelcome2#python.createEnvironment2\\\":{\\\"done\\\":true},\\\"scm\\\":{\\\"done\\\":true},\\\"ms-python.python#pythonWelcome2#python.createPythonFolder\\\":{\\\"done\\\":true},\\\"ms-python.python#pythonDataScienceWelcome#python.createNewNotebook\\\":{\\\"done\\\":true},\\\"ms-azuretools.vscode-docker#dockerStart#openFolder\\\":{\\\"done\\\":true},\\\"ms-vscode-remote.remote-wsl#wslWalkthrough#create.project\\\":{\\\"done\\\":true},\\\"ms-python.python#pythonWelcome#python.selectInterpreter\\\":{\\\"done\\\":true},\\\"ms-vscode-remote.remote-wsl#wslWalkthrough#install.tools\\\":{\\\"done\\\":true},\\\"settings\\\":{\\\"done\\\":true},\\\"ms-vscode-remote.remote-wsl#wslWalkthrough#explore.commands\\\":{\\\"done\\\":true},\\\"ms-toolsai.jupyter#jupyterWelcome#ipynb.newUntitledIpynb\\\":{\\\"done\\\":true},\\\"quickOpenWeb\\\":{\\\"done\\\":true}}\",\"snippets.usageTimestamps\":\"[[\\\"python.json/if(main)\\\",1701898276566],[\\\"python.json/async/def\\\",1707499559275],[\\\"python.json/def(abstract class method)\\\",1707843586846],[\\\"python.json/else\\\",1711551909138],[\\\"python.json/def(static class method)\\\",1711561020537],[\\\"python.json/def\\\",1711709400081],[\\\"python.json/def(class method)\\\",1711717425798],[\\\"python.json/for\\\",1712269054568],[\\\"python.json/try/except\\\",1712274814161]]\",\"memento/workbench.editor.keybindings\":\"{\\\"searchHistory\\\":[\\\"\\\\\\\"ctrl+shift+k\\\\\\\"\\\",\\\"Tra\\\",\\\"Transf\\\",\\\"\\\\\\\"ctrl+shift+u\\\\\\\"\\\",\\\"Tras\\\",\\\"Trasform\\\",\\\"Trasfo\\\",\\\"Trans\\\",\\\"Transfor\\\"]}\",\"tabs-list-width-horizontal\":\"167\",\"workbench.activityBar.location\":\"default\",\"expandSuggestionDocs\":\"false\",\"workbench.panel.testResults.state.hidden\":\"[{\\\"id\\\":\\\"workbench.panel.testResults.view\\\",\\\"isHidden\\\":false}]\",\"fileBasedRecommendations/promptedRecommendations\":\"{\\\"powershell\\\":[\\\"ms-vscode.powershell\\\"],\\\"yaml\\\":[\\\"github.vscode-github-actions\\\"],\\\"dockercompose\\\":[\\\"ms-azuretools.vscode-docker\\\"]}\",\"workbench.view.extension.PowerShell.state.hidden\":\"[{\\\"id\\\":\\\"PowerShellCommands\\\",\\\"isHidden\\\":false}]\",\"workbench.view.extension.github-actions.state.hidden\":\"[{\\\"id\\\":\\\"github-actions.current-branch\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github-actions.workflows\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github-actions.settings\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"github-actions.empty-view\\\",\\\"isHidden\\\":false}]\",\"timeline.excludeSources\":\"[\\\"timeline.localHistory\\\"]\",\"remote.tunnels.toRestore.ssh-remote+2kgbalapp3.-626202016\":\"[{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":34277,\\\"localPort\\\":34277,\\\"localAddress\\\":\\\"localhost:34277\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:34277\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":59678,\\\"localPort\\\":59678,\\\"localAddress\\\":\\\"localhost:59678\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:59678\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":33167,\\\"localPort\\\":33167,\\\"localAddress\\\":\\\"localhost:33167\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:33167\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":51216,\\\"localPort\\\":51216,\\\"localAddress\\\":\\\"localhost:51216\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:51216\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":51208,\\\"localPort\\\":51208,\\\"localAddress\\\":\\\"localhost:51208\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:51208\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":48712,\\\"localPort\\\":48712,\\\"localAddress\\\":\\\"localhost:48712\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:48712\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}},{\\\"remoteHost\\\":\\\"localhost\\\",\\\"remotePort\\\":48698,\\\"localPort\\\":48698,\\\"localAddress\\\":\\\"localhost:48698\\\",\\\"localUri\\\":{\\\"$mid\\\":1,\\\"path\\\":\\\"/\\\",\\\"scheme\\\":\\\"http\\\",\\\"authority\\\":\\\"localhost:48698\\\"},\\\"protocol\\\":\\\"http\\\",\\\"source\\\":{\\\"source\\\":1,\\\"description\\\":\\\"Auto Forwarded\\\"}}]\",\"remote.tunnels.toRestoreExpiration.ssh-remote+2kgbalapp3.-626202016\":\"1712603637740\",\"memento/notebookGettingStarted2\":\"{\\\"hasOpenedNotebook\\\":true}\",\"workbench.view.extension.jupyter-variables.state.hidden\":\"[{\\\"id\\\":\\\"jupyterViewVariables\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"cell-tag\\\",\\\"isHidden\\\":false}]\",\"workbench.view.extension.terraform.state.hidden\":\"[{\\\"id\\\":\\\"terraform.providers\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.modules\\\",\\\"isHidden\\\":false}]\",\"workbench.view.extension.terraform-cloud.state.hidden\":\"[{\\\"id\\\":\\\"terraform.cloud.workspaces\\\",\\\"isHidden\\\":false},{\\\"id\\\":\\\"terraform.cloud.runs\\\",\\\"isHidden\\\":false}]\",\"workbench.panel.chatSidebar.hidden\":\"[{\\\"id\\\":\\\"workbench.panel.chat.view.copilot\\\",\\\"isHidden\\\":false}]\"}}"} \ No newline at end of file diff --git a/scripts/resources/unreal/init_unreal.py b/scripts/resources/unreal/init_unreal.py new file mode 100644 index 00000000..937d8b86 --- /dev/null +++ b/scripts/resources/unreal/init_unreal.py @@ -0,0 +1,25 @@ +import os +import sys +import unreal +from pathlib import Path + +debug = os.environ.get('UNREAL_DEBUGGING_ON', 'no').lower() == 'yes' + +if debug: + try: + import debugpy + platform_folder = 'Linux' + if sys.platform == 'win32': + platform_folder = 'Win64' + + port = int(os.environ.get('UNREAL_DEBUG_PORT', 5679)) + python_exe_path = Path(sys.executable).parent.parent / 'ThirdParty' / 'Python3' / platform_folder / 'python' + debugpy.configure(python=os.environ.get('UNREAL_PYTHON_EXE', str(python_exe_path))) + debugpy.listen(port) + unreal.log(f'Waiting for debugger to attach on port {port}...') # type: ignore + debugpy.wait_for_client() + except ImportError: + unreal.log_error( # type: ignore + 'Failed to initialize debugger because debugpy is not available ' + 'in the current python environment.' + ) \ No newline at end of file diff --git a/send2ue/__init__.py b/src/addons/send2ue/__init__.py similarity index 100% rename from send2ue/__init__.py rename to src/addons/send2ue/__init__.py diff --git a/send2ue/constants.py b/src/addons/send2ue/constants.py similarity index 100% rename from send2ue/constants.py rename to src/addons/send2ue/constants.py diff --git a/send2ue/core/__init__.py b/src/addons/send2ue/core/__init__.py similarity index 100% rename from send2ue/core/__init__.py rename to src/addons/send2ue/core/__init__.py diff --git a/send2ue/core/export.py b/src/addons/send2ue/core/export.py similarity index 100% rename from send2ue/core/export.py rename to src/addons/send2ue/core/export.py diff --git a/send2ue/core/extension.py b/src/addons/send2ue/core/extension.py similarity index 100% rename from send2ue/core/extension.py rename to src/addons/send2ue/core/extension.py diff --git a/send2ue/core/formatting.py b/src/addons/send2ue/core/formatting.py similarity index 100% rename from send2ue/core/formatting.py rename to src/addons/send2ue/core/formatting.py diff --git a/send2ue/core/ingest.py b/src/addons/send2ue/core/ingest.py similarity index 100% rename from send2ue/core/ingest.py rename to src/addons/send2ue/core/ingest.py diff --git a/send2ue/core/io/__init__.py b/src/addons/send2ue/core/io/__init__.py similarity index 100% rename from send2ue/core/io/__init__.py rename to src/addons/send2ue/core/io/__init__.py diff --git a/send2ue/core/io/fbx_b3.py b/src/addons/send2ue/core/io/fbx_b3.py similarity index 100% rename from send2ue/core/io/fbx_b3.py rename to src/addons/send2ue/core/io/fbx_b3.py diff --git a/send2ue/core/io/fbx_b4.py b/src/addons/send2ue/core/io/fbx_b4.py similarity index 100% rename from send2ue/core/io/fbx_b4.py rename to src/addons/send2ue/core/io/fbx_b4.py diff --git a/send2ue/core/settings.py b/src/addons/send2ue/core/settings.py similarity index 100% rename from send2ue/core/settings.py rename to src/addons/send2ue/core/settings.py diff --git a/send2ue/core/utilities.py b/src/addons/send2ue/core/utilities.py similarity index 100% rename from send2ue/core/utilities.py rename to src/addons/send2ue/core/utilities.py diff --git a/send2ue/core/validations.py b/src/addons/send2ue/core/validations.py similarity index 100% rename from send2ue/core/validations.py rename to src/addons/send2ue/core/validations.py diff --git a/send2ue/dependencies/__init__.py b/src/addons/send2ue/dependencies/__init__.py similarity index 100% rename from send2ue/dependencies/__init__.py rename to src/addons/send2ue/dependencies/__init__.py diff --git a/send2ue/dependencies/remote_execution.py b/src/addons/send2ue/dependencies/remote_execution.py similarity index 100% rename from send2ue/dependencies/remote_execution.py rename to src/addons/send2ue/dependencies/remote_execution.py diff --git a/send2ue/dependencies/rpc/__init__.py b/src/addons/send2ue/dependencies/rpc/__init__.py similarity index 100% rename from send2ue/dependencies/rpc/__init__.py rename to src/addons/send2ue/dependencies/rpc/__init__.py diff --git a/send2ue/dependencies/rpc/base_server.py b/src/addons/send2ue/dependencies/rpc/base_server.py similarity index 96% rename from send2ue/dependencies/rpc/base_server.py rename to src/addons/send2ue/dependencies/rpc/base_server.py index 53d7ffb2..f795aca6 100644 --- a/send2ue/dependencies/rpc/base_server.py +++ b/src/addons/send2ue/dependencies/rpc/base_server.py @@ -110,10 +110,13 @@ def _dispatch(self, method, params): import traceback traceback.print_exc() - # dump the traceback to a file so that the client can read it. - os.makedirs(TRACEBACK_FILE.parent, exist_ok=True) - with open(TRACEBACK_FILE, 'w') as file: - file.write(f'Error from server:\n{traceback.format_exc()}') + try: + # dump the traceback to a file so that the client can read it. + os.makedirs(TRACEBACK_FILE.parent, exist_ok=True) + with open(TRACEBACK_FILE, 'w') as file: + file.write(f'Error from server:\n{traceback.format_exc()}') + except PermissionError: + pass raise error @@ -153,7 +156,6 @@ def __init__(self, name, port, is_thread=False): self.server.register_function(self.set_env) self.server.register_introspection_functions() self.server.register_multicall_functions() - logger.info(f'Started RPC server "{name}".') @staticmethod def is_running(): diff --git a/send2ue/dependencies/rpc/blender_server.py b/src/addons/send2ue/dependencies/rpc/blender_server.py similarity index 100% rename from send2ue/dependencies/rpc/blender_server.py rename to src/addons/send2ue/dependencies/rpc/blender_server.py diff --git a/send2ue/dependencies/rpc/client.py b/src/addons/send2ue/dependencies/rpc/client.py similarity index 94% rename from send2ue/dependencies/rpc/client.py rename to src/addons/send2ue/dependencies/rpc/client.py index 32838dd2..477d7a4f 100644 --- a/send2ue/dependencies/rpc/client.py +++ b/src/addons/send2ue/dependencies/rpc/client.py @@ -26,9 +26,12 @@ def __init__(self, *args, **kwargs): @staticmethod def _show_server_traceback() -> Optional[str]: - if TRACEBACK_FILE.exists(): - with open(TRACEBACK_FILE, 'r') as file: - logger.error(file.read()) + try: + if TRACEBACK_FILE.exists(): + with open(TRACEBACK_FILE, 'r') as file: + logger.error(file.read()) + except PermissionError: + pass @staticmethod def _get_built_in_exceptions() -> List: diff --git a/send2ue/dependencies/rpc/exceptions.py b/src/addons/send2ue/dependencies/rpc/exceptions.py similarity index 100% rename from send2ue/dependencies/rpc/exceptions.py rename to src/addons/send2ue/dependencies/rpc/exceptions.py diff --git a/send2ue/dependencies/rpc/factory.py b/src/addons/send2ue/dependencies/rpc/factory.py similarity index 100% rename from send2ue/dependencies/rpc/factory.py rename to src/addons/send2ue/dependencies/rpc/factory.py diff --git a/send2ue/dependencies/rpc/server.py b/src/addons/send2ue/dependencies/rpc/server.py similarity index 100% rename from send2ue/dependencies/rpc/server.py rename to src/addons/send2ue/dependencies/rpc/server.py diff --git a/send2ue/dependencies/rpc/unreal_server.py b/src/addons/send2ue/dependencies/rpc/unreal_server.py similarity index 100% rename from send2ue/dependencies/rpc/unreal_server.py rename to src/addons/send2ue/dependencies/rpc/unreal_server.py diff --git a/send2ue/dependencies/rpc/validations.py b/src/addons/send2ue/dependencies/rpc/validations.py similarity index 100% rename from send2ue/dependencies/rpc/validations.py rename to src/addons/send2ue/dependencies/rpc/validations.py diff --git a/send2ue/dependencies/unreal.py b/src/addons/send2ue/dependencies/unreal.py similarity index 100% rename from send2ue/dependencies/unreal.py rename to src/addons/send2ue/dependencies/unreal.py diff --git a/send2ue/operators.py b/src/addons/send2ue/operators.py similarity index 100% rename from send2ue/operators.py rename to src/addons/send2ue/operators.py diff --git a/send2ue/properties.py b/src/addons/send2ue/properties.py similarity index 100% rename from send2ue/properties.py rename to src/addons/send2ue/properties.py diff --git a/send2ue/release_notes.md b/src/addons/send2ue/release_notes.md similarity index 100% rename from send2ue/release_notes.md rename to src/addons/send2ue/release_notes.md diff --git a/send2ue/resources/extensions/affixes.py b/src/addons/send2ue/resources/extensions/affixes.py similarity index 100% rename from send2ue/resources/extensions/affixes.py rename to src/addons/send2ue/resources/extensions/affixes.py diff --git a/send2ue/resources/extensions/apply_groom_modifiers.py b/src/addons/send2ue/resources/extensions/apply_groom_modifiers.py similarity index 100% rename from send2ue/resources/extensions/apply_groom_modifiers.py rename to src/addons/send2ue/resources/extensions/apply_groom_modifiers.py diff --git a/send2ue/resources/extensions/combine_assets.py b/src/addons/send2ue/resources/extensions/combine_assets.py similarity index 100% rename from send2ue/resources/extensions/combine_assets.py rename to src/addons/send2ue/resources/extensions/combine_assets.py diff --git a/send2ue/resources/extensions/create_post_import_assets_for_groom.py b/src/addons/send2ue/resources/extensions/create_post_import_assets_for_groom.py similarity index 100% rename from send2ue/resources/extensions/create_post_import_assets_for_groom.py rename to src/addons/send2ue/resources/extensions/create_post_import_assets_for_groom.py diff --git a/send2ue/resources/extensions/instance_assets.py b/src/addons/send2ue/resources/extensions/instance_assets.py similarity index 100% rename from send2ue/resources/extensions/instance_assets.py rename to src/addons/send2ue/resources/extensions/instance_assets.py diff --git a/send2ue/resources/extensions/ue2rigify.py b/src/addons/send2ue/resources/extensions/ue2rigify.py similarity index 100% rename from send2ue/resources/extensions/ue2rigify.py rename to src/addons/send2ue/resources/extensions/ue2rigify.py diff --git a/send2ue/resources/extensions/use_collections_as_folders.py b/src/addons/send2ue/resources/extensions/use_collections_as_folders.py similarity index 100% rename from send2ue/resources/extensions/use_collections_as_folders.py rename to src/addons/send2ue/resources/extensions/use_collections_as_folders.py diff --git a/send2ue/resources/extensions/use_immediate_parent_name.py b/src/addons/send2ue/resources/extensions/use_immediate_parent_name.py similarity index 100% rename from send2ue/resources/extensions/use_immediate_parent_name.py rename to src/addons/send2ue/resources/extensions/use_immediate_parent_name.py diff --git a/send2ue/resources/setting_templates/default.json b/src/addons/send2ue/resources/setting_templates/default.json similarity index 100% rename from send2ue/resources/setting_templates/default.json rename to src/addons/send2ue/resources/setting_templates/default.json diff --git a/send2ue/resources/settings.json b/src/addons/send2ue/resources/settings.json similarity index 100% rename from send2ue/resources/settings.json rename to src/addons/send2ue/resources/settings.json diff --git a/send2ue/ui/__init__.py b/src/addons/send2ue/ui/__init__.py similarity index 100% rename from send2ue/ui/__init__.py rename to src/addons/send2ue/ui/__init__.py diff --git a/send2ue/ui/addon_preferences.py b/src/addons/send2ue/ui/addon_preferences.py similarity index 100% rename from send2ue/ui/addon_preferences.py rename to src/addons/send2ue/ui/addon_preferences.py diff --git a/send2ue/ui/dialog.py b/src/addons/send2ue/ui/dialog.py similarity index 100% rename from send2ue/ui/dialog.py rename to src/addons/send2ue/ui/dialog.py diff --git a/send2ue/ui/file_browser.py b/src/addons/send2ue/ui/file_browser.py similarity index 100% rename from send2ue/ui/file_browser.py rename to src/addons/send2ue/ui/file_browser.py diff --git a/send2ue/ui/header_menu.py b/src/addons/send2ue/ui/header_menu.py similarity index 100% rename from send2ue/ui/header_menu.py rename to src/addons/send2ue/ui/header_menu.py diff --git a/ue2rigify/__init__.py b/src/addons/ue2rigify/__init__.py similarity index 100% rename from ue2rigify/__init__.py rename to src/addons/ue2rigify/__init__.py diff --git a/ue2rigify/constants.py b/src/addons/ue2rigify/constants.py similarity index 100% rename from ue2rigify/constants.py rename to src/addons/ue2rigify/constants.py diff --git a/ue2rigify/core/__init__.py b/src/addons/ue2rigify/core/__init__.py similarity index 100% rename from ue2rigify/core/__init__.py rename to src/addons/ue2rigify/core/__init__.py diff --git a/ue2rigify/core/nodes.py b/src/addons/ue2rigify/core/nodes.py similarity index 100% rename from ue2rigify/core/nodes.py rename to src/addons/ue2rigify/core/nodes.py diff --git a/ue2rigify/core/scene.py b/src/addons/ue2rigify/core/scene.py similarity index 100% rename from ue2rigify/core/scene.py rename to src/addons/ue2rigify/core/scene.py diff --git a/ue2rigify/core/templates.py b/src/addons/ue2rigify/core/templates.py similarity index 100% rename from ue2rigify/core/templates.py rename to src/addons/ue2rigify/core/templates.py diff --git a/ue2rigify/core/utilities.py b/src/addons/ue2rigify/core/utilities.py similarity index 100% rename from ue2rigify/core/utilities.py rename to src/addons/ue2rigify/core/utilities.py diff --git a/ue2rigify/core/validations.py b/src/addons/ue2rigify/core/validations.py similarity index 100% rename from ue2rigify/core/validations.py rename to src/addons/ue2rigify/core/validations.py diff --git a/ue2rigify/operators.py b/src/addons/ue2rigify/operators.py similarity index 100% rename from ue2rigify/operators.py rename to src/addons/ue2rigify/operators.py diff --git a/ue2rigify/properties.py b/src/addons/ue2rigify/properties.py similarity index 100% rename from ue2rigify/properties.py rename to src/addons/ue2rigify/properties.py diff --git a/ue2rigify/release_notes.md b/src/addons/ue2rigify/release_notes.md similarity index 100% rename from ue2rigify/release_notes.md rename to src/addons/ue2rigify/release_notes.md diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/control_metadata.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/control_metadata.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/control_metadata.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/control_metadata.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/metarig.py diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE4/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/metarig.py diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/female_mannequin_UE5/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/metarig.py diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE4/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/metarig.py diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b3_6/male_mannequin_UE5/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/metarig.py diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE4/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/metarig.py diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/female_mannequin_UE5/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/metarig.py diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE4/source_to_deform_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/fk_to_source_nodes.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/metarig.py b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/metarig.py similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/metarig.py rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/metarig.py diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_links.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_links.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_links.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_links.json diff --git a/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_nodes.json b/src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_nodes.json similarity index 100% rename from ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_nodes.json rename to src/addons/ue2rigify/resources/rig_templates/b4_0/male_mannequin_UE5/source_to_deform_nodes.json diff --git a/ue2rigify/settings/__init__.py b/src/addons/ue2rigify/settings/__init__.py similarity index 100% rename from ue2rigify/settings/__init__.py rename to src/addons/ue2rigify/settings/__init__.py diff --git a/ue2rigify/settings/tool_tips.py b/src/addons/ue2rigify/settings/tool_tips.py similarity index 100% rename from ue2rigify/settings/tool_tips.py rename to src/addons/ue2rigify/settings/tool_tips.py diff --git a/ue2rigify/settings/viewport_settings.py b/src/addons/ue2rigify/settings/viewport_settings.py similarity index 100% rename from ue2rigify/settings/viewport_settings.py rename to src/addons/ue2rigify/settings/viewport_settings.py diff --git a/ue2rigify/ui/__init__.py b/src/addons/ue2rigify/ui/__init__.py similarity index 100% rename from ue2rigify/ui/__init__.py rename to src/addons/ue2rigify/ui/__init__.py diff --git a/ue2rigify/ui/addon_preferences.py b/src/addons/ue2rigify/ui/addon_preferences.py similarity index 100% rename from ue2rigify/ui/addon_preferences.py rename to src/addons/ue2rigify/ui/addon_preferences.py diff --git a/ue2rigify/ui/exporter.py b/src/addons/ue2rigify/ui/exporter.py similarity index 100% rename from ue2rigify/ui/exporter.py rename to src/addons/ue2rigify/ui/exporter.py diff --git a/ue2rigify/ui/node_editor.py b/src/addons/ue2rigify/ui/node_editor.py similarity index 100% rename from ue2rigify/ui/node_editor.py rename to src/addons/ue2rigify/ui/node_editor.py diff --git a/ue2rigify/ui/view_3d.py b/src/addons/ue2rigify/ui/view_3d.py similarity index 100% rename from ue2rigify/ui/view_3d.py rename to src/addons/ue2rigify/ui/view_3d.py diff --git a/tests/run_tests.py b/tests/run_tests.py index 69a8b764..5f726132 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -3,9 +3,12 @@ import os import sys import logging +DOCKER_ENVIRONMENT = os.environ.get('DOCKER_ENVIRONMENT', 'no').lower() == 'yes' +if DOCKER_ENVIRONMENT: + os.environ['TEST_ENVIRONMENT'] = '1' # adds the rpc module to the path -sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'send2ue', 'dependencies')) +sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'src', 'addons', 'send2ue', 'dependencies')) from utils.addon_packager import AddonPackager from utils.container_test_manager import ContainerTestManager @@ -18,11 +21,10 @@ # switch ports depending on whether in test environment or not BLENDER_PORT = os.environ.get('BLENDER_PORT', '9997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '9998') -if os.environ.get('TEST_ENVIRONMENT'): +if DOCKER_ENVIRONMENT: BLENDER_PORT = os.environ.get('BLENDER_PORT', '8997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '8998') -TEST_ENVIRONMENT = os.environ.get('TEST_ENVIRONMENT') HOST_REPO_FOLDER = os.environ.get('HOST_REPO_FOLDER', os.path.normpath(os.path.join(os.getcwd(), os.pardir))) CONTAINER_REPO_FOLDER = os.environ.get('CONTAINER_REPO_FOLDER', '/tmp/blender_tools/') HOST_TEST_FOLDER = os.environ.get('HOST_TEST_FOLDER', os.getcwd()) @@ -48,19 +50,19 @@ 'RPC_TRACEBACK_FILE': '/tmp/blender/send2ue/data/traceback.log', 'RPC_TIME_OUT': '60' } - # add the test environment variable if specified - if TEST_ENVIRONMENT: - os.environ['TEST_ENVIRONMENT'] = TEST_ENVIRONMENT - os.environ['RPC_TRACEBACK_FILE'] = os.path.join(HOST_TEST_FOLDER, 'data', 'traceback.log') - # make sure this is set in the current environment os.environ.update(environment) + # add the test environment variable if specified + if DOCKER_ENVIRONMENT: + os.environ['TEST_ENVIRONMENT'] = '1' + os.environ['RPC_TRACEBACK_FILE'] = os.path.join(HOST_TEST_FOLDER, 'data', 'traceback.log') + # zip and copy addons into release folder - if TEST_ENVIRONMENT: + if DOCKER_ENVIRONMENT: # copy each addons code into the test directory for addon_name in list(filter(None, os.environ.get('BLENDER_ADDONS', '').split(','))): - addon_folder_path = os.path.join(HOST_REPO_FOLDER, addon_name) + addon_folder_path = os.path.join(HOST_REPO_FOLDER, 'src', 'addons', addon_name) addon_packager = AddonPackager(addon_name, addon_folder_path, os.path.join(HOST_REPO_FOLDER, 'release')) addon_packager.zip_addon() @@ -93,7 +95,7 @@ '--python-exit-code', '1', '--python', - '/tmp/blender_tools/send2ue/dependencies/rpc/server.py', + '/tmp/blender_tools/src/addons/send2ue/dependencies/rpc/server.py', ] }, 'unreal': { @@ -118,7 +120,7 @@ '-nosplash', '-noloadstartuppackages' '-log', - '-ExecutePythonScript=/tmp/blender_tools/send2ue/dependencies/rpc/server.py', + '-ExecutePythonScript=/tmp/blender_tools/src/addons/send2ue/dependencies/rpc/server.py', ], 'auth_config': { 'username': os.environ.get('GITHUB_USERNAME'), @@ -132,10 +134,10 @@ exclusive_test_files=EXCLUSIVE_TEST_FILES, exclusive_tests=EXCLUSIVE_TESTS, ) - if TEST_ENVIRONMENT: + if DOCKER_ENVIRONMENT: container_test_manager.start() container_test_manager.run_test_cases() - if TEST_ENVIRONMENT and os.environ.get('REMOVE_CONTAINERS', '').lower() != 'false': + if DOCKER_ENVIRONMENT and os.environ.get('REMOVE_CONTAINERS', '').lower() != 'false': container_test_manager.stop() diff --git a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini index ad7e369c..82caa850 100644 --- a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini +++ b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini @@ -44,7 +44,7 @@ ManualIPAddress= [/Script/PythonScriptPlugin.PythonScriptPluginSettings] bRemoteExecution=True -RemoteExecutionMulticastBindAddress=0.0.0.0 +RemoteExecutionMulticastBindAddress=127.0.0.1 [/Script/WindowsTargetPlatform.WindowsTargetSettings] DefaultGraphicsRHI=DefaultGraphicsRHI_Default diff --git a/tests/utils/base_test_case.py b/tests/utils/base_test_case.py index 4df86dc5..b6c12027 100644 --- a/tests/utils/base_test_case.py +++ b/tests/utils/base_test_case.py @@ -14,12 +14,14 @@ def __init__(self, *args, **kwargs): self.test_environment = os.environ.get('TEST_ENVIRONMENT') self.test_folder = os.environ.get('HOST_TEST_FOLDER') self.repo_folder = os.environ.get('HOST_REPO_FOLDER') + sys.path.append(os.path.join(self.repo_folder, 'src', 'addons')) if self.test_environment: self.test_folder = os.environ.get('CONTAINER_TEST_FOLDER') self.repo_folder = os.environ.get('CONTAINER_REPO_FOLDER') + sys.path.append(f'{self.repo_folder}/src/addons') + self.addons_folder = os.path.join(self.repo_folder, 'release') - sys.path.append(self.repo_folder) import send2ue import ue2rigify self.send2ue = send2ue @@ -38,7 +40,7 @@ def setUp(self): else: self.blender.open_default() - if os.environ.get('TEST_ENVIRONMENT'): + if self.test_environment: self.blender.install_addons(self.repo_folder, self.blender_addons) self.blender.send2ue_setup_project() else: diff --git a/tests/utils/blender.py b/tests/utils/blender.py index 567e4f4f..b6987537 100644 --- a/tests/utils/blender.py +++ b/tests/utils/blender.py @@ -47,7 +47,7 @@ def install_addons(repo_folder, addons): Installs the given addons from the release folder. """ for addon in addons: - addon_folder_path = os.path.join(repo_folder, addon) + addon_folder_path = os.path.join(repo_folder, 'src', 'addons', addon) release_folder = os.path.join(repo_folder, 'release') addon_packager = AddonPackager(addon, addon_folder_path, release_folder) addon_packager.install_addon() From bbe4c870fe89db043309b24b82880d0176c99267 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Thu, 11 Jul 2024 14:55:05 -0500 Subject: [PATCH 02/10] reverted TEST_ENVIRONMENT variable --- tests/run_tests.py | 13 +++++++++---- tests/utils/base_test_case.py | 2 +- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/tests/run_tests.py b/tests/run_tests.py index 5f726132..107c0a96 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -21,10 +21,11 @@ # switch ports depending on whether in test environment or not BLENDER_PORT = os.environ.get('BLENDER_PORT', '9997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '9998') -if DOCKER_ENVIRONMENT: +if os.environ.get('TEST_ENVIRONMENT'): BLENDER_PORT = os.environ.get('BLENDER_PORT', '8997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '8998') +TEST_ENVIRONMENT = os.environ.get('TEST_ENVIRONMENT') HOST_REPO_FOLDER = os.environ.get('HOST_REPO_FOLDER', os.path.normpath(os.path.join(os.getcwd(), os.pardir))) CONTAINER_REPO_FOLDER = os.environ.get('CONTAINER_REPO_FOLDER', '/tmp/blender_tools/') HOST_TEST_FOLDER = os.environ.get('HOST_TEST_FOLDER', os.getcwd()) @@ -59,7 +60,7 @@ os.environ['RPC_TRACEBACK_FILE'] = os.path.join(HOST_TEST_FOLDER, 'data', 'traceback.log') # zip and copy addons into release folder - if DOCKER_ENVIRONMENT: + if TEST_ENVIRONMENT: # copy each addons code into the test directory for addon_name in list(filter(None, os.environ.get('BLENDER_ADDONS', '').split(','))): addon_folder_path = os.path.join(HOST_REPO_FOLDER, 'src', 'addons', addon_name) @@ -134,10 +135,14 @@ exclusive_test_files=EXCLUSIVE_TEST_FILES, exclusive_tests=EXCLUSIVE_TESTS, ) - if DOCKER_ENVIRONMENT: + if TEST_ENVIRONMENT: + # remove existing containers first + if os.environ.get('REMOVE_CONTAINERS', '').lower() != 'false': + container_test_manager.stop() + container_test_manager.start() container_test_manager.run_test_cases() - if DOCKER_ENVIRONMENT and os.environ.get('REMOVE_CONTAINERS', '').lower() != 'false': + if TEST_ENVIRONMENT and os.environ.get('REMOVE_CONTAINERS', '').lower() != 'false': container_test_manager.stop() diff --git a/tests/utils/base_test_case.py b/tests/utils/base_test_case.py index b6c12027..fe86ac10 100644 --- a/tests/utils/base_test_case.py +++ b/tests/utils/base_test_case.py @@ -40,7 +40,7 @@ def setUp(self): else: self.blender.open_default() - if self.test_environment: + if os.environ.get('TEST_ENVIRONMENT'): self.blender.install_addons(self.repo_folder, self.blender_addons) self.blender.send2ue_setup_project() else: From 03e890ccab6a0bc06fc6ac2c5377cc66ac8ca0c8 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Sat, 13 Jul 2024 16:19:44 -0500 Subject: [PATCH 03/10] Added debug configuration for containers and remove chmod command from addons code --- .vscode/launch.json | 54 ++++++++++++++++-- .vscode/python.env | 2 +- scripts/resources/blender/startup.py | 25 +++++++-- scripts/resources/unreal/init_unreal.py | 31 +++++++++-- src/addons/send2ue/core/settings.py | 4 ++ src/addons/send2ue/core/utilities.py | 19 +++---- .../send2ue/dependencies/remote_execution.py | 2 +- .../send2ue/dependencies/rpc/base_server.py | 3 +- src/addons/ue2rigify/__init__.py | 18 +++--- src/addons/ue2rigify/core/templates.py | 30 +++------- tests/run_tests.py | 55 +++++++++++-------- .../test01/Config/DefaultEngine.ini | 2 +- tests/utils/base_test_case.py | 1 - tests/utils/container_test_manager.py | 6 +- 14 files changed, 166 insertions(+), 86 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index bd592db1..6bbc4344 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -28,7 +28,7 @@ } }, { - "name": "Run All Tests", + "name": "Debug Tests: Host Environment", "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/tests/run_tests.py", @@ -37,11 +37,12 @@ "cwd": "${workspaceFolder}/tests", "justMyCode": false, "env": { - "DOCKER_ENVIRONMENT": "${input:docker-environment}" + "EXCLUSIVE_TEST_FILES": "${input:test-file}", + "EXCLUSIVE_TESTS": "${input:test-name}", } }, { - "name": "Run Specific Tests", + "name": "Debug Tests: Docker Environment", "type": "debugpy", "request": "launch", "program": "${workspaceFolder}/tests/run_tests.py", @@ -50,18 +51,50 @@ "cwd": "${workspaceFolder}/tests", "justMyCode": false, "env": { + "DOCKER_ENVIRONMENT": "yes", "EXCLUSIVE_TEST_FILES": "${input:test-file}", "EXCLUSIVE_TESTS": "${input:test-name}", - "DOCKER_ENVIRONMENT": "${input:docker-environment}" + "DEBUGGING_ON": "${input:debugging-on}" } }, + { + "name": "Python Debugger: Attach Blender Docker", + "type": "debugpy", + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}/", + "remoteRoot": "/tmp/blender_tools/" + } + ], + "connect": { + "host": "localhost", + "port": 5668 + } + }, + { + "name": "Python Debugger: Attach Unreal Docker", + "type": "debugpy", + "request": "attach", + "pathMappings": [ + { + "localRoot": "${workspaceFolder}/", + "remoteRoot": "/tmp/blender_tools/" + } + ], + "connect": { + "host": "localhost", + "port": 5669 + } + } ], "inputs": [ { "id": "test-file", "type": "pickString", - "default": "test_send2ue_core.py", + "default": "all", "options": [ + "all", "test_send2ue_core.py", "test_send2ue_cubes.py", "test_send2ue_extension_affixes.py", @@ -90,7 +123,18 @@ { "id": "test-name", "type": "promptString", + "default": "all", "description": "The name of the specific test to run" + }, + { + "id": "debugging-on", + "type": "pickString", + "default": "", + "options": [ + "no", + "yes" + ], + "description": "Turn on remote debugging in the containers?" } ] } \ No newline at end of file diff --git a/.vscode/python.env b/.vscode/python.env index 4e6951a4..a888ff76 100644 --- a/.vscode/python.env +++ b/.vscode/python.env @@ -1,2 +1,2 @@ -PYTHONPATH=';./.venv/Lib/site-packages' +PYTHONPATH=';./.venv/Lib/site-packages;./src/addons;./src/addons/send2ue/dependencies' PYTHONUNBUFFERED=1 \ No newline at end of file diff --git a/scripts/resources/blender/startup.py b/scripts/resources/blender/startup.py index a15c6a74..70d8b3cb 100644 --- a/scripts/resources/blender/startup.py +++ b/scripts/resources/blender/startup.py @@ -8,11 +8,12 @@ logging.basicConfig(level=logging.DEBUG) +REPO_ROOT = Path(__file__).parent.parent.parent.parent debug = os.environ.get('BLENDER_DEBUGGING_ON', 'no').lower() == 'yes' ADDONS = { - 'send2ue': Path(__file__).parent.parent.parent.parent / 'src', - 'ue2rigify': Path(__file__).parent.parent.parent.parent / 'src' + 'send2ue': REPO_ROOT / 'src', + 'ue2rigify': REPO_ROOT / 'src' } @@ -28,6 +29,7 @@ script_directory = bpy.context.preferences.filepaths.script_directories.new() script_directory.name = name # type: ignore script_directory.directory = str(scripts_folder) # type: ignore + sys.path.append(os.path.join(str(scripts_folder), 'addons')) try: bpy.ops.script.reload() @@ -46,11 +48,26 @@ import debugpy port = int(os.environ.get('BLENDER_DEBUG_PORT', 5678)) debugpy.configure(python=os.environ.get('BLENDER_PYTHON_EXE', sys.executable)) - debugpy.listen(port) + if os.environ.get('TEST_ENVIRONMENT'): + debugpy.listen(('0.0.0.0', port)) + else: + debugpy.listen(port) logger.info(f'Waiting for debugger to attach on port {port}...') debugpy.wait_for_client() except ImportError: logger.error( 'Failed to initialize debugger because debugpy is not available ' 'in the current python environment.' - ) \ No newline at end of file + ) + + +if os.environ.get('TEST_ENVIRONMENT'): + from send2ue.dependencies.rpc.server import RPCServer + rpc_server = RPCServer() + rpc_server.start_server_blocking() +else: + from send2ue.dependencies.rpc import blender_server + rpc_server = blender_server.RPCServer() + rpc_server.start(threaded=True) + +print('Blender RPC Server started') \ No newline at end of file diff --git a/scripts/resources/unreal/init_unreal.py b/scripts/resources/unreal/init_unreal.py index 937d8b86..0fb7eb21 100644 --- a/scripts/resources/unreal/init_unreal.py +++ b/scripts/resources/unreal/init_unreal.py @@ -3,23 +3,44 @@ import unreal from pathlib import Path +REPO_ROOT = os.environ.get('CONTAINER_REPO_FOLDER') +if REPO_ROOT: + REPO_ROOT = Path(REPO_ROOT) +else: + REPO_ROOT = Path(__file__).parent.parent.parent.parent + debug = os.environ.get('UNREAL_DEBUGGING_ON', 'no').lower() == 'yes' if debug: try: import debugpy - platform_folder = 'Linux' + python_exe_path = Path(sys.executable).parent.parent / 'ThirdParty' / 'Python3' / 'Linux' / 'bin' / 'python3' if sys.platform == 'win32': - platform_folder = 'Win64' + python_exe_path = Path(sys.executable).parent.parent / 'ThirdParty' / 'Python3' / 'Win64' / 'python' port = int(os.environ.get('UNREAL_DEBUG_PORT', 5679)) - python_exe_path = Path(sys.executable).parent.parent / 'ThirdParty' / 'Python3' / platform_folder / 'python' debugpy.configure(python=os.environ.get('UNREAL_PYTHON_EXE', str(python_exe_path))) - debugpy.listen(port) + if os.environ.get('TEST_ENVIRONMENT'): + debugpy.listen(('0.0.0.0', port)) + else: + debugpy.listen(port) + unreal.log(f'Waiting for debugger to attach on port {port}...') # type: ignore debugpy.wait_for_client() except ImportError: unreal.log_error( # type: ignore 'Failed to initialize debugger because debugpy is not available ' 'in the current python environment.' - ) \ No newline at end of file + ) + +sys.path.append(str(REPO_ROOT / 'src' / 'addons'/ 'send2ue' / 'dependencies')) +if os.environ.get('TEST_ENVIRONMENT'): + from rpc.server import RPCServer + rpc_server = RPCServer() + rpc_server.start_server_blocking() +else: + from rpc import unreal_server + rpc_server = unreal_server.RPCServer() + rpc_server.start(threaded=True) + +print('Unreal Engine RPC Server started') diff --git a/src/addons/send2ue/core/settings.py b/src/addons/send2ue/core/settings.py index 896a97f5..01020690 100644 --- a/src/addons/send2ue/core/settings.py +++ b/src/addons/send2ue/core/settings.py @@ -124,6 +124,10 @@ def get_template_folder(): :return str: The file path to the temp folder. """ + template_folder = os.environ.get('SEND2UE_TEMPLATE_FOLDER') + if template_folder: + return template_folder + return os.path.join( tempfile.gettempdir(), ToolInfo.APP.value, diff --git a/src/addons/send2ue/core/utilities.py b/src/addons/send2ue/core/utilities.py index 1fc50ddc..e83ef26c 100644 --- a/src/addons/send2ue/core/utilities.py +++ b/src/addons/send2ue/core/utilities.py @@ -881,17 +881,12 @@ def remove_from_disk(path, directory=False): :param str path: An file path. :param bool directory: Whether or not the path is a directory. - """ - try: - original_umask = os.umask(0) - if os.path.exists(path): - os.chmod(path, 0o777) - if directory: - shutil.rmtree(path) - else: - os.remove(path) - finally: - os.umask(original_umask) + """ + if os.path.exists(path): + if directory: + shutil.rmtree(path, ignore_errors=True) + else: + os.remove(path) def remove_temp_folder(): @@ -911,7 +906,7 @@ def remove_temp_data(): """ temp_folder = get_temp_folder() if os.path.exists(temp_folder): - shutil.rmtree(temp_folder) + shutil.rmtree(temp_folder, ignore_errors=True) def remove_unpacked_files(unpacked_files): diff --git a/src/addons/send2ue/dependencies/remote_execution.py b/src/addons/send2ue/dependencies/remote_execution.py index 61026038..bfdf70f8 100644 --- a/src/addons/send2ue/dependencies/remote_execution.py +++ b/src/addons/send2ue/dependencies/remote_execution.py @@ -7,7 +7,6 @@ import socket as _socket import logging as _logging import threading as _threading -import bpy # Protocol constants (see PythonScriptRemoteExecution.cpp for the full protocol definition) _PROTOCOL_VERSION = 1 # Protocol version number @@ -34,6 +33,7 @@ class RemoteExecutionConfig(object): Configuration data for establishing a remote connection with a Unreal Editor instance running Python. ''' def __init__(self): + import bpy # The multicast group endpoint tuple that the UDP multicast socket should join (must match the "Multicast Group Endpoint" setting in the Python plugin) self.multicast_ttl = bpy.context.preferences.addons["send2ue"].preferences.multicast_ttl diff --git a/src/addons/send2ue/dependencies/rpc/base_server.py b/src/addons/send2ue/dependencies/rpc/base_server.py index f795aca6..311c8010 100644 --- a/src/addons/send2ue/dependencies/rpc/base_server.py +++ b/src/addons/send2ue/dependencies/rpc/base_server.py @@ -35,8 +35,7 @@ def run_in_main_thread(callable_instance, *args): globals().pop(ERROR_VALUE_NAME, None) EXECUTION_QUEUE.put((callable_instance, args)) - start_time = time.time() - while time.time() - start_time < timeout: + for attempt in range(timeout * 10): if RETURN_VALUE_NAME in globals(): return globals().get(RETURN_VALUE_NAME) elif ERROR_VALUE_NAME in globals(): diff --git a/src/addons/ue2rigify/__init__.py b/src/addons/ue2rigify/__init__.py index ad724861..fa489632 100644 --- a/src/addons/ue2rigify/__init__.py +++ b/src/addons/ue2rigify/__init__.py @@ -65,10 +65,14 @@ def unregister(): """ Unregisters the addon classes when the addon is disabled. """ - nodes.remove_pie_menu_hot_keys() - node_editor.unregister() - nodes.unregister() - operators.unregister() - view_3d.unregister() - addon_preferences.unregister() - properties.unregister() + try: + nodes.remove_pie_menu_hot_keys() + node_editor.unregister() + nodes.unregister() + operators.unregister() + view_3d.unregister() + addon_preferences.unregister() + properties.unregister() + except Exception as e: + print(f"Error un-registering UE2Rigify: \n{e}") + diff --git a/src/addons/ue2rigify/core/templates.py b/src/addons/ue2rigify/core/templates.py index 541aa0c1..53fbb8e1 100644 --- a/src/addons/ue2rigify/core/templates.py +++ b/src/addons/ue2rigify/core/templates.py @@ -309,11 +309,7 @@ def create_template_folder(template_name, properties): # create the template folder template_path = os.path.join(Template.RIG_TEMPLATES_PATH(), template_name) if not os.path.exists(template_path): - try: - original_umask = os.umask(0) - os.makedirs(template_path, 0o777) - finally: - os.umask(original_umask) + os.makedirs(template_path, exist_ok=True) # keep checking the os file system till the new folder exists while not os.path.exists(template_path): @@ -361,15 +357,9 @@ def save_text_file(data, file_path): :param str file_path: The full file path to where the file will be saved. """ if '.py' in os.path.basename(file_path): - try: - original_umask = os.umask(0) - if os.path.exists(file_path): - os.chmod(file_path, 0o777) - file = open(file_path, 'w+') - finally: - os.umask(original_umask) - file.write(data) - file.close() + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, 'w+') as file: + file.write(data) def save_json_file(data, file_path): @@ -380,15 +370,9 @@ def save_json_file(data, file_path): :param str file_path: The full file path to where the file will be saved. """ if '.json' in os.path.basename(file_path): - try: - original_umask = os.umask(0) - if os.path.exists(file_path): - os.chmod(file_path, 0o777) - file = open(file_path, 'w+') - finally: - os.umask(original_umask) - json.dump(data, file, indent=1) - file.close() + os.makedirs(os.path.dirname(file_path), exist_ok=True) + with open(file_path, 'w+') as file: + json.dump(data, file, indent=1) def save_constraints(constraints_data, properties): diff --git a/tests/run_tests.py b/tests/run_tests.py index 107c0a96..7aec939f 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -3,6 +3,7 @@ import os import sys import logging +DEBUGGING_ON = os.environ.get('DEBUGGING_ON', 'no').lower() == 'yes' DOCKER_ENVIRONMENT = os.environ.get('DOCKER_ENVIRONMENT', 'no').lower() == 'yes' if DOCKER_ENVIRONMENT: os.environ['TEST_ENVIRONMENT'] = '1' @@ -10,7 +11,6 @@ # adds the rpc module to the path sys.path.append(os.path.join(os.path.dirname(__file__), os.path.pardir, 'src', 'addons', 'send2ue', 'dependencies')) -from utils.addon_packager import AddonPackager from utils.container_test_manager import ContainerTestManager BLENDER_ADDONS = os.environ.get('BLENDER_ADDONS', 'send2ue,ue2rigify') @@ -21,6 +21,8 @@ # switch ports depending on whether in test environment or not BLENDER_PORT = os.environ.get('BLENDER_PORT', '9997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '9998') +BLENDER_CONTAINER_DEBUG_PORT = os.environ.get('BLENDER_DEBUG_PORT', '5668') +UNREAL_CONTAINER_DEBUG_PORT = os.environ.get('UNREAL_DEBUG_PORT', '5669') if os.environ.get('TEST_ENVIRONMENT'): BLENDER_PORT = os.environ.get('BLENDER_PORT', '8997') UNREAL_PORT = os.environ.get('UNREAL_PORT', '8998') @@ -30,9 +32,13 @@ CONTAINER_REPO_FOLDER = os.environ.get('CONTAINER_REPO_FOLDER', '/tmp/blender_tools/') HOST_TEST_FOLDER = os.environ.get('HOST_TEST_FOLDER', os.getcwd()) CONTAINER_TEST_FOLDER = os.environ.get('CONTAINER_TEST_FOLDER', f'{CONTAINER_REPO_FOLDER}tests') +ALWAYS_PULL = bool(int(os.environ.get('ALWAYS_PULL', '0'))) EXCLUSIVE_TEST_FILES = list(filter(None, os.environ.get('EXCLUSIVE_TEST_FILES', '').split(','))) or None +if EXCLUSIVE_TEST_FILES == ['all']: + EXCLUSIVE_TEST_FILES = [] EXCLUSIVE_TESTS = list(filter(None, os.environ.get('EXCLUSIVE_TESTS', '').split(','))) or None -ALWAYS_PULL = bool(int(os.environ.get('ALWAYS_PULL', '0'))) +if EXCLUSIVE_TESTS == ['all']: + EXCLUSIVE_TESTS = [] if __name__ == '__main__': @@ -40,32 +46,34 @@ environment = { 'SEND2UE_DEV': '1', 'UE2RIGIFY_DEV': '1', + 'SEND2UE_TEMPLATE_FOLDER': '/tmp', + 'UE_PYTHONPATH': '$UE_PYTHONPATH:/tmp/blender_tools/scripts/resources/unreal', + 'BLENDER_DEBUG_PORT': BLENDER_CONTAINER_DEBUG_PORT, + 'UNREAL_DEBUG_PORT': UNREAL_CONTAINER_DEBUG_PORT, 'BLENDER_ADDONS': BLENDER_ADDONS, 'BLENDER_PORT': BLENDER_PORT, 'BLENDER_VERSION': BLENDER_VERSION, + 'UNREAL_VERSION': UNREAL_VERSION, 'UNREAL_PORT': UNREAL_PORT, 'HOST_REPO_FOLDER': HOST_REPO_FOLDER, 'CONTAINER_REPO_FOLDER': CONTAINER_REPO_FOLDER, 'HOST_TEST_FOLDER': HOST_TEST_FOLDER, 'CONTAINER_TEST_FOLDER': CONTAINER_TEST_FOLDER, 'RPC_TRACEBACK_FILE': '/tmp/blender/send2ue/data/traceback.log', - 'RPC_TIME_OUT': '60' + 'RPC_TIME_OUT': '120' } # make sure this is set in the current environment os.environ.update(environment) # add the test environment variable if specified if DOCKER_ENVIRONMENT: - os.environ['TEST_ENVIRONMENT'] = '1' os.environ['RPC_TRACEBACK_FILE'] = os.path.join(HOST_TEST_FOLDER, 'data', 'traceback.log') - - # zip and copy addons into release folder - if TEST_ENVIRONMENT: - # copy each addons code into the test directory - for addon_name in list(filter(None, os.environ.get('BLENDER_ADDONS', '').split(','))): - addon_folder_path = os.path.join(HOST_REPO_FOLDER, 'src', 'addons', addon_name) - addon_packager = AddonPackager(addon_name, addon_folder_path, os.path.join(HOST_REPO_FOLDER, 'release')) - addon_packager.zip_addon() + if DEBUGGING_ON: + environment['TEST_ENVIRONMENT'] = '1' + environment['BLENDER_DEBUGGING_ON'] = 'yes' + environment['UNREAL_DEBUGGING_ON'] = 'yes' + environment['BLENDER_DEBUG_PORT'] = BLENDER_CONTAINER_DEBUG_PORT + environment['UNREAL_DEBUG_PORT'] = UNREAL_CONTAINER_DEBUG_PORT # define the additional volume paths # this is the temp data location where send2ue export/imports data @@ -85,8 +93,9 @@ 'always_pull': ALWAYS_PULL, 'tag': f'blender-linux:{BLENDER_VERSION}', 'repository': 'ghcr.io/poly-hammer', - 'user': 'root', + 'user': 'ubuntu', 'rpc_port': BLENDER_PORT, + 'debug_port': BLENDER_CONTAINER_DEBUG_PORT, 'environment': environment, 'volumes': volumes, 'command': [ @@ -96,32 +105,32 @@ '--python-exit-code', '1', '--python', - '/tmp/blender_tools/src/addons/send2ue/dependencies/rpc/server.py', + '/tmp/blender_tools/scripts/resources/blender/startup.py', ] }, 'unreal': { 'refresh': False, 'always_pull': ALWAYS_PULL, 'rpc_port': UNREAL_PORT, + 'debug_port': UNREAL_CONTAINER_DEBUG_PORT, 'environment': environment, 'volumes': volumes, - # 'tag': 'unreal-linux:5.4', - # 'repository': 'ghcr.io/poly-hammer', - 'tag': f'unreal-engine:dev-slim-{UNREAL_VERSION}', - 'repository': 'ghcr.io/epicgames', + 'tag': f'unreal-linux:{UNREAL_VERSION}', + 'repository': 'ghcr.io/poly-hammer', + # 'tag': f'unreal-engine:dev-slim-{UNREAL_VERSION}', + # 'repository': 'ghcr.io/epicgames', 'user': 'ue4', 'command': [ '/home/ue4/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd', - # '/tmp/unreal_projects/test01/test01.uproject', - f'{CONTAINER_TEST_FOLDER}/test_files/unreal_projects/test01/test01.uproject', + '-nullrhi', + '/tmp/unreal_projects/test01.uproject', '-stdout', '-unattended', '-nopause', - '-nullrhi', '-nosplash', - '-noloadstartuppackages' + '-noloadstartuppackages', '-log', - '-ExecutePythonScript=/tmp/blender_tools/src/addons/send2ue/dependencies/rpc/server.py', + # '-ExecutePythonScript=/tmp/blender_tools/scripts/resources/unreal/init_unreal.py', ], 'auth_config': { 'username': os.environ.get('GITHUB_USERNAME'), diff --git a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini index 82caa850..8680d260 100644 --- a/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini +++ b/tests/test_files/unreal_projects/test01/Config/DefaultEngine.ini @@ -47,7 +47,7 @@ bRemoteExecution=True RemoteExecutionMulticastBindAddress=127.0.0.1 [/Script/WindowsTargetPlatform.WindowsTargetSettings] -DefaultGraphicsRHI=DefaultGraphicsRHI_Default +DefaultGraphicsRHI=DefaultGraphicsRHI_DX11 -D3D12TargetedShaderFormats=PCD3D_SM5 +D3D12TargetedShaderFormats=PCD3D_SM5 +D3D12TargetedShaderFormats=PCD3D_SM6 diff --git a/tests/utils/base_test_case.py b/tests/utils/base_test_case.py index fe86ac10..912de1bd 100644 --- a/tests/utils/base_test_case.py +++ b/tests/utils/base_test_case.py @@ -41,7 +41,6 @@ def setUp(self): self.blender.open_default() if os.environ.get('TEST_ENVIRONMENT'): - self.blender.install_addons(self.repo_folder, self.blender_addons) self.blender.send2ue_setup_project() else: for addon_name in self.blender_addons: diff --git a/tests/utils/container_test_manager.py b/tests/utils/container_test_manager.py index e376807a..5c51442c 100644 --- a/tests/utils/container_test_manager.py +++ b/tests/utils/container_test_manager.py @@ -381,13 +381,17 @@ def run_containers(self): for name, data in self.images.items(): refresh = data.get('refresh', True) rpc_port = data.get('rpc_port') + debug_port = data.get('debug_port') additional_packages = data.get('additional_packages', []) repository = data.get('repository') tag = data.get('tag') command = data.get('command', []) user = data.get('user') ports = data.get('ports', {}) - ports.update({f'{rpc_port}/tcp': ('127.0.0.1', rpc_port)}) + ports.update({ + f'{rpc_port}/tcp': ('127.0.0.1', rpc_port), + f'{debug_port}/tcp': ('127.0.0.1', debug_port) + }) entrypoint = data.get('entrypoint') environment = data.get('environment', {}) environment.update({ From 6a53056d92f554ee55ac85b786dbeff8b06a4854 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Tue, 16 Jul 2024 19:11:35 -0500 Subject: [PATCH 04/10] added shared volume for exported data in tests --- src/addons/send2ue/dependencies/unreal.py | 8 +-- tests/run_tests.py | 19 ++++---- tests/utils/container_test_manager.py | 59 ++++++++++++++++++++++- 3 files changed, 71 insertions(+), 15 deletions(-) diff --git a/src/addons/send2ue/dependencies/unreal.py b/src/addons/send2ue/dependencies/unreal.py index a56faf3a..16e0dabc 100644 --- a/src/addons/send2ue/dependencies/unreal.py +++ b/src/addons/send2ue/dependencies/unreal.py @@ -1081,7 +1081,7 @@ def get_enabled_plugins(): :returns: Returns a list of missing plugins if any. :rtype: list[str] """ - uproject_path = unreal.Paths.get_project_file_path() + uproject_path = unreal.Paths.get_project_file_path() or os.environ.get('UE_PROJECT_FILE') with open(uproject_path, 'r') as uproject: project_data = json.load(uproject) @@ -1098,8 +1098,10 @@ def get_project_settings_value(config_name, section_name, setting_name): :param str setting_name: The setting to query in the supplied section. :return: Value of the queried setting. """ - engine_config_dir = unreal.Paths.project_config_dir() - config_path = f'{engine_config_dir}{config_name}.ini' + uproject_path = unreal.Paths.get_project_file_path() or os.environ.get('UE_PROJECT_FILE') + config_path = os.path.join(os.path.dirname(uproject_path), 'Config', f'{config_name}.ini') # type: ignore + if not os.path.exists(config_path): + return None from configparser import ConfigParser # setting strict to False to bypass duplicate keys in the config file diff --git a/tests/run_tests.py b/tests/run_tests.py index 7aec939f..b04e3715 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -46,8 +46,6 @@ environment = { 'SEND2UE_DEV': '1', 'UE2RIGIFY_DEV': '1', - 'SEND2UE_TEMPLATE_FOLDER': '/tmp', - 'UE_PYTHONPATH': '$UE_PYTHONPATH:/tmp/blender_tools/scripts/resources/unreal', 'BLENDER_DEBUG_PORT': BLENDER_CONTAINER_DEBUG_PORT, 'UNREAL_DEBUG_PORT': UNREAL_CONTAINER_DEBUG_PORT, 'BLENDER_ADDONS': BLENDER_ADDONS, @@ -68,8 +66,8 @@ # add the test environment variable if specified if DOCKER_ENVIRONMENT: os.environ['RPC_TRACEBACK_FILE'] = os.path.join(HOST_TEST_FOLDER, 'data', 'traceback.log') + environment['TEST_ENVIRONMENT'] = '1' if DEBUGGING_ON: - environment['TEST_ENVIRONMENT'] = '1' environment['BLENDER_DEBUGGING_ON'] = 'yes' environment['UNREAL_DEBUGGING_ON'] = 'yes' environment['BLENDER_DEBUG_PORT'] = BLENDER_CONTAINER_DEBUG_PORT @@ -79,9 +77,11 @@ # this is the temp data location where send2ue export/imports data host_temp_folder = os.path.join(HOST_TEST_FOLDER, 'data') volumes = [ - f'{HOST_REPO_FOLDER}:{CONTAINER_REPO_FOLDER}', - f'{host_temp_folder}:/tmp/blender/send2ue/data' + f'{HOST_REPO_FOLDER}:{CONTAINER_REPO_FOLDER}' ] + shared_volumes = { + 'send2ue-export-data': '/tmp/blender/send2ue/data', + } logging.debug('Launching ContainerTestManager...') # instance the container test manager with the blender and unreal containers @@ -93,7 +93,7 @@ 'always_pull': ALWAYS_PULL, 'tag': f'blender-linux:{BLENDER_VERSION}', 'repository': 'ghcr.io/poly-hammer', - 'user': 'ubuntu', + 'user': 'root', 'rpc_port': BLENDER_PORT, 'debug_port': BLENDER_CONTAINER_DEBUG_PORT, 'environment': environment, @@ -117,20 +117,18 @@ 'volumes': volumes, 'tag': f'unreal-linux:{UNREAL_VERSION}', 'repository': 'ghcr.io/poly-hammer', - # 'tag': f'unreal-engine:dev-slim-{UNREAL_VERSION}', - # 'repository': 'ghcr.io/epicgames', 'user': 'ue4', 'command': [ '/home/ue4/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd', '-nullrhi', - '/tmp/unreal_projects/test01.uproject', + '/tmp/blender_tools/tests/test_files/unreal_projects/test01/test01.uproject', '-stdout', '-unattended', '-nopause', '-nosplash', '-noloadstartuppackages', '-log', - # '-ExecutePythonScript=/tmp/blender_tools/scripts/resources/unreal/init_unreal.py', + '-ExecutePythonScript=/tmp/blender_tools/scripts/resources/unreal/init_unreal.py', ], 'auth_config': { 'username': os.environ.get('GITHUB_USERNAME'), @@ -138,6 +136,7 @@ } } }, + shared_volumes=shared_volumes, test_case_folder=HOST_TEST_FOLDER, additional_python_paths=[HOST_REPO_FOLDER, CONTAINER_REPO_FOLDER], prefix_service_logs=True, diff --git a/tests/utils/container_test_manager.py b/tests/utils/container_test_manager.py index 5c51442c..ec398a5b 100644 --- a/tests/utils/container_test_manager.py +++ b/tests/utils/container_test_manager.py @@ -8,6 +8,7 @@ import unittest import docker import docker.errors +from docker.types import Mount import xmlrunner import shutil import inspect @@ -76,7 +77,8 @@ def __init__( poll_interval=1, additional_python_paths=None, exclusive_test_files=None, - exclusive_tests=None + exclusive_tests=None, + shared_volumes=None, ): self.test_case_container_folder = os.environ.get('CONTAINER_TEST_FOLDER', '/tmp/test_cases/') @@ -93,6 +95,7 @@ def __init__( self.exclusive_test_files = exclusive_test_files or [] self.additional_python_paths = additional_python_paths or [] self.exclusive_tests = exclusive_tests or [] + self.shared_volumes = shared_volumes or {} self._test_case_ids = [] self.logger = logging.getLogger(f'{self.__class__.__name__}') @@ -117,6 +120,24 @@ def __init__( self.should_continue = True self.test_suite = unittest.TestSuite() + def _get_shared_volumes(self): + volumes = [] + for volume_name in self.shared_volumes.keys(): + if volume_name not in [volume.name for volume in self.docker_client.volumes.list()]: # type: ignore + self.logger.info(f'Created volume "{volume_name}" ...') + volume = self.docker_client.volumes.create( + name=volume_name, + driver='local' + ) + self.logger.info(f'Created "{volume_name}"!') + volumes.append(volume) + else: + for volume in self.docker_client.volumes.list(): + if volume.name == volume_name: # type: ignore + volumes.append(volume) + break + return volumes + def _call_on_all_services(self, callable_instance, **kwargs): """ Helper method for calling a given method on all services. @@ -400,10 +421,30 @@ def run_containers(self): 'RPC_HOST': '0.0.0.0' }) + mounts = [] + volume_configs = {} volumes = data.get('volumes', []) volumes = volumes + self.get_package_volume_mounts(additional_packages) volumes.append(f'{self.test_case_folder}:{self.test_case_container_folder}') + + # make all volumes read write + for volume in volumes: + source, destination = volume.split(':/') + volume_configs[source] = {'bind': f'/{destination}', 'mode': 'rw'} + + # add the shared volumes + for volume in self._get_shared_volumes(): + container_path = self.shared_volumes.get(volume.name) + mounts.append( + Mount( + target=container_path, + source=volume.name, + type='volume', + read_only=False + ) + ) + image = f'{repository}/{tag}' if not repository: image = tag @@ -425,7 +466,8 @@ def run_containers(self): image=image, command=command, user=user, - volumes=volumes, + volumes=volume_configs, + mounts=mounts, ports=ports, entrypoint=entrypoint, environment=environment, @@ -450,6 +492,18 @@ def stop_containers(self): self.logger.debug(f'Removed container {container_name}') self.services_summary.update({container_name: {'stats': stats, 'inspect_data': inspect_data}}) + def remove_volumes(self): + """ + Removes all volumes. + """ + for volume_name in self.shared_volumes.keys(): + try: + volume = client.volumes.get(volume_name)# type: ignore + volume.remove() + self.logger.debug(f"Volume '{volume_name}' successfully deleted.") + except: + pass + def await_service(self, service_name, rpc_client): """ Awaits a response from the given service. @@ -611,4 +665,5 @@ def stop(self): for thread in self.log_streaming_threads: thread.join() + self.remove_volumes() self.log_summary() From 57c6c2622f97026a88131124f43a3d579c4522f7 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Wed, 17 Jul 2024 06:33:43 -0500 Subject: [PATCH 05/10] Added UE_PROJECT_FILE env to tests --- docs/contributing/development.md | 2 +- tests/run_tests.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/contributing/development.md b/docs/contributing/development.md index 33dee655..33f01cf0 100644 --- a/docs/contributing/development.md +++ b/docs/contributing/development.md @@ -33,7 +33,7 @@ git checkout some-task-branch ``` ## VSCode -The repo contains the tasks, launch actions, and settings for for developing with vscode. To get setup do the following: +The repo contains the tasks, launch actions, and settings for developing with vscode. To get setup do the following: ### Setup 1. Install the VSCode profile under `scripts/resources/profiles/dev.code-profile`. You can do this by typing `> Profile Import Profile` in the command pallette. This will give you all the vscode extensions you will need. diff --git a/tests/run_tests.py b/tests/run_tests.py index b04e3715..154569da 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -46,6 +46,7 @@ environment = { 'SEND2UE_DEV': '1', 'UE2RIGIFY_DEV': '1', + 'UE_PROJECT_FILE': '/tmp/blender_tools/tests/test_files/unreal_projects/test01/test01.uproject', 'BLENDER_DEBUG_PORT': BLENDER_CONTAINER_DEBUG_PORT, 'UNREAL_DEBUG_PORT': UNREAL_CONTAINER_DEBUG_PORT, 'BLENDER_ADDONS': BLENDER_ADDONS, From fb7c054c737adb0802570de3ca1dccc7a7089329 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Thu, 18 Jul 2024 06:24:09 -0500 Subject: [PATCH 06/10] updated CI workflow --- .github/workflows/re-use-tests.yml | 1 + .github/workflows/tests.yml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/re-use-tests.yml b/.github/workflows/re-use-tests.yml index 6cee1e8e..05f2c907 100644 --- a/.github/workflows/re-use-tests.yml +++ b/.github/workflows/re-use-tests.yml @@ -53,6 +53,7 @@ jobs: export DOCKER_ENVIRONMENT=yes export UNREAL_VERSION=${{ inputs.unreal-version }} export BLENDER_VERSION=${{ inputs.blender-version }} + export ALWAYS_PULL=1 echo $GITHUB_TOKEN | docker login ghcr.io -u ${{ secrets.GH_USER }} --password-stdin diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b0437d82..6ab5379e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,7 +22,7 @@ permissions: jobs: test-lts: name: Test LTS - uses: poly-hammer/BlenderTools/.github/workflows/re-use-tests.yml@main + uses: poly-hammer/BlenderTools/.github/workflows/re-use-tests.yml@restructure-addons-for-better-dev-setup secrets: inherit with: title: Test LTS @@ -32,7 +32,7 @@ jobs: test-latest: needs: test-lts name: Test Latest - uses: poly-hammer/BlenderTools/.github/workflows/re-use-tests.yml@main + uses: poly-hammer/BlenderTools/.github/workflows/re-use-tests.yml@restructure-addons-for-better-dev-setup secrets: inherit with: title: Test Latest From 3bb1ef520e308b6a2dcfdcb529e08376ed820410 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Fri, 19 Jul 2024 05:33:32 -0500 Subject: [PATCH 07/10] updated workflow --- .github/workflows/re-use-tests.yml | 3 ++- tests/run_tests.py | 6 +++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/re-use-tests.yml b/.github/workflows/re-use-tests.yml index 05f2c907..528a0747 100644 --- a/.github/workflows/re-use-tests.yml +++ b/.github/workflows/re-use-tests.yml @@ -53,9 +53,10 @@ jobs: export DOCKER_ENVIRONMENT=yes export UNREAL_VERSION=${{ inputs.unreal-version }} export BLENDER_VERSION=${{ inputs.blender-version }} - export ALWAYS_PULL=1 echo $GITHUB_TOKEN | docker login ghcr.io -u ${{ secrets.GH_USER }} --password-stdin + docker pull ghcr.io/poly-hammer/blender-linux:$BLENDER_VERSION + docker pull ghcr.io/poly-hammer/unreal-linux:$UNREAL_VERSION cd ./tests python run_tests.py diff --git a/tests/run_tests.py b/tests/run_tests.py index 154569da..e1393e7a 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -107,7 +107,11 @@ '1', '--python', '/tmp/blender_tools/scripts/resources/blender/startup.py', - ] + ], + 'auth_config': { + 'username': os.environ.get('GITHUB_USERNAME'), + 'password': os.environ.get('GITHUB_TOKEN') + } }, 'unreal': { 'refresh': False, From 820058bbbceecb40c705a3d0ebab05216e7dec9d Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Fri, 19 Jul 2024 07:45:00 -0500 Subject: [PATCH 08/10] reverted env changes --- src/addons/send2ue/core/settings.py | 4 ---- src/addons/send2ue/dependencies/unreal.py | 4 ++-- tests/run_tests.py | 5 ++--- 3 files changed, 4 insertions(+), 9 deletions(-) diff --git a/src/addons/send2ue/core/settings.py b/src/addons/send2ue/core/settings.py index 01020690..896a97f5 100644 --- a/src/addons/send2ue/core/settings.py +++ b/src/addons/send2ue/core/settings.py @@ -124,10 +124,6 @@ def get_template_folder(): :return str: The file path to the temp folder. """ - template_folder = os.environ.get('SEND2UE_TEMPLATE_FOLDER') - if template_folder: - return template_folder - return os.path.join( tempfile.gettempdir(), ToolInfo.APP.value, diff --git a/src/addons/send2ue/dependencies/unreal.py b/src/addons/send2ue/dependencies/unreal.py index 16e0dabc..4dd646f2 100644 --- a/src/addons/send2ue/dependencies/unreal.py +++ b/src/addons/send2ue/dependencies/unreal.py @@ -1081,7 +1081,7 @@ def get_enabled_plugins(): :returns: Returns a list of missing plugins if any. :rtype: list[str] """ - uproject_path = unreal.Paths.get_project_file_path() or os.environ.get('UE_PROJECT_FILE') + uproject_path = unreal.Paths.get_project_file_path() with open(uproject_path, 'r') as uproject: project_data = json.load(uproject) @@ -1098,7 +1098,7 @@ def get_project_settings_value(config_name, section_name, setting_name): :param str setting_name: The setting to query in the supplied section. :return: Value of the queried setting. """ - uproject_path = unreal.Paths.get_project_file_path() or os.environ.get('UE_PROJECT_FILE') + uproject_path = unreal.Paths.get_project_file_path() config_path = os.path.join(os.path.dirname(uproject_path), 'Config', f'{config_name}.ini') # type: ignore if not os.path.exists(config_path): return None diff --git a/tests/run_tests.py b/tests/run_tests.py index e1393e7a..41844c6b 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -46,7 +46,6 @@ environment = { 'SEND2UE_DEV': '1', 'UE2RIGIFY_DEV': '1', - 'UE_PROJECT_FILE': '/tmp/blender_tools/tests/test_files/unreal_projects/test01/test01.uproject', 'BLENDER_DEBUG_PORT': BLENDER_CONTAINER_DEBUG_PORT, 'UNREAL_DEBUG_PORT': UNREAL_CONTAINER_DEBUG_PORT, 'BLENDER_ADDONS': BLENDER_ADDONS, @@ -125,13 +124,13 @@ 'user': 'ue4', 'command': [ '/home/ue4/UnrealEngine/Engine/Binaries/Linux/UnrealEditor-Cmd', - '-nullrhi', '/tmp/blender_tools/tests/test_files/unreal_projects/test01/test01.uproject', '-stdout', '-unattended', '-nopause', + '-nullrhi', '-nosplash', - '-noloadstartuppackages', + '-noloadstartuppackages' '-log', '-ExecutePythonScript=/tmp/blender_tools/scripts/resources/unreal/init_unreal.py', ], From 548ffeab316a17a2382f286fa998ad6ab47edfa3 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Fri, 19 Jul 2024 08:23:41 -0500 Subject: [PATCH 09/10] deferred instancing rpc client --- src/addons/send2ue/dependencies/unreal.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addons/send2ue/dependencies/unreal.py b/src/addons/send2ue/dependencies/unreal.py index 4dd646f2..f41b0505 100644 --- a/src/addons/send2ue/dependencies/unreal.py +++ b/src/addons/send2ue/dependencies/unreal.py @@ -31,8 +31,6 @@ default_imports=['import unreal'], remap_pairs=REMAP_PAIRS, ) - -rpc_client = rpc.client.RPCClient(port=UNREAL_PORT) unreal_response = '' @@ -170,6 +168,7 @@ def is_connected(): Checks the rpc server connection """ try: + rpc_client = rpc.client.RPCClient(port=UNREAL_PORT) return rpc_client.proxy.is_running() except (RemoteDisconnected, ConnectionRefusedError, ProtocolError): return False @@ -179,6 +178,7 @@ def set_rpc_env(key, value): """ Sets an env value on the unreal RPC server. """ + rpc_client = rpc.client.RPCClient(port=UNREAL_PORT) rpc_client.proxy.set_env(key, value) From 9b6e710a87e37e1ba5ebec3d7274631d1840f2f5 Mon Sep 17 00:00:00 2001 From: Jack Yao Date: Fri, 19 Jul 2024 09:35:42 -0500 Subject: [PATCH 10/10] updated debug image --- docs/contributing/images/development/2.2.png | Bin 21181 -> 25031 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/docs/contributing/images/development/2.2.png b/docs/contributing/images/development/2.2.png index b0f0770709480018ce63cc4a9920bbcefe34d9b5..7f77c8b91085e445d50b195fc867899fb16f4e46 100644 GIT binary patch literal 25031 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